krz/nfogen

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

Commit 83f9f24efa

83f9f24efa383a310ce1dfbdd50d5864764e4a10

parent: 907e55cc50

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-08 05:56 UTC

add two-column info panel, scene field aliases, and logo art (tier 2)

Layout: unified · split

.gitignore added +2
@@ -0,0 +1,2 @@
1__pycache__/
2*.pyc
README.nfo +9 −6
@@ -29,17 +29,20 @@
2929│ default plus group, site, width. Or use -c PATH. │
3030│ │
3131│ FIELDS (all optional but title): │
32│ core: title date type source size files │
33│ url notes greets │
34│ media: format video audio resolution │
35│ language genre runtime │
32│ release: cracker supplier packager protection │
33│ install rating date source │
34│ program: type publisher format disks size files url │
35│ media: video audio resolution language genre runtime │
36│ text: notes greets │
3637│ │
38│ LAYOUT: rows (default) or panel (2-col, --layout) │
3739│ STYLES: single, double, block (--style) │
3840│ ENCODING: utf8 or cp437 (--encoding cp437 = DOS) │
41│ LOGO: --logo art.asc places art above the box │
3942│ BANNERS: --presents --footer scene boilerplate │
4043│ │
41│ OPTIONS: -o -c -g -s -w --style --encoding │
42│ --presents --footer -i --no-input │
44│ OPTIONS: -o -c -g -s -w --style --layout --logo │
45│ --encoding --presents --footer -i --no-input │
4346│ │
4447│ Generated by nfogen itself. │
4548├──────────────────────────────────────────────────────────────┤
examples/krazy-toolkit-v3.cp437.nfo added +43
@@ -0,0 +1,43 @@
1 _ _______ ___
2 | |/ / _ \ \ / / |
3 | ' /| |_) \ V /| | k r a z y w a r e z
4 | . \| _ < | | |_|
5 |_|\_\_| \_\|_| (_) ------- since 1985 -------
6
7ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
8º K R A Z Y . T O O L K I T . V 3 . 2 . 1 - K R Z º
9º [ KRZ ] krz.sh º
10º -={ KRZ proudly presents }=- º
11ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
12º Release Information º Game Information º
13ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
14º DATE : 2026-08-08 º TYPE : APP º
15º CRACKER : Lime º PUBLISHER : Krazy Softworks º
16º SUPPLIER : The Witch King º FORMAT : ZIP / self-extracting º
17º PACKAGER : Renegade Chemist º DISKS : 7 x 1.44 HD º
18º PROTECTION : Serial + Doc Check º SIZE : 9.84 MB º
19º INSTALL : unzip, run SETUP.EXE.. º FILES : 42 x 240 KB º
20º RATING : 10/10 º URL : krz.sh/toolkit º
21ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
22º NOTES º
23º º
24º Here is the release everyone has been waiting for. The º
25º complete Krazy Toolkit, cracked clean -- no nag screens, º
26º no phone-home, no time limit. Tested on a fresh install. º
27º º
28º Install : unzip to a folder of your choice º
29º run SETUP.EXE and follow the prompts º
30º apply the included keygen when asked for a serial º
31º º
32º Greets to everyone who keeps the scene alive. Fix it º
33º yourself if you think you can do better. º
34ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
35º GREETS º
36º º
37º razor-blade jack knightmare stingray the-couriers º
38ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
39º º
40º SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE º
41º if you enjoyed this release, buy it! º
42º º
43ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
examples/krazy-toolkit-v3.nfo added +43
@@ -0,0 +1,43 @@
1 _ _______ ___
2 | |/ / _ \ \ / / |
3 | ' /| |_) \ V /| | k r a z y w a r e z
4 | . \| _ < | | |_|
5 |_|\_\_| \_\|_| (_) ------- since 1985 -------
6
7╔�����������������������������������������������������������������������������╗
8â•‘ K R A Z Y . T O O L K I T . V 3 . 2 . 1 - K R Z â•‘
9â•‘ [ KRZ ] krz.sh â•‘
10â•‘ -={ KRZ proudly presents }=- â•‘
11╠��������������������������������������╦��������������������������������������╣
12â•‘ Release Information â•‘ Game Information â•‘
13╠��������������������������������������╬��������������������������������������╣
14â•‘ DATE : 2026-08-08 â•‘ TYPE : APP â•‘
15â•‘ CRACKER : Lime â•‘ PUBLISHER : Krazy Softworks â•‘
16â•‘ SUPPLIER : The Witch King â•‘ FORMAT : ZIP / self-extracting â•‘
17â•‘ PACKAGER : Renegade Chemist â•‘ DISKS : 7 x 1.44 HD â•‘
18â•‘ PROTECTION : Serial + Doc Check â•‘ SIZE : 9.84 MB â•‘
19â•‘ INSTALL : unzip, run SETUP.EXE.. â•‘ FILES : 42 x 240 KB â•‘
20â•‘ RATING : 10/10 â•‘ URL : krz.sh/toolkit â•‘
21╠��������������������������������������╩��������������������������������������╣
22â•‘ NOTES â•‘
23â•‘ â•‘
24â•‘ Here is the release everyone has been waiting for. The â•‘
25â•‘ complete Krazy Toolkit, cracked clean -- no nag screens, â•‘
26â•‘ no phone-home, no time limit. Tested on a fresh install. â•‘
27â•‘ â•‘
28â•‘ Install : unzip to a folder of your choice â•‘
29â•‘ run SETUP.EXE and follow the prompts â•‘
30â•‘ apply the included keygen when asked for a serial â•‘
31â•‘ â•‘
32â•‘ Greets to everyone who keeps the scene alive. Fix it â•‘
33â•‘ yourself if you think you can do better. â•‘
34╠�����������������������������������������������������������������������������╣
35â•‘ GREETS â•‘
36â•‘ â•‘
37â•‘ razor-blade jack knightmare stingray the-couriers â•‘
38╠�����������������������������������������������������������������������������╣
39â•‘ â•‘
40â•‘ SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE â•‘
41â•‘ if you enjoyed this release, buy it! â•‘
42â•‘ â•‘
43╚������������������������������������������������������������������������������
examples/krazy-toolkit-v3.toml added +43
@@ -0,0 +1,43 @@
1group = "KRZ"
2site = "krz.sh"
3style = "double"
4layout = "panel"
5presents = true
6footer = true
7logo = "examples/krz-logo.asc"
8logo_encoding = "utf8"
9
10title = "Krazy.Toolkit.v3.2.1-KRZ"
11date = "2026-08-08"
12
13# release information (left panel)
14cracker = "Lime"
15supplier = "The Witch King"
16packager = "Renegade Chemist"
17protection = "Serial + Doc Check"
18install = "unzip, run SETUP.EXE, use keygen"
19rating = "10/10"
20
21# game / program information (right panel)
22type = "APP"
23publisher = "Krazy Softworks"
24format = "ZIP / self-extracting"
25disks = "7 x 1.44 HD"
26size = "9.84 MB"
27files = "42 x 240 KB"
28url = "krz.sh/toolkit"
29
30notes = """
31Here is the release everyone has been waiting for. The
32complete Krazy Toolkit, cracked clean -- no nag screens,
33no phone-home, no time limit. Tested on a fresh install.
34
35Install : unzip to a folder of your choice
36 run SETUP.EXE and follow the prompts
37 apply the included keygen when asked for a serial
38
39Greets to everyone who keeps the scene alive. Fix it
40yourself if you think you can do better.
41"""
42
43greets = ["razor-blade", "jack", "knightmare", "stingray", "the-couriers"]
examples/krz-logo.asc added +5
@@ -0,0 +1,5 @@
1 _ _______ ___
2| |/ / _ \ \ / / |
3| ' /| |_) \ V /| | k r a z y w a r e z
4| . \| _ < | | |_|
5|_|\_\_| \_\|_| (_) ------- since 1985 -------
examples/the-midnight-cartographer-2026.cp437.nfo +1 −1
@@ -4,8 +4,8 @@
44º -={ KRZ proudly presents }=- º
55ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
66º DATE 2026-08-08 º
7º TYPE MOVIE º
87º SOURCE BluRay (Retail) º
8º TYPE MOVIE º
99º FORMAT Matroska (MKV) º
1010º VIDEO x264 @ 8500 kbps, 23.976 fps º
1111º AUDIO DTS-HD MA 5.1 @ 1509 kbps (English) º
examples/the-midnight-cartographer-2026.nfo +1 −1
@@ -4,8 +4,8 @@
44â•‘ -={ KRZ proudly presents }=- â•‘
55╠�����������������������������������������������������������������������������╣
66â•‘ DATE 2026-08-08 â•‘
7â•‘ TYPE MOVIE â•‘
87â•‘ SOURCE BluRay (Retail) â•‘
8â•‘ TYPE MOVIE â•‘
99â•‘ FORMAT Matroska (MKV) â•‘
1010â•‘ VIDEO x264 @ 8500 kbps, 23.976 fps â•‘
1111â•‘ AUDIO DTS-HD MA 5.1 @ 1509 kbps (English) â•‘
nfogen.py +130 −49
@@ -13,6 +13,7 @@ import os
1313import sys
1414import textwrap
1515from datetime import date
16from itertools import zip_longest
1617from pathlib import Path
1718
1819# --- field schema ------------------------------------------------------------
@@ -23,27 +24,39 @@ from pathlib import Path
2324# kind : "line" scalar shown as an aligned INFO row
2425# "list" many values, shown space-joined in its own section
2526# "block" free text, wrapped in its own section
27# col : "L"/"R" panel column for "line" fields; None otherwise
2628
2729FIELDS = [
28 ("title", "TITLE", "release title (required)", "line"),
29 ("date", "DATE", "release date", "line"),
30 ("type", "TYPE", "release type (MOVIE, TV, APP, ...)", "line"),
31 ("source", "SOURCE", "source (BluRay, WEB-DL, CD, ...)", "line"),
32 ("format", "FORMAT", "container / format", "line"),
33 ("video", "VIDEO", "video codec / details", "line"),
34 ("audio", "AUDIO", "audio codec / details", "line"),
35 ("resolution", "RESOLUTION", "resolution", "line"),
36 ("language", "LANGUAGE", "language(s)", "line"),
37 ("genre", "GENRE", "genre", "line"),
38 ("runtime", "RUNTIME", "runtime / duration", "line"),
39 ("size", "SIZE", "total size", "line"),
40 ("files", "FILES", "file count / listing", "line"),
41 ("url", "URL", "reference url (imdb, homepage, ...)", "line"),
42 ("notes", "NOTES", "free-text notes / description", "block"),
43 ("greets", "GREETS", "groups to greet (space/comma sep)", "list"),
30 ("title", "TITLE", "release title (required)", "line", None),
31 # --- release information (left panel) ---
32 ("date", "DATE", "release date", "line", "L"),
33 ("source", "SOURCE", "source (BluRay, WEB-DL, CD, ...)", "line", "L"),
34 ("cracker", "CRACKER", "cracker / cracked by", "line", "L"),
35 ("supplier", "SUPPLIER", "supplier / supplied by", "line", "L"),
36 ("packager", "PACKAGER", "packager / packed by", "line", "L"),
37 ("protection", "PROTECTION", "protection type", "line", "L"),
38 ("install", "INSTALL", "install method", "line", "L"),
39 ("rating", "RATING", "rating", "line", "L"),
40 # --- game / media information (right panel) ---
41 ("type", "TYPE", "release type (MOVIE, TV, APP, ...)", "line", "R"),
42 ("publisher", "PUBLISHER", "publisher / vendor", "line", "R"),
43 ("format", "FORMAT", "container / format", "line", "R"),
44 ("disks", "DISKS", "number of disks / archives", "line", "R"),
45 ("video", "VIDEO", "video codec / details", "line", "R"),
46 ("audio", "AUDIO", "audio codec / details", "line", "R"),
47 ("resolution", "RESOLUTION", "resolution", "line", "R"),
48 ("language", "LANGUAGE", "language(s)", "line", "R"),
49 ("genre", "GENRE", "genre", "line", "R"),
50 ("runtime", "RUNTIME", "runtime / duration", "line", "R"),
51 ("size", "SIZE", "total size", "line", "R"),
52 ("files", "FILES", "file count / listing", "line", "R"),
53 ("url", "URL", "reference url (imdb, homepage, ...)", "line", "R"),
54 # --- free-form sections ---
55 ("notes", "NOTES", "free-text notes / description", "block", None),
56 ("greets", "GREETS", "groups to greet (space/comma sep)", "list", None),
4457]
45FIELD_KINDS = {k: kind for k, _, _, kind in FIELDS}
46INFO_KEYS = [k for k, _, _, kind in FIELDS if kind == "line" and k != "title"]
58FIELD_KINDS = {k: kind for k, _, _, kind, _ in FIELDS}
59INFO_KEYS = [k for k, _, _, kind, _ in FIELDS if kind == "line" and k != "title"]
4760
4861DEFAULT_SITE = "krz.sh"
4962DEFAULT_GROUP = "KRZ"
@@ -53,16 +66,20 @@ DEFAULT_FOOTER = (
5366 "SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE\n"
5467 "if you enjoyed this release, buy it!"
5568)
69DEFAULT_LAYOUT = "rows"
70PANEL_TITLES = ("Release Information", "Game Information")
5671
5772# Box-drawing character sets. "single"/"double" are CP437-encodable; "block"
5873# is a solid fill frame. Keys: corners + horizontal/vertical + tee separators.
74# jt/jm/jb are the down-tee, cross, and up-tee used where the panel's
75# vertical column divider meets a horizontal rule.
5976STYLES = {
60 "single": dict(tl="┌", tr="�", bl="└", br="┘",
61 h="─", v="│", ls="├", rs="┤"),
62 "double": dict(tl="╔", tr="╗", bl="╚", br="�",
63 h="�", v="║", ls="╠", rs="╣"),
64 "block": dict(tl="â–ˆ", tr="â–ˆ", bl="â–ˆ", br="â–ˆ",
65 h="â–ˆ", v="â–ˆ", ls="â–ˆ", rs="â–ˆ"),
77 "single": dict(tl="┌", tr="�", bl="└", br="┘", h="─", v="│",
78 ls="├", rs="┤", jt="┬", jm="┼", jb="┴"),
79 "double": dict(tl="╔", tr="╗", bl="╚", br="�", h="�", v="║",
80 ls="╠", rs="╣", jt="╦", jm="╬", jb="╩"),
81 "block": dict(tl="â–ˆ", tr="â–ˆ", bl="â–ˆ", br="â–ˆ", h="â–ˆ", v="â–ˆ",
82 ls="â–ˆ", rs="â–ˆ", jt="â–ˆ", jm="â–ˆ", jb="â–ˆ"),
6683}
6784
6885CONFIG_CANDIDATES = [
@@ -105,7 +122,7 @@ def prompt_missing(data: dict, force_all: bool) -> None:
105122 return
106123 print("nfogen: interactive mode (blank to skip)\n", file=sys.stderr)
107124 for key in keys:
108 _, label, help_text, _ = next(f for f in FIELDS if f[0] == key)
125 _, label, help_text, _, _ = next(f for f in FIELDS if f[0] == key)
109126 current = data.get(key, "")
110127 suffix = f" [{current}]" if current else ""
111128 try:
@@ -125,20 +142,42 @@ def _split_list(value) -> list[str]:
125142
126143def render(data: dict, group: str, site: str, width: int,
127144 style: str = DEFAULT_STYLE, presents: bool = False,
128 footer: str | None = None) -> str:
129 inner = width - 4 # borders + one space of padding on each side
145 footer: str | None = None, layout: str = DEFAULT_LAYOUT,
146 logo: str | None = None,
147 panel_titles: tuple[str, str] = PANEL_TITLES) -> str:
130148 s = STYLES[style]
131 top = s["tl"] + s["h"] * (width - 2) + s["tr"]
132 sep = s["ls"] + s["h"] * (width - 2) + s["rs"]
133 bot = s["bl"] + s["h"] * (width - 2) + s["br"]
149 h, v = s["h"], s["v"]
150 inner = width - 4 # single-column content width (1 space padding)
151 cl = (width - 7) // 2 # left panel content width
152 cr = (width - 7) - cl # right panel content width
153 rfill = width - cl - 5 # h-run right of the divider in a rule
154
155 def hrule(left: str, right: str, joint: str | None = None) -> str:
156 if joint is None:
157 return left + h * (width - 2) + right
158 return left + h * (cl + 2) + joint + h * rfill + right
134159
135160 def line(content: str = "") -> str:
136 return f"{s['v']} " + content.ljust(inner) + f" {s['v']}"
161 return f"{v} " + content.ljust(inner) + f" {v}"
137162
138163 def center(content: str) -> str:
139 return f"{s['v']} " + content.center(inner) + f" {s['v']}"
164 return f"{v} " + content.center(inner) + f" {v}"
165
166 def prow(left: str, right: str, centered: bool = False) -> str:
167 fn = str.center if centered else str.ljust
168 return (f"{v} " + fn(_clip(left, cl), cl)
169 + f" {v} " + fn(_clip(right, cr), cr) + f" {v}")
170
171 out: list[str] = []
172
173 # logo art above the box, block-centered to the full width
174 if logo:
175 art = logo.rstrip("\n").split("\n")
176 pad = max((width - max((len(a) for a in art), default=0)) // 2, 0)
177 out.extend((" " * pad + a).rstrip() for a in art)
178 out.append("")
140179
141 out: list[str] = [top]
180 out.append(hrule(s["tl"], s["tr"]))
142181
143182 # header: spaced title, then group tag + site, optional presents banner
144183 title = str(data.get("title") or "UNTITLED").upper()
@@ -149,24 +188,46 @@ def render(data: dict, group: str, site: str, width: int,
149188 if presents:
150189 out.append(center(_clip(f"-={{ {group.upper()} proudly presents }}=-", inner)))
151190
152 # info rows
153 rows = [(label, str(data[key]))
154 for key, label, _, _ in FIELDS
155 if key in INFO_KEYS and data.get(key)]
156 if rows:
157 out.append(sep)
158 lw = max(len(label) for label, _ in rows)
191 line_fields = [(k, lbl, col) for k, lbl, _, kind, col in FIELDS
192 if kind == "line" and k != "title" and data.get(k)]
193 prev_panel = False
194
195 def sep_before() -> str:
196 nonlocal prev_panel
197 joint = s["jb"] if prev_panel else None
198 prev_panel = False
199 return hrule(s["ls"], s["rs"], joint)
200
201 # info: two-column panel or single-column rows
202 if layout == "panel" and line_fields:
203 left = [(lbl, str(data[k])) for k, lbl, col in line_fields if col == "L"]
204 right = [(lbl, str(data[k])) for k, lbl, col in line_fields if col == "R"]
205 lw_l = max((len(lbl) for lbl, _ in left), default=0)
206 lw_r = max((len(lbl) for lbl, _ in right), default=0)
207
208 def cell(pair, lw):
209 return f" {pair[0]:<{lw}} : {pair[1]}" if pair else ""
210
211 out.append(hrule(s["ls"], s["rs"], s["jt"]))
212 out.append(prow(panel_titles[0], panel_titles[1], centered=True))
213 out.append(hrule(s["ls"], s["rs"], s["jm"]))
214 for lp, rp in zip_longest(left, right):
215 out.append(prow(cell(lp, lw_l), cell(rp, lw_r)))
216 prev_panel = True
217 elif line_fields:
218 out.append(hrule(s["ls"], s["rs"]))
219 lw = max(len(lbl) for _, lbl, _ in line_fields)
159220 avail = inner - (2 + lw + 2)
160 for label, value in rows:
161 wrapped = textwrap.wrap(value, avail) or [""]
162 out.append(line(f" {label:<{lw}} {wrapped[0]}"))
221 for k, lbl, _ in line_fields:
222 wrapped = textwrap.wrap(str(data[k]), avail) or [""]
223 out.append(line(f" {lbl:<{lw}} {wrapped[0]}"))
163224 for cont in wrapped[1:]:
164225 out.append(line(" " * (2 + lw + 2) + cont))
165226
166227 # notes block
167228 notes = str(data.get("notes") or "").strip()
168229 if notes:
169 out.append(sep)
230 out.append(sep_before())
170231 out.append(line(" NOTES"))
171232 out.append(line())
172233 for para in notes.splitlines() or [""]:
@@ -176,7 +237,7 @@ def render(data: dict, group: str, site: str, width: int,
176237 # greets block
177238 greets = _split_list(data.get("greets") or [])
178239 if greets:
179 out.append(sep)
240 out.append(sep_before())
180241 out.append(line(" GREETS"))
181242 out.append(line())
182243 for wrapped in textwrap.wrap(" ".join(greets), inner - 4):
@@ -184,14 +245,14 @@ def render(data: dict, group: str, site: str, width: int,
184245
185246 # footer disclaimer, centered
186247 if footer:
187 out.append(sep)
248 out.append(sep_before())
188249 out.append(line())
189250 for para in footer.splitlines():
190251 for wrapped in (textwrap.wrap(para, inner - 4) or [""]):
191252 out.append(center(wrapped))
192253 out.append(line())
193254
194 out.append(bot)
255 out.append(hrule(s["bl"], s["br"], s["jb"] if prev_panel else None))
195256 return "\n".join(out) + "\n"
196257
197258
@@ -215,6 +276,12 @@ def build_parser() -> argparse.ArgumentParser:
215276 help=f"box width in chars (default {DEFAULT_WIDTH})")
216277 p.add_argument("--style", choices=sorted(STYLES),
217278 help=f"box-drawing style (default {DEFAULT_STYLE})")
279 p.add_argument("--layout", choices=["rows", "panel"],
280 help=f"info layout (default {DEFAULT_LAYOUT})")
281 p.add_argument("--logo", metavar="PATH",
282 help="art file to place above the box")
283 p.add_argument("--logo-encoding", choices=["cp437", "utf8"],
284 help="encoding of the logo file (default cp437)")
218285 p.add_argument("--encoding", choices=["utf8", "cp437"], default="utf8",
219286 help="output encoding (default utf8; cp437 for true DOS nfos)")
220287 p.add_argument("--presents", action=argparse.BooleanOptionalAction,
@@ -226,7 +293,7 @@ def build_parser() -> argparse.ArgumentParser:
226293 help="prompt for every field")
227294 p.add_argument("--no-input", action="store_true",
228295 help="never prompt, even if fields are missing")
229 for key, _, help_text, _ in FIELDS:
296 for key, _, help_text, _, _ in FIELDS:
230297 p.add_argument(f"--{key}", help=help_text)
231298 return p
232299
@@ -258,6 +325,19 @@ def main(argv: list[str] | None = None) -> int:
258325 footer_text = args.footer_text or config.get("footer_text") or DEFAULT_FOOTER
259326 footer = footer_text if show_footer else None
260327
328 layout = args.layout or config.get("layout") or DEFAULT_LAYOUT
329 panel_titles = (config.get("panel_left") or PANEL_TITLES[0],
330 config.get("panel_right") or PANEL_TITLES[1])
331
332 logo = None
333 logo_path = args.logo or config.get("logo")
334 if logo_path:
335 enc = args.logo_encoding or config.get("logo_encoding") or "cp437"
336 try:
337 logo = Path(logo_path).read_text(encoding=enc, errors="replace")
338 except FileNotFoundError:
339 sys.exit(f"nfogen: logo not found: {logo_path}")
340
261341 interactive = sys.stdin.isatty() and not args.no_input
262342 if args.interactive:
263343 prompt_missing(data, force_all=True)
@@ -268,7 +348,8 @@ def main(argv: list[str] | None = None) -> int:
268348 sys.exit("nfogen: a title is required (use --title, a config, or -i)")
269349
270350 text = render(data, group=group, site=site, width=width, style=style,
271 presents=presents, footer=footer)
351 presents=presents, footer=footer, layout=layout,
352 logo=logo, panel_titles=panel_titles)
272353
273354 if args.encoding == "cp437":
274355 raw = text.encode("cp437", errors="replace")
nfogen.toml +9 −6
@@ -28,17 +28,20 @@ CONFIG: a .toml or .json auto-loaded from
2828default plus group, site, width. Or use -c PATH.
2929
3030FIELDS (all optional but title):
31core: title date type source size files
32url notes greets
33media: format video audio resolution
34language genre runtime
31release: cracker supplier packager protection
32 install rating date source
33program: type publisher format disks size files url
34media: video audio resolution language genre runtime
35text: notes greets
3536
37LAYOUT: rows (default) or panel (2-col, --layout)
3638STYLES: single, double, block (--style)
3739ENCODING: utf8 or cp437 (--encoding cp437 = DOS)
40LOGO: --logo art.asc places art above the box
3841BANNERS: --presents --footer scene boilerplate
3942
40OPTIONS: -o -c -g -s -w --style --encoding
41 --presents --footer -i --no-input
43OPTIONS: -o -c -g -s -w --style --layout --logo
44 --encoding --presents --footer -i --no-input
4245
4346Generated by nfogen itself.
4447"""