pogoda_terminal/
lib.rs

1use anyhow::{anyhow, Context, Result};
2
3use std::{
4    env,
5    fs::File,
6    io::{Read, Write},
7};
8
9pub mod api_usage;
10pub mod cmd_line;
11pub mod user_setup;
12
13/// Constants for the information of the program.
14pub mod program_info {
15    /// The name of the program.
16    pub const PROGRAM_NAME: &str = "pogoda-terminal";
17    /// The description of the program.
18    pub const PROGRAM_DESCRIPTION: &str = "Weather for command-line fans!";
19    /// The authors of the program.
20    pub const PROGRAM_AUTHORS: &str = "er2de2";
21    /// URL of the program in crates.io.
22    pub const CRATES_IO_URL: &str = "https://crates.io/crates/pogoda-terminal";
23    /// URL of the program repository in GitHub.
24    pub const REPOSITORY_URL: &str = "https://github.com/er2de2/pogoda-terminal";
25}
26
27/// Constants related to the API and settings.
28mod constants {
29    /// The name of the json file for the API key.
30    pub const API_JSON_NAME: &str = "api";
31
32    /// The name of the json file for settings.
33    pub const SETTINGS_JSON_NAME: &str = "settings";
34
35    /// The URL template for the OpenWeatherMap API.
36    ///
37    /// This template can be used to retrieve weather data by replacing the following placeholders:
38    ///
39    /// - `{lat_value}`: Latitude value of the location.
40    /// - `{lon_value}`: Longitude value of the location.
41    /// - `{api_key}`: Your OpenWeatherMap API key.
42    /// - `{unit}`: The desired measurement unit. (ex. `metric` or `imperial`)
43    ///
44    /// ## Example Usage
45    ///
46    /// ```
47    /// pub const API_URL: &str = "https://api.openweathermap.org/data/2.5/weather?lat={lat_value}&lon={lon_value}&appid={api_key}&units={unit}";
48    ///
49    /// let url = API_URL
50    ///     .replace("{lat_value}", "37.3361663")
51    ///     .replace("{lon_value}", "-121.890591")
52    ///     .replace("{api_key}", "EXAMPLE_KEY")
53    ///     .replace("{unit}", "imperial");
54    /// ```
55    pub const API_URL: &str = "https://api.openweathermap.org/data/2.5/weather?lat={lat_value}&lon={lon_value}&lang=pl&appid={api_key}&units={unit}";
56}
57
58/// Returns the running executable directory.
59pub fn get_executable_directory() -> Result<String> {
60    let executable_path =
61        env::current_exe().context("Couldn't get the executable file directory!")?;
62    let executable_directory = executable_path
63        .parent()
64        .context("Couldn't get the executable directory!")?;
65
66    if let Some(dir_str) = executable_directory.to_str() {
67        return Ok(dir_str.to_string());
68    }
69
70    Err(anyhow!("Unable to get the executable directory."))
71}
72
73/// Formats the given file name with the executable directory.
74pub fn get_json_file(name: &str) -> Result<File> {
75    let executable_dir = get_executable_directory()?;
76
77    let file = match File::open(format!("{}/weather-cli-{}.json", executable_dir, name)) {
78        Ok(f) => f,
79        Err(_) => {
80            let mut new_file =
81                File::create(format!("{}/weather-cli-{}.json", executable_dir, name))
82                    .context("Couldn't create a json file.")?;
83            new_file
84                .write_all("{}".as_bytes())
85                .context("Couldn't create a json file.")?;
86
87            File::open(format!("{}/weather-cli-{}.json", executable_dir, name))
88                .context("Couldn't get the json file.")?
89        }
90    };
91
92    Ok(file)
93}
94
95pub enum ErrorMessageType {
96    SettingRead,
97    ApiResponseRead,
98}
99
100fn get_file_read_error_message(error_type: ErrorMessageType, context: &str) -> String {
101    match error_type {
102        ErrorMessageType::SettingRead => {
103            format!("Failed to read the following file: {}", context)
104        }
105        ErrorMessageType::ApiResponseRead => {
106            format!("The given '{}' JSON input may be invalid.", context)
107        }
108    }
109}
110
111/// Reads the given json file and returns the string.
112pub fn read_json_file<T: serde::de::DeserializeOwned>(json_name: &str) -> Result<T> {
113    use constants::API_JSON_NAME;
114
115    let mut file = get_json_file(json_name)?;
116    let mut json_string = String::new();
117    file.read_to_string(&mut json_string)?;
118
119    let api_key_data: T = serde_json::from_str(&json_string).context(
120        get_file_read_error_message(ErrorMessageType::SettingRead, API_JSON_NAME),
121    )?;
122
123    Ok(api_key_data)
124}
125
126/// Reads the given json file and returns the string.
127pub fn read_json_response<T: serde::de::DeserializeOwned>(
128    response: &str,
129    error_message_type: ErrorMessageType,
130    error_context: &str,
131) -> Result<T> {
132    let response_data: T = serde_json::from_str(response).context(get_file_read_error_message(
133        error_message_type,
134        error_context,
135    ))?;
136
137    Ok(response_data)
138}
139
140/// Returns the emoji for the given icon id.
141pub fn get_emoji(icon_id: &str) -> String {
142    let return_value = match icon_id {
143        "01d" => "☀️",
144        "02d" => "⛅️",
145        "03d" => "☁️",
146        "04d" => "☁️",
147        "09d" => "🌧️",
148        "10d" => "🌦️",
149        "11d" => "⛈️",
150        "13d" => "❄️",
151        "50d" => "🌨️",
152        "01n" => "🌑",
153        "02n" => "🌑☁️",
154        "03n" => "☁️",
155        "04n" => "☁️☁️",
156        "09n" => "🌧️",
157        "10n" => "☔️",
158        "11n" => "⛈️",
159        "13n" => "❄️",
160        _ => "",
161    };
162
163    if !return_value.is_empty() {
164        format!("{} ", return_value)
165    } else {
166        return_value.to_string()
167    }
168}
169
170/// This module is only used for testing.
171#[cfg(test)]
172mod tests;