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())
|
|||
|
}
|