Commit cc082e51ff
Verified · cmc
Layout: unified · split
README.nfo +5 −1
| @@ -41,7 +41,11 @@ | |||
| 41 | │ LOGO: --logo art.asc places art above the box │ | 41 | │ LOGO: --logo art.asc places art above the box │ |
| 42 | │ BANNERS: --presents --footer scene boilerplate │ | 42 | │ BANNERS: --presents --footer scene boilerplate │ |
| 43 | │ │ | 43 | │ │ |
| 44 | │ OPTIONS: -o -c -g -s -w --style --layout --logo │ | 44 | │ PROFILE: -p group.toml supplies logo, footer, │ |
| 45 | │ members, couriers, boards, affiliates, │ | ||
| 46 | │ news, outposts. release cfg overrides it. │ | ||
| 47 | │ │ | ||
| 48 | │ OPTIONS: -o -c -p -g -s -w --style --layout --logo │ | ||
| 45 | │ --encoding --presents --footer -i --no-input │ | 49 | │ --encoding --presents --footer -i --no-input │ |
| 46 | │ │ | 50 | │ │ |
| 47 | │ Generated by nfogen itself. │ | 51 | │ Generated by nfogen itself. │ |
examples/krz.profile.toml added +82
| @@ -0,0 +1,82 @@ | |||
| 1 | # A group profile: the reusable KRZ boilerplate. Point any release config at | ||
| 2 | # it with `profile = "examples/krz.profile.toml"` (or nfogen -p PATH) and it | ||
| 3 | # supplies the logo, roster, boards, footer and house style. The release | ||
| 4 | # config only carries per-release fields (title, cracker, notes, ...). | ||
| 5 | |||
| 6 | group = "KRZ" | ||
| 7 | site = "krz.sh" | ||
| 8 | style = "double" | ||
| 9 | layout = "panel" | ||
| 10 | presents = true | ||
| 11 | footer = true | ||
| 12 | logo = "examples/krz-logo.asc" | ||
| 13 | logo_encoding = "utf8" | ||
| 14 | |||
| 15 | footer_text = """ | ||
| 16 | SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE | ||
| 17 | if you enjoyed this release, buy it! | ||
| 18 | KRZ does not accept cash or hardware of any kind. | ||
| 19 | """ | ||
| 20 | |||
| 21 | news = """ | ||
| 22 | Welcome Celestial Wizard back to the group! | ||
| 23 | We are in need of couriers for the release rush -- especially | ||
| 24 | those available in the daytime. Contact a senior member. | ||
| 25 | """ | ||
| 26 | |||
| 27 | members = [ | ||
| 28 | "Avalon", "Black Spyrit", "Butcher", "The Chairman", "Darwin", | ||
| 29 | "Doctor No", "Eldar", "Grimstalk", "Hologram", "Intrepid", "Jack", | ||
| 30 | "Knightmare", "Marko Ramius", "ONYX", "Razor Blade", "Red Baron", | ||
| 31 | "Savage Steel", "StingRay", "The Unknown", "The Witch King", | ||
| 32 | ] | ||
| 33 | |||
| 34 | [couriers] | ||
| 35 | Director = ["The Unforgiven"] | ||
| 36 | Senior = ["Ur Lord", "Lord Tiamat", "Wind Walker", "Bubba"] | ||
| 37 | Elite = ["Shadow Phantom", "Baddog", "Load Master", "Joebee", "Azmodeus"] | ||
| 38 | Trial = ["Dead Pool", "Beach Bum", "Black Mage", "Ben Jammin"] | ||
| 39 | |||
| 40 | [[boards]] | ||
| 41 | name = "Suburbia" | ||
| 42 | role = "World HQ" | ||
| 43 | sysop = "The Chairman" | ||
| 44 | phone = "ITS/PRIVATE" | ||
| 45 | |||
| 46 | [[boards]] | ||
| 47 | name = "TerrorDome" | ||
| 48 | role = "Canadian HQ" | ||
| 49 | sysop = "StingRay" | ||
| 50 | phone = "ITS/PRIVATE" | ||
| 51 | |||
| 52 | [[boards]] | ||
| 53 | name = "Kindergarden" | ||
| 54 | role = "European HQ" | ||
| 55 | sysop = "Doctor No" | ||
| 56 | phone = "+47-7-525156" | ||
| 57 | |||
| 58 | [[boards]] | ||
| 59 | name = "Apocalypse" | ||
| 60 | role = "Courier HQ" | ||
| 61 | sysop = "P.O.W." | ||
| 62 | phone = "ITS/PRIVATE" | ||
| 63 | |||
| 64 | [[affiliates]] | ||
| 65 | name = "Reign In Blood" | ||
| 66 | role = "Affiliate" | ||
| 67 | sysop = "Uriel" | ||
| 68 | phone = "718/698-1969" | ||
| 69 | |||
| 70 | [[affiliates]] | ||
| 71 | name = "So-Krate's" | ||
| 72 | role = "Affiliate" | ||
| 73 | sysop = "Philosopher" | ||
| 74 | phone = "310/578-7226" | ||
| 75 | |||
| 76 | [[affiliates]] | ||
| 77 | name = "The Space Zone" | ||
| 78 | role = "Affiliate" | ||
| 79 | sysop = "Death Ray" | ||
| 80 | phone = "301/416-0118" | ||
| 81 | |||
| 82 | outposts = ["Infinite Darkness", "The Atomic Cafe", "Sanctuary"] | ||
examples/nightfall-demo.cp437.nfo added +83
| @@ -0,0 +1,83 @@ | |||
| 1 | _ _______ ___ | ||
| 2 | | |/ / _ \ \ / / | | ||
| 3 | | ' /| |_) \ V /| | k r a z y w a r e z | ||
| 4 | | . \| _ < | | |_| | ||
| 5 | |_|\_\_| \_\|_| (_) ------- since 1985 ------- | ||
| 6 | |||
| 7 | ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» | ||
| 8 | º N I G H T F A L L . P R O T O C O L . V 1 . 4 - K R Z º | ||
| 9 | º [ KRZ ] krz.sh º | ||
| 10 | º -={ KRZ proudly presents }=- º | ||
| 11 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 12 | º Release Information º Game Information º | ||
| 13 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 14 | º DATE : 2026-08-08 º TYPE : GAME º | ||
| 15 | º CRACKER : Lime º PUBLISHER : Nocturne Interactive º | ||
| 16 | º SUPPLIER : The Witch King º FORMAT : ISO + patch º | ||
| 17 | º PACKAGER : Renegade Chemist º DISKS : 3 x CD º | ||
| 18 | º PROTECTION : SafeDisc + Serial º SIZE : 1.87 GB º | ||
| 19 | º INSTALL : unzip, run SETUP, ap.. º FILES : 96 x 20 MB º | ||
| 20 | º RATING : 9/10 º URL : krz.sh/nightfall º | ||
| 21 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 22 | º NOTES º | ||
| 23 | º º | ||
| 24 | º A stealth-action thriller set in a city that never sleeps. º | ||
| 25 | º Cracked clean -- no disc check, no online activation. º | ||
| 26 | º º | ||
| 27 | º Mount or burn the ISO, install, then run the KRZ patch over º | ||
| 28 | º the game directory. Serial is in the keygen. Tested working. º | ||
| 29 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 30 | º GREETS º | ||
| 31 | º º | ||
| 32 | º razor-blade jack the-couriers everyone-in-the-scene º | ||
| 33 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 34 | º GROUP NEWS º | ||
| 35 | º º | ||
| 36 | º Welcome Celestial Wizard back to the group! º | ||
| 37 | º We are in need of couriers for the release rush -- especially º | ||
| 38 | º those available in the daytime. Contact a senior member. º | ||
| 39 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 40 | º MEMBERS º | ||
| 41 | º º | ||
| 42 | º Avalon Black Spyrit Butcher The Chairman Darwin Doctor No º | ||
| 43 | º Eldar Grimstalk Hologram Intrepid Jack Knightmare Marko º | ||
| 44 | º Ramius ONYX Razor Blade Red Baron Savage Steel StingRay The º | ||
| 45 | º Unknown The Witch King º | ||
| 46 | º º | ||
| 47 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 48 | º COURIERS º | ||
| 49 | º º | ||
| 50 | º - Director - º | ||
| 51 | º The Unforgiven º | ||
| 52 | º º | ||
| 53 | º - Senior - º | ||
| 54 | º Ur Lord Lord Tiamat Wind Walker Bubba º | ||
| 55 | º º | ||
| 56 | º - Elite - º | ||
| 57 | º Shadow Phantom Baddog Load Master Joebee Azmodeus º | ||
| 58 | º º | ||
| 59 | º - Trial - º | ||
| 60 | º Dead Pool Beach Bum Black Mage Ben Jammin º | ||
| 61 | º º | ||
| 62 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 63 | º BOARDS º | ||
| 64 | º º | ||
| 65 | º BOARD ROLE SYSOP CONTACT º | ||
| 66 | º Suburbia World HQ The Chairman ITS/PRIVATE º | ||
| 67 | º TerrorDome Canadian HQ StingRay ITS/PRIVATE º | ||
| 68 | º Kindergarden European HQ Doctor No +47-7-525156 º | ||
| 69 | º Apocalypse Courier HQ P.O.W. ITS/PRIVATE º | ||
| 70 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 71 | º AFFILIATES º | ||
| 72 | º º | ||
| 73 | º BOARD ROLE SYSOP CONTACT º | ||
| 74 | º Reign In Blood Affiliate Uriel 718/698-1969 º | ||
| 75 | º So-Krate's Affiliate Philosopher 310/578-7226 º | ||
| 76 | º The Space Zone Affiliate Death Ray 301/416-0118 º | ||
| 77 | ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ | ||
| 78 | º º | ||
| 79 | º SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE º | ||
| 80 | º if you enjoyed this release, buy it! º | ||
| 81 | º KRZ does not accept cash or hardware of any kind. º | ||
| 82 | º º | ||
| 83 | ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ | ||
examples/nightfall-demo.nfo added +83
| @@ -0,0 +1,83 @@ | |||
| 1 | _ _______ ___ | ||
| 2 | | |/ / _ \ \ / / | | ||
| 3 | | ' /| |_) \ V /| | k r a z y w a r e z | ||
| 4 | | . \| _ < | | |_| | ||
| 5 | |_|\_\_| \_\|_| (_) ------- since 1985 ------- | ||
| 6 | |||
| 7 | ╔�����������������������������������������������������������������������������╗ | ||
| 8 | â•‘ N I G H T F A L L . P R O T O C O L . V 1 . 4 - K R Z â•‘ | ||
| 9 | â•‘ [ KRZ ] krz.sh â•‘ | ||
| 10 | â•‘ -={ KRZ proudly presents }=- â•‘ | ||
| 11 | ╠��������������������������������������╦��������������������������������������╣ | ||
| 12 | â•‘ Release Information â•‘ Game Information â•‘ | ||
| 13 | ╠��������������������������������������╬��������������������������������������╣ | ||
| 14 | â•‘ DATE : 2026-08-08 â•‘ TYPE : GAME â•‘ | ||
| 15 | â•‘ CRACKER : Lime â•‘ PUBLISHER : Nocturne Interactive â•‘ | ||
| 16 | â•‘ SUPPLIER : The Witch King â•‘ FORMAT : ISO + patch â•‘ | ||
| 17 | â•‘ PACKAGER : Renegade Chemist â•‘ DISKS : 3 x CD â•‘ | ||
| 18 | â•‘ PROTECTION : SafeDisc + Serial â•‘ SIZE : 1.87 GB â•‘ | ||
| 19 | â•‘ INSTALL : unzip, run SETUP, ap.. â•‘ FILES : 96 x 20 MB â•‘ | ||
| 20 | â•‘ RATING : 9/10 â•‘ URL : krz.sh/nightfall â•‘ | ||
| 21 | ╠��������������������������������������╩��������������������������������������╣ | ||
| 22 | â•‘ NOTES â•‘ | ||
| 23 | â•‘ â•‘ | ||
| 24 | â•‘ A stealth-action thriller set in a city that never sleeps. â•‘ | ||
| 25 | â•‘ Cracked clean -- no disc check, no online activation. â•‘ | ||
| 26 | â•‘ â•‘ | ||
| 27 | â•‘ Mount or burn the ISO, install, then run the KRZ patch over â•‘ | ||
| 28 | â•‘ the game directory. Serial is in the keygen. Tested working. â•‘ | ||
| 29 | ╠�����������������������������������������������������������������������������╣ | ||
| 30 | â•‘ GREETS â•‘ | ||
| 31 | â•‘ â•‘ | ||
| 32 | â•‘ razor-blade jack the-couriers everyone-in-the-scene â•‘ | ||
| 33 | ╠�����������������������������������������������������������������������������╣ | ||
| 34 | â•‘ GROUP NEWS â•‘ | ||
| 35 | â•‘ â•‘ | ||
| 36 | â•‘ Welcome Celestial Wizard back to the group! â•‘ | ||
| 37 | â•‘ We are in need of couriers for the release rush -- especially â•‘ | ||
| 38 | â•‘ those available in the daytime. Contact a senior member. â•‘ | ||
| 39 | ╠�����������������������������������������������������������������������������╣ | ||
| 40 | â•‘ MEMBERS â•‘ | ||
| 41 | â•‘ â•‘ | ||
| 42 | â•‘ Avalon Black Spyrit Butcher The Chairman Darwin Doctor No â•‘ | ||
| 43 | â•‘ Eldar Grimstalk Hologram Intrepid Jack Knightmare Marko â•‘ | ||
| 44 | â•‘ Ramius ONYX Razor Blade Red Baron Savage Steel StingRay The â•‘ | ||
| 45 | â•‘ Unknown The Witch King â•‘ | ||
| 46 | â•‘ â•‘ | ||
| 47 | ╠�����������������������������������������������������������������������������╣ | ||
| 48 | â•‘ COURIERS â•‘ | ||
| 49 | â•‘ â•‘ | ||
| 50 | â•‘ - Director - â•‘ | ||
| 51 | â•‘ The Unforgiven â•‘ | ||
| 52 | â•‘ â•‘ | ||
| 53 | â•‘ - Senior - â•‘ | ||
| 54 | â•‘ Ur Lord Lord Tiamat Wind Walker Bubba â•‘ | ||
| 55 | â•‘ â•‘ | ||
| 56 | â•‘ - Elite - â•‘ | ||
| 57 | â•‘ Shadow Phantom Baddog Load Master Joebee Azmodeus â•‘ | ||
| 58 | â•‘ â•‘ | ||
| 59 | â•‘ - Trial - â•‘ | ||
| 60 | â•‘ Dead Pool Beach Bum Black Mage Ben Jammin â•‘ | ||
| 61 | â•‘ â•‘ | ||
| 62 | ╠�����������������������������������������������������������������������������╣ | ||
| 63 | â•‘ BOARDS â•‘ | ||
| 64 | â•‘ â•‘ | ||
| 65 | â•‘ BOARD ROLE SYSOP CONTACT â•‘ | ||
| 66 | â•‘ Suburbia World HQ The Chairman ITS/PRIVATE â•‘ | ||
| 67 | â•‘ TerrorDome Canadian HQ StingRay ITS/PRIVATE â•‘ | ||
| 68 | â•‘ Kindergarden European HQ Doctor No +47-7-525156 â•‘ | ||
| 69 | â•‘ Apocalypse Courier HQ P.O.W. ITS/PRIVATE â•‘ | ||
| 70 | ╠�����������������������������������������������������������������������������╣ | ||
| 71 | â•‘ AFFILIATES â•‘ | ||
| 72 | â•‘ â•‘ | ||
| 73 | â•‘ BOARD ROLE SYSOP CONTACT â•‘ | ||
| 74 | â•‘ Reign In Blood Affiliate Uriel 718/698-1969 â•‘ | ||
| 75 | â•‘ So-Krate's Affiliate Philosopher 310/578-7226 â•‘ | ||
| 76 | â•‘ The Space Zone Affiliate Death Ray 301/416-0118 â•‘ | ||
| 77 | ╠�����������������������������������������������������������������������������╣ | ||
| 78 | â•‘ â•‘ | ||
| 79 | â•‘ SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE â•‘ | ||
| 80 | â•‘ if you enjoyed this release, buy it! â•‘ | ||
| 81 | â•‘ KRZ does not accept cash or hardware of any kind. â•‘ | ||
| 82 | â•‘ â•‘ | ||
| 83 | ╚������������������������������������������������������������������������������ | ||
examples/nightfall-demo.toml added +31
| @@ -0,0 +1,31 @@ | |||
| 1 | # A release config. It names a group profile for all the shared boilerplate | ||
| 2 | # and only sets the fields that change from one release to the next. | ||
| 3 | profile = "examples/krz.profile.toml" | ||
| 4 | |||
| 5 | title = "Nightfall.Protocol.v1.4-KRZ" | ||
| 6 | date = "2026-08-08" | ||
| 7 | |||
| 8 | cracker = "Lime" | ||
| 9 | supplier = "The Witch King" | ||
| 10 | packager = "Renegade Chemist" | ||
| 11 | protection = "SafeDisc + Serial" | ||
| 12 | install = "unzip, run SETUP, apply keygen" | ||
| 13 | rating = "9/10" | ||
| 14 | |||
| 15 | type = "GAME" | ||
| 16 | publisher = "Nocturne Interactive" | ||
| 17 | format = "ISO + patch" | ||
| 18 | disks = "3 x CD" | ||
| 19 | size = "1.87 GB" | ||
| 20 | files = "96 x 20 MB" | ||
| 21 | url = "krz.sh/nightfall" | ||
| 22 | |||
| 23 | notes = """ | ||
| 24 | A stealth-action thriller set in a city that never sleeps. | ||
| 25 | Cracked clean -- no disc check, no online activation. | ||
| 26 | |||
| 27 | Mount or burn the ISO, install, then run the KRZ patch over | ||
| 28 | the game directory. Serial is in the keygen. Tested working. | ||
| 29 | """ | ||
| 30 | |||
| 31 | greets = ["razor-blade", "jack", "the-couriers", "everyone-in-the-scene"] | ||
nfogen.py +83 −3
| @@ -140,10 +140,18 @@ def _split_list(value) -> list[str]: | |||
| 140 | return [p for p in str(value).replace(",", " ").split() if p] | 140 | return [p for p in str(value).replace(",", " ").split() if p] |
| 141 | 141 | ||
| 142 | 142 | ||
| 143 | def _col_widths(available: int, fracs: list[float]) -> list[int]: | ||
| 144 | """Split `available` chars into columns by fraction; last column absorbs | ||
| 145 | the remainder so the widths always sum exactly.""" | ||
| 146 | widths = [max(4, int(available * f)) for f in fracs[:-1]] | ||
| 147 | widths.append(available - sum(widths)) | ||
| 148 | return widths | ||
| 149 | |||
| 150 | |||
| 143 | def render(data: dict, group: str, site: str, width: int, | 151 | def render(data: dict, group: str, site: str, width: int, |
| 144 | style: str = DEFAULT_STYLE, presents: bool = False, | 152 | style: str = DEFAULT_STYLE, presents: bool = False, |
| 145 | footer: str | None = None, layout: str = DEFAULT_LAYOUT, | 153 | footer: str | None = None, layout: str = DEFAULT_LAYOUT, |
| 146 | logo: str | None = None, | 154 | logo: str | None = None, roster: dict | None = None, |
| 147 | panel_titles: tuple[str, str] = PANEL_TITLES) -> str: | 155 | panel_titles: tuple[str, str] = PANEL_TITLES) -> str: |
| 148 | s = STYLES[style] | 156 | s = STYLES[style] |
| 149 | h, v = s["h"], s["v"] | 157 | h, v = s["h"], s["v"] |
| @@ -243,6 +251,66 @@ def render(data: dict, group: str, site: str, width: int, | |||
| 243 | for wrapped in textwrap.wrap(" ".join(greets), inner - 4): | 251 | for wrapped in textwrap.wrap(" ".join(greets), inner - 4): |
| 244 | out.append(line(" " + wrapped)) | 252 | out.append(line(" " + wrapped)) |
| 245 | 253 | ||
| 254 | # group roster sections (from a profile): news, members, couriers, | ||
| 255 | # boards/affiliates tables, outposts | ||
| 256 | r = roster or {} | ||
| 257 | |||
| 258 | def section(header: str) -> None: | ||
| 259 | out.append(sep_before()) | ||
| 260 | out.append(line(f" {header}")) | ||
| 261 | out.append(line()) | ||
| 262 | |||
| 263 | def names_block(names: list[str]) -> None: | ||
| 264 | for wrapped in textwrap.wrap(" ".join(names), inner - 4): | ||
| 265 | out.append(center(wrapped)) | ||
| 266 | out.append(line()) | ||
| 267 | |||
| 268 | def table(header: str, entries: list) -> None: | ||
| 269 | section(header) | ||
| 270 | if entries and isinstance(entries[0], dict): | ||
| 271 | ws = _col_widths(inner - 2, [0.34, 0.24, 0.22, 0.20]) | ||
| 272 | keys = ["name", "role", "sysop", "phone"] | ||
| 273 | |||
| 274 | def row(vals: list[str]) -> str: | ||
| 275 | cells = "".join(_clip(str(x), w - 1).ljust(w) | ||
| 276 | for x, w in zip(vals, ws)) | ||
| 277 | return line(" " + cells) | ||
| 278 | |||
| 279 | out.append(row(["BOARD", "ROLE", "SYSOP", "CONTACT"])) | ||
| 280 | for e in entries: | ||
| 281 | out.append(row([e.get(k, "") for k in keys])) | ||
| 282 | else: | ||
| 283 | names_block(_split_list(entries)) | ||
| 284 | |||
| 285 | news = str(r.get("news") or "").strip() | ||
| 286 | if news: | ||
| 287 | section("GROUP NEWS") | ||
| 288 | for para in news.splitlines(): | ||
| 289 | for wrapped in (textwrap.wrap(para, inner - 4) or [""]): | ||
| 290 | out.append(line(" " + wrapped)) | ||
| 291 | |||
| 292 | members = _split_list(r.get("members") or []) | ||
| 293 | if members: | ||
| 294 | section("MEMBERS") | ||
| 295 | names_block(members) | ||
| 296 | |||
| 297 | couriers = r.get("couriers") or {} | ||
| 298 | if couriers: | ||
| 299 | section("COURIERS") | ||
| 300 | for tier, who in couriers.items(): | ||
| 301 | out.append(center(f"- {tier} -")) | ||
| 302 | names_block(_split_list(who)) | ||
| 303 | |||
| 304 | if r.get("boards"): | ||
| 305 | table("BOARDS", r["boards"]) | ||
| 306 | if r.get("affiliates"): | ||
| 307 | table("AFFILIATES", r["affiliates"]) | ||
| 308 | |||
| 309 | outposts = _split_list(r.get("outposts") or []) | ||
| 310 | if outposts: | ||
| 311 | section("OUTPOSTS") | ||
| 312 | names_block(outposts) | ||
| 313 | |||
| 246 | # footer disclaimer, centered | 314 | # footer disclaimer, centered |
| 247 | if footer: | 315 | if footer: |
| 248 | out.append(sep_before()) | 316 | out.append(sep_before()) |
| @@ -268,6 +336,8 @@ def build_parser() -> argparse.ArgumentParser: | |||
| 268 | ) | 336 | ) |
| 269 | p.add_argument("-c", "--config", metavar="PATH", | 337 | p.add_argument("-c", "--config", metavar="PATH", |
| 270 | help="config file (.toml or .json) with field defaults") | 338 | help="config file (.toml or .json) with field defaults") |
| 339 | p.add_argument("-p", "--profile", metavar="PATH", | ||
| 340 | help="group profile (logo, roster, boards) merged under the config") | ||
| 271 | p.add_argument("-o", "--output", metavar="PATH", | 341 | p.add_argument("-o", "--output", metavar="PATH", |
| 272 | help="write to PATH instead of stdout") | 342 | help="write to PATH instead of stdout") |
| 273 | p.add_argument("-g", "--group", help=f"release group (default {DEFAULT_GROUP})") | 343 | p.add_argument("-g", "--group", help=f"release group (default {DEFAULT_GROUP})") |
| @@ -300,7 +370,13 @@ def build_parser() -> argparse.ArgumentParser: | |||
| 300 | 370 | ||
| 301 | def main(argv: list[str] | None = None) -> int: | 371 | def main(argv: list[str] | None = None) -> int: |
| 302 | args = build_parser().parse_args(argv) | 372 | args = build_parser().parse_args(argv) |
| 303 | config = load_config(args.config) | 373 | release = load_config(args.config) |
| 374 | |||
| 375 | # a group profile supplies base values (logo, roster, style, footer, ...); | ||
| 376 | # the release config overrides it, and CLI flags override both. | ||
| 377 | profile_path = args.profile or release.get("profile") | ||
| 378 | profile = load_config(profile_path) if profile_path else {} | ||
| 379 | config = {**profile, **release} | ||
| 304 | 380 | ||
| 305 | # merge: config defaults, then any CLI flag that was supplied | 381 | # merge: config defaults, then any CLI flag that was supplied |
| 306 | data = {k: config[k] for k, *_ in FIELDS if k in config and config[k] != ""} | 382 | data = {k: config[k] for k, *_ in FIELDS if k in config and config[k] != ""} |
| @@ -338,6 +414,10 @@ def main(argv: list[str] | None = None) -> int: | |||
| 338 | except FileNotFoundError: | 414 | except FileNotFoundError: |
| 339 | sys.exit(f"nfogen: logo not found: {logo_path}") | 415 | sys.exit(f"nfogen: logo not found: {logo_path}") |
| 340 | 416 | ||
| 417 | roster = {k: config[k] for k in | ||
| 418 | ("news", "members", "couriers", "boards", "affiliates", "outposts") | ||
| 419 | if config.get(k)} | ||
| 420 | |||
| 341 | interactive = sys.stdin.isatty() and not args.no_input | 421 | interactive = sys.stdin.isatty() and not args.no_input |
| 342 | if args.interactive: | 422 | if args.interactive: |
| 343 | prompt_missing(data, force_all=True) | 423 | prompt_missing(data, force_all=True) |
| @@ -349,7 +429,7 @@ def main(argv: list[str] | None = None) -> int: | |||
| 349 | 429 | ||
| 350 | text = render(data, group=group, site=site, width=width, style=style, | 430 | text = render(data, group=group, site=site, width=width, style=style, |
| 351 | presents=presents, footer=footer, layout=layout, | 431 | presents=presents, footer=footer, layout=layout, |
| 352 | logo=logo, panel_titles=panel_titles) | 432 | logo=logo, roster=roster, panel_titles=panel_titles) |
| 353 | 433 | ||
| 354 | if args.encoding == "cp437": | 434 | if args.encoding == "cp437": |
| 355 | raw = text.encode("cp437", errors="replace") | 435 | raw = text.encode("cp437", errors="replace") |
nfogen.toml +5 −1
| @@ -40,7 +40,11 @@ ENCODING: utf8 or cp437 (--encoding cp437 = DOS) | |||
| 40 | LOGO: --logo art.asc places art above the box | 40 | LOGO: --logo art.asc places art above the box |
| 41 | BANNERS: --presents --footer scene boilerplate | 41 | BANNERS: --presents --footer scene boilerplate |
| 42 | 42 | ||
| 43 | OPTIONS: -o -c -g -s -w --style --layout --logo | 43 | PROFILE: -p group.toml supplies logo, footer, |
| 44 | members, couriers, boards, affiliates, | ||
| 45 | news, outposts. release cfg overrides it. | ||
| 46 | |||
| 47 | OPTIONS: -o -c -p -g -s -w --style --layout --logo | ||
| 44 | --encoding --presents --footer -i --no-input | 48 | --encoding --presents --footer -i --no-input |
| 45 | 49 | ||
| 46 | Generated by nfogen itself. | 50 | Generated by nfogen itself. |