Generate a UNIX man page (fixes #874)

This commit is contained in:
Jay Berkenbilt 2023-12-22 21:03:47 -05:00
parent 1f45686843
commit c0c7cef16c
9 changed files with 1198 additions and 20 deletions

View File

@ -4,7 +4,8 @@ cmake_minimum_required(VERSION 3.16)
# make_dist expects the version line to be on a line by itself after
# the project line. When updating the version, check make_dist for all
# the places it has to be updated. The doc configuration and CI build
# also find the version number here.
# also find the version number here. generate_auto_job also reads the
# version from here.
project(qpdf
VERSION 11.7.0
LANGUAGES C CXX)
@ -312,9 +313,12 @@ endif()
set(auto_job_inputs
# Keep in sync with SOURCES in generate_auto_job
generate_auto_job
CMakeLists.txt
manual/_ext/qpdf.py
job.yml
manual/cli.rst)
manual/cli.rst
manual/qpdf.1.in
)
set(auto_job_outputs
# Keep in sync with DESTS in generate_auto_job
@ -323,7 +327,9 @@ set(auto_job_outputs
libqpdf/qpdf/auto_job_help.hh
libqpdf/qpdf/auto_job_schema.hh
libqpdf/qpdf/auto_job_json_decl.hh
libqpdf/qpdf/auto_job_json_init.hh)
libqpdf/qpdf/auto_job_json_init.hh
manual/qpdf.1
)
if(GENERATE_AUTO_JOB)
add_custom_command(

View File

@ -1,5 +1,8 @@
2023-12-22 Jay Berkenbilt <ejb@ql.org>
* Generate a more complete qpdf "man page" from the same source as
qpdf --help. Fixes #874.
* Allow the syntax "--encrypt --user-password=user-password
--owner-password=owner-password --bits={40,128,256}" when
encrypting PDF files. This is an alternative to the syntax

View File

@ -134,6 +134,12 @@ BANNER = f'''//
// clang-format off
//'''
MAN_BANNER = f'''.\\"
.\\" This file is automatically generated by {whoami}.
.\\" Edits will be automatically overwritten if the build is
.\\" run in maintainer mode.
.\\"
'''
def warn(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
@ -156,9 +162,11 @@ class Main:
SOURCES = [
# Keep this list in sync with CMakeLists.txt: auto_job_inputs
whoami,
'CMakeLists.txt',
'manual/_ext/qpdf.py',
'job.yml',
'manual/cli.rst',
'manual/qpdf.1.in',
]
# DESTS is a map to the output files this code generates. These
# generated files, as well as those added to DESTS later in the
@ -172,6 +180,7 @@ class Main:
'schema': 'libqpdf/qpdf/auto_job_schema.hh',
'json_decl': 'libqpdf/qpdf/auto_job_json_decl.hh',
'json_init': 'libqpdf/qpdf/auto_job_json_init.hh',
'man': 'manual/qpdf.1',
# Others are added in top
}
# SUMS contains a checksum for each source and destination and is
@ -277,7 +286,7 @@ class Main:
for k, v in hashes.items():
print(f'{k} {v}', file=f)
def generate_doc(self, df, f):
def generate_doc(self, df, f, f_man):
st_top = 0
st_topic = 1
st_option = 2
@ -324,6 +333,23 @@ class Main:
return True
return False
def manify(text):
lines = text.split('\n')
out = []
last_was_item = False
for line in lines:
if line.startswith('- '):
last_was_item = True
out.append('.IP \\[bu]')
out.append(line[2:])
elif last_was_item and line.startswith(' '):
out.append(line[2:])
else:
last_was_item = False
out.append(line)
return '\n'.join(out)
last_option_topic = ''
lineno = 0
for line in df.readlines():
if help_lines == 0:
@ -366,6 +392,8 @@ class Main:
self.all_topics.add(topic)
print(f'ap.addHelpTopic("{topic}", "{short_text}",'
f' R"({long_text})");', file=f)
print(f'.SH {topic.upper()} ({short_text})', file=f_man)
print(manify(long_text), file=f_man, end='')
help_lines += 1
state = st_top
elif state == st_option:
@ -389,6 +417,11 @@ class Main:
self.jdata[option[2:]]['help'] = short_text
print(f'ap.addOptionHelp("{option}", "{topic}",'
f' "{short_text}", R"({long_text})");', file=f)
if last_option_topic != topic:
print('.PP\nRelated Options:', file=f_man)
last_option_topic = topic
print(f'.TP\n.B {option} \\-\\- {short_text}', file=f_man)
print(manify(long_text), file=f_man, end='')
help_lines += 1
state = st_top
if help_lines == 20:
@ -400,6 +433,11 @@ class Main:
print('ap.addHelpFooter("For detailed help, visit'
' the qpdf manual: https://qpdf.readthedocs.io\\n");', file=f)
print('}\n', file=f)
print('''.SH SEE ALSO
.PP
For a summary of qpdf's options, please run \\fBqpdf \\-\\-help\\fR.
A complete manual can be found at https://qpdf.readthedocs.io.
''', file=f_man, end='')
for i in self.referenced_topics:
if i not in self.all_topics:
raise Exception(f'help text referenced --help={i}')
@ -412,6 +450,14 @@ class Main:
warn(f'{whoami}: regenerating auto job files')
self.validate(data)
version = None
with open('CMakeLists.txt', 'r') as f:
for line in f.readlines():
if line.strip().startswith('VERSION '):
version = line.strip().split(' ')[1]
if version is None:
raise Exception("can't read version from CMakeLists.txt")
# Keep track of which options are help options since they are
# handled specially. Add the built-in help options to tables
# that we populate as we read job.yml since we won't encounter
@ -436,9 +482,15 @@ class Main:
for i in self.init:
print(i, file=f)
with write_file(self.DESTS['help']) as f:
with open('manual/cli.rst', 'r') as df:
print(BANNER, file=f)
self.generate_doc(df, f)
with write_file(self.DESTS['man']) as f_man:
print(MAN_BANNER, file=f_man, end='')
with open('manual/qpdf.1.in', 'r') as m_in:
for line in m_in.readlines():
line = line.replace('@PROJECT_VERSION@', version)
print(line, file=f_man, end='')
with open('manual/cli.rst', 'r') as df:
print(BANNER, file=f)
self.generate_doc(df, f, f_man)
# Compute the json files after the config and arg parsing
# files. We need to have full information about all the

View File

@ -1,5 +1,6 @@
# Generated by generate_auto_job
generate_auto_job bf44181b610d335511a41b6c2b9c3497d0b023a1ca2c8e4537b34cb6262ce173
CMakeLists.txt 66e8f9bf15a0c3394b1b13baaf5a709f7af35a6f733cd173092168e87202c7a5
generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86
include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
@ -8,10 +9,12 @@ include/qpdf/auto_job_c_pages.hh b3cc0f21029f6d89efa043dcdbfa183cb59325b6506001c
include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1
job.yml 4f89fc7b622df897d30d403d8035aa36fc7de8d8c43042c736e0300d904cb05c
libqpdf/qpdf/auto_job_decl.hh 9c6f701c29f3f764d620186bed92685a2edf2e4d11e4f4532862c05470cfc4d2
libqpdf/qpdf/auto_job_help.hh ea1fdca2aa405bdf193732c5a2789c602efe2add3aa6e2dceecfacee175ce65c
libqpdf/qpdf/auto_job_help.hh bbd37ac0e8b3e38892a328ca08829d6e71c31ea3ab6c1a91b5f6983018695ef9
libqpdf/qpdf/auto_job_init.hh b4c2b3724fba61f1206fd3bae81951636852592f67a63ef9539839c2c5995065
libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a9474319fb7c86d92634cc8297
libqpdf/qpdf/auto_job_json_init.hh f5acb9aa103131cb68dec0e12c4d237a6459bdb49b24773c24f0c2724a462b8f
libqpdf/qpdf/auto_job_schema.hh b53c006fec2e75b1b73588d242d49a32f7d3db820b1541de106c5d4c27fbb4d9
manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
manual/cli.rst 28cc6b36b26377404022bab467e6a16085023fdfa5d9d419595ffcae6c69d531
manual/cli.rst 7bbeb2f234ca3d095c069f52e4a3c5e42a525b5ef6231955d036a6313eaffcd2
manual/qpdf.1 745cb32c1772e6d84ef962aca7a439ee045226ae547330778a4a3ba3cd8d25df
manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b

View File

@ -37,10 +37,10 @@ description of the JSON input file format.
)");
ap.addHelpTopic("exit-status", "meanings of qpdf's exit codes", R"(Meaning of exit codes:
0: no errors or warnings
1: not used by qpdf but may be used by the shell if unable to invoke qpdf
2: errors detected
3: warnings detected, unless --warning-exit-0 is given
- 0: no errors or warnings
- 1: not used by qpdf but may be used by the shell if unable to invoke qpdf
- 2: errors detected
- 3: warnings detected, unless --warning-exit-0 is given
)");
ap.addOptionHelp("--warning-exit-0", "exit-status", "exit 0 even with warnings", R"(Use exit status 0 instead of 3 when warnings are present. When
combined with --no-warn, warnings are completely ignored.

View File

@ -22,7 +22,7 @@ if(BUILD_DOC)
endif()
set(MANUAL_SRC ${qpdf_SOURCE_DIR}/manual)
foreach(F qpdf.1 fix-qdf.1 zlib-flate.1)
foreach(F fix-qdf.1 zlib-flate.1)
configure_file(
${MANUAL_SRC}/${F}.in
${CMAKE_CURRENT_BINARY_DIR}/${F}
@ -129,7 +129,7 @@ if(NOT WIN32)
# environment, especially when all they do is refer people to the
# manual.
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/qpdf.1
${qpdf_SOURCE_DIR}/manual/qpdf.1
${CMAKE_CURRENT_BINARY_DIR}/fix-qdf.1
${CMAKE_CURRENT_BINARY_DIR}/zlib-flate.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1

View File

@ -184,10 +184,10 @@ Exit Status
Meaning of exit codes:
0: no errors or warnings
1: not used by qpdf but may be used by the shell if unable to invoke qpdf
2: errors detected
3: warnings detected, unless --warning-exit-0 is given
- 0: no errors or warnings
- 1: not used by qpdf but may be used by the shell if unable to invoke qpdf
- 2: errors detected
- 3: warnings detected, unless --warning-exit-0 is given
The exit status of :command:`qpdf` may be interpreted as follows:

1109
manual/qpdf.1 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -80,6 +80,11 @@ Planned changes for future 12.x (subject to change):
``README-maintainer.md`` for a detailed explanation of how to
maintain this.
- Package Enhancements:
- A UNIX man page is now automatically generated from the
documentation. It contains the same text as ``qpdf --help=all``.
- Library Enhancements:
- Add C++ functions ``qpdf_c_wrap`` and ``qpdf_c_get_qpdf`` to the