Skip to main content

git_lfs/
man.rs

1//! Per-subcommand documentation extras (man pages + mdbook).
2//!
3//! The clap derive in [`crate::args`] is the source of truth for the
4//! NAME / SYNOPSIS / OPTIONS surface — xtask renders that automatically
5//! into both groff (man pages) and markdown (mdbook). This module owns
6//! everything richer: DESCRIPTION prose, EXAMPLES, NOTES, FILES,
7//! SEE ALSO.
8//!
9//! **Bodies are authored in markdown.** xtask passes them through
10//! verbatim for the markdown output and converts them to groff for the
11//! man pages, so a single source feeds both formats. The supported
12//! markdown vocabulary is intentionally small — paragraphs, bold/italic,
13//! code spans / fenced code blocks, bulleted and numbered lists. Stick
14//! to that and the groff conversion stays predictable.
15//!
16//! Each subcommand exposes its extras here as a [`ManContent`] entry in
17//! [`extras_for`]. Bodies live under `cli/man/<sub>/*.md` and are pulled
18//! in via [`include_str!`], keeping prose out of `man.rs`.
19//!
20//! Onboarding a new section is two-step:
21//! 1. Drop one or more `.md` files into `cli/man/<subcommand>/`.
22//! 2. Add a match arm in [`extras_for`] referencing them.
23//!
24//! Subcommands without an entry get the auto-generated page with no
25//! extras — still useful, just shorter.
26
27/// Hand-authored extras for a single command's documentation. Returned
28/// by [`extras_for`] keyed on the subcommand name (or `""` for the top-
29/// level `git-lfs` page). Both fields are markdown — xtask renders them
30/// to either groff or markdown depending on output format.
31#[derive(Debug)]
32pub struct ManContent {
33    /// Replaces the auto-generated DESCRIPTION (which is just the short
34    /// `about` from the clap derive). Markdown.
35    pub description: Option<&'static str>,
36
37    /// Sections appended after OPTIONS, in order. Each entry is
38    /// `(title, markdown body)`. Conventional titles: `EXAMPLES`,
39    /// `FILES`, `ENVIRONMENT`, `NOTES`, `BUGS`, `SEE ALSO`. The title
40    /// becomes a `.SH` in groff and a top-level `##` in markdown.
41    pub extra_sections: &'static [(&'static str, &'static str)],
42}
43
44impl ManContent {
45    pub const fn empty() -> Self {
46        Self {
47            description: None,
48            extra_sections: &[],
49        }
50    }
51}
52
53const EMPTY: ManContent = ManContent::empty();
54
55/// Markdown body for the `REPORTING BUGS` section that xtask appends
56/// to every generated man / mdbook page. Single source of truth for
57/// the project URL and the "this is the Rust implementation" framing
58/// — change here and every page picks it up on the next regen.
59pub const REPORTING_BUGS: &str = include_str!("../man/reporting_bugs.md");
60
61const SMUDGE: ManContent = ManContent {
62    description: None,
63    extra_sections: &[
64        ("ENVIRONMENT", include_str!("../man/smudge/environment.md")),
65        ("KNOWN BUGS", include_str!("../man/smudge/known_bugs.md")),
66    ],
67};
68
69const CHECKOUT: ManContent = ManContent {
70    description: None,
71    extra_sections: &[("EXAMPLES", include_str!("../man/checkout/examples.md"))],
72};
73
74const FETCH: ManContent = ManContent {
75    description: None,
76    extra_sections: &[
77        (
78            "DEFAULT REMOTE",
79            include_str!("../man/fetch/default_remote.md"),
80        ),
81        ("DEFAULT REFS", include_str!("../man/fetch/default_refs.md")),
82        (
83            "INCLUDE AND EXCLUDE",
84            include_str!("../man/fetch/include_and_exclude.md"),
85        ),
86        ("EXAMPLES", include_str!("../man/fetch/examples.md")),
87        ("SEE ALSO", include_str!("../man/fetch/see_also.md")),
88    ],
89};
90
91const PULL: ManContent = ManContent {
92    description: None,
93    extra_sections: &[
94        (
95            "DEFAULT REMOTE",
96            include_str!("../man/pull/default_remote.md"),
97        ),
98        (
99            "INCLUDE AND EXCLUDE",
100            include_str!("../man/pull/include_and_exclude.md"),
101        ),
102        ("SEE ALSO", include_str!("../man/pull/see_also.md")),
103    ],
104};
105
106const PUSH: ManContent = ManContent {
107    description: None,
108    extra_sections: &[("SEE ALSO", include_str!("../man/push/see_also.md"))],
109};
110
111const INSTALL: ManContent = ManContent {
112    description: None,
113    extra_sections: &[("SEE ALSO", include_str!("../man/install/see_also.md"))],
114};
115
116const UNINSTALL: ManContent = ManContent {
117    description: None,
118    extra_sections: &[("SEE ALSO", include_str!("../man/uninstall/see_also.md"))],
119};
120
121const TRACK: ManContent = ManContent {
122    description: None,
123    extra_sections: &[
124        ("EXAMPLES", include_str!("../man/track/examples.md")),
125        ("SEE ALSO", include_str!("../man/track/see_also.md")),
126    ],
127};
128
129const UNTRACK: ManContent = ManContent {
130    description: None,
131    extra_sections: &[
132        ("EXAMPLES", include_str!("../man/untrack/examples.md")),
133        ("SEE ALSO", include_str!("../man/untrack/see_also.md")),
134    ],
135};
136
137const LOCK: ManContent = ManContent {
138    description: None,
139    extra_sections: &[("SEE ALSO", include_str!("../man/lock/see_also.md"))],
140};
141
142const LOCKS: ManContent = ManContent {
143    description: None,
144    extra_sections: &[("SEE ALSO", include_str!("../man/locks/see_also.md"))],
145};
146
147const UNLOCK: ManContent = ManContent {
148    description: None,
149    extra_sections: &[("SEE ALSO", include_str!("../man/unlock/see_also.md"))],
150};
151
152const STATUS: ManContent = ManContent {
153    description: None,
154    extra_sections: &[("SEE ALSO", include_str!("../man/status/see_also.md"))],
155};
156
157const LS_FILES: ManContent = ManContent {
158    description: None,
159    extra_sections: &[("SEE ALSO", include_str!("../man/ls-files/see_also.md"))],
160};
161
162const PRUNE: ManContent = ManContent {
163    description: None,
164    extra_sections: &[("SEE ALSO", include_str!("../man/prune/see_also.md"))],
165};
166
167const FSCK: ManContent = ManContent {
168    description: None,
169    extra_sections: &[("SEE ALSO", include_str!("../man/fsck/see_also.md"))],
170};
171
172const CLEAN: ManContent = ManContent {
173    description: None,
174    extra_sections: &[("SEE ALSO", include_str!("../man/clean/see_also.md"))],
175};
176
177const FILTER_PROCESS: ManContent = ManContent {
178    description: None,
179    extra_sections: &[(
180        "SEE ALSO",
181        include_str!("../man/filter-process/see_also.md"),
182    )],
183};
184
185const CLONE: ManContent = ManContent {
186    description: None,
187    extra_sections: &[("SEE ALSO", include_str!("../man/clone/see_also.md"))],
188};
189
190const PRE_PUSH: ManContent = ManContent {
191    description: None,
192    extra_sections: &[("SEE ALSO", include_str!("../man/pre-push/see_also.md"))],
193};
194
195const POST_CHECKOUT: ManContent = ManContent {
196    description: None,
197    extra_sections: &[("SEE ALSO", include_str!("../man/post-checkout/see_also.md"))],
198};
199
200const POST_COMMIT: ManContent = ManContent {
201    description: None,
202    extra_sections: &[("SEE ALSO", include_str!("../man/post-commit/see_also.md"))],
203};
204
205const POST_MERGE: ManContent = ManContent {
206    description: None,
207    extra_sections: &[("SEE ALSO", include_str!("../man/post-merge/see_also.md"))],
208};
209
210const MIGRATE: ManContent = ManContent {
211    description: None,
212    extra_sections: &[
213        (
214            "INCLUDE AND EXCLUDE",
215            include_str!("../man/migrate/include_and_exclude.md"),
216        ),
217        (
218            "INCLUDE AND EXCLUDE REFERENCES",
219            include_str!("../man/migrate/include_and_exclude_references.md"),
220        ),
221        ("EXAMPLES", include_str!("../man/migrate/examples.md")),
222        ("SEE ALSO", include_str!("../man/migrate/see_also.md")),
223    ],
224};
225
226/// Look up the doc extras for `subcommand` (e.g. `"fetch"`,
227/// `"checkout"`). Pass `""` for the top-level `git-lfs` page.
228/// Returns a reference to [`ManContent::empty`] when there's no entry,
229/// so the caller can always splice unconditionally.
230pub fn extras_for(subcommand: &str) -> &'static ManContent {
231    match subcommand {
232        "smudge" => &SMUDGE,
233        "checkout" => &CHECKOUT,
234        "fetch" => &FETCH,
235        "pull" => &PULL,
236        "push" => &PUSH,
237        "install" => &INSTALL,
238        "uninstall" => &UNINSTALL,
239        "track" => &TRACK,
240        "untrack" => &UNTRACK,
241        "lock" => &LOCK,
242        "locks" => &LOCKS,
243        "unlock" => &UNLOCK,
244        "status" => &STATUS,
245        "ls-files" => &LS_FILES,
246        "prune" => &PRUNE,
247        "fsck" => &FSCK,
248        "clean" => &CLEAN,
249        "filter-process" => &FILTER_PROCESS,
250        "clone" => &CLONE,
251        "pre-push" => &PRE_PUSH,
252        "post-checkout" => &POST_CHECKOUT,
253        "post-commit" => &POST_COMMIT,
254        "post-merge" => &POST_MERGE,
255        "migrate" => &MIGRATE,
256        _ => &EMPTY,
257    }
258}