aboutsummaryrefslogtreecommitdiffstats
path: root/spec
diff options
context:
space:
mode:
authorAhmed <git@gumx.cc>2026-06-20 00:07:58 +0300
committerAhmed <git@gumx.cc>2026-06-20 00:07:58 +0300
commitf0b79f27b7f17e3862285e01b71aad30cb25cd89 (patch)
tree47b3e67d55a98c089ce16d77cb55ff04f5f3b216 /spec
parentba5a9613f6c5b7d9b5f7eb7add147a9bdb8b6a44 (diff)
fix: bunch of vibed edits to fix vibed edits
Diffstat (limited to 'spec')
-rw-r--r--spec/README.md349
-rw-r--r--spec/grammar/UGI_GRAMMAR_v0.abnf50
-rw-r--r--spec/grammar/UGI_GRAMMAR_v0.ebnf50
-rw-r--r--spec/scripts/gen_abnf.py4
-rw-r--r--spec/scripts/gen_ebnf.py4
-rw-r--r--spec/scripts/gen_spec.py94
-rw-r--r--spec/tests/cases.json6
7 files changed, 278 insertions, 279 deletions
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": {