Skip to content

Commit b8cd25c

Browse files
Merge pull request #65 from wack/cli-redesign
Implement "Up and Running" CLI redesign with new core commands
2 parents d980f63 + b2df968 commit b8cd25c

30 files changed

Lines changed: 3792 additions & 1529 deletions

File tree

TODO.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
* Refactor src/cli/compile/mod.rs::Compile::dispatch
2+
to be more modular.
3+
4+
* Refactor src/cli/history/mod.rs::History::dispatch
5+
to be more modular.

src/cli/CLAUDE.md

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,31 @@ Each subcommand of `tern` (e.g., `tern verify`) is organized as follows:
1616
}
1717
```
1818

19-
3. **Enum Variant**: Reference the struct as a tuple variant in `CliCommand` (in `src/cli/mod.rs`):
19+
3. **Dispatch Method**: Each arguments struct must implement a `dispatch()` method that contains the full command implementation:
20+
```rust
21+
impl Verify {
22+
/// Dispatch the verify command.
23+
pub async fn dispatch(self) -> miette::Result<()> {
24+
// Full implementation here using self.* fields
25+
let backend = load_backend(self.path.as_deref());
26+
ensure_backend_initialized(&backend).await?;
27+
28+
// ... rest of implementation
29+
30+
Ok(())
31+
}
32+
}
33+
```
34+
35+
4. **Helper Functions**: Keep reusable helper functions (like `confirm_destructive_changes()`, `find_migration()`, `parse_migration_id()`) as separate freestanding functions within the same module:
36+
```rust
37+
/// Helper function for user confirmation.
38+
fn confirm_destructive_changes() -> miette::Result<bool> {
39+
// ...
40+
}
41+
```
42+
43+
5. **Enum Variant**: Reference the struct as a tuple variant in `CliCommand` (in `src/cli/mod.rs`):
2044
```rust
2145
#[derive(Debug, Subcommand, Clone)]
2246
pub enum CliCommand {
@@ -26,14 +50,23 @@ Each subcommand of `tern` (e.g., `tern verify`) is organized as follows:
2650
}
2751
```
2852

29-
4. **Dispatch**: Handle the tuple variant in the `dispatch` method:
53+
6. **Enum Dispatch**: The `CliCommand::dispatch` method simply delegates to the wrapped type's `dispatch()` method:
3054
```rust
31-
CliCommand::Verify(args) => {
32-
verify::run_verify(&args.database_url, args.format).await
55+
impl CliCommand {
56+
pub async fn dispatch(self) -> miette::Result<()> {
57+
match self {
58+
CliCommand::Verify(args) => args.dispatch().await,
59+
// ...
60+
}
61+
}
3362
}
3463
```
3564

36-
This pattern keeps command definitions close to their implementations and makes the `CliCommand` enum more concise.
65+
This pattern:
66+
- Keeps command definitions close to their implementations
67+
- Makes the `CliCommand` enum dispatch trivial (just delegation)
68+
- Ensures no business logic lives in `src/cli/mod.rs`
69+
- Makes each command self-contained and testable
3770

3871
## Nested Subcommands
3972

@@ -47,7 +80,7 @@ For nested subcommands (e.g., `tern schema export`), the same pattern applies re
4780
src/cli/schema/migrate/ # Nested subcommand
4881
```
4982

50-
2. **Arguments Struct**: Each nested subcommand has its own struct in its `mod.rs`:
83+
2. **Arguments Struct with Dispatch**: Each nested subcommand has its own struct with a `dispatch()` method containing the full implementation:
5184
```rust
5285
// src/cli/schema/export/mod.rs
5386
#[derive(Debug, Clone, clap::Args)]
@@ -56,6 +89,18 @@ For nested subcommands (e.g., `tern schema export`), the same pattern applies re
5689
pub output: Option<PathBuf>,
5790
// ...
5891
}
92+
93+
impl Export {
94+
/// Dispatch the schema export command.
95+
pub async fn dispatch(self) -> miette::Result<()> {
96+
let backend = load_backend(self.path.as_deref());
97+
ensure_backend_initialized(&backend).await?;
98+
99+
// Full implementation using self.output, self.path, etc.
100+
101+
Ok(())
102+
}
103+
}
59104
```
60105

61106
3. **Parent Enum**: The parent command defines a subcommand enum using tuple variants:
@@ -70,16 +115,69 @@ For nested subcommands (e.g., `tern schema export`), the same pattern applies re
70115
}
71116
```
72117

73-
4. **Dispatch**: The parent enum's dispatch method delegates to the nested commands:
118+
4. **Parent Dispatch**: The parent enum's dispatch method delegates to each nested command's `dispatch()`:
74119
```rust
75120
impl SchemaAction {
76121
pub async fn dispatch(self) -> miette::Result<()> {
77122
match self {
78-
SchemaAction::Export(args) => {
79-
schema::run_schema_export(args.output, args.path.as_deref(), args.format).await
80-
}
123+
SchemaAction::Export(args) => args.dispatch().await,
124+
SchemaAction::Diff(args) => args.dispatch().await,
81125
// ...
82126
}
83127
}
84128
}
85129
```
130+
131+
## Deprecated Commands
132+
133+
For deprecated commands, include the deprecation warning at the start of the `dispatch()` method:
134+
135+
```rust
136+
impl Compile {
137+
/// Dispatch the compile command.
138+
pub async fn dispatch(self) -> miette::Result<()> {
139+
anstream::eprintln!(
140+
"WARNING: 'compile' is deprecated. Use 'tern import' + 'tern build' instead."
141+
);
142+
143+
// Full implementation follows...
144+
let backend = load_backend(self.path.as_deref());
145+
// ...
146+
}
147+
}
148+
```
149+
150+
Also mark the command as hidden in the enum:
151+
```rust
152+
#[derive(Debug, Subcommand, Clone)]
153+
pub enum CliCommand {
154+
/// [DEPRECATED] Compile a migration to source code
155+
#[command(hide = true)]
156+
Compile(compile::Compile),
157+
// ...
158+
}
159+
```
160+
161+
## Testing Commands
162+
163+
When writing tests for commands, use the struct-based approach rather than calling helper functions:
164+
165+
```rust
166+
#[tokio::test]
167+
async fn test_show_migration() {
168+
let temp_dir = TempDir::new().unwrap();
169+
// ... setup ...
170+
171+
let show = Show {
172+
migration_id: baseline_id,
173+
format: OutputFormat::Text,
174+
path: Some(temp_dir.path().to_path_buf()),
175+
};
176+
show.dispatch().await.unwrap();
177+
}
178+
```
179+
180+
This approach:
181+
- Tests the actual command interface users will use
182+
- Ensures argument parsing and dispatch work together
183+
- Makes tests more representative of real usage

0 commit comments

Comments
 (0)