Documentation
¶
Overview ¶
Package estentor construye consolas interactivas.
Index ¶
- Constants
- Variables
- func AlMenos(n int) func([]string) error
- func BannersDeCarpeta(ruta string) ([]string, error)
- func Exactamente(n int) func([]string) error
- type Comando
- type Consola
- func (c *Consola) Arrancar()
- func (c *Consola) ArrancarCon(ctx context.Context) error
- func (c *Consola) Cancelar(id int) bool
- func (c *Consola) Cargando(texto string) func()
- func (c *Consola) CargarHistorial(r io.Reader) error
- func (c *Consola) Claves() []string
- func (c *Consola) Contexto() []string
- func (c *Consola) Entrar(nombre string)
- func (c *Consola) Expandir(texto string) string
- func (c *Consola) GuardarHistorial(w io.Writer) error
- func (c *Consola) HayColor() bool
- func (c *Consola) Historial() []string
- func (c *Consola) Lanzar(nombre string, tarea func(ctx context.Context) error) int
- func (c *Consola) OlvidarTrabajos() int
- func (c *Consola) Pintar(texto string, estilos ...Estilo) string
- func (c *Consola) Poner(clave, valor string)
- func (c *Consola) Procedencia(clave string) (valor, deNivel string, existe bool)
- func (c *Consola) Quitar(clave string) bool
- func (c *Consola) Registrando(w io.Writer) func()
- func (c *Consola) Registrar(cmd Comando)
- func (c *Consola) Tabla(t *Tabla)
- func (c *Consola) Trabajos() []Trabajo
- func (c *Consola) Valor(clave string) (string, bool)
- func (c *Consola) Volver() bool
- func (c *Consola) VolverATodo()
- type EstadoTrabajo
- type Estilo
- type ModoColor
- type ModoPegado
- type Tabla
- func (t *Tabla) BorrarFila(i int) bool
- func (t *Tabla) Buscar(columna int, valor string) int
- func (t *Tabla) BuscarSi(cumple func(fila []string) bool) int
- func (t *Tabla) CambiarFila(i int, celdas ...string) *Tabla
- func (t *Tabla) Celda(fila, columna int, valor string) *Tabla
- func (t *Tabla) Colorear(valor string, estilos ...Estilo) *Tabla
- func (t *Tabla) ColorearCabeceras(estilos ...Estilo) *Tabla
- func (t *Tabla) ColorearColumna(columna int, valor string, estilos ...Estilo) *Tabla
- func (t *Tabla) ColorearColumnaEntera(columna int, estilos ...Estilo) *Tabla
- func (t *Tabla) ColorearFila(fila int, estilos ...Estilo) *Tabla
- func (t *Tabla) ColorearSi(cumple func(fila, columna int, valor string) []Estilo) *Tabla
- func (t *Tabla) Escribir(w io.Writer) error
- func (t *Tabla) Fila(celdas ...string) *Tabla
- func (t *Tabla) Filtrar(cumple func(fila []string) bool) *Tabla
- func (t *Tabla) InsertarFila(i int, celdas ...string) *Tabla
- func (t *Tabla) Limpiar() *Tabla
- func (t *Tabla) Obtener(fila, columna int) (string, bool)
- func (t *Tabla) Ordenar(columna int) *Tabla
- func (t *Tabla) String() string
- type Trabajo
Constants ¶
const MaxCrecimiento = 1 << 20
MaxCrecimiento es hasta dónde pueden estirar la tabla Celda y CambiarFila. Un índice mayor no hace nada: a esa escala viene de un error de cálculo, y reservar la memoria se llevaría el proceso por delante.
const MaxLineaPorDefecto = 1 << 20
MaxLineaPorDefecto es el límite de longitud de línea que pone Nueva: 1 MB. El de bufio.Scanner son 64 KB, que se pasan pegando un texto mediano.
const NivelGlobal = "global"
NivelGlobal es cómo se llama el nivel de fuera, el que siempre existe.
Variables ¶
var ErrInterrumpido = errors.New("interrumpido")
ErrInterrumpido lo devuelve la lectura cuando se pulsa Ctrl-C.
Existe porque golang.org/x/term colapsa Ctrl-C y Ctrl-D en el mismo io.EOF, y no son lo mismo: uno cancela lo que estás escribiendo y el otro cierra la consola.
var ErrSalir = errors.New("salir de la consola")
ErrSalir lo devuelve un comando para que la consola termine. No es un fallo: es la forma de decir "hemos acabado", igual que io.EOF.
Functions ¶
func BannersDeCarpeta ¶
BannersDeCarpeta lee los ficheros .txt de una carpeta y devuelve su contenido, para usarlo como Consola.Banners. No entra en subcarpetas.
Devuelve los que sí se pudieron leer Y el error de los que no, diciendo cuál falló de cada uno. Las dos cosas a la vez, porque quien llama es quien decide: su argumento de venta es "deja un fichero ahí y ya", y antes un solo .txt sin permisos se llevaba por delante a todos los buenos.
banners, err := estentor.BannersDeCarpeta("banners")
if err != nil {
fmt.Fprintln(os.Stderr, err) // avisa...
}
c.Banners = banners // ...y arranca con los que haya
Que la carpeta entera no se pueda leer sigue siendo un fallo completo: ahí no hay nada que devolver a medias.
func Exactamente ¶
Exactamente exige ese número de argumentos, ni uno más ni uno menos.
Types ¶
type Comando ¶
type Comando struct {
Nombre string
// Sinonimos son nombres alternativos. Funcionan igual que Nombre, pero en
// la ayuda salen junto a él en vez de como entradas aparte.
Sinonimos []string
Ayuda string
// Uso es la firma del comando, para que "help <nombre>" pueda decir en qué
// orden van los argumentos. Una línea de Ayuda no da para eso.
//
// Uso: "escanear <rango> [--perfil ics]"
//
// Es documentación escrita a mano, así que se queda obsoleta sin que nada
// avise. Una firma mentirosa es peor que no tener ninguna: si cambias los
// argumentos, cambia esto.
Uso string
// Args comprueba los argumentos antes de ejecutar. Si es nil no se
// comprueba nada, que es como se comportaba todo antes de que existiera.
//
// Args: estentor.AlMenos(1)
//
// La línea se para aquí a propósito: una función y dos ayudas. Nada de
// tipos ni de banderas, que es donde esto dejaría de ser una biblioteca
// pequeña.
Args func(args []string) error
// Completar ofrece los valores posibles para el argumento que se está
// tecleando. Si es nil, el tabulador no hace nada sobre los argumentos de
// este comando, que es como se comportaba todo antes.
//
// args son los argumentos ya cerrados que hay delante, así que se puede
// decidir por posición; prefijo es lo que se lleva escrito del que toca.
//
// Completar: func(args []string, prefijo string) []string {
// if len(args) > 0 {
// return puertos(prefijo)
// }
// return protocolos(prefijo)
// }
//
// Devuelve los candidatos sin filtrar por prefijo si no quieres: la consola
// no los filtra, así que **filtrarlos es cosa tuya**. Se hace así porque hay
// listas que no se filtran por prefijo —rutas, rangos— y decidirlo por ti
// sería quitarte esa opción.
Completar func(args []string, prefijo string) []string
// Ejecutar recibe el contexto de la consola, que se cancela si se pulsa
// Ctrl-C mientras el comando corre.
Ejecutar func(ctx context.Context, args []string) error
}
Comando es algo que la consola sabe hacer. Se añaden con Registrar y aparecen solos en la ayuda.
type Consola ¶
type Consola struct {
Nombre string
Entrada io.Reader
Salida io.Writer
// Banners son textos de bienvenida. Al arrancar se muestra uno al azar.
// Si está vacío no se muestra nada.
Banners []string
// Variables se sustituyen en banners y textos de carga cuando aparecen
// entre llaves. Nueva rellena "usuario" y "host"; "nombre" siempre sale
// del campo Nombre. Se pueden añadir las que se quieran.
Variables map[string]string
// Prompt compone lo que se pinta antes de cada línea. Si es nil se usa
// "nombre> ". Recibe la consola para poder reflejar su estado.
Prompt func(c *Consola) string
// Color decide cuándo se pinta con color. Por defecto, automático.
Color ModoColor
// Pegado decide si lo que se pega entra como texto o como teclas. Por
// defecto, como texto.
Pegado ModoPegado
// MaxLinea es lo más larga que puede ser una línea, en bytes. Al pasarse,
// la consola avisa y termina: el lector de la biblioteca estándar queda
// inservible tras ese error y no se puede seguir leyendo de él.
MaxLinea int
// NoRecordar decide qué líneas no entran en el historial. Si es nil entran
// todas.
//
// Existe porque el historial se puede guardar en disco, y en una consola de
// auditoría lo que se teclea son objetivos, rangos de red de un cliente y a
// veces credenciales. La consola no adivina cuáles: lo dice quien la usa.
//
// c.NoRecordar = func(linea string) bool {
// return strings.Contains(linea, "--clave")
// }
NoRecordar func(linea string) bool
// contains filtered or unexported fields
}
Consola es una consola interactiva. Se crea con Nueva y se pone en marcha con Arrancar.
func Nueva ¶
Nueva devuelve una consola que lee de la entrada estándar y escribe en la salida estándar.
func (*Consola) Arrancar ¶
func (c *Consola) Arrancar()
Arrancar es ArrancarCon con un contexto vacío.
No devuelve nada a propósito, para que el ejemplo mínimo siga siendo una línea: estentor.Nueva("miapp").Arrancar(). Quien necesite saber cómo terminó usa ArrancarCon.
func (*Consola) ArrancarCon ¶
ArrancarCon muestra un banner y va ejecutando lo que se escriba, hasta que un comando devuelve ErrSalir, se acaba la entrada, o se cancela el contexto.
Devuelve nil cuando la consola terminó como debía —con ErrSalir, con Ctrl-D, al acabarse la entrada o al cancelarse el contexto— y el error de lectura si lo hubo. Sirve para que un main termine con código distinto de cero:
if err := c.ArrancarCon(ctx); err != nil {
os.Exit(1)
}
La cancelación surte efecto entre líneas: si llega mientras se está esperando a que alguien teclee, no se nota hasta que teclee.
func (*Consola) Cancelar ¶
Cancelar le pide a una tarea que pare. Devuelve false si ese número no existe o si ya había acabado.
Pedir no es obligar: una tarea que no mire su contexto seguirá hasta el final. Eso no lo puede arreglar la consola.
func (*Consola) Cargando ¶
Cargando escribe el texto con un indicador giratorio delante y devuelve la función que lo para y limpia la línea.
parar := c.Cargando("Cargando módulos")
defer parar()
func (*Consola) CargarHistorial ¶
CargarHistorial mete en el historial lo que haya en r, una línea por línea, de lo más viejo a lo más nuevo. Hay que llamarla antes de arrancar.
Se salta las líneas en blanco y las repetidas seguidas, y se queda con las últimas si vienen más de las que caben, para que un fichero que ha ido creciendo no arrastre basura de hace meses.
**La biblioteca no decide dónde vive el fichero**, solo lee de un io.Reader. Eso es de quien la usa, que es quien sabe si va en el home, junto al proyecto o en ningún sitio. Y quien tiene que ponerle los permisos: en una consola de auditoría el historial guarda objetivos y rangos de red de un cliente, así que 0600 no es una recomendación.
f, err := os.Open(ruta)
if err == nil {
defer f.Close()
_ = c.CargarHistorial(f)
}
func (*Consola) Claves ¶
Claves devuelve los nombres de las variables que se ven desde donde estás, en orden alfabético. Una tapada por un nivel más cercano sale una sola vez.
func (*Consola) Contexto ¶
Contexto devuelve los nombres de los niveles, de fuera hacia dentro, sin contar el global.
func (*Consola) Entrar ¶
Entrar mete un nivel nuevo en la pila. Lo que se ponga con Poner a partir de ahora vive ahí, y desaparece al Volver.
c.Entrar("10.20.0.41")
c.Poner("puerto", "102") // solo existe dentro de ese nivel
El nivel hereda lo de fuera: una variable que no esté aquí se busca en el nivel de arriba, y así hasta el global.
func (*Consola) Expandir ¶
Expandir sustituye las variables entre llaves por su valor. Las que no existan se dejan tal cual, para que un error de escritura se vea.
Una llave con una barra invertida delante es literal: \{objetivo} da {objetivo} tal cual, exista la variable o no. Es el único sitio donde la barra significa algo aquí; una barra suelta se deja como está, para no estropear rutas de Windows ni dibujos de banner.
Doblar la llave NO escapa: {{7*7}} llega entero al comando. Es a propósito, porque eso es una carga de prueba contra plantillas de servidor, y mutilarla en silencio sería el peor fallo posible en una consola de auditoría.
Recorre el texto una sola vez y no vuelve a mirar lo ya sustituido. Con la versión anterior, que encadenaba reemplazos recorriendo el mapa, el valor de una variable podía contener la marca de otra y el resultado dependía del orden de iteración: el mismo banner salía distinto entre arranques.
func (*Consola) GuardarHistorial ¶
GuardarHistorial escribe el historial en w, una línea por línea, de lo más viejo a lo más nuevo. Lo que NoRecordar dejó fuera no está aquí, porque nunca llegó a entrar.
Llámala al cerrar, cuando ArrancarCon ya ha vuelto: es entonces cuando el historial está completo.
f, err := os.OpenFile(ruta, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err == nil {
defer f.Close()
_ = c.GuardarHistorial(f)
}
func (*Consola) HayColor ¶
HayColor dice si ahora mismo se pintaría con color.
En automático manda el entorno antes que la terminal, porque quien pone NO_COLOR o TERM=dumb lo hace justo para que no le pinten nada.
func (*Consola) Historial ¶
Historial devuelve lo tecleado, de lo más viejo a lo más nuevo. Es una copia: tocarla no cambia el historial de la consola.
func (*Consola) Lanzar ¶
Lanzar arranca una tarea de fondo y devuelve su número.
La tarea recibe un contexto que se cancela al pararla con Cancelar, y también cuando se cierra la consola. Escribir en c.Salida desde dentro es seguro: la consola pone un candado, y en modo crudo aparta la línea que se esté tecleando y la vuelve a pintar.
Lánzalas desde un comando, o sea con la consola ya en marcha. Arrancar envuelve c.Salida al empezar, así que una tarea lanzada antes podría estar leyendo ese campo justo mientras se cambia.
func (*Consola) OlvidarTrabajos ¶
OlvidarTrabajos quita de la lista los que ya acabaron.
func (*Consola) Pintar ¶
Pintar envuelve el texto con esos estilos, o lo devuelve tal cual si no toca pintar.
c.Pintar("no se pudo", estentor.Rojo)
c.Pintar("listo", estentor.Verde, estentor.Negrita)
func (*Consola) Poner ¶
Poner guarda una variable. Es lo que hace el comando "set", y lo que hay que usar desde un comando o desde una tarea de fondo: escribir en el campo Variables mientras la consola corre no lleva candado.
La clave "nombre" se guarda en el campo Nombre, para que cambiarla se note en el prompt.
func (*Consola) Procedencia ¶
Procedencia dice cuánto vale una variable y de qué nivel sale. Es lo que distingue "lo puse yo aquí" de "lo heredé de fuera".
func (*Consola) Quitar ¶
Quitar borra una variable y dice si existía. La clave "nombre" no se puede quitar: siempre hay un nombre, aunque esté vacío.
func (*Consola) Registrando ¶
Registrando duplica en w todo lo que pase por la consola: lo que se teclea y lo que se contesta, incluidos los avisos de las tareas de fondo. Devuelve la función que lo para, que se puede llamar las veces que haga falta.
f, err := os.OpenFile(ruta, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600)
if err == nil {
parar := c.Registrando(f)
defer func() { parar(); f.Close() }()
}
**Llámala con la consola en marcha**, o sea desde un comando, igual que las tareas de fondo: es el mismo contrato de siempre (los campos se configuran antes de arrancar, lo que corre se lanza desde dentro). Antes de arrancar no hay por dónde derivar y no hace nada.
Va sin códigos de color: un fichero con secuencias de escape dentro no se puede leer.
Lo que se registra es una sesión real contra sistemas de alguien, así que el fichero tiene el mismo problema de custodia que el historial. El 0600 del ejemplo no es decorativo.
func (*Consola) Registrar ¶
Registrar añade un comando con sus sinónimos.
Si ya había un comando con ese nombre lo sustituye entero, sinónimos incluidos: un help propio sin "?" deja de responder a "?". El comando es dueño de sus nombres, así que reemplazarlo se los lleva todos.
func (*Consola) Tabla ¶
Tabla pinta una tabla en la salida de la consola. La tabla se construye aparte con NuevaTabla, así que sirve igual fuera de la consola.
func (*Consola) Volver ¶
Volver saca el nivel actual y tira sus variables. Devuelve false si ya estabas en el global, que no se puede sacar.
func (*Consola) VolverATodo ¶
func (c *Consola) VolverATodo()
VolverATodo sale de todos los niveles de golpe.
type EstadoTrabajo ¶
type EstadoTrabajo int
EstadoTrabajo es en qué anda un trabajo de fondo.
const ( // Corriendo es que sigue trabajando. Corriendo EstadoTrabajo = iota // Terminado es que acabó sin error. Terminado // Fallado es que acabó devolviendo un error. Fallado // Cancelado es que se le pidió parar. Cancelado )
func (EstadoTrabajo) String ¶
func (e EstadoTrabajo) String() string
type Estilo ¶
type Estilo string
Estilo es un código de color o de énfasis. Se combinan pasando varios.
const ( Rojo Estilo = "\033[31m" Verde Estilo = "\033[32m" Amarillo Estilo = "\033[33m" Azul Estilo = "\033[34m" Magenta Estilo = "\033[35m" Cian Estilo = "\033[36m" Gris Estilo = "\033[90m" Negrita Estilo = "\033[1m" Tenue Estilo = "\033[2m" Cursiva Estilo = "\033[3m" )
Los estilos disponibles. Se combinan pasando varios a Pintar.
type ModoColor ¶
type ModoColor int
ModoColor decide cuándo se pinta con color.
const ( // ColorAuto pinta si la salida es una terminal y el entorno no dice lo // contrario. Es el valor por defecto. ColorAuto ModoColor = iota // ColorSiempre pinta pase lo que pase. Sirve para volcar a un fichero que // luego se va a mirar con `less -R`. ColorSiempre // ColorNunca no pinta nunca. ColorNunca )
type ModoPegado ¶
type ModoPegado int
ModoPegado decide si se le pide a la terminal que avise de lo que se pega.
const ( // PegadoAuto pide el aviso. Lo pegado entra como texto: ni los saltos de // línea ejecutan, ni el tabulador completa, ni un Ctrl-C interrumpe. Es el // valor por defecto. PegadoAuto ModoPegado = iota // PegadoNunca no lo pide, y pegar vuelve a ser teclear muy rápido: cada // salto de línea ejecuta lo que lleves escrito. // // Existe por una lección ajena. GNU readline trae el pegado entre corchetes // activado desde la 8.1, pero CPython y FRR tuvieron que desactivarlo // porque rompía a quien pegaba una configuración entera contando con que // los saltos la fueran ejecutando. Si alguien depende de eso, aquí lo // apaga en vez de quedarse sin salida. PegadoNunca )
type Tabla ¶
type Tabla struct {
Cabeceras []string
Filas [][]string
// Sangria son los espacios de la izquierda. Por defecto dos. Un valor
// negativo cuenta como cero.
Sangria int
// Color decide si se aplican las reglas al pintar. Consola.Tabla lo pone
// según la terminal; usando la tabla suelta hay que ponerlo a mano.
//
// Las reglas se guardan igual aunque esté apagado, así que una tabla se
// puede preparar con colores y volcarse en limpio a un fichero.
Color bool
// contains filtered or unexported fields
}
Tabla son unas cabeceras y sus filas, que se pintan con las columnas alineadas. No depende de la consola: se puede usar en cualquier programa.
t := estentor.NuevaTabla("IP", "Estado")
t.Fila("10.20.0.41", "responde")
fmt.Println(t)
Índices ¶
Una sola regla para todos los métodos que reciben un índice:
- Negativo: no hace nada. Los de consulta dicen que no lo encontraron (false, o -1). Ninguno revienta y ninguno hace algo distinto de lo pedido.
- Pasado del final: los que modifican estiran la tabla hasta llegar (Celda, CambiarFila) o añaden al final (InsertarFila); los de consulta dicen que no lo encontraron.
El tope de estirado es MaxCrecimiento.
func NuevaTabla ¶
NuevaTabla crea una tabla con esas cabeceras.
func (*Tabla) BorrarFila ¶
BorrarFila quita una fila. Devuelve false si esa fila no existía.
func (*Tabla) Buscar ¶
Buscar devuelve el índice de la primera fila cuya columna valga eso, o -1 si no hay ninguna. El -1 es el mismo convenio que usa strings.Index.
func (*Tabla) BuscarSi ¶
BuscarSi devuelve el índice de la primera fila que cumpla la condición, o -1.
i := t.BuscarSi(func(fila []string) bool {
return strings.HasPrefix(fila[0], "10.20.")
})
func (*Tabla) CambiarFila ¶
CambiarFila sustituye una fila entera. Si no existe, la tabla crece hasta llegar. Los índices negativos y los que pasen de MaxCrecimiento se ignoran.
func (*Tabla) Celda ¶
Celda cambia una celda. Si la posición no existe, la tabla crece hasta llegar: así se puede rellenar por coordenadas sin preparar nada antes. Los índices negativos y los que pasen de MaxCrecimiento se ignoran.
func (*Tabla) Colorear ¶
Colorear pinta con esos estilos las celdas cuyo valor sea exactamente ese, en cualquier columna.
t.Colorear("responde", estentor.Verde)
t.Colorear("sin identificar", estentor.Rojo, estentor.Negrita)
func (*Tabla) ColorearCabeceras ¶
ColorearCabeceras pone estilo a la fila de títulos.
func (*Tabla) ColorearColumna ¶
ColorearColumna es igual que Colorear pero solo mira una columna. Sirve cuando el mismo texto significa cosas distintas según dónde salga.
func (*Tabla) ColorearColumnaEntera ¶
ColorearColumnaEntera pinta una columna entera, mire lo que mire.
func (*Tabla) ColorearFila ¶
ColorearFila pinta una fila entera. El índice es el de la fila de datos: la primera es la 0, sin contar la cabecera.
func (*Tabla) ColorearSi ¶
ColorearSi decide con una función, para lo que no sea un valor exacto: números por encima de un umbral, prefijos, expresiones regulares.
t.ColorearSi(func(_, columna int, valor string) []estentor.Estilo {
if columna == 2 && strings.HasPrefix(valor, "10.20.") {
return []estentor.Estilo{estentor.Cian}
}
return nil
})
Devolver nil deja pasar a la regla siguiente.
func (*Tabla) Fila ¶
Fila añade una fila y devuelve la tabla, para poder encadenar llamadas. Las filas con menos celdas de la cuenta se completan con huecos, y las celdas que sobran se ignoran.
func (*Tabla) Filtrar ¶
Filtrar devuelve una tabla nueva con las mismas cabeceras y solo las filas que cumplan. No toca la original.
func (*Tabla) InsertarFila ¶
InsertarFila mete una fila en la posición i y corre las demás. Si i se pasa del final, la añade al final. Un índice negativo no hace nada.
func (*Tabla) Obtener ¶
Obtener devuelve una celda. El segundo valor dice si existía, para poder distinguir una celda vacía de una que no está.