main.rs
1 use clap::{Args, Parser, Subcommand}; 2 use strum::VariantArray as _; 3 4 use omnical::*; 5 6 #[derive(Parser, Debug)] 7 #[command(version, about, long_about = None)] 8 struct Cli { 9 #[command(flatten)] 10 args: PrintArgs, 11 #[command(subcommand)] 12 command: Option<Commands>, 13 } 14 15 #[derive(Subcommand, Debug)] 16 enum Commands { 17 /// Print a calendar. 18 Print(PrintArgs), 19 /// List the days of a calendar in details. 20 List(ListArgs), 21 // TODO: Convert a date from one calendar to another. 22 /// Query the information of a date. 23 Query(QueryArgs), 24 } 25 26 #[derive(Args, Debug)] 27 struct RangeArgs { 28 /// The year. 29 year: Option<i32>, 30 /// The month. 31 month: Option<u8>, 32 } 33 34 #[derive(Args, Debug)] 35 struct PrintArgs { 36 /// The range of the calendar to print. 37 #[command(flatten)] 38 range: RangeArgs, 39 } 40 41 #[derive(Args, Debug)] 42 struct OptionArgs { 43 /// Display the date in Chinese calendar. 44 #[arg(short, long)] 45 chinese: bool, 46 /// Display the weekday. 47 #[arg(short, long)] 48 weekday: bool, 49 /// Display the lunar phase. 50 #[arg(short, long)] 51 lunar_phase: bool, 52 /// Display the lunar phase emoji. 53 #[arg(short = 'e', long)] 54 lunar_phase_emoji: bool, 55 /// Display the solar term if applicable. 56 #[arg(short, long)] 57 solar_term: bool, 58 } 59 60 #[derive(Args, Debug)] 61 struct ListArgs { 62 /// The range of the calendar to list. 63 #[command(flatten)] 64 range: RangeArgs, 65 /// List options. 66 #[command(flatten)] 67 option: OptionArgs, 68 } 69 70 #[derive(Args, Debug)] 71 struct QueryArgs { 72 /// The date to query. 73 date: Option<String>, 74 /// Query options. 75 #[command(flatten)] 76 option: OptionArgs, 77 } 78 79 fn parse_range(args: &RangeArgs) -> (i32, Option<u8>) { 80 match args { 81 RangeArgs { 82 year: Some(y), 83 month: Some(m), 84 } => (*y, Some(*m)), 85 RangeArgs { 86 year: Some(y), 87 month: None, 88 } => (*y, None), 89 RangeArgs { 90 year: None, 91 month: None, 92 } => { 93 let today = Date::from_unix_time_with_tz(unix_time_now(), BEIJING_TIMEZONE); 94 let today = GregorianDay::from(today); 95 (today.the_year().ord(), Some(today.the_month().ord())) 96 } 97 _ => unreachable!(), 98 } 99 } 100 101 fn print_calendar(args: &PrintArgs) { 102 let (y, m) = parse_range(&args.range); 103 if let Some(m) = m { 104 let month = GregorianCalendar::from_ym(y, m).unwrap(); 105 println!("{:^28}", format!("{:-}", month)); 106 print_month(month); 107 } else { 108 let year = GregorianCalendar::from_y(y).unwrap(); 109 println!("{:^28}", format!("Year {}", year)); 110 print_year(year); 111 } 112 } 113 114 fn print_year(year: GregorianYear) { 115 for month in year.months() { 116 println!("{:^28}", month.name()); 117 print_month(month); 118 } 119 } 120 121 fn print_month(month: GregorianMonth) { 122 let today: GregorianDay = 123 Date::from_unix_time_with_tz(unix_time_now(), BEIJING_TIMEZONE).into(); 124 for weekday in Weekday::VARIANTS { 125 print!(" {:3}", weekday); 126 } 127 println!(); 128 let days = month.days(); 129 for _ in 0..month.first_day().weekday() as u8 { 130 print!(" "); 131 } 132 for day in days { 133 if day == today { 134 print!("[{:>2}]", day.ord()); 135 } else { 136 print!(" {:>2} ", day.ord()); 137 } 138 if day.weekday() == Weekday::last() { 139 println!(); 140 } 141 } 142 if month.last_day().weekday() != Weekday::last() { 143 println!(); 144 } 145 } 146 147 fn list_month(month: GregorianMonth, options: &OptionArgs, chinese_day: &mut Option<ChineseDay>) { 148 for day in month.days() { 149 let date: Date = day.into(); 150 print!("{:#}", day); 151 if options.chinese { 152 if chinese_day.is_none() { 153 *chinese_day = Some(ChineseDay::from(date)); 154 } else { 155 *chinese_day = Some(chinese_day.unwrap().succ()); 156 } 157 print!(" {}", chinese_day.unwrap()); 158 } 159 if options.weekday { 160 print!(" {:#}", day.weekday()); 161 } 162 if options.lunar_phase { 163 print!(" {}", date.lunar_phase(BEIJING_TIMEZONE).chinese()); 164 } 165 if options.lunar_phase_emoji { 166 print!(" {}", date.lunar_phase(BEIJING_TIMEZONE).emoji()); 167 } 168 if options.solar_term 169 && let Some(st) = date.solar_term(BEIJING_TIMEZONE) 170 { 171 print!(" {}", st.chinese()); 172 } 173 if options.lunar_phase_emoji { 174 print!(" {}", date.lunar_phase(BEIJING_TIMEZONE).emoji()); 175 } 176 if options.solar_term 177 && let Some(st) = date.solar_term(BEIJING_TIMEZONE) 178 { 179 print!(" {}", st.chinese()); 180 } 181 if options.lunar_phase_emoji { 182 print!(" {}", date.lunar_phase(BEIJING_TIMEZONE).emoji()); 183 } 184 if options.solar_term 185 && let Some(st) = date.solar_term(BEIJING_TIMEZONE) 186 { 187 print!(" {}", st.chinese()); 188 } 189 println!(); 190 } 191 } 192 193 fn list_year(year: GregorianYear, options: &OptionArgs, chinese_day: &mut Option<ChineseDay>) { 194 for month in year.months() { 195 list_month(month, options, chinese_day); 196 } 197 } 198 199 fn list_dates(args: &ListArgs) { 200 let (y, m) = parse_range(&args.range); 201 if let Some(m) = m { 202 let month = GregorianCalendar::from_ym(y, m).unwrap(); 203 list_month(month, &args.option, &mut None); 204 } else { 205 let year = GregorianCalendar::from_y(y).unwrap(); 206 list_year(year, &args.option, &mut None); 207 } 208 } 209 210 fn query_date(args: &QueryArgs) { 211 // TODO: Use parse function when it's available. 212 let date = if let Some(date) = &args.date { 213 let y: i32 = date[0..4].parse().unwrap(); 214 let m: u8 = date[4..6].parse().unwrap(); 215 let d: u8 = date[6..8].parse().unwrap(); 216 GregorianCalendar::from_ymd(y, m, d).unwrap().into() 217 } else { 218 Date::from_unix_time_with_tz(unix_time_now(), BEIJING_TIMEZONE) 219 }; 220 if args.option.chinese { 221 println!("{}", ChineseDay::from(date)); 222 } 223 if args.option.weekday { 224 println!("{}", date.weekday()); 225 } 226 if args.option.lunar_phase { 227 println!("{}", date.lunar_phase(BEIJING_TIMEZONE).chinese()); 228 } 229 if args.option.lunar_phase_emoji { 230 println!("{}", date.lunar_phase(BEIJING_TIMEZONE).emoji()); 231 } 232 if args.option.solar_term 233 && let Some(st) = date.solar_term(BEIJING_TIMEZONE) 234 { 235 println!("{}", st.chinese()); 236 } 237 if !args.option.chinese 238 && !args.option.weekday 239 && !args.option.lunar_phase 240 && !args.option.lunar_phase_emoji 241 && !args.option.solar_term 242 { 243 println!("{}", ChineseDay::from(date)); 244 } 245 } 246 247 fn main() { 248 let cli = Cli::parse(); 249 250 match &cli.command { 251 Some(Commands::Print(args)) => print_calendar(args), 252 Some(Commands::List(args)) => list_dates(args), 253 Some(Commands::Query(args)) => query_date(args), 254 None => print_calendar(&cli.args), 255 } 256 } 257 258 #[test] 259 fn verify_cli() { 260 use clap::CommandFactory; 261 Cli::command().debug_assert(); 262 }