forked from manubot/manubot
-
Notifications
You must be signed in to change notification settings - Fork 1
/
command.py
231 lines (213 loc) · 8.52 KB
/
command.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""
Manubot's command line interface
"""
import argparse
import logging
import pathlib
import sys
import warnings
import manubot
from manubot.util import import_function
def parse_arguments():
"""
Read and process command line arguments.
"""
parser = argparse.ArgumentParser(
description="Manubot: the manuscript bot for scholarly writing"
)
parser.add_argument(
"--version", action="version", version=f"v{manubot.__version__}"
)
subparsers = parser.add_subparsers(
title="subcommands", description="All operations are done through subcommands:"
)
# Require specifying a sub-command
subparsers.required = True # https://bugs.python.org/issue26510
subparsers.dest = "subcommand" # https://bugs.python.org/msg186387
add_subparser_process(subparsers)
add_subparser_cite(subparsers)
add_subparser_webpage(subparsers)
for subparser in subparsers.choices.values():
subparser.add_argument(
"--log-level",
default="WARNING",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set the logging level for stderr logging",
)
args = parser.parse_args()
return args
def add_subparser_process(subparsers):
parser = subparsers.add_parser(
name="process",
help="process manuscript content",
description="Process manuscript content to create outputs for Pandoc consumption. "
"Performs bibliographic processing and templating.",
)
parser.add_argument(
"--content-directory",
type=pathlib.Path,
required=True,
help="Directory where manuscript content files are located.",
)
parser.add_argument(
"--output-directory",
type=pathlib.Path,
required=True,
help="Directory to output files generated by this script.",
)
parser.add_argument(
"--template-variables-path",
action="append",
default=[],
help="Path or URL of a file containing template variables for jinja2. "
"Serialization format is inferred from the file extension, with support for JSON, YAML, and TOML. "
"If the format cannot be detected, the parser assumes JSON. "
"Specify this argument multiple times to read multiple files. "
"Variables can be applied to a namespace (i.e. stored under a dictionary key) "
"like `--template-variables-path=namespace=path_or_url`. "
"Namespaces must match the regex `[a-zA-Z_][a-zA-Z0-9_]*`.",
)
parser.add_argument(
"--skip-citations",
action="store_true",
required=True,
help="Skip citation and reference processing. "
"Support for citation and reference processing has been moved from `manubot process` to the pandoc-manubot-cite filter. "
"Therefore this argument is now required. "
"If citation-tags.tsv is found in content, "
"these tags will be inserted in the markdown output using the reference-link syntax for citekey aliases. "
"Appends content/manual-references*.* paths to Pandoc's metadata.bibliography field.",
)
parser.add_argument(
"--cache-directory",
type=pathlib.Path,
help="Custom cache directory. If not specified, caches to output-directory.",
)
parser.add_argument("--clear-requests-cache", action="store_true")
parser.set_defaults(function="manubot.process.process_command.cli_process")
def add_subparser_cite(subparsers):
parser = subparsers.add_parser(
name="cite",
help="citation to CSL command line utility",
description="Retrieve bibliographic metadata for one or more citation keys.",
)
parser.add_argument(
"--render",
action="store_true",
help="Whether to render CSL Data into a formatted reference list using Pandoc. "
"Pandoc version 2.0 or higher is required for complete support of available output formats.",
)
parser.add_argument(
"--csl",
default="https://github.com/greenelab/manubot-rootstock/raw/master/build/assets/style.csl",
help="When --render, specify an XML CSL definition to style references (i.e. Pandoc's --csl option). "
"Defaults to Manubot's style.",
)
parser.add_argument(
"--format",
choices=["plain", "markdown", "docx", "html", "jats"],
help="When --render, format to use for output file. "
"If not specified, attempt to infer this from filename extension. "
"Otherwise, default to plain.",
)
parser.add_argument(
"--output",
type=pathlib.Path,
help="Specify a file to write output, otherwise default to stdout.",
)
parser.add_argument(
"--allow-invalid-csl-data",
dest="prune_csl",
action="store_false",
help="Allow CSL Items that do not conform to the JSON Schema. Skips CSL pruning.",
)
parser.add_argument(
"citekeys",
nargs="+",
help="One or more (space separated) citation keys to produce CSL for.",
)
parser.set_defaults(function="manubot.cite.cite_command.cli_cite")
def add_subparser_webpage(subparsers):
parser = subparsers.add_parser(
name="webpage",
help="deploy Manubot outputs to a webpage directory tree",
description="Update the webpage directory tree with Manubot output files. "
"This command should be run from the root directory of a Manubot manuscript that follows the Rootstock layout, containing `output` and `webpage` directories. "
"HTML and PDF outputs are copied to the webpage directory, which is structured as static source files for website hosting.",
)
parser.add_argument(
"--checkout",
nargs="?",
const="gh-pages",
default=None,
help="branch to checkout /v directory contents from. "
"For example, --checkout=upstream/gh-pages. "
"--checkout is equivalent to --checkout=gh-pages. "
"If --checkout is ommitted, no checkout is performed.",
)
parser.add_argument(
"--version",
help="Used to create webpage/v/{version} directory. "
"Generally a commit hash, tag, or 'local'. "
"When omitted, version defaults to the commit hash on CI builds and 'local' elsewhere.",
)
parser.add_argument(
"--timestamp",
action="store_true",
help="timestamp versioned manuscripts in webpage/v using OpenTimestamps. "
"Specify this flag to create timestamps for the current HTML and PDF outputs and upgrade any timestamps from past manuscript versions.",
)
cache_group = parser.add_mutually_exclusive_group()
cache_group.add_argument(
"--no-ots-cache", action="store_true", help="disable the timestamp cache."
)
cache_group.add_argument(
"--ots-cache",
default=pathlib.Path("ci/cache/ots"),
type=pathlib.Path,
help="location for the timestamp cache (default: ci/cache/ots).",
)
parser.set_defaults(function="manubot.webpage.webpage_command.cli_webpage")
def setup_logging_and_errors() -> dict:
"""
Configure warnings and logging.
Set up an ErrorHandler to detect whether messages have been logged
at or above the ERROR level.
"""
import errorhandler
# Track if message gets logged with severity of error or greater
# See https://stackoverflow.com/a/45446664/4651668
error_handler = errorhandler.ErrorHandler()
# Log DeprecationWarnings
warnings.simplefilter("always", DeprecationWarning)
logging.captureWarnings(True)
# Log to stderr
logger = logging.getLogger()
stream_handler = logging.StreamHandler(stream=sys.stderr)
stream_handler.setFormatter(
logging.Formatter("## {levelname}\n{message}", style="{")
)
logger.addHandler(stream_handler)
return {
"logger": logger,
"error_handler": error_handler,
}
def exit_if_error_handler_fired(error_handler):
"""
If a message has been logged with severity of ERROR or greater,
exit Python with a nonzero code.
"""
if error_handler.fired:
logging.critical("Failure: exiting with code 1 due to logged errors")
raise SystemExit(1)
def main():
"""
Called as a console_scripts entry point in setup.py. This function defines
the manubot command line script.
"""
diagnostics = setup_logging_and_errors()
args = parse_arguments()
diagnostics["logger"].setLevel(getattr(logging, args.log_level))
function = import_function(args.function)
function(args)
exit_if_error_handler_fired(diagnostics["error_handler"])