diff options
| -rw-r--r-- | cli/README.md | 6 | ||||
| -rwxr-xr-x | cli/ugi | 6 | ||||
| -rw-r--r-- | spec/README.md | 349 | ||||
| -rw-r--r-- | spec/grammar/UGI_GRAMMAR_v0.abnf | 50 | ||||
| -rw-r--r-- | spec/grammar/UGI_GRAMMAR_v0.ebnf | 50 | ||||
| -rw-r--r-- | spec/scripts/gen_abnf.py | 4 | ||||
| -rw-r--r-- | spec/scripts/gen_ebnf.py | 4 | ||||
| -rw-r--r-- | spec/scripts/gen_spec.py | 94 | ||||
| -rw-r--r-- | spec/tests/cases.json | 6 | ||||
| -rw-r--r-- | web/README.md | 12 | ||||
| -rw-r--r-- | web/scripts/gen_tool.py | 46 |
11 files changed, 313 insertions, 314 deletions
diff --git a/cli/README.md b/cli/README.md index 478c178..2b0c189 100644 --- a/cli/README.md +++ b/cli/README.md @@ -5,8 +5,8 @@ Command-line tool for encoding, decoding, and converting UGI (Unified Geek Ident ## Dependencies - **bash** 4.0+ -- **jq** — [jqlang.github.io/jq](https://jqlang.github.io/jq/) -- **ugi_registry_v0.json** — from [ugi-spec](https://git.sr.ht/~gumxcc/ugi-spec) +- **jq**: [jqlang.github.io/jq](https://jqlang.github.io/jq/) +- **ugi_registry_v0.json**: from [ugi-spec](https://git.sr.ht/~gumxcc/ugi-spec) ## Setup @@ -115,4 +115,4 @@ The test runner validates decode output (version, handle, field labels) and conv ## License -MIT — see [LICENSE](./LICENSE). +MIT. see [LICENSE](./LICENSE). @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# ugi — encode, decode, and convert UGI strings +# ugi: encode, decode, and convert UGI strings set -euo pipefail VERSION="0.1.0" @@ -473,7 +473,7 @@ list_fields() { # Reserved codes jq -r '.format.reserved_codes[]' "$REGISTRY" 2>/dev/null | sort | while read -r code; do - printf "%-6s %-25s %-15s %s\n" "$code" "(reserved)" "—" "—" + printf "%-6s %-25s %-15s %s\n" "$code" "(reserved)" "-" "-" done } @@ -597,7 +597,7 @@ encode_interactive() { usage() { cat <<'EOF' -ugi — UGI (Unified Geek Identifier) CLI tool +ugi: UGI (Unified Geek Identifier) CLI tool Usage: ugi decode <ugi-string> Decode a UGI string (URI or block) diff --git a/spec/README.md b/spec/README.md index 3cedcbb..993f1a5 100644 --- a/spec/README.md +++ b/spec/README.md @@ -1,4 +1,4 @@ -# UGI — Unified Geek Identifier +# UGI: Unified Geek Identifier **Version: 0 (Draft) · 2026-03-28 · MIT License** @@ -16,10 +16,10 @@ UGI takes the cultural breadth of the Geek Code, the numeric compactness of the ### Design goals -1. Single-letter field codes — 24 active, 2 reserved -2. Octal (0–7) rating scale -3. URI-safe characters only — no percent-encoding needed -4. Dual format — URI for machines, block for humans +1. Single-letter field codes: 24 active, 2 reserved +2. Octal (0-7) rating scale +3. URI-safe characters only, no percent-encoding needed +4. Dual format: URI for machines, block for humans 5. Case-insensitive 6. Extensible via `~` custom sub-IDs @@ -49,7 +49,7 @@ Block → URI: remove header/footer, merge repeated field codes, replace spaces URI → Block: split on commas, expand merged sub-IDs by repeating field codes, add header/footer. -No modifier or character changes are needed — both formats use identical symbols. +No modifier or character changes are needed; both formats use identical symbols. ### Mandatory fields @@ -62,15 +62,15 @@ No modifier or character changes are needed — both formats use identical symbo <field_code><sub_id><digit>[<modifier>]... ``` -- **Field codes** — single letter (a–z), case-insensitive -- **Sub-IDs** — 1–4 lowercase alpha chars -- **Ratings** — single octal digit (0–7), required on every sub-ID -- **Custom sub-IDs** — `~` prefix: `~fermenting5` -- **Modifiers** — after the digit: - - `$` — paid - - `+` followed by digit — aspiring toward that level - - `$+` combined — paid and aspiring -- **Alternatives** — `/` between two digits on direct-value fields: `c3/6` +- **Field codes**: single letter (a-z), case-insensitive +- **Sub-IDs**: 1-4 lowercase alpha chars +- **Ratings**: single octal digit (0-7), required on every sub-ID +- **Custom sub-IDs**: `~` prefix: `~fermenting5` +- **Modifiers**: after the digit: + - `$`: paid + - `+` followed by digit: aspiring toward that level + - `$+` combined: paid and aspiring +- **Alternatives**: `/` between two digits on direct-value fields: `c3/6` ### Special formats @@ -134,14 +134,14 @@ After each comma or space, read a single letter (field code). Then: | `k` | Books | Direct | Entertainment | | `l` | Spoken Languages | Multi (ISO) | Identity | | `m` | Music | Multi | Entertainment | -| `n` | *(reserved)* | — | — | +| `n` | *(reserved)* | - | - | | `o` | Operating System | Multi | Tech | | `p` | Programming Languages | Multi | Tech | | `q` | Security / Hacking | Direct, `/` ok | Tech | | `r` | Religion | Single | Stance | | `s` | Sex / Relationships | Direct, `/` ok | Lifestyle | | `t` | TV / Film | Multi | Entertainment | -| `u` | *(reserved)* | — | — | +| `u` | *(reserved)* | - | - | | `v` | Politics | Special (s/e) | Stance | | `w` | Hardware | Direct | Tech | | `x` | Comics / Manga | Multi | Entertainment | @@ -153,20 +153,20 @@ After each comma or space, read a single letter (field code). Then: --- -### a — Age +### a: Age -0. **Under 10** — Still loading. Baby Shark has more life experience. -1. **10-14** — Old enough for Linux opinions, too young to drive to the meetup. -2. **15-19** — Convinced you know everything. Might be right about the tech stuff. -3. **20-24** — Student loans arrived but so has 3am pizza freedom. -4. **25-34** — Peak earning-to-caring ratio. GitHub green, back complaining. -5. **35-49** — You remember dial-up noises. Kids ask what a floppy disk is. -6. **50-64** — You were there when it happened. All of it. -7. **65+** — Living legend. Your software runs on OSes born decades after you. +0. **Under 10**: Still loading. Baby Shark has more life experience. +1. **10-14**: Old enough for Linux opinions, too young to drive to the meetup. +2. **15-19**: Convinced you know everything. Might be right about the tech stuff. +3. **20-24**: Student loans arrived but so has 3am pizza freedom. +4. **25-34**: Peak earning-to-caring ratio. GitHub green, back complaining. +5. **35-49**: You remember dial-up noises. Kids ask what a floppy disk is. +6. **50-64**: You were there when it happened. All of it. +7. **65+**: Living legend. Your software runs on OSes born decades after you. --- -### b — Build +### b: Build Format: `b<height>/<width>`. Each 0–7. 0 = extremely small, 4 = average, 7 = extremely large. `+` modifier permitted. @@ -174,25 +174,25 @@ Format: `b<height>/<width>`. Each 0–7. 0 = extremely small, 4 = average, 7 = e **Width:** 0 = Extremely thin · 1 = Very thin · 2 = Slim · 3 = Below average · 4 = Average · 5 = Above average · 6 = Broad · 7 = Extremely broad -> `b6/3` — tall and slim. -> `b4/4` — average. Chairs were designed for you. +> `b6/3`: tall and slim. +> `b4/4`: average. Chairs were designed for you. --- -### c — Clothing +### c: Clothing -0. **Nudist** — Quite a fashion statement. -1. **Punk** — Safety pins are load-bearing. -2. **Political tees** — Your shirts have opinions so you don't have to. -3. **Jeans & tee** — Universal geek uniform. You own seven identical shirts. -4. **Average** — Inoffensive. Forgettable. Exactly as intended. -5. **Smart casual** — You've mastered the blazer-over-tee. -6. **Formal** — You iron things. On purpose. -7. **Suit daily** — CEO, lawyer, or cosplaying as one. +0. **Nudist**: Quite a fashion statement. +1. **Punk**: Safety pins are load-bearing. +2. **Political tees**: Your shirts have opinions so you don't have to. +3. **Jeans & tee**: Universal geek uniform. You own seven identical shirts. +4. **Average**: Inoffensive. Forgettable. Exactly as intended. +5. **Smart casual**: You've mastered the blazer-over-tee. +6. **Formal**: You iron things. On purpose. +7. **Suit daily**: CEO, lawyer, or cosplaying as one. --- -### d — Editor / IDE +### d: Editor / IDE The editor holy war, now quantified. @@ -201,7 +201,7 @@ The editor holy war, now quantified. | `vi` | `(vim)` | Vim / Neovim | `em` | `(emacs)` | Emacs | | `vs` | `(vscode)` | VS Code | `ij` | `(idea)` | IntelliJ / JetBrains | | `xc` | `(xcode)` | Xcode | `vt` | `(vstudio)` | Visual Studio | -| `ed` | — | ed | `na` | `(nano)` | nano / pico | +| `ed` | - | ed | `na` | `(nano)` | nano / pico | | `su` | `(sublime)` | Sublime Text | `ec` | `(eclipse)` | Eclipse | | `np` | `(npp)` | Notepad++ | `hx` | `(helix)` | Helix | | `ze` | `(zed)` | Zed | `ka` | `(kakoune)` | Kakoune | @@ -215,33 +215,33 @@ Scale: **proficiency** (see global scale table). --- -### e — Education +### e: Education -0. **None / primary** — School of life. And YouTube. -1. **Middle school** — Survived. Emotionally, jury's still out. -2. **High school** — They said best years. They lied. -3. **Trade / associates** — You can actually do things, unlike philosophy majors. -4. **Bachelors** — Four years and paper that cost more than your car. -5. **Masters** — First degree wasn't enough to convince anyone. -6. **PhD** — Call me Doctor. Especially at parties. -7. **Post-doc** — Forgotten what sunlight looks like. +0. **None / primary**: School of life. And YouTube. +1. **Middle school**: Survived. Emotionally, jury's still out. +2. **High school**: They said best years. They lied. +3. **Trade / associates**: You can actually do things, unlike philosophy majors. +4. **Bachelors**: Four years and paper that cost more than your car. +5. **Masters**: First degree wasn't enough to convince anyone. +6. **PhD**: Call me Doctor. Especially at parties. +7. **Post-doc**: Forgotten what sunlight looks like. --- -### f — Food / Cooking +### f: Food / Cooking -0. **Microwave only** — Smoke detector is your kitchen timer. -1. **Boils water** — Sometimes remembers to turn off stove. -2. **Basic meals** — Eggs and pasta. The two food groups. -3. **Follows recipes** — To the letter. Substitution is heresy. -4. **Home cook** — Friends don't dread your dinner invitations. -5. **Experiments** — Sometimes works. You pretend you planned it. -6. **Hosts dinners** — Spice rack has subdivisions. -7. **Chef level** — Own a mandoline, all fingers intact. +0. **Microwave only**: Smoke detector is your kitchen timer. +1. **Boils water**: Sometimes remembers to turn off stove. +2. **Basic meals**: Eggs and pasta. The two food groups. +3. **Follows recipes**: To the letter. Substitution is heresy. +4. **Home cook**: Friends don't dread your dinner invitations. +5. **Experiments**: Sometimes works. You pretend you planned it. +6. **Hosts dinners**: Spice rack has subdivisions. +7. **Chef level**: Own a mandoline, all fingers intact. --- -### g — Geek Specialization +### g: Geek Specialization Mandatory. `/` separates domains. `~` prefixes custom domains. `$` after a domain = paid. No ratings. @@ -323,13 +323,13 @@ Mandatory. `/` separates domains. `~` prefixes custom domains. `$` after a domai #### Custom -`~fermenting` `~beekeeping` `~lockpicking` `~origami` — anything goes. +`~fermenting` `~beekeeping` `~lockpicking` `~origami`. Anything goes. -> `gcs$/ai/wr~fermenting` — paid CS geek, also into AI, writing, and fermenting. +> `gcs$/ai/wr~fermenting`: paid CS geek, also into AI, writing, and fermenting. --- -### h — Hair +### h: Hair | Short | Name | |---|---| @@ -350,20 +350,20 @@ Mandatory. `/` separates domains. `~` prefixes custom domains. `$` after a domai --- -### i — AI Stance +### i: AI Stance -0. **Butlerian Jihadist** — Would unplug every GPU on earth. -1. **Strongly opposed** — 'No AI' in bio, code, and dating profile. -2. **Skeptical** — Prefer human-authored everything. -3. **Cautious** — Ethics papers keep you up at night. -4. **Neutral tool** — Like a hammer. No opinions about hammers. -5. **Positive** — Copilot saved 20 minutes today. -6. **Enthusiast** — Cyborg partnership workflow. -7. **Singularity evangelist** — Grocery list is AI-generated. +0. **Butlerian Jihadist**: Would unplug every GPU on earth. +1. **Strongly opposed**: 'No AI' in bio, code, and dating profile. +2. **Skeptical**: Prefer human-authored everything. +3. **Cautious**: Ethics papers keep you up at night. +4. **Neutral tool**: Like a hammer. No opinions about hammers. +5. **Positive**: Copilot saved 20 minutes today. +6. **Enthusiast**: Cyborg partnership workflow. +7. **Singularity evangelist**: Grocery list is AI-generated. --- -### j — Gaming +### j: Gaming **Genre:** @@ -382,20 +382,20 @@ Mandatory. `/` separates domains. `~` prefixes custom domains. `$` after a domai | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| -| `pc` | — | PC | `ps` | — | PlayStation | +| `pc` | - | PC | `ps` | - | PlayStation | | `xb` | `(xbox)` | Xbox | `ns` | `(switch)` | Switch | | `mo` | `(mobile)` | Mobile | `re` | `(retro)` | Retro | -| `vr` | — | VR | `sd` | `(steamdeck)` | Steam Deck | +| `vr` | - | VR | `sd` | `(steamdeck)` | Steam Deck | **Tabletop:** | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| | `dn` | `(dnd)` | D&D | `pf` | `(path)` | Pathfinder | -| `cc` | `(coc)` | Call of Cthulhu | `sr` | — | Shadowrun | +| `cc` | `(coc)` | Call of Cthulhu | `sr` | - | Shadowrun | | `wo` | `(wod)` | World of Darkness | `ft` | `(fate)` | Fate | -| `gu` | `(gurps)` | GURPS | `bg` | — | Board games | -| `mt` | `(mtg)` | Magic: TG | `wh` | — | Warhammer | +| `gu` | `(gurps)` | GURPS | `bg` | - | Board games | +| `mt` | `(mtg)` | Magic: TG | `wh` | - | Warhammer | Scale: **enthusiasm** (see global scale table). @@ -404,20 +404,20 @@ Scale: **enthusiasm** (see global scale table). --- -### k — Books +### k: Books -0. **Illiterate by choice** — Words are tiny drawings that haven't tried hard enough. -1. **Not since school** — Didn't finish that one either. -2. **Web only** — You call this 'reading.' -3. **Occasional** — Mostly when trapped on a plane. -4. **Few/year** — Goodreads updated sporadically. -5. **Regular** — To-read pile doubles as furniture. -6. **Avid** — Library cards from multiple jurisdictions. -7. **Book/week** — Shelves have shelves. Read this spec for fun. +0. **Illiterate by choice**: Words are tiny drawings that haven't tried hard enough. +1. **Not since school**: Didn't finish that one either. +2. **Web only**: You call this 'reading.' +3. **Occasional**: Mostly when trapped on a plane. +4. **Few/year**: Goodreads updated sporadically. +5. **Regular**: To-read pile doubles as furniture. +6. **Avid**: Library cards from multiple jurisdictions. +7. **Book/week**: Shelves have shelves. Read this spec for fun. --- -### l — Spoken Languages +### l: Spoken Languages ISO 639-1 codes + proficiency 0–7. `$` and `+` permitted. @@ -432,11 +432,11 @@ ISO 639-1 codes + proficiency 0–7. `$` and `+` permitted. Common codes: `en` English · `es` Spanish · `fr` French · `de` German · `zh` Chinese · `ar` Arabic · `ja` Japanese · `ko` Korean · `pt` Portuguese · `ru` Russian · `hi` Hindi · `it` Italian · `nl` Dutch · `sv` Swedish · `tr` Turkish · `pl` Polish · `uk` Ukrainian · `fa` Farsi · `he` Hebrew · `th` Thai · `vi` Vietnamese · `id` Indonesian · `ms` Malay · `sw` Swahili · `bn` Bengali · `ta` Tamil -> `lar7$en6de3+5` — native Arabic (paid translator), fluent English, learning German (beginner, aspiring proficient). +> `lar7$en6de3+5`: native Arabic (paid translator), fluent English, learning German (beginner, aspiring proficient). --- -### m — Music +### m: Music | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| @@ -461,7 +461,7 @@ Scale: **enthusiasm** (see global scale table). --- -### o — Operating System +### o: Operating System **Linux (x prefix):** @@ -519,26 +519,26 @@ Scale: **proficiency** (see global scale table). --- -### p — Programming Languages +### p: Programming Languages | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| | `py` | `(python)` | Python | `rs` | `(rust)` | Rust | -| `c` | — | C | `cp` | `(cpp)` | C++ | +| `c` | - | C | `cp` | `(cpp)` | C++ | | `cs` | `(csharp)` | C# | `jv` | `(java)` | Java | | `kt` | `(kotlin)` | Kotlin | `sw` | `(swift)` | Swift | | `ph` | `(php)` | PHP | `pl` | `(perl)` | Perl | | `lu` | `(lua)` | Lua | `hs` | `(haskell)` | Haskell | | `cj` | `(clj)` | Clojure | `er` | `(erlang)` | Erlang | | `ex` | `(elixir)` | Elixir | `sc` | `(scala)` | Scala | -| `jl` | `(julia)` | Julia | `r` | — | R | +| `jl` | `(julia)` | Julia | `r` | - | R | | `m` | `(matlab)` | MATLAB | `zg` | `(zig)` | Zig | | `ni` | `(nim)` | Nim | `as` | `(asm)` | Assembly | | `sq` | `(sql)` | SQL | `ba` | `(bash)` | Bash | -| `ps` | `(psh)` | PowerShell | `vb` | — | Visual Basic | +| `ps` | `(psh)` | PowerShell | `vb` | - | Visual Basic | | `da` | `(dart)` | Dart | `oc` | `(objc)` | Obj-C | -| `ls` | `(lisp)` | Lisp | `js` | — | JavaScript | -| `ts` | — | TypeScript | `go` | — | Go | +| `ls` | `(lisp)` | Lisp | `js` | - | JavaScript | +| `ts` | - | TypeScript | `go` | - | Go | | `rb` | `(ruby)` | Ruby | `ml` | `(ocaml)` | OCaml | | `fs` | `(fsharp)` | F# | `pr` | `(prolog)` | Prolog | | `co` | `(cobol)` | COBOL | `fo` | `(fortran)` | Fortran | @@ -555,22 +555,22 @@ Scale: **proficiency** (see global scale table). --- -### q — Security / Hacking +### q: Security / Hacking -0. **No security** — Password is 'password'. Click every link. -1. **Don't care** — Firewalls are a construction thing. -2. **Default passwords** — Pet name plus birth year. Reused everywhere. -3. **Basic awareness** — Know phishing is bad. Fallen for it once. -4. **Password manager** — Normal human security. -5. **2FA everywhere** — Judge friends' passwords. -6. **Encrypts everything** — Paranoid but justified. -7. **Tinfoil hat** — NSA has a file but can't read it either. +0. **No security**: Password is 'password'. Click every link. +1. **Don't care**: Firewalls are a construction thing. +2. **Default passwords**: Pet name plus birth year. Reused everywhere. +3. **Basic awareness**: Know phishing is bad. Fallen for it once. +4. **Password manager**: Normal human security. +5. **2FA everywhere**: Judge friends' passwords. +6. **Encrypts everything**: Paranoid but justified. +7. **Tinfoil hat**: NSA has a file but can't read it either. --- -### r — Religion +### r: Religion -**Single value only** — one tradition + devoutness rating. Parser rejects multiple sub-IDs. +**Single value only**: one tradition + devoutness rating. Parser rejects multiple sub-IDs. | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| @@ -586,24 +586,24 @@ Scale: **proficiency** (see global scale table). Rating = devoutness: 0 = Hostile, 4 = Cultural, 7 = Clergy-level. -> `ris6` — devout Muslim. `rath4` — culturally atheist. +> `ris6`: devout Muslim. `rath4`: culturally atheist. --- -### s — Sex / Relationships +### s: Sex / Relationships -0. **Celibate** — Made your peace. -1. **Not active (unwilling)** — Dating profile is unrewarded effort. -2. **Not active** — 'Focusing on yourself.' Friends skeptical. -3. **Complicated** — Status requires a flowchart. -4. **Private** — None of your business. -5. **Dating** — At least two dating apps. -6. **Partnered** — Found someone who tolerates your geekery. -7. **Married w/ kids** — Evidence is small, loud, doesn't sleep. +0. **Celibate**: Made your peace. +1. **Not active (unwilling)**: Dating profile is unrewarded effort. +2. **Not active**: 'Focusing on yourself.' Friends skeptical. +3. **Complicated**: Status requires a flowchart. +4. **Private**: None of your business. +5. **Dating**: At least two dating apps. +6. **Partnered**: Found someone who tolerates your geekery. +7. **Married w/ kids**: Evidence is small, loud, doesn't sleep. --- -### t — TV / Film +### t: TV / Film **Sci-Fi:** @@ -612,7 +612,7 @@ Rating = devoutness: 0 = Hostile, 4 = Cultural, 7 = Clergy-level. | `st` | `(trek)` | Star Trek | `sw` | `(wars)` | Star Wars | | `sg` | `(gate)` | Stargate | `ex` | `(exp)` | Expanse | | `bs` | `(bsg)` | Battlestar | `bb` | `(bab)` | Babylon 5 | -| `ff` | — | Firefly | `du` | `(dune)` | Dune | +| `ff` | - | Firefly | `du` | `(dune)` | Dune | | `fn` | `(fnd)` | Foundation | `dw` | `(drwho)` | Doctor Who | | `tb` | `(tbp)` | 3 Body Problem | `si` | `(silo)` | Silo | | `fa` | `(fall)` | Fallout | `al` | `(alien)` | Alien | @@ -631,7 +631,7 @@ Rating = devoutness: 0 = Hostile, 4 = Cultural, 7 = Clergy-level. | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| | `mp` | `(monty)` | Monty Python | `bt` | `(bbt)` | Big Bang Theory | -| `rm` | — | Rick & Morty | `bl` | — | Black Mirror | +| `rm` | - | Rick & Morty | `bl` | - | Black Mirror | | `sv` | `(sev)` | Severance | `mr` | `(mrobot)` | Mr. Robot | | `hm` | `(humans)` | Humans | `ww` | `(westworld)` | Westworld | | `dp` | `(devs)` | Devs | | | | @@ -643,36 +643,36 @@ Scale: **enthusiasm** (see global scale table). --- -### v — Politics +### v: Politics Format: `v<social>/<economic>`. No modifiers. **Social:** 0 = Far-right authoritarian · 4 = Centrist · 7 = Far-left libertarian **Economic:** 0 = Unregulated capitalism · 4 = Mixed economy · 7 = Full socialism -> `v5/3` — socially center-left, economically center-right. -> `v4/4` — centrist. You annoy both sides equally. +> `v5/3`: socially center-left, economically center-right. +> `v4/4`: centrist. You annoy both sides equally. --- -### w — Hardware +### w: Hardware -0. **Defeated by USB** — The cable won. -1. **Can plug in** — Big hole is for power. -2. **Replaced a part** — Three YouTube videos and mild weeping. -3. **Troubleshoots** — Can tell RAM from a graphics card. -4. **Built a PC** — Cable management isn't great but it posted. -5. **Comfortable** — Friends call when things break. -6. **Mods regularly** — Void warranties recreationally. -7. **Builds everything** — Manufactured your own PCBs. +0. **Defeated by USB**: The cable won. +1. **Can plug in**: Big hole is for power. +2. **Replaced a part**: Three YouTube videos and mild weeping. +3. **Troubleshoots**: Can tell RAM from a graphics card. +4. **Built a PC**: Cable management isn't great but it posted. +5. **Comfortable**: Friends call when things break. +6. **Mods regularly**: Void warranties recreationally. +7. **Builds everything**: Manufactured your own PCBs. --- -### x — Comics / Manga +### x: Comics / Manga | Short | (Long) | Name | Short | (Long) | Name | |---|---|---|---|---|---| -| `dc` | — | DC Comics | `mv` | `(marvel)` | Marvel | +| `dc` | - | DC Comics | `mv` | `(marvel)` | Marvel | | `mg` | `(manga)` | Manga | `dl` | `(dilbert)` | Dilbert | | `xk` | `(xkcd)` | XKCD | `in` | `(indie)` | Indie | | `wt` | `(webtoon)` | Webtoon | `gn` | `(graphicnovel)` | Graphic Novels | @@ -684,29 +684,29 @@ Scale: **enthusiasm** (see global scale table). --- -### y — Fitness +### y: Fitness -0. **What is outside** — Cardiovascular exercise is walking to the fridge. -1. **Avoids activity** — Physical activity is a social obligation. -2. **Occasional walk** — Coffee is the point, not the walk. -3. **Light activity** — Own athletic wear. Never been to a gym. -4. **Some exercise** — Can climb stairs without existential crisis. -5. **Regular workouts** — Track your steps. -6. **Very active** — Resting heart rate impresses doctors. -7. **Daily athlete** — Body is a temple. Temple has a squat rack. +0. **What is outside**: Cardiovascular exercise is walking to the fridge. +1. **Avoids activity**: Physical activity is a social obligation. +2. **Occasional walk**: Coffee is the point, not the walk. +3. **Light activity**: Own athletic wear. Never been to a gym. +4. **Some exercise**: Can climb stairs without existential crisis. +5. **Regular workouts**: Track your steps. +6. **Very active**: Resting heart rate impresses doctors. +7. **Daily athlete**: Body is a temple. Temple has a squat rack. --- -### z — Maker / DIY +### z: Maker / DIY -0. **Defeated by IKEA** — Allen keys are your nemesis. -1. **IKEA once** — One screw left over. Probably fine. -2. **Basic repairs** — Holes approximately where they should be. -3. **Handy** — Fixes hold. Duct tape is structural. -4. **Simple projects** — A shelf here, a birdhouse there. -5. **Regular maker** — Projects folder never shrinks. -6. **Advanced** — Workshop has zones. -7. **Built my house** — Hardware store sends Christmas card. +0. **Defeated by IKEA**: Allen keys are your nemesis. +1. **IKEA once**: One screw left over. Probably fine. +2. **Basic repairs**: Holes approximately where they should be. +3. **Handy**: Fixes hold. Duct tape is structural. +4. **Simple projects**: A shelf here, a birdhouse there. +5. **Regular maker**: Projects folder never shrinks. +6. **Advanced**: Workshop has zones. +7. **Built my house**: Hardware store sends Christmas card. --- ## Extending UGI @@ -726,7 +726,7 @@ Letters `n` and `u` are reserved for future spec versions. --- ## Examples -### jdoe — Full-stack developer +### jdoe: Full-stack developer ``` ------- BEGIN UGI BLOCK ------- @@ -743,7 +743,7 @@ B5/4 C3 Hh4 Hf5 Hm4 S6 Y4 F4 Z3 ugi:0@jdoe:gcs$/dev,a4,b5/4,c3,hh4f5m4,e4,len7,oxa6we5,ppy6$js6$ts5rs4+6,dvs6,w5,q4,i5,v5/4,rag4,tst5ex7,k5,xxk6,jst5pc6,mrk5in4,s6,y4,f4,z3 ``` -### neo — Security researcher +### neo: Security researcher ``` ------- BEGIN UGI BLOCK ------- @@ -760,7 +760,7 @@ B5/3 C1 Hh2 S2 Y5 F3 Z6 ugi:0@neo:gcy$/sec/os,a3,b5/3,c1,hh2,e6,len6zh4,oxd7$fb6,pc6$ba7as5rs5,ded7,w6,q7$,i1,v6/5,rat5,tff7bs6bl6,k7,jro6pc7,mmt6el5,s2,y5,f3,z6 ``` -### hax0r — Hardware hacker +### hax0r: Hardware hacker ``` ------- BEGIN UGI BLOCK ------- @@ -793,24 +793,24 @@ ugi:0/3@hax0r:gee$/rb/dy~solder,a5,b6/5,c2,hh3f6+7m5,e4,lja7en5,oxg7fb5,pc5ba6as ### Fields retained -`a` age brackets, `b` dimensions, `c` dress/clothing, `e` education, `g` geek type, expanded, `h` hair/beard, `k` books, `o` os merged, `p` programming, `q` security/pgp, `s` sex, `t` tv merged, `v` politics, `w` hardware — all present in one or both predecessors. +`a` age brackets, `b` dimensions, `c` dress/clothing, `e` education, `g` geek type, expanded, `h` hair/beard, `k` books, `o` os merged, `p` programming, `q` security/pgp, `s` sex, `t` tv merged, `v` politics, `w` hardware. All present in one or both predecessors. ### Fields added -`d` — Editor war must be documented. -`f` — Geeks cook now. -`i` — Defining divide of the 2020s. -`j` — Gaming beyond DOOM. -`l` — Geek culture is global. -`m` — Absent from predecessors. -`r` — From Hacker Key. -`x` — Beyond Dilbert. -`y` — Modern geek exercises. -`z` — Maker movement. +`d`: Editor war must be documented. +`f`: Geeks cook now. +`i`: Defining divide of the 2020s. +`j`: Gaming beyond DOOM. +`l`: Geek culture is global. +`m`: Absent from predecessors. +`r`: From Hacker Key. +`x`: Beyond Dilbert. +`y`: Modern geek exercises. +`z`: Maker movement. ### Fields removed -Usenet — dead. Kibo — niche. DOOM — use j. OS/2, VMS — use o+~custom. MBTI — pop psychology. Residence — low value. GitHub handle — @handle. I/O — abstract. Cracking — use q. Lang hacking — in g. Math level — in e+g. Prog methodology — in p. +Usenet: dead. Kibo: niche. DOOM: use j. OS/2, VMS: use o+~custom. MBTI: pop psychology. Residence: low value. GitHub handle: @handle. I/O: abstract. Cracking: use q. Lang hacking: in g. Math level: in e+g. Prog methodology: in p. --- ## Quick reference @@ -833,9 +833,9 @@ $+ f j l o p q w y z $ only g + only b c d e h k s none a i m r --- ## References -- [Geek Code (v0.1–latest)](https://sig.codes/geek/) — Robert A. Hayden, 1993-1996 -- [Geek Code 4.0+](https://sig.codes/geek-4.0/) — exarobibliologist, 2019-2026 -- [Hacker Key (v0.1–latest)](https://sig.codes/hacker-key/) — Chris Allegretta, 2003-2006 +- [Geek Code](https://sig.codes/geek/), Robert A. Hayden, 1993-1996 +- [Geek Code 4.0+](https://sig.codes/geek-4.0/), exarobibliologist, 2019-2026 +- [Hacker Key](https://sig.codes/hacker-key/), Chris Allegretta, 2003-2006 - [sig.codes](https://sig.codes/) - [Geek Code on Wikipedia](https://en.wikipedia.org/wiki/Geek_Code) - [Geek Code v3.12 mirror](https://geekcode.xyz/) @@ -845,11 +845,11 @@ $+ f j l o p q w y z $ only g + only b c d e h k s none a i m r ## License -MIT — see [LICENSE](./LICENSE). +MIT. see [LICENSE](./LICENSE). ## Check -- **[ugi.gumx.cc](https://ugi.gumx.cc)** ([repo](https://git.sr.ht/~gumxcc/ugi.gumx.cc)) — web-based UGI encoder, decoder, and format converter -- **ugi-cli** ([repo](https://git.sr.ht/~gumxcc/ugi-cli)) — command-line tool for encoding, decoding, and converting UGI strings +- **[ugi.gumx.cc](https://ugi.gumx.cc)** ([repo](https://git.sr.ht/~gumxcc/ugi.gumx.cc)): web-based UGI encoder, decoder, and format converter +- **ugi-cli** ([repo](https://git.sr.ht/~gumxcc/ugi-cli)): command-line tool for encoding, decoding, and converting UGI strings --- @@ -890,5 +890,4 @@ ugi-spec/ ## License -MIT — see [LICENSE](./LICENSE). - +MIT. see [LICENSE](./LICENSE). diff --git a/spec/grammar/UGI_GRAMMAR_v0.abnf b/spec/grammar/UGI_GRAMMAR_v0.abnf index d0f7da1..0c411fc 100644 --- a/spec/grammar/UGI_GRAMMAR_v0.abnf +++ b/spec/grammar/UGI_GRAMMAR_v0.abnf @@ -1,4 +1,4 @@ -; UGI Grammar v0 — RFC 5234 ABNF +; UGI Grammar v0: RFC 5234 ABNF ; Generated from ugi_registry_v0.json ; ; Compatible with abnf.dev/abnf2svg @@ -40,87 +40,87 @@ sub-id = 1*4ALPHA ; === Per-field rules === -; a — Age (direct) +; a: Age (direct) field-a = %x61 ODIGIT -; b — Build (special) +; b: Build (special) field-b = %x62 ODIGIT [aspire] "/" ODIGIT [aspire] -; c — Clothing (direct) +; c: Clothing (direct) field-c = %x63 ODIGIT [alternative / aspire] -; d — Editor / IDE (multi) +; d: Editor / IDE (multi) field-d-entry = (sub-id / custom-sub) ODIGIT [aspire] field-d = %x64 1*field-d-entry -; e — Education (direct) +; e: Education (direct) field-e = %x65 ODIGIT [aspire] -; f — Food / Cooking (direct) +; f: Food / Cooking (direct) field-f = %x66 ODIGIT [modifier] -; g — Geek Specialization (special) +; g: Geek Specialization (special) g-domain = 2*4ALPHA ["$"] g-custom = "~" 2*ALPHA field-g = %x67 (g-domain / g-custom) *("/" (g-domain / g-custom)) -; h — Hair (multi) +; h: Hair (multi) field-h-entry = (sub-id / custom-sub) ODIGIT [aspire] field-h = %x68 1*field-h-entry -; i — AI Stance (direct) +; i: AI Stance (direct) field-i = %x69 ODIGIT -; j — Gaming (multi) +; j: Gaming (multi) field-j-entry = (sub-id / custom-sub) ODIGIT [modifier] field-j = %x6A 1*field-j-entry -; k — Books (direct) +; k: Books (direct) field-k = %x6B ODIGIT [aspire] -; l — Spoken Languages (multi) +; l: Spoken Languages (multi) field-l-entry = 2ALPHA ODIGIT [modifier] field-l = %x6C 1*field-l-entry -; m — Music (multi) +; m: Music (multi) field-m-entry = (sub-id / custom-sub) ODIGIT field-m = %x6D 1*field-m-entry -; o — Operating System (multi) +; o: Operating System (multi) field-o-entry = (sub-id / custom-sub) ODIGIT [modifier] field-o = %x6F 1*field-o-entry -; p — Programming Languages (multi) +; p: Programming Languages (multi) field-p-entry = (sub-id / custom-sub) ODIGIT [modifier] field-p = %x70 1*field-p-entry -; q — Security / Hacking (direct) +; q: Security / Hacking (direct) field-q = %x71 ODIGIT [alternative / modifier] -; r — Religion (single) +; r: Religion (single) field-r = %x72 (sub-id / custom-sub) ODIGIT -; s — Sex / Relationships (direct) +; s: Sex / Relationships (direct) field-s = %x73 ODIGIT [alternative / aspire] -; t — TV / Film (multi) +; t: TV / Film (multi) field-t-entry = (sub-id / custom-sub) ODIGIT field-t = %x74 1*field-t-entry -; v — Politics (special) +; v: Politics (special) field-v = %x76 ODIGIT "/" ODIGIT -; w — Hardware (direct) +; w: Hardware (direct) field-w = %x77 ODIGIT [modifier] -; x — Comics / Manga (multi) +; x: Comics / Manga (multi) field-x-entry = (sub-id / custom-sub) ODIGIT field-x = %x78 1*field-x-entry -; y — Fitness (direct) +; y: Fitness (direct) field-y = %x79 ODIGIT [modifier] -; z — Maker / DIY (direct) +; z: Maker / DIY (direct) field-z = %x7A ODIGIT [modifier] ; === Block format === diff --git a/spec/grammar/UGI_GRAMMAR_v0.ebnf b/spec/grammar/UGI_GRAMMAR_v0.ebnf index e37f87f..d950818 100644 --- a/spec/grammar/UGI_GRAMMAR_v0.ebnf +++ b/spec/grammar/UGI_GRAMMAR_v0.ebnf @@ -1,4 +1,4 @@ -/* UGI Grammar v0 — W3C EBNF */ +/* UGI Grammar v0: W3C EBNF */ /* Generated from ugi_registry_v0.json */ /* Compatible with bottlecaps.de/rr/ui */ @@ -62,87 +62,87 @@ sub_id ::= alpha alpha? alpha? alpha? /* === Per-field rules === */ -/* a — Age (direct) */ +/* a: Age (direct) */ field_a ::= 'a' odigit -/* b — Build (special) */ +/* b: Build (special) */ field_b ::= 'b' odigit aspire? '/' odigit aspire? -/* c — Clothing (direct) */ +/* c: Clothing (direct) */ field_c ::= 'c' odigit ( alternative | aspire )? -/* d — Editor / IDE (multi) */ +/* d: Editor / IDE (multi) */ field_d_entry ::= ( sub_id | custom_sub ) odigit ( aspire )? field_d ::= 'd' field_d_entry+ -/* e — Education (direct) */ +/* e: Education (direct) */ field_e ::= 'e' odigit ( aspire )? -/* f — Food / Cooking (direct) */ +/* f: Food / Cooking (direct) */ field_f ::= 'f' odigit ( modifier )? -/* g — Geek Specialization (special) */ +/* g: Geek Specialization (special) */ g_domain ::= alpha alpha alpha? alpha? paid? g_custom ::= '~' alpha alpha alpha* field_g ::= 'g' ( g_domain | g_custom ) ( '/' ( g_domain | g_custom ) )* -/* h — Hair (multi) */ +/* h: Hair (multi) */ field_h_entry ::= ( sub_id | custom_sub ) odigit ( aspire )? field_h ::= 'h' field_h_entry+ -/* i — AI Stance (direct) */ +/* i: AI Stance (direct) */ field_i ::= 'i' odigit -/* j — Gaming (multi) */ +/* j: Gaming (multi) */ field_j_entry ::= ( sub_id | custom_sub ) odigit ( modifier )? field_j ::= 'j' field_j_entry+ -/* k — Books (direct) */ +/* k: Books (direct) */ field_k ::= 'k' odigit ( aspire )? -/* l — Spoken Languages (multi) */ +/* l: Spoken Languages (multi) */ field_l_entry ::= alpha alpha odigit ( modifier )? field_l ::= 'l' field_l_entry+ -/* m — Music (multi) */ +/* m: Music (multi) */ field_m_entry ::= ( sub_id | custom_sub ) odigit field_m ::= 'm' field_m_entry+ -/* o — Operating System (multi) */ +/* o: Operating System (multi) */ field_o_entry ::= ( sub_id | custom_sub ) odigit ( modifier )? field_o ::= 'o' field_o_entry+ -/* p — Programming Languages (multi) */ +/* p: Programming Languages (multi) */ field_p_entry ::= ( sub_id | custom_sub ) odigit ( modifier )? field_p ::= 'p' field_p_entry+ -/* q — Security / Hacking (direct) */ +/* q: Security / Hacking (direct) */ field_q ::= 'q' odigit ( alternative | modifier )? -/* r — Religion (single) */ +/* r: Religion (single) */ field_r ::= 'r' ( sub_id | custom_sub ) odigit -/* s — Sex / Relationships (direct) */ +/* s: Sex / Relationships (direct) */ field_s ::= 's' odigit ( alternative | aspire )? -/* t — TV / Film (multi) */ +/* t: TV / Film (multi) */ field_t_entry ::= ( sub_id | custom_sub ) odigit field_t ::= 't' field_t_entry+ -/* v — Politics (special) */ +/* v: Politics (special) */ field_v ::= 'v' odigit '/' odigit -/* w — Hardware (direct) */ +/* w: Hardware (direct) */ field_w ::= 'w' odigit ( modifier )? -/* x — Comics / Manga (multi) */ +/* x: Comics / Manga (multi) */ field_x_entry ::= ( sub_id | custom_sub ) odigit field_x ::= 'x' field_x_entry+ -/* y — Fitness (direct) */ +/* y: Fitness (direct) */ field_y ::= 'y' odigit ( modifier )? -/* z — Maker / DIY (direct) */ +/* z: Maker / DIY (direct) */ field_z ::= 'z' odigit ( modifier )? /* === Block format === */ diff --git a/spec/scripts/gen_abnf.py b/spec/scripts/gen_abnf.py index 9569195..88354ea 100644 --- a/spec/scripts/gen_abnf.py +++ b/spec/scripts/gen_abnf.py @@ -53,7 +53,7 @@ def main(): fields = reg["fields"] lines = [] - lines.append("; UGI Grammar v0 — RFC 5234 ABNF") + lines.append("; UGI Grammar v0: RFC 5234 ABNF") lines.append("; Generated from ugi_registry_v0.json") lines.append(";") lines.append("; Compatible with abnf.dev/abnf2svg") @@ -119,7 +119,7 @@ def main(): has_alt = mods.get("alternative", False) hex_code = hex_char(code) - lines.append(f"; {code} — {f['name']} ({ftype})") + lines.append(f"; {code}: {f['name']} ({ftype})") if ftype == "direct": # Build modifier part diff --git a/spec/scripts/gen_ebnf.py b/spec/scripts/gen_ebnf.py index 65824bd..990ae07 100644 --- a/spec/scripts/gen_ebnf.py +++ b/spec/scripts/gen_ebnf.py @@ -21,7 +21,7 @@ def main(): fields = reg["fields"] lines = [] - lines.append("/* UGI Grammar v0 — W3C EBNF */") + lines.append("/* UGI Grammar v0: W3C EBNF */") lines.append("/* Generated from ugi_registry_v0.json */") lines.append("/* Compatible with bottlecaps.de/rr/ui */") lines.append("") @@ -82,7 +82,7 @@ def main(): has_aspire = mods.get("aspire", False) has_alt = mods.get("alternative", False) - lines.append(f"/* {code} — {f['name']} ({ftype}) */") + lines.append(f"/* {code}: {f['name']} ({ftype}) */") if ftype == "direct": mod_parts = [] diff --git a/spec/scripts/gen_spec.py b/spec/scripts/gen_spec.py index 4ab8a20..bf4739b 100644 --- a/spec/scripts/gen_spec.py +++ b/spec/scripts/gen_spec.py @@ -13,7 +13,7 @@ def load_registry(path): def gen_title(spec): lines = [] - lines.append(f"# {spec['name']} — {spec['full_name']}") + lines.append(f"# {spec['name']}: {spec['full_name']}") lines.append("") lines.append( f"**Version: {spec['version']} ({spec['status'].title()}) · " @@ -47,10 +47,10 @@ def gen_overview(spec): lines.append("") lines.append("### Design goals") lines.append("") - lines.append("1. Single-letter field codes — 24 active, 2 reserved") - lines.append("2. Octal (0–7) rating scale") - lines.append("3. URI-safe characters only — no percent-encoding needed") - lines.append("4. Dual format — URI for machines, block for humans") + lines.append("1. Single-letter field codes: 24 active, 2 reserved") + lines.append("2. Octal (0-7) rating scale") + lines.append("3. URI-safe characters only, no percent-encoding needed") + lines.append("4. Dual format: URI for machines, block for humans") lines.append("5. Case-insensitive") lines.append("6. Extensible via `~` custom sub-IDs") lines.append("") @@ -97,7 +97,7 @@ def gen_formats(fmt): ) lines.append("") lines.append( - "No modifier or character changes are needed — both formats use identical symbols." + "No modifier or character changes are needed; both formats use identical symbols." ) lines.append("") lines.append("### Mandatory fields") @@ -120,16 +120,16 @@ def gen_grammar(fields): lines.append("<field_code><sub_id><digit>[<modifier>]...") lines.append("```") lines.append("") - lines.append("- **Field codes** — single letter (a–z), case-insensitive") - lines.append("- **Sub-IDs** — 1–4 lowercase alpha chars") - lines.append("- **Ratings** — single octal digit (0–7), required on every sub-ID") - lines.append("- **Custom sub-IDs** — `~` prefix: `~fermenting5`") - lines.append("- **Modifiers** — after the digit:") - lines.append(" - `$` — paid") - lines.append(" - `+` followed by digit — aspiring toward that level") - lines.append(" - `$+` combined — paid and aspiring") + lines.append("- **Field codes**: single letter (a-z), case-insensitive") + lines.append("- **Sub-IDs**: 1-4 lowercase alpha chars") + lines.append("- **Ratings**: single octal digit (0-7), required on every sub-ID") + lines.append("- **Custom sub-IDs**: `~` prefix: `~fermenting5`") + lines.append("- **Modifiers**: after the digit:") + lines.append(" - `$`: paid") + lines.append(" - `+` followed by digit: aspiring toward that level") + lines.append(" - `$+` combined: paid and aspiring") lines.append( - '- **Alternatives** — `/` between two digits on direct-value fields: `c3/6`' + '- **Alternatives**: `/` between two digits on direct-value fields: `c3/6`' ) lines.append("") lines.append("### Special formats") @@ -241,7 +241,7 @@ def gen_field_list(fields, fmt): for code in all_codes: if code in reserved: - lines.append(f"| `{code}` | *(reserved)* | — | — |") + lines.append(f"| `{code}` | *(reserved)* | - | - |") continue f = fields[code] ftype = f["type"] @@ -285,10 +285,10 @@ def gen_field_def_direct(code, f): if vals: for i in range(8): v = vals.get(str(i), {}) - label = v.get("label", "—") + label = v.get("label", "") humor = v.get("humor") if humor: - lines.append(f"{i}. **{label}** — {humor}") + lines.append(f"{i}. **{label}**: {humor}") else: lines.append(f"{i}. **{label}**") lines.append("") @@ -310,8 +310,8 @@ def gen_field_def_special_b(f): parts = [f"{k} = {v}" for k, v in sorted(vals.items(), key=lambda x: int(x[0]))] lines[-1] += " · ".join(parts) lines.append("") - lines.append("> `b6/3` — tall and slim.") - lines.append("> `b4/4` — average. Chairs were designed for you.") + lines.append("> `b6/3`: tall and slim.") + lines.append("> `b4/4`: average. Chairs were designed for you.") lines.append("") return "\n".join(lines) @@ -328,8 +328,8 @@ def gen_field_def_special_v(f): high = vals.get("7", "") lines.append(f"**{dim_name.title()}:** 0 = {low} · 4 = {mid} · 7 = {high}") lines.append("") - lines.append("> `v5/3` — socially center-left, economically center-right.") - lines.append("> `v4/4` — centrist. You annoy both sides equally.") + lines.append("> `v5/3`: socially center-left, economically center-right.") + lines.append("> `v4/4`: centrist. You annoy both sides equally.") lines.append("") return "\n".join(lines) @@ -385,10 +385,10 @@ def gen_field_def_special_g(f): lines.append("#### Custom") lines.append("") lines.append( - "`~fermenting` `~beekeeping` `~lockpicking` `~origami` — anything goes." + "`~fermenting` `~beekeeping` `~lockpicking` `~origami`. Anything goes." ) lines.append("") - lines.append("> `gcs$/ai/wr~fermenting` — paid CS geek, also into AI, writing, and fermenting.") + lines.append("> `gcs$/ai/wr~fermenting`: paid CS geek, also into AI, writing, and fermenting.") lines.append("") return "\n".join(lines) @@ -410,20 +410,20 @@ def gen_sub_id_table(sub_ids): for i in range(0, len(items), 2): c1, v1 = items[i] if isinstance(v1, dict): - long1 = f"`({v1['long']})`" if "long" in v1 else "—" + long1 = f"`({v1['long']})`" if "long" in v1 else "-" name1 = v1.get("name", c1) else: - long1 = "—" + long1 = "-" name1 = v1 row = f"| `{c1}` | {long1} | {name1}" if i + 1 < len(items): c2, v2 = items[i + 1] if isinstance(v2, dict): - long2 = f"`({v2['long']})`" if "long" in v2 else "—" + long2 = f"`({v2['long']})`" if "long" in v2 else "-" name2 = v2.get("name", c2) else: - long2 = "—" + long2 = "-" name2 = v2 row += f" | `{c2}` | {long2} | {name2} |" else: @@ -480,19 +480,19 @@ def gen_grouped_sub_ids(groups, inline_threshold=7): for i in range(0, len(items), 2): c1, v1 = items[i] if isinstance(v1, dict): - long1 = f"`({v1['long']})`" if "long" in v1 else "—" + long1 = f"`({v1['long']})`" if "long" in v1 else "-" name1 = v1.get("name", c1) else: - long1 = "—" + long1 = "-" name1 = v1 row = f"| `{c1}` | {long1} | {name1}" if i + 1 < len(items): c2, v2 = items[i + 1] if isinstance(v2, dict): - long2 = f"`({v2['long']})`" if "long" in v2 else "—" + long2 = f"`({v2['long']})`" if "long" in v2 else "-" name2 = v2.get("name", c2) else: - long2 = "—" + long2 = "-" name2 = v2 row += f" | `{c2}` | {long2} | {name2} |" else: @@ -532,10 +532,10 @@ def gen_field_def_multi(code, f, scale): if "scale_labels" in f: humor = f.get("humor_scale", {}) or {} for i in range(8): - label = f["scale_labels"].get(str(i), "—") + label = f["scale_labels"].get(str(i), "") h = humor.get(str(i)) if h: - lines.append(f"{i}. **{label}** — {h}") + lines.append(f"{i}. **{label}**: {h}") else: lines.append(f"{i}. **{label}**") lines.append("") @@ -552,7 +552,7 @@ def gen_field_def_multi(code, f, scale): def gen_field_def_single(code, f): """Generate a single-type field definition.""" lines = [] - lines.append("**Single value only** — one tradition + devoutness rating. Parser rejects multiple sub-IDs.") + lines.append("**Single value only**: one tradition + devoutness rating. Parser rejects multiple sub-IDs.") lines.append("") if "sub_ids" in f: @@ -565,7 +565,7 @@ def gen_field_def_single(code, f): # Example if code == "r": - lines.append("> `ris6` — devout Muslim. `rath4` — culturally atheist.") + lines.append("> `ris6`: devout Muslim. `rath4`: culturally atheist.") lines.append("") return "\n".join(lines) @@ -580,10 +580,10 @@ def gen_field_def_lang(f): if "scale_labels" in f: humor = f.get("humor_scale", {}) or {} for i in range(8): - label = f["scale_labels"].get(str(i), "—") + label = f["scale_labels"].get(str(i), "") h = humor.get(str(i)) if h: - lines.append(f"{i}. **{label}** — {h}") + lines.append(f"{i}. **{label}**: {h}") else: lines.append(f"{i}. **{label}**") lines.append("") @@ -598,7 +598,7 @@ def gen_field_def_lang(f): lines.append("") lines.append( - "> `lar7$en6de3+5` — native Arabic (paid translator), fluent English, " + "> `lar7$en6de3+5`: native Arabic (paid translator), fluent English, " "learning German (beginner, aspiring proficient)." ) lines.append("") @@ -614,7 +614,7 @@ def gen_field_definitions(fields, scale): for code in sorted(fields.keys()): f = fields[code] - lines.append(f"### {code} — {f['name']}") + lines.append(f"### {code}: {f['name']}") lines.append("") ftype = f["type"] @@ -673,7 +673,7 @@ def gen_examples(examples, fields): desc = ex["description"] uri = ex["uri"] - lines.append(f"### {handle} — {desc}") + lines.append(f"### {handle}: {desc}") lines.append("") # Generate block format from URI @@ -905,7 +905,7 @@ def gen_comparison(comparison): parts.append(f"`{r['ugi']}` {r['note'].lower()}") lines.append( ", ".join(parts) - + " — all present in one or both predecessors." + + ". All present in one or both predecessors." ) lines.append("") @@ -913,7 +913,7 @@ def gen_comparison(comparison): lines.append("### Fields added") lines.append("") for a in comparison["added"]: - lines.append(f"`{a['ugi']}` — {a['reason']}. ", ) + lines.append(f"`{a['ugi']}`: {a['reason']}. ") lines.append("") # Removed @@ -921,7 +921,7 @@ def gen_comparison(comparison): lines.append("") parts = [] for r in comparison["removed"]: - parts.append(f"{r['field']} — {r['reason'].lower()}") + parts.append(f"{r['field']}: {r['reason'].lower()}") lines.append(". ".join(parts) + ".") lines.append("") lines.append("---") @@ -992,10 +992,10 @@ def gen_references(spec): lines.append("## References") lines.append("") for p in spec["predecessors"]: - lines.append(f"- [{p['name']} (v0.1–latest)]({p['url']}) — {p['author']}, {p['years']}") + lines.append(f"- [{p['name']}]({p['url']}), {p['author']}, {p['years']}") if "continuation" in p: c = p["continuation"] - lines.append(f"- [{c['name']}]({c['url']}) — {c['author']}, {c['years']}") + lines.append(f"- [{c['name']}]({c['url']}), {c['author']}, {c['years']}") for r in spec.get("references", []): lines.append(f"- [{r['title']}]({r['url']})") lines.append("") @@ -1003,7 +1003,7 @@ def gen_references(spec): lines.append("") lines.append("## License") lines.append("") - lines.append(f"{spec['license']} — see [LICENSE](./LICENSE).") + lines.append(f"{spec['license']}. see [LICENSE](./LICENSE).") lines.append("") return "\n".join(lines) diff --git a/spec/tests/cases.json b/spec/tests/cases.json index d27976f..92cb499 100644 --- a/spec/tests/cases.json +++ b/spec/tests/cases.json @@ -193,7 +193,7 @@ { "id": "direct_all_three_modifiers", - "description": "Direct field with $, /, and + (security: q5$/6+7 — paid, fluctuates, aspires)", + "description": "Direct field with $, /, and + (security: q5$/6+7, paid, fluctuates, aspires)", "uri": "ugi:0@t6b:gcs,q5$/6+7", "block": "------- BEGIN UGI BLOCK -------\nv:0 @t6b Gcs\nQ5$/6+7\n-------- END UGI BLOCK --------", "decoded": { @@ -406,7 +406,7 @@ { "id": "multi_1char_subid", - "description": "Multi field with 1-char sub-IDs (hair: hh4f6) and single-char sub-IDs (programming: pc5 — 'c' is 1-char)", + "description": "Multi field with 1-char sub-IDs (hair: hh4f6) and single-char sub-IDs (programming: pc5, 'c' is 1-char)", "uri": "ugi:0@t18:gcs,hh4f6,pc5", "block": "------- BEGIN UGI BLOCK -------\nv:0 @t18 Gcs\nHh4 Hf6\nPc5\n-------- END UGI BLOCK --------", "decoded": { @@ -426,7 +426,7 @@ { "id": "multi_3char_subid", - "description": "Multi field with 3-char sub-ID from sub_id_groups (OS: oxsl5 — 'xsl' is Slackware)", + "description": "Multi field with 3-char sub-ID from sub_id_groups (OS: oxsl5, 'xsl' is Slackware)", "uri": "ugi:0@t19:gcs,oxsl5", "block": "------- BEGIN UGI BLOCK -------\nv:0 @t19 Gcs\nOxsl5\n-------- END UGI BLOCK --------", "decoded": { diff --git a/web/README.md b/web/README.md index 3acccf5..31c1c28 100644 --- a/web/README.md +++ b/web/README.md @@ -4,11 +4,11 @@ Web-based UGI (Unified Geek Identifier) tool: encoder, decoder, format converter ## Pages -- **/** — about the project and format examples -- **/specs/** — full field-by-field spec reference -- **/encoder/** — form-based interface to build a UGI string -- **/decoder/** — paste a UGI string (URI or block format) and get a human-readable breakdown -- **/converter/** — convert between URI format and block format +- **/** : about the project and format examples +- **/specs/** : full field-by-field spec reference +- **/encoder/** : form-based interface to build a UGI string +- **/decoder/** : paste a UGI string (URI or block format) and get a human-readable breakdown +- **/converter/** : convert between URI format and block format Each page shares the same network-standard CSS and sub-navigation. @@ -34,7 +34,7 @@ web/ └── README.md ``` -Generated HTML files are not committed — they are produced by the post-receive hook on the server. +Generated HTML files are not committed. They are produced by the post-receive hook on the server. ## License diff --git a/web/scripts/gen_tool.py b/web/scripts/gen_tool.py index bbafcfb..a3d3012 100644 --- a/web/scripts/gen_tool.py +++ b/web/scripts/gen_tool.py @@ -131,17 +131,17 @@ def build_home(reg): <pre><code>ugi:0@neo:gcy$/sec/os,a3,b5/3,c1,hh2,e6,len6zh4,oxd7$fb6,pc6$ba7as5rs5,ded7,w6,q7$,i1,v6/5,rat5,tff7bs6bl6,k7,jro6pc7,mmt6el5,s2,y5,f3,z6</code></pre>""" fields_summary = "\n".join( - f"<li><code>{code}</code> — {f['name']}</li>" + f"<li><code>{code}</code>: {f['name']}</li>" for code, f in sorted(reg["fields"].items()) ) content = f"""<p>{desc}</p> <h2>design goals</h2> <ul> -<li>Single-letter field codes — 24 active, 2 reserved</li> -<li>Octal (0–7) rating scale</li> -<li>URI-safe characters only — no percent-encoding needed</li> -<li>Dual format — URI for machines, block for humans</li> +<li>Single-letter field codes: 24 active, 2 reserved</li> +<li>Octal (0-7) rating scale</li> +<li>URI-safe characters only, no percent-encoding needed</li> +<li>Dual format: URI for machines, block for humans</li> <li>Case-insensitive</li> <li>Extensible via <code>~</code> custom sub-IDs</li> </ul> @@ -180,7 +180,7 @@ def build_specs(reg): for code in codes: f = reg["fields"][code] req = " <em>(required)</em>" if code == "g" else "" - out += f"<hr><h2>{htmlmod.escape(code)} — {htmlmod.escape(f['name'])}{req}</h2>" + out += f"<hr><h2>{htmlmod.escape(code)}: {htmlmod.escape(f['name'])}{req}</h2>" if f.get("description"): out += f'<p>{htmlmod.escape(f["description"])}</p>' @@ -198,22 +198,22 @@ def build_specs(reg): out += '<ol start="0">' for i in range(8): v = f["values"].get(str(i), {}) - label = htmlmod.escape(v.get("label", "—")) + label = htmlmod.escape(v.get("label", "")) humor = v.get("humor", "") out += f"<li><strong>{label}</strong>" if humor: - out += f" — <em>{htmlmod.escape(humor)}</em>" + out += f": <em>{htmlmod.escape(humor)}</em>" out += "</li>" out += "</ol>" if f.get("scale_labels"): out += '<ol start="0">' for i in range(8): - label = htmlmod.escape(f["scale_labels"].get(str(i), "—")) + label = htmlmod.escape(f["scale_labels"].get(str(i), "")) humor = f.get("humor_scale", {}).get(str(i), "") out += f"<li><strong>{label}</strong>" if humor: - out += f" — <em>{htmlmod.escape(humor)}</em>" + out += f": <em>{htmlmod.escape(humor)}</em>" out += "</li>" out += "</ol>" elif f.get("humor_scale"): @@ -227,7 +227,7 @@ def build_specs(reg): for sid, sv in f["sub_ids"].items(): name = sv.get("name", sid) if isinstance(sv, dict) else sv long = sv.get("long", "") if isinstance(sv, dict) else "" - out += f"<li><code>{sid}</code> — {htmlmod.escape(str(name))}" + out += f"<li><code>{sid}</code>: {htmlmod.escape(str(name))}" if long: out += f" (block alias: <code>{long}</code>)" out += "</li>" @@ -239,7 +239,7 @@ def build_specs(reg): out += f"<h3>{htmlmod.escape(label)}</h3><ul>" for sid, sv in (group.get("sub_ids") or {}).items(): name = sv.get("name", sid) if isinstance(sv, dict) else sv - out += f"<li><code>{sid}</code> — {htmlmod.escape(str(name))}</li>" + out += f"<li><code>{sid}</code>: {htmlmod.escape(str(name))}</li>" out += "</ul>" if f.get("dimensions"): @@ -441,7 +441,7 @@ function buildEncoder() { const summary = document.createElement('summary'); const skipId = 'skip-' + code; const reqLabel = isRequired ? ' (required)' : ''; - summary.innerHTML = code.toUpperCase() + ' — ' + f.name + reqLabel + + summary.innerHTML = code.toUpperCase() + ': ' + f.name + reqLabel + '<label class="skip-check"><input type="checkbox" id="' + skipId + '" checked onchange="encodeUGI()"' + (isRequired ? ' disabled' : '') + '> include</label>'; section.appendChild(summary); @@ -514,7 +514,7 @@ function buildDirectField(container, code, f) { aSel.id = 'enc-' + code + '-aspire'; for (let i = 5; i <= 7; i++) { const opt = document.createElement('option'); - opt.value = i; opt.textContent = i + ' — ' + (scale[i] ? (scale[i].label || '') : ''); + opt.value = i; opt.textContent = i + ': ' + (scale[i] ? (scale[i].label || '') : ''); aSel.appendChild(opt); } aSel.onchange = function() { encodeUGI(); }; @@ -532,7 +532,7 @@ function buildMultiField(container, code, f) { const sel = document.createElement('select'); sel.id = 'enc-' + code + '-sel'; const defOpt = document.createElement('option'); - defOpt.value = ''; defOpt.textContent = '— select —'; + defOpt.value = ''; defOpt.textContent = 'select'; sel.appendChild(defOpt); let lastGroup = ''; for (const s of subIds) { @@ -541,7 +541,7 @@ function buildMultiField(container, code, f) { optg.label = s.group; sel.appendChild(optg); lastGroup = s.group; } const opt = document.createElement('option'); - opt.value = s.code; opt.textContent = s.code + ' — ' + s.name; + opt.value = s.code; opt.textContent = s.code + ': ' + s.name; if (lastGroup) sel.lastElementChild.appendChild(opt); else sel.appendChild(opt); } @@ -574,7 +574,7 @@ function buildMultiField(container, code, f) { aSel.id = 'enc-' + code + '-add-aspire'; for (let i = 5; i <= 7; i++) { const opt = document.createElement('option'); - opt.value = i; opt.textContent = i + ' — ' + (scale[i] ? (scale[i].label || '') : ''); + opt.value = i; opt.textContent = i + ': ' + (scale[i] ? (scale[i].label || '') : ''); aSel.appendChild(opt); } addRow.appendChild(aSel); @@ -616,10 +616,10 @@ function buildSingleField(container, code, f) { const subIds = getSubIds(code); const scale = getScale(code); const sel = document.createElement('select'); sel.id = 'enc-' + code + '-sub'; sel.onchange = function() { markIncluded(code); encodeUGI(); }; - const defOpt = document.createElement('option'); defOpt.value = ''; defOpt.textContent = '— select —'; + const defOpt = document.createElement('option'); defOpt.value = ''; defOpt.textContent = 'select'; sel.appendChild(defOpt); for (const s of subIds) { - const opt = document.createElement('option'); opt.value = s.code; opt.textContent = s.code + ' — ' + s.name; + const opt = document.createElement('option'); opt.value = s.code; opt.textContent = s.code + ': ' + s.name; sel.appendChild(opt); } container.appendChild(sel); @@ -710,10 +710,10 @@ function buildLangField(container, f) { const addRow = document.createElement('div'); addRow.className = 'multi-add'; const codes = f.common_codes || {}; const sel = document.createElement('select'); sel.id = 'enc-l-sel'; - const defOpt = document.createElement('option'); defOpt.value = ''; defOpt.textContent = '— select or type —'; + const defOpt = document.createElement('option'); defOpt.value = ''; defOpt.textContent = 'select or type'; sel.appendChild(defOpt); for (const [k,v] of Object.entries(codes)) { - const opt = document.createElement('option'); opt.value = k; opt.textContent = k + ' — ' + v; sel.appendChild(opt); + const opt = document.createElement('option'); opt.value = k; opt.textContent = k + ': ' + v; sel.appendChild(opt); } addRow.appendChild(sel); const customInput = document.createElement('input'); @@ -732,7 +732,7 @@ function buildLangField(container, f) { if (mods.aspire) { const aCb = document.createElement('label'); aCb.innerHTML = '<input type="checkbox" id="enc-l-add-aspire-on"> +'; addRow.appendChild(aCb); const aSel = document.createElement('select'); aSel.id = 'enc-l-add-aspire'; - for (let i = 5; i <= 7; i++) { const opt = document.createElement('option'); opt.value = i; opt.textContent = i + ' — ' + (scale[i] ? (scale[i].label || '') : ''); aSel.appendChild(opt); } + for (let i = 5; i <= 7; i++) { const opt = document.createElement('option'); opt.value = i; opt.textContent = i + ': ' + (scale[i] ? (scale[i].label || '') : ''); aSel.appendChild(opt); } addRow.appendChild(aSel); } const addBtn = document.createElement('button'); addBtn.textContent = 'add'; @@ -956,7 +956,7 @@ function convertUGI() { try { if (input.includes('BEGIN UGI BLOCK')) output.textContent = blockToUri(input); else if (input.toLowerCase().startsWith('ugi:')) output.textContent = uriToBlock(input); - else output.textContent = '(could not detect format — paste a URI starting with "ugi:" or a block)'; + else output.textContent = '(could not detect format: paste a URI starting with "ugi:" or a block)'; } catch(e) { output.textContent = 'error: ' + e.message; } } |
