├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── _config.yml ├── appveyor.yml ├── examples ├── loki.rs └── thor.rs ├── logo.svg └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific stuff 2 | target 3 | Cargo.lock 4 | # rustfmt 5 | *.bk 6 | 7 | # Editor Stuff 8 | *.sublime-project 9 | *.sublime-workspace 10 | .*.swp 11 | *.bak 12 | *~ 13 | .vscode 14 | 15 | # System stuff 16 | .DS_Store 17 | .Spotlight-V100 18 | .Trashes 19 | Thumbs.db 20 | ehthumbs.db 21 | Desktop.ini 22 | $RECYCLE.BIN/ 23 | .directory 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Some basic stuff about what we're doing here 2 | dist: trusty 3 | language: rust 4 | services: docker 5 | sudo: required 6 | language: rust 7 | cache: cargo 8 | rust: 9 | - nightly 10 | 11 | # This is required for coveralls 12 | addons: 13 | apt: 14 | packages: 15 | - libcurl4-openssl-dev 16 | - libelf-dev 17 | - libdw-dev 18 | - binutils-dev 19 | - cmake 20 | sources: 21 | - kalakris-cmake 22 | 23 | # If nightly explodes we don't care aaas much 24 | matrix: 25 | allow_failures: 26 | - rust: nightly 27 | 28 | # This is a pretty big hack and only really needed on the first of a build chain 29 | before_script: 30 | - (cargo install cargo-travis | true) && export PATH=$HOME/.cargo/bin:$PATH 31 | 32 | # Build, test, benchmark, document. Gogogogo! 33 | script: 34 | - cargo build --verbose --all 35 | - cargo test --verbose --all 36 | - cargo bench --all 37 | - cargo doc 38 | 39 | # Upload the whole mess 40 | after_success: 41 | - cargo coveralls --verbose 42 | 43 | # AND GOD DAMN IT LET ME SLEEP! 44 | notifications: 45 | email: 46 | on_success: never 47 | on_failure: never 48 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.11.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "atty" 11 | version = "0.2.10" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "bitflags" 21 | version = "1.0.3" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "clap" 26 | version = "2.31.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "libc" 40 | version = "0.2.40" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "proc-macro2" 45 | version = "0.2.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | dependencies = [ 48 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 49 | ] 50 | 51 | [[package]] 52 | name = "quote" 53 | version = "0.4.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 57 | ] 58 | 59 | [[package]] 60 | name = "redox_syscall" 61 | version = "0.1.37" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | 64 | [[package]] 65 | name = "redox_termios" 66 | version = "0.1.1" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "strsim" 74 | version = "0.7.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | 77 | [[package]] 78 | name = "syn" 79 | version = "0.12.15" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | dependencies = [ 82 | "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "termion" 89 | version = "1.5.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | dependencies = [ 92 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "textwrap" 99 | version = "0.9.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | dependencies = [ 102 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "thunder" 107 | version = "0.3.1" 108 | dependencies = [ 109 | "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", 112 | ] 113 | 114 | [[package]] 115 | name = "unicode-width" 116 | version = "0.1.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | [[package]] 120 | name = "unicode-xid" 121 | version = "0.1.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | 124 | [[package]] 125 | name = "vec_map" 126 | version = "0.8.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | 129 | [[package]] 130 | name = "winapi" 131 | version = "0.3.4" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | dependencies = [ 134 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 136 | ] 137 | 138 | [[package]] 139 | name = "winapi-i686-pc-windows-gnu" 140 | version = "0.4.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | 143 | [[package]] 144 | name = "winapi-x86_64-pc-windows-gnu" 145 | version = "0.4.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | 148 | [metadata] 149 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 150 | "checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" 151 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 152 | "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" 153 | "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" 154 | "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" 155 | "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" 156 | "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" 157 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 158 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 159 | "checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5" 160 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 161 | "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 162 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 163 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 164 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 165 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 166 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 167 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 168 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thunder" 3 | description = "Create simple commandline apps with *zero* boilerplate!" 4 | version = "0.3.1" 5 | authors = ["Katharina Fey "] 6 | license = "MIT/X11 OR Apache-2.0" 7 | readme = "README.md" 8 | documentation = "https://docs.rs/thunder" 9 | repository = "https://github.com/spacekookie/thunder" 10 | keywords = ["argument", "cli", "arg", "parser", "parse"] 11 | categories = ["command-line-interface"] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | clap = "2.0" 18 | syn = { version = "0.12", features = ["extra-traits", "full", "fold", "visit", "visit-mut"] } 19 | quote = "0.4" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/travis/spacekookie/thunder.svg)](https://travis-ci.org/spacekookie/thunder/) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/clrwni1vork68vq6?svg=true)](https://ci.appveyor.com/project/spacekookie/thunder) 3 | [![](https://img.shields.io/crates/v/thunder.svg)](https://crates.io/crates/thunder) 4 | [![Docs.rs](https://docs.rs/thunder/badge.svg)](https://docs.rs/thunder/) 5 | 6 |
7 |

