Compare commits

...

2 Commits

Author SHA1 Message Date
0681a8b8d1 implemented more commands 2024-03-30 00:10:18 +01:00
09e06c57ea moved sunrise into own file 2024-03-29 23:27:24 +01:00
2 changed files with 190 additions and 92 deletions

View File

@@ -1,113 +1,114 @@
use chrono::{Local, Timelike}; use chrono::{Local, Timelike};
use clap::Parser; use clap::{Parser, Subcommand};
use serde::{de, Deserialize, Deserializer}; use std::{error::Error, path::PathBuf};
use std::{ use sunrise::Sunrise;
error::Error,
fs, mod sunrise;
path::{Path, PathBuf},
};
use toml::value::Datetime;
#[derive(Parser)] #[derive(Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
struct Cli { struct Cli {
/// Sunrise file #[command(subcommand)]
file: PathBuf, command: Commands,
} }
#[derive(Deserialize, Debug)] #[derive(Subcommand)]
struct Sunrise { enum Commands {
/// Sorted array of Timetables /// Print the current wallpaper
schedule: Vec<Timetable>, Current {
/// Sunrise file
file: PathBuf,
},
/// Print the time to apply the next wallpaper
NextAt {
/// Sunrise file
file: PathBuf,
},
/// Print the time til the next wallpaper
NextIn {
/// Sunrise file
file: PathBuf,
/// Display time in seconds
#[arg(short,long)]
seconds: bool,
},
} }
#[derive(Deserialize, Debug)] fn sec_to_readable(sec: u32) -> String {
struct Timetable { let hours = sec / 3600;
/// Time of day in seconds from 0:00 let minutes = (sec % 3600) / 60;
#[serde(deserialize_with = "deserialize_seconds")] let seconds = sec % 60;
time: u32,
image: PathBuf,
}
impl Sunrise { format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
fn load_from_file(path: &Path) -> Result<Sunrise, Box<dyn Error>> {
let file = fs::read_to_string(path)?;
let mut data: Sunrise = toml::from_str(&file)?;
data.schedule.sort_by_key(|e| e.time);
Ok(data)
}
fn closest_passed_time(&self, current: u32) -> Option<&Timetable> {
let idx = match self
.schedule
.binary_search_by_key(&current, |time| time.time)
{
Ok(idx) => idx,
Err(idx) => idx,
};
if idx > 0 {
return Some(&self.schedule[idx - 1]); // Return the time before the current time
} else if !self.schedule.is_empty() {
return self.schedule.last(); // Wrap around to the last time if current time is before the first time
} else {
return None; // Return None if the times array is empty
}
}
}
impl Timetable {
fn get_absolute_image_path(&self, config_path: &Path) -> PathBuf {
if self.image.is_absolute() {
return self.image.clone();
}
return config_path.join(&self.image);
}
}
fn deserialize_seconds<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
let datetime: Datetime = Deserialize::deserialize(deserializer)?;
match datetime.time {
Some(time) => {
let h: u32 = time.hour.into();
let m: u32 = time.minute.into();
let s: u32 = time.second.into();
return Ok(h * 3600 + m * 60 + s);
}
None => return Err(de::Error::custom("Must include a time of day")),
}
} }
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse(); let cli = Cli::parse();
let sunrise = Sunrise::load_from_file(&cli.file)?; match cli.command {
Commands::Current { file } => {
let sunrise = Sunrise::load_from_file(&file)?;
let now = Local::now();
let sec = now.hour() * 3600 + now.minute() * 60 + now.second();
match sunrise.closest_passed_time(sec) {
Some(timetable) => match file.canonicalize()?.parent() {
Some(sunrise_absolute_base) => {
let image = timetable.get_absolute_image_path(&sunrise_absolute_base);
println!("{}", image.to_str().unwrap());
}
None => {
println!("Failed");
}
},
let now = Local::now(); None => {
let sec = now.hour() * 3600 + now.minute() * 60 + now.second(); println!("Failed");
}
match sunrise.closest_passed_time(sec) {
Some(timetable) => match cli.file.canonicalize()?.parent() {
Some(sunrise_absolute_base) => {
let image = timetable.get_absolute_image_path(&sunrise_absolute_base);
println!("{}", image.to_str().unwrap());
} }
None => { }
println!("Failed");
}
},
None => { Commands::NextAt { file } => {
println!("Failed"); let sunrise = Sunrise::load_from_file(&file)?;
let now = Local::now();
let sec = now.hour() * 3600 + now.minute() * 60 + now.second();
match sunrise.next_time(sec) {
Some(timetable) => {
println!("{}", sec_to_readable(timetable.time));
}
None => {
println!("Failed");
}
}
}
Commands::NextIn { file, seconds } => {
let sunrise = Sunrise::load_from_file(&file)?;
let now = Local::now();
let sec = now.hour() * 3600 + now.minute() * 60 + now.second();
match sunrise.next_time(sec) {
Some(timetable) => {
let next_in = if timetable.time >= sec {
timetable.time - sec
}else{
timetable.time + (24 * 60 * 60) - sec
};
if seconds {
println!("{}",next_in);
}else{
println!("{}",sec_to_readable(next_in));
}
}
None => {
println!("Failed");
}
}
} }
} }

97
src/sunrise.rs Normal file
View File

@@ -0,0 +1,97 @@
use serde::{de, Deserialize, Deserializer};
use std::{
error::Error,
fs,
path::{Path, PathBuf},
};
use toml::value::Datetime;
#[derive(Deserialize, Debug)]
pub struct Sunrise {
/// Sorted array of Timetables
schedule: Vec<Timetable>,
}
#[derive(Deserialize, Debug)]
pub struct Timetable {
/// Time of day in seconds from 0:00
#[serde(deserialize_with = "deserialize_seconds")]
pub time: u32,
pub image: PathBuf,
}
impl Sunrise {
pub fn load_from_file(path: &Path) -> Result<Sunrise, Box<dyn Error>> {
let file = fs::read_to_string(path)?;
let mut data: Sunrise = toml::from_str(&file)?;
data.schedule.sort_by_key(|e| e.time);
Ok(data)
}
pub fn closest_passed_time(&self, current: u32) -> Option<&Timetable> {
let idx = match self
.schedule
.binary_search_by_key(&current, |time| time.time)
{
Ok(idx) => idx,
Err(idx) => idx,
};
if idx > 0 {
return Some(&self.schedule[idx - 1]); // Return the time before the current time
} else if !self.schedule.is_empty() {
return self.schedule.last(); // Wrap around to the last time if current time is before the first time
} else {
return None; // Return None if the times array is empty
}
}
pub fn next_time(&self, current: u32) -> Option<&Timetable> {
let idx = match self
.schedule
.binary_search_by_key(&current, |time| time.time)
{
Ok(idx) => idx + 1,
Err(idx) => idx,
};
if idx < self.schedule.len() {
Some(&self.schedule[idx])
} else if !self.schedule.is_empty() {
Some(&self.schedule[0]) // Wrap around to the first time
} else {
None // Return None if the times array is empty
}
}
}
impl Timetable {
pub fn get_absolute_image_path(&self, config_path: &Path) -> PathBuf {
if self.image.is_absolute() {
return self.image.clone();
}
return config_path.join(&self.image);
}
}
fn deserialize_seconds<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
let datetime: Datetime = Deserialize::deserialize(deserializer)?;
match datetime.time {
Some(time) => {
let h: u32 = time.hour.into();
let m: u32 = time.minute.into();
let s: u32 = time.second.into();
return Ok(h * 3600 + m * 60 + s);
}
None => return Err(de::Error::custom("Must include a time of day")),
}
}