krz/nfogen

A scene-style ASCII .nfo generator. ascii cli generator nfo

Commit cc082e51ff

cc082e51fff8fccc1d9d8f9285a8439edc806733

parent: 83f9f24efa

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-08 06:01 UTC

add group profiles with roster/couriers/boards sections (tier 3)

Layout: unified · split

README.nfo +5 −1
@@ -41,7 +41,11 @@
4141│ LOGO: --logo art.asc places art above the box │
4242│ BANNERS: --presents --footer scene boilerplate │
4343│ │
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 │
4549│ --encoding --presents --footer -i --no-input │
4650│ │
4751│ 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
6group = "KRZ"
7site = "krz.sh"
8style = "double"
9layout = "panel"
10presents = true
11footer = true
12logo = "examples/krz-logo.asc"
13logo_encoding = "utf8"
14
15footer_text = """
16SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE
17if you enjoyed this release, buy it!
18KRZ does not accept cash or hardware of any kind.
19"""
20
21news = """
22Welcome Celestial Wizard back to the group!
23We are in need of couriers for the release rush -- especially
24those available in the daytime. Contact a senior member.
25"""
26
27members = [
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]
35Director = ["The Unforgiven"]
36Senior = ["Ur Lord", "Lord Tiamat", "Wind Walker", "Bubba"]
37Elite = ["Shadow Phantom", "Baddog", "Load Master", "Joebee", "Azmodeus"]
38Trial = ["Dead Pool", "Beach Bum", "Black Mage", "Ben Jammin"]
39
40[[boards]]
41name = "Suburbia"
42role = "World HQ"
43sysop = "The Chairman"
44phone = "ITS/PRIVATE"
45
46[[boards]]
47name = "TerrorDome"
48role = "Canadian HQ"
49sysop = "StingRay"
50phone = "ITS/PRIVATE"
51
52[[boards]]
53name = "Kindergarden"
54role = "European HQ"
55sysop = "Doctor No"
56phone = "+47-7-525156"
57
58[[boards]]
59name = "Apocalypse"
60role = "Courier HQ"
61sysop = "P.O.W."
62phone = "ITS/PRIVATE"
63
64[[affiliates]]
65name = "Reign In Blood"
66role = "Affiliate"
67sysop = "Uriel"
68phone = "718/698-1969"
69
70[[affiliates]]
71name = "So-Krate's"
72role = "Affiliate"
73sysop = "Philosopher"
74phone = "310/578-7226"
75
76[[affiliates]]
77name = "The Space Zone"
78role = "Affiliate"
79sysop = "Death Ray"
80phone = "301/416-0118"
81
82outposts = ["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.
3profile = "examples/krz.profile.toml"
4
5title = "Nightfall.Protocol.v1.4-KRZ"
6date = "2026-08-08"
7
8cracker = "Lime"
9supplier = "The Witch King"
10packager = "Renegade Chemist"
11protection = "SafeDisc + Serial"
12install = "unzip, run SETUP, apply keygen"
13rating = "9/10"
14
15type = "GAME"
16publisher = "Nocturne Interactive"
17format = "ISO + patch"
18disks = "3 x CD"
19size = "1.87 GB"
20files = "96 x 20 MB"
21url = "krz.sh/nightfall"
22
23notes = """
24A stealth-action thriller set in a city that never sleeps.
25Cracked clean -- no disc check, no online activation.
26
27Mount or burn the ISO, install, then run the KRZ patch over
28the game directory. Serial is in the keygen. Tested working.
29"""
30
31greets = ["razor-blade", "jack", "the-couriers", "everyone-in-the-scene"]
nfogen.py +83 −3
@@ -140,10 +140,18 @@ def _split_list(value) -> list[str]:
140140 return [p for p in str(value).replace(",", " ").split() if p]
141141
142142
143def _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
143151def render(data: dict, group: str, site: str, width: int,
144152 style: str = DEFAULT_STYLE, presents: bool = False,
145153 footer: str | None = None, layout: str = DEFAULT_LAYOUT,
146 logo: str | None = None,
154 logo: str | None = None, roster: dict | None = None,
147155 panel_titles: tuple[str, str] = PANEL_TITLES) -> str:
148156 s = STYLES[style]
149157 h, v = s["h"], s["v"]
@@ -243,6 +251,66 @@ def render(data: dict, group: str, site: str, width: int,
243251 for wrapped in textwrap.wrap(" ".join(greets), inner - 4):
244252 out.append(line(" " + wrapped))
245253
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
246314 # footer disclaimer, centered
247315 if footer:
248316 out.append(sep_before())
@@ -268,6 +336,8 @@ def build_parser() -> argparse.ArgumentParser:
268336 )
269337 p.add_argument("-c", "--config", metavar="PATH",
270338 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")
271341 p.add_argument("-o", "--output", metavar="PATH",
272342 help="write to PATH instead of stdout")
273343 p.add_argument("-g", "--group", help=f"release group (default {DEFAULT_GROUP})")
@@ -300,7 +370,13 @@ def build_parser() -> argparse.ArgumentParser:
300370
301371def main(argv: list[str] | None = None) -> int:
302372 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}
304380
305381 # merge: config defaults, then any CLI flag that was supplied
306382 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:
338414 except FileNotFoundError:
339415 sys.exit(f"nfogen: logo not found: {logo_path}")
340416
417 roster = {k: config[k] for k in
418 ("news", "members", "couriers", "boards", "affiliates", "outposts")
419 if config.get(k)}
420
341421 interactive = sys.stdin.isatty() and not args.no_input
342422 if args.interactive:
343423 prompt_missing(data, force_all=True)
@@ -349,7 +429,7 @@ def main(argv: list[str] | None = None) -> int:
349429
350430 text = render(data, group=group, site=site, width=width, style=style,
351431 presents=presents, footer=footer, layout=layout,
352 logo=logo, panel_titles=panel_titles)
432 logo=logo, roster=roster, panel_titles=panel_titles)
353433
354434 if args.encoding == "cp437":
355435 raw = text.encode("cp437", errors="replace")
nfogen.toml +5 −1
@@ -40,7 +40,11 @@ ENCODING: utf8 or cp437 (--encoding cp437 = DOS)
4040LOGO: --logo art.asc places art above the box
4141BANNERS: --presents --footer scene boilerplate
4242
43OPTIONS: -o -c -g -s -w --style --layout --logo
43PROFILE: -p group.toml supplies logo, footer,
44 members, couriers, boards, affiliates,
45 news, outposts. release cfg overrides it.
46
47OPTIONS: -o -c -p -g -s -w --style --layout --logo
4448 --encoding --presents --footer -i --no-input
4549
4650Generated by nfogen itself.