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::Input;
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
186
fn main() {
35
186
    text::init_crate_colored();
36

            
37
186
    let opts = match cli::options::parse() {
38
180
        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
180
    let logger = Logger::new(opts.color);
52
180
    let mut output_all = String::new();
53

            
54
348
    for input_file in &opts.input_files {
55
183
        let input_file = Input::new(input_file);
56

            
57
        // Get content of the input
58
183
        let content = match input_file.read_to_string() {
59
180
            Ok(c) => c,
60
3
            Err(e) => {
61
3
                logger.error(&format!(
62
3
                    "Input file {} can not be read - {e}",
63
3
                    &input_file.to_string()
64
3
                ));
65
3
                process::exit(EXIT_INVALID_INPUT);
66
            }
67
        };
68

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

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

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

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

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

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