1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! The `badness` command-line surface.
//!
//! Kept as a self-contained module (referencing only `std` and `clap`) so that
//! `build.rs` can `#[path = "src/cli.rs"]`-include it to generate man pages,
//! shell completions, and the markdown CLI reference, exactly as arity does.
//! Conversions to library types (e.g. [`WrapArg`] → `formatter::WrapMode`) live
//! in `main.rs`, never here, so the file compiles inside the build script too.
use std::path::PathBuf;
use clap::{Parser, Subcommand, ValueEnum};
/// CLI surface for `formatter::WrapMode`. Kept here (not in the formatter) so the
/// formatter API stays clap-free.
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum WrapArg {
/// Greedy fill: wrap words to the line width (default).
Reflow,
/// One sentence per line. (Not yet implemented — behaves like `preserve`.)
Sentence,
/// Semantic line breaks (sembr.org). (Not yet implemented — like `preserve`.)
Semantic,
/// Leave authored line breaks untouched.
Preserve,
}
#[derive(Parser)]
#[command(
name = "badness",
version,
about = "A formatter, linter, and language server for LaTeX"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
/// Path to a `badness.toml` to use instead of discovering one. Applies to
/// `format` and `lint`; ignored by `parse`, `lsp`, and `init`.
#[arg(long, value_name = "PATH", global = true, conflicts_with = "no_config")]
pub config: Option<PathBuf>,
/// Ignore any `badness.toml` and use built-in defaults.
#[arg(long, global = true)]
pub no_config: bool,
}
#[derive(Subcommand)]
pub enum Command {
/// Format LaTeX source.
///
/// With paths, formats each file in place. With no paths, reads stdin and
/// writes the formatted result to stdout.
Format {
/// Files to format. Omit to read from stdin.
paths: Vec<PathBuf>,
/// Report which files would change without writing them. Exits non-zero
/// if any file is not already formatted.
#[arg(long)]
check: bool,
/// Name the stdin buffer so its language is dispatched by extension
/// (`.bib` → BibTeX, anything else → LaTeX). No file is read or written;
/// only the extension is used. Ignored when paths are given.
#[arg(long, value_name = "PATH")]
stdin_filepath: Option<PathBuf>,
/// Maximum line width before the formatter breaks a line.
#[arg(long)]
line_width: Option<usize>,
/// Number of spaces per indent step.
#[arg(long)]
indent_width: Option<usize>,
/// How to lay out line breaks inside a paragraph.
#[arg(long, value_enum)]
wrap: Option<WrapArg>,
/// Gitignore-style pattern to skip during directory discovery (repeatable).
/// Added on top of any `exclude`/`extend-exclude` from `badness.toml`.
#[arg(long, value_name = "PATTERN")]
exclude: Vec<String>,
},
/// Lint LaTeX source, reporting parse diagnostics.
///
/// With paths, lints each file. With no paths, reads stdin. Exits non-zero
/// if any diagnostics are reported.
Lint {
/// Files to lint. Omit to read from stdin.
paths: Vec<PathBuf>,
/// Apply safe autofixes in place, then report what remains. Requires
/// path arguments; has no effect on stdin (there is nothing to write).
#[arg(long)]
fix: bool,
/// Also apply fixes that may change typeset output (requires `--fix`).
#[arg(long)]
unsafe_fixes: bool,
/// Name the stdin buffer so its language is dispatched by extension
/// (`.bib` → BibTeX, anything else → LaTeX). No file is read or written;
/// only the extension is used. Ignored when paths are given.
#[arg(long, value_name = "PATH")]
stdin_filepath: Option<PathBuf>,
/// Gitignore-style pattern to skip during directory discovery (repeatable).
/// Added on top of any `exclude`/`extend-exclude` from `badness.toml`.
#[arg(long, value_name = "PATTERN")]
exclude: Vec<String>,
/// Run only these rules (repeatable). Overrides `[lint] select` from
/// `badness.toml` when given.
#[arg(long, value_name = "RULE")]
select: Vec<String>,
/// Disable these rules (repeatable). Overrides `[lint] ignore` from
/// `badness.toml` when given.
#[arg(long, value_name = "RULE")]
ignore: Vec<String>,
},
/// Parse LaTeX source and print its concrete syntax tree (CST).
///
/// A debugging aid: prints the lossless parse tree as an indented
/// `KIND@range` listing, with token text, followed by any parse errors.
/// With a path, parses that file. With no path, reads stdin.
Parse {
/// File to parse. Omit to read from stdin.
path: Option<PathBuf>,
},
/// Run the language server over stdio.
Lsp,
/// Write a commented starter `badness.toml` to the current directory.
Init {
/// Overwrite an existing `badness.toml`.
#[arg(long)]
force: bool,
},
}