Navigate a directory full of directories, identifying repos and worktrees
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make --json respect --format for column selection

rektide d36504a5 21bac4f4

+110 -5
+5 -2
README.md
··· 32 32 # Custom output format with interpolated columns 33 33 is-tree --all --format "{status} {directory} {commit-date} {change-date} {workparent}" 34 34 is-tree --all --format "{status} {directory} {ahead}" 35 + is-tree --all --format all 35 36 36 - # Output as JSON 37 + # Output as JSON (respects --format for column selection) 37 38 is-tree --all --json 39 + is-tree --all --format "{status} {directory} {ahead}" --json 38 40 39 41 # Custom date format 40 42 is-tree --all --date "%Y-%m-%d %H:%M:%S" ··· 46 48 - `--filter <type>` - Filter by type(s). Multiple types comma-separated. Suffix `-` for NOT. Types: `git`, `jj`, `worktree`, `worktree-git`, `worktree-jj` 47 49 - `--sort <column>` - Sort by column(s). Multiple columns comma-separated. Suffix `+` for ascending, `-` for descending 48 50 - `--format <string>` - Custom output format with interpolated columns 51 + - Shortcut: `--format all` includes all available columns 49 52 - `--date <format>` - Date format string (default: ISO 8601) 50 53 - `--json` - Output results in JSON format 51 54 - Positional arguments - Test specific directories ··· 71 74 - `none` - Not a recognized repository 72 75 73 76 ### JSON Output 74 - When `--json` is specified, output is an array of objects with all available columns as fields. 77 + When `--json` is specified, output is an array of objects. By default, includes `status` and `directory` columns. Use `--format` to control which columns appear in the JSON output. 75 78 76 79 ## Detection 77 80
+105 -3
src/main.rs
··· 52 52 53 53 --format <STRING> 54 54 Custom output format using {column} placeholders. 55 + Use --format all as a shortcut for all columns. 55 56 56 57 Columns: status, directory, commit-date, change-date, workparent, variant, ahead 57 58 ··· 59 60 --format '{status} {directory}' 60 61 --format '{directory} - {status} ({workparent})' 61 62 --format '{directory} ({variant})' 63 + --format all 62 64 ")] 63 65 struct ListArgs { 64 66 #[arg(short, long)] ··· 94 96 ahead: Option<isize>, 95 97 } 96 98 99 + #[derive(Debug, Clone, Serialize)] 100 + struct JsonResult { 101 + #[serde(skip_serializing_if = "Option::is_none")] 102 + status: Option<String>, 103 + #[serde(skip_serializing_if = "Option::is_none")] 104 + directory: Option<String>, 105 + #[serde(skip_serializing_if = "Option::is_none")] 106 + commit_date: Option<String>, 107 + #[serde(skip_serializing_if = "Option::is_none")] 108 + change_date: Option<String>, 109 + #[serde(skip_serializing_if = "Option::is_none")] 110 + workparent: Option<String>, 111 + #[serde(skip_serializing_if = "Option::is_none")] 112 + variant: Option<String>, 113 + #[serde(skip_serializing_if = "Option::is_none")] 114 + ahead: Option<isize>, 115 + } 116 + 97 117 fn main() { 98 118 let cli = Cli::parse(); 99 119 ··· 110 130 fn run_list(args: ListArgs) { 111 131 let filters = parse_filters(args.filter.as_deref()); 112 132 let sort_specs = parse_sort_specs(args.sort.as_deref()); 133 + let format_str = args.format.as_deref().map(resolve_format_shortcuts); 113 134 114 135 let paths = if args.all { 115 136 let current_dir = Path::new("."); ··· 151 172 sort_results(&mut results, &sort_specs); 152 173 153 174 if args.json { 154 - let json_output = serde_json::to_string_pretty(&results).unwrap(); 175 + let columns = if let Some(fmt) = format_str { 176 + parse_columns_from_format(fmt) 177 + } else { 178 + parse_columns_from_format("{status} {directory}") 179 + }; 180 + let json_results: Vec<JsonResult> = results 181 + .iter() 182 + .map(|r| filter_json_result(r, &columns)) 183 + .collect(); 184 + let json_output = serde_json::to_string_pretty(&json_results).unwrap(); 155 185 println!("{}", json_output); 156 - } else if let Some(format_str) = args.format { 186 + } else if let Some(format_str) = format_str { 157 187 for result in results { 158 - let formatted = format_result(&result, &format_str); 188 + let formatted = format_result(&result, format_str); 159 189 println!("{}", formatted); 160 190 } 161 191 } else { 162 192 for result in results { 163 193 println!("{} {}", result.status, result.directory); 164 194 } 195 + } 196 + } 197 + 198 + fn resolve_format_shortcuts(format: &str) -> &str { 199 + if format == "all" { 200 + "{status} {directory} {commit-date} {change-date} {workparent} {variant} {ahead}" 201 + } else { 202 + format 203 + } 204 + } 205 + 206 + fn parse_columns_from_format(format: &str) -> Vec<String> { 207 + let mut columns = Vec::new(); 208 + let mut chars = format.chars().peekable(); 209 + 210 + while let Some(ch) = chars.next() { 211 + if ch == '{' { 212 + let mut col = String::new(); 213 + while let Some(&next) = chars.peek() { 214 + if next == '}' { 215 + chars.next(); 216 + break; 217 + } 218 + col.push(chars.next().unwrap()); 219 + } 220 + let trimmed = col.trim(); 221 + if !trimmed.is_empty() { 222 + columns.push(trimmed.to_string()); 223 + } 224 + } 225 + } 226 + 227 + columns 228 + } 229 + 230 + fn filter_json_result(result: &Result, columns: &[String]) -> JsonResult { 231 + JsonResult { 232 + status: if columns.contains(&"status".to_string()) { 233 + Some(result.status.clone()) 234 + } else { 235 + None 236 + }, 237 + directory: if columns.contains(&"directory".to_string()) { 238 + Some(result.directory.clone()) 239 + } else { 240 + None 241 + }, 242 + commit_date: if columns.contains(&"commit-date".to_string()) { 243 + result.commit_date.clone() 244 + } else { 245 + None 246 + }, 247 + change_date: if columns.contains(&"change-date".to_string()) { 248 + result.change_date.clone() 249 + } else { 250 + None 251 + }, 252 + workparent: if columns.contains(&"workparent".to_string()) { 253 + result.workparent.clone() 254 + } else { 255 + None 256 + }, 257 + variant: if columns.contains(&"variant".to_string()) { 258 + result.variant.clone() 259 + } else { 260 + None 261 + }, 262 + ahead: if columns.contains(&"ahead".to_string()) { 263 + result.ahead 264 + } else { 265 + None 266 + }, 165 267 } 166 268 } 167 269