use crate::grammar::attributes::ALLOWABLE_LINT_IDENTIFIERS;
use clap::ArgAction::Append;
use clap::{Parser, ValueEnum};
#[derive(Debug, Default, Parser)]
#[command(author, version, about, long_about = DESCRIPTION, rename_all = "kebab-case")]
pub struct SliceOptions {
pub sources: Vec<String>,
#[arg(short = 'R', num_args = 1, action = Append, value_name = "REFERENCE")]
pub references: Vec<String>,
#[arg(short = 'G', long = "generator", num_args = 1, action = Append, value_name = "GENERATOR", value_parser = plugin_parser, verbatim_doc_comment)]
pub generators: Vec<Plugin>,
#[arg(short = 'O', long, value_name = "DIRECTORY")]
pub output_dir: Option<String>,
#[arg(short = 'D', num_args = 1, action = Append, value_name = "SYMBOL")]
pub defined_symbols: Vec<String>,
#[arg(short = 'A', long = "allow", num_args = 1, action = Append, value_name = "LINT_NAME", value_parser = ALLOWABLE_LINT_IDENTIFIERS, hide_possible_values = true, ignore_case = true)]
pub allowed_lints: Vec<String>,
#[arg(long)]
pub dry_run: bool,
#[arg(long, value_name = "FORMAT", value_enum, default_value_t = DiagnosticFormat::Human, ignore_case = true)]
pub diagnostic_format: DiagnosticFormat,
#[arg(long)]
pub disable_color: bool,
}
const DESCRIPTION: &str = "\
The Slice compiler.
Parses Slice files into a typed Abstract Syntax Tree (AST) describing the provided Slice definitions.
This AST is encoded with Slice, and then output, to be consumed by other tools.";
fn plugin_parser<'a>(s: &str) -> Result<Plugin, &'a str> {
enum State {
Path,
Key,
Value,
}
assert!(!s.is_empty());
let mut plugin_path = String::new();
let mut plugin_args = Vec::<(String, String)>::new();
let mut string_buffer = &mut plugin_path;
let mut state = State::Path;
let mut char_iter = s.chars().peekable();
while let Some(c) = char_iter.next() {
match c {
'\\' if matches!(char_iter.peek(), Some(',' | '=')) => {
string_buffer.push(char_iter.next().unwrap());
}
',' => {
if char_iter.peek().is_some() {
plugin_args.push(Default::default());
string_buffer = &mut plugin_args.last_mut().unwrap().0;
state = State::Key;
}
}
'=' => match state {
State::Path => string_buffer.push('='), State::Key => {
string_buffer = &mut plugin_args.last_mut().unwrap().1;
state = State::Value;
}
State::Value => {
return Err("'=' can only appear once per argument (for a literal '=' character, use '\\=')")
}
},
_ => string_buffer.push(c),
}
}
let path = plugin_path.trim().to_owned();
let args: Vec<_> = plugin_args
.into_iter()
.map(|(key, value)| (key.trim().to_owned(), value.trim().to_owned()))
.collect();
if path.is_empty() {
return Err("missing plugin path (ex: 'PATH,KEY=VALUE')");
}
for arg in &args {
if arg.0.is_empty() {
return Err("missing argument key (ex: 'PATH,KEY=VALUE')");
}
}
Ok(Plugin { path, args })
}
#[derive(Clone, Debug)]
pub struct Plugin {
pub path: String,
pub args: Vec<(String, String)>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, ValueEnum)]
pub enum DiagnosticFormat {
#[default]
Human,
Json,
}