This commit is contained in:
Alexandre 2025-01-31 14:01:40 +01:00
parent ca29c1c439
commit f23e5c3cab
4 changed files with 197 additions and 16 deletions

View File

@ -1,8 +1,15 @@
# GoSh! # GoSh!
A Shell made in Go, for fun A Shell made in Go, for fun
## Features ## Features
At the moment, there isn't much features. - Display Working Directory
I'm planning to add more features later (like syntax hilighting, a history file and a RC file, etc...). - History file wich you can browse for past commands
- Some colors
- Config file
I'm planning to add more features later (like syntax hilighting, etc...).
I'm also learning Go by doing this project. I'm also learning Go by doing this project.
You can expect breaking changes (or code), the problem has a workaround in the commit message. You can expect breaking changes (or code), the problem has a workaround in the commit message.
If not, the issue will be fixed soon (in case of a typo for example) If not, the issue will be fixed soon (in case of a typo for example)
## Installation
You can grab the binaries for your system and architecture, or build it yourself
To build it, clone the repository, cd into it and run go

21
go.mod
View File

@ -4,5 +4,24 @@ go 1.23.5
require ( require (
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

54
go.sum
View File

@ -2,5 +2,59 @@ github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwys
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

119
main.go
View File

@ -4,12 +4,26 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"github.com/chzyer/readline" "github.com/chzyer/readline"
"github.com/spf13/viper"
) )
// Structure pour stocker la configuration
type Config struct {
Prompt string `mapstructure:"prompt"`
Color string `mapstructure:"color"`
HistorySize int `mapstructure:"history_size"`
}
var config Config
func main() { func main() {
// Chargement de la configuration
loadConfig()
// Chargement de l'historique au démarrage // Chargement de l'historique au démarrage
homeDir, _ := os.UserHomeDir() homeDir, _ := os.UserHomeDir()
historyFile := homeDir + "/.gosh_history" historyFile := homeDir + "/.gosh_history"
@ -18,6 +32,7 @@ func main() {
rl, err := readline.NewEx(&readline.Config{ rl, err := readline.NewEx(&readline.Config{
Prompt: getPrompt(), // Utilise une fonction pour générer le prompt dynamiquement Prompt: getPrompt(), // Utilise une fonction pour générer le prompt dynamiquement
HistoryFile: historyFile, // Permet de sauvegarder et charger l'historique HistoryFile: historyFile, // Permet de sauvegarder et charger l'historique
HistoryLimit: config.HistorySize,
AutoComplete: nil, // Peut être amélioré avec l'autocomplétion AutoComplete: nil, // Peut être amélioré avec l'autocomplétion
}) })
if err != nil { if err != nil {
@ -49,6 +64,48 @@ func main() {
} }
} }
// Charger la configuration depuis un fichier
func loadConfig() {
homeDir, _ := os.UserHomeDir()
configPath := homeDir + "/.config/gosh/.gosh_config.toml"
fmt.Println("Chemin du fichier de configuration:", configPath) // Log pour déboguer
viper.SetConfigFile(configPath)
// Valeurs par défaut
viper.SetDefault("prompt", "[{dir}] > ")
viper.SetDefault("color", "blue")
viper.SetDefault("history_size", 1000)
// Lire le fichier de configuration
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Si le fichier n'existe pas, le créer avec les valeurs par défaut
fmt.Println("Création du fichier de configuration avec les valeurs par défaut...")
if err := viper.WriteConfigAs(configPath); err != nil {
fmt.Fprintln(os.Stderr, "Erreur lors de la création du fichier de configuration:", err)
} else {
fmt.Println("Fichier de configuration créé avec succès:", configPath)
}
} else {
// Autre erreur de lecture du fichier
fmt.Fprintln(os.Stderr, "Erreur de configuration:", err)
fmt.Println("Utilisation des valeurs par défaut.")
}
}
// Charger la configuration dans la structure Config
if err := viper.Unmarshal(&config); err != nil {
fmt.Fprintln(os.Stderr, "Erreur de chargement de la configuration:", err)
fmt.Println("Utilisation des valeurs par défaut.")
}
// Validation des valeurs
if config.HistorySize <= 0 {
fmt.Fprintln(os.Stderr, "Taille de l'historique invalide. Utilisation de la valeur par défaut (1000).")
config.HistorySize = 1000
}
}
// Fonction pour générer le prompt avec le répertoire courant // Fonction pour générer le prompt avec le répertoire courant
func getPrompt() string { func getPrompt() string {
wd, err := os.Getwd() wd, err := os.Getwd()
@ -56,21 +113,29 @@ func getPrompt() string {
wd = "?" wd = "?"
} }
// Récupérer le répertoire home de l'utilisateur
homeDir, err := os.UserHomeDir()
if err != nil {
homeDir = "" // Si on ne peut pas obtenir le home, on ne fait pas de remplacement
}
// Remplacer le chemin du home par "~" // Remplacer le chemin du home par "~"
homeDir, _ := os.UserHomeDir()
if homeDir != "" && strings.HasPrefix(wd, homeDir) { if homeDir != "" && strings.HasPrefix(wd, homeDir) {
wd = "~" + strings.TrimPrefix(wd, homeDir) wd = "~" + strings.TrimPrefix(wd, homeDir)
} }
// Séquence ANSI pour le texte en bleu (optionnel) // Utiliser le prompt défini dans la configuration
prompt := strings.Replace(config.Prompt, "{dir}", wd, -1)
// Ajouter de la couleur si configuré
if config.Color == "blue" {
blue := "\033[34m" blue := "\033[34m"
reset := "\033[0m" reset := "\033[0m"
return fmt.Sprintf("%s[%s]%s > ", blue, wd, reset) prompt = blue + prompt + reset
}
if config.Color == "green" {
green := "\033[32m"
reset := "\033[0m"
prompt = green + prompt + reset
}
return prompt
} }
func execInput(input string) error { func execInput(input string) error {
@ -91,8 +156,13 @@ func execInput(input string) error {
case "exit": case "exit":
os.Exit(0) os.Exit(0)
case "version": case "version":
fmt.Println("GoShell Version 2.0.0") fmt.Println("GoShell Version 1.0.0")
return nil return nil
case "set":
if len(args) < 3 {
return fmt.Errorf("Usage: set <key> <value>")
}
return setConfig(args[1], strings.Join(args[2:], " "))
} }
// Exécuter la commande système // Exécuter la commande système
@ -101,3 +171,34 @@ func execInput(input string) error {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
return cmd.Run() return cmd.Run()
} }
// Fonction pour modifier la configuration à la volée
func setConfig(key, value string) error {
switch key {
case "prompt":
viper.Set("prompt", value)
case "color":
viper.Set("color", value)
case "history_size":
intValue, err := strconv.Atoi(value)
if err != nil || intValue <= 0 {
return fmt.Errorf("history_size doit être un entier positif")
}
viper.Set("history_size", intValue)
default:
return fmt.Errorf("Clé de configuration inconnue: %s", key)
}
// Sauvegarder la configuration dans le fichier
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("Erreur lors de la sauvegarde de la configuration: %v", err)
}
// Recharger la configuration
if err := viper.Unmarshal(&config); err != nil {
return fmt.Errorf("Erreur lors du rechargement de la configuration: %v", err)
}
fmt.Printf("Configuration mise à jour: %s = %s\n", key, value)
return nil
}