Implementation of modules in separate structs

This commit is contained in:
Mahesh Asolkar 2025-05-04 22:13:41 -07:00
parent a20e1d8336
commit e1f2518239
Signed by: asolkar
GPG Key ID: 371CA8164433BDCC
6 changed files with 186 additions and 115 deletions

12
Cargo.toml Normal file
View File

@ -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"] }

View File

@ -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(" "));
}
}

View File

@ -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);
}
}

8
src/bar_modules/mod.rs Normal file
View File

@ -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;

30
src/common/mod.rs Normal file
View File

@ -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,
}

View File

@ -1,27 +1,10 @@
use std::str; use std::str;
use std::fmt;
use std::process::{Stdio, Command};
use curl::easy::{Easy, List};
use serde_json::Value;
use clap::Parser; use clap::Parser;
// // Common utilities/types
// Enums mod common;
// // Bar Modules
#[derive(Debug,PartialEq,Eq,Copy,Clone)] mod bar_modules;
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"),
}
}
}
// Commandline parsing // Commandline parsing
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -39,28 +22,22 @@ struct CommandlineArgs {
#[arg(short = 'm', long)] #[arg(short = 'm', long)]
weather_metric: bool, weather_metric: bool,
/// Show music progess
#[arg(short = 'p', long)]
music_progress: bool,
/// Show JSON data returned by query /// Show JSON data returned by query
#[arg(short = 'D', long)] #[arg(short = 'D', long)]
debug_json: bool, debug_json: bool,
} }
//
// Application options
//
#[derive(Debug,Clone)]
struct AppOptions {
units: TemperatureUnits,
weather_station: String,
debug_json: bool,
}
// //
// Application (Unibar) // Application (Unibar)
// //
#[derive(Clone)] #[derive(Clone)]
struct Unibar { struct Unibar {
// acts :UnibarActions // acts :UnibarActions
opts :AppOptions, opts: common::AppOptions,
} }
impl Unibar { impl Unibar {
@ -70,72 +47,17 @@ impl Unibar {
self.debug_msg("Debugging ..."); self.debug_msg("Debugging ...");
} }
self.check_options(); self.check_options();
let mut bar_modules = Vec::new(); let mut parts = Vec::new();
bar_modules.push(self.clone().get_weather()); let bar_modules_enabled: Vec<Box<dyn bar_modules::BarModuleActions>> = vec! [
bar_modules.push(self.clone().get_music()); 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(" | ")); for md in bar_modules_enabled {
parts.push(md.get());
} }
println!("{}", parts.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();
}
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);
} }
// -------------------- // --------------------
@ -148,23 +70,6 @@ impl Unibar {
return all_good; 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) { fn debug_msg(&self, msg: &str) {
println!("[Debug ] -----> {}", msg); println!("[Debug ] -----> {}", msg);
@ -177,10 +82,11 @@ impl Unibar {
fn main() { fn main() {
let cmd_args = CommandlineArgs::parse(); let cmd_args = CommandlineArgs::parse();
let app = Unibar { let app = Unibar {
opts: AppOptions { opts: common::AppOptions {
units: if cmd_args.weather_metric { TemperatureUnits::Metric } weather_units: if cmd_args.weather_metric { common::TemperatureUnits::Metric }
else { TemperatureUnits::Imperial }, else { common::TemperatureUnits::Imperial },
weather_station: cmd_args.weather_station, weather_station: cmd_args.weather_station,
music_progress: cmd_args.music_progress,
debug_json: cmd_args.debug_json debug_json: cmd_args.debug_json
}, },
}; };