Compare commits
2 Commits
fbce0dc3f1
...
0681a8b8d1
| Author | SHA1 | Date | |
|---|---|---|---|
|
0681a8b8d1
|
|||
|
09e06c57ea
|
185
src/main.rs
185
src/main.rs
@@ -1,113 +1,114 @@
|
||||
use chrono::{Local, Timelike};
|
||||
use clap::Parser;
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use std::{
|
||||
error::Error,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use toml::value::Datetime;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::{error::Error, path::PathBuf};
|
||||
use sunrise::Sunrise;
|
||||
|
||||
mod sunrise;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// Sunrise file
|
||||
file: PathBuf,
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Sunrise {
|
||||
/// Sorted array of Timetables
|
||||
schedule: Vec<Timetable>,
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Print the current wallpaper
|
||||
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)]
|
||||
struct Timetable {
|
||||
/// Time of day in seconds from 0:00
|
||||
#[serde(deserialize_with = "deserialize_seconds")]
|
||||
time: u32,
|
||||
image: PathBuf,
|
||||
}
|
||||
fn sec_to_readable(sec: u32) -> String {
|
||||
let hours = sec / 3600;
|
||||
let minutes = (sec % 3600) / 60;
|
||||
let seconds = sec % 60;
|
||||
|
||||
impl Sunrise {
|
||||
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(¤t, |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")),
|
||||
}
|
||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
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();
|
||||
let sec = now.hour() * 3600 + now.minute() * 60 + now.second();
|
||||
|
||||
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 => {
|
||||
println!("Failed");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
None => {
|
||||
println!("Failed");
|
||||
Commands::NextAt { file } => {
|
||||
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
97
src/sunrise.rs
Normal 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(¤t, |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(¤t, |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")),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user