1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2024 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use std::io::{self, Write};
19
use std::path::PathBuf;
20
use std::process;
21

            
22
use hurl_core::input::InputKind;
23
use hurl_core::{parser, text};
24
use hurlfmt::cli::options::{InputFormat, OptionsError, OutputFormat};
25
use hurlfmt::cli::Logger;
26
use hurlfmt::{cli, curl, format, linter};
27

            
28
const EXIT_OK: i32 = 0;
29
const EXIT_ERROR: i32 = 1;
30
const EXIT_INVALID_INPUT: i32 = 2;
31
const EXIT_LINT_ISSUE: i32 = 3;
32

            
33
/// Executes `hurlfmt` entry point.
34
195
fn main() {
35
195
    text::init_crate_colored();
36

            
37
195
    let opts = match cli::options::parse() {
38
189
        Ok(v) => v,
39
6
        Err(e) => match e {
40
6
            OptionsError::Info(message) => {
41
6
                print!("{message}");
42
6
                process::exit(EXIT_OK);
43
            }
44
            OptionsError::Error(message) => {
45
                eprintln!("{message}");
46
                process::exit(EXIT_ERROR);
47
            }
48
        },
49
    };
50

            
51
189
    let logger = Logger::new(opts.color);
52
189
    let mut output_all = String::new();
53

            
54
366
    for input_file in &opts.input_files {
55
        // Get content of the input
56
192
        let content = match input_file.read_to_string() {
57
189
            Ok(c) => c,
58
3
            Err(e) => {
59
3
                logger.error(&format!(
60
3
                    "Input file {} can not be read - {e}",
61
3
                    &input_file.to_string()
62
3
                ));
63
3
                process::exit(EXIT_INVALID_INPUT);
64
            }
65
        };
66

            
67
        // Parse input curl or Hurl file
68
189
        let input = match opts.input_format {
69
186
            InputFormat::Hurl => content.to_string(),
70
3
            InputFormat::Curl => match curl::parse(&content) {
71
3
                Ok(s) => s,
72
                Err(e) => {
73
                    logger.error(&e.to_string());
74
                    process::exit(EXIT_INVALID_INPUT);
75
                }
76
            },
77
        };
78

            
79
        // Parse Hurl content
80
189
        let hurl_file = match parser::parse_hurl_file(&input) {
81
183
            Ok(h) => h,
82
6
            Err(e) => {
83
6
                logger.error_parsing(&content, input_file, &e);
84
6
                process::exit(EXIT_INVALID_INPUT);
85
            }
86
        };
87

            
88
        // Only checks
89
183
        if opts.check {
90
6
            let lints = linter::check_hurl_file(&hurl_file);
91
9
            for e in lints.iter() {
92
9
                logger.warn_lint(&content, input_file, e);
93
            }
94
6
            if lints.is_empty() {
95
                process::exit(EXIT_OK);
96
            } else {
97
6
                process::exit(EXIT_LINT_ISSUE);
98
            }
99
        }
100

            
101
        // Output files
102
177
        let output = match opts.output_format {
103
            OutputFormat::Hurl => {
104
72
                let hurl_file = linter::lint_hurl_file(&hurl_file);
105
72
                format::format_text(&hurl_file, opts.color)
106
            }
107
51
            OutputFormat::Json => format::format_json(&hurl_file),
108
54
            OutputFormat::Html => hurl_core::format::format_html(&hurl_file, opts.standalone),
109
        };
110
177
        if opts.in_place {
111
3
            let InputKind::File(path) = input_file.kind() else {
112
                unreachable!("--in-place and standard input have been filtered in args parsing")
113
            };
114
3
            write_output(&output, Some(path.clone()));
115
174
        } else {
116
174
            output_all.push_str(&output);
117
        }
118
    }
119
174
    if !opts.in_place {
120
171
        write_output(&output_all, opts.output_file);
121
    }
122
}
123

            
124
174
fn write_output(content: &str, filename: Option<PathBuf>) {
125
174
    let content = if !content.ends_with('\n') {
126
105
        format!("{content}\n")
127
    } else {
128
69
        content.to_string()
129
    };
130
174
    let bytes = content.into_bytes();
131
174
    match filename {
132
171
        None => {
133
171
            let stdout = io::stdout();
134
171
            let mut handle = stdout.lock();
135
171

            
136
171
            handle
137
171
                .write_all(bytes.as_slice())
138
171
                .expect("writing bytes to console");
139
        }
140
3
        Some(path_buf) => {
141
3
            let mut file = match std::fs::File::create(&path_buf) {
142
                Err(why) => {
143
                    eprintln!("Issue writing to {}: {:?}", path_buf.display(), why);
144
                    process::exit(1);
145
                }
146
3
                Ok(file) => file,
147
3
            };
148
3
            file.write_all(bytes.as_slice())
149
3
                .expect("writing bytes to file");
150
        }
151
    }
152
}