package console import ( "fmt" "io" "os" "regexp" "strings" . "gitea.zaclys.com/bvaudour/gob/option" ) // Cycler est une interface permettant de cycler dans les entrées d’une liste. type Cycler interface { Len() int // Retourne la longueur de la liste Cursor() int // Retourne l’index de l’entrée pointée Index(int) Option[string] // Retourne l’entrée à l’index donné SetCursor(int) bool // Déplace l’entrée pointée à l’index demandé et retourne vrai si cet index existe Append(string) // Ajoute une entrée dans la liste Clear() // Efface la liste Next() bool // Pointe vers l’entrée suivante Prev() bool // Pointe vers l’entrée précédente } // History est une interface pour gérer l’historique des commandes saisies. type History interface { Cycler Read(io.Reader) Result[int] Write(io.Writer) Result[int] } // Prompter est une interface pour représenter un prompt. type Prompter interface { Prompt(string) Result[string] // Affiche le prompt et retourne la saisie } // Console est un prompt avec gestion de l’historique. type Console interface { Prompter LoadHistory(io.Reader) Result[int] SaveHistory(io.Writer) Result[int] ClearHistory() AppendHistory(string) } // Error représente une erreur terminal type Error interface { Error() string Code() int } // Exit quitte le programme en affichant une erreur. // Si aucun descripteur n’est fourni, le message d’erreur // est écrit sur la sortie erreur standard. func Exit(code int, msg string, w ...io.Writer) { var out io.Writer if len(w) > 0 { out = w[0] } else { out = os.Stderr } fmt.Fprintln(out, msg) os.Exit(code) } // ExitfOn agit comme Exit mais mais formate le message. func ExitfOn(code int, tmpl string, w io.Writer, args ...any) { msg := fmt.Sprintf(tmpl, args...) Exit(code, msg, w) } // Exitf agit comme ExitfOn sur la sortie erreur standard. func Exitf(code int, tmpl string, args ...any) { ExitfOn(code, tmpl, os.Stderr, args...) } // ExitWithError quitte le programme en utilisant // l’erreur fournie. func ExitWithError(err Error, w ...io.Writer) { Exit(err.Code(), err.Error(), w...) } // HistoryList retourne le contenu de l’historique. func HistoryList(h History) (out []string) { l := h.Len() out = make([]string, l) for i := 0; i < l; i++ { if line, ok := h.Index(i).Get(); ok { out[i] = line } } return } func hfilter(h History, motive string, cb func(string, string) bool, insensitive ...bool) (out []string) { l := h.Len() if l == 0 { return } ins := false if len(insensitive) > 0 { ins = insensitive[0] } var cmp func(string) bool if ins { motive = strings.ToLower(motive) cmp = func(e string) bool { return cb(strings.ToLower(e), motive) } } else { cmp = func(e string) bool { return cb(e, motive) } } for i := 0; i < l; i++ { if e, ok := h.Index(i).Get(); ok && cmp(e) { out = append(out, e) } } return } // HistoryFilterMotive retourne les entrées de l’historique contenant le motif donné. // Il est possible de faire une recherche insensible à la casse. func HistoryFilterMotive(h History, motive string, insensitive ...bool) (out []string) { return hfilter(h, motive, strings.Contains, insensitive...) } // HistoryFilterPrefix retourne les entrées de l’historique commençant le préfixe donné. // Il est possible de faire une recherche insensible à la casse. func HistoryFilterPrefix(h History, prefix string, insensitive ...bool) (out []string) { return hfilter(h, prefix, strings.HasPrefix, insensitive...) } // HistoryFilterSuffix retourne les entrées de l’historique terminant par le suffixe donné. // Il est possible de faire une recherche insensible à la casse. func HistoryFilterSuffix(h History, suffix string, insensitive ...bool) (out []string) { return hfilter(h, suffix, strings.HasSuffix, insensitive...) } // HistoryFilter retourne les entrées de l’historique vérifiant la regexp donnée. func HistoryFilter(h History, r *regexp.Regexp) (out []string) { l := h.Len() if l == 0 { return } for i := 0; i < l; i++ { if e, ok := h.Index(i).Get(); ok && r.MatchString(e) { out = append(out, e) } } return } // IsFocused retourne vrai si le cycler pointe sur un élément existant. func IsFocused(c Cycler) bool { l, n := c.Len(), c.Cursor() return n >= 0 && n < l } // FocusedElement retourne l’élément pointé par le cycler. func FocusedElement(c Cycler) Option[string] { return c.Index(c.Cursor()) } // Unfocus enlève le pointage de l’historique. func Unfocus(h History) { h.SetCursor(h.Len()) }