1use anyhow::{Result};
2use chrono::offset::TimeZone;
3use chrono::{LocalResult, NaiveDateTime};
4use chrono_tz::Tz;
5
6pub mod bodyfile;
7pub mod error;
8pub mod filter;
9mod output;
10pub use error::*;
12mod stream;
13
14pub use crate::bodyfile::*;
15use crate::stream::*;
16use clap::clap_derive::ValueEnum;
17pub use filter::*;
18use output::*;
19mod cli;
20pub use cli::Cli;
21
22#[derive(ValueEnum, Clone)]
23pub enum InputFormat {
24 BODYFILE,
25
26 #[cfg(feature = "elastic")]
27 JSON,
28}
29
30#[derive(ValueEnum, Clone)]
31pub enum OutputFormat {
32 CSV,
33 TXT,
34 JSON,
35
36 #[cfg(feature = "elastic")]
37 ELASTIC,
38}
39
40pub struct Mactime2Application {
42 format: OutputFormat,
43 bodyfile: Option<String>,
44 src_zone: Tz,
45 dst_zone: Tz,
46 strict_mode: bool,
47}
48
49impl Mactime2Application {
50
51 fn create_sorter(&self, decoder: &mut BodyfileDecoder) -> Box<dyn Sorter<Result<(), MactimeError>>> {
52 let options = RunOptions {
53 strict_mode: self.strict_mode,
54 src_zone: self.src_zone,
55 };
56
57 if matches!(self.format, OutputFormat::JSON) {
58 Box::new(JsonSorter::with_receiver(decoder.get_receiver(), options))
59 } else {
60 let mut sorter = BodyfileSorter::default().with_receiver(decoder.get_receiver(), options);
61
62 sorter = sorter.with_output(match self.format {
63 OutputFormat::CSV => Box::new(CsvOutput::new(self.src_zone, self.dst_zone)),
64 OutputFormat::TXT => Box::new(TxtOutput::new(self.src_zone, self.dst_zone)),
65 _ => panic!("invalid execution path"),
66 });
67 Box::new(sorter)
68 }
69 }
70
71 pub fn run(&self) -> Result<()> {
72 let options = RunOptions {
73 strict_mode: self.strict_mode,
74 src_zone: self.src_zone,
75 };
76
77 let mut reader = <BodyfileReader as StreamReader<String, ()>>::from(&self.bodyfile)?;
78 let mut decoder = BodyfileDecoder::with_receiver(reader.get_receiver(), options);
79 let mut sorter = self.create_sorter(&mut decoder);
80 sorter.run();
81
82 let _ = reader.join();
83 let _ = decoder.join();
84 sorter.join().unwrap()?;
85 Ok(())
86 }
87
88 pub fn format_date(unix_ts: i64, src_zone: &Tz, dst_zone: &Tz) -> String {
89 if unix_ts >= 0 {
90 let src_timestamp =
91 match src_zone.from_local_datetime(&NaiveDateTime::from_timestamp_opt(unix_ts, 0).unwrap()) {
92 LocalResult::None => {
93 return "INVALID DATETIME".to_owned();
94 }
95 LocalResult::Single(t) => t,
96 LocalResult::Ambiguous(t1, _t2) => t1,
97 };
98 let dst_timestamp = src_timestamp.with_timezone(dst_zone);
99 dst_timestamp.to_rfc3339()
100 } else {
101 "0000-00-00T00:00:00+00:00".to_owned()
102 }
103 }
104}
105
106impl From<Cli> for Mactime2Application {
107 fn from(cli: Cli) -> Self {
108 let format = match cli.output_format {
109 Some(f) => f,
110 None => {
111 if cli.csv_format {
112 OutputFormat::CSV
113 } else if cli.json_format {
114 OutputFormat::JSON
115 } else {
116 OutputFormat::TXT
117 }
118 }
119 };
120
121 Self {
122 format,
123 bodyfile: Some(cli.input_file),
124 src_zone: cli
125 .src_zone
126 .map(|tz| tz.parse().unwrap())
127 .unwrap_or(Tz::UTC),
128 dst_zone: cli
129 .dst_zone
130 .map(|tz| tz.parse().unwrap())
131 .unwrap_or(Tz::UTC),
132 strict_mode: cli.strict_mode,
133 }
134 }
135}
136
137impl Default for Mactime2Application {
138 fn default() -> Self {
139 Self {
140 format: OutputFormat::CSV,
141 bodyfile: None,
142 src_zone: Tz::UTC,
143 dst_zone: Tz::UTC,
144 strict_mode: false,
145 }
146 }
147}