Hlavní navigace

Jazyk Go a aplikace s vlastním příkazovým řádkem

11. 4. 2019
Doba čtení: 44 minut

Sdílet

 Autor: Go Lang
V dnešním článku o jazyku Go se seznámíme s knihovnou go-prompt, která se používá v aplikacích s vlastním příkazovým řádkem vybaveným funkcemi pro automatické doplňování příkazů, kontextovou nápovědu i historii příkazů.

Obsah

1. Jazyk Go a aplikace s vlastním příkazovým řádkem

2. Čtení příkazů s využitím funkce Scanln ze standardního balíčku fmt

3. Čtení ze standardního vstupu pomocí metody Reader.ReadString

4. Knihovna GNU Readline

5. Knihovna prompt_toolkit pro tvorbu aplikací s interaktivní smyčkou REPL v jazyku Python

6. Balíček go-prompt pro aplikace naprogramované v jazyku Go

7. Instalace balíčku go-prompt

8. Základní klávesové zkratky používané knihovnou go-prompt

9. Přečtení řádku s příkazy funkcí Input

10. Plnohodnotný příkazový řádek s vlastní historií příkazů

11. Zajištění automatického doplňování příkazů na základě statické tabulky

12. Kontextová nápověda na základě části zapsaného příkazu

13. Přidání nápovědy k jednotlivým příkazům

14. Rozsáhlejší příklad se všemi klíčovými slovy BASICu

15. Pokročilejší vyhledání vhodných příkazů na základě fuzzy filtru

16. Kontextová nápověda u příkazů, které se skládají ze dvou slov

17. Implementace kontextové nápovědy víceslovních příkazů

18. Úplný zdrojový kód příkladu s víceslovními příkazy

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Jazyk Go a aplikace s vlastním příkazovým řádkem

Již mnohokrát jsme si v seriálu o programovacím jazyku Go řekli, že se tento jazyk primárně používá pro tvorbu síťových utilit, mikroslužeb či dokonce ucelených webových aplikací. Je to ostatně logické, protože právě v těchto oblastech se využijí prakticky všechny užitečné vlastnosti tohoto programovacího jazyka, zejména pak podpora pro práci s gorutinami, komunikace mezi gorutinami s využitím kanálů a v neposlední řadě taktéž možnost přeložit nástroj naprogramovaný v jazyku Go do jediného binárního souboru (pro určenou platformu), který nevyžaduje prakticky žádnou instalaci (na rozdíl od aplikací v některých jiných jazycích, v nichž je nutné zajistit buď kompatibilní dynamicky linkované knihovny či dokonce celý runtime daného programovacího jazyka).

To však samozřejmě neznamená, že by se jazyk Go nemohl využívat i v dalších oblastech. Sice se (s poměrně velkou pravděpodobností) prozatím neprosadí například pro tvorbu her, ovšem naproti tomu nalezneme velké množství nástrojů naprogramovaných právě v jazyce Go, které se ovládají interaktivně příkazy zadávanými z příkazového řádku (command line).

Tyto aplikace mohou pracovat dvěma způsoby:

  1. Příkazy se specifikují vždy při spouštění aplikace/nástroje. V takovém případě aplikace/nástroj daný příkaz provede, vypíše případný výstup či chyby a ukončí se. Příkladem mohou být prakticky všechny základní nástroje Linuxu (či obecně Unixu): cp, ls atd., dále například git a z aplikací naprogramovaných v jazyku Go pak nástroj oc použitý pro práci s OpenShiftem. Pokud jsou takové aplikace naprogramované v Go, nepotřebují kromě zpracování příkazové řádky žádnou další podpůrnou knihovnu.
  2. Ve druhém typu aplikací se používá vlastní interaktivní smyčka REPL (Read-Eval-Print Loop), tj. aplikace se spustí, vypíše tzv. výzvu (prompt) uživateli, akceptuje zadané příkazy, nějakým způsobem je vykoná a opět vypíše výzvu. Zde je již většinou nutné investovat více času na přípravu prostředí aplikace, protože dnes uživatelé (po právu) vyžadují, aby nástroj s vlastní interaktivní smyčkou REPL podporoval historii příkazů, vyhledávání v historii, obarvení vstupů, podporu pro automatické doplňování příkazů atd. atd. A právě tímto typem aplikací, jejichž typickým zástupcem je samotný BASH či jakýkoli jiný shell, se dnes budeme zabývat.

Obrázek 1: Příkladem aplikace s interaktivní smyčkou REPL je IPython.

Pro aplikace s interaktivní smyčkou REPL programované v jazyku Go vzniklo několik knihoven, které nabízí některé vyžadované funkce. Jedná se například o tyto knihovny:

2. Čtení příkazů s využitím funkce Scanln ze standardního balíčku fmt

Podívejme se nejprve na některé prostředky, které nám pro čtení textu z příkazového řádku nabízí standardní balíčky programovacího jazyka Go. Nutno říci, že se jedná o základní funkce, od nichž nelze očekávat uživatelskou přívětivost – například nebudou fungovat ani příkazy pro pohyb kurzoru v rámci vstupního řádku, o neexistenci historie příkazů či kontextové nápovědy ani nemluvě. Jednou z funkcí, které nalezneme v základní knihovně jazyka Go, je funkce pojmenovaná Scanln z balíčku fmt, viz též https://golang.org/pkg/fmt/#Scanln:

func Scanln(a ...interface{}) (n int, err error)
Poznámka: nedejte se zmýlit tím, že tato funkce je umístěna v balíčku fmt, protože tento balíček obsahuje funkce pro formátovaný výstup, ale i vstup.

Funkce Scanln se pokusí načíst celý textový řádek (ukončený Enterem) ze standardního vstupu a následně v tomto řádku nalezne jednotlivé elementy, které jsou od sebe odděleny mezerou. Tyto elementy uloží do proměnných, jejichž reference jsou předány v seznamu parametrů. Navíc funkce Scanln vrátí počet skutečně přečtených elementů a popř. i objekt reprezentující vstupně-výstupní chybu, která může při práci nastat. Můžeme tedy například psát:

var s string
 
n, err := fmt.Scanln(&s)
 
if n != 1 || err != nil {
        println("Error reading login")
}

Vidíme, že tato funkce nejenom že načítá text ze standardního vstupu, ale navíc se snaží text nějakým způsobem „rozkouskovat“ a následně částečně zpracovat. V některých případech se může jednat o očekávané chování, ovšem mnoho aplikací vyžaduje větší kontrolu nad zapisovaným textem, takže v těchto aplikacích nebude možné Scanln použít.

Podívejme se nyní na zdrojový kód dnešního prvního demonstračního příkladu, který si po svém spuštění vyžádá zadání jména a hesla, která se pokusí načíst do proměnných login a password:

package main
 
import "fmt"
 
func main() {
        var login string
        var password string
 
        print("Login: ")
        n, err := fmt.Scanln(&login)
        if n != 1 || err != nil {
                println("Error reading login")
        }
 
        print("Password: ")
        n, err = fmt.Scanln(&password)
        if n != 1 || err != nil {
                println("Error reading password")
        }
 
        println(login)
        println(password)
}

