idgen_cli/
processor.rs

1use crate::id::{new_id, CuidVersion, IDError, IDFormat, UuidVersion};
2use crate::inspector::inspect_id;
3use serde::Serialize;
4use std::env;
5use std::process;
6
7/// Exit codes following Unix conventions
8pub mod exit_codes {
9    /// Successful execution
10    pub const SUCCESS: i32 = 0;
11    /// General runtime error (e.g., ID generation failed due to system error)
12    pub const ERROR: i32 = 1;
13    /// Invalid command-line arguments or usage (e.g., missing required params)
14    pub const USAGE_ERROR: i32 = 2;
15}
16
17#[derive(Serialize)]
18struct IdOutput {
19    value: String,
20}
21
22pub fn parse_n_process() {
23    let args: Vec<String> = env::args().collect();
24    let mut version = UuidVersion::V4;
25    let mut format = IDFormat::Hyphenated(version);
26    let mut count = 1;
27    let mut help = false;
28    let mut show_version = false;
29    let mut len: Option<usize> = None;
30    let mut prefix = "";
31    let mut suffix = "";
32    let mut namespace: Option<String> = None;
33    let mut name: Option<String> = None;
34    let mut show_banner = false;
35    let mut json_output = false;
36    let mut inspect_target: Option<String> = None;
37
38    let mut lastcmd = String::new();
39
40    args.iter().enumerate().for_each(|(_, arg)| {
41        if arg == "-h" || arg == "--help" {
42            help = true;
43        } else if arg == "-v" || arg == "--version" {
44            show_version = true;
45        } else if arg == "--json" {
46            json_output = true;
47            show_banner = false;
48        } else if arg == "-s" || arg == "--simple" {
49            format = IDFormat::Simple(version);
50        } else if arg == "-u" || arg == "--urn" {
51            format = IDFormat::URN(version);
52        } else if arg == "-o" || arg == "--objectid" {
53            format = IDFormat::OID;
54        } else if arg == "-n" || arg == "--nano" {
55            format = IDFormat::NanoID;
56        } else if arg == "-c1" || arg == "--cuid1" {
57            format = IDFormat::Cuid(CuidVersion::V1);
58        } else if arg == "-c2" || arg == "--cuid2" {
59            format = IDFormat::Cuid(CuidVersion::V2);
60        } else if arg == "-l" || arg == "--ulid" {
61            format = IDFormat::Ulid;
62        } else if arg == "-b" || arg == "--banner" {
63            show_banner = true;
64        } else if arg == "-u1" || arg == "--uuid1" {
65            version = UuidVersion::V1;
66            format = match format.clone() {
67                IDFormat::Simple(_) => IDFormat::Simple(version),
68                IDFormat::Hyphenated(_) => IDFormat::Hyphenated(version),
69                IDFormat::URN(_) => IDFormat::URN(version),
70                _ => format.clone(),
71            };
72        } else if arg == "-u3" || arg == "--uuid3" {
73            version = UuidVersion::V3;
74            format = match format.clone() {
75                IDFormat::Simple(_) => IDFormat::Simple(version),
76                IDFormat::Hyphenated(_) => IDFormat::Hyphenated(version),
77                IDFormat::URN(_) => IDFormat::URN(version),
78                _ => format.clone(),
79            };
80        } else if arg == "-u4" || arg == "--uuid4" {
81            version = UuidVersion::V4;
82            format = match format.clone() {
83                IDFormat::Simple(_) => IDFormat::Simple(version),
84                IDFormat::Hyphenated(_) => IDFormat::Hyphenated(version),
85                IDFormat::URN(_) => IDFormat::URN(version),
86                _ => format.clone(),
87            };
88        } else if arg == "-u5" || arg == "--uuid5" {
89            version = UuidVersion::V5;
90            format = match format.clone() {
91                IDFormat::Simple(_) => IDFormat::Simple(version),
92                IDFormat::Hyphenated(_) => IDFormat::Hyphenated(version),
93                IDFormat::URN(_) => IDFormat::URN(version),
94                _ => format.clone(),
95            };
96        }
97
98        if lastcmd == "-c" || lastcmd == "--count" {
99            count = arg.parse::<i32>().unwrap_or(1);
100        } else if lastcmd == "-n" || lastcmd == "--nano" {
101            len = arg.parse::<usize>().ok();
102        } else if lastcmd == "-p" || lastcmd == "--prefix" {
103            prefix = arg;
104        } else if lastcmd == "-f" || lastcmd == "--suffix" {
105            suffix = arg;
106        } else if lastcmd == "--namespace" {
107            namespace = Some(arg.to_string());
108        } else if lastcmd == "--name" {
109            name = Some(arg.to_string());
110        } else if lastcmd == "--inspect" {
111            inspect_target = Some(arg.to_string());
112        }
113
114        lastcmd = arg.clone();
115    });
116
117    if let Some(target) = inspect_target {
118        let result = inspect_id(&target);
119        if json_output {
120            let json = serde_json::to_string_pretty(&result).unwrap();
121            println!("{}", json);
122        } else {
123            println!("ID: {}", target);
124            println!("Valid: {}", result.valid);
125            println!("Type: {}", result.id_type);
126            if let Some(v) = result.version {
127                println!("Version: {}", v);
128            }
129            if let Some(v) = result.variant {
130                println!("Variant: {}", v);
131            }
132            if let Some(ts) = result.timestamp {
133                println!("Timestamp: {}", ts);
134            }
135        }
136        // Exit with error code if ID is invalid
137        if !result.valid {
138            process::exit(exit_codes::ERROR);
139        }
140        return;
141    }
142
143    if show_banner {
144        print_banner();
145    }
146
147    if help {
148        print_help();
149        return;
150    }
151
152    if show_version {
153        print_version();
154        return;
155    }
156
157    // Validate count
158    if count < 1 {
159        eprintln!("Error: Count must be at least 1, got {}", count);
160        process::exit(exit_codes::USAGE_ERROR);
161    }
162
163    match print_uuid(
164        format,
165        len,
166        count,
167        prefix,
168        suffix,
169        namespace.as_deref(),
170        name.as_deref(),
171        json_output,
172    ) {
173        Ok(_) => {}
174        Err(err) => {
175            eprintln!("Error: {}", err);
176            // Check if it's a usage error (missing/invalid arguments)
177            let exit_code = if err.is::<IDError>() {
178                exit_codes::USAGE_ERROR
179            } else {
180                exit_codes::ERROR
181            };
182            process::exit(exit_code);
183        }
184    }
185}
186
187fn print_uuid(
188    id_format: IDFormat,
189    len: Option<usize>,
190    count: i32,
191    prefix: &str,
192    suffix: &str,
193    namespace: Option<&str>,
194    name: Option<&str>,
195    json_output: bool,
196) -> Result<(), Box<dyn std::error::Error>> {
197    if json_output {
198        let mut ids = Vec::new();
199        for _ in 0..count {
200            let id = new_id(&id_format, len, namespace, name)?;
201            ids.push(IdOutput {
202                value: format!("{}{}{}", prefix, id, suffix),
203            });
204        }
205        let json = serde_json::to_string_pretty(&ids)?;
206        println!("{}", json);
207    } else {
208        for i in 0..count {
209            let id = new_id(&id_format, len, namespace, name)?;
210            print!("{}{}{}", prefix, id, suffix);
211            if i < count - 1 {
212                print!("\n");
213            }
214        }
215        print!("\n");
216    }
217    Ok(())
218}
219
220/// Prints the program version.
221fn print_version() {
222    const VERSION: &'static str = env!("CARGO_PKG_VERSION");
223    print!("Version {}", VERSION);
224}
225
226/// Prints the help message!
227fn print_help() {
228    const VERSION: &'static str = env!("CARGO_PKG_VERSION");
229    let help = format!(
230        "ID Generator Version {}
231  Mohamed Aamir Maniar - https://www.linkedin.com/in/aamironline/
232  Generates and prints UUIDs (all versions), NanoID, and MongoDB ObjectIDs.
233
234  USAGE:
235      idgen [OPTIONS]
236
237  FLAGS:
238      -h --help                                       Prints the help information
239      -v --version                                    Prints the version information
240      -b --banner                                     Show the banner output
241         --json                                       Output as JSON
242         --inspect <ID>                               Inspect an ID string
243
244  UUID VERSION OPTIONS:
245      -u1 --uuid1                                     Generates UUID version 1 (Time-based)
246      -u3 --uuid3                                     Generates UUID version 3 (MD5 hash-based)
247      -u4 --uuid4                                     Generates UUID version 4 (Random - Default)
248      -u5 --uuid5                                     Generates UUID version 5 (SHA1 hash-based)
249
250  FORMAT OPTIONS:
251      -s  --simple                                     Generates UUID without hyphens
252      -u  --urn                                        Generates UUID with URN signature
253      -o  --objectid                                   Generates sequential MongoDB ObjectId
254      -d  --hyphen                                     Generates hyphened version of UUID (Default)
255      -n  --nanoid <num?>                              Generates nanoid with specified length (Default: 21)
256      -c1 --cuid1                                      Generates a CUIDv1
257      -c2 --cuid2                                      Generates a CUIDv2
258      -l  --ulid                                       Generates a ULID
259
260  OTHER OPTIONS:
261      -c --count    <num>                             Number of IDs to generate (Default: 1)
262      -p --prefix   <str>                             Prefix for the generated IDs (Default: None)
263      -f --suffix   <str>                             Suffix for the generated IDs (Default: None)
264         --namespace <str>                            Namespace UUID for v3/v5 (Required for v3/v5)
265         --name     <str>                             Name string for v3/v5 (Required for v3/v5)
266
267  EXAMPLES:
268      idgen -u4                                       Generate a random UUID v4 (default)
269      idgen -u1                                       Generate a time-based UUID v1
270      idgen -u3 --namespace DNS --name example.com    Generate a v3 UUID
271      idgen -u5 --namespace DNS --name example.com    Generate a v5 UUID
272      idgen -s -u4                                    Generate a simple UUID v4 without hyphens
273      idgen -u -u4                                    Generate a UUID v4 with URN format
274      idgen -n                                        Generate a NanoID of default length (21)
275      idgen -n 10                                     Generate a NanoID of length 10
276      idgen -o                                        Generate a MongoDB ObjectID
277      idgen -c1                                       Generate a version 1 CUID
278      idgen -c2                                       Generate a version 2 CUID
279      idgen -l                                        Generate a ULID
280      idgen -c 5                                      Generate 5 UUIDs
281      idgen -p 'test-' -c 3                           Generate 3 UUIDs with prefix 'test-'
282      idgen -f '.log' -n                              Generate a NanoID with suffix '.log'
283  ",
284        VERSION
285    )
286    .replace("\n  ", "\n");
287    println!("{}", help);
288}
289
290fn print_banner() {
291    // represents the multiline banner text
292    let banner = r#" _     _
293(_) __| | __ _  ___ _ __
294| |/ _` |/ _` |/ _ \ '_ \
295| | (_| | (_| |  __/ | | |
296|_|\__,_|\__, |\___|_| |_|
297         |___/"#;
298    print!("{}\n", banner); // Changed to print! and explicit \n
299}