diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4d6293f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,191 @@ +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"), + } + } +} + +// Commandline parsing +#[derive(Parser, Debug)] +#[command(name = "unibar")] +#[command(version = "1.0")] +#[command(about = "Get string of info for a status bar")] +#[command(about, long_about = "A tool that returns variety of components in a string + suitable to use in a status bar")] +struct CommandlineArgs { + /// Name of the weather station + #[arg(short = 's', long, default_value = "khio")] + weather_station: String, + + /// Use Metric units in weather data. Imperial units otherwise + #[arg(short = 'm', long)] + weather_metric: 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, +} + +impl Unibar { + // -------------------- + fn run(&self) { + if self.opts.debug_json { + self.debug_msg("Debugging ..."); + } + self.check_options(); + let mut bar_modules = Vec::new(); + + bar_modules.push(self.clone().get_weather()); + bar_modules.push(self.clone().get_music()); + + 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(); + } + 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); + } + + // -------------------- + fn check_options(&self) -> bool { + let all_good = true; + + // If there are option checks to be added, make all_good a + // mutable var and update its status + + 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); + } +} + +// +// Entry point +// +fn main() { + let cmd_args = CommandlineArgs::parse(); + let app = Unibar { + opts: AppOptions { + units: if cmd_args.weather_metric { TemperatureUnits::Metric } + else { TemperatureUnits::Imperial }, + weather_station: cmd_args.weather_station, + debug_json: cmd_args.debug_json + }, + }; + + app.run(); +} +// References: +// https://reintech.io/blog/working-with-json-in-rust