diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6966732..d525195 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -3,8 +3,16 @@ name: Rust on: push: branches: [ master ] + paths: + - "**.rs" + - "**/Cargo.toml" + - ".github/workflows/**" pull_request: branches: [ master ] + paths: + - "**.rs" + - "**/Cargo.toml" + - ".github/workflows/**" jobs: build: @@ -53,4 +61,19 @@ jobs: - uses: actions-rs/cargo@v1 with: command: clippy - args: --workspace --tests --examples -- -D warnings \ No newline at end of file + args: --workspace --tests --examples -- -D warnings + cli: + name: Fake-CLI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/cargo@v1 + with: + command: build + args: --release --bin fake --features cli + - name: Upload Bin Artifact + uses: actions/upload-artifact@v4 + with: + name: fake + path: target/release/fake + diff --git a/README.md b/README.md index dea7009..5ff6cfb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Docs Status](https://docs.rs/fake/badge.svg)](https://docs.rs/fake) [![Latest Version](https://img.shields.io/crates/v/fake.svg)](https://crates.io/crates/fake) -A Rust library for generating fake data. +A Rust library and command line tool for generating fake data. ## Installation @@ -15,7 +15,7 @@ Default: fake = { version = "3.0.1", features = ["derive"] } ``` -Available features: +Available library features: - `derive`: if you want to use `#[derive(Dummy)]` - supported crates feature flags: @@ -40,6 +40,7 @@ Available features: ## Usage +### In rust code ```rust use fake::{Dummy, Fake, Faker}; use rand::rngs::StdRng; @@ -107,6 +108,24 @@ fn main() { } ``` +## Command line +```shell +Usage: cli [OPTIONS] [COMMAND] + +Commands: + Name + FirstName + CityPrefix + Password + help + +Options: + -r, --repeat [default: 1] + -l, --locale [default: EN] + -h, --help Print help + -V, --version Print version +``` + # Fakers with locale ## Lorem diff --git a/fake/Cargo.toml b/fake/Cargo.toml index 7a83993..0a9abd1 100644 --- a/fake/Cargo.toml +++ b/fake/Cargo.toml @@ -2,7 +2,7 @@ name = "fake" version = "3.0.1" authors = ["cksac "] -description = "An easy to use library for generating fake data like name, number, address, lorem, dates, etc." +description = "An easy to use library and command line for generating fake data like name, number, address, lorem, dates, etc." keywords = ["faker", "data", "generator", "random"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -41,6 +41,7 @@ url-escape = { version = "0.1", optional = true } bson = { version = "2", optional = true } url = { version = "2", optional = true } indexmap = { version = "2", optional = true} +clap = { version = "4.0.32", optional = true, features=["cargo"] } [dev-dependencies] chrono = { version = "0.4", features = ["clock"], default-features = false } @@ -60,6 +61,7 @@ bigdecimal = ["bigdecimal-rs", "rust_decimal"] geo = ["geo-types", "num-traits"] http = ["dep:http", "url-escape"] bson_oid = ["bson"] +cli = ["dep:clap"] [[example]] name = "basic" @@ -82,3 +84,10 @@ required-features = [ name = "usage" path = "examples/usage.rs" required-features = ["derive"] + +[[bin]] +name = "fake" +path = "src/bin/cli/main.rs" +required-features = [ + "cli" +] diff --git a/fake/src/bin/cli/fake_gen.rs b/fake/src/bin/cli/fake_gen.rs new file mode 100644 index 0000000..f4d5eaa --- /dev/null +++ b/fake/src/bin/cli/fake_gen.rs @@ -0,0 +1,227 @@ +use clap::{builder::StyledStr, ArgMatches}; +use fake::{faker, Fake}; +use rand::Rng; +#[derive(Clone, Copy, Debug)] +#[allow(non_camel_case_types)] +pub enum AVAILABLE_LOCALES { + EN, + FR_FR, + ZH_TW, + ZH_CN, + JA_JP, + AR_SA, + PT_BR, +} + +macro_rules! fake_gen_on_return_type { + ($s:ident,$rng:ident,Vec) => { + format!("{:?}", $s.fake_with_rng::, _>($rng)) + }; + ($s:ident,$rng:ident) => { + $s.fake_with_rng($rng) + }; +} + +macro_rules! some_rules { + ($locale:expr, $module:ident, $fake:ident($($arg:ident)?)$(,$return_type:ident)?) => { + + match $locale { + AVAILABLE_LOCALES::EN => { + let s = faker::$module::en::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + AVAILABLE_LOCALES::FR_FR => { + let s = faker::$module::fr_fr::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + AVAILABLE_LOCALES::ZH_TW => { + let s = faker::$module::zh_tw::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + AVAILABLE_LOCALES::ZH_CN => { + let s = faker::$module::zh_cn::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + AVAILABLE_LOCALES::AR_SA => { + let s = faker::$module::ar_sa::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + AVAILABLE_LOCALES::JA_JP => { + let s = faker::$module::ja_jp::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + AVAILABLE_LOCALES::PT_BR => { + let s = faker::$module::pt_br::$fake($($arg)?); + Box::new(move |rng: &mut R| fake_gen_on_return_type!(s,rng$(,$return_type)?)) + + } + } + }; +} + +use clap::{value_parser, Arg, Command}; +macro_rules! generate_command { + ($fake:literal) => { + Command::new($fake) + }; + ($fake:ident) => { + Command::new(stringify!($fake)) + }; + ($fake:ident($($arg:ident: $type:ty=$default:literal),+)) => { + Command::new(stringify!($fake))$(.arg( + Arg::new(stringify!($arg)) + .long(stringify!($arg)) + .default_value(stringify!($default)) + .value_parser(value_parser!($type))))+ + + }; +} + +macro_rules! right_arm { + ($locale:ident, $module:ident, $fake:ident) => { + some_rules!($locale, $module, $fake()) + }; + ($locale:ident, $module:ident, $fake:ident($arg:ident: u8),$sub_matches:ident) => {{ + let value = *$sub_matches.get_one::(stringify!($arg)).unwrap(); + some_rules!($locale, $module, $fake(value)) + }}; + + ($locale:ident, $module:ident, $fake:ident(min: usize, max: usize),$sub_matches:ident$(,$return_type:ident)?) => {{ + let min = *$sub_matches.get_one::("min").unwrap(); + let max = *$sub_matches.get_one::("max").unwrap(); + let range = min..max; + some_rules!($locale, $module, $fake(range)$(,$return_type)?) + }}; +} + +macro_rules! fakegen_commands { + ($(($fake:ident$(($($arg:ident: $arg_type:tt=$arg_default:literal),+))?$(->$return_type:ident)?,$module:ident)),+) => { + ( + vec![$(generate_command!($fake$(($($arg:$arg_type=$arg_default),+))?)),+], + |arg_matches:ArgMatches, locale:AVAILABLE_LOCALES, help_message:StyledStr| { + match arg_matches.subcommand(){ + $(Some((stringify!($fake),_sub_matches))=>{ + right_arm!(locale, $module, $fake$(($($arg: $arg_type),+),_sub_matches)?$(,$return_type)?) + })+ + _ => { + println!("Didn't receive subcommand\n {}", help_message); + std::process::exit(0) + } + } + } + + ) + }; +} + +pub fn all_fakegen_commands() -> ( + Vec, + impl Fn(ArgMatches, AVAILABLE_LOCALES, StyledStr) -> Box String>, +) { + fakegen_commands!( + //address + (CityPrefix, address), + (CitySuffix, address), + (CityName, address), + (CountryName, address), + (CountryCode, address), + (StreetSuffix, address), + (StreetName, address), + (TimeZone, address), + (StateName, address), + (StateAbbr, address), + (SecondaryAddressType, address), + (SecondaryAddress, address), + (ZipCode, address), + (PostCode, address), + (BuildingNumber, address), + (Latitude, address), + (Longitude, address), + (Geohash(precision:u8=1),address), + + //barcode + (Isbn,barcode), + (Isbn10,barcode), + (Isbn13,barcode), + + //creditcard + (CreditCardNumber,creditcard), + + //company + (CompanySuffix, company), + (CompanyName, company), + (Buzzword, company), + (BuzzwordMiddle, company), + (BuzzwordTail, company), + (CatchPhrase, company), + (BsVerb, company), + (BsAdj, company), + (BsNoun, company), + (Bs, company), + (Profession, company), + (Industry, company), + + //internet + (FreeEmailProvider,internet), + (DomainSuffix,internet), + (FreeEmail,internet), + (SafeEmail,internet), + (Username,internet), + (Password(min:usize=10, max:usize=20),internet), + (IPv4,internet), + (IPv6,internet), + (IP,internet), + (MACAddress,internet), + (UserAgent,internet), + + //job + (Seniority,job), + (Field,job), + (Position,job), + + //lorem + (Word,lorem), + (Words(min:usize=5, max:usize=10)->Vec,lorem), + (Sentence(min:usize=5, max:usize=10),lorem), + (Sentences(min:usize=5, max:usize=10)->Vec,lorem), + (Paragraph(min:usize=5, max:usize=10),lorem), + (Paragraphs(min:usize=5, max:usize=10)->Vec,lorem), + + //name + (FirstName,name), + (LastName,name), + (Title,name), + (Suffix,name), + (Name,name), + (NameWithTitle,name), + + //phone_number + (PhoneNumber,phone_number), + (CellNumber,phone_number), + + //filesystem + (FilePath,filesystem), + (FileName,filesystem), + (FileExtension,filesystem), + (DirPath,filesystem), + (MimeType,filesystem), + (Semver,filesystem), + (SemverStable,filesystem), + (SemverUnstable,filesystem), + + //currency + (CurrencyCode,currency), + (CurrencyName,currency), + (CurrencySymbol,currency), + + //finance + (Bic,finance), + (Isin,finance) + ) +} diff --git a/fake/src/bin/cli/main.rs b/fake/src/bin/cli/main.rs new file mode 100644 index 0000000..935b0b4 --- /dev/null +++ b/fake/src/bin/cli/main.rs @@ -0,0 +1,81 @@ +use clap::{command, value_parser, Arg}; +use rand::Rng; +use std::io::{self, Write}; + +mod fake_gen; +#[allow(non_upper_case_globals)] +mod names; + +const AVAILABLE_LOCALES: [&str; 7] = ["en", "fr_fr", "zh_tw", "zh_cn", "ja_jp", "ar_sa", "pt_br"]; + +pub use fake_gen::{all_fakegen_commands, AVAILABLE_LOCALES}; +pub fn main() { + let stdout = io::stdout(); + let mut buf_stdout = io::BufWriter::new(stdout); + + let mut thread_rng = rand::thread_rng(); + let args = cli_parser(); + + writeln!( + buf_stdout, + "Generating {} fakes for {:?} locale", + args.0.repeats, args.0.locale + ) + .unwrap(); + + (0..args.0.repeats).for_each(|_| writeln!(buf_stdout, "{}", args.1(&mut thread_rng)).unwrap()); +} + +impl TryFrom<&str> for AVAILABLE_LOCALES { + type Error = String; + fn try_from(str_val: &str) -> Result { + let str_val = str_val.to_lowercase(); + let variant = match str_val.as_str(){ + "en" => AVAILABLE_LOCALES::EN, + "fr_fr" => AVAILABLE_LOCALES::FR_FR, + "zh_tw" => AVAILABLE_LOCALES::ZH_TW, + "zh_cn" => AVAILABLE_LOCALES::ZH_CN, + "ja_jp" => AVAILABLE_LOCALES::JA_JP, + "ar_sa" => AVAILABLE_LOCALES::AR_SA, + "pt_br" => AVAILABLE_LOCALES::PT_BR, + _=> return Err(format!("{} is either an invalid locale or not yet supported.\n The supported locales are: {:?}",str_val,AVAILABLE_LOCALES)) + }; + Ok(variant) + } +} + +fn cli_parser() -> (Args, impl Fn(&mut R) -> String) { + let (subcommands, fake_generator) = all_fakegen_commands::(); + let mut command = command!() + .arg( + Arg::new("repeat") + .long("repeat") + .short('r') + .default_value("1") + .value_parser(value_parser!(u32)), + ) + .arg( + Arg::new("locale") + .short('l') + .long("locale") + .default_value("EN") + .value_parser(|value: &str| AVAILABLE_LOCALES::try_from(value)), + ) + .subcommands(subcommands) + .arg_required_else_help(true); + let help_message = command.render_help(); + let matches = command.get_matches(); + let repeats = *matches.get_one::("repeat").unwrap(); + let locale = matches + .get_one::("locale") + .unwrap() + .to_owned(); + + let fake_gen = fake_generator(matches, locale, help_message); + (Args { repeats, locale }, fake_gen) +} + +struct Args { + repeats: u32, + locale: AVAILABLE_LOCALES, +} diff --git a/fake/src/bin/cli/names.rs b/fake/src/bin/cli/names.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/fake/src/bin/cli/names.rs @@ -0,0 +1 @@ +