Skip to main content

jobberdb/
export.rs

1//! CSV Export
2
3use super::prelude::*;
4use itertools::Itertools;
5
6/// Available export columns.
7#[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    // Create column from String.
24    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/// List of columns to export.
64#[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
88/// Export selected columns of a `JobList`.
89/// * `w`: Where the output goes
90/// * `jobs`: Jobs to export
91/// * `columns`: Column names
92pub 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}