Sami si vyzkoušejte chování aplikace ve chvíli, kdy se na řádek zadá více slov oddělených mezerou či naopak pokud se nezadá žádný text (nebo dokonce jen mezery).

Obrázek 2: Zadání jména a hesla v interaktivní aplikaci používající čtení ze standardního vstupu.

Na dalším obrázku je ukázáno, co se stane ve chvíli, kdy uživatel stiskne šipku doleva ve chvíli, kdy chce upravit již zapsaný text. Aplikace se evidentně nechová příliš uživatelsky přívětivě:

Obrázek 3: Po stisku klávesy „šipka doleva“ se ve skutečnosti do textu vloží sekvence znaků poslaná terminálem.

3. Čtení ze standardního vstupu pomocí metody Reader.ReadString

V případě, že nám z nějakého důvodu nevyhovuje chování výše popsané funkce Scanln a budeme potřebovat více kontroly nad textem načítaným ze standardního vstupu, je možné použít metodu ReadString, která je dostupná pro objekty (resp. přesněji řečeno datové struktury) typu Reader, viz též https://golang.org/pkg/bu­fio/#Reader.ReadString. Hlavička této metody vypadá následovně:

func (b *Reader) ReadString(delim byte) (string, error)

Povšimněte si toho, že funkce akceptuje parametr delim, do kterého se zapisuje kód ASCII znaku (ne Unicode!) sloužící jako oddělovač či ukončovač záznamu, ve skutečnosti se totiž na vstupu pracuje se sekvencí bajtů a nikoli Unikódových znaků. V případě, že budeme chtít přečíst celý textový řádek, bude tímto oddělovačem pochopitelně znak pro konec řádku:

reader.ReadString('\n')

Metoda ReadString vrací jak přečtený text (ve formě běžného řetězce, nyní již v Unicode), tak i případnou hodnotu s popisem chyby. Správně by se tedy mělo každé načtení ze standardního vstupu testovat, a to například následujícím způsobem:

reader := bufio.NewReader(os.Stdin)
 
login, err := reader.ReadString('\n')
if err != nil {
        println("Error reading login")
}

Opět se samozřejmě podíváme na zdrojový kód demonstračního příkladu, který načte dva řetězce ze standardního vstupu:

package main
 
import (
        "bufio"
        "os"
)
 
func main() {
        reader := bufio.NewReader(os.Stdin)
 
        print("Login: ")
        login, err := reader.ReadString('\n')
        if err != nil {
                println("Error reading login")
        }
 
        print("Password: ")
        password, err := reader.ReadString('\n')
        if err != nil {
                println("Error reading password")
        }
 
        println(login)
        println(password)
}

Samozřejmě ani v tomto případě nemůžeme očekávat žádné pokročilejší funkce – posuny kurzoru šipkami, přesuny textu, historii příkazů atd. Ve chvíli, kdy je tato funkcionalita vyžadována, se musíme uchýlit k externím knihovnám popř. aplikaci „obalit“ příkazem rlwrap.

Obrázek 4: Vstup textu s nabodeníčky je sice možný, ovšem posun kurzoru šipkami již nikoli. Navíc se načítá i samotný znak pro konec řádku, který však není nijak problematické v případě potřeby odstranit.

4. Knihovna GNU Readline

Pro vylepšení příkazové řádky o další vlastnosti se v mnoha aplikacích (zejména těch naprogramovaných v jazycích C a C++) používá známá knihovna GNU Readline, která dokáže zajistit například:

  1. Možnost použití kurzorových šipek pro pohyb na řádku se zapisovaným vstupním textem.
  2. Příkazy do určité míry emulující textové editory Emacs či Vi (v Emacs režimu například Ctrl+A pro přechod na začátek řádku, Ctrl+W pro smazání slova atd.)
  3. Doplňování příkazů (vstupních dat).
  4. Vyhledávání v historii již zadaných příkazů (vstupních dat). Tuto funkcionalitu je možné zajistit i externě s využitím knihovnyrlwrap.

Knihovnu GNU Readline samozřejmě můžeme použít i v jazyce Go, ovšem v žádném případě se nejedná o ideální řešení, protože se přidává závislost na externích knihovnách a nástrojích, což například může dělat potíže při tvorbě distribučních souborů s vyvíjenou aplikací či nástrojem. Dále popsaný balíček go-prompt tuto závislost odstraňuje, protože je kompletně naprogramován v Go. Existuje i několik dalších balíčků, jejichž cílem je reimplementovat funkcionalitu GNU Readline v čistém Go (viz též úvodní kapitolu, pozor však, že jeden ze zmíněných balíčků je wrapper právě nad GNU Readline).

