1pub mod utils;
4mod version;
5
6pub use version::{intercept_version, VersionFormat};
7
8use std::path::PathBuf;
9
10use build_info::VERSION;
11use clap::{Args, Command, FromArgMatches, Parser, Subcommand, ValueEnum};
12
13#[derive(Debug, Parser)]
15#[clap(name = "mitex-cli", version = VERSION)]
16pub struct Opts {
17 #[arg(short = 'V', long, group = "version-dump")]
19 pub version: bool,
20
21 #[arg(long = "VV", alias = "version-fmt", group = "version-dump", default_value_t = VersionFormat::None)]
23 pub vv: VersionFormat,
24
25 #[clap(subcommand)]
27 pub sub: Option<Subcommands>,
28}
29
30#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
32#[value(rename_all = "kebab-case")]
33pub enum CompileStage {
34 Syntax,
36 Document,
38}
39
40#[derive(Default, Debug, Clone, Parser)]
42#[clap(next_help_heading = "Compile options")]
43pub struct CompileArgs {
44 #[clap(long, short, default_value = ".")]
62 pub workspace: String,
63
64 #[clap(long, short, default_value = "")]
78 pub input: String,
79
80 #[clap(long, value_enum)]
94 pub stage: Option<CompileStage>,
95
96 #[clap(long, short, default_value = "")]
116 pub output: String,
117
118 #[arg(trailing_var_arg = true, hide = true)]
132 _i_or_o_args: Vec<String>,
133}
134
135#[derive(Debug, Subcommand)]
137#[clap(about = "The CLI for MiTeX.", next_display_order = None)]
138#[allow(clippy::large_enum_variant)]
139pub enum Subcommands {
140 #[clap(visible_alias = "c")]
142 Compile(CompileArgs),
143
144 Completion(CompletionArgs),
146
147 Manual(ManualArgs),
149
150 #[clap(subcommand)]
152 Spec(SpecSubCommands),
153}
154
155#[derive(Debug, Clone, Parser)]
157pub struct CompletionArgs {
158 #[clap(value_enum)]
160 pub shell: clap_complete::Shell,
161}
162
163#[derive(Debug, Clone, Parser)]
165pub struct ManualArgs {
166 pub dest: PathBuf,
168}
169
170#[derive(Debug, Subcommand)]
172#[clap(next_display_order = None)]
173#[allow(clippy::large_enum_variant)]
174pub enum SpecSubCommands {
175 Generate(GenerateSpecArgs),
177}
178
179#[derive(Debug, Clone, Parser)]
181pub struct GenerateSpecArgs {}
182
183pub mod build_info {
185 pub static VERSION: &str = env!("CARGO_PKG_VERSION");
187}
188
189pub fn get_cli(sub_command_required: bool) -> Command {
191 let cli = Command::new("$").disable_version_flag(true);
192 Opts::augment_args(cli).subcommand_required(sub_command_required)
193}
194
195fn process_opts(mut opts: Opts) -> Result<Opts, clap::Error> {
197 if let Some(Subcommands::Compile(args)) = &mut opts.sub {
198 let io_args = std::mem::take(&mut args._i_or_o_args);
199 if args.input.is_empty() && args.output.is_empty() {
200 match io_args.len() {
201 0 => {}
202 1 => {
203 args.input.clone_from(&io_args[0]);
204 }
205 2 => {
206 args.input.clone_from(&io_args[0]);
207 args.output.clone_from(&io_args[1]);
208 }
209 _ => Err(clap::Error::raw(
210 clap::error::ErrorKind::ValueValidation,
211 "Too many positional arguments.",
212 ))?,
213 }
214 } else if !io_args.is_empty() {
215 Err(clap::Error::raw(
216 clap::error::ErrorKind::ValueValidation,
217 "Input and output file cannot be positional arguments\
218 if any of them is specified by named argument.",
219 ))?;
220 }
221
222 if args.input.is_empty() {
223 Err(clap::Error::raw(
224 clap::error::ErrorKind::ValueValidation,
225 "Input file is required.",
226 ))?;
227 }
228 if args.output.is_empty() {
229 std::path::Path::new(&args.input)
230 .with_extension("tex")
231 .to_str()
232 .ok_or_else(|| {
233 clap::Error::raw(
234 clap::error::ErrorKind::ValueValidation,
235 "Input file name is invalid.",
236 )
237 })?
238 .clone_into(&mut args.output);
239 }
240 }
241
242 Ok(opts)
243}
244
245pub fn get_os_opts(sub_command_required: bool) -> Result<Opts, clap::Error> {
250 let res = Opts::from_arg_matches(&get_cli(sub_command_required).get_matches())?;
251
252 process_opts(res)
253}