Skip to content

Commit eeaeee3

Browse files
ST0008: orthogonalising output styling and formatting
1 parent 0566138 commit eeaeee3

File tree

12 files changed

+1410
-2
lines changed

12 files changed

+1410
-2
lines changed
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ verblock: "20 Mar 2025:v0.1: Matthew Sinclair - Updated via STP upgrade"
33
stp_version: 1.0.0
44
status: Completed
55
created: 20250319
6-
completed: 20250319
6+
completed: 20250922
77
---
88
# ST0003: REPL Output Callback System
99

@@ -39,22 +39,26 @@ We need to create a generic callback system within Arca.Cli that enables externa
3939
## Implementation Notes
4040

4141
The callback system was implemented with a chain of responsibility pattern, where:
42+
4243
- Multiple callbacks can be registered for a specific event
4344
- Callbacks are executed in reverse registration order (last registered, first executed)
4445
- Each callback can decide to continue the chain or halt with a specific result
4546
- The system falls back to default behavior if no callbacks are registered
4647

4748
For the REPL output formatting, the implementation:
49+
4850
- Checks if any `:format_output` callbacks are registered
4951
- If yes, executes all callbacks with the output
5052
- If no, uses the original implementation
5153

5254
The callbacks module provides three main functions:
55+
5356
1. `register/2` - Registers a callback function for a specific event
5457
2. `execute/2` - Executes all callbacks for an event in reverse registration order
5558
3. `has_callbacks?/1` - Checks if any callbacks are registered for an event
5659