Poznámka: ve skutečnosti se můžeme setkat i s tím, že se namísto knihovny GNU Readline používá knihovna libedit (editline, viz též http://thrysoee.dk/editline/). Z pohledu uživatele spočívá nejviditelnější rozdíl v tom, že tato knihovna využívá odlišný konfigurační soubor. Dalším rozdílem je odlišná licence, která může vyhovovat lépe, než GPL.

5. Knihovna prompt_toolkit pro tvorbu aplikací s interaktivní smyčkou REPL v jazyku Python

Pro aplikace s příkazovým řádkem vytvářené v programovacím jazyku Python je určena velmi užitečná knihovna, která se jmenuje prompt_toolkit (viz https://github.com/prompt-toolkit/python-prompt-toolkit). Název této knihovny sice naznačuje, že slouží pouze pro implementaci vstupního (či příkazového) řádku do aplikací, to však ve skutečnosti není vše. Tato knihovna například umožňuje využít víceřádkový vstupní text, dovoluje použití myši (kromě implicitní funkce myši v terminálu pro operace výběru textu s následným copy & paste) a dokonce obsahuje sadu základních prvků uživatelského rozhraní (takzvaných widgetů), mezi něž patří například toolbary, menu, checkboxy, tlačítka, či dialogy. Díky tomu lze tuto knihovnu použít i pro tvorbu aplikací s plnohodnotným textovým uživatelským rozhraním (ne nepodobným starodávnému TurboVision).

Obrázek 5: Pohled na textové uživatelské rozhraní debuggeru pudb.

6. Balíček go-prompt pro aplikace naprogramované v jazyku Go

Výše zmíněnou knihovnou prompt_toolkit se inspirovali autoři knihovny go-prompt, která je určena pro použití v aplikacích naprogramovaných v jazyku Go. Jedná se o knihovnu přenositelnou na různé operační systémy (pochopitelně včetně všech tří nejpoužívanějších desktopových systémů) a aplikace, které tuto knihovnu používají, by mělo být možné provozovat na prakticky všech emulátorech terminálu (otestováno především s XTermem). Tato knihovna uživatelům nabízí následující funkce:

  1. Plnohodnotnou editaci na příkazovém řádku, samozřejmě včetně možnosti přesunu kurzoru s využitím příkazů Ctrl+znak, specializovaných kláves Home, End atd.
  2. Mazání textu před kurzorem, za kurzorem, smazání slova apod.
  3. Automatické doplňování příkazů na základě tabulky, kterou je možné dynamicky měnit.
  4. Kontextovou nápovědu s dostupnými příkazy, a to včetně popisu jednotlivých příkazů.
  5. Historii již zapsaných příkazů.

V navazujících kapitolách si ukážeme základy práce s touto knihovnou při tvorbě aplikací s plnohodnotným interaktivním příkazovým řádkem, který je stále jedním z nejdokonalejších nástrojů určených pro komunikaci mezi člověkem a počítačem [1].

7. Instalace balíčku go-prompt

Balíček go-prompt nainstalujeme naprosto stejným způsobem, jako jakýkoli jiný externí balíček určený pro programovací jazyk Go. Pro instalaci použijeme příkaz go get, a to v následující podobě:

$ go get
Poznámka: podobně jako u instalací dalších balíčků se ujistěte, že zda je korektně nastavena proměnná prostředí GOPATH a že se příkaz go get spouští v adresáři, na který GOPATH ukazuje.

Po chvíli by se měly stáhnout všechny potřebné závislé balíčky a knihovna go-prompt by se měla přeložit. Obsah adresáře, na který ukazuje proměnná GOPATH by měl obsahovat mj. i následující podadresáře:

.
├── bin
├── pkg
│   └── linux_amd64
│       └── github.com
│           └── c-bata
└── src
    └── github.com
        ├── c-bata
        │   └── go-prompt
        │       ├── completer
        │       ├── _example
        │       │   ├── exec-command
        │       │   ├── http-prompt
        │       │   ├── live-prefix
        │       │   └── simple-echo
        │       │       └── cjk-cyrillic
        │       ├── internal
        │       │   ├── bisect
        │       │   ├── debug
        │       │   ├── strings
        │       │   └── term
        │       └── _tools
        │           ├── complete_file
        │           ├── sigwinch
        │           └── vt100_debug
        ├── mattn
        │   └── go-runewidth
        └── pkg
            └── term
                └── termios
Poznámka: povšimněte si, že se kromě vlastní knihovny nainstalovaly i balíčky s podporou různých terminálů a informací o terminálech (man terminfo).

Samozřejmě je ihned po instalaci dostupná i nápověda k balíčku:

$ godoc go-prompt
 
PACKAGE DOCUMENTATION
 
package prompt
    import "go-prompt"
...
...
...

či přímo nápověda k nějakému datovému typu:

$ godoc go-prompt Prompt
 
type Prompt struct {
    ASCIICodeBindings []ASCIICodeBind
    // contains filtered or unexported fields
}
 
    Prompt is core struct of go-prompt.
 
func New(executor Executor, completer Completer, opts ...Option) *Prompt
    New returns a Prompt with powerful auto-completion.
 
func (p *Prompt) Input() string
    Input just returns user input text.
 
func (p *Prompt) Run()
    Run starts prompt.

8. Základní klávesové zkratky používané knihovnou go-prompt

V knihovně go-prompt jsou deklarovány některé základní klávesové zkratky, které se snaží být do určité míry kompatibilní jak s BASHem (v režimu Emacs), tak i přímo s textovým editorem Emacs. Ovšem všechny zkratky jsou ve skutečnosti plně konfigurovatelné, takže je možné vytvořit si vlastní sadu příkazů – nutno ovšem říci, že pouze nemodálních (režim Vi/Vimu není a pravděpodobně ani nebude podporován).

Příkazy pro přesuny kurzoru

Základní příkazy pro přesun kurzoru používají kombinaci Ctrl+znak. V případě, že je terminál správně nakonfigurován, měly by fungovat i kurzorové šipky doleva/doprava a navíc i klávesy Home a End (se zřejmou funkcí):

Klávesa Význam
Ctrl+B přesun kurzoru na předchozí znak
šipka doleva přesun kurzoru na předchozí znak
   
Ctrl+F přesun kurzoru na další znak
šipka doprava přesun kurzoru na další znak
   
Ctrl+A přesun kurzoru na začátek řádku
Home přesun kurzoru na začátek řádku
   
Ctrl+E přesun kurzoru na konec řádku
End přesun kurzoru na konec řádku

Mazání textu, práce s kill ringem

Pro přesun části textu v rámci editovaného řádku se například v BASHi používá takzvaný kill ring, do něhož se smazaný text uloží. Pro vložení takto smazaného textu do jiné oblasti se používá operace nazvaná yank (odpovídá paste). V případě knihovny go-prompt jsou v současné verzi implementovány pouze příkazy pro smazání části textu, nikoli však příkaz yank pro jeho vložení na jiné místo (tato operace je však plánována do dalších verzí, popravdě ji není příliš složité doimplementovat):

Klávesa Význam
Ctrl+D smaže jeden znak na pozici kurzoru (pokud je ovšem na řádku nějaký obsah, jinak typicky ukončí vstup či celou aplikaci)
Delete smaže jeden znak na pozici kurzoru
   
Ctrl+H smaže jeden znak před kurzorem
Backspace smaže jeden znak před kurzorem
   
Ctrl+W smaže předchozí slovo
   
Ctrl+K smaže text od pozice kurzoru do konce řádku
Ctrl+U smaže text od začátku řádku do pozice kurzoru

Práce s historií dříve zadaných příkazů

Uživatelé mají k dispozici i historii zapsaných příkazů. K jejich vyvolání slouží několik klávesových zkratek vypsaných v následující tabulce. Prozatím zde však nenajdete zkratky pro vyhledávání v historii příkazů; tuto funkcionalitu je však možné v případě potřeby doprogramovat a pravděpodobně se s ní setkáme v další verzi knihovny go-prompt:

Klávesa Význam
Ctrl+P průchod historií – předchozí text/příkaz
šipka nahoru průchod historií – předchozí text/příkaz
   
Ctrl+N průchod historií – následující text/příkaz
šipka dolů průchod historií – předchozí text/příkaz
Poznámka: k historii příkazů mají vývojáři přístup přes rozhraní poskytované knihovnou go-prompt. Proto je například možné z historie odstranit neplatné příkazy, získat celou historii zapsaných příkazů a uložit ji do externího souboru atd.

9. Přečtení řádku s příkazy funkcí Input

Nyní si ukážeme základní funkcionalitu, kterou nám knihovna go-prompt nabízí. Bude se jednat o nejjednodušší možnou aplikaci, která pouze přečte dva textové údaje z příkazového řádku. Aplikace je napsána takovým způsobem, že jsou podporovány všechny výše zmíněné editační příkazy, ovšem nikoli historie příkazového řádku (nemáme totiž žádný objekt, který by historii držel). Základem je použití funkce Input:

login := prompt.Input("Login: ", completer)
password := prompt.Input("Password: ", completer)

Této funkci je nutné předat referenci na takzvaný completer, s jehož významem se setkáme v navazujících kapitolách.

Zdrojový kód příkladu vypadá následovně:

package main
 
import "github.com/c-bata/go-prompt"
 
func completer(in prompt.Document) []prompt.Suggest {
        return []prompt.Suggest{}
}
 
func main() {
        login := prompt.Input("Login: ", completer)
        password := prompt.Input("Password: ", completer)
        println(login)
        println(password)
}

Obrázek 6: Povšimněte si, že se stiskem klávesy Ctrl+A či Home můžeme bez problémů přesunout na začátek vstupního řádku. Všechny ostatní editační příkazy budou taktéž funkční.

10. Plnohodnotný příkazový řádek s vlastní historií příkazů

Aby bylo možné použít příkazový řádek s vlastní historií příkazů, je možné aplikaci poněkud upravit, a to takovým způsobem, že se reakce na příkazy zadané uživatelem přesune do samostatné funkce. Tato funkce se většinou nazývá executor, i když nám samozřejmě nic nebrání použít odlišné jméno:

func executor(t string) {
        println(t)
}

Doplníme i funkci se jménem completer, která bude vracet prázdnou tabulku s návrhy na doplnění celých slov s příkazy:

func completer(in prompt.Document) []prompt.Suggest {
        return []prompt.Suggest{}
}

Nyní již můžeme zkonstruovat objekt (datovou strukturu) typu Prompt a spustit interní smyčku, která bude jednotlivé příkazy načítat a pro každý příkaz zavolá výše zmíněnou callback funkci executor:

p := prompt.New(executor, completer)
p.Run()

Obrázek 7: Příkazový řádek začíná výzvou (prompt). Ukončení aplikace lze docílit stiskem klávesy Ctrl+D.

Úplný kód příkladu, který implementuje plnohodnotný příkazový řádek i s historií, je dostupný na adrese https://github.com/tisnik/go-root/blob/master/article20/04_prom­pt.go:

package main
 
import "github.com/c-bata/go-prompt"
 
func executor(t string) {
        println(t)
}
 
func completer(in prompt.Document) []prompt.Suggest {
        return []prompt.Suggest{}
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

11. Zajištění automatického doplňování příkazů na základě statické tabulky

Jednou z velmi užitečných funkcí nabízených knihovnou go-prompt je automatické doplňování příkazů, a to bez toho, aby uživatel musel použít nějakou specializovanou klávesovou zkratku – příkazy se nabízí automaticky přímo při psaní. Taktéž se přímo při psaní zobrazuje tabulka s dostupnými příkazy. Interně je tato vlastnost zajištěna neustálým voláním callback funkce completer, které se předá hodnota typu Document obsahující informace jak o zapsaném textu, tak i o pozici kurzoru v rámci tohoto textu. V tom nejjednodušším případě může callback funkce completer vracet vždy stejnou tabulku s příkazy, nezávisle na tom, co uživatel zapisuje. Informace o dostupných příkazech jsou reprezentovány hodnotami typu Suggest:

func completer(in prompt.Document) []prompt.Suggest {
        return []prompt.Suggest{
                {Text: "help"},
                {Text: "exit"},
                {Text: "quit"},
        }
}

Aby byl příklad úplný, doplníme i kód funkce executor, který bude jednoduchý – na každý podporovaný příkaz nějakým způsobem zareagujeme. Aplikace se buď ukončí po příkazech „exit“ a „quit“, nebo se zobrazí nápověda popř. informace o tom, že se jedná o neznámý příkaz:

func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                os.Exit(0)
        case "help":
                println("HELP:\nexit\nquit")
        default:
                println("Nothing happens")
        }
}

Nyní se podívejme na chování aplikace:

Obrázek 8: Po spuštění se pouze očekává příkaz, žádná nápověda se nevypíše.

Obrázek 9: Vrácením kurzoru se zobrazí tabulka se všemi dostupnými příkazy.

Obrázek 10: Nápověda prozatím není kontextová, ovšem klávesou Tab lze příkaz doplnit.

Obrázek 11: Doplněný příkaz se zobrazí odlišnou barvou.

Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article20/05_ba­sic_completer.go:

package main
 
import (
        "github.com/c-bata/go-prompt"
        "os"
)
 
func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                os.Exit(0)
        case "help":
                println("HELP:\nexit\nquit")
        default:
                println("Nothing happens")
        }
}
 
