Compare commits

..

4 Commits

Author SHA1 Message Date
b3a322f694 Proper use of enum 2025-05-03 14:35:58 -07:00
928a0547d3 MPD and weather module 2025-04-20 15:22:40 -07:00
1c0b1e7d8b Added --debug-json option 2024-09-17 22:01:47 -07:00
914e3f2a0c Clean up 2024-08-14 20:11:37 -07:00
2 changed files with 93 additions and 171 deletions

View File

@@ -1,5 +1,6 @@
use std::str; use std::str;
use std::fmt; use std::fmt;
use std::process::{Stdio, Command};
use curl::easy::{Easy, List}; use curl::easy::{Easy, List};
use serde_json::Value; use serde_json::Value;
use clap::Parser; use clap::Parser;
@@ -24,7 +25,12 @@ impl fmt::Display for TemperatureUnits {
// Commandline parsing // Commandline parsing
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(name = "bar_weather")]
#[command(version = "1.0")]
#[command(about = "Gets weather info for the bar")]
#[command(about, long_about = "Get weather information for the station indicated
by the --station argument. Imperial units by default.
Use --metric to use metric units")]
struct CommandlineArgs { struct CommandlineArgs {
/// Name of the weather station /// Name of the weather station
#[arg(short, long, default_value = "khio")] #[arg(short, long, default_value = "khio")]
@@ -33,8 +39,11 @@ struct CommandlineArgs {
/// Use Metric units. Imperial units otherwise /// Use Metric units. Imperial units otherwise
#[arg(short, long)] #[arg(short, long)]
metric: bool, metric: bool,
}
/// Show JSON data returned by query
#[arg(short, long)]
debug_json: bool,
}
// //
// Application options // Application options
@@ -43,78 +52,124 @@ struct CommandlineArgs {
struct AppOptions { struct AppOptions {
units: TemperatureUnits, units: TemperatureUnits,
station: String, station: String,
debug_json: bool,
} }
// //
// Application (BarWeather) // Application (BarWeather)
// //
#[derive(Clone)]
struct BarWeather { struct BarWeather {
// acts :BarWeatherActions // acts :BarWeatherActions
opts :AppOptions, opts :AppOptions,
} }
impl BarWeather { impl BarWeather {
fn run(self) { // --------------------
fn run(&self) {
if self.opts.debug_json {
self.debug_msg("Debugging ...");
}
self.check_options(); self.check_options();
self.get_weather(); 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) { // --------------------
fn get_weather(self) -> String {
// Print a web page onto stdout // Print a web page onto stdout
let mut curl = Easy::new(); let mut curl = Easy::new();
let url_string_1 :String = "https://api.weather.gov/stations/".to_owned(); let mut url_string = Vec::new();
let url_string_2 :&str = self.opts.station.as_str(); let mut curl_ret = Vec::new();
let url_string_3 :String = "/observations?limit=1".to_owned();
let url_string = url_string_1 + url_string_2 + &url_string_3; url_string.push("https://api.weather.gov/stations/".to_owned());
url_string.push(self.opts.station.to_owned());
url_string.push("/observations?limit=1".to_owned());
curl.url(url_string.as_str()).unwrap(); 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 list = List::new(); let mut transfer = curl.transfer();
list.append("User-Agent: Bar Weather (mahesh@heshapps.com)").unwrap();
curl.http_headers(list).unwrap();
curl.write_function(move |data| {
// To debug returned JSON
// use std::io::{stdout, Write};
// stdout().write_all(data).unwrap();
let v: Value = serde_json::from_str(str::from_utf8(data).unwrap()).unwrap();
let temperature_value :f32 = self.get_current_temperature(v);
let temperature_unit :String = self.get_temperature_unit();
println!("{:.2}{}", temperature_value, temperature_unit);
Ok(data.len())
transfer.write_function(|data| {
curl_ret.extend_from_slice(data);
Ok(data.len())
}).unwrap();
transfer.perform().unwrap();
} }
).unwrap(); if self.opts.debug_json {
curl.perform().unwrap(); 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 check_options(&self) -> bool { // --------------------
let mut all_good = true; 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);
}
if ! ((self.opts.units == TemperatureUnits::Metric) // --------------------
|| (self.opts.units == TemperatureUnits::Imperial)) { fn check_options(&self) -> bool {
all_good &= false; 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; return all_good;
} }
// --------------------
fn get_current_temperature(&self, v: Value) -> f32 { fn get_current_temperature(&self, v: Value) -> f32 {
let deg_c :f32 = v["features"][0]["properties"]["temperature"]["value"] let deg_c :f32 = v["features"][0]["properties"]["temperature"]["value"]
.to_string().parse().unwrap(); .to_string().parse().unwrap();
return if self.opts.units == TemperatureUnits::Metric { deg_c } match self.opts.units {
else { (deg_c * 9.0 / 5.0) + 32.0}; TemperatureUnits::Metric => return deg_c,
TemperatureUnits::Imperial => return (deg_c * 9.0 / 5.0) + 32.0,
}
} }
// --------------------
fn get_temperature_unit(&self) -> String{ fn get_temperature_unit(&self) -> String{
return if self.opts.units == TemperatureUnits::Metric { "°C".to_string() } return if self.opts.units == TemperatureUnits::Metric { "°C".to_string() }
else { "°F".to_string() }; else { "°F".to_string() };
} }
// --------------------
fn debug_msg(&self, msg: &str) {
println!("[Debug ] -----> {}", msg);
}
} }
// //
@@ -127,6 +182,7 @@ fn main() {
units: if cmd_args.metric { TemperatureUnits::Metric } units: if cmd_args.metric { TemperatureUnits::Metric }
else { TemperatureUnits::Imperial }, else { TemperatureUnits::Imperial },
station: cmd_args.station, station: cmd_args.station,
debug_json: cmd_args.debug_json
}, },
}; };

View File

@@ -1,134 +0,0 @@
use std::str;
use std::fmt;
use curl::easy::{Easy, List};
use serde_json::Value;
//
// 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(version, about, long_about = None)]
struct CommandlineArgs {
/// Name of the weather station
#[arg(short, long, default_value = "khio")]
station: String,
}
//
// Application options
//
#[derive(Debug,Copy,Clone)]
struct AppOptions {
units: TemperatureUnits,
station: String,
}
//
// Application (BarWeather) actions
struct BarWeatherActions {
opts :AppOptions,
}
impl BarWeatherActions {
fn check_options(&self) -> bool {
let mut all_good = true;
if ! ((self.opts.units == TemperatureUnits::Metric)
|| (self.opts.units == TemperatureUnits::Imperial)) {
all_good &= false;
}
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();
return if self.opts.units == TemperatureUnits::Metric { deg_c }
else { (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() };
}
}
//
// Application (BarWeather)
//
struct BarWeather {
acts :BarWeatherActions
}
impl BarWeather {
fn run(self) {
self.acts.check_options();
self.get_weather();
}
fn get_weather(self) {
// Print a web page onto stdout
let mut easy = Easy::new();
let app = self;
easy.url("https://api.weather.gov/stations/" + + "/observations?limit=1").unwrap();
let mut list = List::new();
list.append("User-Agent: Bar Weather (mahesh@heshapps.com)").unwrap();
easy.http_headers(list).unwrap();
easy.write_function(move |data| {
// To debug returned JSON
// use std::io::{stdout, Write};
// stdout().write_all(data).unwrap();
let v: Value = serde_json::from_str(str::from_utf8(data).unwrap()).unwrap();
let temperature_value :f32 = app.acts.get_current_temperature(v);
let temperature_unit :String = app.acts.get_temperature_unit();
println!("{}{}", temperature_value, temperature_unit);
println!("{:.2}{}", temperature_value, temperature_unit);
Ok(data.len())
}
).unwrap();
easy.perform().unwrap();
}
}
//
// Entry point
//
fn main() {
let app = BarWeather {
acts: BarWeatherActions {
opts: AppOptions {
units: TemperatureUnits::Imperial,
// units: TemperatureUnits::Metric,
},
},
};
app.run();
}
// References:
// https://reintech.io/blog/working-with-json-in-rust