/ src / main.rs
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  }