Obsah
1. Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní
2. Knihovny pro využití textového terminálu určené pro jazyk Go
3. Formátované a obarvené logy vytvářené s využitím knihovny zerolog
4. Tisk logovacích zpráv na standardní výstup s obarvením
5. Tisk logovacích zpráv na chybový výstup s obarvením
6. Větší množství atributů zobrazených v jedné logovací zprávě
7. Zobrazení informace o chybě
8. Podporované úrovně logovacích operací
9. Nastavení minimální úrovně logovacích operací
10. Logování bez specifikace úrovně
12. Binární data v logovací zprávě?
13. Specifikace barev textu na terminálu s knihovnou aurora
14. Povolení či zákaz obarveného výstupu specifikací přepínače na příkazové řádce
15. Dvě varianty specifikace stylu písma na terminálu
17. Výstup ve zvolené úrovni šedi (grayscale)
18. Sada standardních 216 barev terminálu
19. Repositář s demonstračními příklady
1. Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní
Již mnohokrát jsme se na stránkách Roota zabývali problematikou tvorby aplikací s plnohodnotným grafickým uživatelským rozhraním, které se konkrétně (resp. v největší míře) týkají článků o grafickém uživatelském rozhraní v Pythonu. Ovšem existuje relativně velké množství aplikací, popř. nástrojů či her, pro které může být vhodnější použít „pouhý“ textový terminál, resp. textovou konzoli. Příkladem mohou být aplikace, k nimž se přistupuje přes SSH, systémové nástroje apod. Takové aplikace dělíme do dvou skupin. Do první skupiny spadají interaktivně či neinteraktivně ovládané aplikace s příkazovým řádkem. Příkladem takových aplikací mohou být správci balíčků (dnf, apt-get, …), které s uživatelem interagují pouze několika otázkami typu Y/N. Dalším příkladem je nástroj fdisk se složitějším systémem menu, z nichž se však položka vybírá svým jménem (a nikoli například pohybem kurzoru).
Obrázek 1: Poměrně velké množství aplikací sice využívá rozšířených možností textových terminálů, ovšem stále se jedná spíše o neinteraktivní nástroje bez celoobrazovkového přístupu k terminálu. Dobrým příkladem takových typů aplikací je příkaz dnf, který může v některých případech zobrazit jednoduchou otázku a „interaktivně“ očekávat odpověď.
A do druhé skupiny aplikací určených pro běh v terminálu můžeme zařadit aplikace s plnohodnotným textovým uživatelským rozhraním neboli TUI (Text User Interface). Takových aplikací dnes existuje celá řada. Zmínit můžeme zejména celoobrazovkové textové editory (pochopitelně včetně Vimu a Emacsu), dále Midnight Commander, nástroj Aptitude, ale i některé hry používající celoobrazovkový textový režim (0verkill, Rogue, Nethack, Angband, DoomRL atd.). Částečně sem spadá i hra Dwarf Fortress, která by mohla běžet v terminálu, ale kvůli přenositelnosti používá vlastní engine založený na zobrazení textů.
Obrázek 2: DoomRL – úvodní ASCII art použitý v této hře je takřka dokonalý.
Textové uživatelské rozhraní je u některých aplikací volitelné. Dobrým příkladem takového typu aplikace je GNU Debugger, který buď pracuje v režimu příkazového řádku, nebo ho lze v případě potřeby přepnout do režimu s celoobrazovkovým textovým uživatelským rozhraním. I o tvorbě těchto aplikací jsme se ve stručnosti již zmínili v souvislosti s balíčky GNU Readline a především prompt_toolkit.
Obrázek 3: Nástroj GNU Debugger po zobrazení textového uživatelského rozhraní (TUI) ve chvíli, kdy je nastaven breakpoint na vstupní bod (přesněji řečeno na první příkaz) ve funkci main. Na levém okraji se zobrazují jak breakpointy, tak i ukazatel na právě prováděný či krokovaný příkaz.
Obrázek 4: Hra Angband s textovým uživatelským rozhraním ve verzi pro Linux – podrobnější charakteristiky hrdiny.
U obou dvou výše zmíněných typů aplikací je nutné umět do určité míry ovládat terminálový výstup, aby například bylo možné smazat znak/řádek, zvýraznit text změnou jeho stylu, barvy textu, barvy pozadí atd. A u aplikací s plnohodnotným textovým uživatelským rozhraním je pochopitelně nutné terminál ovládat do ještě větší míry, aby bylo možné pracovat s okny, menu, nápovědou a dalšími ovládacími prvky (widgety). Vzhledem k tomu, že se textové terminály vyvíjejí už velmi dlouho (minimálně od roku 1965, kdy vznikl první skutečný počítačový terminál IBM 2741 jako náhrada za dálnopisy) jsou rozdílné i jejich možnosti (vlastnosti) i způsob jejich ovládání (většinou s využitím řídicích kódů, dnes většinou založených na takzvaných escape sekvencích a několika dalších ASCII znacích).
Obrázek 5: Část sálového počítače IBM System/360 Model 40 (rok výroby 1964). V pozadí můžeme vidět jednotky s magnetickými pásky (model IBM 2401), ovládací panel je umístěn přímo za slečnou. A slečna sedí u dálnopisu, tedy přímého předchůdce počítačových terminálů.
Samozřejmě jsme mohli v průběhu předchozích desetiletí vidět snahu o sjednocení ovládání terminálů, která probíhala paralelně s vytvářením databáze obsahující vlastnosti terminálů (terminfo a termcap). Existují také knihovny, které programátora dokážou odizolovat od nízkoúrovňového přístupu k terminálům a jejich řízení s využitím řídicích kódů. Jedná se především o knihovnu curses a jejího následovníka ncurses (viz článek Psaní aplikací pro terminál: jak funguje knihovna ncurses, popř. o utility spouštěné z příkazového řádku (tput, tset, reset atd.).
Obrázek 6: Ovládací prvky vykreslené s využitím knihovny Blessed-contrib (JavaScript).
Zdroj: https://github.com/yaronn/blessed-contrib.
2. Knihovny pro využití textového terminálu určené pro jazyk Go
V dnešním článku i v navazujících článcích se postupně zaměříme na popis knihoven určených pro programovací jazyk Go. Tyto knihovny se od sebe budou odlišovat svým zaměřením a můžeme je zhruba rozdělit do pěti kategorií:
- Knihovny zlepšující logování a podporující barevné zvýraznění logovacích zpráv na terminálu. Sem spadá například dále popsaný balíčekzerolog.
- Knihovny pro podporu různých barev popředí a pozadí textu, změnu stylu textu atd. Příkladem může být balíček aurora, popř. cfmt, go-colortext apod.
- Knihovny pro zobrazení jednoduchých textových dialogů a boxů se zprávami. Tuto problematiku řeší balíček box-cli-maker.
- Knihovny pro tvorbu grafů, „teploměrů“ atd. na ploše terminálu. Příkladem je balíček asciigraph.
- Knihovny určené pro tvorbu plnohodnotných textových uživatelských rozhraní (TUI). Příkladem mohou být balíčky termui, termbox-go, progressbar atd.
Obrázek 7: Jednou z terminálových aplikací naprogramovaných v Go je utilitka gotop.
Zdroj: https://github.com/xxxserxxx/gotop.
Obrázek 8: Spuštění debuggeru, inicializace laděné aplikace, nastavení breakpointu a doskok na breakpoint.
3. Formátované a obarvené logy vytvářené s využitím knihovny zerolog
Prvním balíčkem, s nímž se v dnešním článku setkáme, je balíček nazvaný zerolog, přesněji řečeno plným jménem Zero Allocation JSON Logger. Tento balíček rozšiřuje možnosti standardního logovacího balíčku log o mnohé další vlastnosti, zejména o výstup do snadno parsovatelného formátu JSON, popř. alternativně o výstup se zvýrazněním jednotlivých zpráv s využitím barev. Druhý způsob se používá při vývoji a ladění aplikací, první pak může být použit při reálném nasazení aplikace.
Podívejme se nyní na nejjednodušší použití tohoto balíčku, z něhož bude patrné, jakým způsobem se jednotlivé zprávy vypisované do logu tvoří. Používá se zde jednotný zápis:
log.LogLevel().TypAtributu(jméno, hodnota).Msg(zpráva)
přičemž atributů může být uvedeno prakticky libovolné množství. V příkladu je logLevel nastaven na Info, což je třetí úroveň z celkem sedmi definovaných úrovní.
Atributy se vždy skládají z dvojice jméno (řetězec) a hodnota, přičemž hodnota musí odpovídat typu atributu – řetězec, celé číslo, pravdivostní hodnota, slovník atd.:
package main import ( "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = false func main() { log.Info().Msg("Started") log.Info().Str("address", Address).Msg("Server address") log.Info().Int("port", Port).Msg("Server port") log.Info().Bool("enabled", Enabled).Msg("Server enabled") log.Info().Msg("Finished") }
Výsledkem by měly být zprávy ve formátu JSONu:
Obrázek 9: Zprávy zobrazené ve formátu JSON.
4. Tisk logovacích zpráv na standardní výstup s obarvením
Knihovna zerolog umožňuje specifikaci výstupního proudu pro logovací zprávy. V případě, že se explicitně provede přesměrování do standardního výstupu, předpokládá se využití inteligentního terminálu a namísto JSON formátu jsou zprávy vypsány s barevným zvýrazněním:
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
Úplný zdrojový kód dnešního druhého demonstračního příkladu:
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = false func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) log.Info().Msg("Started") log.Info().Str("address", Address).Msg("Server address") log.Info().Int("port", Port).Msg("Server port") log.Info().Bool("enabled", Enabled).Msg("Server enabled") log.Info().Msg("Finished") }
Výsledkem by měly být zprávy s barevným zvýrazněním:
Obrázek 10: Zprávy barevným zvýrazněním vypsané na standardní výstup na inteligentním terminálu.
5. Tisk logovacích zpráv na chybový výstup s obarvením
Naprosto stejným postupem lze ovšem zajistit výstup na chybový výstup, který je opět proveden s barevným zvýrazněním:
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
Pro jistotu si opět zobrazme úplný zdrojový kód tohoto demonstračního příkladu:
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = false func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Info().Str("address", Address).Msg("Server address") log.Info().Int("port", Port).Msg("Server port") log.Info().Bool("enabled", Enabled).Msg("Server enabled") log.Info().Msg("Finished") }
Výsledek:
Obrázek 11: Zprávy barevným zvýrazněním vypsané na chybový výstup na inteligentním terminálu.
6. Větší množství atributů zobrazených v jedné logovací zprávě
Velmi často je nutné v jedné logovací zprávě zobrazit větší množství atributů. I to pochopitelně knihovna zerolog umožňuje, pouze si musíme dát pozor na to, aby samotná zpráva (Msg) byla poslední volanou metodou:
log.Info(). Str("address", Address). Int("port", Port). Bool("enabled", Enabled). Msg("Server settings")
Úplný zdrojový kód takto upraveného demonstračního příkladu:
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = false func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Info(). Str("address", Address). Int("port", Port). Bool("enabled", Enabled). Msg("Server settings") log.Info().Msg("Finished") }
Výsledek:
Obrázek 12: Logovací zpráva s větším množstvím atributů.
7. Zobrazení informace o chybě
Pro výpis informací o detekované chybě se používá tento zápis:
log.LogLevel().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)
Typicky se používá úroveň logů Error nebo Fatal, takže:
log.LogError().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)
popř.:
log.LogFatal().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)
nebo dokonce:
log.LogPanic().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)
V následujícím demonstračním příkladu je chyba testována (a popř. vypsána do logů) při pokusu o spuštění HTTP serveru:
package main import ( "fmt" "io" "os" "net/http" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = true func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Info(). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") if Enabled { http.HandleFunc("/", handler) where := fmt.Sprintf("%s:%d", Address, Port) err := http.ListenAndServe(where, nil) if err != nil { log.Error().Err(err).Msg("Initialize server") } } log.Info().Msg("Finished") } func handler(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!") }
Pokud se nyní pokusíme příklad spustit dvakrát, dojde k chybě, protože první instance serveru si zabere specifikovaný port 8080:
Obrázek 13: Zobrazení informace o chybě.
8. Podporované úrovně logovacích operací
Knihovna zerolog podporuje celkem sedm úrovní logovacích operací, které jsou pojmenovány následovně (což je kompatibilní i s mnoha dalšími podobně koncipovanými knihovnami):
panic (zerolog.PanicLevel, 5) fatal (zerolog.FatalLevel, 4) error (zerolog.ErrorLevel, 3) warn (zerolog.WarnLevel, 2) info (zerolog.InfoLevel, 1) debug (zerolog.DebugLevel, 0) trace (zerolog.TraceLevel, -1)
Prozatím jsme používali úroveň Strong, ale nikdo nám samozřejmě nezabraňuje využít další úrovně, z nichž každá je určena volanou funkcí z knihovny zerolog. Tento přístup je ukázán v dalším demonstračním příkladu:
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = true func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Trace(). Str("level", "trace"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Debug(). Str("level", "debug"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Info(). Str("level", "info"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Warn(). Str("level", "warn"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Error(). Str("level", "error"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Fatal(). Str("level", "fatal"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Panic(). Str("level", "panic"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Info().Msg("Finished") }
Výsledek může vypadat následovně:
Obrázek 14: Zobrazení logovacích informací s různou úrovní.
9. Nastavení minimální úrovně logovacích operací
Minimální úroveň logovacích informací bude odlišná pro vývoj (kdy budeme vyžadovat podrobnější informace) a jiná pro produkční nasazení. Pro specifikaci minimální úrovně, která se ještě zobrazuje, slouží funkce SetGlobalLevel:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
Příklad použití (uvedený jen pro úplnost):
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = true func main() { zerolog.SetGlobalLevel(zerolog.InfoLevel) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Trace(). Str("level", "trace"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Debug(). Str("level", "debug"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Info(). Str("level", "info"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Warn(). Str("level", "warn"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Error(). Str("level", "error"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Fatal(). Str("level", "fatal"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Panic(). Str("level", "panic"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Info().Msg("Finished") }
10. Logování bez specifikace úrovně
V případě, že se u jednodušších aplikací nechcete rozhodovat, jaké logovací úrovně zvolit, lze celou situaci zjednodušit a úrovně vůbec nepoužívat. V tomto případě se pouze použije funkce Log namísto Info, Error, Fatal atd.:
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
Opět si ukažme úplný zdrojový kód takto upraveného demonstračního příkladu:
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = true func main() { zerolog.SetGlobalLevel(zerolog.InfoLevel) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Log(). Str("level", "trace"). Str("address", Address). Int("port", Port). Bool("enabled", Enabled).Msg("Server settings") log.Info().Msg("Finished") }
S výsledkem:
Obrázek 15: Zpráva vypsaná funkcí Log.
11. Naformátování zprávy
Prozatím jsme při logování zpráv měli možnost specifikovat pouze hodnoty atributů, kdežto samotná zpráva byla reprezentována řetězcem:
log.LogLevel().TypAtributu(jméno, hodnota).Msg(zpráva)
Tento řetězec lze samozřejmě zkonstruovat různými způsoby (konkatenací, s využitím fmt.Sprintf atd.), ovšem tyto alternativy je možné nahradit za metodu Msgf, což je spojení možností metody Msg (bez „f“ na konci) a standardní funkce fmt.Spritnf. Použití je zcela idiomatické:
log.LogLevel().TypAtributu(jméno, hodnota).Msg(formátovací řetězec, parametry, ...)
Podívejme se na praktické použití, konkrétně v tomto příkazu, v němž budeme chtít zobrazit adresu a port serveru:
err := http.ListenAndServe(where, nil) if err != nil { log.Error().Err(err).Msgf("Initialize server on address %s:%d", Address, Port) }
Nyní upravíme demonstrační příklad s HTTP servem tak, aby se v něm použila metoda Msgf:
package main import ( "fmt" "io" "os" "net/http" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = true func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Info(). Str("address", Address). Int("port", Port). Bool("enabled", Enabled). Msg("Server settings") if Enabled { http.HandleFunc("/", handler) where := fmt.Sprintf("%s:%d", Address, Port) err := http.ListenAndServe(where, nil) if err != nil { log.Error().Err(err).Msgf("Initialize server on address %s:%d", Address, Port) } } log.Info().Msg("Finished") } func handler(writer http.ResponseWriter, request *http.Request) { io.WriteString(writer, "Hello world!") }
S tímto výsledkem (pochopitelně v případě chyby):
Obrázek 16: Zpráva naformátovaná metodou Msgf.
12. Binární data v logovací zprávě?
Do logovací zprávy není vhodné posílat přímo binární data, protože některé kombinace bajtů mohou změnit chování terminálu. Namísto toho se jako jedno z možných řešení nabízí výstup binárních dat v přece jen čitelnějším hexadecimálním formátu, který je knihovnou zerolog taktéž podporován:
var rawData []byte = []byte("ěščřžýáíé") log.Info(). Str("level", "info"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings")
Tento výstup/formát je pochopitelně podporován pro všechny úrovně logovacích zpráv:
package main import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const Address = "localhost" const Port = 8080 const Enabled = true func main() { var rawData []byte = []byte("ěščřžýáíé") zerolog.SetGlobalLevel(zerolog.InfoLevel) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Started") log.Trace(). Str("level", "trace"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Debug(). Str("level", "debug"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Info(). Str("level", "info"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Warn(). Str("level", "warn"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Error(). Str("level", "error"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Fatal(). Str("level", "fatal"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Panic(). Str("level", "panic"). Str("address", Address). Int("port", Port). Hex("raw data", rawData). Bool("enabled", Enabled).Msg("Server settings") log.Info().Msg("Finished") }
Obrázek 17: Zprávy s binárními daty reprezentovanými v hexadecimálním formátu.
13. Specifikace barev textu na terminálu s knihovnou aurora
U některých aplikací je nutné přímo zvýraznit barvu určitého textu, důležité hodnoty atd. K tomuto účelu se ovšem logovací knihovna zerolog příliš nehodí. Můžeme ovšem namísto ní použít knihovny cfmt či aurora. Ve druhé části dnešního článku si ukážeme některé možnosti poskytované právě knihovnou aurora.
Nejprve se podívejme na způsob inicializace této knihovny a výpis textu v šesti základních barvách (posledními dvěma barvami jsou bílá a černá, ovšem v závislosti na nastavení pozadí terminálu se někdy jejich role prohazují):
package main import ( "fmt" "github.com/logrusorgru/aurora" ) func main() { // konstrukce "obarvovacího" objektu var colorizer aurora.Aurora colorizer = aurora.NewAurora(true) // výpis textu šesti základními barvami fmt.Println(colorizer.Red("Test")) fmt.Println(colorizer.Green("Test")) fmt.Println(colorizer.Blue("Test")) fmt.Println(colorizer.Cyan("Test")) fmt.Println(colorizer.Magenta("Test")) fmt.Println(colorizer.Yellow("Test")) }
Výsledek:
Obrázek 18: Text obarvený knihovnou aurora.
Obarvení textu lze ovšem zakázat, a to již v konstruktoru:
package main import ( "fmt" "github.com/logrusorgru/aurora" ) func main() { var colorizer aurora.Aurora colorizer = aurora.NewAurora(false) fmt.Println(colorizer.Red("Test")) fmt.Println(colorizer.Green("Test")) fmt.Println(colorizer.Blue("Test")) fmt.Println(colorizer.Cyan("Test")) fmt.Println(colorizer.Magenta("Test")) fmt.Println(colorizer.Yellow("Test")) }
Výsledek:
Obrázek 19: Text vypsaný knihovnou aurora, nyní ovšem bez obarvení.
14. Povolení či zákaz obarveného výstupu specifikací přepínače na příkazové řádce
Slušně napsané aplikace umožňují obarvení povolit či zakázat vhodným přepínačem zadávaným na příkazové řádce. Tuto techniku lze implementovat velmi snadno, což je ostatně ukázáno i v dalším demonstračním příkladu. Využita je zde standardní knihovna flag, kterou jsme si již v seriálu o jazyce Go popsali:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { fmt.Println(colorizer.Red("Test")) fmt.Println(colorizer.Green("Test")) fmt.Println(colorizer.Blue("Test")) fmt.Println(colorizer.Cyan("Test")) fmt.Println(colorizer.Magenta("Test")) fmt.Println(colorizer.Yellow("Test")) }
Výsledky při nepoužití a použití přepínače –colors:
Obrázek 20: Aplikace spuštěná bez přepínače –colors i s přepínačem –colors.
15. Dvě varianty specifikace stylu písma na terminálu
Knihovna aurora umožňuje specifikaci stylu písma – tučné, kurzíva, podtržené, blikající, přeškrtnuté atd. – ovšem prakticky žádný emulátor terminálu nedokáže všechny zmíněné styly zobrazit. Relativně nejvíce podporované je tučné písmo, které se zapíná takto:
colorizer.Bold(text, který má být zobrazen tučně)
Možná je i kombinace tohoto příkazu se specifikací barvy, například:
colorizer.Bold(colorizer.Red("Test"))
Alternativní způsob zápisu vypadá takto:
colorizer.Red("Test").Bold()
V dalším demonstračním příkladu se nejprve zobrazí text v šesti základních barvách a posléze tentýž text, ovšem tučně:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { fmt.Println(colorizer.Red("Test")) fmt.Println(colorizer.Green("Test")) fmt.Println(colorizer.Blue("Test")) fmt.Println(colorizer.Cyan("Test")) fmt.Println(colorizer.Magenta("Test")) fmt.Println(colorizer.Yellow("Test")) fmt.Println() fmt.Println(colorizer.Bold(colorizer.Red("Test"))) fmt.Println(colorizer.Bold(colorizer.Green("Test"))) fmt.Println(colorizer.Bold(colorizer.Blue("Test"))) fmt.Println(colorizer.Bold(colorizer.Cyan("Test"))) fmt.Println(colorizer.Bold(colorizer.Magenta("Test"))) fmt.Println(colorizer.Bold(colorizer.Yellow("Test"))) }
Obrázek 21: Kombinace tučného písma se specifikací barvy.
Použít je pochopitelně možné i alternativní zápis tučného písma:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { fmt.Println(colorizer.Red("Test")) fmt.Println(colorizer.Green("Test")) fmt.Println(colorizer.Blue("Test")) fmt.Println(colorizer.Cyan("Test")) fmt.Println(colorizer.Magenta("Test")) fmt.Println(colorizer.Yellow("Test")) fmt.Println() fmt.Println(colorizer.Red("Test").Bold()) fmt.Println(colorizer.Green("Test").Bold()) fmt.Println(colorizer.Blue("Test").Bold()) fmt.Println(colorizer.Cyan("Test").Bold()) fmt.Println(colorizer.Magenta("Test").Bold()) fmt.Println(colorizer.Yellow("Test").Bold()) }
Obrázek 22: Opět kombinace tučného písma se specifikací barvy.
16. Změna barvy pozadí textu
Podobně jako barvu textu lze měnit i barvu pozadí, ovšem s tím, že některé terminály povolují použít pouze osm barev pozadí (a nikoli šestnáct) – je nutné otestovat v praxi. Pro specifikaci barvy pozadí se používají funkce BgJménoBarvy, například BgRed, BgGreen atd. Ostatně se podívejme na další demonstrační příklad s ukázkou:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { fmt.Println(colorizer.BgRed("Test")) fmt.Println(colorizer.BgGreen("Test")) fmt.Println(colorizer.BgBlue("Test")) fmt.Println(colorizer.BgCyan("Test")) fmt.Println(colorizer.BgMagenta("Test")) fmt.Println(colorizer.BgYellow("Test")) fmt.Println() fmt.Println(colorizer.BgRed("Test").Bold()) fmt.Println(colorizer.BgGreen("Test").Bold()) fmt.Println(colorizer.BgBlue("Test").Bold()) fmt.Println(colorizer.BgCyan("Test").Bold()) fmt.Println(colorizer.BgMagenta("Test").Bold()) fmt.Println(colorizer.BgYellow("Test").Bold()) }
S výsledkem:
Obrázek 23: Šest základních barev pozadí i s alternativní formou využívající tučný text.
17. Výstup ve zvolené úrovni šedi (grayscale)
Většina terminálů podporuje kromě osmi základních barev i jejich světlejší varianty (viz též tento přehledový článek). Navíc jsou u většiny terminálů podporovány i stupně šedi, kterých je 24 (kódy těchto barev jsou v rozmezí 232 až 255). V dalším příkladu je ukázáno, jak se používá metoda Gray pro specifikaci úrovně šedi textu (tedy popředí):
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { for i := uint8(0); i < 24; i++ { message := fmt.Sprintf("Grayscale %d", i) fmt.Println(colorizer.Gray(i, message)) } }
Obrázek 24: Text v 24 úrovních šedi.
Prakticky stejným způsobem, ovšem s využitím metody BgGray, se určí jeden z 24 úrovní šedi pro pozadí textu:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { for i := uint8(0); i < 24; i++ { message := fmt.Sprintf("Grayscale %d", i) fmt.Println(colorizer.BgGray(i, message)) } }
Obrázek 25: Text v 24 úrovních šedi pro pozadí.
Barvu pozadí i popředí lze pochopitelně kombinovat, což platí i pro úrovně šedi. Povšimněte si triku potřebného pro současné nastavení barvy popředí i barvy pozadí:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { for j := uint8(24); j > 0; j-- { for i := uint8(0); i < 24; i += 2 { message := fmt.Sprintf(" [%2d %2d] ", i, j) fmt.Print(colorizer.BgGray(j, colorizer.Gray(i, message))) } fmt.Println() } }
Výsledek by měl vypadat následovně:
Obrázek 26: Kombinace barvy popředí a pozadí.
18. Sada standardních 216 barev terminálu
Z de-facto standardní barvové palety textových terminálů nám zbývá 256–16–24=216 barev, které lze vybrat pro popředí metodou Index a pro pozadí metodou BgIndex. Opět jsou většinou umožněny i kombinace těchto barev, což využijeme v dnes již posledním demonstračním příkladu:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { for j := uint8(16); j < 231; j += 4 { for i := uint8(16); i < 231; i += 2 { fmt.Print(colorizer.Index(i, colorizer.BgIndex(j, "x"))) } fmt.Println() } }
Výsledek by měl vypadat následovně:
Obrázek 27: Kombinace barvy popředí a pozadí.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Tvorba aplikací a her s textovým uživatelským rozhraním s využitím knihovny Blessed
https://www.root.cz/clanky/tvorba-aplikaci-a-her-s-textovym-uzivatelskym-rozhranim-s-vyuzitim-knihovny-blessed/ - Tvorba aplikací a her s textovým rozhraním s knihovnou Blessed (dokončení)
https://www.root.cz/clanky/tvorba-aplikaci-a-her-s-textovym-rozhranim-s-knihovnou-blessed-dokonceni/ - ANSI Escape Code – Colors
https://en.wikipedia.org/wiki/ANSI_escape_code#Colors - A curated list of awesome Go frameworks, libraries and software
https://awesome-go.com/ - Aurora
https://github.com/logrusorgru/aurora - colourize
https://github.com/TreyBastian/colourize - go-colortext
https://github.com/daviddengcn/go-colortext - blessed na PyPi
https://pypi.org/project/blessed/ - blessed na GitHubu
https://github.com/jquast/blessed - Blessed documentation!
https://blessed.readthedocs.io/en/latest/ - termbox-go na GitHubu
https://github.com/nsf/termbox-go - termui na GitHubu
https://github.com/gizak/termui - blessed na GitHubu
https://github.com/chjj/blessed - blessed-contrib na GitHubu
https://github.com/yaronn/blessed-contrib - tui-rs na GitHubu
https://github.com/fdehau/tui-rs - asciigraph
https://github.com/guptarohit/asciigraph - Standardní balíček text/tabwriter
https://golang.org/pkg/text/tabwriter/ - Elastic tabstops: A better way to indent and align code
https://nickgravgaard.com/elastic-tabstops/ - ASCII Table Writer
https://github.com/olekukonko/tablewriter - TablePrinter
https://github.com/lensesio/tableprinter - go-pretty
https://github.com/jedib0t/go-pretty - What are the drawbacks of elastic tabstops?
https://softwareengineering.stackexchange.com/questions/137290/what-are-the-drawbacks-of-elastic-tabstops - Elastic tabstop editors and plugins
https://stackoverflow.com/questions/28652/elastic-tabstop-editors-and-plugins - Příkaz gofmt
https://golang.org/cmd/gofmt/ - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - zerolog
https://github.com/rs/zerolog - Zero Allocation JSON Logger na Go.doc
https://pkg.go.dev/github.com/rs/zerolog?utm_source=godoc - cfmt
https://github.com/mingrammer/cfmt - box-cli-maker
https://github.com/Delta456/box-cli-maker - Who uses zerolog
https://github.com/rs/zerolog/wiki/Who-uses-zerolog