1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use super::Error;
use std::fmt;

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::CellSyntaxError {
                filename,
                parse_error,
                position,
            } => {
                writeln!(
                    f,
                    "Syntax error in cell {position} of {}",
                    filename.display()
                )?;
                writeln!(f, "{parse_error}")
            }

            Self::CodeSyntaxError {
                parse_error,
                filename,
            } => {
                writeln!(f, "Syntax error in code section of {}", filename.display())?;
                writeln!(f, "{parse_error}")
            }

            Self::EvalError {
                eval_error,
                position,
                filename,
            } => {
                if let Some(position) = position {
                    writeln!(
                        f,
                        "Error evaluating formula in cell {position} ({}, {}) of {}",
                        position.column.x,
                        position.row.y,
                        filename.display()
                    )?;
                } else {
                    writeln!(f, "Error evaluating formula in {}", filename.display())?;
                }
                writeln!(f, "{eval_error}")
            }

            Self::GoogleSetupError(message) => {
                // TODO: make this a little smarter, if gcloud isn't in path complain about that?
                // TODO: can we run gcloud auth login automatically?
                // TODO: gcloud ... --update-adc is deprecated.  this gets us close:
                // $ gcloud auth application-default login --scopes openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/appengine.admin,https://www.googleapis.com/auth/sqlservice.login,https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/accounts.reauth,https://www.googleapis.com/auth/drive
                writeln!(
                    f,
                    "Unable to access the specified spreadsheet on Google Sheets.

Are you sure that you have the `gcloud` CLI tools installed and properly configured? To 
authenticate using your Google user account try running:

$ gcloud init
$ gcloud auth login --enable-gdrive-access --update-adc

If you would like to specify service credentials or the path to your own user credentials, call 
csv++ with `GOOGLE_APPLICATION_CREDENTIALS` or the `--google-account-credentials` flag.

{message}"
                )
            }

            Self::InitError(message) | Self::ModuleLoadError(message) => {
                writeln!(f, "{message}")
            }

            Self::ModuleLoadErrors(errors) => {
                for (m, e) in errors {
                    writeln!(f, "Error loading module {m}")?;
                    writeln!(f, "{e}")?;
                }
                Ok(())
            }

            Self::SourceCodeError { filename, message } => {
                writeln!(f, "Error reading source {}", filename.display())?;
                writeln!(f, "{message}")
            }

            Self::TargetWriteError { output, message } => {
                writeln!(f, "Error writing to {output}")?;
                writeln!(f, "{message}")
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::{EvalError, Output, ParseError};
    use super::*;
    use std::path;

    fn build_parse_error() -> ParseError {
        ParseError {
            bad_input: "bar".to_string(),
            message: "it should be foo".to_string(),
            line_number: 3,
            line_offset: 5,
            possible_values: None,
            highlighted_lines: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()],
        }
    }

    #[test]
    fn display_cell_syntax_error() {
        let message = Error::CellSyntaxError {
            filename: path::PathBuf::from("a_file.csvpp"),
            position: a1_notation::Address::new(1, 5),
            parse_error: Box::new(build_parse_error()),
        };

        assert_eq!(
            message.to_string(),
            "Syntax error in cell B6 of a_file.csvpp
On line 4 it should be foo but saw bar

foo
bar
baz

",
        );
    }

    #[test]
    fn display_code_syntax_error() {
        let message = Error::CodeSyntaxError {
            filename: path::PathBuf::from("a_file.csvpp"),
            parse_error: Box::new(build_parse_error()),
        };

        assert_eq!(
            message.to_string(),
            "Syntax error in code section of a_file.csvpp
On line 4 it should be foo but saw bar

foo
bar
baz

"
        );
    }

    #[test]
    fn display_eval_error() {
        let message = Error::EvalError {
            filename: path::PathBuf::from("a_file.csvpp"),
            eval_error: Box::new(EvalError {
                message: "Error".to_string(),
                bad_input: "foo".to_string(),
            }),
            position: Some(a1_notation::Address::new(2, 2)),
        };

        assert_eq!(
            message.to_string(),
            "Error evaluating formula in cell C3 (2, 2) of a_file.csvpp
Error: foo
"
        );
    }

    #[test]
    fn display_init_error() {
        let message = Error::InitError("foo".to_string());

        assert_eq!("foo\n", message.to_string());
    }

    #[test]
    fn display_source_code_error() {
        let message = Error::SourceCodeError {
            filename: path::PathBuf::from("a_file.csvpp"),
            message: "foo".to_string(),
        };

        assert_eq!(
            message.to_string(),
            "Error reading source a_file.csvpp\nfoo\n",
        );
    }

    #[test]
    fn display_target_write_error() {
        let message = Error::TargetWriteError {
            output: Output::Excel(path::PathBuf::from("foo.xlsx")),
            message: "foo".to_string(),
        };

        assert_eq!(message.to_string(), "Error writing to foo.xlsx\nfoo\n",);
    }
}