Skip to main content

sift_queue/cli/
help.rs

1use clap::builder::{StyledStr, Styles};
2
3pub struct HelpDoc {
4    sections: Vec<HelpSection>,
5}
6
7pub struct HelpSection {
8    title: &'static str,
9    rows: Vec<HelpRow>,
10}
11
12pub enum HelpRow {
13    Item {
14        usage: &'static str,
15        description: &'static str,
16    },
17    Text(&'static str),
18}
19
20impl HelpDoc {
21    pub fn new() -> Self {
22        Self {
23            sections: Vec::new(),
24        }
25    }
26
27    pub fn section(mut self, section: HelpSection) -> Self {
28        self.sections.push(section);
29        self
30    }
31
32    pub fn render(&self, styles: &Styles) -> StyledStr {
33        let header = styles.get_header();
34        let literal = styles.get_literal();
35        let mut out = String::new();
36
37        for (section_index, section) in self.sections.iter().enumerate() {
38            if section_index > 0 {
39                out.push_str("\n\n");
40            }
41
42            out.push_str(&format!("{header}{}{header:#}\n", section.title));
43
44            let item_width = section
45                .rows
46                .iter()
47                .filter_map(|row| match row {
48                    HelpRow::Item { usage, .. } => Some(usage.chars().count()),
49                    HelpRow::Text(_) => None,
50                })
51                .max()
52                .unwrap_or(0);
53
54            for (row_index, row) in section.rows.iter().enumerate() {
55                if row_index > 0 {
56                    out.push('\n');
57                }
58
59                match row {
60                    HelpRow::Item { usage, description } => {
61                        let padding =
62                            " ".repeat(item_width.saturating_sub(usage.chars().count()) + 2);
63                        out.push_str(&format!(
64                            "  {literal}{usage}{literal:#}{padding}{description}"
65                        ));
66                    }
67                    HelpRow::Text(text) => {
68                        for (line_index, line) in text.lines().enumerate() {
69                            if line_index > 0 {
70                                out.push('\n');
71                            }
72                            out.push_str("  ");
73                            out.push_str(line);
74                        }
75                    }
76                }
77            }
78        }
79
80        StyledStr::from(out)
81    }
82}
83
84impl HelpSection {
85    pub fn new(title: &'static str) -> Self {
86        Self {
87            title,
88            rows: Vec::new(),
89        }
90    }
91
92    pub fn item(mut self, usage: &'static str, description: &'static str) -> Self {
93        self.rows.push(HelpRow::Item { usage, description });
94        self
95    }
96
97    pub fn text(mut self, text: &'static str) -> Self {
98        self.rows.push(HelpRow::Text(text));
99        self
100    }
101}
102
103pub fn root_after_help(styles: &Styles) -> StyledStr {
104    HelpDoc::new()
105        .section(
106            HelpSection::new("Task file:")
107                .text("By default, sq discovers the nearest existing .sift/issues.jsonl within the current git worktree and otherwise falls back to <cwd>/.sift/issues.jsonl.")
108                .text("Override with -q, --queue <PATH> or SQ_QUEUE_PATH=<PATH>."),
109        )
110        .section(
111            HelpSection::new("Workflow:")
112                .item("sq list --ready", "See the next actionable tasks")
113                .item("sq add --title <TITLE>", "Create a new task")
114                .item("sq show <id>", "Inspect one task in detail")
115                .item(
116                    "sq edit <id> ...",
117                    "Update fields, sources, metadata, or blockers",
118                ),
119        )
120        .section(
121            HelpSection::new("Command help:")
122                .item(
123                    "sq <command> --help",
124                    "See command-specific guidance and examples",
125                )
126                .item("sq prime", "Output workflow context for AI agents"),
127        )
128        .render(styles)
129}