diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/arts.json | 5 | ||||
| -rw-r--r-- | src/configmanager.rs | 38 | ||||
| -rw-r--r-- | src/main.rs | 203 | ||||
| -rw-r--r-- | src/parser.rs | 220 | ||||
| -rw-r--r-- | src/shared.rs | 43 |
5 files changed, 309 insertions, 200 deletions
diff --git a/src/arts.json b/src/arts.json new file mode 100644 index 0000000..27fdbe5 --- /dev/null +++ b/src/arts.json @@ -0,0 +1,5 @@ +arts { + weather: { + cold = "" + }; +} diff --git a/src/configmanager.rs b/src/configmanager.rs deleted file mode 100644 index c0047a5..0000000 --- a/src/configmanager.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fs; -use serde::Deserialize; -use dirs::home_dir; -use std::path::PathBuf; - -#[derive(Debug, Deserialize)] -pub struct Config { - pub lat: String, - pub lon: String, - pub exclude: String, - pub appid: String, - pub lang: String, - pub units: String, - pub cache: bool, - pub rain: String, - pub sunny: String, - pub snowy: String, -} - -pub fn handle_config(_config: &Config) -> Result<(), Box<dyn std::error::Error>> { - Ok(()) -} - -pub fn gen_standard_conf() { - // TODO: Implement -} - -impl Config { - pub fn load() -> Result<Self, Box<dyn std::error::Error>> { - let mut path = home_dir().ok_or("Home directory not found")?; - path.push(".config/WeatherFetch/Config.toml"); - - let config_str = fs::read_to_string(path)?; - let config: Config = toml::from_str(&config_str)?; - Ok(config) - } -} - diff --git a/src/main.rs b/src/main.rs index 9f64ca4..057588b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,51 +1,182 @@ -extern crate image; +/* + _______ ________ __________ + ___ |__________________ _______________ __ \_______ __ ___ ___ \ + __ /| |_ ___/ ___/ __ `/_ __ \ _ \_ / / / _ \_ | / / __ / _ \ | + _ ___ | / / /__ / /_/ /_ / / / __/ /_/ // __/_ |/ / _ / , _/ / + /_/ |_/_/ \___/ \__,_/ /_/ /_/\___//_____/ \___/_____/ |_/_/|_|_/ +MIT License -use clap::{Arg, Command}; -use termimage::{Options}; +Copyright (c) 2023-2025 ArcaneDev +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +use std::fs::{self, File}; +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; +use termimage::Options; -mod configmanager; mod parser; +mod shared; -use crate::configmanager::{Config, handle_config}; +use parser::{get_config, parse_weather, generate_config, Config}; +use shared::WeatherData; -fn main() -> Result<(), Box<dyn std::error::Error>> { - let matches = Command::new("WeatherFetch") - .version("0.1") - .author("Borisov Alexey <arcanetmodl@gmail.com>") - .about("Weather fetch with image and ASCII art support") - .arg(Arg::new("image").short('i').long("image").value_name("PATH")) - .arg(Arg::new("exclude").short('e').long("exclude").value_name("TYPE") - .value_parser(["current", "minutely", "hourly", "daily", "alerts"])) - .arg(Arg::new("help").short('h').long("help")) - .arg(Arg::new("lat").short('t').long("lat").value_name("LATITUDE")) - .arg(Arg::new("lon").short('n').long("lon").value_name("LONGITUDE")) - .get_matches(); - - if matches.contains_id("help") { - println!("Usage: ..."); - return Ok(()); - } - - if let Some(img_path) = matches.get_one::<String>("image") { - let _img = Image::from_path(img_path)?; - } +#[derive(Parser)] +#[command(name = "wfetch")] +#[command(about = "Weather fetch tool")] +struct Cli { + #[command(subcommand)] + command: Option<Commands>, +} - let config = Config::load()?; - handle_config(&config)?; +#[derive(Subcommand)] +enum Commands { + Config, + Fetch, + Clean, + Today, + Tomorrow, + RebuildCache, + CheckCfg +} +fn process_config() -> Result<(), Box<dyn std::error::Error>> { + let _cfg: Config = get_config()?; + println!("Config is valid"); + Ok(()) +} - let opts = Options::parse(); - /* - if !opts { - opts = configmanager::Config::load(); - } +fn parse_cached() -> Result<WeatherData, Box<dyn std::error::Error>> { + let home = std::env::var("HOME")?; + let cache_path = format!("{}/.cache/WeatherFetch/weather.json", home); + + if !PathBuf::from(&cache_path).exists() { + return Err("Cache file not found".into()); + } + + let cache_data = fs::read_to_string(&cache_path)?; + let weather_data: WeatherData = serde_json::from_str(&cache_data)?; + Ok(weather_data) +} - let format = ops::guess_format(&opts.image)?; - let img = ops::load_image(&opts.image, format)?; +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")?; + let cache_path = format!("{}/.cache/WeatherFetch/weather.json", home); + if PathBuf::from(&cache_path).exists() { + fs::remove_file(&cache_path)?; + println!("Cache cleaned successfully"); + } else { + println!("Cache file not found"); + } + Ok(()) +} +fn rebuild_cache() -> Result<(), Box<dyn std::error::Error>> { + let rt = tokio::runtime::Runtime::new()?; + let weather_data = rt.block_on(parse_weather())?; + + let home = std::env::var("HOME")?; + let cache_dir = format!("{}/.cache/WeatherFetch", home); + fs::create_dir_all(&cache_dir)?; + + let cache_path = format!("{}/weather.json", cache_dir); + let json_data = serde_json::to_string_pretty(&weather_data)?; + fs::write(&cache_path, json_data)?; + + println!("Cache rebuilt successfully"); Ok(()) } + +fn main() -> Result<(), Box<dyn std::error::Error>> { + let cli = Cli::parse(); + + match cli.command { + Some(Commands::Config) => { + println!("Config checker command"); + let home = std::env::var("HOME")?; + let config_path = format!("{}/.config/WeatherFetch/Config.toml", home); + if File::open(&config_path).is_err() { + generate_config()?; + println!("Config generated successfully"); + } else { + process_config()?; + println!("Config already exists and is valid."); + } + Ok(()) + }, + Some(Commands::Fetch) => { + println!("Fetch weather-data command"); + let rt = tokio::runtime::Runtime::new()?; + let weather_data = rt.block_on(parse_weather())?; + let home = std::env::var("HOME")?; + println!("Weather data fetched to: {}", format!("{}/.cache/WeatherFetch", home)); + Ok(()) + }, + Some(Commands::Clean) => { + println!("Clean cache command"); + clean_cache()?; + Ok(()) + }, + Some(Commands::Today) => { + println!("Today weather command"); + Ok(()) + }, + Some(Commands::Tomorrow) => { + println!("Tomorrow weather command"); + Ok(()) + }, + Some(Commands::RebuildCache) => { + println!("Rebuild cache command"); + rebuild_cache()?; + Ok(()) + }, + Some(Commands::CheckCfg) => { + println!("Validating cfg..."); + let home = std::env::var("HOME")?; + let config_path = format!("{}/.config/WeatherFetch/Config.toml", home); + if File::open(&config_path).is_ok() { + process_config()?; + } else { + println!("Config file not found, try `wfetch config`, its will generate default cfg."); + } + Ok(()) + } + None => { + print_help() + }, + } +}
\ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs index 5a670b1..f4fd2da 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,146 +1,114 @@ -use reqwest::{Error, Client, get}; -use chrono::{DateTime, Utc, prelude::*}; -use serde::{Serialize, Deserialize}; - -use crate::configmanager; - -//API answer struct`s -#[derive(Debug, Serialize, Deserialize)] -pub struct WeatherData { - pub lat: f64, - pub lon: f64, - pub timezone: String, - pub timezone_offset: i32, - pub current: Current, - pub minutely: Vec<Minutely>, - pub hourly: Vec<Hourly>, - pub daily: Vec<Daily>, - pub alerts: Vec<Alert>, -} +use reqwest::Client; +use std::fs::{self, File}; +use std::io::Read; +use toml; +use serde::Deserialize; +use serde_json; -#[derive(Debug, Serialize, Deserialize)] -pub struct Current { - pub dt: u64, - pub sunrise: u64, - pub sunset: u64, - pub temp: f32, - pub feels_like: f32, - pub pressure: u32, - pub humidity: u32, - pub dew_point: f32, - pub uvi: f32, - pub clouds: u32, - pub visibility: u32, - pub wind_speed: f32, - pub wind_deg: u32, - pub wind_gust: f32, - pub weather: Vec<Weather>, -} +// use crate::configmanager::Config; +use crate::shared::*; -#[derive(Debug, Serialize, Deserialize)] -pub struct Weather { - pub id: u32, - pub main: String, - pub description: String, - pub icon: String, -} +pub type BoxedError = Box<dyn std::error::Error + Send + Sync>; -#[derive(Debug, Serialize, Deserialize)] -pub struct Minutely { - pub dt: u64, - pub precipitation: f32, +fn get_config_path() -> Result<String, Box<dyn std::error::Error>> { + let home = std::env::var("HOME")?; + Ok(format!("{}/.config/WeatherFetch/Config.toml", home)) } -#[derive(Debug, Serialize, Deserialize)] -pub struct Hourly { - pub dt: u64, - pub temp: f32, - pub feels_like: f32, - pub pressure: u32, - pub humidity: u32, - pub dew_point: f32, - pub uvi: f32, - pub clouds: u32, - pub visibility: u32, - pub wind_speed: f32, - pub wind_deg: u32, - pub wind_gust: f32, - pub weather: Vec<Weather>, - pub pop: f32, -} +/* +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>) +}); +*/ -#[derive(Debug, Serialize, Deserialize)] -pub struct Daily { - pub dt: u64, - pub sunrise: u64, - pub sunset: u64, - pub moonrise: u64, - pub moonset: u64, - pub moon_phase: f32, - pub summary: String, - pub temp: Temp, - pub feels_like: FeelsLike, - pub pressure: u32, - pub humidity: u32, - pub dew_point: f32, - pub wind_speed: f32, - pub wind_deg: u32, - pub wind_gust: f32, - pub weather: Vec<Weather>, - pub clouds: u32, - pub pop: f32, - pub rain: Option<f32>, - pub uvi: f32, -} +pub fn get_location(coords_args: bool) -> Result<(), BoxedError> { + let config = get_config().unwrap(); -#[derive(Debug, Serialize, Deserialize)] -pub struct Temp { - pub day: f32, - pub min: f32, - pub max: f32, - pub night: f32, - pub eve: f32, - pub morn: f32, + 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(()) + } } -#[derive(Debug, Serialize, Deserialize)] -pub struct FeelsLike { - pub day: f32, - pub night: f32, - pub eve: f32, - pub morn: f32, +pub async fn parse_weather() -> Result<WeatherData, Box<dyn std::error::Error>> { + let config = get_config()?; + let client = Client::new(); + let response = client.get("https://api.open-meteo.com/v1/forecast") + .query(&[ + ("latitude", config.lat.to_string()), + ("longitude", config.lon.to_string()), + ("current", "temperature_2m,wind_speed_10m".to_string()), + ("hourly", "temperature_2m,relative_humidity_2m,wind_speed_10m".to_string()), + ]) + .send() + .await?; + + let response_text = response.text().await?; + + let weather_data: WeatherData = serde_json::from_str(&response_text)?; + + let home = std::env::var("HOME")?; + let cache_dir = format!("{}/.cache/WeatherFetch", home); + fs::create_dir_all(&cache_dir)?; + + let cache_path = format!("{}/weather.json", cache_dir); + let json_data = serde_json::to_string_pretty(&weather_data)?; + fs::write(&cache_path, json_data)?; + + Ok(weather_data) } -#[derive(Debug, Serialize, Deserialize)] -pub struct Alert { - pub sender_name: String, - pub event: String, - pub start: u64, - pub end: u64, - pub description: String, - pub tags: Vec<String>, + +#[derive(Debug, Deserialize)] +pub struct Config { + lat: f64, + lon: f64, + exclude: String, + appid: String, + units: String, + lang: String, } +pub fn get_config() -> Result<Config, Box<dyn std::error::Error>> { + let config_path = get_config_path()?; + + if File::open(&config_path).is_err() { + generate_config()?; + } + + let mut file = File::open(&config_path)?; + let mut content = String::new(); + file.read_to_string(&mut content)?; + let config: Config = toml::from_str(&content)?; + 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); + + let _ = fs::create_dir(cache_path); -pub fn get_location(coords_args: bool) -> Result<(), String>{ - //Get the lat and lon for API call - let conf: Option<configmanager::load()>; - if conf.lat.is_empty() || conf.lon.is_empty() && !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(<int>)`, `lon(<int>)`."); - println!("HINT: To get more info check https://openweathermap.org/api/one-call-3"); + Ok(()) +} - Err("No coordinates in config or args.".into()) - } else { - Ok(()) +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 path = std::path::Path::new(&config_path); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; } -} - - -pub async fn parse_weather(_config: &Config) -> Result<(), reqwest::Error> { + fs::write(path, config)?; + println!("Config file generated at: {}", config_path); Ok(()) -} +}
\ No newline at end of file diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..75a4d1c --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,43 @@ +use serde::{Serialize, Deserialize}; +//API answer struct`s + +#[derive(Debug, Serialize, Deserialize)] +pub struct WeatherData { + pub latitude: f64, + pub longitude: f64, + pub generationtime_ms: Option<f64>, + pub utc_offset_seconds: Option<i32>, + pub timezone: Option<String>, + pub timezone_abbreviation: Option<String>, + pub elevation: Option<f64>, + pub current_units: Option<CurrentUnits>, + pub current: Current, + pub hourly_units: Option<HourlyUnits>, + pub hourly: Hourly, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct Current { + pub time: String, + pub temperature_2m: f32, + pub wind_speed_10m: f32, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct CurrentUnits { + pub time: Option<String>, + pub temperature_2m: Option<String>, + pub wind_speed_10m: Option<String>, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct Hourly { + pub time: Vec<String>, + pub temperature_2m: Vec<f32>, + pub relative_humidity_2m: Vec<u32>, + pub wind_speed_10m: Vec<f32>, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct HourlyUnits { + pub time: Option<String>, + pub temperature_2m: Option<String>, + pub relative_humidity_2m: Option<String>, + pub wind_speed_10m: Option<String>, +} |
