mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
start reading help file file
This commit is contained in:
+5
-126
@@ -2,22 +2,18 @@ use std::ops::RangeInclusive;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
punctuated::Punctuated, Attribute, Fields, FieldsUnnamed, Ident, Lit, LitStr, Meta, Token,
|
||||
Variant,
|
||||
};
|
||||
use syn::{Attribute, Fields, FieldsUnnamed, Ident, Lit, Meta, Variant};
|
||||
|
||||
use crate::{
|
||||
attributes::{parse_argument_attribute, ArgAttr},
|
||||
flags::{Flags, Value},
|
||||
markdown::str_to_renderer,
|
||||
};
|
||||
|
||||
pub(crate) struct Argument {
|
||||
ident: Ident,
|
||||
name: String,
|
||||
arg_type: ArgType,
|
||||
help: String,
|
||||
pub(crate) ident: Ident,
|
||||
pub(crate) name: String,
|
||||
pub(crate) arg_type: ArgType,
|
||||
pub(crate) help: String,
|
||||
}
|
||||
|
||||
pub(crate) enum ArgType {
|
||||
@@ -31,40 +27,6 @@ pub(crate) enum ArgType {
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn parse_help_flags(attrs: &[Attribute]) -> Flags {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("help") {
|
||||
let mut flags = Flags::new();
|
||||
for s in attr
|
||||
.parse_args_with(Punctuated::<LitStr, Token![,]>::parse_terminated)
|
||||
.unwrap()
|
||||
{
|
||||
let s = s.value().to_string();
|
||||
flags.add(&s);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
Flags::default_help()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_version_flags(attrs: &[Attribute]) -> Flags {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("version") {
|
||||
let mut flags = Flags::new();
|
||||
for s in attr
|
||||
.parse_args_with(Punctuated::<LitStr, Token![,]>::parse_terminated)
|
||||
.unwrap()
|
||||
{
|
||||
let s = s.value().to_string();
|
||||
flags.add(&s);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
Flags::default_version()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_argument(v: Variant) -> Option<Argument> {
|
||||
let ident = v.ident;
|
||||
let name = ident.to_string();
|
||||
@@ -353,20 +315,6 @@ fn argument_expression(arg: &Argument) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn help_handling(help_flags: &Flags) -> TokenStream {
|
||||
if help_flags.is_empty() {
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let pat = help_flags.pat();
|
||||
|
||||
quote!(
|
||||
if let #pat = arg {
|
||||
return Ok(Some(Argument::Help));
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn version_handling(version_flags: &Flags) -> TokenStream {
|
||||
if version_flags.is_empty() {
|
||||
return quote!();
|
||||
@@ -380,72 +328,3 @@ pub(crate) fn version_handling(version_flags: &Flags) -> TokenStream {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn help_string(
|
||||
args: &[Argument],
|
||||
help_flags: &Flags,
|
||||
version_flags: &Flags,
|
||||
) -> TokenStream {
|
||||
let mut options = Vec::new();
|
||||
|
||||
let width: usize = 16;
|
||||
let indent: usize = 2;
|
||||
|
||||
for Argument { arg_type, help, .. } in args {
|
||||
match arg_type {
|
||||
ArgType::Option { flags, .. } => {
|
||||
let flags = flags.format();
|
||||
let renderer = str_to_renderer(help);
|
||||
options.push(quote!((#flags, #renderer)));
|
||||
}
|
||||
ArgType::Positional { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !help_flags.is_empty() {
|
||||
let flags = help_flags.format();
|
||||
let renderer = str_to_renderer("Display this help message");
|
||||
options.push(quote!((#flags, #renderer)));
|
||||
}
|
||||
|
||||
if !version_flags.is_empty() {
|
||||
let flags = version_flags.format();
|
||||
let renderer = str_to_renderer("Display version information");
|
||||
options.push(quote!((#flags, #renderer)));
|
||||
}
|
||||
|
||||
let options = quote!([#(#options),*]);
|
||||
|
||||
quote!(
|
||||
let mut s = format!("{} [OPTIONS] [ARGS]\n\nOptions:\n", bin_name);
|
||||
for (flags, renderer) in #options {
|
||||
let indent = " ".repeat(#indent);
|
||||
|
||||
let help_string = renderer.render();
|
||||
let mut help_lines = help_string.lines();
|
||||
s.push_str(&indent);
|
||||
s.push_str(&flags);
|
||||
|
||||
if flags.len() <= #width {
|
||||
let line = match help_lines.next() {
|
||||
Some(line) => line,
|
||||
None => return s,
|
||||
};
|
||||
let help_indent = " ".repeat(#width-flags.len());
|
||||
s.push_str(&help_indent);
|
||||
s.push_str(line);
|
||||
s.push('\n');
|
||||
} else {
|
||||
s.push('\n');
|
||||
}
|
||||
|
||||
let help_indent = " ".repeat(#width+#indent);
|
||||
for line in help_lines {
|
||||
s.push_str(&help_indent);
|
||||
s.push_str(line);
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
s
|
||||
)
|
||||
}
|
||||
|
||||
+114
-110
@@ -15,27 +15,58 @@ pub(crate) enum ArgAttr {
|
||||
|
||||
pub(crate) fn parse_argument_attribute(attr: &Attribute) -> ArgAttr {
|
||||
if attr.path.is_ident("option") {
|
||||
ArgAttr::Option(parse_option_attr(attr))
|
||||
ArgAttr::Option(OptionAttr::parse(attr))
|
||||
} else if attr.path.is_ident("positional") {
|
||||
ArgAttr::Positional(parse_positional_attr(attr))
|
||||
ArgAttr::Positional(PositionalAttr::parse(attr))
|
||||
} else {
|
||||
panic!("Internal error: invalid argument attribute");
|
||||
}
|
||||
}
|
||||
|
||||
enum AttributeArguments {
|
||||
String(String),
|
||||
Parser(Expr),
|
||||
Default(Expr),
|
||||
Value(Expr),
|
||||
NumArgs(RangeInclusive<usize>),
|
||||
File(String),
|
||||
}
|
||||
|
||||
impl AttributeArguments {
|
||||
fn parse_all(attr: &Attribute) -> Vec<Self> {
|
||||
attr.parse_args_with(Punctuated::<AttributeArguments, Token![,]>::parse_terminated)
|
||||
.map(|iter| iter.into_iter().collect::<Vec<_>>())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OptionAttr {
|
||||
pub(crate) flags: Flags,
|
||||
// This should probably not accept any expr to give better errors.
|
||||
// Closures should be allowed though.
|
||||
pub(crate) parser: Option<Expr>,
|
||||
pub(crate) default: Option<Expr>,
|
||||
}
|
||||
|
||||
enum OptionAttrArg {
|
||||
Arg(String),
|
||||
Parser(Expr),
|
||||
Default(Expr),
|
||||
impl OptionAttr {
|
||||
pub(crate) fn parse(attr: &Attribute) -> Self {
|
||||
let mut option_attr = OptionAttr::default();
|
||||
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::String(a) => option_attr.flags.add(&a),
|
||||
AttributeArguments::Parser(e) => option_attr.parser = Some(e),
|
||||
AttributeArguments::Default(e) => option_attr.default = Some(e),
|
||||
_ => panic!("Invalid argument"),
|
||||
};
|
||||
}
|
||||
|
||||
assert!(
|
||||
!option_attr.flags.is_empty(),
|
||||
"must give a flag in an option attribute"
|
||||
);
|
||||
|
||||
option_attr
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -44,9 +75,20 @@ pub(crate) struct ValueAttr {
|
||||
pub(crate) value: Option<Expr>,
|
||||
}
|
||||
|
||||
enum ValueAttrArg {
|
||||
Key(String),
|
||||
Value(Expr),
|
||||
impl ValueAttr {
|
||||
pub(crate) fn parse(attr: &Attribute) -> Self {
|
||||
let mut value_attr = Self::default();
|
||||
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::String(k) => value_attr.keys.push(k),
|
||||
AttributeArguments::Value(e) => value_attr.value = Some(e),
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
|
||||
value_attr
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PositionalAttr {
|
||||
@@ -59,108 +101,67 @@ impl Default for PositionalAttr {
|
||||
}
|
||||
}
|
||||
|
||||
enum PositionalAttrArg {
|
||||
NumArgs(RangeInclusive<usize>),
|
||||
}
|
||||
impl PositionalAttr {
|
||||
pub(crate) fn parse(attr: &Attribute) -> Self {
|
||||
let mut positional_attr = Self::default();
|
||||
|
||||
pub(crate) fn parse_option_attr(attr: &Attribute) -> OptionAttr {
|
||||
let mut option_attr = OptionAttr::default();
|
||||
let parsed_args = attr
|
||||
.parse_args_with(Punctuated::<OptionAttrArg, Token![,]>::parse_terminated)
|
||||
.unwrap_or_default();
|
||||
|
||||
for arg in parsed_args {
|
||||
match arg {
|
||||
OptionAttrArg::Arg(a) => option_attr.flags.add(&a),
|
||||
OptionAttrArg::Parser(e) => option_attr.parser = Some(e),
|
||||
OptionAttrArg::Default(e) => option_attr.default = Some(e),
|
||||
};
|
||||
}
|
||||
assert!(
|
||||
!option_attr.flags.is_empty(),
|
||||
"must give a flag in an option attribute"
|
||||
);
|
||||
option_attr
|
||||
}
|
||||
|
||||
impl Parse for OptionAttrArg {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(LitStr) {
|
||||
return parse_flag(input).map(Self::Arg);
|
||||
}
|
||||
|
||||
if input.peek(Ident) {
|
||||
let name = input.parse::<Ident>()?.to_string();
|
||||
input.parse::<Token![=]>()?;
|
||||
match name.as_str() {
|
||||
"parser" => return Ok(Self::Parser(input.parse::<Expr>()?)),
|
||||
"default" => return Ok(Self::Default(input.parse::<Expr>()?)),
|
||||
_ => panic!("Unrecognized argument {} for option attribute", name),
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::NumArgs(k) => positional_attr.num_args = k,
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
panic!("Arguments to option attribute must be string literals");
|
||||
|
||||
positional_attr
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_value_attr(attr: Attribute) -> ValueAttr {
|
||||
let mut value_attr = ValueAttr::default();
|
||||
let Ok(parsed_args) = attr
|
||||
.parse_args_with(Punctuated::<ValueAttrArg, Token![,]>::parse_terminated)
|
||||
else {
|
||||
return value_attr;
|
||||
};
|
||||
|
||||
for arg in parsed_args {
|
||||
match arg {
|
||||
ValueAttrArg::Key(k) => value_attr.keys.push(k),
|
||||
ValueAttrArg::Value(e) => value_attr.value = Some(e),
|
||||
};
|
||||
}
|
||||
|
||||
value_attr
|
||||
#[derive(Default)]
|
||||
pub(crate) struct HelpAttr {
|
||||
pub(crate) flags: Flags,
|
||||
pub(crate) file: Option<String>,
|
||||
}
|
||||
|
||||
impl Parse for ValueAttrArg {
|
||||
impl HelpAttr {
|
||||
pub(crate) fn parse(attr: &Attribute) -> Self {
|
||||
let mut help = Self::default();
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::String(s) => help.flags.add(&s),
|
||||
AttributeArguments::File(filename) => help.file = Some(filename),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
help
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct VersionAttr {
|
||||
pub(crate) flags: Flags,
|
||||
}
|
||||
|
||||
impl VersionAttr {
|
||||
pub(crate) fn parse(attr: &Attribute) -> Self {
|
||||
let mut help = Self::default();
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::String(s) => help.flags.add(&s),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
help
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for AttributeArguments {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(LitStr) {
|
||||
return Ok(Self::Key(input.parse::<LitStr>()?.value()));
|
||||
return Ok(Self::String(input.parse::<LitStr>().unwrap().value()));
|
||||
}
|
||||
|
||||
if input.peek(Ident) {
|
||||
let name = input.parse::<Ident>()?.to_string();
|
||||
input.parse::<Token![=]>()?;
|
||||
match name.as_str() {
|
||||
"value" => return Ok(Self::Value(input.parse::<Expr>()?)),
|
||||
_ => panic!("Unrecognized argument {} for option attribute", name),
|
||||
};
|
||||
}
|
||||
panic!("Arguments to option attribute must be string literals");
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flag(input: ParseStream) -> syn::Result<String> {
|
||||
Ok(input.parse::<LitStr>().unwrap().value())
|
||||
}
|
||||
|
||||
pub(crate) fn parse_positional_attr(attr: &Attribute) -> PositionalAttr {
|
||||
let mut positional_attr = PositionalAttr::default();
|
||||
let Ok(parsed_args) = attr
|
||||
.parse_args_with(Punctuated::<PositionalAttrArg, Token![,]>::parse_terminated)
|
||||
else {
|
||||
return positional_attr;
|
||||
};
|
||||
|
||||
for arg in parsed_args {
|
||||
match arg {
|
||||
PositionalAttrArg::NumArgs(k) => positional_attr.num_args = k,
|
||||
};
|
||||
}
|
||||
|
||||
positional_attr
|
||||
}
|
||||
|
||||
impl Parse for PositionalAttrArg {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if (input.peek(LitInt) && input.peek2(Token![..])) || input.peek(Token![..]) {
|
||||
// We're dealing with a range
|
||||
let range = input.parse::<ExprRange>()?;
|
||||
@@ -204,14 +205,17 @@ impl Parse for PositionalAttrArg {
|
||||
return Ok(Self::NumArgs(n..=n));
|
||||
}
|
||||
|
||||
// if input.peek(Ident) {
|
||||
// let name = input.parse::<Ident>()?.to_string();
|
||||
// input.parse::<Token![=]>()?;
|
||||
// match name.as_str() {
|
||||
// "value" => return Ok(Self::Value(input.parse::<Expr>()?)),
|
||||
// _ => panic!("Unrecognized argument {} for option attribute", name),
|
||||
// };
|
||||
// }
|
||||
panic!("unexpected argument to positional");
|
||||
if input.peek(Ident) {
|
||||
let name = input.parse::<Ident>()?.to_string();
|
||||
input.parse::<Token![=]>()?;
|
||||
match name.as_str() {
|
||||
"parser" => return Ok(Self::Parser(input.parse::<Expr>()?)),
|
||||
"default" => return Ok(Self::Default(input.parse::<Expr>()?)),
|
||||
"value" => return Ok(Self::Value(input.parse::<Expr>()?)),
|
||||
"file" => return Ok(Self::File(input.parse::<LitStr>()?.value())),
|
||||
_ => panic!("Unrecognized argument {} for option attribute", name),
|
||||
};
|
||||
}
|
||||
panic!("Arguments to option attribute must be string literals");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,6 @@ pub(crate) struct Flag<T> {
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
short: Vec::new(),
|
||||
long: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, flag: &str) {
|
||||
assert!(flag.starts_with('-'), "Flags must start with a '-'");
|
||||
if let Some(s) = flag.strip_prefix("--") {
|
||||
@@ -117,26 +110,6 @@ impl Flags {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_help() -> Self {
|
||||
Self {
|
||||
short: Vec::new(),
|
||||
long: vec![Flag {
|
||||
flag: "help".into(),
|
||||
value: Value::No,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_version() -> Self {
|
||||
Self {
|
||||
short: Vec::new(),
|
||||
long: vec![Flag {
|
||||
flag: "version".into(),
|
||||
value: Value::No,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format(&self) -> String {
|
||||
let short = self
|
||||
.short
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
use std::{
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
argument::{ArgType, Argument},
|
||||
attributes::{HelpAttr, VersionAttr},
|
||||
flags::Flags,
|
||||
markdown::{get_h2, str_to_renderer},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::Attribute;
|
||||
|
||||
pub(crate) fn parse_help_attr(attrs: &[Attribute]) -> HelpAttr {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("help") {
|
||||
return HelpAttr::parse(attr);
|
||||
}
|
||||
}
|
||||
HelpAttr::default()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_version_attr(attrs: &[Attribute]) -> VersionAttr {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("version") {
|
||||
return VersionAttr::parse(attr);
|
||||
}
|
||||
}
|
||||
VersionAttr::default()
|
||||
}
|
||||
|
||||
pub(crate) fn help_handling(help_flags: &Flags) -> TokenStream {
|
||||
if help_flags.is_empty() {
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let pat = help_flags.pat();
|
||||
|
||||
quote!(
|
||||
if let #pat = arg {
|
||||
return Ok(Some(Argument::Help));
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn help_string(
|
||||
args: &[Argument],
|
||||
help_attr: &HelpAttr,
|
||||
version_flags: &Flags,
|
||||
) -> TokenStream {
|
||||
let mut options = Vec::new();
|
||||
|
||||
let width: usize = 16;
|
||||
let indent: usize = 2;
|
||||
|
||||
for Argument { arg_type, help, .. } in args {
|
||||
match arg_type {
|
||||
ArgType::Option { flags, .. } => {
|
||||
let flags = flags.format();
|
||||
let renderer = str_to_renderer(help);
|
||||
options.push(quote!((#flags, #renderer)));
|
||||
}
|
||||
ArgType::Positional { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
let summary = if let Some(file) = &help_attr.file {
|
||||
let renderer = read_help_file(file);
|
||||
quote!(s.push_str(&#renderer.render());)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
if !help_attr.flags.is_empty() {
|
||||
let flags = help_attr.flags.format();
|
||||
let renderer = str_to_renderer("Display this help message");
|
||||
options.push(quote!((#flags, #renderer)));
|
||||
}
|
||||
|
||||
if !version_flags.is_empty() {
|
||||
let flags = version_flags.format();
|
||||
let renderer = str_to_renderer("Display version information");
|
||||
options.push(quote!((#flags, #renderer)));
|
||||
}
|
||||
|
||||
let options = if !options.is_empty() {
|
||||
let options = quote!([#(#options),*]);
|
||||
quote!(
|
||||
s.push_str("\nOptions:\n");
|
||||
for (flags, renderer) in #options {
|
||||
let indent = " ".repeat(#indent);
|
||||
|
||||
let help_string = renderer.render();
|
||||
let mut help_lines = help_string.lines();
|
||||
s.push_str(&indent);
|
||||
s.push_str(&flags);
|
||||
|
||||
if flags.len() <= #width {
|
||||
let line = match help_lines.next() {
|
||||
Some(line) => line,
|
||||
None => return s,
|
||||
};
|
||||
let help_indent = " ".repeat(#width-flags.len()+2);
|
||||
s.push_str(&help_indent);
|
||||
s.push_str(line);
|
||||
s.push('\n');
|
||||
} else {
|
||||
s.push('\n');
|
||||
}
|
||||
|
||||
let help_indent = " ".repeat(#width+#indent+2);
|
||||
for line in help_lines {
|
||||
s.push_str(&help_indent);
|
||||
s.push_str(line);
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote!(
|
||||
let mut s = String::new();
|
||||
|
||||
s.push_str(&format!("{} {}\n",
|
||||
option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME")),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
));
|
||||
|
||||
#summary
|
||||
|
||||
s.push_str(&format!("\nUsage:\n {} [OPTIONS] [ARGS]\n", bin_name));
|
||||
|
||||
#options
|
||||
|
||||
s
|
||||
)
|
||||
}
|
||||
|
||||
fn read_help_file(file: &str) -> TokenStream {
|
||||
let path = Path::new(file);
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let mut location = PathBuf::from(manifest_dir);
|
||||
location.push(path);
|
||||
let mut contents = String::new();
|
||||
let mut f = std::fs::File::open(location).unwrap();
|
||||
f.read_to_string(&mut contents).unwrap();
|
||||
|
||||
get_h2("summary", &contents)
|
||||
}
|
||||
+12
-11
@@ -2,14 +2,15 @@ mod action;
|
||||
mod argument;
|
||||
mod attributes;
|
||||
mod flags;
|
||||
mod help;
|
||||
mod markdown;
|
||||
|
||||
use action::{parse_action_attr, ActionAttr, ActionType};
|
||||
use argument::{
|
||||
help_handling, help_string, long_handling, parse_argument, parse_help_flags,
|
||||
parse_version_flags, positional_handling, short_handling, version_handling,
|
||||
long_handling, parse_argument, positional_handling, short_handling, version_handling,
|
||||
};
|
||||
use attributes::{parse_value_attr, ValueAttr};
|
||||
use attributes::ValueAttr;
|
||||
use help::{help_handling, help_string, parse_help_attr, parse_version_attr};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
@@ -119,7 +120,7 @@ pub fn options(input: TokenStream) -> TokenStream {
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Arguments, attributes(flag, option, positional, help))]
|
||||
#[proc_macro_derive(Arguments, attributes(flag, option, positional, help, version))]
|
||||
pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
@@ -130,16 +131,16 @@ pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
panic!("Input should be an enum!");
|
||||
};
|
||||
|
||||
let help_flags = parse_help_flags(&input.attrs);
|
||||
let version_flags = parse_version_flags(&input.attrs);
|
||||
let help_attr = parse_help_attr(&input.attrs);
|
||||
let version_attr = parse_version_attr(&input.attrs);
|
||||
let arguments: Vec<_> = data.variants.into_iter().flat_map(parse_argument).collect();
|
||||
|
||||
let short = short_handling(&arguments);
|
||||
let long = long_handling(&arguments, &help_flags);
|
||||
let long = long_handling(&arguments, &help_attr.flags);
|
||||
let (positional, missing_argument_checks) = positional_handling(&arguments);
|
||||
let help_string = help_string(&arguments, &help_flags, &version_flags);
|
||||
let help = help_handling(&help_flags);
|
||||
let version = version_handling(&version_flags);
|
||||
let help_string = help_string(&arguments, &help_attr, &version_attr.flags);
|
||||
let help = help_handling(&help_attr.flags);
|
||||
let version = version_handling(&version_attr.flags);
|
||||
let version_string = quote!(format!(
|
||||
"{} {}",
|
||||
option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME")),
|
||||
@@ -205,7 +206,7 @@ pub fn from_value(input: TokenStream) -> TokenStream {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ValueAttr { keys, value } = parse_value_attr(attr);
|
||||
let ValueAttr { keys, value } = ValueAttr::parse(&attr);
|
||||
|
||||
let keys = if keys.is_empty() {
|
||||
vec![variant_name.to_lowercase()]
|
||||
|
||||
+63
-32
@@ -6,42 +6,73 @@ fn prefix(t: TokenStream) -> TokenStream {
|
||||
quote!(uutils_args::term_md::#t)
|
||||
}
|
||||
|
||||
fn md_to_quote(event: Event) -> TokenStream {
|
||||
let tokens = match event {
|
||||
Event::Start(tag) => {
|
||||
let tag = quote_tag(tag);
|
||||
quote!(Event::Start(#tag))
|
||||
}
|
||||
Event::End(tag) => {
|
||||
let tag = quote_tag(tag);
|
||||
quote!(Event::End(#tag))
|
||||
}
|
||||
Event::Text(t) => {
|
||||
let t = t.to_string();
|
||||
let text = quote!(String::from(#t));
|
||||
quote!(Event::Text(#text))
|
||||
}
|
||||
Event::Code(t) => {
|
||||
let t = t.to_string();
|
||||
let text = quote!(String::from(#t));
|
||||
quote!(Event::Code(#text))
|
||||
}
|
||||
Event::SoftBreak => quote!(Event::SoftBreak),
|
||||
Event::HardBreak => quote!(Event::HardBreak),
|
||||
Event::Rule => quote!(Event::Rule),
|
||||
|
||||
// Below are unsupported in term_md
|
||||
Event::Html(_) => todo!(),
|
||||
Event::FootnoteReference(_) => todo!(),
|
||||
Event::TaskListMarker(_) => todo!(),
|
||||
};
|
||||
prefix(tokens)
|
||||
}
|
||||
|
||||
pub(crate) fn str_to_renderer(s: &str) -> TokenStream {
|
||||
let events = Parser::new(s);
|
||||
let parsed_events = events.map(|e| {
|
||||
let event = match e {
|
||||
Event::Start(tag) => {
|
||||
let tag = quote_tag(tag);
|
||||
quote!(Event::Start(#tag))
|
||||
}
|
||||
Event::End(tag) => {
|
||||
let tag = quote_tag(tag);
|
||||
quote!(Event::End(#tag))
|
||||
}
|
||||
Event::Text(t) => {
|
||||
let t = t.to_string();
|
||||
let text = quote!(String::from(#t));
|
||||
quote!(Event::Text(#text))
|
||||
}
|
||||
Event::Code(t) => {
|
||||
let t = t.to_string();
|
||||
let text = quote!(String::from(#t));
|
||||
quote!(Event::Code(#text))
|
||||
}
|
||||
Event::SoftBreak => quote!(Event::SoftBreak),
|
||||
Event::HardBreak => quote!(Event::HardBreak),
|
||||
Event::Rule => quote!(Event::Rule),
|
||||
|
||||
// Below are unsupported in term_md
|
||||
Event::Html(_) => todo!(),
|
||||
Event::FootnoteReference(_) => todo!(),
|
||||
Event::TaskListMarker(_) => todo!(),
|
||||
};
|
||||
prefix(event)
|
||||
});
|
||||
let parsed_events = events.map(md_to_quote);
|
||||
|
||||
prefix(quote!(Renderer::new(
|
||||
40,
|
||||
60,
|
||||
vec![#(#parsed_events),*].into_iter()
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn get_h2(heading_name: &str, s: &str) -> TokenStream {
|
||||
let mut events = Parser::new(s);
|
||||
let mut selected_events = Vec::new();
|
||||
while let Some(event) = events.next() {
|
||||
if let Event::Start(Tag::Heading(HeadingLevel::H2, _, _)) = event {
|
||||
if let Some(Event::Text(s)) = events.next() {
|
||||
if s.to_lowercase() == heading_name.to_lowercase() {
|
||||
selected_events.extend(
|
||||
(&mut events)
|
||||
.skip_while(|e| {
|
||||
!matches!(e, Event::End(Tag::Heading(HeadingLevel::H2, _, _)))
|
||||
})
|
||||
.skip(1)
|
||||
.take_while(|e| {
|
||||
!matches!(e, Event::Start(Tag::Heading(HeadingLevel::H2, _, _)))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let parsed_events = selected_events.into_iter().map(md_to_quote);
|
||||
prefix(quote!(Renderer::new(
|
||||
80,
|
||||
vec![#(#parsed_events),*].into_iter()
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use uutils_args::{Arguments, Options};
|
||||
|
||||
#[derive(Clone, Arguments)]
|
||||
#[help("--help")]
|
||||
#[help("--help", file = "examples/hello_world_help.md")]
|
||||
#[version("--version")]
|
||||
enum Arg {
|
||||
/// The *name* to **greet**
|
||||
///
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Helloworld
|
||||
|
||||
## Summary
|
||||
|
||||
Hello this is the summary.
|
||||
Reference in New Issue
Block a user