func completer(in prompt.Document) []prompt.Suggest {
        return []prompt.Suggest{
                {Text: "help"},
                {Text: "exit"},
                {Text: "quit"},
        }
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

12. Kontextová nápověda na základě části zapsaného příkazu

Předchozí příklad pochopitelně nebyl dokonalý, protože nezávisle na vstupu zapsaném uživatelem nabízel stále stejné příkazy. Ovšem díky funkci FilterHasPrefix je možné z tabulky se všemi dostupnými příkazy vyfiltrovat pouze ty, které začínají zadaným řetězcem. Povšimněte si, jakým způsobem se získá text umístěný před kurzorem (to je jeden z důvodů, proč se funkci completer nepředává pouhý řetězec, ale objekt typu Document:

func completer(in prompt.Document) []prompt.Suggest {
        s := []prompt.Suggest{
                {Text: "help"},
                {Text: "exit"},
                {Text: "quit"},
        }
        return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
}
Poznámka: posledním parametrem funkce FilterHasPrefix se určuje, zda se mají ignorovat velikosti znaků či nikoli.

Po této nepatrné úpravě se chování aplikace radikálně změní, protože bude možné použít plnohodnotné doplňování, kontextovou nápovědu atd.:

Obrázek 12: Stiskem q a klávesy Tab se automaticky doplní příkaz „quit“.

Obrázek 13: Tabulka je nyní již kontextová, protože nabízí jen relevantní příkazy.

Opět se podívejme na úplný zdrojový kód tohoto příkladu:

package main
 
import (
        "github.com/c-bata/go-prompt"
        "os"
)
 
func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                println("Quitting")
                os.Exit(0)
        case "help":
                println("HELP:\nexit\nquit")
        default:
                println("Nothing happens")
        }
}
 
func completer(in prompt.Document) []prompt.Suggest {
        s := []prompt.Suggest{
                {Text: "help"},
                {Text: "exit"},
                {Text: "quit"},
        }
        return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

13. Přidání nápovědy k jednotlivým příkazům

Rozšířením datové struktury Suggest o položku Description je možné zajistit zobrazení nápovědy k jednotlivým příkazům, které jsou nabízeny v kontextovém okně. Úprava zdrojového kódu je snadná:

s := []prompt.Suggest{
        {Text: "help", Description: "show help with all commands"},
        {Text: "exit", Description: "quit the application"},
        {Text: "quit", Description: "quit the application"},
}

Výsledek by měl vypadat takto:

Obrázek 14: Zobrazení všech příkazů i s nápovědou.

Zdrojový kód takto upraveného demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article20/07_com­pletion_description.go:

package main
 
import (
        "github.com/c-bata/go-prompt"
        "os"
)
 
func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                os.Exit(0)
        case "help":
                println("HELP:\nexit\nquit")
        default:
                println("Nothing happens")
        }
        return
}
 
