Hoppa till innehållet
Ridgun
Svenska

24 Boken om Ridgun

Lib:en cli — Flaggor, Alternativ, ---barriär, Underkommandon och Miljövariabler

24.1. Varför finns lib:en cli? (Det verkliga problemet och Ridguns filosofi)

”Varför behöver jag en cli-lib skriven från grunden i ren Ridgun i stället för att bara länka in ett gammalt C-bibliotek?”

Det är den första fråga varje systemingenjör ställer sig när man möter cli. Och svaret sammanfattar hela filosofin bakom ridgun-libs:

Låt oss vara ärliga: att tolka argv för hand börjar alltid som en oskyldig loop och går sönder så fort någon skickar kombinerade -la-flaggor, --port=9090 eller -- följt av ett filnamn som börjar med bindestreck.

Lib:en cli hanterar långa flaggor (--verbose), korta flaggor (-v), kombinerade kortflaggor (-la), värdealternativ (--port=8080, --port 8080, -p 8080), POSIX-barriären --, positionella argument, underkommandon och typade miljövariabler.

I ett traditionellt ekosystem innebär uppgifter inom domänen CLI ofta att man drar in externa C-beroenden, brottas med råa pekare, ogenomskinliga typkonverteringar, okontrollerade undantag eller oförutsägbara skräpsamlarpauser. I Ridgun löser lib:en cli (src/main/ridgun/lib/cli.rg) problemet genom fyra kompromisslösa principer:

  1. Deklarera import cli (eller import cli as alias) överst i filen: varje lib definierar sin module :: cli-namnrymd och i --strict-läge anropas flerordsfunktioner alltid med naturliga mellanslag.
  2. Kedja dina datapipelines från vänster till höger med ->: skriv indata steg ett -> steg två -> resultat, använd (+ _ _ +)-sekvensmasker för att packa upp tupler och utelämna _ = vid sekventiella satser (eller kasta med -> ()).
  3. Svansrekursion med TCO (Tail-Call Optimization): alla interna genomlöpningar i cli kompileras till inbyggda hårdvaruloopar med $O(1)$ stackutrymme.
  4. Deterministiskt minne (RMM1 / RMM2): varje mellanliggande struktur frigörs i exakt det ögonblick dess sista läsning avslutas, helt utan Garbage Collector.

24.2. Inbyggda artefakter, import och komplett API-katalog för cli

I ridgun-libs är cli en förstklassig lib placerad i src/main/ridgun/lib/cli.rg (domän CLI). När du bygger med stable bundle paketeras den i tre inbyggda format:

ArtefaktSökvägSyfte
Ridgun-bundletarget/bundles/cli.rglibPortabel distribution och direkt import med import cli
Kompilerat arkivtarget/cli.rgcTypkontrollerad mellanartefakt verifierad av ridgunc
Delat ELF64-objekttarget/libcli.soInbyggt dynamiskt bibliotek helt utan C- eller libc-beroenden

Varje lib deklarerar sin egen namnrymd med module :: cli: skriv bara import cli (eller import cli as alias) överst och anropa dess funktioner med naturliga mellanslag (min funktion x) och ->-pipelines (x min funktion -> resultat).

24.2.1. Översikt över funktionella familjer i cli

Signatur / Funktion / TypVad den gör och när den används
cli has flag : [String] -> String -> String -> BoolDetekterar --long, -s eller kombinerade -xyz före --
cli get opt / cli get int optExtraherar sträng- eller heltalsalternativ (--k=v, --k v, -k v) med standardvärde
cli positionals / cli subcommandSamlar in positionella argument (inklusive allt efter --)
env get or / env get int or / env get bool orLäser miljövariabler med typad konvertering och reservvärden

24.2.2. Komplett katalog över typer och signaturer i src/main/ridgun/lib/cli.rg

Nedan följer hela förteckningen över algebraiska datatyper (type), mixfix-operatorer (op ::) och funktionssignaturer (fn ::) som definieras i src/main/ridgun/lib/cli.rg (0 typer, 0 operatorer och 26 funktioner):

