Skip to content

Commit 5fbecda

Browse files
committed
wip
1 parent edb0cbd commit 5fbecda

File tree

10 files changed

+342
-32
lines changed

10 files changed

+342
-32
lines changed

module/move/unilang/spec.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,26 @@ The unilang framework **must** provide control over debug output and verbosity l
511511
- Runtime configuration
512512
* **Thread Safety**: Verbosity control **must** be thread-safe and respect per-instance settings in multi-threaded environments.
513513

514+
#### 9.5. Help System Formatting
515+
516+
The help system **must** provide clear, readable output that is optimized for human consumption and easy scanning.
517+
518+
* **Multi-line Format**: Command help output **must** use a multi-line format that separates different types of information visually:
519+
- Command header line with name and version
520+
- Description section with proper spacing
521+
- Arguments section with clear visual hierarchy
522+
* **Argument Display**: Each argument **must** be displayed with:
523+
- Argument name on its own line or prominently displayed
524+
- Type and requirement status clearly indicated
525+
- Description text separated from technical details
526+
- Optional and multiple indicators separated from core information
527+
* **Readability Principles**: Help text **must** follow these formatting principles:
528+
- No single line should contain more than 80 characters when possible
529+
- Technical information (Kind, validation rules) should be visually distinct from user-facing descriptions
530+
- Redundant words like "Hint:" should be eliminated when the context is clear
531+
- Visual hierarchy should guide the eye from most important to least important information
532+
* **Consistent Spacing**: The help system **must** use consistent indentation and spacing to create visual groupings and improve readability.
533+
514534
---
515535
## Part II: Internal Design (Design Recommendations)
516536
*This part of the specification describes the recommended internal architecture and implementation strategies. These are best-practice starting points, and the development team has the flexibility to modify them as needed.*

