docs: generalize manpage generation script as json-to-tree.py

This should be capable of replacing every invocation of
nix eval --write-to.

Change-Id: I60387bc9b0fc54a91244eddb639beaa64d705878
This commit is contained in:
Qyriad 2024-04-08 21:43:38 -06:00 committed by eldritch horrors
parent 7e139c52dd
commit 723ddadf92
3 changed files with 85 additions and 80 deletions

View file

@ -1,63 +0,0 @@
#!/usr/bin/env python3
"""
This script is a helper for this project's Meson buildsystem, to generate
manpages as mdbook markdown for nix3 CLI commands. It is an analogue to an
inline sequence of bash commands in the autoconf+Make buildsystem, which works
around a limitation in `nix eval --write-to`, in that it refuses to write to any
directory that already exists.
Basically, this script is a glorified but hopefully-more-robust version of:
$ rm -rf $output
$ nix eval --write-to $output.tmp --expr 'import doc/manual/generate-manpage.nix true
(builtins.readFile ./generate-manpage.nix)'
$ mv $output.tmp $output
"""
import argparse
import os.path
import shlex
import shutil
import subprocess
import sys
import tempfile
name = 'generate-manpage.py'
def log(*args, **kwargs):
kwargs['file'] = sys.stderr
return print(f'{name}:', *args, **kwargs)
def main():
parser = argparse.ArgumentParser(name)
parser.add_argument('--nix', required=True, help='Full path to the nix binary to use')
parser.add_argument('-o', '--output', required=True, help='Output directory')
parser.add_argument('--generator', required=True, help='Path to generate-manpage.nix')
parser.add_argument('--cli-json', required=True, help='Path to the nix.json output from Nix')
args = parser.parse_args()
with tempfile.TemporaryDirectory() as tempdir:
temp_out = os.path.join(tempdir, 'new-cli')
nix_args = [
args.nix,
'--experimental-features',
'nix-command',
'eval',
'-I', 'nix/corepkgs=corepkgs',
'--store', 'dummy://',
'--impure',
'--raw',
'--write-to', temp_out,
'--expr',
f'import {args.generator} true (builtins.readFile {args.cli_json})',
]
log('generating nix3 man pages with', shlex.join(nix_args))
subprocess.check_call(nix_args)
shutil.copytree(temp_out, args.output, dirs_exist_ok=True)
sys.exit(main())

61
doc/manual/json-to-tree.py Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
This script is a helper for this project's Meson buildsystem, to replace its
usage of `nix eval --write-to`. Writing a JSON object as a nested directory
tree is more generic, easier to maintain, and far, far less cursed. Nix
has 'good' support for JSON output. Let's just use it.
"""
import argparse
from pathlib import Path
import json
import sys
name = 'json-to-tree.py'
def log(*args, **kwargs):
kwargs['file'] = sys.stderr
return print(f'{name}:', *args, **kwargs)
def write_dict_to_directory(current_directory: Path, data: dict, files_written=0):
current_directory.mkdir(parents=True, exist_ok=True)
for key, value in data.items():
nested_path = current_directory / key
match value:
case dict(nested_data):
files_written += write_dict_to_directory(nested_path, nested_data)
case str(content):
nested_path.write_text(content)
files_written += 1
case rest:
assert False, \
f'should have been called on a dict or string, not {type(rest)=}\n\t{rest=}'
return files_written
def main():
parser = argparse.ArgumentParser(name)
parser.add_argument('-i', '--input', type=argparse.FileType('r'), default='-',
help='The JSON input to operate on and output as a directory tree',
)
parser.add_argument('-o', '--output', type=Path, required=True,
help='The place to put the directory tree',
)
args = parser.parse_args()
json_string = args.input.read()
try:
data = json.loads(json_string)
except json.JSONDecodeError:
log(f'could not decode JSON from input: {json_string}')
raise
files_written = write_dict_to_directory(args.output, data)
log(f'wrote {files_written} files')
sys.exit(main())

View file

@ -7,13 +7,13 @@ nix_env_for_docs = {
} }
nix_for_docs = [ nix, '--experimental-features', 'nix-command' ] nix_for_docs = [ nix, '--experimental-features', 'nix-command' ]
nix_eval_for_docs = nix_for_docs + [ nix_eval_for_docs_common = nix_for_docs + [
'eval', 'eval',
'-I', 'nix/corepkgs=corepkgs', '-I', 'nix/corepkgs=corepkgs',
'--store', 'dummy://', '--store', 'dummy://',
'--impure', '--impure',
'--raw',
] ]
nix_eval_for_docs = nix_eval_for_docs_common + '--raw'
nix_conf_file_json = custom_target( nix_conf_file_json = custom_target(
command : nix_for_docs + [ 'show-config', '--json' ], command : nix_for_docs + [ 'show-config', '--json' ],
@ -132,27 +132,34 @@ nix3_cli_json = custom_target(
capture : true, capture : true,
output : 'nix.json', output : 'nix.json',
) )
# `nix eval --write-to` is rather picky, and refuses to write to a directory that exists at all.
# This is fine for clean builds because it creates the directory and then is never run again, # Intermediate step for manpage generation.
# but during development if nix.json changes, then using `nix eval --write-to` will error, # This splorks the output of generate-manpage.nix as JSON,
# since that part of the build directory already exists. # which gets written as a directory tree below.
# Thus, another Python script. It runs the relevant `nix eval --write-to` command in a temporary nix3_cli_files_json = custom_target(
# directory, and then copies that tree to our build output directory. command : nix_eval_for_docs_common + [
'--json',
'--expr',
'import @INPUT0@ true (builtins.readFile @INPUT1@)',
],
input : [
'generate-manpage.nix',
nix3_cli_json,
],
capture : true,
output : 'new-cli.json',
env : nix_env_for_docs,
)
nix3_cli_files = custom_target( nix3_cli_files = custom_target(
command : [ command : [
python, python,
'@INPUT0@', '@INPUT0@',
'--nix=@INPUT1@', '-i', '@INPUT1@',
'--generator=@INPUT2@', '-o', '@OUTPUT@',
'--cli-json=@INPUT3@',
'--output=@OUTPUT@',
], ],
input : [ input : [
'generate-manpage.py', # INPUT0 'json-to-tree.py',
nix, # INPUT1 nix3_cli_files_json,
'generate-manpage.nix', # INPUT2
nix3_cli_json, # INPUT3
'utils.nix',
], ],
output : 'new-cli', output : 'new-cli',
) )