summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNamilskyy <alive6863@gmail.com>2025-11-18 21:24:04 +0300
committerNamilskyy <alive6863@gmail.com>2025-11-18 22:03:57 +0300
commit0e3574d26990e93b5a66af2426cb2102b2ba0a5f (patch)
treef28a5b0e8520e62e3d5bfcc8a2651e73fbf4cd89
parentb417227555dded641b03e9583e4b3f893b5d2e83 (diff)
Fixed all warnings, added minimal design.
-rw-r--r--Cargo.lock34
-rw-r--r--Cargo.toml28
-rw-r--r--src/arts.json5
-rw-r--r--src/arts.yaml61
-rw-r--r--src/main.rs111
-rw-r--r--src/parser.rs126
-rw-r--r--src/shared.rs11
7 files changed, 276 insertions, 100 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7ca77db..aeb9b07 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1054,6 +1054,16 @@ dependencies = [
]
[[package]]
+name = "libyml"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980"
+dependencies = [
+ "anyhow",
+ "version_check",
+]
+
+[[package]]
name = "linux-raw-sys"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1719,6 +1729,21 @@ dependencies = [
]
[[package]]
+name = "serde_yml"
+version = "0.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "libyml",
+ "memchr",
+ "ryu",
+ "serde",
+ "version_check",
+]
+
+[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2160,6 +2185,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2272,7 +2303,7 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "wfetch"
-version = "0.0.1"
+version = "0.0.2"
dependencies = [
"chrono",
"clap 4.5.51",
@@ -2280,6 +2311,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
+ "serde_yml",
"termimage",
"tokio",
"toml 0.7.8",
diff --git a/Cargo.toml b/Cargo.toml
index 16c3faa..32dab6b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,15 +1,19 @@
[package]
-name = "wfetch"
-version = "0.0.1"
-edition = "2021"
+name = "wfetch"
+version = "0.0.2"
+edition = "2021"
+categories = ["command-line-utilities"]
+authors = ["namilsk <namilsk@namilsk.tech>"]
+license = "MIT"
[dependencies]
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-toml = "0.7"
-clap = { version = "4.4", features = ["derive"] }
-reqwest = { version = "0.11", features = ["json"] }
-tokio = { version = "1.0", features = ["full"] }
-chrono = "0.4"
-termimage = "1.2.2"
-clap_derive = "4.5.49"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+toml = "0.7"
+clap = { version = "4.4", features = ["derive"] }
+reqwest = { version = "0.11", features = ["json"] }
+tokio = { version = "1.0", features = ["full"] }
+chrono = "0.4"
+termimage = "1.2.2"
+clap_derive = "4.5.49"
+serde_yml = "0.0.12"
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>,