1use super::prelude::*;
4use itertools::Itertools;
5
6#[derive(Debug, Clone)]
8pub enum Column {
9 Pos,
10 Start,
11 End,
12 Duration,
13 Hours,
14 Message,
15 Tags,
16 Pay,
17 Rate,
18 MaxHours,
19 Resolution,
20}
21
22impl Column {
23 pub fn from(column: &str) -> Result<Self, Error> {
25 Ok(match column.to_lowercase().as_str() {
26 "#" | "pos" => Column::Pos,
27 "s" | "start" => Column::Start,
28 "e" | "end" => Column::End,
29 "d" | "duration" => Column::Duration,
30 "h" | "hours" => Column::Hours,
31 "m" | "message" => Column::Message,
32 "t" | "tags" => Column::Tags,
33 "p" | "pay" => Column::Pay,
34 "rate" => Column::Rate,
35 "resolution" => Column::Resolution,
36 _ => return Err(Error::UnknownColumn(column.to_string())),
37 })
38 }
39}
40
41impl std::fmt::Display for Column {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(
44 f,
45 "{}",
46 match self {
47 Column::Pos => "Position",
48 Column::Start => "Start",
49 Column::End => "End",
50 Column::Duration => "Duration",
51 Column::Hours => "Hours",
52 Column::Message => "Message",
53 Column::Tags => "Tags",
54 Column::Pay => "Pay",
55 Column::Rate => "Rate",
56 Column::MaxHours => "Max.Hours",
57 Column::Resolution => "Resolution",
58 }
59 )
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct Columns(Vec<Column>);
66
67impl Columns {
68 pub fn iter(&self) -> core::slice::Iter<'_, Column> {
69 self.0.iter()
70 }
71}
72impl std::fmt::Display for Columns {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}", self.iter().join(","))
75 }
76}
77impl From<String> for Columns {
78 fn from(value: String) -> Self {
79 Columns(
80 value
81 .split(',')
82 .map(|c| Column::from(c).expect("unknown column"))
83 .collect(),
84 )
85 }
86}
87
88pub fn export_csv<W: std::io::Write>(
93 w: &mut W,
94 jobs: &JobList,
95 columns: &Columns,
96 context: &Context,
97) -> Result<(), Error> {
98 let title = columns
99 .0
100 .iter()
101 .map(|c| format!(r#""{}""#, c))
102 .collect::<Vec<String>>()
103 .join(",");
104 writeln!(w, "{}", title)?;
105 for (pos, job) in jobs.iter().sorted_by(|l, r| l.1.cmp(r.1)) {
106 for (c, column) in columns.iter().enumerate() {
107 if c > 0 {
108 write!(w, ",")?;
109 }
110 let properties = jobs.configuration.get_checked(&job.tags)?;
111 match column {
112 Column::Pos => write!(w, "{}", pos + 1)?,
113 Column::Start => write!(w, r#""{}""#, job.start.format("%m/%d/%Y %H:%M"))?,
114 Column::End => write!(
115 w,
116 r#""{}""#,
117 if let Some(end) = job.end {
118 end.format("%m/%d/%Y %H:%M")
119 } else {
120 context.time().format("%m/%d/%Y %H:%M")
121 }
122 )?,
123 Column::Duration => write!(
124 w,
125 r#""{}""#,
126 if let Some(end) = job.end {
127 &end - &job.start
128 } else {
129 &context.time() - &job.start
130 }
131 )?,
132 Column::Message => write!(
133 w,
134 r#""{}""#,
135 str::replace(
136 job.message.as_ref().unwrap_or(&"".to_string()),
137 "\"",
138 "\"\""
139 )
140 )?,
141 Column::Hours => write!(w, "{}", job.hours(properties))?,
142 Column::Tags => write!(w, r#""{}""#, job.tags.0.join(","))?,
143 Column::Pay => {
144 if let Some(rate) = properties.rate {
145 write!(w, "{}", job.hours(properties) * rate)?;
146 }
147 }
148 Column::Rate => {
149 if let Some(rate) = jobs.get_configuration(&job.tags).rate {
150 write!(w, "{rate}")?;
151 }
152 }
153 Column::MaxHours => {
154 if let Some(max_hours) = jobs.get_configuration(&job.tags).max_hours {
155 write!(w, "{max_hours}",)?;
156 }
157 }
158 Column::Resolution => {
159 if let Some(resolution) = jobs.get_configuration(&job.tags).resolution {
160 write!(w, "{resolution}",)?;
161 }
162 }
163 }
164 }
165 writeln!(w)?;
166 }
167 Ok(())
168}