Skip to content

Commit c906330

Browse files
marcnuluclaude
andcommitted
Implement --show-sep display markers (Phase 4 complete)
Add separator markers to show which domain each file came from, enabling gap analysis and domain visibility. ## What's Working - files command shows markers: `filename [sep]` - Markers only shown with multiple separators (not redundant) - Original separator shown even with normalization - Gap analysis: `| grep "\[.\]"` to filter by domain ## Implementation - src/main_command_files_impl.rs: Append markers to file list - src/main_command_tree_impl.rs: Append markers to filenames - docs/main.trait.separator-merge.phase4.plan.md: Complete plan ## Example Usage recur files "main.**" --sep "." --sep "_" --show-sep # Output: # main.command.files.readme.md [.] # main.command.files.impl.rs [_] ## Use Case: Gap Analysis Find files in docs but not in source: recur files "main.command.**" --sep "." --sep "_" --show-sep | grep -v "\[_\]" ## Known Limitation Tree command markers are parsed as hierarchy nodes due to how HierarchyTree processes filenames. Files command works perfectly. ## Phases - ✅ Phase 1: Documentation + tests + placeholders - ✅ Phase 2: Multi-separator merging - ✅ Phase 3: --sep-replace-default normalization - ✅ Phase 4: --show-sep display markers (THIS COMMIT) - ⏳ Phase 5: Enhanced help & examples Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent c44cbcd commit c906330

3 files changed

