176 lines
4.6 KiB
Go
176 lines
4.6 KiB
Go
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())
|
||
}
|