···3232# Custom output format with interpolated columns
3333is-tree --all --format "{status} {directory} {commit-date} {change-date} {workparent}"
3434is-tree --all --format "{status} {directory} {ahead}"
3535+is-tree --all --format all
35363636-# Output as JSON
3737+# Output as JSON (respects --format for column selection)
3738is-tree --all --json
3939+is-tree --all --format "{status} {directory} {ahead}" --json
38403941# Custom date format
4042is-tree --all --date "%Y-%m-%d %H:%M:%S"
···4648- `--filter <type>` - Filter by type(s). Multiple types comma-separated. Suffix `-` for NOT. Types: `git`, `jj`, `worktree`, `worktree-git`, `worktree-jj`
4749- `--sort <column>` - Sort by column(s). Multiple columns comma-separated. Suffix `+` for ascending, `-` for descending
4850- `--format <string>` - Custom output format with interpolated columns
5151+ - Shortcut: `--format all` includes all available columns
4952- `--date <format>` - Date format string (default: ISO 8601)
5053- `--json` - Output results in JSON format
5154- Positional arguments - Test specific directories
···7174- `none` - Not a recognized repository
72757376### JSON Output
7474-When `--json` is specified, output is an array of objects with all available columns as fields.
7777+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.
75787679## Detection
7780
+105-3
src/main.rs
···52525353 --format <STRING>
5454 Custom output format using {column} placeholders.
5555+ Use --format all as a shortcut for all columns.
55565657 Columns: status, directory, commit-date, change-date, workparent, variant, ahead
5758···5960 --format '{status} {directory}'
6061 --format '{directory} - {status} ({workparent})'
6162 --format '{directory} ({variant})'
6363+ --format all
6264")]
6365struct ListArgs {
6466 #[arg(short, long)]
···9496 ahead: Option<isize>,
9597}
96989999+#[derive(Debug, Clone, Serialize)]
100100+struct JsonResult {
101101+ #[serde(skip_serializing_if = "Option::is_none")]
102102+ status: Option<String>,
103103+ #[serde(skip_serializing_if = "Option::is_none")]
104104+ directory: Option<String>,
105105+ #[serde(skip_serializing_if = "Option::is_none")]
106106+ commit_date: Option<String>,
107107+ #[serde(skip_serializing_if = "Option::is_none")]
108108+ change_date: Option<String>,
109109+ #[serde(skip_serializing_if = "Option::is_none")]
110110+ workparent: Option<String>,
111111+ #[serde(skip_serializing_if = "Option::is_none")]
112112+ variant: Option<String>,
113113+ #[serde(skip_serializing_if = "Option::is_none")]
114114+ ahead: Option<isize>,
115115+}
116116+97117fn main() {
98118 let cli = Cli::parse();
99119···110130fn run_list(args: ListArgs) {
111131 let filters = parse_filters(args.filter.as_deref());
112132 let sort_specs = parse_sort_specs(args.sort.as_deref());
133133+ let format_str = args.format.as_deref().map(resolve_format_shortcuts);
113134114135 let paths = if args.all {
115136 let current_dir = Path::new(".");
···151172 sort_results(&mut results, &sort_specs);
152173153174 if args.json {
154154- let json_output = serde_json::to_string_pretty(&results).unwrap();
175175+ let columns = if let Some(fmt) = format_str {
176176+ parse_columns_from_format(fmt)
177177+ } else {
178178+ parse_columns_from_format("{status} {directory}")
179179+ };
180180+ let json_results: Vec<JsonResult> = results
181181+ .iter()
182182+ .map(|r| filter_json_result(r, &columns))
183183+ .collect();
184184+ let json_output = serde_json::to_string_pretty(&json_results).unwrap();
155185 println!("{}", json_output);
156156- } else if let Some(format_str) = args.format {
186186+ } else if let Some(format_str) = format_str {
157187 for result in results {
158158- let formatted = format_result(&result, &format_str);
188188+ let formatted = format_result(&result, format_str);
159189 println!("{}", formatted);
160190 }
161191 } else {
162192 for result in results {
163193 println!("{} {}", result.status, result.directory);
164194 }
195195+ }
196196+}
197197+198198+fn resolve_format_shortcuts(format: &str) -> &str {
199199+ if format == "all" {
200200+ "{status} {directory} {commit-date} {change-date} {workparent} {variant} {ahead}"
201201+ } else {
202202+ format
203203+ }
204204+}
205205+206206+fn parse_columns_from_format(format: &str) -> Vec<String> {
207207+ let mut columns = Vec::new();
208208+ let mut chars = format.chars().peekable();
209209+210210+ while let Some(ch) = chars.next() {
211211+ if ch == '{' {
212212+ let mut col = String::new();
213213+ while let Some(&next) = chars.peek() {
214214+ if next == '}' {
215215+ chars.next();
216216+ break;
217217+ }
218218+ col.push(chars.next().unwrap());
219219+ }
220220+ let trimmed = col.trim();
221221+ if !trimmed.is_empty() {
222222+ columns.push(trimmed.to_string());
223223+ }
224224+ }
225225+ }
226226+227227+ columns
228228+}
229229+230230+fn filter_json_result(result: &Result, columns: &[String]) -> JsonResult {
231231+ JsonResult {
232232+ status: if columns.contains(&"status".to_string()) {
233233+ Some(result.status.clone())
234234+ } else {
235235+ None
236236+ },
237237+ directory: if columns.contains(&"directory".to_string()) {
238238+ Some(result.directory.clone())
239239+ } else {
240240+ None
241241+ },
242242+ commit_date: if columns.contains(&"commit-date".to_string()) {
243243+ result.commit_date.clone()
244244+ } else {
245245+ None
246246+ },
247247+ change_date: if columns.contains(&"change-date".to_string()) {
248248+ result.change_date.clone()
249249+ } else {
250250+ None
251251+ },
252252+ workparent: if columns.contains(&"workparent".to_string()) {
253253+ result.workparent.clone()
254254+ } else {
255255+ None
256256+ },
257257+ variant: if columns.contains(&"variant".to_string()) {
258258+ result.variant.clone()
259259+ } else {
260260+ None
261261+ },
262262+ ahead: if columns.contains(&"ahead".to_string()) {
263263+ result.ahead
264264+ } else {
265265+ None
266266+ },
165267 }
166268}
167269