#Funktion / Signatur (fn ::)Returtyp
1fn :: cl str contains char String Int Int IntBool
2fn :: cl is bundled short String StringBool
3fn :: cl list contains str [String] StringBool
4fn :: cl is digits loop String Int IntBool
5fn :: cl is signed int len String IntBool
6fn :: cl is signed int StringBool
7fn :: cl is ws IntBool
8fn :: cl lower char String Int IntString
9fn :: cl trim lower loop String Int IntString
10fn :: cli has flag [String] String StringBool
11fn :: cl match eq pfx String StringString
12fn :: cl match eq prefix String StringString
13fn :: cl has eq prefix String StringBool
14fn :: cli get opt [String] String String StringString
15fn :: cl parse int opt String IntInt
16fn :: cli get int opt [String] String String IntInt
17fn :: cl is flag tok StringBool
18fn :: cli positionals with opts [String] [String][String]
19fn :: cli positionals [String][String]
20fn :: cl first str [String]String
21fn :: cli subcommand [String]String
22fn :: cl env val or String StringString
23fn :: env get or String StringString
24fn :: env get int or String IntInt
25fn :: cl parse bool val String BoolBool
26fn :: env get bool or String BoolBool

24.3. Djupdykning i cli: Steg-för-steg-exempel för varje funktion

Det räcker inte med en signaturtabell: låt oss utforska varje funktionalitet i lib:en cli genom progressiva, körbara Ridgun-exempel. Vi börjar med två tabellbaserade vardagsrecept och går därefter igenom alla funktionella scenarier, transformationer och gränsfall som verifieras i src/test/ridgun/test cli.rg.

24.3.1. Exempel 1: Huvudflöde i tabellform med cli

Lägg märke till hur =-tecknen och transformationsstegen med cli linjeras som en ren tabell utan onödiga muterbara variabler eller dolda sidoeffekter:

import cli

fn :: analizar comando [String] => String Bool Int [String]
      analizar comando args =
        args cli subcommand -> sub
        cli has flag args "--all" "-a" -> all_f
        cli get int opt args "--port" "-p" 8080 -> port
        cli positionals with opts args ["--port", "-p"] -> arch
        (sub, all_f, port, arch)

24.3.2. Exempel 2: Säker komposition och hantering av gränsfall

Varje funktion i lib:en cli är utformad för att kombineras sömlöst med booleska vakter | och med övriga 32 libs i ridgun-libs:

import cli

fn :: leer config entorno => Int Bool
      leer config entorno =
        env get int or "SOLSTICE_WORKERS" 4 -> workers
        env get bool or "SOLSTICE_DEBUG" False -> debug
        (workers, debug)

24.3.3. Exempel 3: Booleska flaggor (cli has flag) och nyckel-värde-flaggor (--key=val och --key val, cli opt int, cli opt bool)

I detta praktiska exempel undersöker vi grundligt hur cli beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

fn :: test cli flags and options => ()
      test cli flags and options =
        args = ["serve", "-la", "--verbose", "--port=9090", "-h", "127.0.0.1", "--workers", "-4", "--", "--ignored-flag", "-z", "file.rg"]
        assert true (cli has flag args "--verbose" "-v") "AC1: long flag --verbose"
        assert true (cli has flag args "--all" "-a") "AC1: bundled short flag -a in -la"
        assert true (cli has flag args "--list" "-l") "AC1: bundled short flag -l in -la"
        assert false (cli has flag args "--quiet" "-q") "AC1: missing flag returns False"
        assert false (cli has flag args "--ignored-flag" "") "AC1: long flag after -- is ignored"
        assert false (cli has flag args "" "-z") "AC1: short flag after -- is ignored"
        assert false (cli has flag [] "--verbose" "-v") "AC1: empty args returns False"

        -- Option extraction: --key=val, --key val, -k=val, -k val, empty value --key=
        assert str eq (cli get opt args "--port" "-p" "8080") "9090" "AC2: --port=9090 inline equals"
        assert str eq (cli get opt args "--host" "-h" "0.0.0.0") "127.0.0.1" "AC2: -h 127.0.0.1 separate token"
        assert str eq (cli get opt args "--mode" "-m" "prod") "prod" "AC2: missing option returns fallback"
        assert int equals (cli get int opt args "--port" "-p" 8080) 9090 "AC2: cli_get_int_opt positive int"
        assert int equals (cli get int opt args "--workers" "-w" 1) (0 - 4) "AC2: cli_get_int_opt negative int"
        assert int equals (cli get int opt args "--host" "-h" 8080) 8080 "AC2: non-numeric option falls back to default int"

        short_eq_args = ["-p=3000", "--empty="]
        assert int equals (cli get int opt short_eq_args "--port" "-p" 80) 3000 "AC2: short option -p=3000"
        assert str eq (cli get opt short_eq_args "--empty" "-e" "def") "" "AC2: explicit empty --empty= returns empty string"
        ()

24.3.4. Exempel 4: Positionsargument med stöd för -- (cli positionals), underkommandon (cli subcommand) och typade miljövariabler (env str/int/bool or)