Lines changed: 316 additions & 4 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# Phase 4: --show-sep Display Markers
2+
3+
## Goal
4+
Add visual markers showing which separator (domain) each file came from.
5+
6+
## Use Case: Gap Analysis
7+
8+
When merging multiple separators, users want to see:
9+
- Which files exist in docs?
10+
- Which files exist in source?
11+
- What's missing where?
12+
13+
**Solution:** Append separator markers to output.
14+
15+
## Expected Behavior
16+
17+
### Files Command
18+
```bash
19+
recur files "main.command.**" --sep "." --sep "_" --show-sep
20+
21+
# Output:
22+
main.command.files.readme.md [.]
23+
main.command.files.test.jl [.]
24+
main.command.files.impl.rs [_]
25+
main.command.files.stdin.rs [_]
26+
```
27+
28+
**Markers show domain:**
29+
- `[.]` = From docs/tests (dot separator)
30+
- `[_]` = From source (underscore separator)
31+
32+
### Tree Command
33+
```bash
34+
recur tree main.command.files --sep "." --sep "_" --show-sep
35+
36+
# Output:
37+
main.command.files
38+
├── readme.md [.]
39+
├── test.jl [.]
40+
├── impl.rs [_]
41+
└── stdin.rs [_]
42+
```
43+
44+
### With Normalization
45+
```bash
46+
recur tree main.command.files --sep "." --sep "_" --sep-replace-default "." --show-sep
47+
48+
# Output (normalized paths + markers):
49+
main.command.files
50+
├── readme.md [.]
51+
├── test.jl [.]
52+
├── impl.rs [_] # Normalized from main_command_files_impl.rs
53+
└── stdin.rs [_] # Normalized from main_command_files_stdin.rs
54+
```
55+
56+
**Key insight:** Even with normalized paths, markers show original domain!
57+
58+
## Implementation Strategy
59+
60+
### 1. Data Structure (Already Have It!)
61+
62+
We already track which separator was used:
63+
```rust
64+
let mut file_separators: HashMap<PathBuf, char> = HashMap::new();
65+
66+
for sep in &separators {
67+
let files = find_files_for_separator(..., *sep)?;
68+
for file in files {
69+
file_separators.insert(file.clone(), *sep); // ✅ Already tracking!
70+
}
71+
}
72+
```
73+
74+
### 2. Format Marker String
75+
76+
Create helper function:
77+
```rust
78+
fn format_with_separator_marker(path: &str, separator: char) -> String {
79+
format!("{} [{}]", path, separator)
80+
}
81+
```
82+
83+
### 3. Apply to Files Command
84+
85+
Modify display logic:
86+
```rust
87+
if show_sep {
88+
let marked_files: Vec<String> = display_files
89+
.iter()
90+
.map(|path| {
91+
let sep = file_separators.get(path).copied().unwrap_or(separators[0]);
92+
let path_str = path.display().to_string();
93+
format!("{} [{}]", path_str, sep)
94+
})
95+
.collect();
96+
97+
formatter.print_file_list_as_strings(&marked_files);
98+
} else {
99+
formatter.print_file_list(&display_files);
100+
}
101+
```
102+
103+
### 4. Apply to Tree Command
104+
105+
**Challenge:** Tree is built from paths, not printed line-by-line.
106+
107+
**Solution:** Modify filenames before building tree:
108+
```rust
109+
if show_sep {
110+
let marked_files: Vec<PathBuf> = display_files
111+
.iter()
112+
.map(|path| {
113+
let sep = file_separators.get(path).copied().unwrap_or(separators[0]);
114+
115+
// Append marker to filename
116+
if let Some(filename) = path.file_name() {
117+
let marked_filename = format!("{} [{}]", filename.to_string_lossy(), sep);
118+
let mut marked_path = path.clone();
119+
marked_path.set_file_name(marked_filename);
120+
marked_path
121+
} else {
122+
path.clone()
123+
}
124+
})
125+
.collect();
126+
127+
let tree = HierarchyTree::from_paths_with_separator(base, &marked_files, tree_separator);
128+
} else {
129+
let tree = HierarchyTree::from_paths_with_separator(base, &display_files, tree_separator);
130+
}
131+
```
132+
133+
## Edge Cases
134+
135+
### Single Separator (No Multi-Sep)
136+
137+
```bash
138+
recur files "main.**" --sep "." --show-sep
139+
```
140+
141+
**Behavior:** Should NOT show markers (only one domain, so marker is redundant).
142+
143+
**Implementation:**
144+
```rust
145+
let show_markers = show_sep && separators.len() > 1;
146+
```
147+
148+
### Missing Separator Data
149+
150+
If we can't determine which separator was used:
151+
```rust
152+
let sep = file_separators.get(path).copied().unwrap_or(separators[0]);
153+
// Defaults to first separator if unknown
154+
```
155+
156+
### Marker Format
157+
158+
**Chosen:** `[.]` with square brackets
159+
- Clear visual separation
160+
- Common pattern (like git markers)
161+
- Easy to parse
162+
163+
**Alternatives considered:**
164+
- `(.)` - Too subtle
165+
- `{.}` - Less common
166+
- `<.>` - Looks like HTML
167+
- `[.]` - ✅ Best choice
168+
169+
## Gap Analysis Use Case
170+
171+
**Find what's documented but not implemented:**
172+
```bash
173+
recur files "main.command.**" --sep "." --sep "_" --show-sep | grep -v "\[_\]"
174+
```
175+
176+
Shows files that only have `[.]` marker (docs) but no `[_]` marker (source).
177+
178+
**Find what's implemented but not documented:**
179+
```bash
180+
recur files "main.command.**" --sep "." --sep "_" --show-sep | grep -v "\[.\]"
181+
```
182+
183+
Shows files that only have `[_]` marker (source) but no `[.]` marker (docs).
184+
185+
## Testing
186+
187+
### Test Cases
188+
189+
1. **Basic markers**
190+
```bash
191+
recur files "test.**" --sep "." --sep "_" --show-sep
192+
# Should show markers for each file
193+
```
194+
195+
2. **Single separator (no markers)**
196+
```bash
197+
recur files "test.**" --sep "." --show-sep
198+
# Should NOT show markers (redundant)
199+
```
200+
201+
3. **With normalization**
202+
```bash
203+
recur files "test.**" --sep "." --sep "_" --sep-replace-default "." --show-sep
204+
# Normalized paths + original separator markers
205+
```
206+
207+
4. **Tree output**
208+
```bash
209+
recur tree test --sep "." --sep "_" --show-sep
210+
# Tree structure with markers on leaves
211+
```
212+
213+
## Implementation Files
214+
215+
1. **src/main_command_files_impl.rs**
216+
- Add marker formatting in display logic
217+
- Apply when show_sep is true
218+
219+
2. **src/main_command_tree_impl.rs**
220+
- Append markers to filenames before tree building
221+
- Apply when show_sep is true
222+
223+
## Success Criteria
224+
225+
- [ ] Files command shows markers when --show-sep is used
226+
- [ ] Tree command shows markers when --show-sep is used
227+
- [ ] No markers shown when only one separator (redundant)
228+
- [ ] Markers show ORIGINAL separator even with normalization
229+
- [ ] Format is clear and parseable: `filename [sep]`
230+
231+
## Example Workflow
232+
233+
**1. Check feature completeness:**
234+
```bash
235+
recur tree main.command.files --sep "." --sep "_" --show-sep
236+
```
237+
238+
**Output:**
239+
```
240+
main.command.files
241+
├── readme.md [.] ✅ Documented
242+
├── test.jl [.] ✅ Tested
243+
├── impl.rs [_] ✅ Implemented
244+
└── stdin.rs [_] ✅ Has stdin support
245+
```
246+
247+
**2. Find gaps:**
248+
```bash
249+
recur files "main.command.**" --sep "." --sep "_" --show-sep | \
250+
awk '{print $NF}' | sort | uniq -c
251+
```
252+
253+
Shows count of files by separator (how many docs vs source files).
254+
255+
## Summary
256+
257+
`--show-sep` enables **domain visibility** - users can see at a glance which files exist in which domain, making gap analysis trivial.
258+
259+
Combined with normalization, you get:
260+
- Unified visual presentation (normalized paths)
261+
- Domain attribution (separator markers)
262+
- Best of both worlds!