func completer(in prompt.Document) []prompt.Suggest {
        s := []prompt.Suggest{
                {Text: "help", Description: "show help with all commands"},
                {Text: "exit", Description: "quit the application"},
                {Text: "quit", Description: "quit the application"},
        }
        return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

14. Rozsáhlejší příklad se všemi klíčovými slovy BASICu

Nyní již máme k dispozici všechny informace nutné pro vytvoření rozsáhlejšího příkladu, který uživatelům bude na příkazovém řádku nabízet všechny příkazy, funkce a klíčová slova Atari BASICu. Jak seznam příkazů, tak i jejich popis byl získán z Wikipedie. Tento demonstrační příklad by se měl chovat následovně:

Obrázek 15: Nabídka některých příkazů Atari BASICu.

V ostatních ohledech se tento příklad neliší od demonstračního kódu, s nímž jsme se seznámili v předchozí kapitole, takže se podívejme na jeho výpis:

package main
 
import (
        "github.com/c-bata/go-prompt"
        "os"
)
 
func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                os.Exit(0)
        default:
                println("READY")
        }
        return
}
 
func completer(in prompt.Document) []prompt.Suggest {
        s := []prompt.Suggest{
                {Text: "ABS", Description: "Returns the absolute value of a number"},
                {Text: "ADR", Description: "Returns the address in memory of a variable (mostly used for machine code routines stored in variables)"},
                {Text: "AND", Description: "Logical conjunction"},
                {Text: "ASC", Description: "Returns the ATASCII value of a character"},
                {Text: "ATN", Description: "Returns the arctangent of a number"},
                {Text: "BYE", Description: "Transfers control to the internal Self Test program"},
                {Text: "CHR", Description: "Returns a character given an ATASCII value"},
                {Text: "CLOAD", Description: "Loads from cassette tape a tokenized program that was saved with CSAVE"},
                {Text: "CLOG", Description: "Returns the common logarithm of a number"},
                {Text: "CLOSE", Description: "Terminates pending transfers (flush) and closes an I/O channel"},
                {Text: "CLR", Description: "Clears variables' memory and program stack"},
                {Text: "COLOR", Description: "Chooses which logical color to draw in"},
                {Text: "COM", Description: "Implementation of MS Basic's COMMON was cancelled. Recognized but the code for DIM is executed instead"},
                {Text: "CONT", Description: "Resumes execution of a program after a STOP at the next line number (see STOP)"},
                {Text: "COS", Description: "Returns the cosine of a number"},
                {Text: "CSAVE", Description: "Saves to cassette tape a program in tokenized form (see CLOAD)"},
                {Text: "DATA", Description: "Stores data in lists of numeric or string values"},
                {Text: "DEG", Description: "Switches trigonometric functions to compute in degrees (radians is the default mode) (see RAD)"},
                {Text: "DIM", Description: "Defines the size of a string or array (see COM)"},
                {Text: "DOS", Description: "Transfers control to the Disk Operating System (DOS); if DOS was not loaded, same as BYE"},
                {Text: "DRAWTO", Description: "Draws a line to given coordinates"},
                {Text: "END", Description: "Finishes execution of the program, closes open I/O channels and stops any sound"},
                {Text: "ENTER", Description: "Loads and merges into memory a plain text program from an external device, usually from cassette tape or disk (see LIST)"},
                {Text: "EXP", Description: "Exponential function"},
                {Text: "FOR", Description: "Starts a for loop"},
                {Text: "FRE", Description: "Returns the amount of free memory in bytes"},
                {Text: "GET", Description: "Reads one byte from an I/O channel (see PUT)"},
                {Text: "GOSUB", Description: "Jumps to a subroutine at a given line in the program, placing the return address on the stack (see POP and RETURN)"},
                {Text: "GOTO", Description: "and GO TO  Jumps to a given line in the program. GOTO can be omitted in IF ... THEN GOTO ..."},
                {Text: "GRAPHICS", Description: "Sets the graphics mode"},
                {Text: "IF", Description: "Executes code depending on whether a condition is true or not"},
                {Text: "INPUT", Description: "Retrieves a stream of text from an I/O channel; usually data from keyboard (default), cassette tape or disk"},
                {Text: "INT", Description: "Returns the floor of a number"},
                {Text: "LEN", Description: "Returns the length of a string"},
                {Text: "LET", Description: "Assigns a value to a variable. LET can be omitted"},
                {Text: "LIST", Description: "Lists (all or part of) the program to screen (default), printer, disk, cassette tape, or any other external device (see ENTER)"},
                {Text: "LOAD", Description: "Loads a tokenized program from an external device; usually a cassette tape or disk (see SAVE)"},
                {Text: "LOCATE", Description: "Stores the logical color or ATASCII character at given coordinates"},
                {Text: "LOG", Description: "Returns the natural logarithm of a number"},
                {Text: "LPRINT", Description: "Prints text to a printer device (same result can be achieved with OPEN, PRINT and CLOSE statements)"},
                {Text: "NEW", Description: "Erases the program and all the variables from memory; automatically executed before a LOAD or CLOAD"},
                {Text: "NEXT", Description: "Continues the next iteration of a FOR loop"},
                {Text: "NOT", Description: "Logical negation"},
                {Text: "NOTE", Description: "Returns the current position on an I/O channel"},
                {Text: "ON", Description: "A computed goto - performs a jump based on the value of an expression"},
                {Text: "OPEN", Description: "Initialises an I/O channel"},
                {Text: "OR", Description: "Logical disjunction"},
                {Text: "PADDLE", Description: "Returns the position of a paddle controller"},
                {Text: "PEEK", Description: "Returns the value at an address in memory"},
                {Text: "PLOT", Description: "Draws a point at given coordinates"},
                {Text: "POINT", Description: "Sets the current position on an I/O channel"},
                {Text: "POKE", Description: "Sets a value at an address in memory"},
                {Text: "POP", Description: "Removes a subroutine return address from the stack (see GOSUB and RETURN)"},
                {Text: "POSITION", Description: "Sets the position of the graphics cursor"},
                {Text: "PRINT", Description: "and ?     Writes text to an I/O channel; usually to screen (default), printer, cassette tape or disk (see LPRINT and INPUT)"},
                {Text: "PTRIG", Description: "Indicates whether a paddle trigger is pressed or not"},
                {Text: "PUT", Description: "Writes one byte to an I/O channel (see GET)"},
                {Text: "RAD", Description: "Switches trigonometric functions to compute in radians (see DEG)"},
                {Text: "READ", Description: "Reads data from a DATA statement"},
                {Text: "REM", Description: "Marks a comment in a program"},
                {Text: "RESTORE", Description: "Sets the position of where to read data from a DATA statement"},
                {Text: "RETURN", Description: "Ends a subroutine, effectively branching to the line immediately following the calling GOSUB (see GOSUB and POP)"},
                {Text: "RND", Description: "Returns a pseudorandom number"},
                {Text: "RUN", Description: "Starts execution of a program, optionally loading it from an external device (see LOAD)"},
                {Text: "SAVE", Description: "Writes a tokenized program to an external device; usually a cassette tape or disk (see LOAD)"},
                {Text: "SETCOLOR", Description: "Maps a logical color to a physical color"},
                {Text: "SGN", Description: "Returns the signum of a number"},
                {Text: "SIN", Description: "Returns the sine of a number"},
                {Text: "SOUND", Description: "Starts or stops playing a tone on a sound channel (see END)"},
                {Text: "SQR", Description: "Returns the square root of a number"},
                {Text: "STATUS", Description: "Returns the status of an I/O channel"},
                {Text: "STEP", Description: "Indicates the increment used in a FOR loop"},
                {Text: "STICK", Description: "Returns a joystick position"},
                {Text: "STOP", Description: "Stops the program, allowing later resumption (see CONT)"},
                {Text: "STRIG", Description: "Indicates whether a joystick trigger is pressed or not"},
                {Text: "STR", Description: "Converts a number to string form"},
                {Text: "THEN", Description: "Indicates the statements to execute if the condition is true in an IF statement"},
                {Text: "TO", Description: "Indicates the limiting condition in a FOR statement"},
                {Text: "TRAP", Description: "Sets to jump to a given program line if an error occurs (TRAP 40000 cancels this order)"},
                {Text: "USR", Description: "Calls a machine code routine, optionally with parameters"},
                {Text: "VAL", Description: "Returns the numeric value of a string"},
                {Text: "XIO", Description: "General-purpose I/O routine (from Fill screen to Rename file to Format disk instructions) "},
                {Text: "exit", Description: "Quit the application"},
                {Text: "quit", Description: "Quit the application"},
        }
        if in.GetWordBeforeCursor() == "" {
                return nil
        } else {
                return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
        }
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

15. Pokročilejší vyhledání vhodných příkazů na základě fuzzy filtru

V mnoha případech je výhodnější změnit způsob výběru nabízených příkazů. Namísto seznamu příkazů, které začínají určitým řetězcem lze použít takzvaný fuzzy filtr, který vrátí všechny příkazy obsahující zadanou sekvenci znaků (v daném pořadí). Například pro řetězec „PN“ se mohou vrátit příkazy PRINT, LPRINT, POINT a POSITION, ale i OPEN apod. Úprava funkce completer je snadná – pouze zavoláme funkci FilterFuzzy namísto funkce FilterHasPrefix:

func completer(in prompt.Document) []prompt.Suggest {
        if in.GetWordBeforeCursor() == "" {
                return nil
        } else {
                return prompt.FilterFuzzy(suggestions, in.GetWordBeforeCursor(), true)
        }
}

Obrázek 16: Všech šest příkazů, které jsou zobrazeny v kontextové nápovědě, skutečně obsahuje znaky „C“ a „L“ v uvedeném pořadí.

Následuje výpis zdrojového kódu tohoto příkladu:

package main
 
import (
        "github.com/c-bata/go-prompt"
        "os"
)
 
var suggestions = []prompt.Suggest{
        {Text: "ABS", Description: "Returns the absolute value of a number"},
        {Text: "ADR", Description: "Returns the address in memory of a variable (mostly used for machine code routines stored in variables)"},
        {Text: "AND", Description: "Logical conjunction"},
        {Text: "ASC", Description: "Returns the ATASCII value of a character"},
        {Text: "ATN", Description: "Returns the arctangent of a number"},
        {Text: "BYE", Description: "Transfers control to the internal Self Test program"},
        {Text: "CHR", Description: "Returns a character given an ATASCII value"},
        {Text: "CLOAD", Description: "Loads from cassette tape a tokenized program that was saved with CSAVE"},
        {Text: "CLOG", Description: "Returns the common logarithm of a number"},
        {Text: "CLOSE", Description: "Terminates pending transfers (flush) and closes an I/O channel"},
        {Text: "CLR", Description: "Clears variables' memory and program stack"},
        {Text: "COLOR", Description: "Chooses which logical color to draw in"},
        {Text: "COM", Description: "Implementation of MS Basic's COMMON was cancelled. Recognized but the code for DIM is executed instead"},
        {Text: "CONT", Description: "Resumes execution of a program after a STOP at the next line number (see STOP)"},
        {Text: "COS", Description: "Returns the cosine of a number"},
        {Text: "CSAVE", Description: "Saves to cassette tape a program in tokenized form (see CLOAD)"},
        {Text: "DATA", Description: "Stores data in lists of numeric or string values"},
        {Text: "DEG", Description: "Switches trigonometric functions to compute in degrees (radians is the default mode) (see RAD)"},
        {Text: "DIM", Description: "Defines the size of a string or array (see COM)"},
        {Text: "DOS", Description: "Transfers control to the Disk Operating System (DOS); if DOS was not loaded, same as BYE"},
        {Text: "DRAWTO", Description: "Draws a line to given coordinates"},
        {Text: "END", Description: "Finishes execution of the program, closes open I/O channels and stops any sound"},
        {Text: "ENTER", Description: "Loads and merges into memory a plain text program from an external device, usually from cassette tape or disk (see LIST)"},
        {Text: "EXP", Description: "Exponential function"},
        {Text: "FOR", Description: "Starts a for loop"},
        {Text: "FRE", Description: "Returns the amount of free memory in bytes"},
        {Text: "GET", Description: "Reads one byte from an I/O channel (see PUT)"},
        {Text: "GOSUB", Description: "Jumps to a subroutine at a given line in the program, placing the return address on the stack (see POP and RETURN)"},
        {Text: "GOTO", Description: "and GO TO  Jumps to a given line in the program. GOTO can be omitted in IF ... THEN GOTO ..."},
        {Text: "GRAPHICS", Description: "Sets the graphics mode"},
        {Text: "IF", Description: "Executes code depending on whether a condition is true or not"},
        {Text: "INPUT", Description: "Retrieves a stream of text from an I/O channel; usually data from keyboard (default), cassette tape or disk"},
        {Text: "INT", Description: "Returns the floor of a number"},
        {Text: "LEN", Description: "Returns the length of a string"},
        {Text: "LET", Description: "Assigns a value to a variable. LET can be omitted"},
        {Text: "LIST", Description: "Lists (all or part of) the program to screen (default), printer, disk, cassette tape, or any other external device (see ENTER)"},
        {Text: "LOAD", Description: "Loads a tokenized program from an external device; usually a cassette tape or disk (see SAVE)"},
        {Text: "LOCATE", Description: "Stores the logical color or ATASCII character at given coordinates"},
        {Text: "LOG", Description: "Returns the natural logarithm of a number"},
        {Text: "LPRINT", Description: "Prints text to a printer device (same result can be achieved with OPEN, PRINT and CLOSE statements)"},
        {Text: "NEW", Description: "Erases the program and all the variables from memory; automatically executed before a LOAD or CLOAD"},
        {Text: "NEXT", Description: "Continues the next iteration of a FOR loop"},
        {Text: "NOT", Description: "Logical negation"},
        {Text: "NOTE", Description: "Returns the current position on an I/O channel"},
        {Text: "ON", Description: "A computed goto - performs a jump based on the value of an expression"},
        {Text: "OPEN", Description: "Initialises an I/O channel"},
        {Text: "OR", Description: "Logical disjunction"},
        {Text: "PADDLE", Description: "Returns the position of a paddle controller"},
        {Text: "PEEK", Description: "Returns the value at an address in memory"},
        {Text: "PLOT", Description: "Draws a point at given coordinates"},
        {Text: "POINT", Description: "Sets the current position on an I/O channel"},
        {Text: "POKE", Description: "Sets a value at an address in memory"},
        {Text: "POP", Description: "Removes a subroutine return address from the stack (see GOSUB and RETURN)"},
        {Text: "POSITION", Description: "Sets the position of the graphics cursor"},
        {Text: "PRINT", Description: "and ?     Writes text to an I/O channel; usually to screen (default), printer, cassette tape or disk (see LPRINT and INPUT)"},
        {Text: "PTRIG", Description: "Indicates whether a paddle trigger is pressed or not"},
        {Text: "PUT", Description: "Writes one byte to an I/O channel (see GET)"},
        {Text: "RAD", Description: "Switches trigonometric functions to compute in radians (see DEG)"},
        {Text: "READ", Description: "Reads data from a DATA statement"},
        {Text: "REM", Description: "Marks a comment in a program"},
        {Text: "RESTORE", Description: "Sets the position of where to read data from a DATA statement"},
        {Text: "RETURN", Description: "Ends a subroutine, effectively branching to the line immediately following the calling GOSUB (see GOSUB and POP)"},
        {Text: "RND", Description: "Returns a pseudorandom number"},
        {Text: "RUN", Description: "Starts execution of a program, optionally loading it from an external device (see LOAD)"},
        {Text: "SAVE", Description: "Writes a tokenized program to an external device; usually a cassette tape or disk (see LOAD)"},
        {Text: "SETCOLOR", Description: "Maps a logical color to a physical color"},
        {Text: "SGN", Description: "Returns the signum of a number"},
        {Text: "SIN", Description: "Returns the sine of a number"},
        {Text: "SOUND", Description: "Starts or stops playing a tone on a sound channel (see END)"},
        {Text: "SQR", Description: "Returns the square root of a number"},
        {Text: "STATUS", Description: "Returns the status of an I/O channel"},
        {Text: "STEP", Description: "Indicates the increment used in a FOR loop"},
        {Text: "STICK", Description: "Returns a joystick position"},
        {Text: "STOP", Description: "Stops the program, allowing later resumption (see CONT)"},
        {Text: "STRIG", Description: "Indicates whether a joystick trigger is pressed or not"},
        {Text: "STR", Description: "Converts a number to string form"},
        {Text: "THEN", Description: "Indicates the statements to execute if the condition is true in an IF statement"},
        {Text: "TO", Description: "Indicates the limiting condition in a FOR statement"},
        {Text: "TRAP", Description: "Sets to jump to a given program line if an error occurs (TRAP 40000 cancels this order)"},
        {Text: "USR", Description: "Calls a machine code routine, optionally with parameters"},
        {Text: "VAL", Description: "Returns the numeric value of a string"},
        {Text: "XIO", Description: "General-purpose I/O routine (from Fill screen to Rename file to Format disk instructions) "},
        {Text: "exit", Description: "Quit the application"},
        {Text: "quit", Description: "Quit the application"},
}
 
func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                os.Exit(0)
        default:
                println("Nothing happens")
        }
        return
}
 
