usage-spec 1.0.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.
- usage_spec/__init__.py +32 -0
- usage_spec/json.py +123 -0
- usage_spec/kdl.py +247 -0
- usage_spec/spec.py +67 -0
- usage_spec-1.0.0.dist-info/METADATA +68 -0
- usage_spec-1.0.0.dist-info/RECORD +7 -0
- usage_spec-1.0.0.dist-info/WHEEL +4 -0
usage_spec/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Usage spec core - Python implementation aligned with @usage-spec/core."""
|
|
2
|
+
|
|
3
|
+
from .spec import Spec, SpecArg, SpecFlag, SpecCommand, SpecChoices
|
|
4
|
+
from .kdl import render_kdl, validate_kdl
|
|
5
|
+
from .json import render_json
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Spec",
|
|
9
|
+
"SpecArg",
|
|
10
|
+
"SpecFlag",
|
|
11
|
+
"SpecCommand",
|
|
12
|
+
"SpecChoices",
|
|
13
|
+
"render_kdl",
|
|
14
|
+
"validate_kdl",
|
|
15
|
+
"render_json",
|
|
16
|
+
"generate",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate(
|
|
21
|
+
spec: Spec,
|
|
22
|
+
*,
|
|
23
|
+
format: str = "kdl",
|
|
24
|
+
comment: str | None = None,
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Generate usage spec output in the specified format."""
|
|
27
|
+
output = render_json(spec) if format == "json" else render_kdl(spec)
|
|
28
|
+
|
|
29
|
+
if comment:
|
|
30
|
+
return f"// {comment}\n{output}"
|
|
31
|
+
|
|
32
|
+
return output
|
usage_spec/json.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""JSON renderer for usage spec, aligned with @usage-spec/core json.ts output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .spec import Spec, SpecArg, SpecFlag, SpecCommand
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _arg_to_json(arg: SpecArg) -> dict[str, Any]:
|
|
12
|
+
result: dict[str, Any] = {"name": arg.name}
|
|
13
|
+
|
|
14
|
+
if arg.help:
|
|
15
|
+
result["help"] = arg.help
|
|
16
|
+
if not arg.required:
|
|
17
|
+
result["required"] = False
|
|
18
|
+
if arg.var:
|
|
19
|
+
result["var"] = True
|
|
20
|
+
if arg.hide:
|
|
21
|
+
result["hide"] = True
|
|
22
|
+
if len(arg.default) == 1:
|
|
23
|
+
result["default"] = arg.default[0]
|
|
24
|
+
if len(arg.default) > 1:
|
|
25
|
+
result["default"] = arg.default
|
|
26
|
+
if arg.choices:
|
|
27
|
+
result["choices"] = arg.choices.values
|
|
28
|
+
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _flag_to_json(flag: SpecFlag) -> dict[str, Any]:
|
|
33
|
+
result: dict[str, Any] = {}
|
|
34
|
+
|
|
35
|
+
name_parts: list[str] = []
|
|
36
|
+
if flag.short:
|
|
37
|
+
name_parts.append(f"-{flag.short}")
|
|
38
|
+
if flag.long:
|
|
39
|
+
name_parts.append(f"--{flag.long}")
|
|
40
|
+
result["name"] = " ".join(name_parts)
|
|
41
|
+
|
|
42
|
+
if flag.help:
|
|
43
|
+
result["help"] = flag.help
|
|
44
|
+
if flag.help_long:
|
|
45
|
+
result["help_long"] = flag.help_long
|
|
46
|
+
if flag.required:
|
|
47
|
+
result["required"] = True
|
|
48
|
+
if flag.hide:
|
|
49
|
+
result["hide"] = True
|
|
50
|
+
if flag.global_:
|
|
51
|
+
result["global"] = True
|
|
52
|
+
if flag.count:
|
|
53
|
+
result["count"] = True
|
|
54
|
+
if flag.var:
|
|
55
|
+
result["var"] = True
|
|
56
|
+
if flag.negate:
|
|
57
|
+
result["negate"] = flag.negate
|
|
58
|
+
if flag.deprecated:
|
|
59
|
+
result["deprecated"] = flag.deprecated
|
|
60
|
+
if flag.env:
|
|
61
|
+
result["env"] = flag.env
|
|
62
|
+
if len(flag.default) == 1:
|
|
63
|
+
result["default"] = flag.default[0]
|
|
64
|
+
if len(flag.default) > 1:
|
|
65
|
+
result["default"] = flag.default
|
|
66
|
+
|
|
67
|
+
if flag.arg:
|
|
68
|
+
result["arg"] = _arg_to_json(flag.arg)
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _cmd_to_json(cmd: SpecCommand) -> dict[str, Any]:
|
|
74
|
+
result: dict[str, Any] = {"name": cmd.name}
|
|
75
|
+
|
|
76
|
+
if cmd.help:
|
|
77
|
+
result["help"] = cmd.help
|
|
78
|
+
if cmd.help_long:
|
|
79
|
+
result["help_long"] = cmd.help_long
|
|
80
|
+
if cmd.hide:
|
|
81
|
+
result["hide"] = True
|
|
82
|
+
if cmd.deprecated:
|
|
83
|
+
result["deprecated"] = cmd.deprecated
|
|
84
|
+
if cmd.aliases:
|
|
85
|
+
result["aliases"] = cmd.aliases
|
|
86
|
+
if cmd.subcommand_required:
|
|
87
|
+
result["subcommand_required"] = True
|
|
88
|
+
|
|
89
|
+
if cmd.flags:
|
|
90
|
+
result["flags"] = [_flag_to_json(f) for f in cmd.flags]
|
|
91
|
+
if cmd.args:
|
|
92
|
+
result["args"] = [_arg_to_json(a) for a in cmd.args]
|
|
93
|
+
if cmd.cmds:
|
|
94
|
+
result["cmds"] = [_cmd_to_json(c) for c in cmd.cmds]
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def render_json(spec: Spec) -> str:
|
|
100
|
+
"""Render a Spec to JSON format string."""
|
|
101
|
+
result: dict[str, Any] = {}
|
|
102
|
+
|
|
103
|
+
if spec.name:
|
|
104
|
+
result["name"] = spec.name
|
|
105
|
+
if spec.bin:
|
|
106
|
+
result["bin"] = spec.bin
|
|
107
|
+
if spec.version:
|
|
108
|
+
result["version"] = spec.version
|
|
109
|
+
if spec.about:
|
|
110
|
+
result["about"] = spec.about
|
|
111
|
+
if spec.long:
|
|
112
|
+
result["long_about"] = spec.long
|
|
113
|
+
if spec.usage:
|
|
114
|
+
result["usage"] = spec.usage
|
|
115
|
+
|
|
116
|
+
if spec.flags:
|
|
117
|
+
result["flags"] = [_flag_to_json(f) for f in spec.flags]
|
|
118
|
+
if spec.args:
|
|
119
|
+
result["args"] = [_arg_to_json(a) for a in spec.args]
|
|
120
|
+
if spec.cmds:
|
|
121
|
+
result["cmds"] = [_cmd_to_json(c) for c in spec.cmds]
|
|
122
|
+
|
|
123
|
+
return json.dumps(result, indent=2) + "\n"
|
usage_spec/kdl.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""KDL renderer for usage spec, aligned with @usage-spec/core kdl.ts output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .spec import Spec, SpecArg, SpecFlag, SpecCommand, SpecChoices
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _escape_kdl_string(value: str) -> str:
|
|
9
|
+
"""Escape a string for KDL format."""
|
|
10
|
+
if not value:
|
|
11
|
+
return '""'
|
|
12
|
+
# KDL strings: if the value contains special chars, wrap in quotes
|
|
13
|
+
needs_quoting = any(c in value for c in (' ', '"', '\n', '\r', '\t', '\\', '{', '}', '#', '\0'))
|
|
14
|
+
if not needs_quoting and value not in ('true', 'false', 'null'):
|
|
15
|
+
return value
|
|
16
|
+
escaped = value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
|
|
17
|
+
return f'"{escaped}"'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _format_bool(value: bool) -> str:
|
|
21
|
+
return "#true" if value else "#false"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _indent(lines: list[str], level: int) -> list[str]:
|
|
25
|
+
prefix = " " * level
|
|
26
|
+
return [prefix + line for line in lines]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _render_choices(choices: SpecChoices) -> list[str]:
|
|
30
|
+
parts = " ".join(_escape_kdl_string(c) for c in choices.values)
|
|
31
|
+
return [f"choices {parts}"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _render_arg(arg: SpecArg, *, is_flag_arg: bool = False, indent_level: int = 0) -> list[str]:
|
|
35
|
+
lines: list[str] = []
|
|
36
|
+
|
|
37
|
+
if is_flag_arg:
|
|
38
|
+
usage = f"<{arg.name}>"
|
|
39
|
+
line_parts = [f"arg {_escape_kdl_string(usage)}"]
|
|
40
|
+
else:
|
|
41
|
+
# Build usage string: <required> or [optional], with … for variadic
|
|
42
|
+
if arg.required:
|
|
43
|
+
usage = f"<{arg.name}>"
|
|
44
|
+
else:
|
|
45
|
+
usage = f"[{arg.name}]"
|
|
46
|
+
if arg.var:
|
|
47
|
+
usage += "\u2026"
|
|
48
|
+
|
|
49
|
+
line_parts = [f"arg {_escape_kdl_string(usage)}"]
|
|
50
|
+
|
|
51
|
+
if arg.help:
|
|
52
|
+
line_parts.append(f"help={_escape_kdl_string(arg.help)}")
|
|
53
|
+
if not arg.required:
|
|
54
|
+
line_parts.append(f"required={_format_bool(False)}")
|
|
55
|
+
if arg.var:
|
|
56
|
+
line_parts.append(f"var={_format_bool(True)}")
|
|
57
|
+
if arg.hide:
|
|
58
|
+
line_parts.append(f"hide={_format_bool(True)}")
|
|
59
|
+
if len(arg.default) == 1:
|
|
60
|
+
line_parts.append(f"default={_escape_kdl_string(arg.default[0])}")
|
|
61
|
+
|
|
62
|
+
# Check for children (choices, multiple defaults)
|
|
63
|
+
children: list[str] = []
|
|
64
|
+
|
|
65
|
+
if not is_flag_arg and len(arg.default) > 1:
|
|
66
|
+
parts = " ".join(_escape_kdl_string(d) for d in arg.default)
|
|
67
|
+
children.append(f"default {parts}")
|
|
68
|
+
|
|
69
|
+
if arg.choices:
|
|
70
|
+
children.extend(_render_choices(arg.choices))
|
|
71
|
+
|
|
72
|
+
if is_flag_arg:
|
|
73
|
+
# Flag arg: only has help and choices as children/properties
|
|
74
|
+
line_parts_flag = [f"arg {_escape_kdl_string(f'<{arg.name}>')}"]
|
|
75
|
+
if arg.help:
|
|
76
|
+
line_parts_flag.append(f"help={_escape_kdl_string(arg.help)}")
|
|
77
|
+
|
|
78
|
+
if arg.choices:
|
|
79
|
+
children_flag = _indent(_render_choices(arg.choices), 1)
|
|
80
|
+
lines.append(" ".join(line_parts_flag) + " {")
|
|
81
|
+
lines.extend(children_flag)
|
|
82
|
+
lines.append("}")
|
|
83
|
+
else:
|
|
84
|
+
lines.append(" ".join(line_parts_flag))
|
|
85
|
+
return lines
|
|
86
|
+
|
|
87
|
+
if children:
|
|
88
|
+
lines.append(" ".join(line_parts) + " {")
|
|
89
|
+
lines.extend(_indent(children, 1))
|
|
90
|
+
lines.append("}")
|
|
91
|
+
else:
|
|
92
|
+
lines.append(" ".join(line_parts))
|
|
93
|
+
|
|
94
|
+
return lines
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _render_flag(flag: SpecFlag) -> list[str]:
|
|
98
|
+
# Build the flag name: "-s --long"
|
|
99
|
+
name_parts: list[str] = []
|
|
100
|
+
if flag.short:
|
|
101
|
+
name_parts.append(f"-{flag.short}")
|
|
102
|
+
if flag.long:
|
|
103
|
+
name_parts.append(f"--{flag.long}")
|
|
104
|
+
flag_name = " ".join(name_parts)
|
|
105
|
+
|
|
106
|
+
line_parts = [f"flag {_escape_kdl_string(flag_name)}"]
|
|
107
|
+
|
|
108
|
+
if flag.help:
|
|
109
|
+
line_parts.append(f"help={_escape_kdl_string(flag.help)}")
|
|
110
|
+
if flag.required:
|
|
111
|
+
line_parts.append(f"required={_format_bool(True)}")
|
|
112
|
+
if flag.var:
|
|
113
|
+
line_parts.append(f"var={_format_bool(True)}")
|
|
114
|
+
if flag.hide:
|
|
115
|
+
line_parts.append(f"hide={_format_bool(True)}")
|
|
116
|
+
if flag.global_:
|
|
117
|
+
line_parts.append(f"global={_format_bool(True)}")
|
|
118
|
+
if flag.count:
|
|
119
|
+
line_parts.append(f"count={_format_bool(True)}")
|
|
120
|
+
if flag.negate:
|
|
121
|
+
line_parts.append(f"negate={flag.negate}")
|
|
122
|
+
if flag.deprecated:
|
|
123
|
+
line_parts.append(f"deprecated={_escape_kdl_string(flag.deprecated)}")
|
|
124
|
+
if len(flag.default) == 1:
|
|
125
|
+
line_parts.append(f"default={_escape_kdl_string(flag.default[0])}")
|
|
126
|
+
elif flag.default_bool is not None:
|
|
127
|
+
line_parts.append(f"default={_format_bool(flag.default_bool)}")
|
|
128
|
+
if flag.env:
|
|
129
|
+
line_parts.append(f"env={_escape_kdl_string(flag.env)}")
|
|
130
|
+
|
|
131
|
+
# Check for children
|
|
132
|
+
children: list[str] = []
|
|
133
|
+
|
|
134
|
+
if flag.help_long:
|
|
135
|
+
children.append(f"long_help {_escape_kdl_string(flag.help_long)}")
|
|
136
|
+
|
|
137
|
+
if len(flag.default) > 1:
|
|
138
|
+
parts = " ".join(_escape_kdl_string(d) for d in flag.default)
|
|
139
|
+
children.append(f"default {parts}")
|
|
140
|
+
|
|
141
|
+
if flag.arg:
|
|
142
|
+
children.extend(_render_flag_arg(flag.arg))
|
|
143
|
+
|
|
144
|
+
if children:
|
|
145
|
+
return [" ".join(line_parts) + " {", *_indent(children, 1), "}"]
|
|
146
|
+
else:
|
|
147
|
+
return [" ".join(line_parts)]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _render_flag_arg(arg: SpecArg) -> list[str]:
|
|
151
|
+
"""Render a flag's inner arg node."""
|
|
152
|
+
line_parts = [f"arg {_escape_kdl_string(f'<{arg.name}>')}"]
|
|
153
|
+
|
|
154
|
+
if arg.help:
|
|
155
|
+
line_parts.append(f"help={_escape_kdl_string(arg.help)}")
|
|
156
|
+
|
|
157
|
+
if arg.choices:
|
|
158
|
+
children = _indent(_render_choices(arg.choices), 1)
|
|
159
|
+
return [" ".join(line_parts) + " {", *children, "}"]
|
|
160
|
+
else:
|
|
161
|
+
return [" ".join(line_parts)]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _render_command(cmd: SpecCommand) -> list[str]:
|
|
165
|
+
line_parts = [f"cmd {_escape_kdl_string(cmd.name)}"]
|
|
166
|
+
|
|
167
|
+
if cmd.hide:
|
|
168
|
+
line_parts.append(f"hide={_format_bool(True)}")
|
|
169
|
+
if cmd.subcommand_required:
|
|
170
|
+
line_parts.append("subcommand_required=#true")
|
|
171
|
+
if cmd.help:
|
|
172
|
+
line_parts.append(f"help={_escape_kdl_string(cmd.help)}")
|
|
173
|
+
if cmd.deprecated:
|
|
174
|
+
line_parts.append(f"deprecated={_escape_kdl_string(cmd.deprecated)}")
|
|
175
|
+
|
|
176
|
+
# Check for children
|
|
177
|
+
children: list[str] = []
|
|
178
|
+
|
|
179
|
+
if cmd.aliases:
|
|
180
|
+
parts = " ".join(_escape_kdl_string(a) for a in cmd.aliases)
|
|
181
|
+
children.append(f"alias {parts}")
|
|
182
|
+
|
|
183
|
+
if cmd.help_long:
|
|
184
|
+
children.append(f"long_help {_escape_kdl_string(cmd.help_long)}")
|
|
185
|
+
|
|
186
|
+
for flag in cmd.flags:
|
|
187
|
+
children.extend(_render_flag(flag))
|
|
188
|
+
|
|
189
|
+
for arg in cmd.args:
|
|
190
|
+
children.extend(_render_arg(arg))
|
|
191
|
+
|
|
192
|
+
for sub in cmd.cmds:
|
|
193
|
+
children.extend(_render_command(sub))
|
|
194
|
+
|
|
195
|
+
if children:
|
|
196
|
+
indented = _indent(children, 1)
|
|
197
|
+
return [" ".join(line_parts) + " {", *indented, "}"]
|
|
198
|
+
else:
|
|
199
|
+
return [" ".join(line_parts)]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def render_kdl(spec: Spec) -> str:
|
|
203
|
+
"""Render a Spec to KDL format string."""
|
|
204
|
+
lines: list[str] = []
|
|
205
|
+
|
|
206
|
+
if spec.name:
|
|
207
|
+
lines.append(f"name {_escape_kdl_string(spec.name)}")
|
|
208
|
+
if spec.bin:
|
|
209
|
+
lines.append(f"bin {_escape_kdl_string(spec.bin)}")
|
|
210
|
+
if spec.version:
|
|
211
|
+
lines.append(f"version {_escape_kdl_string(spec.version)}")
|
|
212
|
+
if spec.about:
|
|
213
|
+
lines.append(f"about {_escape_kdl_string(spec.about)}")
|
|
214
|
+
if spec.long:
|
|
215
|
+
lines.append(f"long_about {_escape_kdl_string(spec.long)}")
|
|
216
|
+
if spec.usage:
|
|
217
|
+
lines.append(f"usage {_escape_kdl_string(spec.usage)}")
|
|
218
|
+
|
|
219
|
+
for flag in spec.flags:
|
|
220
|
+
lines.extend(_render_flag(flag))
|
|
221
|
+
|
|
222
|
+
for arg in spec.args:
|
|
223
|
+
lines.extend(_render_arg(arg))
|
|
224
|
+
|
|
225
|
+
for cmd in spec.cmds:
|
|
226
|
+
lines.extend(_render_command(cmd))
|
|
227
|
+
|
|
228
|
+
return "\n".join(lines) + "\n"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def validate_kdl(kdl: str) -> None:
|
|
232
|
+
"""Validate KDL output by attempting a basic structural parse.
|
|
233
|
+
|
|
234
|
+
This is a simplified validator - for full validation, use the
|
|
235
|
+
@bgotink/kdl parser via the TypeScript @usage-spec/core package.
|
|
236
|
+
"""
|
|
237
|
+
if not kdl.strip():
|
|
238
|
+
return
|
|
239
|
+
# Basic bracket matching for child blocks
|
|
240
|
+
depth = 0
|
|
241
|
+
for line in kdl.split("\n"):
|
|
242
|
+
stripped = line.strip()
|
|
243
|
+
if not stripped or stripped.startswith("//"):
|
|
244
|
+
continue
|
|
245
|
+
depth += stripped.count("{") - stripped.count("}")
|
|
246
|
+
if depth != 0:
|
|
247
|
+
raise ValueError(f"Unbalanced braces in KDL output (depth={depth})")
|
usage_spec/spec.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Usage spec type definitions, aligned with @usage-spec/core TypeScript types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SpecChoices:
|
|
10
|
+
values: list[str] = field(default_factory=list)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class SpecArg:
|
|
15
|
+
name: str = ""
|
|
16
|
+
help: str = ""
|
|
17
|
+
required: bool = True
|
|
18
|
+
var: bool = False
|
|
19
|
+
hide: bool = False
|
|
20
|
+
default: list[str] = field(default_factory=list)
|
|
21
|
+
choices: SpecChoices | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class SpecFlag:
|
|
26
|
+
short: str = ""
|
|
27
|
+
long: str = ""
|
|
28
|
+
help: str = ""
|
|
29
|
+
help_long: str = ""
|
|
30
|
+
required: bool = False
|
|
31
|
+
hide: bool = False
|
|
32
|
+
global_: bool = False
|
|
33
|
+
count: bool = False
|
|
34
|
+
var: bool = False
|
|
35
|
+
negate: str = ""
|
|
36
|
+
deprecated: str = ""
|
|
37
|
+
default: list[str] = field(default_factory=list)
|
|
38
|
+
default_bool: bool | None = None
|
|
39
|
+
env: str = ""
|
|
40
|
+
arg: SpecArg | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class SpecCommand:
|
|
45
|
+
name: str = ""
|
|
46
|
+
help: str = ""
|
|
47
|
+
help_long: str = ""
|
|
48
|
+
hide: bool = False
|
|
49
|
+
deprecated: str = ""
|
|
50
|
+
aliases: list[str] = field(default_factory=list)
|
|
51
|
+
subcommand_required: bool = False
|
|
52
|
+
flags: list[SpecFlag] = field(default_factory=list)
|
|
53
|
+
args: list[SpecArg] = field(default_factory=list)
|
|
54
|
+
cmds: list[SpecCommand] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Spec:
|
|
59
|
+
name: str = ""
|
|
60
|
+
bin: str = ""
|
|
61
|
+
version: str = ""
|
|
62
|
+
about: str = ""
|
|
63
|
+
long: str = ""
|
|
64
|
+
usage: str = ""
|
|
65
|
+
flags: list[SpecFlag] = field(default_factory=list)
|
|
66
|
+
args: list[SpecArg] = field(default_factory=list)
|
|
67
|
+
cmds: list[SpecCommand] = field(default_factory=list)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: usage-spec
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Core types and rendering for usage spec
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: cli,kdl,spec,usage
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# usage-spec
|
|
21
|
+
|
|
22
|
+
Core types and rendering for [usage spec](https://usage.jdx.dev).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
pip install usage-spec
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
### Types
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
@dataclass
|
|
36
|
+
class Spec:
|
|
37
|
+
name: str
|
|
38
|
+
bin: str
|
|
39
|
+
version: str
|
|
40
|
+
about: str
|
|
41
|
+
long: str
|
|
42
|
+
usage: str
|
|
43
|
+
flags: list[SpecFlag]
|
|
44
|
+
args: list[SpecArg]
|
|
45
|
+
cmds: list[SpecCommand]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Functions
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from usage_spec import generate, render_kdl, render_json, validate_kdl
|
|
52
|
+
|
|
53
|
+
# Render Spec as KDL string
|
|
54
|
+
render_kdl(spec: Spec) -> str
|
|
55
|
+
|
|
56
|
+
# Render Spec as JSON string
|
|
57
|
+
render_json(spec: Spec) -> str
|
|
58
|
+
|
|
59
|
+
# Generate spec output with optional format and comment
|
|
60
|
+
generate(spec: Spec, format: str = "kdl", comment: str | None = None) -> str
|
|
61
|
+
|
|
62
|
+
# Validate KDL string by basic structural check
|
|
63
|
+
validate_kdl(kdl: str) -> None
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
usage_spec/__init__.py,sha256=8lxB83JURrWanWgMs8tpBqs3ZTXWBBSfMZX8dhHIVJM,705
|
|
2
|
+
usage_spec/json.py,sha256=P52RghAuK72fOi8G12FcyaouW05fUMrABPA5-Qn7UGI,3293
|
|
3
|
+
usage_spec/kdl.py,sha256=tvQgiReAq3zl3pIxN7mVpIF7avUp6UlnK8aATx9CTxc,8032
|
|
4
|
+
usage_spec/spec.py,sha256=mVZQBLPtWDuteOTFWY8oJeIkq3DZHd2HoIP-bq6jzS0,1604
|
|
5
|
+
usage_spec-1.0.0.dist-info/METADATA,sha256=lv_yp0Ck38YjP55zIOEF58f4ZS1ySS4Ppsx7hjxxR3k,1492
|
|
6
|
+
usage_spec-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
usage_spec-1.0.0.dist-info/RECORD,,
|