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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
//! Code to parse the command line using `clap`, and definitions of the parsed result
use crate::help;
use crate::operations::LogType;
use crate::styles::{set_color_choice, ColorChoice};
use clap::{Parser, ValueEnum};
use std::path::PathBuf;
/// Returns the parsed command line: the `Args` return value's `op` field is the set operation
/// desired, and the `files` field holds the files to take as operands.
#[must_use]
pub fn parsed() -> Args {
let parsed = CliArgs::parse();
let cc = parsed.color.unwrap_or(ColorChoice::Auto);
set_color_choice(cc);
if parsed.help {
help_and_exit();
}
if parsed.version {
println!("{}", help::version());
exit_success();
}
let Some(op) = parsed.command else { help_and_exit() };
if op == CliName::Help {
help_and_exit()
}
let log_type = if parsed.count_files {
LogType::Files
} else if parsed.count_lines {
LogType::Lines
} else if parsed.count {
if parsed.files {
LogType::Files
} else {
LogType::Lines
}
} else {
LogType::None
};
let op = match op {
CliName::Help => help_and_exit(), // This can't happen, but...
CliName::Intersect => OpName::Intersect,
CliName::Union => OpName::Union,
CliName::Diff => OpName::Diff,
CliName::Single => {
if parsed.files {
OpName::SingleByFile
} else {
OpName::Single
}
}
CliName::Multiple => {
if parsed.files {
OpName::MultipleByFile
} else {
OpName::Multiple
}
}
};
Args { op, log_type, paths: parsed.paths }
}
fn help_and_exit() -> ! {
help::print();
exit_success();
}
const SUCCESS_CODE: i32 = 0;
fn exit_success() -> ! {
safe_exit(SUCCESS_CODE)
}
/// From clap
fn safe_exit(code: i32) -> ! {
use std::io::Write;
let _ = std::io::stdout().lock().flush();
let _ = std::io::stderr().lock().flush();
std::process::exit(code)
}
pub struct Args {
/// `op` is the set operation requested
pub op: OpName,
/// Should we count the number of times each line occurs?
pub log_type: LogType,
/// `paths` is the list of files from the command line
pub paths: Vec<PathBuf>,
}
/// Set operation to perform
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum OpName {
/// Print the lines present in every file
Intersect,
/// Print the lines present in any file
Union,
/// Print the lines present in the first file but no other
Diff,
/// Print the lines present exactly once in the entire input
Single,
/// Print the lines present in exactly one file
SingleByFile,
/// Print the lines present more than once in the entire input
Multiple,
/// Print the lines present in two or more files
MultipleByFile,
}
#[derive(Debug, Parser)]
#[command(name = "zet")]
/// `CliArgs` contains the parsed command line.
struct CliArgs {
#[arg(short, long, overrides_with_all(["count", "count_files", "count_lines", "count_none"]))]
/// The --count-files flag tells `zet` to report the number of files a line occurs in
count_files: bool,
#[arg(long, overrides_with_all(["count", "count_files", "count_lines", "count_none"]))]
/// The --count-lines flag tells `zet` to report the times a line appears in the entire input
count_lines: bool,
#[arg(long, overrides_with_all(["count", "count_files", "count_lines", "count_none"]))]
/// The --count-none flag tells `zet` to turn off reporting
count_none: bool,
#[arg(long, overrides_with_all(["count", "count_files", "count_lines", "count_none"]))]
/// The --count is like --count-lines, but --files makes it act like --count-files
count: bool,
#[arg(long, alias("file"), overrides_with_all(["files", "lines"]))]
/// With `--files`, the `single` and `multiple` commands count a line as occuring
/// once if it's only contained in one file, even if it occurs many times in that file.
files: bool,
#[arg(long, alias("line"), overrides_with_all(["files", "lines"]))]
/// `--lines` is the default. Specify it explicitly to override a previous `--files`
lines: bool,
#[arg(short, long)]
/// Like the `help` command, the `-h` or `--help` flags tell us to print the help message
/// and exit
help: bool,
#[arg(short('V'), long)]
/// The `-V` or `--version` flags tell us to print our name and version, then exit
version: bool,
#[arg(long)]
/// The `color` flag tells us whether to print color or not (Auto means Yes, if
/// stdout is a terminal that supports color)
color: Option<ColorChoice>,
#[arg(value_enum)]
/// `op` is the set operation requested
command: Option<CliName>,
#[arg(name = "Input files")]
/// `paths` is the list of file paths from the command line
paths: Vec<PathBuf>,
}
#[derive(PartialEq, Eq, Debug, Clone, Copy, ValueEnum)]
/// Name of the requested operation
enum CliName {
/// Print the lines present in every file
Intersect,
/// Print the lines present in any file
Union,
/// Print the lines present in the first file but no other
Diff,
/// Print the lines present in exactly one file
Single,
/// Print the lines present in two or more files
Multiple,
/// Print a help message
Help,
}