I detta praktiska exempel undersöker vi grundligt hur cli beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

fn :: test cli positionals subcommand and env => ()
      test cli positionals subcommand and env =
        args = ["build", "-v", "--output", "target/app", "-j", "8", "--", "-dashfile.rg", "--another-literal", "main.rg"]
        assert str eq (cli subcommand args) "build" "AC3: cli_subcommand first positional"
        assert str eq (cli subcommand ["-v", "--release", "test"]) "test" "AC3: cli_subcommand skips leading flags"
        assert str eq (cli subcommand ["--help", "-v"]) "" "AC3: cli_subcommand empty when only flags"
        assert str eq (cli subcommand []) "" "AC3: cli_subcommand empty on []"

        pos = cli positionals with opts args ["--output", "-j"]
        assert eq pos ["build", "-dashfile.rg", "--another-literal", "main.rg"]

        -- Single "-" (stdin/stdout convention) is treated as a positional argument, not a flag!
        stdin_args = ["cat", "-n", "-"]
        assert eq (cli positionals stdin_args) ["cat", "-"]

        -- Environment variable readers
        assert true ((str len (env get or "PATH" "")) > 0) "AC4: env_get_or PATH non-empty"
        assert str eq (env get or "RIDGUN_NONEXISTENT_ENV_999" "fallback_val") "fallback_val" "AC4: env_get_or fallback"
        assert int equals (env get int or "RIDGUN_NONEXISTENT_ENV_999" 42) 42 "AC4: env_get_int_or fallback"
        assert true (env get bool or "RIDGUN_NONEXISTENT_ENV_999" True) "AC4: env_get_bool_or True fallback"
        assert false (env get bool or "RIDGUN_NONEXISTENT_ENV_999" False) "AC4: env_get_bool_or False fallback"
        ()

24.3.5. Exempel 5: Fullständig CLI-kommandohanterare som kombinerar underkommandon, flaggor, typade alternativ och miljövariabler

I detta praktiska exempel undersöker vi grundligt hur cli beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

import cli

fn :: despachar comando (argv : [String]) => (String, String, Int, Bool, [String])
      despachar comando argv =
        cmd     = cli subcommand argv
        host    = cli opt or argv "--host" "-H" (env str or "RG_HOST" "127.0.0.1")
        puerto  = cli opt int or argv "--port" "-p" (env int or "RG_PORT" 8080)
        verbose = (cli has flag argv "--verbose" "-v") or (env bool or "RG_VERBOSE" False)
        archivos = cli positionals argv
        (cmd, host, puerto, verbose, archivos)

24.4. Under huven: hur cli är byggd i ren Ridgun (src/main/ridgun/lib/cli.rg)

En av de största fördelarna med ridgun-libs är att det inte finns några svarta lådor i C. Du kan öppna src/main/ridgun/lib/cli.rg och läsa exakt hur varje operation är implementerad med mönstermatchning, | / |.-vaktträd och svansrekursion (TCO). Låt oss titta på nyckeldelarna i den faktiska implementationen:

24.4.1. Intern implementation: Internal String & List Helpers

Se hur cli.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

module :: cli

fn :: cl str contains char String Int Int Int => Bool
      cl str contains char s ch idx len =
        | idx >= len                = False
        | (str char at s idx) == ch = True
        | otherwise                 = cl str contains char s ch (idx + 1) len

fn :: cl is bundled short String String => Bool
      cl is bundled short arg short_flag =
        | (str len short_flag) != 2        = False
        | (str char at short_flag 0) != 45 = False
        | (str len arg) < 2                = False
        | (str char at arg 0) != 45        = False
        | (str char at arg 1) == 45        = False
        | otherwise                        = cl str contains char arg (str char at short_flag 1) 1 (str len arg)

fn :: cl list contains str [String] String => Bool
      cl list contains str [] _ = False
      cl list contains str (x:rest) tgt =
        | x == tgt  = True
        | otherwise = cl list contains str rest tgt

fn :: cl is digits loop String Int Int => Bool
      cl is digits loop s idx len =
        | idx >= len                                           = True
        | (str char at s idx) < 48 or (str char at s idx) > 57 = False
        | otherwise                                            = cl is digits loop s (idx + 1) len

fn :: cl is signed int len String Int => Bool
      cl is signed int len s n =
        | n == 0                  = False
        | (str char at s 0) == 45 =
          | n <= 1    = False
          | otherwise = cl is digits loop s 1 n |.
        | otherwise               = cl is digits loop s 0 n

fn :: cl is signed int String => Bool
      cl is signed int s      =  cl is signed int len s (str len s)

