summaryrefslogtreecommitdiffstats
path: root/vendor/xflags-macros/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/xflags-macros/src
parentInitial commit. (diff)
downloadrustc-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/src')
-rw-r--r--vendor/xflags-macros/src/ast.rs51
-rw-r--r--vendor/xflags-macros/src/emit.rs544
-rw-r--r--vendor/xflags-macros/src/lib.rs26
-rw-r--r--vendor/xflags-macros/src/parse.rs340
-rw-r--r--vendor/xflags-macros/src/update.rs98
5 files changed, 1059 insertions, 0 deletions
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))
+}