├── .gitignore ├── tests ├── main.rs └── test_cli_subcommand.rs ├── src └── main.rs ├── example ├── Cargo.toml └── main.rs ├── Cargo.toml ├── terse_cli_lib ├── Cargo.toml └── lib.rs ├── terse_cli ├── lib.rs └── Cargo.toml ├── LICENSE.md ├── readme.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /tests/main.rs: -------------------------------------------------------------------------------- 1 | mod test_cli_subcommand; 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("workspace root, this doesn't do anything!"); 3 | } 4 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.21", features = ["derive", "unstable-doc"] } 8 | terse_cli = { path = "../terse_cli" } 9 | 10 | [[bin]] 11 | name = "cli" 12 | path = "main.rs" 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terse-cli-workspace" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | terse_cli_lib = { path = "./terse_cli_lib" } 8 | quote = "1.0.37" 9 | pretty_assertions = "1.4.1" 10 | 11 | [workspace] 12 | members = ["terse_cli_lib", "terse_cli", "example"] 13 | -------------------------------------------------------------------------------- /terse_cli_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terse_cli_lib" 3 | version = "0.1.2" 4 | authors = ["David McNamee ", "Jeffray Zhang "] 5 | edition = "2021" 6 | description = "A library for building no-boilerplate CLI apps" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | convert_case = "0.6.0" 11 | proc-macro2 = "1.0.92" 12 | quote = "1.0.37" 13 | syn = { version = "2.0.89", features = ["full"] } 14 | 15 | [lib] 16 | path = "lib.rs" 17 | -------------------------------------------------------------------------------- /terse_cli/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::TokenStream; 3 | 4 | #[proc_macro_attribute] 5 | pub fn command(_attr: TokenStream, item: TokenStream) -> TokenStream { 6 | let _attr = proc_macro2::TokenStream::from(_attr); 7 | let item = proc_macro2::TokenStream::from(item); 8 | 9 | terse_cli_lib::command(_attr, item).unwrap().into() 10 | } 11 | 12 | #[proc_macro] 13 | pub fn subcommands(item: TokenStream) -> TokenStream { 14 | terse_cli_lib::subcommands(proc_macro2::TokenStream::from(item)) 15 | .unwrap() 16 | .into() 17 | } 18 | -------------------------------------------------------------------------------- /terse_cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terse_cli" 3 | version = "0.1.4" 4 | authors = ["David McNamee ", "Jeffray Zhang "] 5 | edition = "2021" 6 | description = "A library for building no-boilerplate CLI apps" 7 | license = "MIT" 8 | repository = "https://github.com/JeffrayZhang/terse-cli" 9 | homepage = "https://github.com/JeffrayZhang/terse-cli" 10 | readme = "../readme.md" 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.92" 14 | terse_cli_lib = { path = "../terse_cli_lib", version = "0.1.2" } 15 | 16 | [lib] 17 | proc-macro = true 18 | path = "lib.rs" 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jeffray Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use terse_cli::{command, subcommands}; 3 | 4 | /// Example: cargo run -- command-one --a 3 5 | #[command] 6 | fn command_one(a: i32, b: Option) -> i32 { 7 | a + b.unwrap_or(0) 8 | } 9 | 10 | /// Example: cargo run -- my-subcommands command-two --name Bob 11 | #[command] 12 | fn command_two(name: String) -> String { 13 | format!("hello {}", name) 14 | } 15 | 16 | /// Example: cargo run -- my-subcommands command-three --a 7 --b 3 17 | #[command] 18 | fn command_three(a: i32, b: i32) -> String { 19 | format!("the difference is {}", a - b) 20 | } 21 | 22 | /// Example: cargo run -- my-subcommands command-four 23 | #[command] 24 | fn command_four() -> String { 25 | "command four".to_string() 26 | } 27 | 28 | subcommands!( 29 | /// These are the subcommands 30 | my_subcommands, [command_two, command_three, command_four]); 31 | 32 | subcommands!( 33 | /// Example docs for the "root" 34 | cli, [command_one, my_subcommands]); 35 | 36 | fn main() { 37 | cli::run(cli::Args::parse()); 38 | } 39 | 40 | // you can also use `--help` as you would expect 41 | // Example: cargo run -- my-subcommands --help 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Terse CLI 3 | 4 | A wrapper around [clap](https://github.com/clap-rs/clap) that lets you build CLI applications with very little boilerplate code. 5 | 6 | Modeled after [tiangolo's typer python library](https://github.com/fastapi/typer), you simply define your commands and subcommands as functions and annotate them with the `#[command]` attribute. 7 | 8 | ## Current Status: Alpha 9 | 10 | This is a work in progress. The core functionality is implemented, but if you want any customization on how your CLI is used (e.g. positional arguments, custom help messages, etc.) those things are not yet supported. 11 | 12 | Known issues: 13 | - [ ] every command must have a return type that implements `Display` 14 | - [ ] positional arguments are not yet supported 15 | - [ ] argument docs are not yet supported 16 | 17 | ## Installation 18 | 19 | Install both clap and terse_cli: 20 | 21 | ```sh 22 | $ cargo add clap --features derive 23 | $ cargo add terse_cli 24 | ``` 25 | 26 | ## Example 27 | 28 | Below snippet is from [./example/main.rs](./example/main.rs). 29 | 30 | ```rs 31 | use clap::Parser; 32 | use terse_cli::{command, subcommands}; 33 | 34 | /// Example: cargo run -- command-one --a 3 35 | #[command] 36 | fn command_one(a: i32, b: Option) -> i32 { 37 | a + b.unwrap_or(0) 38 | } 39 | 40 | /// Example: cargo run -- my-subcommands command-two --name Bob 41 | #[command] 42 | fn command_two(name: String) -> String { 43 | format!("hello {}", name) 44 | } 45 | 46 | /// Example: cargo run -- my-subcommands command-three --a 7 --b 3 47 | #[command] 48 | fn command_three(a: i32, b: i32) -> String { 49 | format!("the difference is {}", a - b) 50 | } 51 | 52 | /// Example: cargo run -- my-subcommands command-four 53 | #[command] 54 | fn command_four() -> String { 55 | "command four".to_string() 56 | } 57 | 58 | subcommands!( 59 | /// These are the subcommands 60 | my_subcommands, [command_two, command_three, command_four]); 61 | 62 | subcommands!( 63 | /// Example docs for the "root" 64 | cli, [command_one, my_subcommands]); 65 | 66 | fn main() { 67 | cli::run(cli::Args::parse()); 68 | } 69 | 70 | // you can also use `--help` as you would expect 71 | // Example: cargo run -- my-subcommands --help 72 | 73 | ``` 74 | -------------------------------------------------------------------------------- /tests/test_cli_subcommand.rs: -------------------------------------------------------------------------------- 1 | use terse_cli_lib::{command, subcommands}; 2 | use pretty_assertions::assert_eq; 3 | use quote::quote; 4 | 5 | #[test] 6 | pub fn test_command_macro() { 7 | let in_stream = quote! { 8 | fn my_func(a: i32, b: i32) -> i32 { 9 | a + b 10 | } 11 | }; 12 | let attr_stream = quote! { #[subcommand] }; 13 | let out_stream = command(attr_stream, in_stream).unwrap(); 14 | let expected = quote! { 15 | mod my_func { 16 | use super::my_func; 17 | use clap::{command, Parser}; 18 | #[derive(Parser)] 19 | # [command (version , about , long_about = None)] 20 | pub struct Args { 21 | #[arg(long)] 22 | a: i32, 23 | #[arg(long)] 24 | b: i32 25 | } 26 | pub fn run(Args { a, b }: Args) { 27 | let result = my_func(a, b); 28 | println!("{}", result); 29 | } 30 | } 31 | fn my_func(a: i32, b: i32) -> i32 { 32 | a + b 33 | } 34 | }; 35 | assert_eq!(out_stream.to_string(), expected.to_string()); 36 | } 37 | 38 | 39 | 40 | #[test] 41 | pub fn test_command_macro_no_args() { 42 | let in_stream = quote! { 43 | /// doc comment test 44 | fn my_func() -> i32 { 45 | 42 46 | } 47 | }; 48 | let attr_stream = quote! { #[subcommand] }; 49 | let out_stream = command(attr_stream, in_stream).unwrap(); 50 | let expected = quote! { 51 | mod my_func { 52 | use super::my_func; 53 | use clap::{command, Parser}; 54 | 55 | #[derive(Parser)] 56 | #[command(version, about, long_about = None)] 57 | #[doc = r" doc comment test"] 58 | pub struct Args {} 59 | 60 | pub fn run(Args {}: Args) { 61 | let result = my_func(); 62 | println!("{}", result); 63 | } 64 | } 65 | 66 | #[doc = r" doc comment test"] 67 | fn my_func() -> i32 { 68 | 42 69 | } 70 | }; 71 | assert_eq!(out_stream.to_string(), expected.to_string()); 72 | } 73 | 74 | #[test] 75 | pub fn test_subcommands_macro() { 76 | let in_stream = quote! { 77 | cli, [command_one, command_two] 78 | }; 79 | let out_stream = subcommands(in_stream).unwrap(); 80 | 81 | let expected = quote! { 82 | mod cli { 83 | use super::{command_one, command_two}; 84 | use clap::{command, Parser, Subcommand}; 85 | 86 | #[derive(Subcommand)] 87 | pub enum Subcommands { 88 | CommandOne(command_one::Args), 89 | CommandTwo(command_two::Args) 90 | } 91 | 92 | #[derive(Parser)] 93 | #[command(version, about, long_about = None)] 94 | pub struct Args { 95 | #[command(subcommand)] 96 | command: Subcommands, 97 | } 98 | 99 | pub fn run(Args { command }: Args) { 100 | match command { 101 | Subcommands::CommandOne(args) => command_one::run(args), 102 | Subcommands::CommandTwo(args) => command_two::run(args) 103 | }; 104 | } 105 | } 106 | }; 107 | assert_eq!(out_stream.to_string(), expected.to_string()); 108 | } 109 | -------------------------------------------------------------------------------- /terse_cli_lib/lib.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | use proc_macro2::TokenStream; 3 | use quote::{quote, ToTokens}; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::{bracketed, parse2, Attribute, Ident, ItemFn, Token}; 6 | 7 | fn extract_docs(a: &[Attribute]) -> impl Iterator { 8 | a.iter().filter(|a| a.path().is_ident("doc")) 9 | } 10 | 11 | #[derive(Debug)] 12 | pub enum CommandMacroError { 13 | InvalidCliArgumentError(InvalidCliArgumentError), 14 | InvalidSubcommandFunctionError(InvalidSubcommandFunctionError), 15 | } 16 | 17 | /// don't use this directly, use apps/macros instead 18 | pub fn command( 19 | _attr: TokenStream, 20 | item: TokenStream, 21 | ) -> Result { 22 | let input = parse2::(item).map_err(|err| CommandMacroError::InvalidSubcommandFunctionError(InvalidSubcommandFunctionError { 23 | message: "Failed to parse function", 24 | err, 25 | }), 26 | )?; 27 | let fn_name = &input.sig.ident; 28 | let args = &input.sig.inputs; 29 | let (fields, arg_names): (Vec<_>, Vec<_>) = args 30 | .iter() 31 | .map(|arg| match arg { 32 | syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => { 33 | Ok((quote! { #[arg(long)] #pat: #ty }, quote! { #pat })) 34 | } 35 | _ => Err(CommandMacroError::InvalidCliArgumentError(InvalidCliArgumentError(format!( 36 | "Invalid cli argument: {}", 37 | arg.to_token_stream() 38 | )))), 39 | }) 40 | .collect::, _>>()? 41 | .into_iter() 42 | .unzip(); 43 | 44 | let docs = extract_docs(&input.attrs); 45 | 46 | let expanded = quote! { 47 | mod #fn_name { 48 | use super::#fn_name; 49 | use clap::{command, Parser}; 50 | 51 | #[derive(Parser)] 52 | #[command(version, about, long_about = None)] 53 | #(#docs)* 54 | pub struct Args { 55 | #(#fields),* 56 | } 57 | pub fn run(Args { #(#arg_names),* }: Args) { 58 | let result = #fn_name(#(#arg_names),*); 59 | println!("{}", result); 60 | } 61 | } 62 | #input 63 | }; 64 | Ok(expanded) 65 | } 66 | 67 | #[derive(Debug)] 68 | pub enum SubcommandsMacroError { 69 | InvalidIdentifierError(InvalidIdentifierError), 70 | InvalidIdentifierListError(InvalidIdentifierListError), 71 | } 72 | 73 | /// don't use this directly, use apps/macros instead 74 | pub fn subcommands( 75 | item: TokenStream, 76 | ) -> Result { 77 | let MergeSubcommandsInput { 78 | sub_doc, 79 | cli_ident, 80 | subcommands, 81 | } = parse2::(item).map_err(|err| SubcommandsMacroError::InvalidIdentifierListError(InvalidIdentifierListError { 82 | message: "subcommands only accepts lists of identifiers", 83 | err, 84 | }))?; 85 | 86 | let match_arms = subcommands.iter().map(|sc| { 87 | let ident = &sc.ident; 88 | let cmd_name = get_command_name(ident); 89 | quote! { 90 | Subcommands::#cmd_name(args) => #ident::run(args) 91 | } 92 | }); 93 | let command_enum_fields = subcommands.iter().map(|sc| { 94 | let docs = extract_docs(&sc.attrs); 95 | let ident = &sc.ident; 96 | let cmd_name = get_command_name(ident); 97 | quote! { 98 | #(#docs)* 99 | #cmd_name(#ident::Args) 100 | } 101 | }); 102 | let idents_tokens = subcommands.iter().map(|sc| sc.ident.to_token_stream()); 103 | let sub_doc_mod = sub_doc.clone(); 104 | 105 | let expanded = quote! { 106 | #(#sub_doc_mod)* 107 | mod #cli_ident { 108 | use super::{#(#idents_tokens),*}; 109 | use clap::{command, Parser, Subcommand}; 110 | 111 | #[derive(Subcommand)] 112 | #(#sub_doc)* 113 | pub enum Subcommands { 114 | #(#command_enum_fields),* 115 | } 116 | #[derive(Parser)] 117 | #[command(version, about, long_about = None)] 118 | pub struct Args { 119 | #[command(subcommand)] 120 | command: Subcommands, 121 | } 122 | pub fn run(Args { command }: Args) { 123 | match command { 124 | #(#match_arms),* 125 | }; 126 | } 127 | } 128 | }; 129 | 130 | Ok(expanded) 131 | } 132 | 133 | #[allow(dead_code)] 134 | struct Subcommand { 135 | attrs: Vec, 136 | ident: Ident, 137 | } 138 | 139 | struct MergeSubcommandsInput { 140 | sub_doc: Vec, 141 | cli_ident: Ident, 142 | subcommands: Vec, 143 | } 144 | 145 | impl Parse for Subcommand { 146 | fn parse(input: ParseStream) -> syn::Result { 147 | let attrs = input.call(Attribute::parse_outer)?; 148 | let ident: Ident = input.parse()?; 149 | Ok(Subcommand { attrs, ident }) 150 | } 151 | } 152 | 153 | impl Parse for MergeSubcommandsInput { 154 | fn parse(input: ParseStream) -> syn::Result { 155 | // parse any doc comments attached to the cli_ident 156 | let sub_doc = Attribute::parse_outer(input) 157 | .unwrap_or_default() 158 | .into_iter() 159 | .filter(|attr| attr.path().is_ident("doc")) 160 | .collect::>(); 161 | 162 | let cli_ident: Ident = input.parse()?; 163 | input.parse::()?; 164 | let content; 165 | bracketed!(content in input); 166 | let subcommands: syn::punctuated::Punctuated = 167 | content.parse_terminated(Subcommand::parse, Token![,])?; 168 | Ok(MergeSubcommandsInput { 169 | sub_doc, 170 | cli_ident, 171 | subcommands: subcommands.into_iter().collect(), 172 | }) 173 | } 174 | } 175 | 176 | fn get_command_name(func_name: &Ident) -> Ident { 177 | Ident::new( 178 | &func_name.to_string().to_case(Case::UpperCamel), 179 | func_name.span(), 180 | ) 181 | } 182 | 183 | #[allow(dead_code)] 184 | #[derive(Debug, Clone, Copy)] 185 | pub struct InvalidIdentifierError(&'static str); 186 | 187 | #[allow(dead_code)] 188 | #[derive(Debug, Clone)] 189 | pub struct InvalidIdentifierListError { 190 | message: &'static str, 191 | err: syn::Error, 192 | } 193 | 194 | #[allow(dead_code)] 195 | #[derive(Debug, Clone)] 196 | pub struct InvalidSubcommandFunctionError { 197 | message: &'static str, 198 | err: syn::Error, 199 | } 200 | 201 | #[allow(dead_code)] 202 | #[derive(Debug, Clone)] 203 | pub struct InvalidCliArgumentError(String); 204 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys 0.59.0", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys 0.59.0", 52 | ] 53 | 54 | [[package]] 55 | name = "bitflags" 56 | version = "2.6.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 59 | 60 | [[package]] 61 | name = "clap" 62 | version = "4.5.21" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 65 | dependencies = [ 66 | "clap_builder", 67 | "clap_derive", 68 | ] 69 | 70 | [[package]] 71 | name = "clap_builder" 72 | version = "4.5.21" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 75 | dependencies = [ 76 | "anstream", 77 | "anstyle", 78 | "clap_lex", 79 | "strsim", 80 | "terminal_size", 81 | "unicase", 82 | "unicode-width", 83 | ] 84 | 85 | [[package]] 86 | name = "clap_derive" 87 | version = "4.5.18" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 90 | dependencies = [ 91 | "heck", 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "clap_lex" 99 | version = "0.7.3" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 102 | 103 | [[package]] 104 | name = "colorchoice" 105 | version = "1.0.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 108 | 109 | [[package]] 110 | name = "convert_case" 111 | version = "0.6.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 114 | dependencies = [ 115 | "unicode-segmentation", 116 | ] 117 | 118 | [[package]] 119 | name = "diff" 120 | version = "0.1.13" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 123 | 124 | [[package]] 125 | name = "errno" 126 | version = "0.3.9" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 129 | dependencies = [ 130 | "libc", 131 | "windows-sys 0.52.0", 132 | ] 133 | 134 | [[package]] 135 | name = "example" 136 | version = "0.1.0" 137 | dependencies = [ 138 | "clap", 139 | "terse_cli", 140 | ] 141 | 142 | [[package]] 143 | name = "heck" 144 | version = "0.5.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 147 | 148 | [[package]] 149 | name = "is_terminal_polyfill" 150 | version = "1.70.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 153 | 154 | [[package]] 155 | name = "libc" 156 | version = "0.2.164" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 159 | 160 | [[package]] 161 | name = "linux-raw-sys" 162 | version = "0.4.14" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 165 | 166 | [[package]] 167 | name = "pretty_assertions" 168 | version = "1.4.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" 171 | dependencies = [ 172 | "diff", 173 | "yansi", 174 | ] 175 | 176 | [[package]] 177 | name = "proc-macro2" 178 | version = "1.0.92" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 181 | dependencies = [ 182 | "unicode-ident", 183 | ] 184 | 185 | [[package]] 186 | name = "quote" 187 | version = "1.0.37" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 190 | dependencies = [ 191 | "proc-macro2", 192 | ] 193 | 194 | [[package]] 195 | name = "rustix" 196 | version = "0.38.41" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" 199 | dependencies = [ 200 | "bitflags", 201 | "errno", 202 | "libc", 203 | "linux-raw-sys", 204 | "windows-sys 0.52.0", 205 | ] 206 | 207 | [[package]] 208 | name = "strsim" 209 | version = "0.11.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 212 | 213 | [[package]] 214 | name = "syn" 215 | version = "2.0.89" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "unicode-ident", 222 | ] 223 | 224 | [[package]] 225 | name = "terminal_size" 226 | version = "0.4.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" 229 | dependencies = [ 230 | "rustix", 231 | "windows-sys 0.59.0", 232 | ] 233 | 234 | [[package]] 235 | name = "terse-cli-workspace" 236 | version = "0.1.0" 237 | dependencies = [ 238 | "pretty_assertions", 239 | "quote", 240 | "terse_cli_lib", 241 | ] 242 | 243 | [[package]] 244 | name = "terse_cli" 245 | version = "0.1.4" 246 | dependencies = [ 247 | "proc-macro2", 248 | "terse_cli_lib", 249 | ] 250 | 251 | [[package]] 252 | name = "terse_cli_lib" 253 | version = "0.1.2" 254 | dependencies = [ 255 | "convert_case", 256 | "proc-macro2", 257 | "quote", 258 | "syn", 259 | ] 260 | 261 | [[package]] 262 | name = "unicase" 263 | version = "2.8.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" 266 | 267 | [[package]] 268 | name = "unicode-ident" 269 | version = "1.0.14" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 272 | 273 | [[package]] 274 | name = "unicode-segmentation" 275 | version = "1.12.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 278 | 279 | [[package]] 280 | name = "unicode-width" 281 | version = "0.2.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 284 | 285 | [[package]] 286 | name = "utf8parse" 287 | version = "0.2.2" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 290 | 291 | [[package]] 292 | name = "windows-sys" 293 | version = "0.52.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 296 | dependencies = [ 297 | "windows-targets", 298 | ] 299 | 300 | [[package]] 301 | name = "windows-sys" 302 | version = "0.59.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 305 | dependencies = [ 306 | "windows-targets", 307 | ] 308 | 309 | [[package]] 310 | name = "windows-targets" 311 | version = "0.52.6" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 314 | dependencies = [ 315 | "windows_aarch64_gnullvm", 316 | "windows_aarch64_msvc", 317 | "windows_i686_gnu", 318 | "windows_i686_gnullvm", 319 | "windows_i686_msvc", 320 | "windows_x86_64_gnu", 321 | "windows_x86_64_gnullvm", 322 | "windows_x86_64_msvc", 323 | ] 324 | 325 | [[package]] 326 | name = "windows_aarch64_gnullvm" 327 | version = "0.52.6" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 330 | 331 | [[package]] 332 | name = "windows_aarch64_msvc" 333 | version = "0.52.6" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 336 | 337 | [[package]] 338 | name = "windows_i686_gnu" 339 | version = "0.52.6" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 342 | 343 | [[package]] 344 | name = "windows_i686_gnullvm" 345 | version = "0.52.6" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 348 | 349 | [[package]] 350 | name = "windows_i686_msvc" 351 | version = "0.52.6" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 354 | 355 | [[package]] 356 | name = "windows_x86_64_gnu" 357 | version = "0.52.6" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 360 | 361 | [[package]] 362 | name = "windows_x86_64_gnullvm" 363 | version = "0.52.6" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 366 | 367 | [[package]] 368 | name = "windows_x86_64_msvc" 369 | version = "0.52.6" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 372 | 373 | [[package]] 374 | name = "yansi" 375 | version = "1.0.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 378 | --------------------------------------------------------------------------------