diff options
| author | Namilskyy <alive6863@gmail.com> | 2025-11-18 21:24:04 +0300 |
|---|---|---|
| committer | Namilskyy <alive6863@gmail.com> | 2025-11-18 22:03:57 +0300 |
| commit | 0e3574d26990e93b5a66af2426cb2102b2ba0a5f (patch) | |
| tree | f28a5b0e8520e62e3d5bfcc8a2651e73fbf4cd89 /src | |
| parent | b417227555dded641b03e9583e4b3f893b5d2e83 (diff) | |
Fixed all warnings, added minimal design.
Diffstat (limited to 'src')
| -rw-r--r-- | src/arts.json | 5 | ||||
| -rw-r--r-- | src/arts.yaml | 61 | ||||
| -rw-r--r-- | src/main.rs | 111 | ||||
| -rw-r--r-- | src/parser.rs | 126 | ||||
| -rw-r--r-- | src/shared.rs | 11 |
5 files changed, 227 insertions, 87 deletions
diff --git a/src/arts.json b/src/arts.json deleted file mode 100644 index 27fdbe5..0000000 --- a/src/arts.json +++ /dev/null @@ -1,5 +0,0 @@ -arts { - weather: { - cold = "" - }; -} diff --git a/src/arts.yaml b/src/arts.yaml new file mode 100644 index 0000000..20159fc --- /dev/null +++ b/src/arts.yaml @@ -0,0 +1,61 @@ +Arts: + sun: | + {0} ' + {0} . ' . + {0} . . : . . + {0} '. ______ .' + {0} ' _.-'` `'-._ ' + {0} .' '. + {0}`'--. / \ .--'` + {0} / \ + {0} ; ; + {0}- -- | | -- - + {0} | _. | + {0} ; /__`A ,_ ; + {0} .-' \ |= |;._.}{__ / '-. + {0} _.-''-|.' # '. ` `.-"{}<._ + {0} / 1938 \ \ x `' + {0} ----/ \_.-'|--X---- + {0} -=_ | | |- X. =_ + {0} - __ |_________|_.-'|_X-X## + {0} jgs `'-._|_|;:;_.-'` '::. `'- + {0} .:;. .:. ::. '::. + snow: | + {0} () + {0} /\ + {0} //\\ + {0} << >> + {0} () \\// () + {0}()._____ /\ \\ /\ _____.() + {0} \.--.\ //\\ //\\ //\\ /.--./ + {0} \\__\\/__\//__\//__\\/__// + {0} '--/\\--//\--//\--/\\--' + {0} \\\\///\\//\\\//// + {0} ()-= >>\\< <\\> >\\<< =-() + {0} ////\\\//\\///\\\\ + {0} .--\\/--\//--\//--\//--. + {0} //""/\\""//\""//\""//\""\\ + {0} /'--'/ \\// \\// \\// \'--'\ + {0} ()`"""` \/ // \/ `""""`() + {0} () //\\ () + {0} << >> + {0} jgs \\// + {0} \/ + {0} () + {0} + rain: | + {0} __I__ + {0} .-'" . "'-. + {0} .' / . ' . \ '. + {0}/_.-..-..-..-..-._\ .---------------------------------. + {0} # _,,_ ( I hear it might rain people today ) + {0} #/` `\ /'---------------------------------' + {0} / / 6 6\ \ + {0} \/\ Y /\/ /\-/\ + {0} #/ `'U` \ /a a \ _ + {0} , ( \ | \ =\ Y =/-~~~~~~-,_____/ ) + {0} |\|\_/# \_/ '^--' ______/ + {0} \/'. \ /'\ \ / + {0} \ /=\ / || |---'\ \ + {0} jgs /____)/____) (_(__| ((__| + diff --git a/src/main.rs b/src/main.rs index 930a429..2d6d1dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,9 @@ mod parser; mod shared; use parser::{get_config, parse_weather, generate_config, Config}; -use shared::WeatherData; +use shared::WeatherData; + +use crate::parser::{determine_weather_type, prepare_art}; #[derive(Parser)] #[command(name = "wfetch")] @@ -59,6 +61,7 @@ enum Commands { CheckCfg } +/// Micro config-validator, easily you can just `wfetch fetch` and see the error fn process_config() -> Result<(), Box<dyn std::error::Error>> { let _cfg: Config = get_config()?; println!("Config is valid"); @@ -78,19 +81,6 @@ fn parse_cached() -> Result<WeatherData, Box<dyn std::error::Error>> { Ok(weather_data) } -fn print_help() -> Result<(), Box<dyn std::error::Error>> { - println!("Help command"); - println!("Usage: wfetch <command>"); - println!("Commands:"); - println!(" config - Check config"); - println!(" fetch - Fetch weather-data"); - println!(" clean - Clean cache"); - println!(" help - Print help"); - println!(" today - Print today weather"); - println!(" tomorrow - Print tomorrow weather"); - println!(" rebuild-cache - Rebuild cache"); - Ok(()) -} fn clean_cache() -> Result<(), Box<dyn std::error::Error>> { let home = std::env::var("HOME")?; @@ -120,6 +110,45 @@ fn rebuild_cache() -> Result<(), Box<dyn std::error::Error>> { Ok(()) } +fn generate_weather_table_content(data: &WeatherData) -> Vec<String> { + let mut lines = Vec::new(); + + lines.push("╔═══════════════════════════════════════╗".to_string()); + lines.push("║ Today`s weather ║".to_string()); + lines.push("╠═══════════════════════════════════════╣".to_string()); + lines.push(format!("║ Time: {} ║", format!("{:>28}", data.current.time))); + lines.push(format!("║ Temp: {}°C ║", format!("{:>22}", data.current.temperature_2m))); + lines.push(format!("║ Wind speed: {} m/s ║", format!("{:>19}", data.current.wind_speed_10m))); + lines.push(format!("║ Type: {} ║", determine_weather_type(data.current.temperature_2m, Some(data.hourly.relative_humidity_2m[0])))); + if let Some(elevation) = data.elevation { + lines.push(format!("║ Height: {} m ║", format!("{:>26}", elevation))); + } + if let Some(timezone) = &data.timezone { + lines.push(format!("║ Time: {} ║", format!("{:>22}", timezone))); + } + lines.push(format!("║ Coords: {:.2}, {:.2}, ║", data.latitude, data.longitude)); + lines.push("╚═══════════════════════════════════════╝".to_string()); + + if !data.hourly.time.is_empty() { + lines.push("┌──────────────────┬──────────────┬──────────────┬──────────────┐".to_string()); + lines.push("│ Time │ Temp │ Humidity │ Wind │".to_string()); + lines.push("├──────────────────┼──────────────┼──────────────┼──────────────┤".to_string()); + + let hours_to_show = data.hourly.time.len().min(24); + for i in 0..hours_to_show { + let time = &data.hourly.time[i]; + let temp = data.hourly.temperature_2m[i]; + let humidity = data.hourly.relative_humidity_2m[i]; + let wind = data.hourly.wind_speed_10m[i]; + + lines.push(format!("│ {:12} │ {:>10}°C │ {:>10}% │ {:>10} m/s │", + time, temp, humidity, wind)); + } + lines.push("└──────────────────┴──────────────┴──────────────┴──────────────┘".to_string()); + } + + lines +} fn main() -> Result<(), Box<dyn std::error::Error>> { let cli = Cli::parse(); @@ -140,7 +169,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { Some(Commands::Fetch) => { println!("Fetch weather-data command"); let rt = tokio::runtime::Runtime::new()?; - let weather_data = rt.block_on(parse_weather())?; + let _weather_data = rt.block_on(parse_weather())?; let home = std::env::var("HOME")?; println!("Weather data fetched to: {}", format!("{}/.cache/WeatherFetch", home)); Ok(()) @@ -151,42 +180,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { Ok(()) }, Some(Commands::Today) => { + // to much vars let data: WeatherData = parse_cached()?; - - println!("╔═══════════════════════════════════════╗"); - println!("║ Today`s weather ║"); - println!("╠═══════════════════════════════════════╣"); - println!("║ Time: {} {}", format!("{:>28}", data.current.time), "║"); - println!("║ Temp: {}°C {}" , format!("{:>22}", data.current.temperature_2m), "║"); - println!("║ Wind speed: {} m/s {}", format!("{:>19}", data.current.wind_speed_10m), "║"); - if let Some(elevation) = data.elevation { - println!("║ Height: {} m {}", format!("{:>26}", elevation), "║"); - } - if let Some(timezone) = &data.timezone { - println!("║ Time: {} {}", format!("{:>22}", timezone), "║"); - } - println!("║ Coords: {:.2}, {:.2}, {}", data.latitude, data.longitude, "║"); - println!("╚═══════════════════════════════════════╝"); - - - if !data.hourly.time.is_empty() { - println!("┌──────────────┬──────────────┬──────────────┬──────────────┐"); - println!("│ Time │ Temp │ Humidity │ Wind │"); - println!("├──────────────┼──────────────┼──────────────┼──────────────┤"); - - let hours_to_show = data.hourly.time.len().min(24); - for i in 0..hours_to_show { - let time = &data.hourly.time[i]; - let temp = data.hourly.temperature_2m[i]; - let humidity = data.hourly.relative_humidity_2m[i]; - let wind = data.hourly.wind_speed_10m[i]; - - println!("│ {:12} │ {:>10}°C │ {:>10}% │ {:>10} m/s │", - time, temp, humidity, wind); - } - println!("└──────────────┴──────────────┴──────────────┴──────────────┘"); + let art_string = prepare_art(&data)?; + let table_lines = generate_weather_table_content(&data); + + let art_lines: Vec<&str> = art_string.lines().collect(); + + let max_art_width = art_lines.iter().map(|line| line.len()).max().unwrap_or(0); + + let max_lines = art_lines.len().max(table_lines.len()); + + for i in 0..max_lines { + let art_part = art_lines.get(i).unwrap_or(&""); + let table_part = table_lines.get(i).map(|s| s.as_str()).unwrap_or(""); + + println!("{:<width$} {}", art_part, table_part, width = max_art_width); } - Ok(()) }, Some(Commands::Tomorrow) => { @@ -210,7 +220,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { Ok(()) } None => { - print_help() + println!("No subcommand specified."); + Ok(()) }, } }
\ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs index f4fd2da..daa9059 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,40 +2,18 @@ use reqwest::Client; use std::fs::{self, File}; use std::io::Read; use toml; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json; +use serde_yml; // use crate::configmanager::Config; use crate::shared::*; -pub type BoxedError = Box<dyn std::error::Error + Send + Sync>; - fn get_config_path() -> Result<String, Box<dyn std::error::Error>> { let home = std::env::var("HOME")?; Ok(format!("{}/.config/WeatherFetch/Config.toml", home)) } -/* -const CONF: Lazy<Result<Config, Box<dyn std::error::Error + Send + Sync>>> = Lazy::new(|| { - Config::load().map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>) -}); -*/ - -pub fn get_location(coords_args: bool) -> Result<(), BoxedError> { - let config = get_config().unwrap(); - - if (config.lat == 0.0 || config.lon == 0.0) && !coords_args { - - println!("No coordinates in configuration file or conf not founded."); - println!("HINT: Try create ~/.config/WeatherFetch/Config.toml"); - println!("HINT: And add `lat(<float>)`, `lon(<float>)`."); - println!("HINT: To get more info check https://open-meteo.com/en/docs"); - - Err("Invalid coordinates in config".into()) - } else { - Ok(()) - } -} pub async fn parse_weather() -> Result<WeatherData, Box<dyn std::error::Error>> { let config = get_config()?; @@ -66,14 +44,12 @@ pub async fn parse_weather() -> Result<WeatherData, Box<dyn std::error::Error>> } +// TODO: Add exclude processing #[derive(Debug, Deserialize)] pub struct Config { lat: f64, lon: f64, - exclude: String, - appid: String, - units: String, - lang: String, + exclude: String } pub fn get_config() -> Result<Config, Box<dyn std::error::Error>> { @@ -92,6 +68,7 @@ pub fn get_config() -> Result<Config, Box<dyn std::error::Error>> { Ok(config) } +/* pub fn generate_cachedir() -> Result<(), Box<dyn std::error::Error>> { let home = std::env::var("HOME")?; let cache_path = format!("{}/.cache/WeatherFetch/", home); @@ -100,10 +77,17 @@ pub fn generate_cachedir() -> Result<(), Box<dyn std::error::Error>> { Ok(()) } - +*/ + +/// Just writing example config to ~/.config/WeatherFetch/Config.toml +/// let _ = generate_config() +/// Result: ~/.config/WeatherFetch/Config.toml: +/// lat = 55.75 +/// lon = 37.62 +/// exclude = "" pub fn generate_config() -> Result<(), Box<dyn std::error::Error>> { let config_path = get_config_path()?; - let config = "lat = 55.75\nlon = 37.62\nexclude = \"\"\nappid = \"\"\nunits = \"metric\"\nlang = \"ru\""; + let config = "lat = 55.75\nlon = 37.62\nexclude = \"\""; let path = std::path::Path::new(&config_path); if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; @@ -111,4 +95,84 @@ pub fn generate_config() -> Result<(), Box<dyn std::error::Error>> { fs::write(path, config)?; println!("Config file generated at: {}", config_path); Ok(()) -}
\ No newline at end of file +} + +/// For usage like type in `let` +#[derive(Serialize, Deserialize, Debug)] +struct Arts { + #[serde(rename = "Arts")] + arts: ArtsData, +} + +/// Strings with arts from arts.yaml +#[derive(Serialize, Deserialize, Debug)] +struct ArtsData { + sun: String, + snow: String, + rain: String, +} + +/// Weather type checker, crutch, it hurts me to look at it +pub fn determine_weather_type(temp: f32, humidity: Option<u32>) -> &'static str { + if temp < 0.0 { + return "snow"; + } + + if let Some(hum) = humidity { + if hum > 70 && temp < 25.0 { + return "rain"; + } + } + + "sun" +} + +/// Arts loader with exception wrappers +fn load_arts() -> Result<ArtsData, Box<dyn std::error::Error>> { + let home = std::env::var("HOME")?; + let arts_path = format!("{}/.config/WeatherFetch/arts.yaml", home); + + let content = match fs::read_to_string(&arts_path) { + Ok(c) => c, + Err(_) => { + // if arts.yaml not found + return Ok(ArtsData { + sun: "☀️ - ts emoji means program cant found ~/.config/WeatherFetch/arts.yaml".to_string(), + snow: "❄️ - ts emoji means program cant found ~/.config/WeatherFetch/arts.yaml".to_string(), + rain: "🌧️ - ts emoji means program cant found ~/.config/WeatherFetch/arts.yaml".to_string(), + }); + } + }; + + let arts: Arts = serde_yml::from_str(&content)?; + Ok(arts.arts) +} + +fn process_placeholders(art: &str) -> String { + art.replace("{0}", "") +} + +/// Choosing and retuns art (String) +/// Usage: +/// let data: WeatherData = parse_cached()?; +/// prepare_art(&data); +pub fn prepare_art(weather_data: &WeatherData) -> Result<String, Box<dyn std::error::Error>> { + let arts = load_arts()?; + + let weather_type = determine_weather_type( + weather_data.current.temperature_2m, + weather_data.hourly.relative_humidity_2m.first().copied(), + ); + + let selected_art = match weather_type { + "snow" => &arts.snow, + "rain" => &arts.rain, + "sun" => &arts.sun, + _ => &arts.sun, + }; + + let processed_art = process_placeholders(selected_art); + + Ok(processed_art) +} + diff --git a/src/shared.rs b/src/shared.rs index 75a4d1c..43589aa 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,6 +1,7 @@ use serde::{Serialize, Deserialize}; -//API answer struct`s +// API answer struct`s +/// Main struct with fetched weather data #[derive(Debug, Serialize, Deserialize)] pub struct WeatherData { pub latitude: f64, @@ -15,18 +16,24 @@ pub struct WeatherData { pub hourly_units: Option<HourlyUnits>, pub hourly: Hourly, } + +/// WeatherData.current = ts struct #[derive(Debug, Serialize, Deserialize)] pub struct Current { pub time: String, pub temperature_2m: f32, pub wind_speed_10m: f32, } + +/// WeatherData.current_units = ts struct #[derive(Debug, Serialize, Deserialize)] pub struct CurrentUnits { pub time: Option<String>, pub temperature_2m: Option<String>, pub wind_speed_10m: Option<String>, } + +/// WeatherData.hourly = ts struct #[derive(Debug, Serialize, Deserialize)] pub struct Hourly { pub time: Vec<String>, @@ -34,6 +41,8 @@ pub struct Hourly { pub relative_humidity_2m: Vec<u32>, pub wind_speed_10m: Vec<f32>, } + +/// WeatherData.hourly_units = ts struct #[derive(Debug, Serialize, Deserialize)] pub struct HourlyUnits { pub time: Option<String>, |