module/move/unilang/src/help.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,49 @@ impl< 'a > HelpGenerator< 'a >
8383
writeln!( &mut help, "\nArguments:" ).unwrap();
8484
for arg in &command.arguments
8585
{
86-
let mut arg_info = String::new();
87-
write!( &mut arg_info, "{} (Kind: {}) - Hint: {}", arg.name, arg.kind, arg.hint ).unwrap();
88-
if arg.attributes.optional
89-
{
90-
write!( &mut arg_info, ", Optional" ).unwrap();
86+
// Improved formatting: Multi-line, clear hierarchy, eliminate redundant text
87+
88+
// Argument name on its own line
89+
write!( &mut help, "{}", arg.name ).unwrap();
90+
91+
// Type and status indicators on separate line with clear formatting
92+
write!( &mut help, " (Type: {})", arg.kind ).unwrap();
93+
94+
// Add status indicators
95+
let mut status_parts = Vec::new();
96+
if arg.attributes.optional {
97+
status_parts.push("Optional");
9198
}
92-
if arg.attributes.multiple
93-
{
94-
write!( &mut arg_info, ", Multiple" ).unwrap();
99+
if arg.attributes.multiple {
100+
status_parts.push("Multiple");
95101
}
96-
if !arg.validation_rules.is_empty()
97-
{
98-
write!( &mut arg_info, ", Rules: [{}]", arg.validation_rules.iter().map(|r| format!("{r:?}")).collect::<Vec<_>>().join( ", " ) ).unwrap();
102+
if !status_parts.is_empty() {
103+
write!( &mut help, " - {}", status_parts.join(", ") ).unwrap();
99104
}
100-
writeln!( &mut help, "{arg_info}" ).unwrap();
105+
writeln!( &mut help ).unwrap();
106+
107+
// Description and hint on separate lines with indentation for readability
108+
if !arg.description.is_empty() {
109+
writeln!( &mut help, " {}", arg.description ).unwrap();
110+
// If hint is different from description, show it too
111+
if !arg.hint.is_empty() && arg.hint != arg.description {
112+
writeln!( &mut help, " ({})", arg.hint ).unwrap();
113+
}
114+
} else if !arg.hint.is_empty() {
115+
writeln!( &mut help, " {}", arg.hint ).unwrap();
116+
}
117+
118+
// Validation rules on separate line if present
119+
if !arg.validation_rules.is_empty() {
120+
writeln!(
121+
&mut help,
122+
" Rules: [{}]",
123+
arg.validation_rules.iter().map(|r| format!("{r:?}")).collect::<Vec<_>>().join( ", " )
124+
).unwrap();
125+
}
126+
127+
// Empty line between arguments for better separation
128+
writeln!( &mut help ).unwrap();
101129
}
102130
}
103131

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
//! Tests for help system formatting improvements
2+
//!
3+
//! This module tests that help output follows improved formatting principles
4+
//! for better readability and user experience.
5+
//!
6+
//! Bug Coverage: Prevents regression where help output is cramped, hard to read,
7+
//! or contains redundant information that makes it difficult for users to quickly
8+
//! understand command usage.
9+
10+
use unilang::prelude::*;
11+
12+
#[test]
13+
fn test_help_formatting_is_readable()
14+
{
15+
// This test ensures help output follows the improved formatting specification
16+
17+
// Create a command with multiple arguments to test formatting
18+
let mut registry = CommandRegistry::new();
19+
20+
let test_cmd = CommandDefinition {
21+
name: "run_file".to_string(),
22+
namespace: String::new(),
23+
description: "Execute prompts from structured or plain text files".to_string(),
24+
hint: "Run prompts from a file (text, YAML, JSON, or TOML)".to_string(),
25+
arguments: vec![
26+
ArgumentDefinition {
27+
name: "file".to_string(),
28+
description: "Path to prompt file".to_string(),
29+
kind: Kind::File,
30+
hint: "Path to prompt file".to_string(),
31+
attributes: ArgumentAttributes {
32+
optional: false,
33+
..Default::default()
34+
},
35+
validation_rules: vec![],
36+
aliases: vec![],
37+
tags: vec!["automation".to_string(), "file".to_string()],
38+
},
39+
ArgumentDefinition {
40+
name: "working_dir".to_string(),
41+
description: "Directory to run commands in".to_string(),
42+
kind: Kind::Directory,
43+
hint: "Directory to run commands in".to_string(),
44+
attributes: ArgumentAttributes {
45+
optional: true,
46+
..Default::default()
47+
},
48+
validation_rules: vec![],
49+
aliases: vec![],
50+
tags: vec![],
51+
},
52+
ArgumentDefinition {
53+
name: "simple".to_string(),
54+
description: "Simple mode without session management".to_string(),
55+
kind: Kind::Boolean,
56+
hint: "Simple mode without session management".to_string(),
57+
attributes: ArgumentAttributes {
58+
optional: true,
59+
..Default::default()
60+
},
61+
validation_rules: vec![],
62+
aliases: vec![],
63+
tags: vec![],
64+
},
65+
],
66+
routine_link: None,
67+
status: "stable".to_string(),
68+
version: "0.1.0".to_string(),
69+
tags: vec!["automation".to_string(), "file".to_string()],
70+
aliases: vec![],
71+
permissions: vec![],
72+
idempotent: true,
73+
deprecation_message: String::new(),
74+
http_method_hint: String::new(),
75+
examples: vec![],
76+
};
77+
78+
registry.register(test_cmd);
79+
80+
let help_gen = HelpGenerator::new(&registry);
81+
let help_output = help_gen.command("run_file").expect("Command should exist");
82+
83+
// Test formatting requirements from specification section 9.5
84+
85+
// 1. Should not have overly long lines (no single line over 100 chars for readability)
86+
for line in help_output.lines() {
87+
assert!(
88+
line.len() <= 100,
89+
"Help line too long ({}): '{}'", line.len(), line
90+
);
91+
}
92+
93+
// 2. Should not have redundant "Hint:" prefix when context is clear in arguments section
94+
let lines = help_output.lines().collect::<Vec<_>>();
95+
let in_arguments_section = lines.iter().any(|line| line.contains("Arguments:"));
96+
if in_arguments_section {
97+
// Find lines in arguments section (after "Arguments:" line)
98+
let mut found_arguments_section = false;
99+
for line in &lines {
100+
if line.contains("Arguments:") {
101+
found_arguments_section = true;
102+
continue;
103+
}
104+
if found_arguments_section && !line.trim().is_empty() {
105+
// Arguments section lines should not have redundant "Hint:" when description is clear
106+
if line.contains(" - Hint: ") {
107+
// Check if the hint is identical or very similar to what comes before "Hint:"
108+
let parts: Vec<&str> = line.split(" - Hint: ").collect();
109+
if parts.len() == 2 {
110+
let before_hint = parts[0];
111+
let hint_text = parts[1].split(',').next().unwrap_or("");
112+
113+
// If the hint is redundant with information already present, fail the test
114+
if before_hint.contains(hint_text) {
115+
panic!("Redundant hint text found: '{}' already contains '{}'", before_hint, hint_text);
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
// 3. Should have proper visual hierarchy
124+
assert!(help_output.contains("Usage:"), "Should have Usage header");
125+
assert!(help_output.contains("Arguments:"), "Should have Arguments section");
126+
assert!(help_output.contains("Status:"), "Should have Status information");
127+
128+
// 4. Arguments should be clearly separated and readable
129+
// This test will initially fail with current formatting, then pass after improvement
130+
let argument_lines = lines.iter()
131+
.skip_while(|line| !line.contains("Arguments:"))
132+
.skip(1) // Skip "Arguments:" line itself
133+
.take_while(|line| !line.trim().is_empty() && !line.starts_with("Status"))
134+
.collect::<Vec<_>>();
135+
136+
// Each argument should be well-formatted
137+
for arg_line in argument_lines {
138+
// Verify improved formatting - should NOT have the old cramped format
139+
// Old bad: "file (Kind: File) - Hint: Path to prompt file"
140+
// New good: "file (Type: File)" followed by indented description
141+
142+
// Should not contain the old cramped patterns
143+
assert!(
144+
!arg_line.contains("(Kind:"),
145+
"Found old 'Kind:' format, should use 'Type:': '{}'", arg_line
146+
);
147+
assert!(
148+
!(arg_line.contains("- Hint:") && arg_line.len() > 60),
149+
"Found old cramped 'Hint:' format: '{}'", arg_line
150+
);
151+
152+
// Should use improved patterns
153+
if arg_line.contains("(Type:") {
154+
// Main argument lines should be reasonably short
155+
assert!(
156+
arg_line.len() <= 80,
157+
"Argument header line too long: '{}'", arg_line
158+
);
159+
}
160+
}
161+
}
162+
163+
#[test]
164+
fn test_help_formatting_visual_hierarchy()
165+
{
166+
// This test verifies that help output has clear visual hierarchy
167+
168+
let mut registry = CommandRegistry::new();
169+
170+
let test_cmd = CommandDefinition {
171+
name: "test_command".to_string(),
172+
namespace: String::new(),
173+
description: "A test command for formatting verification".to_string(),
174+
hint: "Tests help formatting".to_string(),
175+
arguments: vec![
176+
ArgumentDefinition {
177+
name: "required_arg".to_string(),
178+
description: "A required argument".to_string(),
179+
kind: Kind::String,
180+
hint: "Required string input".to_string(),
181+
attributes: ArgumentAttributes {
182+
optional: false,
183+
..Default::default()
184+
},
185+
validation_rules: vec![],
186+
aliases: vec![],
187+
tags: vec![],
188+
},
189+
],
190+
routine_link: None,
191+
status: "stable".to_string(),
192+
version: "1.0.0".to_string(),
193+
tags: vec![],
194+
aliases: vec![],
195+
permissions: vec![],
196+
idempotent: true,
197+
deprecation_message: String::new(),
198+
http_method_hint: String::new(),
199+
examples: vec![],
200+
};
201+
202+
registry.register(test_cmd);
203+
204+
let help_gen = HelpGenerator::new(&registry);
205+
let help_output = help_gen.command("test_command").expect("Command should exist");
206+
207+
// Verify section headers are properly spaced
208+
let lines: Vec<&str> = help_output.lines().collect();
209+
210+
// Find the Arguments section
211+
let args_index = lines.iter().position(|line| line.contains("Arguments:"))
212+
.expect("Should have Arguments section");
213+
214+
// There should be proper spacing around sections
215+
if args_index > 0 && args_index < lines.len() - 1 {
216+
// Check that there's visual separation (empty line or clear distinction)
217+
let line_before = lines[args_index - 1];
218+
let _line_after = if args_index + 1 < lines.len() { lines[args_index + 1] } else { "" };
219+
220+
// Arguments section should be well-separated from other content
221+
assert!(
222+
line_before.trim().is_empty() || !line_before.starts_with(" "),
223+
"Arguments section should be properly separated from previous content"
224+
);
225+
}
226+
}
227+
228+
#[test]
229+
fn test_documentation_of_improved_formatting_requirements()
230+
{
231+
// This test documents the expected improvements to help formatting
232+
233+
// These are the formatting principles that should be followed
234+
const MAX_LINE_LENGTH: usize = 80;
235+
let requires_multiline_format = true;
236+
let eliminates_redundant_hints = true;
237+
let provides_visual_hierarchy = true;
238+
239+
// Verify that formatting requirements are understood
240+
assert_eq!(MAX_LINE_LENGTH, 80, "Lines should not exceed 80 characters when possible");
241+
assert!(requires_multiline_format, "Help should use multi-line format for clarity");
242+
assert!(eliminates_redundant_hints, "Redundant hint text should be eliminated");
243+
assert!(provides_visual_hierarchy, "Help should have clear visual hierarchy");
244+
245+
// Document the problem with current formatting
246+
let current_bad_example = "file (Kind: File) - Hint: Path to prompt file, Optional";
247+
assert!(current_bad_example.len() > 50, "Current format crams too much info on one line");
248+
249+
// Document what improved formatting should look like
250+
let improved_format_example = vec![
251+
"file",
252+
" Type: File",
253+
" Path to prompt file",
254+
];
255+
256+
// Improved format separates concerns and is more readable
257+
for line in improved_format_example {
258+
assert!(line.len() <= MAX_LINE_LENGTH, "Improved format should have reasonable line lengths");
259+
}
260+
}

module/move/unilang/tests/inc/phase2/help_generation_test.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ fn test_cli_specific_command_help_add()
104104
.and( predicate::str::contains( "Adds two numbers." ) ) // Modified this line
105105
.and( predicate::str::contains( "Status: stable" ) )
106106
.and( predicate::str::contains( "Arguments:" ) )
107-
.and( predicate::str::contains( "a (Kind: Integer) - Hint: First number." ) ) // Modified this line
108-
.and( predicate::str::contains( "b (Kind: Integer) - Hint: Second number." ) ), // Modified this line
107+
.and( predicate::str::contains( "a (Type: Integer)" ) ) // Updated for new format
108+
.and( predicate::str::contains( "First number." ) ) // Description on separate line
109+
.and( predicate::str::contains( "b (Type: Integer)" ) ) // Updated for new format
110+
.and( predicate::str::contains( "Second number." ) ), // Description on separate line
109111
)
110112
.stderr( "" );
111113
}

module/move/unilang/tests/inc/phase3/data_model_features_test.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ fn test_argument_hint_in_help()
6262
cmd
6363
.assert()
6464
.success()
65-
.stdout( predicate::str::contains( "arg1 (Kind: String) - Hint: The first argument to echo." ) )
65+
// Updated to match improved formatting: argument name with type, description on separate line
66+
.stdout( predicate::str::contains( "arg1 (Type: String)" ) )
67+
.stdout( predicate::str::contains( "The first argument to echo." ) )
6668
.stderr( "" );
6769
}
6870

0 commit comments

Comments
 (0)