Coverage for afasi/cli.py: 100.00%
29 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-21 13:35:37 +00:00
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-21 13:35:37 +00:00
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3# pylint: disable=line-too-long
4"""Commandline API gateway for afasi."""
5import sys
7import typer
9import afasi
10import afasi.afasi as af
12APP_NAME = 'Fuzz a language by mixing up only few words.'
13APP_ALIAS = 'afasi'
14app = typer.Typer(
15 add_completion=False,
16 context_settings={'help_option_names': ['-h', '--help']},
17 no_args_is_help=True,
18)
20TEMPLATE_EXAMPLE_JSON = """\
21{
22 "table": {
23 "description": "table level default constraints, row attributes do replace those if present.",
24 "contra": [
25 "extracomment",
26 "source"
27 ],
28 "count": 0,
29 "flip_is_stop": true,
30 "flip_flop": [
31 "<message id=\\"SOME_TRACK\\">",
32 "</message>"
33 ],
34 "pro": [
35 "translation"
36 ]
37 },
38 "foo": "bar",
39 "translations": [
40 {
41 "repl": ">Lock",
42 "ace": ">Launch"
43 },
44 {
45 "repl": ">Track",
46 "ace": ">Lock"
47 },
48 {
49 "repl": ">Autotrack",
50 "ace": ">Autolock"
51 },
52 {
53 "repl": "lock r",
54 "ace": "launch r"
55 },
56 {
57 "repl": "track r",
58 "ace": "lock r"
59 }
60 ]
61}
62"""
64TEMPLATE_EXAMPLE_YAML = """\
65---
66table:
67 description: table level default constraints, row attributes do replace those if
68 present.
69 contra:
70 - extracomment
71 - source
72 count: 0
73 flip_is_stop: true
74 flip_flop:
75 - <message id="SOME_TRACK">
76 - </message>
77 pro:
78 - translation
79foo: bar
80translations:
81- repl: ">Lock"
82 ace: ">Launch"
83- repl: ">Track"
84 ace: ">Lock"
85- repl: ">Autotrack"
86 ace: ">Autolock"
87- repl: lock r
88 ace: launch r
89- repl: track r
90 ace: lock r
91"""
94@app.callback(invoke_without_command=True)
95def callback(
96 version: bool = typer.Option(
97 False,
98 '-V',
99 '--version',
100 help='Display the afasi version and exit',
101 is_eager=True,
102 )
103) -> None:
104 """
105 Fuzz a language by mixing up only few words.
107 The translation table entries are applied in order per line of input.
108 So, with large translation tables the performance will obviously degrade with a power of two.
109 The latter should be taken as a hint to maintain both language files in separate entities not as a patch task.
111 The translation table is either an array of two element arrays provided as YAML or JSON and thus shall
112 be in a shape like either:
114 \b
115 [
116 ["repl", "ace"],
117 ["als", "othis"]
118 ]
120 or:
122 \b
123 ---
124 - - repl
125 - ace
126 - - als
127 - othis
129 Or the table is given as an object providing more detailed instructions constraining the translation rules like:
131 \b
132 * contra indicators - when given exempting a line from translation
133 * pro indicators - when given marking a line for translation
134 * flip_flop indicators - providing either stop-start (default) or start-stop state switching
136 The JSON object format is best understood when executing the template command and adapting the resulting JSON
137 object written to standard out.
139 Default for input source is standard in and out per default is sent to standard out.
140 """
141 if version:
142 typer.echo(f'{APP_NAME} version {afasi.__version__}')
143 raise typer.Exit()
146@app.command('template')
147def app_template(
148 template_format: str = typer.Option(
149 'yaml',
150 '-f',
151 '--format',
152 help='Format of template',
153 metavar='<translation-table>',
154 ),
155) -> int:
156 """
157 Write a template of a translation table YAML or JSON structure to standard out and exit
158 """
159 sys.stdout.write(TEMPLATE_EXAMPLE_JSON if template_format.lower() == 'json' else TEMPLATE_EXAMPLE_YAML)
160 return sys.exit(0)
163@app.command('translate')
164def translate(
165 source: str = typer.Argument(af.STDIN),
166 target: str = typer.Argument(af.STDOUT),
167 inp: str = typer.Option(
168 '',
169 '-i',
170 '--input',
171 help='Path to input file (default is reading from standard in)',
172 metavar='<sourcepath>',
173 ),
174 out: str = typer.Option(
175 '',
176 '-o',
177 '--output',
178 help='Path to non-existing output file (default is writing to standard out)',
179 metavar='<targetpath>',
180 ),
181 translation_table_path: str = typer.Option(
182 '',
183 '-t',
184 '--table',
185 help=(
186 'Path to translation table file in YAML or JSON format.'
187 '\nStructure of table data is [["repl", "ace"], ["als", "othis"]]'
188 ),
189 metavar='<translation table path>',
190 ),
191 dry: bool = typer.Option(
192 False,
193 '-n',
194 '--dryrun',
195 help='Flag to execute without writing the translation but a diff instead (default is False)',
196 metavar='bool',
197 ),
198) -> int:
199 """
200 Translate from a language to a 'langauge'.
201 """
202 command = 'translate'
203 incoming = inp if inp else (source if source != af.STDIN else '')
204 outgoing = out if out else (target if target != af.STDOUT else '')
205 dryrun = 'DRYRUN' if dry else ''
206 action = [command, incoming, outgoing, translation_table_path, dryrun]
207 return sys.exit(af.main(action))
210@app.command('version')
211def app_version() -> None:
212 """
213 Display the afasi version and exit
214 """
215 callback(True)