Compare commits

..

21 Commits
v2.1.0 ... main

Author SHA1 Message Date
Alexandre1a
169929f577
Added a Todo 2025-04-14 15:19:33 +02:00
Alexandre1a
f25176ea2b
Added some features 2025-03-30 22:40:27 +02:00
Alexandre1a
53c5cba02a
Added the ability to create the config folder 2025-03-30 22:02:39 +02:00
Alexandre1a
b5fb8a86e9
Delete the key 2025-03-30 17:16:06 +02:00
Alexandre1a
bd17ee4cba
Changed mac to darwin 2025-03-30 13:26:13 +02:00
Alexandre1a
b9c24fe445
Added a file for my package manager 2025-03-30 12:09:13 +02:00
Alexandre1a
1aa84809a6
Update README.md
Fixed indentation
2025-03-19 15:37:50 +01:00
Alexandre1a
af73e3e4de
Update README.md
Fixed a typo
2025-03-19 15:36:31 +01:00
Alexandre1a
ed30bd50d0
Update main.go
Changed some defaults
2025-03-19 15:33:16 +01:00
Alexandre1a
47761fae5d
Update go.yml
fixed the changelog not being displayed
2025-03-19 15:28:00 +01:00
Alexandre1a
1f19d9b602
Update go.yml
Deleted build.
Re added AMD64 into muliarch
2025-03-19 15:25:11 +01:00
Alexandre1a
9a9d0979de
Update go.yml
Removed the Linux64 build from multiarch as it's already built in the previous step
2025-03-19 15:20:39 +01:00
Alexandre1a
8be9ac9e82
Update go.yml
Fixed the indentation, and added a release overview
2025-03-19 15:18:54 +01:00
Alexandre
c882c891d4 Removed the Windows Build because tests proved that Windows environement is really different 2025-02-03 14:58:34 +01:00
Alexandre
5c2adeb8ec Fixed a problem with 'ghosting' in the shell 2025-02-03 14:50:48 +01:00
Alexandre
b59fb4bf51 Changed the README to tell all avaiable colors 2025-02-03 14:45:13 +01:00
Alexandre
84b59ca9be Added prompt colors 2025-02-03 14:43:28 +01:00
Alexandre
73b0429195 Merge branch 'main' of github.com:Alexandre1a/GoSH
Again, I forgot
2025-02-03 14:03:34 +01:00
Alexandre
2b76386641 Added the Windows version for testing 2025-02-03 14:03:11 +01:00
Alexandre1a
50d280ec5f
Update README.md 2025-02-03 09:57:55 +01:00
Alexandre
9d406f8dfc Finally fixed the bug where the config won't be created 2025-02-03 09:54:58 +01:00
5 changed files with 481 additions and 88 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -8,22 +8,6 @@ on:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23.5"
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
build-multiarch:
runs-on: ubuntu-latest
steps:
@ -32,22 +16,25 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23.5"
go-version: "1.24.1"
- name: Build binaries for multiple architectures
run: |
mkdir -p dist
# Linux
GOOS=linux GOARCH=amd64 go build -o dist/gosh-linux-amd64
GOOS=linux GOARCH=arm64 go build -o dist/gosh-linux-arm64
# macOS
GOOS=darwin GOARCH=amd64 go build -o dist/gosh-mac-amd64
GOOS=darwin GOARCH=arm64 go build -o dist/gosh-mac-arm64
GOOS=darwin GOARCH=amd64 go build -o dist/gosh-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o dist/gosh-darwin-arm64
ls -lh dist/
# Windows
# GOOS=windows GOARCH=amd64 go build -o dist/gosh-windows-amd64.exe
# GOOS=windows GOARCH=arm64 go build -o dist/gosh-windows-arm64.exe
- name: Upload binaries as artifacts
uses: actions/upload-artifact@v4
with:
@ -67,10 +54,46 @@ jobs:
name: gosh-binaries
path: dist/
- name: Generate Changelog
id: changelog
run: |
CURRENT_TAG=${GITHUB_REF#refs/tags/}
# Get all tags sorted by version (descending)
ALL_TAGS=$(git tag --sort=-version:refname)
PREVIOUS_TAG=""
found_current=0
# Find the tag immediately before the current one
for tag in $ALL_TAGS; do
if [ "$found_current" -eq 1 ]; then
PREVIOUS_TAG=$tag
break
fi
if [ "$tag" == "$CURRENT_TAG" ]; then
found_current=1
fi
done
# Fallback to initial commit if no previous tag
if [ -z "$PREVIOUS_TAG" ]; then
PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD)
fi
# Generate changelog
CHANGELOG=$(git log --pretty=format:"- %s (%h)" $PREVIOUS_TAG..$CURRENT_TAG)
if [ -z "$CHANGELOG" ]; then
CHANGELOG="No changes since previous release."
fi
# Output for GitHub Action
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: dist/*
body: |
**Changelog**
${{ steps.changelog.outputs.CHANGELOG }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

27
.goblin.yaml Normal file
View File

@ -0,0 +1,27 @@
name: gosh
version: 0.1.0
language: go
go_version: "1.20+"
build:
commands:
- go build -o gosh .
install:
files:
- src: gosh
dest: $BIN_DIR/gosh
permissions:
- path: $BIN_DIR/gosh
mode: 0755
dependencies:
build:
- git
- golang >= 1.20
runtime: []
tests:
- command: ./gosh --version
output: "gosh 0.1.0"

View File

@ -19,16 +19,26 @@ To test the shell and see if it suits you.
If everything works, then move the binary to a place inside your path.
To directly install it with the Go toolchain, just use
`go intall`
`go install`
This will build and place the binary inside your `$HOME/go/bin` folder.
Add this folder to your path and you are good to go !
## Usage
To use the program, just invoke it with `GoSH`
If you see a message about a config file, create `~/.config/gosh/gosh_config.toml` and populate it with the defaults written inside this repo -> [here](/defaults.toml).
~~If you see a message about a config file, create `~/.config/gosh/gosh_config.toml` and populate it with the defaults written inside this repo -> [here](/defaults.toml).~~
To change config parameter on the fly, use the `set` builtin.
Currently, `set` has a limited amount of configuration options and need to have a valid config file to write to.
To change the color of the prompt use `set color <color>`
All the avalable colors :
- black
- red
- purple
- cyan
- white
- green
- yellow
- blue
You can change the history size with `set history_size <int>`
You can change the prompt with `set prompt <promp>`
Here is some exemple of prompts :
@ -41,5 +51,8 @@ You can use all "console colors", listed [here](https://gist.github.com/kamito/7
Currently there is a number of known or unkwown issues.
We can list the fact that interactive programs, like SSH or VIM work partialy.
The config has to be manualy created and populated.
Also pipes aren't supported yet, so no ls | grep "thing"
Also pipes aren't supported yet, so no `ls | grep "thing"`
PTY currently don't support signals like 'Ctrl+C' so don't use vim, nano nor nvim for exemple.
## ToDo
- Tab completion (for cd)

456
main.go
View File

@ -5,8 +5,12 @@ import (
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/chzyer/readline"
"github.com/creack/pty"
@ -16,27 +20,58 @@ import (
// Structure pour stocker la configuration
type Config struct {
Prompt string `mapstructure:"prompt"`
Color string `mapstructure:"color"`
HistorySize int `mapstructure:"history_size"`
Prompt string `mapstructure:"prompt"`
Color string `mapstructure:"color"`
HistorySize int `mapstructure:"history_size"`
Aliases map[string]string `mapstructure:"aliases"`
}
var config Config
// Constantes pour la version et le nom du shell
const (
VERSION = "2.3.0"
SHELL_NAME = "GoShell"
)
var (
config Config
// Map des couleurs ANSI
colors = map[string]string{
"black": "\033[30m",
"red": "\033[31m",
"green": "\033[32m",
"yellow": "\033[33m",
"blue": "\033[34m",
"purple": "\033[35m",
"cyan": "\033[36m",
"white": "\033[37m",
"reset": "\033[0m",
// Ajout de couleurs supplémentaires
"bold": "\033[1m",
"underline": "\033[4m",
}
)
func main() {
// Gestion des signaux
setupSignalHandling()
// Chargement de la configuration
loadConfig()
// Chargement de l'historique au démarrage
homeDir, _ := os.UserHomeDir()
historyFile := homeDir + "/.gosh_history"
historyFile := filepath.Join(homeDir, ".gosh_history")
// Configuration du shell interactif
rl, err := readline.NewEx(&readline.Config{
Prompt: getPrompt(), // Utilise une fonction pour générer le prompt dynamiquement
HistoryFile: historyFile, // Permet de sauvegarder et charger l'historique
HistoryLimit: config.HistorySize,
AutoComplete: nil, // Peut être amélioré avec l'autocomplétion
Prompt: getPrompt(),
HistoryFile: historyFile,
HistoryLimit: config.HistorySize,
AutoComplete: newCompleter(),
InterruptPrompt: "^C",
EOFPrompt: "exit",
HistorySearchFold: true,
FuncFilterInputRune: nil,
})
if err != nil {
fmt.Fprintln(os.Stderr, "Erreur readline:", err)
@ -44,69 +79,135 @@ func main() {
}
defer rl.Close()
fmt.Printf("Bienvenue dans %s version %s\nTapez 'help' pour afficher l'aide.\n\n", SHELL_NAME, VERSION)
for {
// Mettre à jour le prompt avec le répertoire courant
rl.SetPrompt(getPrompt())
// Lecture de l'entrée utilisateur avec édition et historique
input, err := rl.Readline()
if err != nil { // EOF ou Ctrl+D
if err != nil {
if err == readline.ErrInterrupt {
continue // Ignorer Ctrl+C
} else if err == io.EOF {
break // Ctrl+D pour quitter
}
fmt.Fprintln(os.Stderr, "Erreur de lecture:", err)
break
}
// Suppression des espaces inutiles
input = strings.TrimSpace(input)
if input == "" {
continue
}
// Exécute la commande
startTime := time.Now()
if err := execInput(input); err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "Erreur:", err)
}
duration := time.Since(startTime)
// Afficher le temps d'exécution pour les commandes qui prennent plus d'une seconde
if duration > time.Second {
fmt.Printf("Temps d'exécution: %s\n", duration.Round(time.Millisecond))
}
}
}
// Mise en place de la gestion des signaux
func setupSignalHandling() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
fmt.Println("\nAu revoir!")
os.Exit(0)
}()
}
// Implémentation simple de l'auto-complétion
func newCompleter() *readline.PrefixCompleter {
// Liste des commandes internes
internalCommands := []readline.PrefixCompleterInterface{
readline.PcItem("cd"),
readline.PcItem("exit"),
readline.PcItem("help"),
readline.PcItem("version"),
readline.PcItem("set",
readline.PcItem("prompt"),
readline.PcItem("color",
readline.PcItem("black"),
readline.PcItem("red"),
readline.PcItem("green"),
readline.PcItem("yellow"),
readline.PcItem("blue"),
readline.PcItem("purple"),
readline.PcItem("cyan"),
readline.PcItem("white"),
readline.PcItem("bold"),
readline.PcItem("underline"),
),
readline.PcItem("history_size"),
),
readline.PcItem("alias"),
readline.PcItem("unalias"),
readline.PcItem("aliases"),
}
return readline.NewPrefixCompleter(internalCommands...)
}
// 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)
viper.SetConfigFile(configPath)
configPath := filepath.Join(homeDir, ".config", "gosh")
if err := os.MkdirAll(configPath, 0755); err != nil {
fmt.Fprintln(os.Stderr, "Erreur lors de la création du dossier de configuration:", err)
}
viper.AddConfigPath(configPath)
viper.SetConfigName("gosh_config")
viper.SetConfigType("toml")
// Valeurs par défaut
viper.SetDefault("prompt", "[{dir}] > ")
viper.SetDefault("color", "blue")
viper.SetDefault("prompt", "[{dir}] $ ")
viper.SetDefault("color", "green")
viper.SetDefault("history_size", 1000)
viper.SetDefault("aliases", map[string]string{
"ll": "ls -la",
"la": "ls -a",
})
// 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 {
configFilePath := filepath.Join(configPath, "gosh_config.toml")
if err := viper.WriteConfigAs(configFilePath); 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)
fmt.Println("Fichier de configuration créé avec succès:", configFilePath)
}
} 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
}
// Initialiser la map des alias si elle est nil
if config.Aliases == nil {
config.Aliases = make(map[string]string)
}
}
// Fonction pour générer le prompt avec le répertoire courant
@ -116,89 +217,241 @@ func getPrompt() string {
wd = "?"
}
// Remplacer le chemin du home par "~"
homeDir, _ := os.UserHomeDir()
if homeDir != "" && strings.HasPrefix(wd, homeDir) {
wd = "~" + strings.TrimPrefix(wd, homeDir)
}
// Utiliser le prompt défini dans la configuration
// Remplacer des variables dans le prompt
prompt := strings.Replace(config.Prompt, "{dir}", wd, -1)
prompt = strings.Replace(prompt, "{time}", time.Now().Format("15:04:05"), -1)
prompt = strings.Replace(prompt, "{date}", time.Now().Format("2006-01-02"), -1)
prompt = strings.Replace(prompt, "{shell}", SHELL_NAME, -1)
prompt = strings.Replace(prompt, "{version}", VERSION, -1)
// Ajouter de la couleur si configuré
if config.Color == "blue" {
blue := "\033[34m"
reset := "\033[0m"
prompt = blue + prompt + reset
// Appliquer la couleur si elle existe dans la map
if colorCode, exists := colors[config.Color]; exists {
prompt = colorCode + prompt + colors["reset"]
}
return prompt
}
func execInput(input string) error {
input = strings.TrimSuffix(input, "\n")
// Fonction pour déterminer si une commande est interactive
func isInteractiveCommand(cmd string) bool {
interactiveCommands := map[string]bool{
"vim": true,
"nano": true,
"emacs": true,
"ssh": true,
"top": true,
"htop": true,
"less": true,
"more": true,
"man": true,
"vi": true,
"pico": true,
}
return interactiveCommands[cmd]
}
// Remplacer les alias par leurs commandes correspondantes
func replaceAliases(input string) string {
args, err := shlex.Split(input)
if err != nil || len(args) == 0 {
return input
}
// Si le premier mot est un alias, le remplacer
if aliasCmd, exists := config.Aliases[args[0]]; exists {
// Si l'alias contient des arguments, les combiner avec ceux de la commande
aliasArgs, err := shlex.Split(aliasCmd)
if err != nil {
return input
}
if len(args) > 1 {
// Joindre les arguments de l'alias avec ceux de la commande
return strings.Join(append(aliasArgs, args[1:]...), " ")
}
return aliasCmd
}
return input
}
func execInput(input string) error {
// Remplacer les alias
expandedInput := replaceAliases(input)
if expandedInput != input {
fmt.Printf("Alias expanded: %s\n", expandedInput)
input = expandedInput
}
// Diviser la commande en arguments en respectant les guillemets
args, err := shlex.Split(input)
if err != nil {
return fmt.Errorf("Erreur lors de la division des arguments: %v", err)
return fmt.Errorf("erreur lors de la division des arguments: %v", err)
}
if len(args) == 0 {
return nil
}
// Gérer les commandes intégrées
switch args[0] {
case "cd":
// Gestion du changement de répertoire
if len(args) < 2 || args[1] == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("Impossible de trouver le home")
return fmt.Errorf("impossible de trouver le home")
}
return os.Chdir(homeDir)
}
return os.Chdir(args[1])
case "exit":
os.Exit(0)
case "version":
fmt.Println("GoShell Version 2.1.2")
// Expansion du tilde en chemin complet du répertoire utilisateur
if args[1] == "~" || strings.HasPrefix(args[1], "~/") {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("impossible de trouver le home: %v", err)
}
args[1] = strings.Replace(args[1], "~", homeDir, 1)
}
if err := os.Chdir(args[1]); err != nil {
return fmt.Errorf("cd: %v", err)
}
return nil
case "exit":
fmt.Println("Au revoir!")
os.Exit(0)
case "version":
fmt.Printf("%s Version %s\n", SHELL_NAME, VERSION)
return nil
case "help":
printHelp()
return nil
case "set":
if len(args) < 2 {
// Afficher la configuration actuelle
fmt.Printf("Configuration actuelle:\n")
fmt.Printf(" prompt = %s\n", config.Prompt)
fmt.Printf(" color = %s\n", config.Color)
fmt.Printf(" history_size = %d\n", config.HistorySize)
return nil
}
if len(args) < 3 {
return fmt.Errorf("Usage: set <key> <value>")
return fmt.Errorf("usage: set <key> <value>")
}
return setConfig(args[1], strings.Join(args[2:], " "))
case "alias":
if len(args) == 1 {
// Afficher tous les alias
return listAliases()
}
if len(args) < 3 {
return fmt.Errorf("usage: alias <name> <command>")
}
return addAlias(args[1], strings.Join(args[2:], " "))
case "unalias":
if len(args) != 2 {
return fmt.Errorf("usage: unalias <name>")
}
return removeAlias(args[1])
case "aliases":
return listAliases()
}
// Exécuter la commande système dans un PTY
// Exécution des commandes externes
cmd := exec.Command(args[0], args[1:]...)
ptmx, err := pty.Start(cmd)
if err != nil {
return fmt.Errorf("Erreur lors du démarrage du PTY: %v", err)
if isInteractiveCommand(args[0]) {
// Utiliser un PTY pour les commandes interactives
ptmx, err := pty.Start(cmd)
if err != nil {
return fmt.Errorf("erreur lors du démarrage du PTY: %v", err)
}
defer ptmx.Close()
// Gérer le redimensionnement du terminal
go func() {
// TODO: Implémenter la gestion du redimensionnement
}()
// Rediriger stdin et stdout
go func() {
io.Copy(ptmx, os.Stdin)
}()
io.Copy(os.Stdout, ptmx)
} else {
// Exécuter directement les commandes non interactives
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
// Démarrer la commande
if err := cmd.Start(); err != nil {
return fmt.Errorf("erreur lors du démarrage de la commande: %v", err)
}
}
defer ptmx.Close()
// Rediriger les entrées/sorties entre le terminal parent et le PTY
go func() {
io.Copy(ptmx, os.Stdin) // Rediriger stdin vers le PTY
}()
io.Copy(os.Stdout, ptmx) // Rediriger stdout du PTY vers le terminal
// Attendre la fin de la commande
// Attendre la fin du processus
if err := cmd.Wait(); err != nil {
return fmt.Errorf("Erreur lors de l'exécution de la commande: %v", err)
// Vérifier si l'erreur est due à un code de sortie non nul
if exitErr, ok := err.(*exec.ExitError); ok {
return fmt.Errorf("la commande a échoué avec le code: %d", exitErr.ExitCode())
}
return fmt.Errorf("erreur lors de l'exécution de la commande: %v", err)
}
return nil
}
// Afficher l'aide du shell
func printHelp() {
fmt.Printf("%s v%s - Un shell léger écrit en Go\n\n", SHELL_NAME, VERSION)
fmt.Println("Commandes internes:")
fmt.Println(" cd [dir] - Changer de répertoire")
fmt.Println(" exit - Quitter le shell")
fmt.Println(" version - Afficher la version du shell")
fmt.Println(" help - Afficher cette aide")
fmt.Println(" set [key] [value] - Afficher ou modifier la configuration")
fmt.Println(" alias <nom> <cmd> - Créer un alias pour une commande")
fmt.Println(" unalias <nom> - Supprimer un alias")
fmt.Println(" aliases - Lister tous les alias")
fmt.Println()
fmt.Println("Variables de prompt:")
fmt.Println(" {dir} - Répertoire courant")
fmt.Println(" {time} - Heure actuelle")
fmt.Println(" {date} - Date actuelle")
fmt.Println(" {shell} - Nom du shell")
fmt.Println(" {version} - Version du shell")
fmt.Println()
fmt.Println("Couleurs disponibles:")
fmt.Print(" ")
for color := range colors {
if color != "reset" {
fmt.Printf("%s ", color)
}
}
fmt.Println()
}
// Fonction pour modifier la configuration à la volée
func setConfig(key, value string) error {
switch key {
case "prompt":
viper.Set("prompt", value)
case "color":
if _, exists := colors[value]; !exists {
return fmt.Errorf("couleur inconnue: %s. Couleurs disponibles: %v", value, getAvailableColors())
}
viper.Set("color", value)
case "history_size":
intValue, err := strconv.Atoi(value)
@ -207,19 +460,96 @@ func setConfig(key, value string) error {
}
viper.Set("history_size", intValue)
default:
return fmt.Errorf("Clé de configuration inconnue: %s", key)
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)
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)
return fmt.Errorf("erreur lors du rechargement de la configuration: %v", err)
}
fmt.Printf("Configuration mise à jour: %s = %s\n", key, value)
return nil
}
// Ajouter un alias
func addAlias(name, command string) error {
if name == "" || command == "" {
return fmt.Errorf("le nom et la commande ne peuvent pas être vides")
}
// Vérifier que le nom n'est pas un mot clé réservé
reservedCommands := map[string]bool{
"cd": true,
"exit": true,
"version": true,
"help": true,
"set": true,
"alias": true,
"unalias": true,
"aliases": true,
}
if reservedCommands[name] {
return fmt.Errorf("impossible de créer un alias avec un nom réservé: %s", name)
}
// Ajouter ou mettre à jour l'alias
if config.Aliases == nil {
config.Aliases = make(map[string]string)
}
config.Aliases[name] = command
viper.Set("aliases", config.Aliases)
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("erreur lors de la sauvegarde des alias: %v", err)
}
fmt.Printf("Alias ajouté: %s = %s\n", name, command)
return nil
}
// Supprimer un alias
func removeAlias(name string) error {
if config.Aliases == nil || config.Aliases[name] == "" {
return fmt.Errorf("alias non trouvé: %s", name)
}
delete(config.Aliases, name)
viper.Set("aliases", config.Aliases)
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("erreur lors de la sauvegarde des alias: %v", err)
}
fmt.Printf("Alias supprimé: %s\n", name)
return nil
}
// Lister tous les alias
func listAliases() error {
if config.Aliases == nil || len(config.Aliases) == 0 {
fmt.Println("Aucun alias défini.")
return nil
}
fmt.Println("Aliases définis:")
for name, command := range config.Aliases {
fmt.Printf(" %s = %s\n", name, command)
}
return nil
}
// Retourne la liste des couleurs disponibles
func getAvailableColors() []string {
keys := make([]string, 0, len(colors))
for k := range colors {
if k != "reset" {
keys = append(keys, k)
}
}
return keys
}