|
1
|
|
|
""" |
|
2
|
|
|
OCR-D CLI: ocrd-tool.json management |
|
3
|
|
|
|
|
4
|
|
|
.. click:: ocrd.cli.ocrd_tool:ocrd_tool_cli |
|
5
|
|
|
:prog: ocrd ocrd-tool |
|
6
|
|
|
:nested: full |
|
7
|
|
|
|
|
8
|
|
|
""" |
|
9
|
|
|
from inspect import getmodule |
|
10
|
|
|
from json import dumps |
|
11
|
|
|
import codecs |
|
12
|
|
|
import sys |
|
13
|
|
|
import os |
|
14
|
|
|
import click |
|
15
|
|
|
|
|
16
|
|
|
from ocrd.decorators import parameter_option, parameter_override_option |
|
17
|
|
|
from ocrd.processor import Processor |
|
18
|
|
|
from ocrd_utils import ( |
|
19
|
|
|
set_json_key_value_overrides, |
|
20
|
|
|
parse_json_string_or_file, |
|
21
|
|
|
parse_json_string_with_comments as loads |
|
22
|
|
|
) |
|
23
|
|
|
from ocrd_validators import ParameterValidator, OcrdToolValidator |
|
24
|
|
|
|
|
25
|
|
|
class OcrdToolCtx(): |
|
26
|
|
|
|
|
27
|
|
|
def __init__(self, filename): |
|
28
|
|
|
self.filename = filename |
|
29
|
|
|
with codecs.open(filename, encoding='utf-8') as f: |
|
30
|
|
|
self.content = f.read() |
|
31
|
|
|
# perhaps the validator should _always_ run (for default expansion) |
|
32
|
|
|
# so validate command only for the report? |
|
33
|
|
|
self.json = loads(self.content) |
|
34
|
|
|
self.tool_name = '' |
|
35
|
|
|
|
|
36
|
|
|
class BashProcessor(Processor): |
|
37
|
|
|
@property |
|
38
|
|
|
def metadata(inner_self): # pylint: disable=no-self-argument,arguments-renamed |
|
39
|
|
|
return self.json |
|
40
|
|
|
@property |
|
41
|
|
|
def executable(inner_self): # pylint: disable=no-self-argument,arguments-renamed |
|
42
|
|
|
return self.tool_name |
|
43
|
|
|
@property |
|
44
|
|
|
def moduledir(inner_self): # pylint: disable=no-self-argument,arguments-renamed |
|
45
|
|
|
return os.path.dirname(self.filename) |
|
46
|
|
|
# set docstrings to empty |
|
47
|
|
|
__doc__ = None |
|
48
|
|
|
# HACK: override the module-level docstring, too |
|
49
|
|
|
getmodule(OcrdToolCtx).__doc__ = None |
|
50
|
|
|
def process(inner_self): # pylint: disable=no-self-argument,arguments-renamed |
|
51
|
|
|
return super() |
|
52
|
|
|
|
|
53
|
|
|
self.processor = BashProcessor |
|
54
|
|
|
|
|
55
|
|
|
pass_ocrd_tool = click.make_pass_decorator(OcrdToolCtx) |
|
56
|
|
|
|
|
57
|
|
|
# ---------------------------------------------------------------------- |
|
58
|
|
|
# ocrd ocrd-tool |
|
59
|
|
|
# ---------------------------------------------------------------------- |
|
60
|
|
|
|
|
61
|
|
|
@click.group('ocrd-tool', help='Work with ocrd-tool.json JSON_FILE') |
|
62
|
|
|
@click.argument('json_file') |
|
63
|
|
|
@click.pass_context |
|
64
|
|
|
def ocrd_tool_cli(ctx, json_file): |
|
65
|
|
|
ctx.obj = OcrdToolCtx(json_file) |
|
66
|
|
|
|
|
67
|
|
|
# ---------------------------------------------------------------------- |
|
68
|
|
|
# ocrd ocrd-tool version |
|
69
|
|
|
# ---------------------------------------------------------------------- |
|
70
|
|
|
|
|
71
|
|
|
@ocrd_tool_cli.command('version', help='Version of ocrd-tool.json') |
|
72
|
|
|
@pass_ocrd_tool |
|
73
|
|
|
def ocrd_tool_version(ctx): |
|
74
|
|
|
print(ctx.json['version']) |
|
75
|
|
|
|
|
76
|
|
|
# ---------------------------------------------------------------------- |
|
77
|
|
|
# ocrd ocrd-tool validate |
|
78
|
|
|
# ---------------------------------------------------------------------- |
|
79
|
|
|
|
|
80
|
|
|
@ocrd_tool_cli.command('validate', help='Validate an ocrd-tool.json') |
|
81
|
|
|
@pass_ocrd_tool |
|
82
|
|
|
def ocrd_tool_validate(ctx): |
|
83
|
|
|
report = OcrdToolValidator.validate(ctx.json) |
|
84
|
|
|
print(report.to_xml()) |
|
85
|
|
|
if not report.is_valid: |
|
86
|
|
|
return 128 |
|
87
|
|
|
|
|
88
|
|
|
# ---------------------------------------------------------------------- |
|
89
|
|
|
# ocrd ocrd-tool list-tools |
|
90
|
|
|
# ---------------------------------------------------------------------- |
|
91
|
|
|
|
|
92
|
|
|
@ocrd_tool_cli.command('list-tools', help="List tools") |
|
93
|
|
|
@pass_ocrd_tool |
|
94
|
|
|
def ocrd_tool_list(ctx): |
|
95
|
|
|
for tool in ctx.json['tools']: |
|
96
|
|
|
print(tool) |
|
97
|
|
|
|
|
98
|
|
|
# ---------------------------------------------------------------------- |
|
99
|
|
|
# ocrd ocrd-tool dump-tools |
|
100
|
|
|
# ---------------------------------------------------------------------- |
|
101
|
|
|
|
|
102
|
|
|
@ocrd_tool_cli.command('dump-tools', help="Dump tools") |
|
103
|
|
|
@pass_ocrd_tool |
|
104
|
|
|
def ocrd_tool_dump(ctx): |
|
105
|
|
|
print(dumps(ctx.json['tools'], indent=True)) |
|
106
|
|
|
|
|
107
|
|
|
# ---------------------------------------------------------------------- |
|
108
|
|
|
# ocrd ocrd-tool tool |
|
109
|
|
|
# ---------------------------------------------------------------------- |
|
110
|
|
|
|
|
111
|
|
|
@ocrd_tool_cli.group('tool', help='Work with a single tool TOOL_NAME') |
|
112
|
|
|
@click.argument('tool_name') |
|
113
|
|
|
@pass_ocrd_tool |
|
114
|
|
|
def ocrd_tool_tool(ctx, tool_name): |
|
115
|
|
|
if tool_name not in ctx.json['tools']: |
|
116
|
|
|
raise Exception("No such tool: %s" % tool_name) |
|
117
|
|
|
ctx.tool_name = tool_name |
|
118
|
|
|
|
|
119
|
|
|
# ---------------------------------------------------------------------- |
|
120
|
|
|
# ocrd ocrd-tool tool description |
|
121
|
|
|
# ---------------------------------------------------------------------- |
|
122
|
|
|
|
|
123
|
|
|
@ocrd_tool_tool.command('description', help="Describe tool") |
|
124
|
|
|
@pass_ocrd_tool |
|
125
|
|
|
def ocrd_tool_tool_description(ctx): |
|
126
|
|
|
print(ctx.json['tools'][ctx.tool_name]['description']) |
|
127
|
|
|
|
|
128
|
|
|
@ocrd_tool_tool.command('list-resources', help="List tool's file resources") |
|
129
|
|
|
@pass_ocrd_tool |
|
130
|
|
|
def ocrd_tool_tool_list_resources(ctx): |
|
131
|
|
|
ctx.processor(None).list_resources() |
|
132
|
|
|
|
|
133
|
|
|
@ocrd_tool_tool.command('resolve-resource', help="Get a tool's file resource full path name") |
|
134
|
|
|
@click.argument('res_name') |
|
135
|
|
|
@pass_ocrd_tool |
|
136
|
|
|
def ocrd_tool_tool_resolve_resource(ctx, res_name): |
|
137
|
|
|
print(ctx.processor(None).resolve_resource(res_name)) |
|
138
|
|
|
|
|
139
|
|
|
@ocrd_tool_tool.command('show-resource', help="Dump a tool's file resource") |
|
140
|
|
|
@click.argument('res_name') |
|
141
|
|
|
@pass_ocrd_tool |
|
142
|
|
|
def ocrd_tool_tool_show_resource(ctx, res_name): |
|
143
|
|
|
ctx.processor(None).show_resource(res_name) |
|
144
|
|
|
|
|
145
|
|
|
@ocrd_tool_tool.command('help', help="Generate help for processors") |
|
146
|
|
|
@click.argument('subcommand', required=False) |
|
147
|
|
|
@pass_ocrd_tool |
|
148
|
|
|
def ocrd_tool_tool_params_help(ctx, subcommand): |
|
149
|
|
|
ctx.processor(None).show_help(subcommand=subcommand) |
|
150
|
|
|
|
|
151
|
|
|
# ---------------------------------------------------------------------- |
|
152
|
|
|
# ocrd ocrd-tool tool categories |
|
153
|
|
|
# ---------------------------------------------------------------------- |
|
154
|
|
|
|
|
155
|
|
|
@ocrd_tool_tool.command('categories', help="Categories of tool") |
|
156
|
|
|
@pass_ocrd_tool |
|
157
|
|
|
def ocrd_tool_tool_categories(ctx): |
|
158
|
|
|
print('\n'.join(ctx.json['tools'][ctx.tool_name]['categories'])) |
|
159
|
|
|
|
|
160
|
|
|
# ---------------------------------------------------------------------- |
|
161
|
|
|
# ocrd ocrd-tool tool steps |
|
162
|
|
|
# ---------------------------------------------------------------------- |
|
163
|
|
|
|
|
164
|
|
|
@ocrd_tool_tool.command('steps', help="Steps of tool") |
|
165
|
|
|
@pass_ocrd_tool |
|
166
|
|
|
def ocrd_tool_tool_steps(ctx): |
|
167
|
|
|
print('\n'.join(ctx.json['tools'][ctx.tool_name]['steps'])) |
|
168
|
|
|
|
|
169
|
|
|
# ---------------------------------------------------------------------- |
|
170
|
|
|
# ocrd ocrd-tool tool dump |
|
171
|
|
|
# ---------------------------------------------------------------------- |
|
172
|
|
|
|
|
173
|
|
|
@ocrd_tool_tool.command('dump', help="Dump JSON of tool") |
|
174
|
|
|
@pass_ocrd_tool |
|
175
|
|
|
def ocrd_tool_tool_dump(ctx): |
|
176
|
|
|
print(dumps(ctx.json['tools'][ctx.tool_name], indent=True)) |
|
177
|
|
|
|
|
178
|
|
|
# ---------------------------------------------------------------------- |
|
179
|
|
|
# ocrd ocrd-tool tool parse-params |
|
180
|
|
|
# ---------------------------------------------------------------------- |
|
181
|
|
|
|
|
182
|
|
|
@ocrd_tool_tool.command('parse-params') |
|
183
|
|
|
@parameter_option |
|
184
|
|
|
@parameter_override_option |
|
185
|
|
|
@click.option('-j', '--json', help='Output JSON instead of shell variables', is_flag=True, default=False) |
|
186
|
|
|
@pass_ocrd_tool |
|
187
|
|
|
def ocrd_tool_tool_parse_params(ctx, parameter, parameter_override, json): |
|
188
|
|
|
""" |
|
189
|
|
|
Parse parameters with fallback to defaults and output as shell-eval'able assignments to params var. |
|
190
|
|
|
""" |
|
191
|
|
|
parameter = set_json_key_value_overrides(parse_json_string_or_file(*parameter), *parameter_override) |
|
192
|
|
|
parameterValidator = ParameterValidator(ctx.json['tools'][ctx.tool_name]) |
|
193
|
|
|
report = parameterValidator.validate(parameter) |
|
194
|
|
|
if not report.is_valid: |
|
195
|
|
|
print(report.to_xml()) |
|
196
|
|
|
sys.exit(1) |
|
197
|
|
|
if json: |
|
198
|
|
|
print(dumps(parameter)) |
|
199
|
|
|
else: |
|
200
|
|
|
for k in parameter: |
|
201
|
|
|
print('params["%s"]="%s"' % (k, parameter[k])) |
|
202
|
|
|
|