8 | 9 |

10 |
11 | 12 | Write simple commandline applications in Rust with *zero* boilerplate. Bind Rust functions to CLI functions and options with macros. This crate uses [clap.rs](https://github.com/kbknapp/clap-rs) for the actual argument parsing. 13 | 14 | ## Example 15 | 16 | ```rust,norun 17 | // ... ignore the imports for now ... 18 | 19 | struct MyApp; 20 | 21 | /// Describe your application with style ✨ 22 | #[thunderclap] 23 | impl MyApp { 24 | /// Say hello to someone 25 | fn hello(name: &str) { 26 | println!("Hello {}", name); 27 | } 28 | } 29 | 30 | fn main() { 31 | MyApp::start(); 32 | } 33 | ``` 34 | 35 | **This prints** 36 | 37 | ```norun 38 | USAGE: 39 | MyApp [SUBCOMMAND] 40 | 41 | FLAGS: 42 | -h, --help Prints help information 43 | -V, --version Prints version information 44 | 45 | SUBCOMMANDS: 46 | hello Say hello to someone 47 | help Prints this message or the help of the given subcommand(s) 48 | ``` 49 | 50 | Check the documentation for more examples. 51 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - RUST: nightly 4 | BITS: 32 5 | - RUST: nightly 6 | BITS: 64 7 | 8 | install: 9 | - IF "%BITS%" == "32" SET ARCH=i686 10 | - IF "%BITS%" == "64" SET ARCH=x86_64 11 | - SET RUST_URL=https://static.rust-lang.org/dist/rust-nightly-x86_64-pc-windows-gnu.exe 12 | - SET PATH=C:\Rust\bin;C:\msys64\mingw%BITS%\bin;%PATH%;C:\msys64\usr\bin 13 | - ps: Start-FileDownload $Env:RUST_URL -FileName rust-dist.exe 14 | - rust-dist.exe /VERYSILENT /NORESTART /COMPONENTS="Rustc,Gcc,Cargo,Std" /DIR="C:\Rust" 15 | - rustc -V 16 | - cargo -V 17 | 18 | build_script: 19 | - cargo build --verbose 20 | - cargo test --verbose 21 | -------------------------------------------------------------------------------- /examples/loki.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro)] 2 | extern crate thunder; 3 | extern crate clap; 4 | 5 | use thunder::thunderclap; 6 | 7 | struct Loki; 8 | 9 | #[thunderclap(example: Option: "Error, here", another_example: String: "No error, here")] 10 | impl Loki { 11 | fn hello() { 12 | println!("{:?}", Self::example()); 13 | } 14 | } 15 | 16 | fn main() { 17 | Loki::start(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/thor.rs: -------------------------------------------------------------------------------- 1 | //! Thor is the god of thunder 2 | #![feature(proc_macro)] 3 | extern crate clap; 4 | extern crate thunder; 5 | use thunder::thunderclap; 6 | 7 | struct Thor; 8 | 9 | /// An application that shoots lightning out of its hands 10 | #[thunderclap(drunk: bool: "Bla bla bla")] 11 | impl Thor { 12 | /// Say hello to someone at home 13 | fn hello(name: &str, times: Option) { 14 | (0..times.unwrap_or(1)).for_each(|_| { 15 | println!("Hello {}!", name); 16 | }); 17 | } 18 | 19 | // /// Say goodbye. Or don't, if you're shy 20 | // fn bye(name: Option<&str>) { 21 | // println!("Not saying bye is rude: {:?}", name); 22 | // } 23 | 24 | // /// Thor will rudely comment on your age 25 | // fn aged(age: Option) { 26 | // println!("Ha, look at you being: {:?}", age); 27 | // } 28 | 29 | // /// Prints 'bar' 30 | // fn foo() { 31 | // println!("bar"); 32 | // } 33 | } 34 | 35 | fn main() { 36 | Thor::start(); 37 | } 38 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | rust_wasm4 26 | 27 | 28 | 29 | 49 | 50 | rust_wasm4 52 | Created with Sketch. 54 | 56 | 59 | 63 | 64 | 69 | 72 | 77 | 82 | 87 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `thunder.rs` a zero-boilerplate commandline argument parser ✨ 2 | #![feature(external_doc)] 3 | #![doc(include = "../README.md")] 4 | #![feature(proc_macro, proc_macro_lib, iterator_flatten)] 5 | #![allow(unused_imports, unused_variables)] 6 | 7 | extern crate proc_macro; 8 | 9 | #[macro_use] 10 | extern crate syn; 11 | 12 | #[macro_use] 13 | extern crate quote; 14 | 15 | use proc_macro::TokenStream; 16 | use quote::ToTokens; 17 | use std::collections::HashSet as Set; 18 | use std::str::FromStr; 19 | use syn::fold::{self, Fold}; 20 | use syn::punctuated::Punctuated; 21 | use syn::synom::Synom; 22 | use syn::LitStr; 23 | use syn::{ 24 | DeriveInput, Expr, FnArg, GenericArgument, Ident, ImplItem, ImplItemMethod, Item, ItemImpl, 25 | ItemStatic, Pat, PathArguments, PathSegment, Stmt, Type, 26 | }; 27 | 28 | #[derive(Debug)] 29 | struct Args { 30 | args: Vec, 31 | last_field: bool, 32 | string: String 33 | } 34 | 35 | impl Args { 36 | fn new() -> Args { 37 | Args { 38 | args: Vec::new(), 39 | last_field: false, 40 | string: String::new() 41 | } 42 | } 43 | 44 | fn push(&mut self) { 45 | self.string = self.string.trim().to_owned(); 46 | if self.string.starts_with(',') { 47 | self.string.remove(0); 48 | } 49 | 50 | self.args.push(self.string.trim().to_owned()); 51 | } 52 | 53 | fn collect(self) -> Vec<(String, String, String)> { 54 | self.args 55 | .into_iter() 56 | .fold((Vec::new(), Vec::new()), |(mut acc, mut zip), x| { 57 | acc.push(x); 58 | if acc.len() == 3 { 59 | zip.push(acc); 60 | acc = Vec::new(); 61 | } 62 | 63 | (acc, zip) 64 | }) 65 | .1.into_iter() 66 | .map(|mut triple| (triple.remove(0), triple.remove(0), triple.remove(0))) 67 | .collect() 68 | } 69 | } 70 | 71 | macro_rules! check_input { 72 | ($y:expr, $x:expr) => { 73 | match $x { 74 | Ok(s) => s, 75 | Err(e) => panic!( 76 | "Failed to parse type in global arg annotation '{}'. Specific error: {:?}", 77 | $y, e 78 | ), 79 | } 80 | }; 81 | } 82 | 83 | /// Main macro that implements automated clap generation. This invokes ✨ *magic* ✨ 84 | /// 85 | /// Every `impl` block tagged with the macro will turn into a Thunder-app. At 86 | /// the moment only a single Thunder app can exist in the same scope (this will change). 87 | /// 88 | /// What a `thunder` app does is take every function in its scope and turn it into a 89 | /// CLI handle with `clap`, meaning that all arguments will be mapped to the user shell 90 | /// as they are described in the function body. 91 | /// 92 | /// ## Example 93 | /// 94 | /// ```norun 95 | /// fn say_hello(name: &str, age: Option) { 96 | /// // ... 97 | /// } 98 | /// ``` 99 | /// 100 | /// This function will turn into the CLI option `say_hello` that always takes a name 101 | /// parameter (which is a String) and optionally a number (which has to fit into u16!) 102 | /// 103 | /// These conversion checks are done at run-time but functions are only called if 104 | /// the parameters are valid. As such, you don't have to worry :) 105 | /// 106 | /// ### A more complete example 107 | /// 108 | /// The block below defines a medium sized `thunder` application. 109 | /// 110 | /// ```norun 111 | /// struct MyApp; 112 | /// 113 | /// #[thunderclap] 114 | /// impl MyApp { 115 | /// /// Say hello to someone on the other side 116 | /// fn say_hello(name: &str, age: Option) { /* ... */ } 117 | /// 118 | /// /// It was nice to meet you! 119 | /// fn goodybe(name: Option<&str>) { /* ... */ } 120 | /// } 121 | /// 122 | /// fn main() { 123 | /// // This starts the match execution 124 | /// MyApp::start(); 125 | /// } 126 | /// ``` 127 | /// 128 | /// ## Global variables 129 | /// 130 | /// It's possible to declare argument parameters that can be invoked on any function and 131 | /// are available outside of regular context. `thunder` generates an argument store which 132 | /// you can use to get results from these global arguments. 133 | /// 134 | /// They can be both mandatory (`T`) or optional (`Option`) and are named and also have 135 | /// a description displayed to the user. Their names are abbreviated with `--name` and `-n` 136 | /// if the parameter was called `name`. 137 | /// 138 | /// A small example below. 139 | /// 140 | /// ```norun 141 | /// struct MyApp; 142 | /// 143 | /// #[thunder(arg1: u32: "A small description", arg2: Option: "Optional global")] 144 | /// impl MyApp { 145 | /// fn hello(name: &str) {} 146 | /// } 147 | /// 148 | /// fn main() { 149 | /// MyApp::start(); 150 | /// } 151 | /// ``` 152 | /// 153 | /// If you have more questions or encounter bugs, don't hesitate to contact us! 154 | /// PR's always welcome (we're friendly ❤️) 155 | #[proc_macro_attribute] 156 | pub fn thunderclap(args: TokenStream, input: TokenStream) -> TokenStream { 157 | let i: ItemImpl = match syn::parse(input.clone()) { 158 | Ok(input) => input, 159 | Err(e) => panic!("Error: '{}'", e), 160 | }; 161 | 162 | /* Manually parse any argument pars given to us */ 163 | let args: String = args.to_string(); 164 | let global_args = if args.len() != 0 { 165 | args 166 | .chars() 167 | .fold(Args::new(), |mut acc, char| { 168 | if char == ':' && !acc.last_field { 169 | acc.push(); 170 | acc.string = String::new(); 171 | return acc; 172 | } 173 | 174 | acc.string.push(char); 175 | 176 | if char == '"' { 177 | if acc.last_field { 178 | acc.push(); 179 | acc.string = String::new(); 180 | } 181 | 182 | acc.last_field = !acc.last_field; 183 | } 184 | 185 | acc 186 | }) 187 | .collect() 188 | .into_iter() 189 | .map(|(x, y, z)| { 190 | ( 191 | check_input! { x, TokenStream::from_str(&x.replace("\"", "")) }, 192 | check_input! { y, TokenStream::from_str(&y) }, 193 | z.replace("\"", ""), 194 | ) 195 | }) 196 | .map(|(x, y, z)| { 197 | ( 198 | check_input! { x, syn::parse(x.clone()) }, 199 | check_input! { y, syn::parse(y.clone()) }, 200 | z, 201 | ) 202 | }) 203 | .map(|(x, y, z)| (x, y, String::from(z))) 204 | .collect::>() 205 | } else { 206 | Vec::new() 207 | }; 208 | 209 | let (name, app_token) = match *i.self_ty { 210 | Type::Path(ref p) => { 211 | let meh = p.path.segments[0].ident; 212 | (format!("{}", p.path.segments[0].ident), quote!( #meh )) 213 | } 214 | _ => (format!("Unknown App"), quote!()), 215 | }; 216 | 217 | let about = i.attrs 218 | .iter() 219 | .map(|x| (x, x.path.segments.first())) 220 | .filter(|(a, x)| x.is_some()) 221 | .map(|(a, x)| (a, x.unwrap().value().clone())) 222 | .map(|(a, v)| match &v.ident.to_string().as_str() { 223 | &"doc" => String::from( 224 | format!("{}", a.tts) 225 | .replace("/", "") 226 | .replace("\\", "") 227 | .replace("\"", "") 228 | .replace("=", "") 229 | .trim(), 230 | ), 231 | _ => String::from(""), 232 | }) 233 | .collect::(); 234 | 235 | let mut matches: Vec = Vec::new(); 236 | let orignal = quote!(#i); 237 | let mut app = quote! { 238 | App::new(#name).about(#about).setting(AppSettings::SubcommandRequired) 239 | }; 240 | 241 | let mut accessors = quote!{}; 242 | let mut data_struct_fields = quote!{}; 243 | let mut init_struct_fields = quote!{}; 244 | let mut global_match_state_matcher = quote!{}; 245 | 246 | global_args.iter().for_each(|(name, typed, about)| { 247 | let (name, name_token) = match name { 248 | Type::Path(ref p) => { 249 | let meh = p.path.segments[0].ident; 250 | (format!("{}", p.path.segments[0].ident), quote!( #meh )) 251 | } 252 | _ => (format!("Unknown App"), quote!()), 253 | }; 254 | 255 | let name = format!("{}", name); 256 | let optional = match typed { 257 | Type::Path(ref p) => match p.path.segments.first() { 258 | Some(ps) => match &ps.value().ident.to_string().as_str() { 259 | &"Option" => true, 260 | _ => false, 261 | }, 262 | _ => false, 263 | }, 264 | _ => false, 265 | }; 266 | 267 | let inner = if optional { 268 | match typed { 269 | Type::Path(ref p) => match p.path.segments.first() { 270 | Some(ps) => match ps.value().arguments { 271 | PathArguments::AngleBracketed(ref b) => match b.args.first() { 272 | Some(pair) => match pair.value() { 273 | GenericArgument::Type(Type::Path(pp)) => { 274 | Some(Type::from(pp.clone())) 275 | } 276 | _ => None, 277 | }, 278 | _ => None, 279 | }, 280 | _ => None, 281 | }, 282 | _ => None, 283 | }, 284 | _ => None, 285 | } 286 | } else { 287 | None 288 | }; 289 | 290 | accessors = quote! { 291 | #accessors 292 | 293 | #[allow(unused)] 294 | fn #name_token ( /* No Parameters */ ) -> #typed { 295 | unsafe { 296 | __THUNDER_DATA_STATIC.as_ref().unwrap().#name_token.as_ref().unwrap().clone() 297 | } 298 | } 299 | }; 300 | 301 | data_struct_fields = quote! { 302 | #data_struct_fields 303 | #name_token : Option< #typed > , 304 | }; 305 | 306 | init_struct_fields = quote! { 307 | #init_struct_fields 308 | #name_token : None , 309 | }; 310 | 311 | global_match_state_matcher = if optional { 312 | let inner = inner.unwrap(); 313 | quote! { 314 | #global_match_state_matcher 315 | global_match_states.#name_token = match args.value_of(#name) { 316 | Some(v) => Some(Some(v.parse::<#inner>().expect("Failed to parse value. Double check!"))), 317 | None => Some(None), 318 | }; 319 | } 320 | } else { 321 | quote! { 322 | #global_match_state_matcher 323 | global_match_states.#name_token = Some(args.value_of(#name).unwrap().parse::<#typed>().expect("Failed to parse value. Double check!")); 324 | } 325 | }; 326 | 327 | app = if optional { 328 | let long = format!("--{}", name); 329 | let short = format!("-{}", &name[..1]); 330 | quote! { 331 | #app 332 | .arg(Arg::with_name(#name).long(#long).short(#short).takes_value(true).help(#about)) 333 | } 334 | } else { 335 | quote! { 336 | #app 337 | .arg(Arg::with_name(#name).takes_value(true).required(true).help(#about)) 338 | } 339 | }; 340 | }); 341 | 342 | for item in &i.items { 343 | match item { 344 | &ImplItem::Method(ref i) => { 345 | let name = LitStr::new(&i.sig.ident.to_string(), i.sig.ident.span); 346 | let func_id = &i.sig.ident; 347 | let about = match i.attrs.first() { 348 | Some(a) => String::from( 349 | format!("{}", a.tts) 350 | /* Clean the tokens TODO: Make this not suck */ 351 | .replace("/", "") 352 | .replace("\\", "") 353 | .replace("\"", "") 354 | .replace("=", "").trim(), 355 | ), 356 | _ => String::new(), 357 | }; 358 | 359 | let mut arguments = quote!(); 360 | 361 | let mut index: usize = 0; 362 | let args = i.sig 363 | .decl 364 | .inputs 365 | .iter() 366 | .fold(quote!{}, |acc, arg| match arg { 367 | &FnArg::Captured(ref arg) => match &arg.pat { 368 | &Pat::Ident(ref i) => { 369 | let name = format!("{}", i.ident); 370 | let optional = match arg.ty { 371 | Type::Path(ref p) => match p.path.segments.first() { 372 | Some(ps) => match &ps.value().ident.to_string().as_str() { 373 | &"Option" => true, 374 | _ => false, 375 | }, 376 | _ => false, 377 | }, 378 | _ => false, 379 | }; 380 | 381 | let mmm = if let Some(typed) = match arg.ty { 382 | Type::Path(ref p) => match p.path.segments.first() { 383 | Some(ps) => match optional { 384 | false => Some(arg.ty.clone()), 385 | true => match ps.value().arguments { 386 | PathArguments::AngleBracketed(ref b) => { 387 | match b.args.first() { 388 | Some(pair) => match pair.value() { 389 | GenericArgument::Type(Type::Path( 390 | pp, 391 | )) => Some(Type::from(pp.clone())), 392 | _ => None, 393 | }, 394 | _ => None, 395 | } 396 | } 397 | _ => None, 398 | }, 399 | }, 400 | _ => None, 401 | }, 402 | _ => None, 403 | } { 404 | if optional { 405 | quote! { 406 | match m.value_of(#name) { 407 | Some(m) => Some(m.parse::<#typed>().unwrap()), 408 | None => None 409 | } 410 | } 411 | } else { 412 | quote! { m.value_of(#name).unwrap().parse::<#typed>().unwrap() } 413 | } 414 | } else { 415 | if optional { 416 | quote! { m.value_of(#name) } 417 | } else { 418 | quote! { m.value_of(#name).unwrap() } 419 | } 420 | }; 421 | 422 | index += 1; 423 | if optional { 424 | arguments = quote! { 425 | #arguments 426 | #mmm 427 | }; 428 | quote! { #acc.arg(Arg::with_name(#name)) } 429 | } else { 430 | arguments = quote! { 431 | #arguments 432 | #mmm, 433 | }; 434 | quote! { #acc.arg(Arg::with_name(#name).required(true)) } 435 | } 436 | } 437 | _ => quote!{ #acc }, 438 | }, 439 | _ => quote!{ #acc }, 440 | }); 441 | 442 | app = quote! { 443 | #app.subcommand( 444 | SubCommand::with_name(#name).about(#about)#args 445 | ) 446 | }; 447 | 448 | matches.push(quote! { (#name, Some(m)) => #app_token :: #func_id ( #arguments ), }); 449 | } 450 | _ => {} 451 | } 452 | } 453 | 454 | // let mut matchy = quote!{ match args.subcommand() { }; 455 | let mut matchy = quote!{}; 456 | 457 | for m in &matches { 458 | matchy = quote! { 459 | #matchy 460 | #m 461 | }; 462 | } 463 | 464 | matchy = quote! { 465 | match args.subcommand() { 466 | #matchy 467 | _ => { /* We drop errors for now... */ }, 468 | } 469 | }; 470 | 471 | matchy = quote! { 472 | let mut global_match_states = __ThunderDataStaticStore::new_empty_store(); 473 | #global_match_state_matcher 474 | 475 | unsafe { 476 | __THUNDER_DATA_STATIC = Some(global_match_states); 477 | } 478 | 479 | #matchy 480 | }; 481 | 482 | let tokens = quote! { 483 | #orignal 484 | 485 | /// This block was generated by thunder v0.0.0 486 | #[allow(unused)] 487 | impl #app_token { 488 | 489 | /// Starts the CLI parsing and calls whichever function handles the input 490 | fn start() { 491 | use clap::{App, SubCommand, Arg, AppSettings}; 492 | 493 | let app = #app; 494 | let args = app.get_matches(); 495 | #matchy 496 | } 497 | 498 | #accessors 499 | } 500 | 501 | static mut __THUNDER_DATA_STATIC: Option<__ThunderDataStaticStore> = None; 502 | 503 | /// This block was generated by thunder v0.0.0 504 | #[allow(unused)] 505 | #[doc(hidden)] 506 | struct __ThunderDataStaticStore { 507 | #data_struct_fields 508 | } 509 | 510 | #[allow(unused)] 511 | #[doc(hidden)] 512 | impl __ThunderDataStaticStore { 513 | pub fn new_empty_store() -> __ThunderDataStaticStore { 514 | __ThunderDataStaticStore { 515 | #init_struct_fields 516 | } 517 | } 518 | } 519 | }; 520 | 521 | tokens.into() 522 | } 523 | --------------------------------------------------------------------------------