summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/arts.json5
-rw-r--r--src/configmanager.rs38
-rw-r--r--src/main.rs203
-rw-r--r--src/parser.rs220
-rw-r--r--src/shared.rs43
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>,
+}