5760
The REPL's print function was modified to use a `with` pattern for cleaner control flow:
61+
5862
```elixir
5963
defp print(out) do
6064
with true <- Code.ensure_loaded?(Callbacks),
@@ -83,11 +87,13 @@ The following benefits have been achieved:
8387
The callback system has been fully implemented and tested, providing a flexible mechanism for output customization. This enables Multiplyer to integrate its OutputContext system with Arca.Cli's REPL while maintaining proper separation of concerns.
8488

8589
User documentation has been updated to reflect these changes:
90+
8691
1. Added callback system details to user guides
8792
2. Added integration examples for external applications
8893
3. Updated the reference guide with detailed callback API documentation
8994

9095
Remaining work includes:
96+
9197
1. Integrating with Multiplyer's OutputContext system
9298
2. Potential enhancements to provide more specific formatting events for different output types
9399

@@ -98,5 +104,5 @@ Remaining work includes:
98104
- [Callbacks Tests](/test/arca_cli/callbacks/callbacks_test.exs)
99105
- [Formatter Tests](/test/arca_cli/repl/repl_formatter_test.exs)
100106
- [Example Formatter](/test/arca_cli/callbacks/example_formatter.ex)
101-
- [Related ST0002: REPL Tab Completion Improvements](/stp/prj/st/ST0002.md)
107+
- [Related ST0002: REPL Tab Completion Improvements](/stp/prj/st/ST0002.md)
102108
- [Instructions on what to do](./ST0003_arca_cli_changes.md)

intent/st/ST0008/design.md

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# Design - ST0008: Orthogonalised formatting and outputting
2+
3+
## Approach
4+
5+
Leverage Arca.Cli's existing callback system (ST0003) to create an orthogonal output system that:
6+
7+
1. Extends callbacks beyond REPL mode to ALL command execution
8+
2. Adds a context structure for carrying structured output data
9+
3. Provides style renderers for different output modes (fancy, plain, dump)
10+
4. Maintains full backwards compatibility with existing commands
11+
12+
The system follows pure functional Elixir idioms with composable functions and pattern matching throughout.
13+
14+
## Design Decisions
15+
16+
### 1. Build on Existing Callback Infrastructure
17+
18+
- **Decision**: Extend the existing `Arca.Cli.Callbacks` module rather than creating new infrastructure
19+
- **Rationale**: Minimizes complexity, leverages proven code, maintains consistency
20+
21+
### 2. Context-Based Data Structure
22+
23+
- **Decision**: Use a `%Arca.Cli.Ctx{}` struct to carry command output
24+
- **Rationale**:
25+
- Provides clean separation between data and presentation
26+
- Enables composable pipeline processing
27+
- Follows established patterns from Multiplyer/MeetZaya
28+
29+
### 3. Semantic Output Types
30+
31+
- **Decision**: Use tagged tuples for output items (e.g., `{:success, msg}`, `{:table, rows, opts}`)
32+
- **Rationale**:
33+
- Enables pattern matching for different renderers
34+
- Self-documenting output intent
35+
- Extensible for new output types
36+
37+
### 4. Automatic Style Detection
38+
39+
- **Decision**: Auto-detect appropriate style based on environment
40+
- **Rationale**:
41+
- Plain style in tests (MIX_ENV=test)
42+
- Plain style for non-TTY environments
43+
- Respects NO_COLOR=1 environment variable
44+
- Fancy style for interactive terminals
45+
46+
### 5. Full Backwards Compatibility
47+
48+
- **Decision**: Support all existing command return patterns
49+
- **Rationale**:
50+
- No breaking changes for existing commands
51+
- Gradual migration path
52+
- Opt-in adoption for new features
53+
54+
## Architecture
55+
56+
### Core Components
57+
58+
```
59+
┌─────────────────────┐
60+
│ Command Handler │
61+
│ returns: Ctx|Value │
62+
└──────────┬──────────┘
63+
64+
65+
┌─────────────────────┐
66+
│ execute_command │
67+
│ checks return type │
68+
└──────────┬──────────┘
69+
70+
71+
┌────────────────────────┐
72+
│ Callbacks System │
73+
│ :format_command_result │
74+
└──────────┬─────────────┘
75+
76+
77+
┌─────────────────────┐
78+
│ Output.render │
79+
│ • determine_style │
80+
│ • apply_renderer │
81+
│ • format_output │
82+
└─────────────────────┘
83+
84+
┌────┴────┐
85+
▼ ▼
86+
┌──────────┐ ┌──────────┐
87+
│ Fancy │ │ Plain │
88+
│ Renderer │ │ Renderer │
89+
└──────────┘ └──────────┘
90+
```
91+
92+
### Module Structure
93+
94+
```elixir
95+
Arca.Cli.Ctx # Context struct and composition functions
96+
Arca.Cli.Output # Main rendering pipeline
97+
Arca.Cli.Output.FancyRenderer # Colored, formatted output
98+
Arca.Cli.Output.PlainRenderer # No ANSI codes
99+
Arca.Cli.Output.DumpRenderer # Raw data inspection
100+
```
101+
102+
### Data Flow
103+
104+
1. **Command Execution**: Handler returns either `%Ctx{}` or legacy value
105+
2. **Callback Processing**: `:format_command_result` callback checks return type
106+
3. **Context Rendering**: If Ctx, render through style pipeline
107+
4. **Style Selection**: Auto-detect or use explicit style setting
108+
5. **Output Generation**: Renderer converts structured data to strings
109+
6. **Display**: Final output sent to appropriate destination
110+
111+
### Context Structure
112+
113+
```elixir
114+
%Arca.Cli.Ctx{
115+
command: atom(), # Command being executed
116+
args: map(), # Parsed arguments
117+
options: map(), # Command options
118+
output: list(), # Structured output items
119+
errors: list(), # Error messages
120+
status: atom(), # :ok | :error | :warning
121+
cargo: map(), # Command-specific data
122+
meta: map() # Style, format, and other metadata
123+
}
124+
```
125+
126+
### Output Item Types
127+
128+
```elixir
129+
# Messages with semantic meaning
130+
{:success, message}
131+
{:error, message}
132+
{:warning, message}
133+
{:info, message}
134+
135+
# Structured data
136+
{:table, rows, headers: headers}
137+
{:list, items, title: title}
138+
{:text, content}
139+
140+
# Interactive elements (fancy mode only)
141+
{:spinner, label, func}
142+
{:progress, label, func}
143+
```
144+
145+
## Implementation Plan
146+
147+
### Phase 1: Core Infrastructure
148+
149+
1. Add `Arca.Cli.Ctx` module with composition functions
150+
2. Add `Arca.Cli.Output` module with rendering pipeline
151+
3. Implement FancyRenderer and PlainRenderer modules
152+
4. Register `:format_command_result` callback point
153+
5. Update `execute_command` to handle Ctx returns
154+
155+
### Phase 2: Global Options
156+
157+
1. Add `--style` option to global CLI options
158+
2. Add `--no-ansi` as alias for `--style plain`
159+
3. Add environment variable support (NO_COLOR)
160+
4. Update help text to document new options
161+
162+
### Phase 3: Testing & Documentation
163+
164+
1. Add comprehensive tests for all components
165+
2. Test backwards compatibility scenarios
166+
3. Create migration guide for command authors
167+
4. Add examples of context-based commands
168+
169+
### Phase 4: Validation
170+
171+
1. Migrate a sample command to use Ctx
172+
2. Verify test fixtures work correctly
173+
3. Performance testing
174+
4. Integration testing with existing apps
175+
176+
## Alternatives Considered
177+
178+
### 1. Separate Output Module per Command
179+
180+
- **Rejected**: Too much boilerplate, violates DRY principle
181+
182+
### 2. Middleware Pipeline Approach
183+
184+
- **Rejected**: Over-engineered for the use case, harder to debug
185+
186+
### 3. Direct Integration into BaseCommand
187+
188+
- **Rejected**: Would require changes to all existing commands, breaks compatibility
189+
190+
### 4. External Formatting Library
191+
192+
- **Rejected**: Adds dependency, less control over implementation
193+
194+
## Success Criteria
195+
196+
1. ✅ Existing commands continue working without changes
197+
2. ✅ New commands can return structured context
198+
3. ✅ Output automatically adapts to environment (TTY, test, etc.)
199+
4. ✅ Clean separation between data and presentation
200+
5. ✅ Easy to add new output types and renderers
201+
6. ✅ No performance degradation
202+
7. ✅ Test fixtures can verify output without ANSI codes
203+
204+
## Example Usage
205+
206+
### Legacy Command (Still Works)
207+
208+
```elixir
209+
def handle(args, settings, optimus) do
210+
"Simple string output"
211+
end
212+
```
213+
214+
### New Context-Based Command
215+
216+
```elixir
217+
def handle(args, settings, optimus) do
218+
Arca.Cli.Ctx.new(args, settings)
219+
|> process_data()
220+
|> Ctx.add_output({:success, "Operation completed"})
221+
|> Ctx.add_output({:table, rows, headers: ["Name", "Value"]})
222+
|> Ctx.complete(:ok)
223+
end
224+
```
225+
226+
### Style Control
227+
228+
```bash
229+
# Automatic style detection
230+
mix my.cli command
231+
232+
# Force plain style
233+
mix my.cli --style plain command
234+
mix my.cli --no-ansi command
235+
NO_COLOR=1 mix my.cli command
236+
237+
# Test environment (automatic plain)
238+
MIX_ENV=test mix my.cli command
239+
```

intent/st/ST0008/done.md

Whitespace-only changes.

intent/st/ST0008/impl.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Implementation - ST0008: Orthogonalised formatting and outputting
2+
3+
## Implementation
4+
5+
[Notes on implementation details, decisions, challenges, and their resolutions]
6+
7+
## Code Examples
8+
9+
[Key code snippets and examples]
10+
11+
## Technical Details
12+
13+
[Specific technical details and considerations]
14+
15+
## Challenges & Solutions
16+
17+
[Challenges encountered during implementation and how they were resolved]

intent/st/ST0008/info.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
verblock: "22 Sep 2025:v0.1: Matthew Sinclair - Initial version"
3+
intent_version: 2.2.0
4+
status: WIP
5+
created: 20250922
6+
completed:
7+
---
8+
# ST0008: Orthogonalised formatting and outputting
9+
10+
## Objective
11+
12+
Implement an orthogonal output system for Arca.Cli that cleanly separates data processing, styling, and formatting concerns while maintaining full backwards compatibility with existing commands.
13+
14+
## Context
15+
16+
Currently, Arca.Cli commands directly output formatted strings, mixing data processing with presentation logic. This creates several issues:
17+
18+
- Commands are tightly coupled to their output format
19+
- Testing is difficult due to ANSI codes and formatting in output
20+
- No consistent way to disable colors/formatting for non-TTY environments
21+
- Commands cannot easily support multiple output formats (JSON, plain text, etc.)
22+
23+
The ST0003 work already established a callback system for REPL output formatting. This steel thread extends that foundation to create a comprehensive output system that works for all commands, not just REPL mode.
24+
25+
## Related Steel Threads
26+
27+
- ST0003: REPL Output Callback System - Provides the callback infrastructure we'll leverage
28+
- ST0002: REPL Tab Completion Improvements - Related REPL functionality
29+
30+
## Context for LLM
31+
32+
This document represents a single steel thread - a self-contained unit of work focused on implementing a specific piece of functionality. When working with an LLM on this steel thread, start by sharing this document to provide context about what needs to be done.
33+
34+
### How to update this document
35+
36+
1. Update the status as work progresses
37+
2. Update related documents (design.md, impl.md, etc.) as needed
38+
3. Mark the completion date when finished
39+
40+
The LLM should assist with implementation details and help maintain this document as work progresses.

0 commit comments

Comments
 (0)