ry-tool 1.0.1__tar.gz
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.
- ry_tool-1.0.1/PKG-INFO +112 -0
- ry_tool-1.0.1/README.md +102 -0
- ry_tool-1.0.1/pyproject.toml +19 -0
- ry_tool-1.0.1/src/ry_tool/__init__.py +27 -0
- ry_tool-1.0.1/src/ry_tool/__main__.py +9 -0
- ry_tool-1.0.1/src/ry_tool/_cli.py +244 -0
- ry_tool-1.0.1/src/ry_tool/app.py +420 -0
- ry_tool-1.0.1/src/ry_tool/context.py +297 -0
- ry_tool-1.0.1/src/ry_tool/executor.py +475 -0
- ry_tool-1.0.1/src/ry_tool/installer.py +176 -0
- ry_tool-1.0.1/src/ry_tool/loader.py +280 -0
- ry_tool-1.0.1/src/ry_tool/matcher.py +233 -0
- ry_tool-1.0.1/src/ry_tool/parser.py +248 -0
- ry_tool-1.0.1/src/ry_tool/template.py +306 -0
- ry_tool-1.0.1/src/ry_tool/utils.py +396 -0
ry_tool-1.0.1/PKG-INFO
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: ry-tool
|
3
|
+
Version: 1.0.1
|
4
|
+
Summary: Pure YAML command orchestrator - CI/CD for humans
|
5
|
+
Author: Fredrik Angelsen
|
6
|
+
Author-email: Fredrik Angelsen <fredrikangelsen@gmail.com>
|
7
|
+
Requires-Dist: pyyaml>=6.0
|
8
|
+
Requires-Python: >=3.12
|
9
|
+
Description-Content-Type: text/markdown
|
10
|
+
|
11
|
+
# ry-next
|
12
|
+
|
13
|
+
A clean, modular command augmentation framework that enhances existing CLI tools without breaking their native behavior.
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
- **Command Augmentation**: Wrap and enhance existing CLI tools
|
18
|
+
- **Clean Architecture**: Modular design with single-responsibility components
|
19
|
+
- **Type-Safe Processing**: Recursive template processing with type dispatch
|
20
|
+
- **Token-Based Safety**: Time-limited tokens for dangerous operations
|
21
|
+
- **Library System**: Reusable command definitions with metadata
|
22
|
+
- **No Shell Escaping**: Direct subprocess execution for safety
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
```bash
|
27
|
+
pip install -e .
|
28
|
+
```
|
29
|
+
|
30
|
+
This installs the `ry-next` command globally.
|
31
|
+
|
32
|
+
## Quick Start
|
33
|
+
|
34
|
+
```bash
|
35
|
+
# List available libraries
|
36
|
+
ry-next --list
|
37
|
+
|
38
|
+
# Get help for a library
|
39
|
+
ry-next git --ry-help
|
40
|
+
|
41
|
+
# Execute augmented command
|
42
|
+
ry-next git commit -m "feat: new feature"
|
43
|
+
|
44
|
+
# Show execution plan (dry run)
|
45
|
+
ry-next --ry-run git commit -m "test"
|
46
|
+
```
|
47
|
+
|
48
|
+
## Production Libraries
|
49
|
+
|
50
|
+
- **git** - Enhanced git workflow with review tokens and commit validation
|
51
|
+
- **uv** - Python package management with automated version workflows
|
52
|
+
- **changelog** - Simple changelog management following Keep a Changelog
|
53
|
+
- **ry-lib** - Library development and management tools
|
54
|
+
|
55
|
+
## Documentation
|
56
|
+
|
57
|
+
- [Full Documentation](docs/README_RYNEXT.md)
|
58
|
+
- [Library Development](docs/libraries/ry-lib/README.md)
|
59
|
+
- [Examples](examples/README.md)
|
60
|
+
|
61
|
+
## Project Structure
|
62
|
+
|
63
|
+
```
|
64
|
+
ry-next/
|
65
|
+
├── src/ry_next/ # Core implementation
|
66
|
+
├── docs/
|
67
|
+
│ ├── libraries/ # Production libraries
|
68
|
+
│ └── README_RYNEXT.md # Full documentation
|
69
|
+
├── examples/ # Example libraries
|
70
|
+
└── _archive/ # Old ry-tool code (deprecated)
|
71
|
+
```
|
72
|
+
|
73
|
+
## Key Concepts
|
74
|
+
|
75
|
+
### Library Format (v2.0)
|
76
|
+
|
77
|
+
```yaml
|
78
|
+
version: "2.0"
|
79
|
+
name: git
|
80
|
+
type: augmentation
|
81
|
+
target: /usr/bin/git
|
82
|
+
|
83
|
+
commands:
|
84
|
+
commit:
|
85
|
+
flags:
|
86
|
+
m/message: string
|
87
|
+
augment:
|
88
|
+
before:
|
89
|
+
- python: |
|
90
|
+
# Validation logic
|
91
|
+
relay: native
|
92
|
+
```
|
93
|
+
|
94
|
+
### Token-Based Safety
|
95
|
+
|
96
|
+
Critical operations require preview and token verification:
|
97
|
+
|
98
|
+
```bash
|
99
|
+
# Preview changes
|
100
|
+
git diff --staged # → Generates REVIEW_TOKEN
|
101
|
+
|
102
|
+
# Execute with token
|
103
|
+
REVIEW_TOKEN=xxx git commit -m "message"
|
104
|
+
```
|
105
|
+
|
106
|
+
## Development
|
107
|
+
|
108
|
+
See [docs/README_RYNEXT.md](docs/README_RYNEXT.md) for complete documentation.
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
MIT
|
ry_tool-1.0.1/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# ry-next
|
2
|
+
|
3
|
+
A clean, modular command augmentation framework that enhances existing CLI tools without breaking their native behavior.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **Command Augmentation**: Wrap and enhance existing CLI tools
|
8
|
+
- **Clean Architecture**: Modular design with single-responsibility components
|
9
|
+
- **Type-Safe Processing**: Recursive template processing with type dispatch
|
10
|
+
- **Token-Based Safety**: Time-limited tokens for dangerous operations
|
11
|
+
- **Library System**: Reusable command definitions with metadata
|
12
|
+
- **No Shell Escaping**: Direct subprocess execution for safety
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
```bash
|
17
|
+
pip install -e .
|
18
|
+
```
|
19
|
+
|
20
|
+
This installs the `ry-next` command globally.
|
21
|
+
|
22
|
+
## Quick Start
|
23
|
+
|
24
|
+
```bash
|
25
|
+
# List available libraries
|
26
|
+
ry-next --list
|
27
|
+
|
28
|
+
# Get help for a library
|
29
|
+
ry-next git --ry-help
|
30
|
+
|
31
|
+
# Execute augmented command
|
32
|
+
ry-next git commit -m "feat: new feature"
|
33
|
+
|
34
|
+
# Show execution plan (dry run)
|
35
|
+
ry-next --ry-run git commit -m "test"
|
36
|
+
```
|
37
|
+
|
38
|
+
## Production Libraries
|
39
|
+
|
40
|
+
- **git** - Enhanced git workflow with review tokens and commit validation
|
41
|
+
- **uv** - Python package management with automated version workflows
|
42
|
+
- **changelog** - Simple changelog management following Keep a Changelog
|
43
|
+
- **ry-lib** - Library development and management tools
|
44
|
+
|
45
|
+
## Documentation
|
46
|
+
|
47
|
+
- [Full Documentation](docs/README_RYNEXT.md)
|
48
|
+
- [Library Development](docs/libraries/ry-lib/README.md)
|
49
|
+
- [Examples](examples/README.md)
|
50
|
+
|
51
|
+
## Project Structure
|
52
|
+
|
53
|
+
```
|
54
|
+
ry-next/
|
55
|
+
├── src/ry_next/ # Core implementation
|
56
|
+
├── docs/
|
57
|
+
│ ├── libraries/ # Production libraries
|
58
|
+
│ └── README_RYNEXT.md # Full documentation
|
59
|
+
├── examples/ # Example libraries
|
60
|
+
└── _archive/ # Old ry-tool code (deprecated)
|
61
|
+
```
|
62
|
+
|
63
|
+
## Key Concepts
|
64
|
+
|
65
|
+
### Library Format (v2.0)
|
66
|
+
|
67
|
+
```yaml
|
68
|
+
version: "2.0"
|
69
|
+
name: git
|
70
|
+
type: augmentation
|
71
|
+
target: /usr/bin/git
|
72
|
+
|
73
|
+
commands:
|
74
|
+
commit:
|
75
|
+
flags:
|
76
|
+
m/message: string
|
77
|
+
augment:
|
78
|
+
before:
|
79
|
+
- python: |
|
80
|
+
# Validation logic
|
81
|
+
relay: native
|
82
|
+
```
|
83
|
+
|
84
|
+
### Token-Based Safety
|
85
|
+
|
86
|
+
Critical operations require preview and token verification:
|
87
|
+
|
88
|
+
```bash
|
89
|
+
# Preview changes
|
90
|
+
git diff --staged # → Generates REVIEW_TOKEN
|
91
|
+
|
92
|
+
# Execute with token
|
93
|
+
REVIEW_TOKEN=xxx git commit -m "message"
|
94
|
+
```
|
95
|
+
|
96
|
+
## Development
|
97
|
+
|
98
|
+
See [docs/README_RYNEXT.md](docs/README_RYNEXT.md) for complete documentation.
|
99
|
+
|
100
|
+
## License
|
101
|
+
|
102
|
+
MIT
|
@@ -0,0 +1,19 @@
|
|
1
|
+
[project]
|
2
|
+
name = "ry-tool"
|
3
|
+
version = "1.0.1"
|
4
|
+
description = "Pure YAML command orchestrator - CI/CD for humans"
|
5
|
+
readme = "README.md"
|
6
|
+
authors = [
|
7
|
+
{ name = "Fredrik Angelsen", email = "fredrikangelsen@gmail.com" }
|
8
|
+
]
|
9
|
+
requires-python = ">=3.12"
|
10
|
+
dependencies = [
|
11
|
+
"pyyaml>=6.0",
|
12
|
+
]
|
13
|
+
|
14
|
+
[project.scripts]
|
15
|
+
ry-next = "ry_tool.app:run"
|
16
|
+
|
17
|
+
[build-system]
|
18
|
+
requires = ["uv_build>=0.8.13,<0.9.0"]
|
19
|
+
build-backend = "uv_build"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
"""
|
2
|
+
ry-next: Next generation command augmentation framework.
|
3
|
+
|
4
|
+
Clean architecture with semantic command understanding.
|
5
|
+
"""
|
6
|
+
|
7
|
+
__version__ = "2.0.0-alpha"
|
8
|
+
|
9
|
+
from .parser import CommandParser, ParsedCommand
|
10
|
+
from .executor import Executor, ExecutionResult
|
11
|
+
from .context import ExecutionContext
|
12
|
+
from .template import TemplateProcessor
|
13
|
+
from .loader import LibraryLoader, LibraryConfig
|
14
|
+
from .matcher import CommandMatcher, MatchResult
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
'CommandParser',
|
18
|
+
'ParsedCommand',
|
19
|
+
'Executor',
|
20
|
+
'ExecutionResult',
|
21
|
+
'ExecutionContext',
|
22
|
+
'TemplateProcessor',
|
23
|
+
'LibraryLoader',
|
24
|
+
'LibraryConfig',
|
25
|
+
'CommandMatcher',
|
26
|
+
'MatchResult',
|
27
|
+
]
|
@@ -0,0 +1,244 @@
|
|
1
|
+
"""
|
2
|
+
Lightweight CLI framework for ry.
|
3
|
+
A mini framework tailored for ry's command parsing needs.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
from typing import Dict, List, Callable, Optional
|
8
|
+
from dataclasses import dataclass
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class Command:
|
13
|
+
"""Represents a CLI command."""
|
14
|
+
|
15
|
+
name: str
|
16
|
+
handler: Callable
|
17
|
+
help: str
|
18
|
+
requires_arg: bool = False
|
19
|
+
arg_name: str = "arg"
|
20
|
+
arg_help: str = ""
|
21
|
+
|
22
|
+
|
23
|
+
class CLI:
|
24
|
+
"""Lightweight CLI framework for ry."""
|
25
|
+
|
26
|
+
def __init__(self, name: str = "ry", description: str = ""):
|
27
|
+
self.name = name
|
28
|
+
self.description = description
|
29
|
+
self.commands: Dict[str, Command] = {}
|
30
|
+
self.default_handler: Optional[Callable] = None
|
31
|
+
self.global_flags: Dict[str, bool] = {} # Track global flags
|
32
|
+
|
33
|
+
def command(
|
34
|
+
self,
|
35
|
+
name: str,
|
36
|
+
help: str = "",
|
37
|
+
requires_arg: bool = False,
|
38
|
+
arg_name: str = "arg",
|
39
|
+
arg_help: str = "",
|
40
|
+
):
|
41
|
+
"""Decorator to register a command."""
|
42
|
+
|
43
|
+
def decorator(func: Callable):
|
44
|
+
self.commands[name] = Command(
|
45
|
+
name=name,
|
46
|
+
handler=func,
|
47
|
+
help=help,
|
48
|
+
requires_arg=requires_arg,
|
49
|
+
arg_name=arg_name,
|
50
|
+
arg_help=arg_help,
|
51
|
+
)
|
52
|
+
return func
|
53
|
+
|
54
|
+
return decorator
|
55
|
+
|
56
|
+
def default(self, func: Callable):
|
57
|
+
"""Decorator to register the default handler for non-command arguments."""
|
58
|
+
self.default_handler = func
|
59
|
+
return func
|
60
|
+
|
61
|
+
def run(self, argv: Optional[List[str]] = None):
|
62
|
+
"""Parse arguments and run the appropriate command."""
|
63
|
+
if argv is None:
|
64
|
+
argv = sys.argv
|
65
|
+
|
66
|
+
# No arguments - show help
|
67
|
+
if len(argv) < 2:
|
68
|
+
self.show_help()
|
69
|
+
sys.exit(0)
|
70
|
+
|
71
|
+
# Parse global flags first
|
72
|
+
self.global_flags = {}
|
73
|
+
filtered_argv = [argv[0]]
|
74
|
+
|
75
|
+
for arg in argv[1:]:
|
76
|
+
if arg == '--ry-run':
|
77
|
+
self.global_flags['ry_run'] = True
|
78
|
+
else:
|
79
|
+
filtered_argv.append(arg)
|
80
|
+
|
81
|
+
# If only global flags were provided, show help
|
82
|
+
if len(filtered_argv) < 2:
|
83
|
+
self.show_help()
|
84
|
+
sys.exit(0)
|
85
|
+
|
86
|
+
first_arg = filtered_argv[1]
|
87
|
+
remaining_args = filtered_argv[2:] if len(filtered_argv) > 2 else []
|
88
|
+
|
89
|
+
# Check for help
|
90
|
+
if first_arg in ["-h", "--help"]:
|
91
|
+
self.show_help()
|
92
|
+
sys.exit(0)
|
93
|
+
|
94
|
+
# Check if it's a registered command
|
95
|
+
if first_arg in self.commands:
|
96
|
+
cmd = self.commands[first_arg]
|
97
|
+
|
98
|
+
# Check if command requires an argument
|
99
|
+
if cmd.requires_arg and not remaining_args:
|
100
|
+
print(f"Error: {first_arg} requires an argument", file=sys.stderr)
|
101
|
+
print(
|
102
|
+
f"Usage: {self.name} {first_arg} <{cmd.arg_name}>", file=sys.stderr
|
103
|
+
)
|
104
|
+
sys.exit(1)
|
105
|
+
|
106
|
+
# Call the handler
|
107
|
+
try:
|
108
|
+
if cmd.requires_arg:
|
109
|
+
result = cmd.handler(remaining_args[0], *remaining_args[1:])
|
110
|
+
else:
|
111
|
+
result = cmd.handler(*remaining_args)
|
112
|
+
|
113
|
+
# Handle result
|
114
|
+
if isinstance(result, bool):
|
115
|
+
sys.exit(0 if result else 1)
|
116
|
+
elif isinstance(result, int):
|
117
|
+
sys.exit(result)
|
118
|
+
else:
|
119
|
+
sys.exit(0)
|
120
|
+
except KeyboardInterrupt:
|
121
|
+
sys.exit(130)
|
122
|
+
except Exception as e:
|
123
|
+
print(f"Error: {e}", file=sys.stderr)
|
124
|
+
sys.exit(1)
|
125
|
+
|
126
|
+
# Not a command - try default handler
|
127
|
+
elif self.default_handler:
|
128
|
+
try:
|
129
|
+
result = self.default_handler(first_arg, *remaining_args)
|
130
|
+
if isinstance(result, bool):
|
131
|
+
sys.exit(0 if result else 1)
|
132
|
+
elif isinstance(result, int):
|
133
|
+
sys.exit(result)
|
134
|
+
else:
|
135
|
+
sys.exit(0)
|
136
|
+
except KeyboardInterrupt:
|
137
|
+
sys.exit(130)
|
138
|
+
except Exception as e:
|
139
|
+
print(f"Error: {e}", file=sys.stderr)
|
140
|
+
sys.exit(1)
|
141
|
+
|
142
|
+
else:
|
143
|
+
print(f"Unknown command: {first_arg}", file=sys.stderr)
|
144
|
+
print(f"Try: {self.name} --help", file=sys.stderr)
|
145
|
+
sys.exit(1)
|
146
|
+
|
147
|
+
def show_help(self):
|
148
|
+
"""Display auto-generated help message."""
|
149
|
+
lines = []
|
150
|
+
|
151
|
+
# Header
|
152
|
+
lines.append(f"{self.name} - {self.description}")
|
153
|
+
lines.append("")
|
154
|
+
|
155
|
+
# Usage
|
156
|
+
lines.append("Usage:")
|
157
|
+
if self.default_handler:
|
158
|
+
lines.append(
|
159
|
+
f" {self.name} <library> [args...] Execute library command"
|
160
|
+
)
|
161
|
+
lines.append(
|
162
|
+
f" {self.name} <file.yaml> [args...] Execute from YAML file"
|
163
|
+
)
|
164
|
+
lines.append(
|
165
|
+
f" {self.name} --ry-run <library> [args...] Show execution plan"
|
166
|
+
)
|
167
|
+
lines.append("")
|
168
|
+
|
169
|
+
# Group commands by type
|
170
|
+
user_commands = {}
|
171
|
+
dev_commands = {}
|
172
|
+
|
173
|
+
for name, cmd in sorted(self.commands.items()):
|
174
|
+
if name.startswith("--dev-"):
|
175
|
+
dev_commands[name] = cmd
|
176
|
+
else:
|
177
|
+
user_commands[name] = cmd
|
178
|
+
|
179
|
+
# User commands
|
180
|
+
if user_commands:
|
181
|
+
lines.append("Package Management:")
|
182
|
+
for name, cmd in user_commands.items():
|
183
|
+
# Format command line
|
184
|
+
if cmd.requires_arg:
|
185
|
+
usage = f"{self.name} {name} <{cmd.arg_name}>"
|
186
|
+
else:
|
187
|
+
usage = f"{self.name} {name}"
|
188
|
+
# Align help text
|
189
|
+
lines.append(f" {usage:<40} {cmd.help}")
|
190
|
+
lines.append("")
|
191
|
+
|
192
|
+
# Developer commands
|
193
|
+
if dev_commands:
|
194
|
+
lines.append("Developer Commands:")
|
195
|
+
for name, cmd in dev_commands.items():
|
196
|
+
if cmd.requires_arg:
|
197
|
+
usage = f"{self.name} {name} <{cmd.arg_name}>"
|
198
|
+
else:
|
199
|
+
usage = f"{self.name} {name}"
|
200
|
+
lines.append(f" {usage:<40} {cmd.help}")
|
201
|
+
lines.append("")
|
202
|
+
|
203
|
+
# Examples
|
204
|
+
lines.append("Examples:")
|
205
|
+
lines.append(f" {self.name} hello.yaml world --name Alice")
|
206
|
+
lines.append(f" {self.name} git commit -m 'feat: add feature'")
|
207
|
+
lines.append(f" {self.name} --ry-run deploy.yaml production")
|
208
|
+
lines.append(f" {self.name} --list")
|
209
|
+
|
210
|
+
print("\n".join(lines))
|
211
|
+
|
212
|
+
|
213
|
+
# Utility decorators for common CLI patterns
|
214
|
+
def requires_git_repo(func):
|
215
|
+
"""Decorator to ensure command runs in a git repository."""
|
216
|
+
from functools import wraps
|
217
|
+
|
218
|
+
@wraps(func)
|
219
|
+
def wrapper(*args, **kwargs):
|
220
|
+
import subprocess
|
221
|
+
try:
|
222
|
+
subprocess.run(['git', 'rev-parse', '--git-dir'],
|
223
|
+
capture_output=True, check=True)
|
224
|
+
return func(*args, **kwargs)
|
225
|
+
except subprocess.CalledProcessError:
|
226
|
+
print("Error: Not in a git repository", file=sys.stderr)
|
227
|
+
return 1
|
228
|
+
return wrapper
|
229
|
+
|
230
|
+
|
231
|
+
def requires_file(file_path: str):
|
232
|
+
"""Decorator to ensure required file exists."""
|
233
|
+
def decorator(func):
|
234
|
+
from functools import wraps
|
235
|
+
|
236
|
+
@wraps(func)
|
237
|
+
def wrapper(*args, **kwargs):
|
238
|
+
from pathlib import Path
|
239
|
+
if not Path(file_path).exists():
|
240
|
+
print(f"Error: Required file not found: {file_path}", file=sys.stderr)
|
241
|
+
return 1
|
242
|
+
return func(*args, **kwargs)
|
243
|
+
return wrapper
|
244
|
+
return decorator
|