fn :: cl is ws Int => Bool
      cl is ws c   =  c == 32 or c == 9 or c == 10 or c == 13

fn :: cl lower char String Int Int => String
      cl lower char s idx c =
        | c >= 65 and c <= 90 = char from code (c + 32)
        | otherwise           = str slice s idx (idx + 1)

fn :: cl trim lower loop String Int Int => String
      cl trim lower loop s idx len =
        | idx >= len                   = ""
        | cl is ws (str char at s idx) = cl trim lower loop s (idx + 1) len
        | otherwise                    = (cl lower char s idx (str char at s idx)) + (cl trim lower loop s (idx + 1) len)

24.4.2. Intern implementation: Positional Arguments & Subcommand Extraction

Se hur cli.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

fn :: cl is flag tok String => Bool
      cl is flag tok a      =
        | (str len a) <= 1        = False
        | (str char at a 0) == 45 = True
        | otherwise               = False

fn :: cli positionals with opts [String] [String] => [String]
      cli positionals with opts [] _ = []
      cli positionals with opts (a:[]) _ =
        | a == "--"        = []
        | cl is flag tok a = []
        | otherwise        = [a]
      cli positionals with opts (a:b:rst) val_opts =
        | a == "--"                       = [b] :: rst
        | cl list contains str val_opts a = cli positionals with opts rst val_opts
        | cl is flag tok a                = cli positionals with opts ([b] :: rst) val_opts
        | otherwise                       = [a] :: (cli positionals with opts ([b] :: rst) val_opts)

fn :: cli positionals [String] => [String]
      cli positionals args     =  cli positionals with opts args []

fn :: cl first str [String] => String
      cl first str []       =  ""
      cl first str (x:_)    =  x

fn :: cli subcommand [String] => String
      cli subcommand args     =  args cli positionals -> cl first str

24.4.3. Intern implementation: Typed Environment Variable Readers

Se hur cli.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

fn :: cl env val or String String => String
      cl env val or val fallback =
        | (str len val) == 0 = fallback
        | otherwise          = val

fn :: env get or String String   => String
      env get or key    fallback =  cl env val or (sys getenv key) fallback

fn :: env get int or String Int => Int
      env get int or key fallback = cl parse int opt (sys getenv key) fallback

fn :: cl parse bool val String Bool => Bool
      cl parse bool val val fallback =
        | val == "1" or val == "true" or val == "yes" or val == "on"  = True
        | val == "0" or val == "false" or val == "no" or val == "off" = False
        | otherwise                                                   = fallback

fn :: env get bool or String Bool => Bool
      env get bool or key fallback =
        key sys getenv -> raw
        cl trim lower loop raw 0 (str len raw) -> norm
        cl parse bool val norm fallback

24.5. Algoritmiska garantier, RMM1/RMM2-minne och de 5 Gyllene Reglerna för cli

  • Algoritmisk garanti och säkerhet: Strikt efterlevnad av POSIX-barriären --: inget argument till höger om -- tolkas någonsin som en flagga.
  • $O(1)$ stackkomplexitet med TCO: Alla rekursiva loopar i src/main/ridgun/lib/cli.rg ligger i svansposition, så ridgunc kompilerar dem till inbyggda hopp utan stacktillväxt.
  • Deterministiskt minne (RMM1/RMM2): Noll skräpsamlarpauser; alla temporära strukturer frigörs deterministiskt när räckvidden lämnas.
  • Kontinuerlig verifiering: Statiskt kontrollerad med stable check och verifierad av den automatiserade testsviten src/test/ridgun/test cli.rg.

De 5 Gyllene Reglerna när du programmerar med cli

  1. Deklarera import cli överst i filen och utnyttja att mellanslag och understreck är utbytbara så att koden läses som naturlig prosa.
  2. Linjera dina datapipelines med =: kedja samman cli-anrop i namngivna mellansteg och låt stable format linjera kolumnerna.
  3. Utnyttja cli:s totala funktioner i dina |-vakter: kontrollera returvärden eller packa upp ADT:er med mönstermatchning i stället för att anta att extern indata alltid är giltig.
  4. Undvik att kopiera stora strukturer när du bara läser ett fält: kom ihåg att i RMM2 medför inargument noll referensräkningskostnad (+0 / -0).
  5. Kombinera cli med test (import test): lås alltid fast dina domäninvarianter med assert equals eller assert true.

I Kapitel 25 går vi vidare och utforskar lib:en path på djupet (Lib:en path — POSIX-normalisering (path clean) och Skydd mot Path Traversal).