From e1f25182395b19ff0aa4a69cf0ae2ad1519a9c37 Mon Sep 17 00:00:00 2001 From: Mahesh Asolkar Date: Sun, 4 May 2025 22:13:41 -0700 Subject: [PATCH] Implementation of modules in separate structs --- Cargo.toml | 12 +++ src/bar_modules/bar_module_music.rs | 44 +++++++++ src/bar_modules/bar_module_weather.rs | 71 ++++++++++++++ src/bar_modules/mod.rs | 8 ++ src/common/mod.rs | 30 ++++++ src/main.rs | 136 ++++---------------------- 6 files changed, 186 insertions(+), 115 deletions(-) create mode 100644 Cargo.toml create mode 100644 src/bar_modules/bar_module_music.rs create mode 100644 src/bar_modules/bar_module_weather.rs create mode 100644 src/bar_modules/mod.rs create mode 100644 src/common/mod.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e534775 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "unibar" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1.0.196" +serde_json = "1.0.113" +curl = "0.4.46" +clap = { version = "4.5.1", features = ["derive"] } diff --git a/src/bar_modules/bar_module_music.rs b/src/bar_modules/bar_module_music.rs new file mode 100644 index 0000000..5f2a560 --- /dev/null +++ b/src/bar_modules/bar_module_music.rs @@ -0,0 +1,44 @@ +use std::process::{Stdio, Command}; + +use crate::common; +use crate::bar_modules; + +#[derive(Clone)] +pub struct UnibarModuleMusic { + pub opts: common::AppOptions, +} + +impl bar_modules::BarModuleActions for UnibarModuleMusic { + + // -------------------- + fn get(&self) -> String { + let mut parts = Vec::new(); + + // MPD format options here: + // https://manpages.ubuntu.com/manpages/plucky/man1/mpc.1.html + let current_output = Command::new("mpc") + .arg("--format") + .arg("[%artist% - ][%title%]") + .arg("current") + .stdout(Stdio::piped()) + .output() + .unwrap(); + let mut current_stdout = String::from_utf8(current_output.stdout).unwrap(); + current_stdout.pop(); + parts.push(format!("{}", current_stdout)); + + if self.opts.music_progress { + let progress_output = Command::new("mpc") + .arg("status") + .arg("%percenttime% %totaltime%") + .stdout(Stdio::piped()) + .output() + .unwrap(); + let mut progress_stdout = String::from_utf8(progress_output.stdout).unwrap(); + progress_stdout.pop(); + parts.push(format!("[{}]", progress_stdout)); + } + + return format!("{}", parts.join(" ")); + } +} diff --git a/src/bar_modules/bar_module_weather.rs b/src/bar_modules/bar_module_weather.rs new file mode 100644 index 0000000..6566ee1 --- /dev/null +++ b/src/bar_modules/bar_module_weather.rs @@ -0,0 +1,71 @@ +use std::str; +use curl::easy::{Easy, List}; +use serde_json::Value; + +use crate::common; +use crate::bar_modules; + +#[derive(Clone)] +pub struct UnibarModuleWeather { + pub opts: common::AppOptions, +} + +impl UnibarModuleWeather { + + // -------------------- + fn get_current_temperature(&self, v: Value) -> f32 { + let deg_c :f32 = v["features"][0]["properties"]["temperature"]["value"] + .to_string().parse().unwrap(); + + match self.opts.weather_units { + common::TemperatureUnits::Metric => return deg_c, + common::TemperatureUnits::Imperial => return (deg_c * 9.0 / 5.0) + 32.0, + } + } + + // -------------------- + fn get_temperature_unit(&self) -> String{ + return if self.opts.weather_units == common::TemperatureUnits::Metric { "°C".to_string() } + else { "°F".to_string() }; + } + +} + +impl bar_modules::BarModuleActions for UnibarModuleWeather { + + // -------------------- + fn get(&self) -> String { + // Print a web page onto stdout + let mut curl = Easy::new(); + let mut url_string = Vec::new(); + let mut curl_ret = Vec::new(); + + url_string.push("https://api.weather.gov/stations/".to_owned()); + url_string.push(self.opts.weather_station.to_owned()); + url_string.push("/observations?limit=1".to_owned()); + + curl.url(url_string.concat().as_str()).unwrap(); + { + let mut list = List::new(); + list.append("User-Agent: Bar Weather (mahesh@heshapps.com)").unwrap(); + curl.http_headers(list).unwrap(); + + let mut transfer = curl.transfer(); + + transfer.write_function(|data| { + curl_ret.extend_from_slice(data); + Ok(data.len()) + }).unwrap(); + transfer.perform().unwrap(); + } + if self.opts.debug_json { + println!("-----> curl_data - [{}]", std::str::from_utf8(&curl_ret).unwrap()); + } + + let v: Value = serde_json::from_str(str::from_utf8(&curl_ret).unwrap()).unwrap(); + let temperature_value :f32 = self.get_current_temperature(v); + let temperature_unit :String = self.get_temperature_unit(); + + return format!("{:.2}{}", temperature_value, temperature_unit); + } +} diff --git a/src/bar_modules/mod.rs b/src/bar_modules/mod.rs new file mode 100644 index 0000000..c6744ee --- /dev/null +++ b/src/bar_modules/mod.rs @@ -0,0 +1,8 @@ +// -------------------- +// All Bar modules must implement the actions +pub trait BarModuleActions { + fn get(&self) -> String; +} + +pub mod bar_module_weather; +pub mod bar_module_music; diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..8a6003d --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,30 @@ +use std::fmt; + +// -------------------- +// Enums +// -------------------- +#[derive(Debug,PartialEq,Eq,Copy,Clone)] +pub enum TemperatureUnits { + Metric, + Imperial, +} + +impl fmt::Display for TemperatureUnits { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TemperatureUnits::Metric => write!(f, "Metric"), + TemperatureUnits::Imperial => write!(f, "Imperial"), + } + } +} + +// -------------------- +// Application options +// -------------------- +#[derive(Debug,Clone)] +pub struct AppOptions { + pub weather_units: TemperatureUnits, + pub weather_station: String, + pub music_progress: bool, + pub debug_json: bool, +} diff --git a/src/main.rs b/src/main.rs index 4d6293f..9d2ea08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,10 @@ use std::str; -use std::fmt; -use std::process::{Stdio, Command}; -use curl::easy::{Easy, List}; -use serde_json::Value; use clap::Parser; -// -// Enums -// -#[derive(Debug,PartialEq,Eq,Copy,Clone)] -enum TemperatureUnits { - Metric, - Imperial, -} - -impl fmt::Display for TemperatureUnits { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - TemperatureUnits::Metric => write!(f, "Metric"), - TemperatureUnits::Imperial => write!(f, "Imperial"), - } - } -} +// Common utilities/types +mod common; +// Bar Modules +mod bar_modules; // Commandline parsing #[derive(Parser, Debug)] @@ -39,28 +22,22 @@ struct CommandlineArgs { #[arg(short = 'm', long)] weather_metric: bool, + /// Show music progess + #[arg(short = 'p', long)] + music_progress: bool, + /// Show JSON data returned by query #[arg(short = 'D', long)] debug_json: bool, } -// -// Application options -// -#[derive(Debug,Clone)] -struct AppOptions { - units: TemperatureUnits, - weather_station: String, - debug_json: bool, -} - // // Application (Unibar) // #[derive(Clone)] struct Unibar { // acts :UnibarActions - opts :AppOptions, + opts: common::AppOptions, } impl Unibar { @@ -70,72 +47,17 @@ impl Unibar { self.debug_msg("Debugging ..."); } self.check_options(); - let mut bar_modules = Vec::new(); + let mut parts = Vec::new(); - bar_modules.push(self.clone().get_weather()); - bar_modules.push(self.clone().get_music()); + let bar_modules_enabled: Vec> = vec! [ + Box::new(bar_modules::bar_module_weather::UnibarModuleWeather { opts: self.opts.clone() }), + Box::new(bar_modules::bar_module_music::UnibarModuleMusic { opts: self.opts.clone() }), + ]; - println!("{}", bar_modules.join(" | ")); - } - - // -------------------- - fn get_weather(self) -> String { - // Print a web page onto stdout - let mut curl = Easy::new(); - let mut url_string = Vec::new(); - let mut curl_ret = Vec::new(); - - url_string.push("https://api.weather.gov/stations/".to_owned()); - url_string.push(self.opts.weather_station.to_owned()); - url_string.push("/observations?limit=1".to_owned()); - - curl.url(url_string.concat().as_str()).unwrap(); - { - let mut list = List::new(); - list.append("User-Agent: Bar Weather (mahesh@heshapps.com)").unwrap(); - curl.http_headers(list).unwrap(); - - let mut transfer = curl.transfer(); - - transfer.write_function(|data| { - curl_ret.extend_from_slice(data); - Ok(data.len()) - }).unwrap(); - transfer.perform().unwrap(); + for md in bar_modules_enabled { + parts.push(md.get()); } - if self.opts.debug_json { - println!("-----> curl_data - [{}]", std::str::from_utf8(&curl_ret).unwrap()); - } - - let v: Value = serde_json::from_str(str::from_utf8(&curl_ret).unwrap()).unwrap(); - let temperature_value :f32 = self.get_current_temperature(v); - let temperature_unit :String = self.get_temperature_unit(); - - return format!("{:.2}{}", temperature_value, temperature_unit); - } - - // -------------------- - fn get_music(&self) -> String { - // MPD format options here: - // https://manpages.ubuntu.com/manpages/plucky/man1/mpc.1.html - let current_output = Command::new("mpc") - .arg("--format") - .arg("[%artist% - ][%title%]") - .arg("current") - .stdout(Stdio::piped()) - .output() - .unwrap(); - let mut current_stdout = String::from_utf8(current_output.stdout).unwrap(); - current_stdout.pop(); - let progress_output = Command::new("mpc") - .arg("status") - .arg("%percenttime% %totaltime%") - .stdout(Stdio::piped()) - .output() - .unwrap(); - let mut progress_stdout = String::from_utf8(progress_output.stdout).unwrap(); - progress_stdout.pop(); - return format!("{} [{}]", current_stdout, progress_stdout); + println!("{}", parts.join(" | ")); } // -------------------- @@ -148,23 +70,6 @@ impl Unibar { return all_good; } - // -------------------- - fn get_current_temperature(&self, v: Value) -> f32 { - let deg_c :f32 = v["features"][0]["properties"]["temperature"]["value"] - .to_string().parse().unwrap(); - - match self.opts.units { - TemperatureUnits::Metric => return deg_c, - TemperatureUnits::Imperial => return (deg_c * 9.0 / 5.0) + 32.0, - } - } - - // -------------------- - fn get_temperature_unit(&self) -> String{ - return if self.opts.units == TemperatureUnits::Metric { "°C".to_string() } - else { "°F".to_string() }; - } - // -------------------- fn debug_msg(&self, msg: &str) { println!("[Debug ] -----> {}", msg); @@ -177,10 +82,11 @@ impl Unibar { fn main() { let cmd_args = CommandlineArgs::parse(); let app = Unibar { - opts: AppOptions { - units: if cmd_args.weather_metric { TemperatureUnits::Metric } - else { TemperatureUnits::Imperial }, + opts: common::AppOptions { + weather_units: if cmd_args.weather_metric { common::TemperatureUnits::Metric } + else { common::TemperatureUnits::Imperial }, weather_station: cmd_args.weather_station, + music_progress: cmd_args.music_progress, debug_json: cmd_args.debug_json }, };