func completer(in prompt.Document) []prompt.Suggest {
        if in.GetWordBeforeCursor() == "" {
                return nil
        } else {
                return prompt.FilterFuzzy(suggestions, in.GetWordBeforeCursor(), true)
        }
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

16. Kontextová nápověda u příkazů, které se skládají ze dvou slov

Často se setkáme s požadavkem, aby program umožňoval zadání příkazů, které se skládají ze dvou či dokonce z většího množství slov. I v takovém případě samozřejmě můžeme použít funkce z knihovny go-prompt, ovšem budeme muset provést několik úprav ve funkci completer. Nejprve si však ukažme, jak by mělo vypadat očekávané chování aplikace:

Obrázek 17: Nápověda prvních slov příkazu.

Obrázek 18: Nápověda dalšího slova dvouslovního příkazu.

Obrázek 19: Nápověda dalšího slova odlišného dvouslovního příkazu.

Vidíme, že kontextová nápověda ke druhému slovu závisí na tom, jakým slovem příkaz začíná.

17. Implementace kontextové nápovědy víceslovních příkazů

Jedna z možných implementací „inteligentní“ kontextové nápovědy pro víceslovní příkazy spočívá v tom, že ve funkci completer nejprve rozložíme řetězec zadaný uživatelem na jednotlivá slova:

blocks := strings.Split(in.TextBeforeCursor(), " ")

Dále si vytvoříme několik tabulek příkazů. První tabulka bude obsahovat pouze první slova příkazu:

s := []prompt.Suggest{
        {Text: "help", Description: "show help with all commands"},
        {Text: "user", Description: "add/delete user"},
        {Text: "ls", Description: "list users/storages/computers"},
        {Text: "list", Description: "list users/storages/computers"},
        {Text: "exit", Description: "quit the application"},
        {Text: "quit", Description: "quit the application"},
}

Další tabulky pak pouze druhá slova příkazů:

user_s := []prompt.Suggest{
        {Text: "add", Description: "add new user"},
        {Text: "assign", Description: "assign a role to user"},
        {Text: "del", Description: "delete user"},
}
 
list_s := []prompt.Suggest{...
 
empty_s := []prompt.Suggest{}

Následně implementujeme primitivní logiku pro rozhodnutí, kterou tabulku s nabídkou příkazů vrátit a použít – zda seznam prvních slov či nějaký ze seznamů slov druhých. Ve větvi default se vrací prázdná tabulka pro jednoslovní či pro neznámé příkazy:

root_podpora

if len(blocks) == 2 {
        switch blocks[0] {
        case "user":
                return prompt.FilterHasPrefix(user_s, blocks[1], true)
        case "ls":
                fallthrough
        case "list":
                return prompt.FilterHasPrefix(list_s, blocks[1], true)
        default:
                return empty_s
        }
}
return prompt.FilterHasPrefix(s, blocks[0], true)

18. Úplný zdrojový kód příkladu s víceslovními příkazy

Úplný zdrojový kód příkladu s víceslovními příkazy nalezneme na adrese https://github.com/tisnik/go-root/blob/master/article20/10_two_wor­d_commands.go:

package main
 
import (
        "github.com/c-bata/go-prompt"
        "os"
        "strings"
)
 
func executor(t string) {
        switch t {
        case "exit":
                fallthrough
        case "quit":
                os.Exit(0)
        case "help":
                println("HELP:\nexit\nquit")
        case "user add":
                println("Adding user")
        case "user del":
                println("Deleting user")
        default:
                println("Nothing happens")
        }
        return
}
 
func completer(in prompt.Document) []prompt.Suggest {
        blocks := strings.Split(in.TextBeforeCursor(), " ")
 
        s := []prompt.Suggest{
                {Text: "help", Description: "show help with all commands"},
                {Text: "user", Description: "add/delete user"},
                {Text: "ls", Description: "list users/storages/computers"},
                {Text: "list", Description: "list users/storages/computers"},
                {Text: "exit", Description: "quit the application"},
                {Text: "quit", Description: "quit the application"},
        }
 
        user_s := []prompt.Suggest{
                {Text: "add", Description: "add new user"},
                {Text: "assign", Description: "assign a role to user"},
                {Text: "del", Description: "delete user"},
        }
 
        list_s := []prompt.Suggest{
                {Text: "users", Description: "show list of all users"},
                {Text: "logs", Description: "show list of all logs"},
                {Text: "storages", Description: "show list of all storages"},
                {Text: "computers", Description: "show list of all computers"},
        }
 
        empty_s := []prompt.Suggest{}
 
        if len(blocks) == 2 {
                switch blocks[0] {
                case "user":
                        return prompt.FilterHasPrefix(user_s, blocks[1], true)
                case "ls":
                        fallthrough
                case "list":
                        return prompt.FilterHasPrefix(list_s, blocks[1], true)
                default:
                        return empty_s
                }
        }
        return prompt.FilterHasPrefix(s, blocks[0], true)
}
 
func main() {
        p := prompt.New(executor, completer)
        p.Run()
}

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do 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ě dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Soubor Popis Cesta
1 01_scanln.go použití standardní funkce Scanln https://github.com/tisnik/go-root/blob/master/article20/01_scan­ln.go
2 02_input_via_reader.go čtení ze standardního vstupu přes Reader https://github.com/tisnik/go-root/blob/master/article20/02_in­put_via_reader.go
3 03_simple_input.go vstupní příkazový řádek realizovaný balíčkem go-prompt https://github.com/tisnik/go-root/blob/master/article20/03_sim­ple_input.go
4 04_prompt.go vstupní příkazový řádek s historií https://github.com/tisnik/go-root/blob/master/article20/04_prom­pt.go
5 05_basic_completer.go nabízení slov/příkazů pro automatické doplnění https://github.com/tisnik/go-root/blob/master/article20/05_ba­sic_completer.go
6 06_prefix_completer.go vylepšení automatického doplňování příkazů https://github.com/tisnik/go-root/blob/master/article20/06_pre­fix_completer.go
7 07_completion_description.go popis jednotlivých příkazů zobrazovaný uživateli https://github.com/tisnik/go-root/blob/master/article20/07_com­pletion_description.go
8 08_basic_statements.go klíčová slova Atari BASICu i s jejich popisem https://github.com/tisnik/go-root/blob/master/article20/08_ba­sic_statements.go
9 09_fuzzy_filter.go použití vylepšeného fuzzy filtru https://github.com/tisnik/go-root/blob/master/article20/09_fuz­zy_filter.go
10 10_two_word_commands.go příkaz složený ze dvou klíčových slov https://github.com/tisnik/go-root/blob/master/article20/10_two_wor­d_commands.go

20. Odkazy na Internetu

  1. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  2. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  3. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  4. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  5. go-prompt
    https://github.com/c-bata/go-prompt
  6. readline
    https://github.com/chzyer/readline
  7. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  8. go-readline
    https://github.com/fiorix/go-readline
  9. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  10. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  11. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  12. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  13. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  14. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  15. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  16. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  17. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  18. Editline Library (libedit)
    http://thrysoee.dk/editline/
  19. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  20. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  21. WinEditLine
    http://mingweditline.sourceforge.net/
  22. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  23. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  24. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  25. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  26. history(3) – Linux man page
    https://linux.die.net/man/3/history
  27. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  28. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  29. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  30. Balíček ogletest
    https://github.com/jacobsa/ogletest
  31. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  32. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  33. Package testing
    https://golang.org/pkg/testing/
  34. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  35. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  36. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  37. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  38. GoConvey
    http://goconvey.co/
  39. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  40. 5 simple tips and tricks for writing unit tests in #golang
    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742
  41. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  42. package gg
    https://godoc.org/github.com/fo­gleman/gg
  43. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  44. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  45. The Go image package
    https://blog.golang.org/go-image-package
  46. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  47. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  48. YAML
    https://yaml.org/
  49. edn
    https://github.com/edn-format/edn
  50. Smile
    https://github.com/FasterXML/smile-format-specification
  51. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  52. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  53. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  54. Introducing JSON
    http://json.org/
  55. Package json
    https://golang.org/pkg/encoding/json/
  56. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  57. Go by Example: JSON
    https://gobyexample.com/json
  58. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  59. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  60. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  61. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  62. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  63. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  64. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  65. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  66. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  67. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  68. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  69. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  70. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  71. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  72. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  73. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  74. Algorithms to Go
    https://yourbasic.org/
  75. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  76. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  77. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  78. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  79. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  80. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  81. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  82. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  83. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  84. The Go Programming Language (home page)
    https://golang.org/
  85. GoDoc
    https://godoc.org/
  86. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  87. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  88. The Go Programming Language Specification
    https://golang.org/ref/spec
  89. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  90. Package builtin
    https://golang.org/pkg/builtin/
  91. Package fmt
    https://golang.org/pkg/fmt/
  92. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  93. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  94. Learning Go
    https://www.miek.nl/go/
  95. Go Bootcamp
    http://www.golangbootcamp.com/
  96. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  97. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  98. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  99. The Go Blog
    https://blog.golang.org/
  100. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  101. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  102. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  103. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  104. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  105. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  106. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  107. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  108. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  109. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  110. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  111. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  112. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  113. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  114. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  115. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  116. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  117. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  118. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  119. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  120. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  121. 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/
  122. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  123. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  124. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  125. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  126. Go vs. Python
    https://www.peterbe.com/plog/govspy
  127. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  128. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  129. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  130. Go by Example: Slices
    https://gobyexample.com/slices
  131. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  132. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  133. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  134. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  135. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  136. nils In Go
    https://go101.org/article/nil.html
  137. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  138. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  139. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  140. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  141. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  142. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  143. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  144. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  145. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  146. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  147. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  148. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  149. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  150. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  151. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  152. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  153. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  154. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  155. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  156. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  157. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  158. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  159. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  160. Selectors
    https://golang.org/ref/spec#Selectors
  161. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  162. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  163. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  164. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  165. Part 21: Goroutines
    https://golangbot.com/goroutines/
  166. Part 22: Channels
    https://golangbot.com/channels/
  167. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  168. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  169. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
  170. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  171. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  172. Control Structures
    https://www.golang-book.com/books/intro/5
  173. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  174. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  175. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  176. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  177. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  178. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  179. Effective Go
    https://golang.org/doc/ef­fective_go.html
  180. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  181. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation

Byl pro vás článek přínosný?