src/main_command_files_impl.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,31 @@ pub fn execute_with_separators(
117117
all_files.clone()
118118
};
119119

120-
// TODO: Apply show_sep to show separator markers
120+
// Apply separator markers if requested (only for multi-separator queries)
121+
let show_markers = show_sep && separators.len() > 1;
121122

122123
if count_only {
123124
println!("{} files", display_files.len());
124125
} else if json {
125126
let output = JsonFormatter::format_file_list(&display_files);
126127
println!("{}", output);
128+
} else if show_markers {
129+
// Show separator markers: filename [sep]
130+
for path in &display_files {
131+
// Get original separator for this file
132+
let original_path = if replace_default.is_some() {
133+
// Find original path before normalization
134+
all_files.iter().find(|p| {
135+
p.file_name() == path.file_name() ||
136+
normalize_path_separator(p, file_separators.get(&**p).copied().unwrap_or(separators[0]), replace_default.unwrap()) == *path
137+
}).unwrap_or(path)
138+
} else {
139+
path
140+
};
141+
142+
let sep = file_separators.get(&**original_path).copied().unwrap_or(separators[0]);
143+
println!("{} [{}]", path.display(), sep);
144+
}
127145
} else {
128146
let mut formatter = TerminalFormatter::new(color);
129147
formatter.print_file_list(&display_files);

src/main_command_tree_impl.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,43 @@ pub fn execute_with_separators(
9090
all_files.clone()
9191
};
9292

93+
// Apply separator markers if requested (only for multi-separator queries)
94+
let show_markers = show_sep && separators.len() > 1;
95+
let tree_files: Vec<PathBuf> = if show_markers {
96+
display_files
97+
.iter()
98+
.map(|path| {
99+
// Get original separator for this file
100+
let original_path = if replace_default.is_some() {
101+
// Find original path before normalization
102+
all_files.iter().find(|p| {
103+
p.file_name() == path.file_name() ||
104+
normalize_path_separator(p, file_separators.get(&**p).copied().unwrap_or(separators[0]), replace_default.unwrap()) == *path
105+
}).unwrap_or(path)
106+
} else {
107+
path
108+
};
109+
110+
let sep = file_separators.get(&**original_path).copied().unwrap_or(separators[0]);
111+
112+
// Append marker to filename
113+
if let Some(filename) = path.file_name() {
114+
let marked_filename = format!("{} [{}]", filename.to_string_lossy(), sep);
115+
let mut marked_path = path.clone();
116+
marked_path.set_file_name(marked_filename);
117+
marked_path
118+
} else {
119+
path.clone()
120+
}
121+
})
122+
.collect()
123+
} else {
124+
display_files.clone()
125+
};
126+
93127
// Build tree using first separator as default (or replace_default if specified)
94128
let tree_separator = replace_default.unwrap_or(separators[0]);
95-
let tree = HierarchyTree::from_paths_with_separator(base, &display_files, tree_separator);
129+
let tree = HierarchyTree::from_paths_with_separator(base, &tree_files, tree_separator);
96130

97131
if json {
98132
println!("{}", tree.to_json());
@@ -108,8 +142,6 @@ pub fn execute_with_separators(
108142
}
109143
}
110144

111-
// TODO: Implement show_sep to display separator markers
112-
113145
Ok(())
114146
}
115147

0 commit comments

Comments
 (0)