comn/
flags.rs

1use crate::*;
2use clap;
3use std::{
4    cell::UnsafeCell,
5    collections::{HashMap, LinkedList},
6    fmt,
7    ops::Deref,
8    str::FromStr,
9    sync::atomic::{AtomicI8, Ordering},
10};
11
12struct item {
13    opt: &'static OptVal,
14    sk: &'static str,
15    lk: Option<&'static str>,
16    lv: Option<&'static str>,
17    dv: Option<&'static str>,
18    help: Option<&'static str>,
19}
20
21fn cmd_hash_get() -> &'static mut HashMap<&'static str, LinkedList<item>> {
22    static mut HASH: *mut HashMap<&'static str, LinkedList<item>> = nil!();
23    unsafe {
24        // 这个函数只在 ctor 中被调用
25        // 因此并没有并发问题
26        if HASH == nil!() {
27            HASH = Box::leak(Box::new(HashMap::new()));
28        }
29        &mut *HASH
30    }
31}
32
33fn cmd_args_list(k: &'static str) -> &mut LinkedList<item> {
34    let h = cmd_hash_get();
35    let opt = h.get_mut(k);
36    if opt.is_none() {
37        let v = LinkedList::new();
38        h.insert(k, v);
39    }
40
41    let opt = h.get_mut(k);
42    match opt {
43        Some(v) => v,
44        None => unreachable!(),
45    }
46}
47
48pub fn declare_argment<'a: 'static>(
49    module: &'a str,
50    name: &'a str,
51    opt: &'a OptVal,
52    cmd: Option<&'a str>,
53    sk: &'a str,
54    lk: Option<&'a str>,
55    lv: Option<&'a str>,
56    dv: Option<&'a str>,
57    help: Option<&'a str>,
58) {
59    let mut module = module.to_owned();
60    module.push_str(name);
61    unsafe {
62        *opt.name.get() = Box::leak(module.into_boxed_str());
63    }
64
65    let k = cmd.unwrap_or("");
66    cmd_args_list(k).push_back(item {
67        sk,
68        lk,
69        lv,
70        dv,
71        help,
72        opt,
73    });
74}
75
76pub struct OptVal {
77    name: UnsafeCell<&'static str>,
78    stat: AtomicI8,
79}
80make!(OptVal: Sync);
81
82impl OptVal {
83    pub const fn new() -> Self {
84        OptVal {
85            name: UnsafeCell::new(""),
86            stat: AtomicI8::new(0),
87        }
88    }
89
90    fn name(&self) -> &'static str {
91        unsafe { *self.name.get() }
92    }
93
94    pub fn str(&self) -> Result<&'static str, String> {
95        let stat = self.stat.load(Ordering::Relaxed);
96        if stat & 4 == 0 {
97            return Err("can't called before main".to_owned());
98        }
99
100        if stat & 8 != 0 {
101            return Err("not in subcommand context".to_owned());
102        }
103        Ok(self.name())
104    }
105
106    pub fn parse<T: FromStr>(&self) -> Result<T, String>
107    where
108        T::Err: fmt::Display,
109    {
110        let stat = self.stat.load(Ordering::Relaxed);
111        if stat & 4 == 0 {
112            return Err("can't called before main".to_owned());
113        }
114
115        if stat & 8 != 0 {
116            return Err("not in subcommand context".to_owned());
117        }
118
119        match self.name().parse() {
120            Ok(v) => Ok(v),
121            Err(e) => Err(format!("{}", e)),
122        }
123    }
124}
125
126pub struct optv(pub &'static dyn Deref<Target = &'static OptVal>);
127impl Deref for optv {
128    type Target = &'static OptVal;
129    fn deref(&self) -> &Self::Target {
130        self.0.deref()
131    }
132}
133make!(optv: Sync);
134
135fn apply<'a, 'b>(itm: &item, mut arg: clap::Arg<'a, 'b>, mask: i8) -> clap::Arg<'a, 'b> {
136    arg = arg.short(itm.sk);
137    if itm.lk.is_some() {
138        arg = arg.long(itm.lk.unwrap());
139    }
140    let mut stat = mask;
141
142    if itm.lv.is_some() {
143        arg = arg.value_name(itm.lv.unwrap());
144        stat |= 1;
145    }
146
147    if itm.dv.is_some() {
148        if stat == mask {
149            arg = arg.value_name("unspecified");
150        }
151        arg = arg.default_value(itm.dv.unwrap());
152        stat |= 3;
153    }
154
155    itm.opt.stat.store(stat, Ordering::Relaxed);
156    if itm.help.is_some() {
157        arg = arg.help(itm.help.unwrap())
158    }
159    arg
160}
161
162fn app_menu<'a, 'b>(
163    name: &'static str,
164    mut app: clap::App<'a, 'b>,
165    args: LinkedList<item>,
166    opts: &mut Vec<(&str, &OptVal)>,
167    mask: i8,
168) -> clap::App<'a, 'b> {
169    for itm in args {
170        let mut arg = clap::Arg::with_name(itm.opt.name());
171        arg = apply(&itm, arg, mask);
172        app = app.arg(arg);
173        opts.push((name, itm.opt));
174    }
175    app
176}
177
178pub fn parse(mut m: clap::App) {
179    let mut opts: Vec<(&str, &OptVal)> = vec![];
180    let h = cmd_hash_get();
181    let opt = h.remove(&"");
182    if let Some(args) = opt {
183        m = app_menu("", m, args, &mut opts, 0x00);
184    }
185
186    for (name, args) in h.drain() {
187        let mut sc = clap::SubCommand::with_name(name);
188        sc = app_menu(name, sc, args, &mut opts, -128);
189        m = m.subcommand(sc);
190    }
191
192    let matches = m.get_matches();
193    for (sc, opt) in opts {
194        let name = opt.name();
195        let mut vals = &matches;
196        let stat = opt.stat.load(Ordering::Relaxed);
197        if stat < 0 {
198            vals = if let Some(x) = matches.subcommand_matches(sc) {
199                x
200            } else {
201                opt.stat.store(stat | 12, Ordering::Relaxed);
202                continue;
203            }
204        }
205
206        let val: String;
207        if stat & 1 == 0 {
208            val = vals.is_present(name).to_string();
209        } else {
210            val = vals.value_of(name).unwrap_or_default().to_owned();
211        }
212
213        unsafe {
214            let ptr = opt.name.get();
215            Box::<str>::from(*ptr);
216            *ptr = Box::leak(val.into_boxed_str());
217        }
218        opt.stat.store(stat | 4, Ordering::Relaxed);
219    }
220}
221
222#[macro_export]
223#[rustfmt::skip]
224macro_rules! option {
225    (None) => { None };
226    (@ None) => { None };
227    (@ $val:literal) => { Some( stringify!($val)) };
228    ($val:literal) => { Some($val) };
229    ($cmd:tt, $sk:literal, $lk:tt, $lv:tt, $dv:tt, $help:tt) => {
230        $crate::flags::optv({
231            #[ctor]
232            static opt: &'static $crate::flags::OptVal = {
233                static x: $crate::flags::OptVal = $crate::flags::OptVal::new();
234                $crate::flags::declare_argment(
235                    module_path!(),
236                    str64!(),
237                    &x,
238                    option!($cmd),
239                    $sk,
240                    option!($lk),
241                    option!($lv),
242                    option!(@ $dv),
243                    option!($help),
244                );
245                &x
246            };
247            &opt
248        })
249    };
250}
251
252#[macro_export]
253macro_rules! option_parse {
254    ($app:literal, $version:literal) => {{
255        extern crate clap;
256        $crate::flags::parse(clap::App::new($app).version($version));
257    }};
258}