diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/xflags-macros | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/xflags-macros')
-rw-r--r-- | vendor/xflags-macros/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | vendor/xflags-macros/Cargo.toml | 27 | ||||
-rw-r--r-- | vendor/xflags-macros/src/ast.rs | 51 | ||||
-rw-r--r-- | vendor/xflags-macros/src/emit.rs | 544 | ||||
-rw-r--r-- | vendor/xflags-macros/src/lib.rs | 26 | ||||
-rw-r--r-- | vendor/xflags-macros/src/parse.rs | 340 | ||||
-rw-r--r-- | vendor/xflags-macros/src/update.rs | 98 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/help.rs | 144 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/main.rs | 198 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/repeated_pos.rs | 94 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/smoke.rs | 112 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/src/help.rs | 25 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/src/repeated_pos.rs | 9 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/src/smoke.rs | 15 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/src/subcommands.rs | 20 | ||||
-rw-r--r-- | vendor/xflags-macros/tests/it/subcommands.rs | 225 |
16 files changed, 1929 insertions, 0 deletions
diff --git a/vendor/xflags-macros/.cargo-checksum.json b/vendor/xflags-macros/.cargo-checksum.json new file mode 100644 index 000000000..98f5dfe97 --- /dev/null +++ b/vendor/xflags-macros/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"23f3d89d0198bfa3552286396432e2ace70627773527eaaf62ed4d3ef1b37166","src/ast.rs":"bf42c4e474893d5dbd4fa3c9b392d3f837219fabb2042cfc26acc08b2ddc6f4a","src/emit.rs":"390be2d24bc21d5644c9c69624fd263bfa7dc0f3081799481177a45952168d51","src/lib.rs":"f00a5c9e671a82263d4cf4cced2895bb93ae03220112bc385553573cf9c4cd05","src/parse.rs":"2c2c9118d1f0447a946a78753edb65edd6eeb4f2a27707f5601232c8880aba85","src/update.rs":"feb2cefca834dad6550b6539be7ec9b98fedb608cd09aeddf808350c638d67b8","tests/it/help.rs":"53dbb8b5c6e09649143dc8f9e5cd690db776271a1ea778726146fc04be8583f4","tests/it/main.rs":"3cde1afb05f2470642f0f590cbed24fb553bd0ac1da0fcae93a02ffe42247d97","tests/it/repeated_pos.rs":"1b2b922beb182899504cd36440fd1547026a8a5ef4f79b483a2695227f06c4a8","tests/it/smoke.rs":"5d304f8b24716d63081965914bf4bcf9fc8bfccf30d3f49fb9d71e3405917fd2","tests/it/src/help.rs":"7430f8773b5a8dc3607fbadc04d562f015f056062d373897675140e591da4b60","tests/it/src/repeated_pos.rs":"161b39be8e543ef6dd3f019162c85b9e6d22c30dcd6297e947a2872ed1bc3c5f","tests/it/src/smoke.rs":"f17d4ffb6177fc83aa7f0ad235a487a15490a9f646e7789af8a92db4ea9552fe","tests/it/src/subcommands.rs":"1147d1a7de73cb20d5604a3e08ce43b32a89dbb9ee37aa8c595f6138a083abb9","tests/it/subcommands.rs":"dab409a1b14755122ea42a3169d4a020ce1e548e52a56211874efeb73551de08"},"package":"45d11d5fc2a97287eded8b170ca80533b3c42646dd7fa386a5eb045817921022"}
\ No newline at end of file diff --git a/vendor/xflags-macros/Cargo.toml b/vendor/xflags-macros/Cargo.toml new file mode 100644 index 000000000..a224e51a6 --- /dev/null +++ b/vendor/xflags-macros/Cargo.toml @@ -0,0 +1,27 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "xflags-macros" +version = "0.2.4" +authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"] +description = "Private implementation details of xflags." +license = "MIT OR Apache-2.0" +repository = "https://github.com/matklad/xflags" + +[lib] +proc-macro = true +[dev-dependencies.expect-test] +version = "1" + +[dev-dependencies.proc-macro2] +version = "1" diff --git a/vendor/xflags-macros/src/ast.rs b/vendor/xflags-macros/src/ast.rs new file mode 100644 index 000000000..5ccebd951 --- /dev/null +++ b/vendor/xflags-macros/src/ast.rs @@ -0,0 +1,51 @@ +#[derive(Debug)] +pub(crate) struct XFlags { + pub(crate) src: Option<String>, + pub(crate) cmd: Cmd, +} + +#[derive(Debug)] +pub(crate) struct Cmd { + pub(crate) name: String, + pub(crate) doc: Option<String>, + pub(crate) args: Vec<Arg>, + pub(crate) flags: Vec<Flag>, + pub(crate) subcommands: Vec<Cmd>, + pub(crate) default: bool, +} + +#[derive(Debug)] +pub(crate) struct Arg { + pub(crate) arity: Arity, + pub(crate) doc: Option<String>, + pub(crate) val: Val, +} + +#[derive(Debug)] +pub(crate) struct Flag { + pub(crate) arity: Arity, + pub(crate) name: String, + pub(crate) short: Option<String>, + pub(crate) doc: Option<String>, + pub(crate) val: Option<Val>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Arity { + Optional, + Required, + Repeated, +} + +#[derive(Debug)] +pub(crate) struct Val { + pub(crate) name: String, + pub(crate) ty: Ty, +} + +#[derive(Debug)] +pub(crate) enum Ty { + PathBuf, + OsString, + FromStr(String), +} diff --git a/vendor/xflags-macros/src/emit.rs b/vendor/xflags-macros/src/emit.rs new file mode 100644 index 000000000..05d5f01dd --- /dev/null +++ b/vendor/xflags-macros/src/emit.rs @@ -0,0 +1,544 @@ +use crate::{ast, update}; + +use std::{fmt::Write, path::Path}; + +pub(crate) fn emit(xflags: &ast::XFlags) -> String { + let mut buf = String::new(); + + emit_cmd(&mut buf, &xflags.cmd); + blank_line(&mut buf); + emit_api(&mut buf, xflags); + + if std::env::var("UPDATE_XFLAGS").is_ok() { + if let Some(src) = &xflags.src { + update::in_place(&buf, Path::new(src.as_str())) + } else { + update::stdout(&buf); + } + } + + if xflags.src.is_some() { + buf.clear() + } + + blank_line(&mut buf); + emit_impls(&mut buf, &xflags); + emit_help(&mut buf, xflags); + + buf +} + +macro_rules! w { + ($($tt:tt)*) => { + drop(write!($($tt)*)) + }; +} + +fn emit_cmd(buf: &mut String, cmd: &ast::Cmd) { + w!(buf, "#[derive(Debug)]\n"); + w!(buf, "pub struct {}", cmd.ident()); + if cmd.args.is_empty() && cmd.flags.is_empty() && cmd.subcommands.is_empty() { + w!(buf, ";\n"); + return; + } + w!(buf, " {{\n"); + + for arg in &cmd.args { + let ty = gen_arg_ty(arg.arity, &arg.val.ty); + w!(buf, " pub {}: {},\n", arg.val.ident(), ty); + } + + if !cmd.args.is_empty() && !cmd.flags.is_empty() { + blank_line(buf); + } + + for flag in &cmd.flags { + let ty = gen_flag_ty(flag.arity, flag.val.as_ref().map(|it| &it.ty)); + w!(buf, " pub {}: {},\n", flag.ident(), ty); + } + + if cmd.has_subcommands() { + w!(buf, " pub subcommand: {},\n", cmd.cmd_enum_ident()); + } + w!(buf, "}}\n"); + + if cmd.has_subcommands() { + blank_line(buf); + w!(buf, "#[derive(Debug)]\n"); + w!(buf, "pub enum {} {{\n", cmd.cmd_enum_ident()); + for sub in &cmd.subcommands { + let name = camel(&sub.name); + w!(buf, " {}({}),\n", name, name); + } + w!(buf, "}}\n"); + + for sub in &cmd.subcommands { + blank_line(buf); + emit_cmd(buf, sub); + } + } +} + +fn gen_flag_ty(arity: ast::Arity, ty: Option<&ast::Ty>) -> String { + match ty { + None => match arity { + ast::Arity::Optional => "bool".to_string(), + ast::Arity::Required => "()".to_string(), + ast::Arity::Repeated => "u32".to_string(), + }, + Some(ty) => gen_arg_ty(arity, ty), + } +} + +fn gen_arg_ty(arity: ast::Arity, ty: &ast::Ty) -> String { + let ty = match ty { + ast::Ty::PathBuf => "PathBuf".into(), + ast::Ty::OsString => "OsString".into(), + ast::Ty::FromStr(it) => it.clone(), + }; + match arity { + ast::Arity::Optional => format!("Option<{}>", ty), + ast::Arity::Required => ty, + ast::Arity::Repeated => format!("Vec<{}>", ty), + } +} + +fn emit_api(buf: &mut String, xflags: &ast::XFlags) { + w!(buf, "impl {} {{\n", camel(&xflags.cmd.name)); + + w!(buf, " pub const HELP: &'static str = Self::HELP_;\n"); + blank_line(buf); + + w!(buf, " #[allow(dead_code)]\n"); + w!(buf, " pub fn from_env() -> xflags::Result<Self> {{\n"); + w!(buf, " Self::from_env_()\n"); + w!(buf, " }}\n"); + blank_line(buf); + + w!(buf, " #[allow(dead_code)]\n"); + w!(buf, " pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {{\n"); + w!(buf, " Self::from_vec_(args)\n"); + w!(buf, " }}\n"); + w!(buf, "}}\n"); +} + +fn emit_impls(buf: &mut String, xflags: &ast::XFlags) -> () { + w!(buf, "impl {} {{\n", camel(&xflags.cmd.name)); + w!(buf, " fn from_env_() -> xflags::Result<Self> {{\n"); + w!(buf, " let mut p = xflags::rt::Parser::new_from_env();\n"); + w!(buf, " Self::parse_(&mut p)\n"); + w!(buf, " }}\n"); + w!(buf, " fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {{\n"); + w!(buf, " let mut p = xflags::rt::Parser::new(args);\n"); + w!(buf, " Self::parse_(&mut p)\n"); + w!(buf, " }}\n"); + w!(buf, "}}\n"); + blank_line(buf); + emit_impls_rec(buf, &xflags.cmd) +} + +fn emit_impls_rec(buf: &mut String, cmd: &ast::Cmd) -> () { + emit_impl(buf, cmd); + for sub in &cmd.subcommands { + blank_line(buf); + emit_impls_rec(buf, sub); + } +} + +fn emit_impl(buf: &mut String, cmd: &ast::Cmd) -> () { + w!(buf, "impl {} {{\n", camel(&cmd.name)); + w!(buf, "fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> {{\n"); + + for flag in &cmd.flags { + w!(buf, "let mut {} = Vec::new();\n", flag.ident()); + } + blank_line(buf); + + if !cmd.args.is_empty() { + for arg in &cmd.args { + w!(buf, "let mut {} = (false, Vec::new());\n", arg.val.ident()); + } + blank_line(buf); + } + + if cmd.has_subcommands() { + w!(buf, "let mut sub_ = None;"); + blank_line(buf); + } + + w!(buf, "while let Some(arg_) = p_.pop_flag() {{\n"); + w!(buf, "match arg_ {{\n"); + { + w!(buf, "Ok(flag_) => match flag_.as_str() {{\n"); + for flag in &cmd.flags { + w!(buf, "\"--{}\"", flag.name); + if let Some(short) = &flag.short { + w!(buf, "| \"-{}\"", short); + } + w!(buf, " => {}.push(", flag.ident()); + match &flag.val { + Some(val) => match &val.ty { + ast::Ty::OsString | ast::Ty::PathBuf => { + w!(buf, "p_.next_value(&flag_)?.into()") + } + ast::Ty::FromStr(ty) => { + w!(buf, "p_.next_value_from_str::<{}>(&flag_)?", ty) + } + }, + None => w!(buf, "()"), + } + w!(buf, "),"); + } + if cmd.default_subcommand().is_some() { + w!(buf, "_ => {{ p_.push_back(Ok(flag_)); break; }}"); + } else { + w!(buf, "_ => return Err(p_.unexpected_flag(&flag_)),"); + } + w!(buf, "}}\n"); + } + { + w!(buf, "Err(arg_) => {{\n"); + if cmd.has_subcommands() { + w!(buf, "match arg_.to_str().unwrap_or(\"\") {{\n"); + for sub in cmd.named_subcommands() { + w!(buf, "\"{}\" => {{\n", sub.name); + w!( + buf, + "sub_ = Some({}::{}({}::parse_(p_)?));", + cmd.cmd_enum_ident(), + sub.ident(), + sub.ident() + ); + w!(buf, "break;"); + w!(buf, "}}\n"); + } + w!(buf, "_ => (),\n"); + w!(buf, "}}\n"); + } + + for arg in &cmd.args { + let done = match arg.arity { + ast::Arity::Optional | ast::Arity::Required => "done_ @ ", + ast::Arity::Repeated => "", + }; + w!(buf, "if let ({}false, buf_) = &mut {} {{\n", done, arg.val.ident()); + w!(buf, "buf_.push("); + match &arg.val.ty { + ast::Ty::OsString | ast::Ty::PathBuf => { + w!(buf, "arg_.into()") + } + ast::Ty::FromStr(ty) => { + w!(buf, "p_.value_from_str::<{}>(\"{}\", arg_)?", ty, arg.val.name); + } + } + w!(buf, ");\n"); + match arg.arity { + ast::Arity::Optional | ast::Arity::Required => { + w!(buf, "*done_ = true;\n"); + } + ast::Arity::Repeated => (), + } + w!(buf, "continue;\n"); + w!(buf, "}}\n"); + } + if cmd.default_subcommand().is_some() { + w!(buf, "p_.push_back(Err(arg_)); break;"); + } else { + w!(buf, "return Err(p_.unexpected_arg(arg_));"); + } + + w!(buf, "}}\n"); + } + w!(buf, "}}\n"); + w!(buf, "}}\n"); + + if let Some(sub) = cmd.default_subcommand() { + w!(buf, "if sub_.is_none() {{\n"); + w!( + buf, + "sub_ = Some({}::{}({}::parse_(p_)?));", + cmd.cmd_enum_ident(), + sub.ident(), + sub.ident() + ); + w!(buf, "}}\n"); + } + + w!(buf, "Ok(Self {{\n"); + if !cmd.args.is_empty() { + for arg in &cmd.args { + let val = &arg.val; + w!(buf, "{}: ", val.ident()); + match arg.arity { + ast::Arity::Optional => { + w!(buf, "p_.optional(\"{}\", {}.1)?", val.name, val.ident()) + } + ast::Arity::Required => { + w!(buf, "p_.required(\"{}\", {}.1)?", val.name, val.ident()) + } + ast::Arity::Repeated => w!(buf, "{}.1", val.ident()), + } + w!(buf, ",\n"); + } + blank_line(buf); + } + + for flag in &cmd.flags { + w!(buf, "{}: ", flag.ident()); + match &flag.val { + Some(_val) => match flag.arity { + ast::Arity::Optional => { + w!(buf, "p_.optional(\"--{}\", {})?", flag.name, flag.ident()) + } + ast::Arity::Required => { + w!(buf, "p_.required(\"--{}\", {})?", flag.name, flag.ident()) + } + ast::Arity::Repeated => w!(buf, "{}", flag.ident()), + }, + None => match flag.arity { + ast::Arity::Optional => { + w!(buf, "p_.optional(\"--{}\", {})?.is_some()", flag.name, flag.ident()) + } + ast::Arity::Required => { + w!(buf, "p_.required(\"--{}\", {})?", flag.name, flag.ident()) + } + ast::Arity::Repeated => w!(buf, "{}.len() as u32", flag.ident()), + }, + } + w!(buf, ",\n"); + } + if cmd.has_subcommands() { + w!(buf, "subcommand: p_.subcommand(sub_)?,\n"); + } + w!(buf, "}})\n"); + + w!(buf, "}}\n"); + w!(buf, "}}\n"); +} + +fn emit_help(buf: &mut String, xflags: &ast::XFlags) { + w!(buf, "impl {} {{\n", xflags.cmd.ident()); + + let help = { + let mut buf = String::new(); + help_rec(&mut buf, "", &xflags.cmd); + buf + }; + let help = format!("{:?}", help); + let help = help.replace("\\n", "\n").replacen("\"", "\"\\\n", 1); + + w!(buf, "const HELP_: &'static str = {};", help); + w!(buf, "}}\n"); +} + +fn write_lines_indented(buf: &mut String, multiline_str: &str, indent: usize) { + for line in multiline_str.split('\n').map(str::trim_end) { + if line.is_empty() { + w!(buf, "\n") + } else { + w!(buf, "{blank:indent$}{}\n", line, indent = indent, blank = ""); + } + } +} + +fn help_rec(buf: &mut String, prefix: &str, cmd: &ast::Cmd) { + w!(buf, "{}{}\n", prefix, cmd.name); + if let Some(doc) = &cmd.doc { + write_lines_indented(buf, doc, 2); + } + let indent = if prefix.is_empty() { "" } else { " " }; + + let args = cmd.args_with_default(); + if !args.is_empty() { + blank_line(buf); + w!(buf, "{}ARGS:\n", indent); + + let mut blank = ""; + for arg in &args { + w!(buf, "{}", blank); + blank = "\n"; + + let (l, r) = match arg.arity { + ast::Arity::Optional => ("[", "]"), + ast::Arity::Required => ("<", ">"), + ast::Arity::Repeated => ("<", ">..."), + }; + w!(buf, " {}{}{}\n", l, arg.val.name, r); + if let Some(doc) = &arg.doc { + write_lines_indented(buf, doc, 6) + } + } + } + + let flags = cmd.flags_with_default(); + if !flags.is_empty() { + blank_line(buf); + w!(buf, "{}OPTIONS:\n", indent); + + let mut blank = ""; + for flag in &flags { + w!(buf, "{}", blank); + blank = "\n"; + + let short = flag.short.as_ref().map(|it| format!("-{}, ", it)).unwrap_or_default(); + let value = flag.val.as_ref().map(|it| format!(" <{}>", it.name)).unwrap_or_default(); + w!(buf, " {}--{}{}\n", short, flag.name, value); + if let Some(doc) = &flag.doc { + write_lines_indented(buf, doc, 6); + } + } + } + + let subcommands = cmd.named_subcommands(); + if !subcommands.is_empty() { + if prefix.is_empty() { + blank_line(buf); + w!(buf, "SUBCOMMANDS:"); + } + + let prefix = format!("{}{} ", prefix, cmd.name); + for sub in subcommands { + blank_line(buf); + blank_line(buf); + help_rec(buf, &prefix, sub); + } + } +} + +impl ast::Cmd { + fn ident(&self) -> String { + camel(&self.name) + } + fn cmd_enum_ident(&self) -> String { + format!("{}Cmd", self.ident()) + } + fn has_subcommands(&self) -> bool { + !self.subcommands.is_empty() + } + fn named_subcommands(&self) -> &[ast::Cmd] { + let start = if self.default { 1 } else { 0 }; + &self.subcommands[start..] + } + fn default_subcommand(&self) -> Option<&ast::Cmd> { + if self.default { + self.subcommands.first() + } else { + None + } + } + fn args_with_default(&self) -> Vec<&ast::Arg> { + let mut res = self.args.iter().collect::<Vec<_>>(); + if let Some(sub) = self.default_subcommand() { + res.extend(sub.args_with_default()); + } + res + } + fn flags_with_default(&self) -> Vec<&ast::Flag> { + let mut res = self.flags.iter().collect::<Vec<_>>(); + if let Some(sub) = self.default_subcommand() { + res.extend(sub.flags_with_default()) + } + res + } +} + +impl ast::Flag { + fn ident(&self) -> String { + snake(&self.name) + } +} + +impl ast::Val { + fn ident(&self) -> String { + snake(&self.name) + } +} + +fn blank_line(buf: &mut String) { + w!(buf, "\n"); +} + +fn camel(s: &str) -> String { + s.split('-').map(first_upper).collect() +} + +fn first_upper(s: &str) -> String { + s.chars() + .next() + .map(|it| it.to_ascii_uppercase()) + .into_iter() + .chain(s.chars().skip(1)) + .collect() +} + +fn snake(s: &str) -> String { + s.replace('-', "_") +} + +#[cfg(test)] +mod tests { + use std::{ + fs, + io::Write, + path::Path, + process::{Command, Stdio}, + }; + + fn reformat(text: String) -> Option<String> { + let mut rustfmt = + Command::new("rustfmt").stdin(Stdio::piped()).stdout(Stdio::piped()).spawn().unwrap(); + let mut stdin = rustfmt.stdin.take().unwrap(); + stdin.write_all(text.as_bytes()).unwrap(); + drop(stdin); + let out = rustfmt.wait_with_output().unwrap(); + let res = String::from_utf8(out.stdout).unwrap(); + if res.is_empty() { + None + } else { + Some(res) + } + } + + fn update_on_disk_if_different(file: &Path, new_contents: String) -> bool { + let old_contents = fs::read_to_string(file).unwrap_or_default(); + if old_contents.trim() == new_contents.trim() { + return false; + } + fs::write(file, new_contents).unwrap(); + true + } + + #[test] + fn gen_it() { + let test_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/it"); + + let mut did_update = false; + for entry in fs::read_dir(test_dir.join("src")).unwrap() { + let entry = entry.unwrap(); + + let text = fs::read_to_string(entry.path()).unwrap(); + let mut lines = text.lines().collect::<Vec<_>>(); + lines.pop(); + lines.remove(0); + let text = lines.join("\n"); + + let res = crate::compile(&text); + let fmt = reformat(res.clone()); + + let code = format!( + "#![allow(unused)]\nuse std::{{ffi::OsString, path::PathBuf}};\n\n{}", + fmt.as_deref().unwrap_or(&res) + ); + + let name = entry.file_name(); + did_update |= update_on_disk_if_different(&test_dir.join(name), code); + + if fmt.is_none() { + panic!("syntax error"); + } + } + if did_update { + panic!("generated output changed") + } + } +} diff --git a/vendor/xflags-macros/src/lib.rs b/vendor/xflags-macros/src/lib.rs new file mode 100644 index 000000000..8fac00587 --- /dev/null +++ b/vendor/xflags-macros/src/lib.rs @@ -0,0 +1,26 @@ +mod ast; +mod parse; +mod emit; +mod update; + +#[proc_macro] +pub fn xflags(_ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Stub out the code, but let rust-analyzer resolve the invocation + #[cfg(not(test))] + { + let cmd = parse::parse(_ts).unwrap(); + let text = emit::emit(&cmd); + text.parse().unwrap() + } + #[cfg(test)] + unimplemented!() +} + +#[cfg(test)] +pub fn compile(src: &str) -> String { + use proc_macro2::TokenStream; + + let ts = src.parse::<TokenStream>().unwrap(); + let cmd = parse::parse(ts).unwrap(); + emit::emit(&cmd) +} diff --git a/vendor/xflags-macros/src/parse.rs b/vendor/xflags-macros/src/parse.rs new file mode 100644 index 000000000..e9749b141 --- /dev/null +++ b/vendor/xflags-macros/src/parse.rs @@ -0,0 +1,340 @@ +use std::mem; + +#[cfg(not(test))] +use proc_macro::{Delimiter, TokenStream, TokenTree}; +#[cfg(test)] +use proc_macro2::{Delimiter, TokenStream, TokenTree}; + +use crate::ast; + +type Result<T, E = Error> = std::result::Result<T, E>; + +#[derive(Debug)] +pub(crate) struct Error { + msg: String, +} + +impl std::error::Error for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.msg) + } +} + +pub(crate) fn parse(ts: TokenStream) -> Result<ast::XFlags> { + let mut p = Parser::new(ts); + xflags(&mut p) +} + +macro_rules! format_err { + ($($tt:tt)*) => { + Error { msg: format!($($tt)*) } + // panic!($($tt)*) + }; +} + +macro_rules! bail { + ($($tt:tt)*) => { + return Err(format_err!($($tt)*)) + }; +} + +fn xflags(p: &mut Parser) -> Result<ast::XFlags> { + let src = if p.eat_keyword("src") { Some(p.expect_string()?) } else { None }; + let doc = opt_doc(p)?; + let mut cmd = cmd(p)?; + cmd.doc = doc; + let res = ast::XFlags { src, cmd }; + Ok(res) +} + +fn cmd(p: &mut Parser) -> Result<ast::Cmd> { + p.expect_keyword("cmd")?; + + let name = cmd_name(p)?; + let mut res = ast::Cmd { + name, + doc: None, + args: Vec::new(), + flags: Vec::new(), + subcommands: Vec::new(), + default: false, + }; + + while !p.at_delim(Delimiter::Brace) { + let doc = opt_doc(p)?; + let arity = arity(p)?; + match opt_val(p)? { + Some(val) => { + let arg = ast::Arg { arity, doc, val }; + res.args.push(arg); + } + None => bail!("expected ident"), + } + } + + p.enter_delim(Delimiter::Brace)?; + while !p.end() { + let doc = opt_doc(p)?; + let default = p.eat_keyword("default"); + if default || p.at_keyword("cmd") { + let mut cmd = cmd(p)?; + cmd.doc = doc; + res.subcommands.push(cmd); + if default { + if res.default { + bail!("only one subcommand can be default") + } + res.default = true; + res.subcommands.rotate_right(1); + } + } else { + let mut flag = flag(p)?; + flag.doc = doc; + res.flags.push(flag); + } + } + p.exit_delim()?; + Ok(res) +} + +fn flag(p: &mut Parser) -> Result<ast::Flag> { + let arity = arity(p)?; + + let mut short = None; + let mut name = flag_name(p)?; + if !name.starts_with("--") { + short = Some(name); + if !p.eat_punct(',') { + bail!("long option is required for `{}`", short.unwrap()); + } + name = flag_name(p)?; + if !name.starts_with("--") { + bail!("long name must begin with `--`: `{}`", name); + } + } + + let val = opt_val(p)?; + Ok(ast::Flag { + arity, + name: name[2..].to_string(), + short: short.map(|it| it[1..].to_string()), + doc: None, + val, + }) +} + +fn opt_val(p: &mut Parser) -> Result<Option<ast::Val>, Error> { + if !p.lookahead_punct(':', 1) { + return Ok(None); + } + + let name = p.expect_name()?; + p.expect_punct(':')?; + let ty = ty(p)?; + let res = ast::Val { name, ty }; + Ok(Some(res)) +} + +fn arity(p: &mut Parser) -> Result<ast::Arity> { + if p.eat_keyword("optional") { + return Ok(ast::Arity::Optional); + } + if p.eat_keyword("required") { + return Ok(ast::Arity::Required); + } + if p.eat_keyword("repeated") { + return Ok(ast::Arity::Repeated); + } + if let Some(name) = p.eat_name() { + bail!("expected one of `optional`, `required`, `repeated`, got `{}`", name) + } + bail!("expected one of `optional`, `required`, `repeated`, got {:?}", p.ts.pop()) +} + +fn ty(p: &mut Parser) -> Result<ast::Ty> { + let name = p.expect_name()?; + let res = match name.as_str() { + "PathBuf" => ast::Ty::PathBuf, + "OsString" => ast::Ty::OsString, + _ => ast::Ty::FromStr(name), + }; + Ok(res) +} + +fn opt_single_doc(p: &mut Parser) -> Result<Option<String>> { + if !p.eat_punct('#') { + return Ok(None); + } + p.enter_delim(Delimiter::Bracket)?; + p.expect_keyword("doc")?; + p.expect_punct('=')?; + let mut res = p.expect_string()?; + if let Some(suf) = res.strip_prefix(' ') { + res = suf.to_string(); + } + p.exit_delim()?; + Ok(Some(res)) +} + +fn opt_doc(p: &mut Parser) -> Result<Option<String>> { + let lines = + core::iter::from_fn(|| opt_single_doc(p).transpose()).collect::<Result<Vec<String>>>()?; + let lines = lines.join("\n"); + + if lines.is_empty() { + Ok(None) + } else { + Ok(Some(lines)) + } +} + +fn cmd_name(p: &mut Parser) -> Result<String> { + let name = p.expect_name()?; + if name.starts_with('-') { + bail!("command name can't begin with `-`: `{}`", name); + } + Ok(name) +} + +fn flag_name(p: &mut Parser) -> Result<String> { + let name = p.expect_name()?; + if !name.starts_with('-') { + bail!("flag name should begin with `-`: `{}`", name); + } + Ok(name) +} + +struct Parser { + stack: Vec<Vec<TokenTree>>, + ts: Vec<TokenTree>, +} + +impl Parser { + fn new(ts: TokenStream) -> Self { + let mut ts = ts.into_iter().collect::<Vec<_>>(); + ts.reverse(); + Self { stack: Vec::new(), ts } + } + + fn at_delim(&mut self, delimiter: Delimiter) -> bool { + match self.ts.last() { + Some(TokenTree::Group(g)) => g.delimiter() == delimiter, + _ => false, + } + } + fn enter_delim(&mut self, delimiter: Delimiter) -> Result<()> { + match self.ts.pop() { + Some(TokenTree::Group(g)) if g.delimiter() == delimiter => { + let mut ts = g.stream().into_iter().collect::<Vec<_>>(); + ts.reverse(); + let ts = mem::replace(&mut self.ts, ts); + self.stack.push(ts); + } + _ => bail!("expected `{{`"), + } + Ok(()) + } + fn exit_delim(&mut self) -> Result<()> { + if !self.end() { + bail!("expected `}}`") + } + self.ts = self.stack.pop().unwrap(); + Ok(()) + } + fn end(&mut self) -> bool { + self.ts.last().is_none() + } + + fn expect_keyword(&mut self, kw: &str) -> Result<()> { + if !self.eat_keyword(kw) { + bail!("expected `{}`", kw) + } + Ok(()) + } + fn eat_keyword(&mut self, kw: &str) -> bool { + if self.at_keyword(kw) { + self.ts.pop().unwrap(); + true + } else { + false + } + } + fn at_keyword(&mut self, kw: &str) -> bool { + match self.ts.last() { + Some(TokenTree::Ident(ident)) => &ident.to_string() == kw, + _ => false, + } + } + + fn expect_name(&mut self) -> Result<String> { + self.eat_name().ok_or_else(|| { + let next = self.ts.pop().map(|it| it.to_string()).unwrap_or_default(); + format_err!("expected a name, got: `{}`", next) + }) + } + fn eat_name(&mut self) -> Option<String> { + let mut buf = String::new(); + let mut prev_ident = false; + loop { + match self.ts.last() { + Some(TokenTree::Punct(p)) if p.as_char() == '-' => { + prev_ident = false; + buf.push('-'); + } + Some(TokenTree::Ident(ident)) if !prev_ident => { + prev_ident = true; + buf.push_str(&ident.to_string()); + } + _ => break, + } + self.ts.pop(); + } + if buf.is_empty() { + None + } else { + Some(buf) + } + } + + fn _expect_ident(&mut self) -> Result<String> { + match self.ts.pop() { + Some(TokenTree::Ident(ident)) => Ok(ident.to_string()), + _ => bail!("expected ident"), + } + } + + fn expect_punct(&mut self, punct: char) -> Result<()> { + if !self.eat_punct(punct) { + bail!("expected `{}`", punct) + } + Ok(()) + } + fn eat_punct(&mut self, punct: char) -> bool { + match self.ts.last() { + Some(TokenTree::Punct(p)) if p.as_char() == punct => { + self.ts.pop(); + true + } + _ => false, + } + } + fn lookahead_punct(&mut self, punct: char, n: usize) -> bool { + match self.ts.iter().rev().nth(n) { + Some(TokenTree::Punct(p)) => p.as_char() == punct, + _ => false, + } + } + + fn expect_string(&mut self) -> Result<String> { + match self.ts.pop() { + Some(TokenTree::Literal(lit)) if lit.to_string().starts_with('"') => { + let text = lit.to_string(); + let res = text.trim_matches('"').to_string(); + Ok(res) + } + _ => bail!("expected a string"), + } + } +} diff --git a/vendor/xflags-macros/src/update.rs b/vendor/xflags-macros/src/update.rs new file mode 100644 index 000000000..e476e37aa --- /dev/null +++ b/vendor/xflags-macros/src/update.rs @@ -0,0 +1,98 @@ +use std::{fs, ops::Range, path::Path}; + +pub(crate) fn in_place(api: &str, path: &Path) { + let path = { + let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + Path::new(&dir).join(path) + }; + + let mut text = + fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read {:?}", path)); + + let (insert_to, indent) = locate(&text); + + let api: String = + with_preamble(api) + .lines() + .map(|it| { + if it.trim().is_empty() { + "\n".to_string() + } else { + format!("{}{}\n", indent, it) + } + }) + .collect(); + text.replace_range(insert_to, &api); + + fs::write(&path, text.as_bytes()).unwrap(); +} + +pub(crate) fn stdout(api: &str) { + print!("{}", with_preamble(api)) +} + +fn with_preamble(api: &str) -> String { + format!( + "\ +// generated start +// The following code is generated by `xflags` macro. +// Run `env UPDATE_XFLAGS=1 cargo build` to regenerate. +{} +// generated end +", + api.trim() + ) +} + +fn locate(text: &str) -> (Range<usize>, String) { + if let Some(it) = locate_existing(text) { + return it; + } + if let Some(it) = locate_new(text) { + return it; + } + panic!("failed to update xflags in place") +} + +fn locate_existing(text: &str) -> Option<(Range<usize>, String)> { + let start_idx = text.find("// generated start")?; + let start_idx = newline_before(text, start_idx); + + let end_idx = text.find("// generated end")?; + let end_idx = newline_after(text, end_idx); + + let indent = indent_at(text, start_idx); + + Some((start_idx..end_idx, indent)) +} + +fn newline_before(text: &str, start_idx: usize) -> usize { + text[..start_idx].rfind('\n').map_or(start_idx, |it| it + 1) +} + +fn newline_after(text: &str, start_idx: usize) -> usize { + start_idx + text[start_idx..].find('\n').map_or(text[start_idx..].len(), |it| it + 1) +} + +fn indent_at(text: &str, start_idx: usize) -> String { + text[start_idx..].chars().take_while(|&it| it == ' ').collect() +} + +fn locate_new(text: &str) -> Option<(Range<usize>, String)> { + let mut idx = text.find("xflags!")?; + let mut lvl = 0i32; + for c in text[idx..].chars() { + idx += c.len_utf8(); + match c { + '{' => lvl += 1, + '}' if lvl == 1 => break, + '}' => lvl -= 1, + _ => (), + } + } + let indent = indent_at(text, newline_before(text, idx)); + if text[idx..].starts_with('\n') { + idx += 1; + } + Some((idx..idx, indent)) +} diff --git a/vendor/xflags-macros/tests/it/help.rs b/vendor/xflags-macros/tests/it/help.rs new file mode 100644 index 000000000..36a966485 --- /dev/null +++ b/vendor/xflags-macros/tests/it/help.rs @@ -0,0 +1,144 @@ +#![allow(unused)] +use std::{ffi::OsString, path::PathBuf}; + +#[derive(Debug)] +pub struct Helpful { + pub src: Option<PathBuf>, + pub extra: Option<String>, + + pub switch: (), + pub subcommand: HelpfulCmd, +} + +#[derive(Debug)] +pub enum HelpfulCmd { + Sub(Sub), +} + +#[derive(Debug)] +pub struct Sub { + pub flag: bool, +} + +impl Helpful { + pub const HELP: &'static str = Self::HELP_; + + #[allow(dead_code)] + pub fn from_env() -> xflags::Result<Self> { + Self::from_env_() + } + + #[allow(dead_code)] + pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + Self::from_vec_(args) + } +} + +impl Helpful { + fn from_env_() -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new_from_env(); + Self::parse_(&mut p) + } + fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new(args); + Self::parse_(&mut p) + } +} + +impl Helpful { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut switch = Vec::new(); + + let mut src = (false, Vec::new()); + let mut extra = (false, Vec::new()); + + let mut sub_ = None; + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--switch" | "-s" => switch.push(()), + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + match arg_.to_str().unwrap_or("") { + "sub" => { + sub_ = Some(HelpfulCmd::Sub(Sub::parse_(p_)?)); + break; + } + _ => (), + } + if let (done_ @ false, buf_) = &mut src { + buf_.push(arg_.into()); + *done_ = true; + continue; + } + if let (done_ @ false, buf_) = &mut extra { + buf_.push(p_.value_from_str::<String>("extra", arg_)?); + *done_ = true; + continue; + } + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { + src: p_.optional("src", src.1)?, + extra: p_.optional("extra", extra.1)?, + + switch: p_.required("--switch", switch)?, + subcommand: p_.subcommand(sub_)?, + }) + } +} + +impl Sub { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut flag = Vec::new(); + + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--flag" | "-f" => flag.push(()), + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { flag: p_.optional("--flag", flag)?.is_some() }) + } +} +impl Helpful { + const HELP_: &'static str = "\ +helpful + Does stuff + + Helpful stuff. + +ARGS: + [src] + With an arg. + + [extra] + Another arg. + + This time, we provide some extra info about the + arg. Maybe some caveats, or what kinds of + values are accepted. + +OPTIONS: + -s, --switch + And a switch. + +SUBCOMMANDS: + +helpful sub + And even a subcommand! + + OPTIONS: + -f, --flag + With an optional flag. This has a really long + description which spans multiple lines. +"; +} diff --git a/vendor/xflags-macros/tests/it/main.rs b/vendor/xflags-macros/tests/it/main.rs new file mode 100644 index 000000000..1ce058814 --- /dev/null +++ b/vendor/xflags-macros/tests/it/main.rs @@ -0,0 +1,198 @@ +mod repeated_pos; +mod smoke; +mod subcommands; +mod help; + +use std::{ffi::OsString, fmt}; + +use expect_test::{expect, Expect}; + +fn check<F, A>(f: F, args: &str, expect: Expect) +where + F: FnOnce(Vec<OsString>) -> xflags::Result<A>, + A: fmt::Debug, +{ + let args = args.split_ascii_whitespace().map(OsString::from).collect::<Vec<_>>(); + let res = f(args); + match res { + Ok(args) => { + expect.assert_debug_eq(&args); + } + Err(err) => { + expect.assert_eq(&err.to_string()); + } + } +} + +#[test] +fn smoke() { + check( + smoke::RustAnalyzer::from_vec, + "-n 92 .", + expect![[r#" + RustAnalyzer { + workspace: ".", + jobs: None, + log_file: None, + verbose: 0, + number: 92, + data: [], + emoji: false, + } + "#]], + ); + check( + smoke::RustAnalyzer::from_vec, + "-n 92 -v --verbose -v --data 0xDEAD --log-file /tmp/log.txt --data 0xBEEF .", + expect![[r#" + RustAnalyzer { + workspace: ".", + jobs: None, + log_file: Some( + "/tmp/log.txt", + ), + verbose: 3, + number: 92, + data: [ + "0xDEAD", + "0xBEEF", + ], + emoji: false, + } + "#]], + ); + + check( + smoke::RustAnalyzer::from_vec, + "-n 92 --werbose", + expect![[r#"unexpected flag: `--werbose`"#]], + ); + check(smoke::RustAnalyzer::from_vec, "", expect![[r#"flag is required: `workspace`"#]]); + check(smoke::RustAnalyzer::from_vec, ".", expect![[r#"flag is required: `--number`"#]]); + check(smoke::RustAnalyzer::from_vec, "-n", expect![[r#"expected a value for `-n`"#]]); + check( + smoke::RustAnalyzer::from_vec, + "-n lol", + expect![[r#"can't parse `-n`, invalid digit found in string"#]], + ); + check( + smoke::RustAnalyzer::from_vec, + "-n 1 -n 2 .", + expect![[r#"flag specified more than once: `--number`"#]], + ); + check( + smoke::RustAnalyzer::from_vec, + "-n 1 . 92 lol", + expect![[r#"unexpected argument: "lol""#]], + ); + check( + smoke::RustAnalyzer::from_vec, + "-n 1 . --emoji --emoji", + expect![[r#"flag specified more than once: `--emoji`"#]], + ); +} + +#[test] +fn repeated_argument() { + check( + repeated_pos::RepeatedPos::from_vec, + "a 11 c d e f", + expect![[r#" + RepeatedPos { + a: "a", + b: Some( + 11, + ), + c: Some( + "c", + ), + rest: [ + "d", + "e", + "f", + ], + } + "#]], + ); +} + +#[test] +fn subcommands() { + check( + subcommands::RustAnalyzer::from_vec, + "server", + expect![[r#" + RustAnalyzer { + verbose: 0, + subcommand: Server( + Server { + dir: None, + subcommand: Launch( + Launch { + log: false, + }, + ), + }, + ), + } + "#]], + ); + + check( + subcommands::RustAnalyzer::from_vec, + "server --dir . --log", + expect![[r#" + RustAnalyzer { + verbose: 0, + subcommand: Server( + Server { + dir: Some( + ".", + ), + subcommand: Launch( + Launch { + log: true, + }, + ), + }, + ), + } + "#]], + ); + + check( + subcommands::RustAnalyzer::from_vec, + "server watch", + expect![[r#" + RustAnalyzer { + verbose: 0, + subcommand: Server( + Server { + dir: None, + subcommand: Watch( + Watch, + ), + }, + ), + } + "#]], + ); + + check( + subcommands::RustAnalyzer::from_vec, + "-v analysis-stats . --parallel", + expect![[r#" + RustAnalyzer { + verbose: 1, + subcommand: AnalysisStats( + AnalysisStats { + path: ".", + parallel: true, + }, + ), + } + "#]], + ); + + check(subcommands::RustAnalyzer::from_vec, "", expect![[r#"subcommand is required"#]]); +} diff --git a/vendor/xflags-macros/tests/it/repeated_pos.rs b/vendor/xflags-macros/tests/it/repeated_pos.rs new file mode 100644 index 000000000..334af371d --- /dev/null +++ b/vendor/xflags-macros/tests/it/repeated_pos.rs @@ -0,0 +1,94 @@ +#![allow(unused)] +use std::{ffi::OsString, path::PathBuf}; + +#[derive(Debug)] +pub struct RepeatedPos { + pub a: PathBuf, + pub b: Option<u32>, + pub c: Option<OsString>, + pub rest: Vec<OsString>, +} + +impl RepeatedPos { + pub const HELP: &'static str = Self::HELP_; + + #[allow(dead_code)] + pub fn from_env() -> xflags::Result<Self> { + Self::from_env_() + } + + #[allow(dead_code)] + pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + Self::from_vec_(args) + } +} + +impl RepeatedPos { + fn from_env_() -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new_from_env(); + Self::parse_(&mut p) + } + fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new(args); + Self::parse_(&mut p) + } +} + +impl RepeatedPos { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut a = (false, Vec::new()); + let mut b = (false, Vec::new()); + let mut c = (false, Vec::new()); + let mut rest = (false, Vec::new()); + + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + if let (done_ @ false, buf_) = &mut a { + buf_.push(arg_.into()); + *done_ = true; + continue; + } + if let (done_ @ false, buf_) = &mut b { + buf_.push(p_.value_from_str::<u32>("b", arg_)?); + *done_ = true; + continue; + } + if let (done_ @ false, buf_) = &mut c { + buf_.push(arg_.into()); + *done_ = true; + continue; + } + if let (false, buf_) = &mut rest { + buf_.push(arg_.into()); + continue; + } + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { + a: p_.required("a", a.1)?, + b: p_.optional("b", b.1)?, + c: p_.optional("c", c.1)?, + rest: rest.1, + }) + } +} +impl RepeatedPos { + const HELP_: &'static str = "\ +RepeatedPos + +ARGS: + <a> + + [b] + + [c] + + <rest>... +"; +} diff --git a/vendor/xflags-macros/tests/it/smoke.rs b/vendor/xflags-macros/tests/it/smoke.rs new file mode 100644 index 000000000..e22c4f1f6 --- /dev/null +++ b/vendor/xflags-macros/tests/it/smoke.rs @@ -0,0 +1,112 @@ +#![allow(unused)] +use std::{ffi::OsString, path::PathBuf}; + +#[derive(Debug)] +pub struct RustAnalyzer { + pub workspace: PathBuf, + pub jobs: Option<u32>, + + pub log_file: Option<PathBuf>, + pub verbose: u32, + pub number: u32, + pub data: Vec<OsString>, + pub emoji: bool, +} + +impl RustAnalyzer { + pub const HELP: &'static str = Self::HELP_; + + #[allow(dead_code)] + pub fn from_env() -> xflags::Result<Self> { + Self::from_env_() + } + + #[allow(dead_code)] + pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + Self::from_vec_(args) + } +} + +impl RustAnalyzer { + fn from_env_() -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new_from_env(); + Self::parse_(&mut p) + } + fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new(args); + Self::parse_(&mut p) + } +} + +impl RustAnalyzer { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut log_file = Vec::new(); + let mut verbose = Vec::new(); + let mut number = Vec::new(); + let mut data = Vec::new(); + let mut emoji = Vec::new(); + + let mut workspace = (false, Vec::new()); + let mut jobs = (false, Vec::new()); + + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--log-file" => log_file.push(p_.next_value(&flag_)?.into()), + "--verbose" | "-v" => verbose.push(()), + "--number" | "-n" => number.push(p_.next_value_from_str::<u32>(&flag_)?), + "--data" => data.push(p_.next_value(&flag_)?.into()), + "--emoji" => emoji.push(()), + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + if let (done_ @ false, buf_) = &mut workspace { + buf_.push(arg_.into()); + *done_ = true; + continue; + } + if let (done_ @ false, buf_) = &mut jobs { + buf_.push(p_.value_from_str::<u32>("jobs", arg_)?); + *done_ = true; + continue; + } + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { + workspace: p_.required("workspace", workspace.1)?, + jobs: p_.optional("jobs", jobs.1)?, + + log_file: p_.optional("--log-file", log_file)?, + verbose: verbose.len() as u32, + number: p_.required("--number", number)?, + data: data, + emoji: p_.optional("--emoji", emoji)?.is_some(), + }) + } +} +impl RustAnalyzer { + const HELP_: &'static str = "\ +rust-analyzer + LSP server for rust. + +ARGS: + <workspace> + + [jobs] + Number of concurrent jobs. + +OPTIONS: + --log-file <path> + Path to log file. By default, logs go to stderr. + + -v, --verbose + + -n, --number <n> + + --data <value> + + --emoji +"; +} diff --git a/vendor/xflags-macros/tests/it/src/help.rs b/vendor/xflags-macros/tests/it/src/help.rs new file mode 100644 index 000000000..d552c1e63 --- /dev/null +++ b/vendor/xflags-macros/tests/it/src/help.rs @@ -0,0 +1,25 @@ +xflags! { + /// Does stuff + /// + /// Helpful stuff. + cmd helpful + /// With an arg. + optional src: PathBuf + /// Another arg. + /// + /// This time, we provide some extra info about the + /// arg. Maybe some caveats, or what kinds of + /// values are accepted. + optional extra: String + { + /// And a switch. + required -s, --switch + + /// And even a subcommand! + cmd sub { + /// With an optional flag. This has a really long + /// description which spans multiple lines. + optional -f, --flag + } + } +} diff --git a/vendor/xflags-macros/tests/it/src/repeated_pos.rs b/vendor/xflags-macros/tests/it/src/repeated_pos.rs new file mode 100644 index 000000000..4106c65eb --- /dev/null +++ b/vendor/xflags-macros/tests/it/src/repeated_pos.rs @@ -0,0 +1,9 @@ +xflags! { + cmd RepeatedPos + required a: PathBuf + optional b: u32 + optional c: OsString + repeated rest: OsString + { + } +} diff --git a/vendor/xflags-macros/tests/it/src/smoke.rs b/vendor/xflags-macros/tests/it/src/smoke.rs new file mode 100644 index 000000000..ae303779e --- /dev/null +++ b/vendor/xflags-macros/tests/it/src/smoke.rs @@ -0,0 +1,15 @@ +xflags! { + /// LSP server for rust. + cmd rust-analyzer + required workspace: PathBuf + /// Number of concurrent jobs. + optional jobs: u32 + { + /// Path to log file. By default, logs go to stderr. + optional --log-file path: PathBuf + repeated -v, --verbose + required -n, --number n: u32 + repeated --data value: OsString + optional --emoji + } +} diff --git a/vendor/xflags-macros/tests/it/src/subcommands.rs b/vendor/xflags-macros/tests/it/src/subcommands.rs new file mode 100644 index 000000000..70a0a5049 --- /dev/null +++ b/vendor/xflags-macros/tests/it/src/subcommands.rs @@ -0,0 +1,20 @@ +xflags! { + cmd rust-analyzer { + repeated -v, --verbose + + cmd server { + optional --dir path:PathBuf + default cmd launch { + optional --log + } + cmd watch { + } + } + + cmd analysis-stats + required path: PathBuf + { + optional --parallel + } + } +} diff --git a/vendor/xflags-macros/tests/it/subcommands.rs b/vendor/xflags-macros/tests/it/subcommands.rs new file mode 100644 index 000000000..7941a395d --- /dev/null +++ b/vendor/xflags-macros/tests/it/subcommands.rs @@ -0,0 +1,225 @@ +#![allow(unused)] +use std::{ffi::OsString, path::PathBuf}; + +#[derive(Debug)] +pub struct RustAnalyzer { + pub verbose: u32, + pub subcommand: RustAnalyzerCmd, +} + +#[derive(Debug)] +pub enum RustAnalyzerCmd { + Server(Server), + AnalysisStats(AnalysisStats), +} + +#[derive(Debug)] +pub struct Server { + pub dir: Option<PathBuf>, + pub subcommand: ServerCmd, +} + +#[derive(Debug)] +pub enum ServerCmd { + Launch(Launch), + Watch(Watch), +} + +#[derive(Debug)] +pub struct Launch { + pub log: bool, +} + +#[derive(Debug)] +pub struct Watch; + +#[derive(Debug)] +pub struct AnalysisStats { + pub path: PathBuf, + + pub parallel: bool, +} + +impl RustAnalyzer { + pub const HELP: &'static str = Self::HELP_; + + #[allow(dead_code)] + pub fn from_env() -> xflags::Result<Self> { + Self::from_env_() + } + + #[allow(dead_code)] + pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + Self::from_vec_(args) + } +} + +impl RustAnalyzer { + fn from_env_() -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new_from_env(); + Self::parse_(&mut p) + } + fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> { + let mut p = xflags::rt::Parser::new(args); + Self::parse_(&mut p) + } +} + +impl RustAnalyzer { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut verbose = Vec::new(); + + let mut sub_ = None; + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--verbose" | "-v" => verbose.push(()), + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + match arg_.to_str().unwrap_or("") { + "server" => { + sub_ = Some(RustAnalyzerCmd::Server(Server::parse_(p_)?)); + break; + } + "analysis-stats" => { + sub_ = Some(RustAnalyzerCmd::AnalysisStats(AnalysisStats::parse_(p_)?)); + break; + } + _ => (), + } + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { verbose: verbose.len() as u32, subcommand: p_.subcommand(sub_)? }) + } +} + +impl Server { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut dir = Vec::new(); + + let mut sub_ = None; + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--dir" => dir.push(p_.next_value(&flag_)?.into()), + _ => { + p_.push_back(Ok(flag_)); + break; + } + }, + Err(arg_) => { + match arg_.to_str().unwrap_or("") { + "watch" => { + sub_ = Some(ServerCmd::Watch(Watch::parse_(p_)?)); + break; + } + _ => (), + } + p_.push_back(Err(arg_)); + break; + } + } + } + if sub_.is_none() { + sub_ = Some(ServerCmd::Launch(Launch::parse_(p_)?)); + } + Ok(Self { dir: p_.optional("--dir", dir)?, subcommand: p_.subcommand(sub_)? }) + } +} + +impl Launch { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut log = Vec::new(); + + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--log" => log.push(()), + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { log: p_.optional("--log", log)?.is_some() }) + } +} + +impl Watch { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self {}) + } +} + +impl AnalysisStats { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> { + let mut parallel = Vec::new(); + + let mut path = (false, Vec::new()); + + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match flag_.as_str() { + "--parallel" => parallel.push(()), + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => { + if let (done_ @ false, buf_) = &mut path { + buf_.push(arg_.into()); + *done_ = true; + continue; + } + return Err(p_.unexpected_arg(arg_)); + } + } + } + Ok(Self { + path: p_.required("path", path.1)?, + + parallel: p_.optional("--parallel", parallel)?.is_some(), + }) + } +} +impl RustAnalyzer { + const HELP_: &'static str = "\ +rust-analyzer + +OPTIONS: + -v, --verbose + +SUBCOMMANDS: + +rust-analyzer server + + OPTIONS: + --dir <path> + + --log + + +rust-analyzer server watch + + +rust-analyzer analysis-stats + + ARGS: + <path> + + OPTIONS: + --parallel +"; +} |