fireaid 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fireaid/__init__.py +400 -0
- fireaid/__main__.py +28 -0
- fireaid-0.1.0.dist-info/METADATA +164 -0
- fireaid-0.1.0.dist-info/RECORD +7 -0
- fireaid-0.1.0.dist-info/WHEEL +5 -0
- fireaid-0.1.0.dist-info/licenses/LICENSE +21 -0
- fireaid-0.1.0.dist-info/top_level.txt +1 -0
fireaid/__init__.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fireaid
|
|
3
|
+
=======
|
|
4
|
+
|
|
5
|
+
A transparent wrapper around Google Python Fire that rounds off its
|
|
6
|
+
command-line help syntax.
|
|
7
|
+
|
|
8
|
+
Instead of:
|
|
9
|
+
|
|
10
|
+
import fire
|
|
11
|
+
|
|
12
|
+
use:
|
|
13
|
+
|
|
14
|
+
import fireaid as fire
|
|
15
|
+
|
|
16
|
+
Existing code can continue to use:
|
|
17
|
+
|
|
18
|
+
fire.Fire(...)
|
|
19
|
+
|
|
20
|
+
and attributes exposed by the original ``fire`` package remain available.
|
|
21
|
+
|
|
22
|
+
Fire itself accepts:
|
|
23
|
+
|
|
24
|
+
tool foo -- --help
|
|
25
|
+
tool foo --help
|
|
26
|
+
tool foo -h
|
|
27
|
+
|
|
28
|
+
fireaid additionally accepts Git-style help:
|
|
29
|
+
|
|
30
|
+
tool help
|
|
31
|
+
tool help foo
|
|
32
|
+
tool help foo bar
|
|
33
|
+
|
|
34
|
+
and makes ``--help`` and ``-h`` print the help text to stdout, without
|
|
35
|
+
Fire's INFO banner or pager, including for functions that take ``**kwargs``.
|
|
36
|
+
|
|
37
|
+
An incomplete command, such as plain ``tool``, prints Fire's usage text
|
|
38
|
+
and exits with an error, where Fire shows the full help as a success.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import inspect
|
|
44
|
+
import shlex
|
|
45
|
+
import sys
|
|
46
|
+
from collections.abc import Sequence
|
|
47
|
+
from importlib import metadata as _metadata
|
|
48
|
+
from typing import Any
|
|
49
|
+
|
|
50
|
+
import fire as _fire
|
|
51
|
+
|
|
52
|
+
__all__ = ["Fire"]
|
|
53
|
+
|
|
54
|
+
_HELP = ["--", "--help"]
|
|
55
|
+
_FLAGS = ("-h", "--help")
|
|
56
|
+
_NOT_CALLED = ("Initial component", "Instantiated class", "Accessed property")
|
|
57
|
+
_NAMED = (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _member(component: Any, name: str) -> tuple[bool, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Look up a member the way Fire does: by name, then with ``-`` as ``_``.
|
|
63
|
+
"""
|
|
64
|
+
for key in (name, name.replace("-", "_")):
|
|
65
|
+
if isinstance(component, dict):
|
|
66
|
+
if key in component:
|
|
67
|
+
return True, component[key]
|
|
68
|
+
else:
|
|
69
|
+
try:
|
|
70
|
+
return True, getattr(component, key)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
return False, None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _resolve(component: Any, path: Sequence[str]) -> tuple[Any, bool]:
|
|
77
|
+
"""
|
|
78
|
+
Follow ``path`` from ``component`` as far as it names members.
|
|
79
|
+
|
|
80
|
+
Returns the component reached and whether it is known to be the
|
|
81
|
+
target of the command. It is when the whole path was followed, or
|
|
82
|
+
when a function was reached, since the rest are then its arguments.
|
|
83
|
+
Members that only exist on an instance cannot be followed; the class
|
|
84
|
+
is returned as not known to be the target.
|
|
85
|
+
"""
|
|
86
|
+
for name in path:
|
|
87
|
+
if inspect.isroutine(component):
|
|
88
|
+
return component, True
|
|
89
|
+
found, member = _member(component, name)
|
|
90
|
+
if not found:
|
|
91
|
+
return component, False
|
|
92
|
+
component = member
|
|
93
|
+
return component, True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _accepts(target: Any, flag: str) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Tell whether Fire would pass ``flag`` to ``target`` as an argument.
|
|
99
|
+
|
|
100
|
+
``--help`` is an argument of a callable with a ``help`` parameter.
|
|
101
|
+
``-h`` is Fire's shortcut for any parameter starting with ``h``.
|
|
102
|
+
"""
|
|
103
|
+
if not (inspect.isclass(target) or inspect.isroutine(target)):
|
|
104
|
+
return False
|
|
105
|
+
try:
|
|
106
|
+
parameters = inspect.signature(target).parameters.values()
|
|
107
|
+
except (TypeError, ValueError):
|
|
108
|
+
return False
|
|
109
|
+
names = [p.name for p in parameters if p.kind in _NAMED]
|
|
110
|
+
if flag == "--help":
|
|
111
|
+
return "help" in names
|
|
112
|
+
return any(name.startswith("h") for name in names)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _normalize_help(argv: Sequence[str], component: Any, ours: bool) -> list[str]:
|
|
116
|
+
"""
|
|
117
|
+
Translate conventional help syntax into Python Fire's native syntax.
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
|
|
121
|
+
--help
|
|
122
|
+
-> -- --help
|
|
123
|
+
|
|
124
|
+
foo --help
|
|
125
|
+
-> foo -- --help
|
|
126
|
+
|
|
127
|
+
foo -h
|
|
128
|
+
-> foo -- --help
|
|
129
|
+
|
|
130
|
+
help
|
|
131
|
+
-> -- --help
|
|
132
|
+
|
|
133
|
+
help foo bar
|
|
134
|
+
-> foo bar -- --help
|
|
135
|
+
|
|
136
|
+
If an explicit ``--`` separator is already present, the command is
|
|
137
|
+
assumed to use native Fire syntax and is left untouched.
|
|
138
|
+
|
|
139
|
+
``help`` is only interpreted as the first word, and only if it is
|
|
140
|
+
``ours``: the component is not a function and has no ``help`` member
|
|
141
|
+
of its own. ``help --help`` is help for the help command itself.
|
|
142
|
+
|
|
143
|
+
``-h`` and ``--help`` are only interpreted as the last word, and
|
|
144
|
+
only if the command they follow does not take them as an argument.
|
|
145
|
+
Where that command cannot be determined, ``-h`` is left to Fire.
|
|
146
|
+
"""
|
|
147
|
+
argv = list(argv)
|
|
148
|
+
|
|
149
|
+
if not argv or "--" in argv:
|
|
150
|
+
return argv
|
|
151
|
+
|
|
152
|
+
flag = argv[-1]
|
|
153
|
+
|
|
154
|
+
if argv[0] == "help" and ours:
|
|
155
|
+
if flag not in _FLAGS:
|
|
156
|
+
return argv[1:] + _HELP
|
|
157
|
+
if len(argv) > 2 or not _member(component, "help")[0]:
|
|
158
|
+
return argv[1:-1] + _HELP
|
|
159
|
+
|
|
160
|
+
if flag not in _FLAGS:
|
|
161
|
+
return argv
|
|
162
|
+
|
|
163
|
+
target, known = _resolve(component, argv[:-1])
|
|
164
|
+
if _accepts(target, flag) or (flag == "-h" and not known):
|
|
165
|
+
return argv
|
|
166
|
+
return argv[:-1] + _HELP
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _help_command(component: Any, name: Any) -> tuple[Any, Any]:
|
|
170
|
+
"""
|
|
171
|
+
Make the help command of a component, as a function and as a method.
|
|
172
|
+
|
|
173
|
+
Command lines starting with ``help`` are translated before Fire sees
|
|
174
|
+
them, so this is mostly there for Fire to list with the component's
|
|
175
|
+
other commands. It is what runs if Fire is given the command in its
|
|
176
|
+
native syntax.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def help(*command):
|
|
180
|
+
"""Show help for the program, or for a command.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
command: The command to show help for, as it would be run.
|
|
184
|
+
"""
|
|
185
|
+
words = [str(word) for word in command]
|
|
186
|
+
return _fire_with(_display_stdout, component, command=words + _HELP, name=name)
|
|
187
|
+
|
|
188
|
+
def method(self, *command):
|
|
189
|
+
return help(*command)
|
|
190
|
+
|
|
191
|
+
method.__name__ = help.__name__
|
|
192
|
+
method.__doc__ = help.__doc__
|
|
193
|
+
return help, method
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _add_help(component: Any, name: Any = None) -> tuple[Any, Any]:
|
|
197
|
+
"""
|
|
198
|
+
Give a component a help command.
|
|
199
|
+
|
|
200
|
+
Returns the component to pass to Fire, and a function to undo it.
|
|
201
|
+
A dict gets a copy with the command added. A module gets it as an
|
|
202
|
+
attribute, and anything else as a method of its class, for as long
|
|
203
|
+
as Fire runs. A component that cannot take one is returned as it is.
|
|
204
|
+
"""
|
|
205
|
+
help, method = _help_command(component, name)
|
|
206
|
+
if isinstance(component, dict):
|
|
207
|
+
return dict(component, help=help), None
|
|
208
|
+
if inspect.ismodule(component):
|
|
209
|
+
owner, member = component, help
|
|
210
|
+
elif inspect.isclass(component):
|
|
211
|
+
owner, member = component, method
|
|
212
|
+
else:
|
|
213
|
+
owner, member = type(component), method
|
|
214
|
+
try:
|
|
215
|
+
setattr(owner, "help", member)
|
|
216
|
+
except (AttributeError, TypeError):
|
|
217
|
+
return component, None
|
|
218
|
+
return component, lambda: delattr(owner, "help")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _display_stdout(lines: Sequence[str], out: Any = None) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Stand in for fire.core.Display, printing to stdout instead of stderr,
|
|
224
|
+
and not through a pager.
|
|
225
|
+
"""
|
|
226
|
+
sys.stdout.write("\n".join(lines) + "\n")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _display_unpaged(lines: Sequence[str], out: Any) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Stand in for fire.core.Display, paging only what Fire sends to stderr.
|
|
232
|
+
|
|
233
|
+
What Fire sends to stdout is the text for a command line that names
|
|
234
|
+
a group rather than a command. Its native help goes to stderr.
|
|
235
|
+
"""
|
|
236
|
+
if out is sys.stdout:
|
|
237
|
+
_display_stdout(lines)
|
|
238
|
+
else:
|
|
239
|
+
_display(lines, out=out)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _called(trace: Any) -> bool:
|
|
243
|
+
"""
|
|
244
|
+
Tell whether the last step of a Fire trace was a function call.
|
|
245
|
+
|
|
246
|
+
If the trace does not say, assume so, which leaves things to Fire.
|
|
247
|
+
"""
|
|
248
|
+
try:
|
|
249
|
+
action = trace.elements[-1]._action
|
|
250
|
+
except (AttributeError, IndexError):
|
|
251
|
+
return True
|
|
252
|
+
return action not in _NOT_CALLED
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class _HelpText:
|
|
256
|
+
"""
|
|
257
|
+
Stand in for the fire.helptext module, as fire.core sees it.
|
|
258
|
+
|
|
259
|
+
Fire prints the full help for a command line that names a group
|
|
260
|
+
rather than a command, and calls it a success. This makes it the
|
|
261
|
+
usage text, and keeps the trace, for the caller to call it an error.
|
|
262
|
+
|
|
263
|
+
Fire prints the same for an object returned by a function. That is
|
|
264
|
+
left alone: the command was complete.
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
def __init__(self) -> None:
|
|
268
|
+
self.incomplete = None
|
|
269
|
+
|
|
270
|
+
def __getattr__(self, name: str) -> Any:
|
|
271
|
+
return getattr(_helptext, name)
|
|
272
|
+
|
|
273
|
+
def HelpText(self, component: Any, trace: Any = None, verbose: bool = False) -> str:
|
|
274
|
+
if trace is None or trace.show_help or trace.HasError() or _called(trace):
|
|
275
|
+
return _helptext.HelpText(component, trace=trace, verbose=verbose)
|
|
276
|
+
self.incomplete = trace
|
|
277
|
+
return _helptext.UsageText(component, trace=trace, verbose=verbose)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
_display = getattr(_fire.core, "Display", None)
|
|
281
|
+
_helptext = getattr(_fire.core, "helptext", None)
|
|
282
|
+
|
|
283
|
+
# Obtain the signature from the installed Fire version instead of
|
|
284
|
+
# duplicating it here. This makes fireaid less dependent on Fire's
|
|
285
|
+
# particular API version.
|
|
286
|
+
_FIRE_SIGNATURE = inspect.signature(_fire.Fire)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def Fire(*args: Any, **kwargs: Any) -> Any:
|
|
290
|
+
"""
|
|
291
|
+
Call fire.Fire(), adding conventional command-line help handling.
|
|
292
|
+
|
|
293
|
+
All arguments other than ``component`` and ``command`` are passed
|
|
294
|
+
through unchanged. The command, by default taken from sys.argv, is
|
|
295
|
+
normalized first, making this work:
|
|
296
|
+
|
|
297
|
+
fire.Fire(MyCLI, command=["foo", "--help"])
|
|
298
|
+
fire.Fire(MyCLI, command="help foo")
|
|
299
|
+
"""
|
|
300
|
+
bound = _FIRE_SIGNATURE.bind_partial(*args, **kwargs)
|
|
301
|
+
arguments = bound.arguments
|
|
302
|
+
|
|
303
|
+
if arguments.get("component") is None:
|
|
304
|
+
# Fire would look at its caller, which is this function. Supply
|
|
305
|
+
# the context of our own caller instead.
|
|
306
|
+
frame = sys._getframe(1)
|
|
307
|
+
arguments["component"] = {**frame.f_globals, **frame.f_locals}
|
|
308
|
+
|
|
309
|
+
command = arguments.get("command")
|
|
310
|
+
if command is None:
|
|
311
|
+
command = sys.argv[1:]
|
|
312
|
+
elif isinstance(command, str):
|
|
313
|
+
command = shlex.split(command)
|
|
314
|
+
|
|
315
|
+
if not isinstance(command, (list, tuple)):
|
|
316
|
+
# Not a valid command. Let Fire report it.
|
|
317
|
+
return _fire.Fire(*bound.args, **bound.kwargs)
|
|
318
|
+
|
|
319
|
+
component = arguments["component"]
|
|
320
|
+
ours = not (inspect.isroutine(component) or _member(component, "help")[0])
|
|
321
|
+
undo = None
|
|
322
|
+
if ours:
|
|
323
|
+
arguments["component"], undo = _add_help(component, arguments.get("name"))
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
arguments["command"] = _normalize_help(command, arguments["component"], ours)
|
|
327
|
+
if arguments["command"] == list(command):
|
|
328
|
+
display = _display_unpaged
|
|
329
|
+
else:
|
|
330
|
+
# Help was asked for in conventional syntax: it belongs on stdout.
|
|
331
|
+
display = _display_stdout
|
|
332
|
+
return _fire_with(display, *bound.args, **bound.kwargs)
|
|
333
|
+
finally:
|
|
334
|
+
if undo is not None:
|
|
335
|
+
undo()
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _fire_with(display: Any, *args: Any, **kwargs: Any) -> Any:
|
|
339
|
+
"""
|
|
340
|
+
Call fire.Fire() with a stand-in for its display of help.
|
|
341
|
+
"""
|
|
342
|
+
if _display is None or _helptext is None:
|
|
343
|
+
return _fire.Fire(*args, **kwargs)
|
|
344
|
+
|
|
345
|
+
helptext = _HelpText()
|
|
346
|
+
_fire.core.helptext = helptext
|
|
347
|
+
_fire.core.Display = display
|
|
348
|
+
try:
|
|
349
|
+
result = _fire.Fire(*args, **kwargs)
|
|
350
|
+
finally:
|
|
351
|
+
_fire.core.Display = _display
|
|
352
|
+
_fire.core.helptext = _helptext
|
|
353
|
+
|
|
354
|
+
if helptext.incomplete is not None:
|
|
355
|
+
# Fire has printed the usage in place of a result.
|
|
356
|
+
raise _fire.core.FireExit(2, helptext.incomplete)
|
|
357
|
+
return result
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Make introspection of fireaid.Fire look like fire.Fire rather than
|
|
361
|
+
# showing the wrapper's (*args, **kwargs) implementation.
|
|
362
|
+
Fire.__signature__ = _FIRE_SIGNATURE
|
|
363
|
+
Fire.__wrapped__ = _fire.Fire
|
|
364
|
+
Fire.__doc__ = _fire.Fire.__doc__
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def __getattr__(name: str) -> Any:
|
|
368
|
+
"""
|
|
369
|
+
Proxy attributes not defined by fireaid to the real fire package.
|
|
370
|
+
|
|
371
|
+
Examples:
|
|
372
|
+
|
|
373
|
+
fireaid.core
|
|
374
|
+
fireaid.decorators
|
|
375
|
+
fireaid.inspectutils
|
|
376
|
+
fireaid.trace
|
|
377
|
+
"""
|
|
378
|
+
return getattr(_fire, name)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def __dir__() -> list[str]:
|
|
382
|
+
"""
|
|
383
|
+
Return the combined namespace of fireaid and the wrapped fire module.
|
|
384
|
+
"""
|
|
385
|
+
return sorted(set(globals()) | set(dir(_fire)))
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# Make the submodules Fire has loaded importable under our name too:
|
|
389
|
+
#
|
|
390
|
+
# import fireaid.core
|
|
391
|
+
# from fireaid.core import FireExit
|
|
392
|
+
#
|
|
393
|
+
for _name, _module in list(sys.modules.items()):
|
|
394
|
+
if _name.startswith("fire.") and not _name.endswith("__main__"):
|
|
395
|
+
sys.modules.setdefault(__name__ + _name[len("fire"):], _module)
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
__version__ = _metadata.version("fireaid")
|
|
399
|
+
except _metadata.PackageNotFoundError:
|
|
400
|
+
__version__ = "unknown"
|
fireaid/__main__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enables ``python -m fireaid [module] [arg] ...``, like ``python -m fire``.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import fireaid
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from fire.__main__ import import_module
|
|
11
|
+
except ImportError:
|
|
12
|
+
import_module = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(args):
|
|
16
|
+
if import_module is None:
|
|
17
|
+
sys.exit("python -m fireaid requires a Fire version with python -m fire")
|
|
18
|
+
|
|
19
|
+
if len(args) < 2:
|
|
20
|
+
print("usage: python -m fireaid [module] [arg] ...")
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
module, module_name = import_module(args[1])
|
|
24
|
+
fireaid.Fire(module, name=module_name, command=args[2:])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main(sys.argv)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fireaid
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Familiar help for command-line tools made with Python Fire
|
|
5
|
+
Author-email: Christiane Ruetten <cr@23bit.net>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cr/fireaid
|
|
8
|
+
Project-URL: Repository, https://github.com/cr/fireaid
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: fire>=0.2.0
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest; extra == "test"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# fireaid
|
|
18
|
+
|
|
19
|
+
*A little help for Python Fire.*
|
|
20
|
+
|
|
21
|
+
[Python Fire](https://github.com/google/python-fire) turns any Python
|
|
22
|
+
function, class or module into a command-line tool with a single line
|
|
23
|
+
of code. `fireaid` is a thin wrapper around it that adds the help
|
|
24
|
+
conventions people know from tools like `git`: a `help` command,
|
|
25
|
+
`--help` printed plainly to the terminal, and a short usage message
|
|
26
|
+
when a command is incomplete. Everything else is Fire, unchanged.
|
|
27
|
+
|
|
28
|
+
All you need is:
|
|
29
|
+
|
|
30
|
+
pip install git+https://github.com/cr/fireaid
|
|
31
|
+
|
|
32
|
+
import fireaid as fire
|
|
33
|
+
|
|
34
|
+
fire.Fire(MyTool)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Directly from the repository:
|
|
39
|
+
|
|
40
|
+
pip install git+https://github.com/cr/fireaid
|
|
41
|
+
|
|
42
|
+
As a dependency in another project's `pyproject.toml`:
|
|
43
|
+
|
|
44
|
+
dependencies = ["fireaid @ git+https://github.com/cr/fireaid"]
|
|
45
|
+
|
|
46
|
+
From the project directory:
|
|
47
|
+
|
|
48
|
+
pip install .
|
|
49
|
+
|
|
50
|
+
For development:
|
|
51
|
+
|
|
52
|
+
pip install -e .
|
|
53
|
+
|
|
54
|
+
fireaid requires Fire 0.2.0 or later, the first version to accept
|
|
55
|
+
`--help` without a separator. On Python 3.13 and later, Fire itself
|
|
56
|
+
requires version 0.7.0.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
Change:
|
|
61
|
+
|
|
62
|
+
import fire
|
|
63
|
+
|
|
64
|
+
to:
|
|
65
|
+
|
|
66
|
+
import fireaid as fire
|
|
67
|
+
|
|
68
|
+
Existing code such as:
|
|
69
|
+
|
|
70
|
+
fire.Fire(MyCLI)
|
|
71
|
+
|
|
72
|
+
continues to work, as do `import fireaid.core` and `python -m fireaid`.
|
|
73
|
+
|
|
74
|
+
## Help
|
|
75
|
+
|
|
76
|
+
Python Fire accepts:
|
|
77
|
+
|
|
78
|
+
tool foo -- --help
|
|
79
|
+
tool foo --help
|
|
80
|
+
tool foo -h
|
|
81
|
+
|
|
82
|
+
fireaid additionally supports Git-style help:
|
|
83
|
+
|
|
84
|
+
tool help
|
|
85
|
+
tool help foo
|
|
86
|
+
tool help foo bar
|
|
87
|
+
|
|
88
|
+
`help` is a command like any other: Fire lists it with the program's
|
|
89
|
+
commands in the usage text and in the help, and `tool help help` shows
|
|
90
|
+
help for it.
|
|
91
|
+
To that end, fireaid adds the command to the program for as long as
|
|
92
|
+
Fire runs: to its class, or to a copy if the program is a dict.
|
|
93
|
+
|
|
94
|
+
With `--help` and `-h`, fireaid also
|
|
95
|
+
|
|
96
|
+
- prints the help text to stdout, so that `tool --help | less` works,
|
|
97
|
+
- prints it as it is, where Fire starts a pager on a terminal,
|
|
98
|
+
- omits Fire's `INFO: Showing help with the command ...` banner,
|
|
99
|
+
- shows help for functions taking `**kwargs`, where Fire passes
|
|
100
|
+
`help=True` to the function.
|
|
101
|
+
|
|
102
|
+
Native Fire syntax continues to work unchanged, printing to stderr,
|
|
103
|
+
or to a pager on a terminal.
|
|
104
|
+
|
|
105
|
+
## Usage text
|
|
106
|
+
|
|
107
|
+
A command line that names a group rather than a command, such as
|
|
108
|
+
plain `tool`, is a success to Fire, which shows the full help for the
|
|
109
|
+
group, in a pager. To fireaid it is an incomplete command, as it is to
|
|
110
|
+
Git: it prints Fire's short usage text to stdout, and exits with 2,
|
|
111
|
+
Fire's exit code for errors:
|
|
112
|
+
|
|
113
|
+
Usage: tool <group|command>
|
|
114
|
+
available groups: shelf
|
|
115
|
+
available commands: add | help | list | remove | search
|
|
116
|
+
|
|
117
|
+
For detailed information on this command, run:
|
|
118
|
+
tool --help
|
|
119
|
+
|
|
120
|
+
A function that returns an object is a complete command. There, Fire
|
|
121
|
+
shows the help for the object, which fireaid only keeps from the pager.
|
|
122
|
+
|
|
123
|
+
`examples/pantry.py` is a small program to try all of this on, and
|
|
124
|
+
`examples/tour.sh` runs through it.
|
|
125
|
+
|
|
126
|
+
## Limits
|
|
127
|
+
|
|
128
|
+
fireaid does not take anything away from the wrapped program:
|
|
129
|
+
|
|
130
|
+
- `help` is only help as the first word, and not if the program has
|
|
131
|
+
a `help` command of its own, or is a single function.
|
|
132
|
+
- `--help` and `-h` are only help as the last word. `--help` is left
|
|
133
|
+
to a command with a `help` parameter, `-h` to a command with a
|
|
134
|
+
parameter starting with `h`, for which it is Fire's shortcut.
|
|
135
|
+
- A command that fireaid cannot find without running the program, such
|
|
136
|
+
as a member created in `__init__`, may have such a parameter. There,
|
|
137
|
+
`-h` is left to Fire, which shows help with its banner on stderr.
|
|
138
|
+
|
|
139
|
+
Help for a command that does not exist is an error, as it is in
|
|
140
|
+
Fire's native syntax.
|
|
141
|
+
|
|
142
|
+
As with Fire, help is shown for what the command line evaluates to.
|
|
143
|
+
`tool foo x --help` calls `foo` and shows help for its result. Ask
|
|
144
|
+
for `tool foo --help` to get help for `foo`.
|
|
145
|
+
|
|
146
|
+
## Tests
|
|
147
|
+
|
|
148
|
+
make test
|
|
149
|
+
|
|
150
|
+
creates a virtual environment in `.venv`, upgrades it to the latest
|
|
151
|
+
stable Fire, and runs the test suite. The tests hold fireaid to what
|
|
152
|
+
this README says, mostly by comparing it to Fire on the same command
|
|
153
|
+
lines. `make test PYTHON=python3.14` selects the Python for a new
|
|
154
|
+
environment, `make clean` removes it.
|
|
155
|
+
|
|
156
|
+
## Releasing
|
|
157
|
+
|
|
158
|
+
Set the new `version` in `pyproject.toml`, commit, then tag and push:
|
|
159
|
+
|
|
160
|
+
git tag v0.1.0 && git push origin main v0.1.0
|
|
161
|
+
|
|
162
|
+
The tag starts `.github/workflows/publish.yml`, which runs the tests,
|
|
163
|
+
checks that the tag is the package version, builds, and publishes to
|
|
164
|
+
PyPI as a trusted publisher. PyPI never takes the same version twice.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
fireaid/__init__.py,sha256=GwBnKkdldgFtZb3RVn0tFbrngkbp7MAkG6EYsI_NkA4,12150
|
|
2
|
+
fireaid/__main__.py,sha256=2UpNfCNbPU0vQqTMGsafsOcyPLqajcAd3bW1CvDwDdE,599
|
|
3
|
+
fireaid-0.1.0.dist-info/licenses/LICENSE,sha256=J_uMfTQ0iAQ7TPxSa2X8EshsnriTEFCGZR9V5qUA-qA,1075
|
|
4
|
+
fireaid-0.1.0.dist-info/METADATA,sha256=bp61sQVuPxZNgdK2Hn9lc3FGSRp7uJxmKzeqf3Yx6-Q,4956
|
|
5
|
+
fireaid-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
fireaid-0.1.0.dist-info/top_level.txt,sha256=65JX_jgamkJKwojn8qXQDY0vqq-ch4gUVbOgnCjiOzk,8
|
|
7
|
+
fireaid-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christiane Ruetten
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fireaid
|