rodalies_cli/config/
cli.rs

1use chrono::{Datelike, Local};
2use clap::{
3    crate_authors, crate_description, crate_name, crate_version, value_parser, Arg, ArgAction,
4    ArgMatches, Command,
5};
6use prettytable::{format, Table};
7use std::error::Error;
8
9/// Configures the CLI behaviour, reads the arguments and returns and returns a container of matches.
10pub fn init_cli() -> ArgMatches {
11    let cli = Command::new(crate_name!())
12        .about(crate_description!())
13        .version(crate_version!())
14        .author(crate_authors!())
15        .arg(
16            Arg::new("interactive")
17                .required(false)
18                .short('i')
19                .long("interactive")
20                .action(ArgAction::SetTrue)
21                .value_parser(value_parser!(bool))
22                .default_missing_value("true")
23                .help("Enable interactive train timetable search. No value required.")
24        )
25        .arg(
26            Arg::new("search")
27                .required(false)
28                .short('s')
29                .long("search")
30                .env("RODALIES_CLI_SEARCH")
31                .action(ArgAction::Set)
32                .help("Search the ID of a given station's name pattern, to later use it on your origin or destination.")
33        )
34        .arg(
35            Arg::new("from")
36                .required(false)
37                .short('f')
38                .long("from")
39                .env("RODALIES_CLI_FROM")
40                .action(ArgAction::Set)
41                .help("The origin's station ID.")
42        )
43        .arg(
44            Arg::new("to")
45                .required(false)
46                .short('t')
47                .long("to")
48                .env("RODALIES_CLI_TO")
49                .action(ArgAction::Set)
50                .help("The destinations's station ID.")
51        )
52        .arg(
53            Arg::new("day")
54                .required(false)
55                .short('d')
56                .long("day")
57                .action(ArgAction::Set)
58                .help("The day value of the date to search for (default = today's day).")
59        )
60        .arg(
61            Arg::new("month")
62                .required(false)
63                .short('m')
64                .long("month")
65                .action(ArgAction::Set)
66                .help("The month value of the date to search for (default = today's month).")
67        )
68        .arg(
69            Arg::new("year")
70                .required(false)
71                .short('y')
72                .long("year")
73                .action(ArgAction::Set)
74                .help("The year value of the date to search for (default = today's year).")
75        );
76
77    cli.get_matches()
78}
79
80/// Configures and returns the Table to print results from.
81pub fn init_results_table() -> Table {
82    let mut results_table = Table::new();
83    results_table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
84    results_table
85}
86
87/// Given a container of CLI args, it processes the `interactive`, `from`, `to` and `search` arguments.
88pub fn interactive_mode(args: &ArgMatches) -> Result<bool, Box<dyn Error>> {
89    let from = args.contains_id("from");
90    let to = args.contains_id("to");
91    let search = args.contains_id("search");
92
93    let is_interactive = !(from || to || search);
94    println!("✨ Interactive mode enabled: '{}'", is_interactive);
95    Ok(is_interactive)
96}
97
98/// Given a container of CLI args, it processes the `search` argument.
99pub fn parse_search(args: &ArgMatches) -> Result<String, Box<dyn Error>> {
100    let search = args.get_one::<String>("search").unwrap();
101    println!("🔍 Searching stations that contain the text: '{}'", search);
102    Ok(search.to_string())
103}
104
105/// Given a container of CLI args, it processes the `from` and `to` arguments.
106pub fn parse_trip(args: &ArgMatches) -> Result<(String, String), Box<dyn Error>> {
107    let from = args.get_one::<String>("from");
108    let to = args.get_one::<String>("to");
109
110    if from.is_none() || to.is_none() {
111        return Err("🚨 Please, specify origin and destination station IDs".into());
112    }
113
114    Ok((from.unwrap().to_string(), to.unwrap().to_string()))
115}
116
117/// Given a container of CLI args, it processes the `day`, `month` and `year` arguments.
118pub fn parse_date(args: &ArgMatches) -> Result<String, Box<dyn Error>> {
119    let dt = Local::now();
120    let day = match args.get_one::<String>("day") {
121        Some(day) => match day.parse::<u32>() {
122            Ok(day) => day,
123            _ => return Err("🚨 Please, specify right value for day".into()),
124        },
125        None => dt.day(),
126    };
127    let month = match args.get_one::<String>("month") {
128        Some(month) => match month.parse::<u32>() {
129            Ok(month) => month,
130            _ => return Err("🚨 Please, specify right value for month".into()),
131        },
132        None => dt.month(),
133    };
134    let year = match args.get_one::<String>("year") {
135        Some(year) => match year.parse::<i32>() {
136            Ok(year) => year,
137            _ => return Err("🚨 Please, specify right value for year".into()),
138        },
139        None => dt.year(),
140    };
141
142    println!(
143        "🔍 Searching timetable for date {:02}/{:02}/{}",
144        day, month, year
145    );
146
147    Ok(format!("{:02}/{:02}/{}", day, month, year))
148}
149
150#[cfg(test)]
151mod tests {
152    use super::{init_cli, init_results_table};
153
154    #[test]
155    fn test_init_results_table_is_empty() {
156        let results_table = init_results_table();
157        assert!(results_table.is_empty());
158    }
159
160    #[test]
161    fn test_init_cli_with_defaults() {
162        let args = init_cli();
163        assert_eq!(args.ids().len(), 1);
164        assert_eq!(
165            args.ids().map(|id| id.as_str()).collect::<Vec<_>>(),
166            ["interactive"]
167        );
168    }
169}