diff --git a/src/config.rs b/src/config.rs index aa73b9c..80a4cd2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,63 +1,55 @@ -use std::{ - str::FromStr, -}; +use std::str::FromStr; -type Result = std::result::Result ; +type Result = std::result::Result; pub struct Config { - pub interval_secs: u64, - pub prompt: String, - - // Open dev tools in a web browser and run `Math.random () * 2225` - pub offset_secs: u64, + pub interval_secs: u64, + pub prompt: String, + + // Open dev tools in a web browser and run `Math.random () * 2225` + pub offset_secs: u64, } impl Default for Config { - fn default () -> Self { - Self { - interval_secs: 2225, - prompt: "Write a journal entry, then hit Tab, Enter to submit it.".into (), - offset_secs: 0, - } - } + fn default() -> Self { + Self { + interval_secs: 2225, + prompt: "Write a journal entry, then hit Tab, Enter to submit it.".into(), + offset_secs: 0, + } + } } impl Config { - pub fn from_args > (mut args: I) -> Result { - use crate::error::Error::{ - CannotParseArg, - ParamNeedsArg, - }; - - let mut that = Self::default (); - - // Finally found the difference: https://stackoverflow.com/questions/1788923/parameter-vs-argument - - while let Some (arg) = args.next () { - if arg == "--interval-secs" { - let val = args.next ().ok_or (ParamNeedsArg ("--interval-secs"))?; - let val = u64::from_str (&val).map_err (|_| CannotParseArg ("--interval-secs "))?; - that.interval_secs = val; - } - else if arg == "--offset-secs" { - let val = args.next ().ok_or (ParamNeedsArg ("--offset-secs"))?; - let val = u64::from_str (&val).map_err (|_| CannotParseArg ("--offset-secs "))?; - that.offset_secs = val; - } - else if arg == "--prompt" { - let val = args.next ().ok_or (ParamNeedsArg ("--prompt"))?; - that.prompt = val; - } - } - - Ok (that) - } + pub fn from_args>(mut args: I) -> Result { + use crate::error::Error::{CannotParseArg, ParamNeedsArg}; + + let mut that = Self::default(); + + // Finally found the difference: https://stackoverflow.com/questions/1788923/parameter-vs-argument + + while let Some(arg) = args.next() { + if arg == "--interval-secs" { + let val = args.next().ok_or(ParamNeedsArg("--interval-secs"))?; + let val = + u64::from_str(&val).map_err(|_| CannotParseArg("--interval-secs "))?; + that.interval_secs = val; + } else if arg == "--offset-secs" { + let val = args.next().ok_or(ParamNeedsArg("--offset-secs"))?; + let val = u64::from_str(&val).map_err(|_| CannotParseArg("--offset-secs "))?; + that.offset_secs = val; + } else if arg == "--prompt" { + let val = args.next().ok_or(ParamNeedsArg("--prompt"))?; + that.prompt = val; + } + } + + Ok(that) + } } -#[cfg (test)] +#[cfg(test)] mod test { - #[test] - fn parse () { - - } + #[test] + fn parse() {} } diff --git a/src/error.rs b/src/error.rs index 9cfbf92..e91949f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,17 +1,17 @@ use fltk::prelude::*; -#[derive (thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug)] pub enum Error { - #[error ("46MVLSEL Cannot parse argument: {0}")] - CannotParseArg (&'static str), - #[error ("4JZ5B2FN Editor has no buffer, this should be impossible")] - EditorHasNoBuffer, - #[error ("OKE7Z5O6 FLTK: {0}")] - Fltk (#[from] FltkError), - #[error ("4BQPBIAJ IO")] - Io (#[from] std::io::Error), - #[error ("KDP4DNOP JSON serialization failed")] - JsonSerialization (#[from] serde_json::Error), - #[error ("3MYHBQWV Parameter {0} needs an argument")] - ParamNeedsArg (&'static str), + #[error("46MVLSEL Cannot parse argument: {0}")] + CannotParseArg(&'static str), + #[error("4JZ5B2FN Editor has no buffer, this should be impossible")] + EditorHasNoBuffer, + #[error("OKE7Z5O6 FLTK: {0}")] + Fltk(#[from] FltkError), + #[error("4BQPBIAJ IO")] + Io(#[from] std::io::Error), + #[error("KDP4DNOP JSON serialization failed")] + JsonSerialization(#[from] serde_json::Error), + #[error("3MYHBQWV Parameter {0} needs an argument")] + ParamNeedsArg(&'static str), } diff --git a/src/main.rs b/src/main.rs index 6b46585..2b40f28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,13 @@ -use std::{ - env, -}; +use std::env; -use chrono::{ - DateTime, - Local, - SecondsFormat, -}; +use chrono::{DateTime, Local, SecondsFormat}; -use fltk::{ - app, - button, - frame, - prelude::*, - text, - window::Window, -}; +use fltk::{app, button, frame, prelude::*, text, window::Window}; use tokio::{ - fs, - io::AsyncWriteExt, - time::{ - Duration, - interval_at, - }, + fs, + io::AsyncWriteExt, + time::{interval_at, Duration}, }; mod config; @@ -34,164 +18,166 @@ use config::Config; use error::Error; #[tokio::main] -async fn main () -> Result <(), Error> { - let config = Config::from_args (env::args ())?; - - let (fltk_tx, fltk_rx) = app::channel:: (); - - let app = app::App::default (); - - tokio::spawn (async move { - let mut i = interval_at ( - offset::get_next_tick ( - config.interval_secs, - config.offset_secs, - ).into (), - Duration::from_secs (config.interval_secs), - ); - i.set_missed_tick_behavior (tokio::time::MissedTickBehavior::Skip); - - loop { - i.tick ().await; - eprintln! ("3SAHNQ43 Popping up"); - fltk_tx.send (Message::PopUp); - } - }); - - let mut gui = Gui::new (config, fltk_tx); - - while app.wait () { - let msg = match fltk_rx.recv () { - Some (x) => x, - None => continue, - }; - - match msg { - Message::Submit => { - if let Err (e) = gui.submit ().await { - eprintln! ("DVW4SBNB Error while submitting: {:?}", e); - } - }, - Message::PopUp => { - if let Err (e) = gui.pop_up () { - eprintln! ("5BWNNQT6 Error while popping up: {:?}", e); - } - }, - } - } - - Ok (()) +async fn main() -> Result<(), Error> { + let config = Config::from_args(env::args())?; + + let (fltk_tx, fltk_rx) = app::channel::(); + + let app = app::App::default(); + + tokio::spawn(async move { + let mut i = interval_at( + offset::get_next_tick(config.interval_secs, config.offset_secs).into(), + Duration::from_secs(config.interval_secs), + ); + i.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + i.tick().await; + eprintln!("3SAHNQ43 Popping up"); + fltk_tx.send(Message::PopUp); + } + }); + + let mut gui = Gui::new(config, fltk_tx); + + while app.wait() { + let msg = match fltk_rx.recv() { + Some(x) => x, + None => continue, + }; + + match msg { + Message::Submit => { + if let Err(e) = gui.submit().await { + eprintln!("DVW4SBNB Error while submitting: {:?}", e); + } + } + Message::PopUp => { + if let Err(e) = gui.pop_up() { + eprintln!("5BWNNQT6 Error while popping up: {:?}", e); + } + } + } + } + + Ok(()) } -#[derive (Clone, Copy)] +#[derive(Clone, Copy)] enum Message { - PopUp, - Submit, + PopUp, + Submit, } -#[derive (serde::Serialize)] +#[derive(serde::Serialize)] struct JournalLine { - text: String, - time_popped_up: Option , - time_submitted: String, + text: String, + time_popped_up: Option, + time_submitted: String, } struct Gui { - time_popped_up: Option >, - editor: text::TextEditor, - status: frame::Frame, - wind: Window, + time_popped_up: Option>, + editor: text::TextEditor, + status: frame::Frame, + wind: Window, } impl Gui { - fn new (config: Config, fltk_tx: app::Sender ) -> Self { - let mut wind = Window::new (100, 100, 640, 480, "Annoying Journal"); - wind.make_resizable (true); - - let mut buffer = text::TextBuffer::default (); - buffer.set_text (&config.prompt); - let mut display = text::TextDisplay::new (0, 0, 640, 50, ""); - display.set_buffer (Some (buffer)); - - let mut editor = text::TextEditor::new (0, 50, 640, 480 - 50 - 50, ""); - editor.set_buffer (Some (text::TextBuffer::default ())); - - editor.set_tab_nav (true); - - let mut but = button::ReturnButton::new (640 - 100, 480 - 50, 100, 50, "Submit"); - but.emit (fltk_tx, Message::Submit); - - let status = frame::Frame::new (0, 480 - 50, 640 - 100, 50, ""); - - wind.set_label ("ANNOYING JOURNAL"); - wind.end (); - wind.show (); - - let mut that = Self { - time_popped_up: Some (Local::now ()), - editor, - status, - wind, - }; - - that.refresh_status (); - - that - } - - fn refresh_status (&mut self) { - let version = option_env! ("CARGO_PKG_VERSION").unwrap_or ("(???)"); - let time_popped_up = self.time_popped_up - .map (|x| x.to_rfc3339_opts (SecondsFormat::Secs, true)) - .unwrap_or_else (|| "(???)".to_string ()); - - self.status.set_label (&format! ("v{}, popped up at {}", version, time_popped_up)); - } - - fn pop_up (&mut self) -> Result <(), Error> { - if self.time_popped_up.is_some () { - eprintln! ("O4U6E36V Ignoring pop-up, already popped up"); - return Ok (()); - } - - self.time_popped_up = Some (Local::now ()); - self.refresh_status (); - self.wind.set_label ("ANNOYING JOURNAL"); - self.wind.show (); - self.editor.take_focus ()?; - - Ok (()) - } - - async fn submit (&mut self) -> Result <(), Error> { - let buffer = match self.editor.buffer () { - None => return Err (Error::EditorHasNoBuffer), - Some (x) => x, - }; - - let jl = JournalLine { - text: buffer.text (), - time_popped_up: self.time_popped_up.map (|x| x.to_rfc3339_opts (SecondsFormat::Secs, true)), - time_submitted: Local::now ().to_rfc3339_opts (SecondsFormat::Secs, true), - }; - - let s = serde_json::to_string (&jl)?; - - fs::create_dir_all ("annoying_journal").await?; - - let mut f = fs::OpenOptions::new () - .append (true) - .create (true) - .open ("annoying_journal/journal.jsonl").await?; - f.write_all (s.as_bytes ()).await?; - f.write_all (b"\n").await?; - - println! ("{}", s); - self.editor.set_buffer (text::TextBuffer::default ()); - self.wind.iconize (); - self.time_popped_up = None; - self.wind.set_label ("annoying journal"); - - Ok (()) - } + fn new(config: Config, fltk_tx: app::Sender) -> Self { + let mut wind = Window::new(100, 100, 640, 480, "Annoying Journal"); + wind.make_resizable(true); + + let mut buffer = text::TextBuffer::default(); + buffer.set_text(&config.prompt); + let mut display = text::TextDisplay::new(0, 0, 640, 50, ""); + display.set_buffer(Some(buffer)); + + let mut editor = text::TextEditor::new(0, 50, 640, 480 - 50 - 50, ""); + editor.set_buffer(Some(text::TextBuffer::default())); + + editor.set_tab_nav(true); + + let mut but = button::ReturnButton::new(640 - 100, 480 - 50, 100, 50, "Submit"); + but.emit(fltk_tx, Message::Submit); + + let status = frame::Frame::new(0, 480 - 50, 640 - 100, 50, ""); + + wind.set_label("ANNOYING JOURNAL"); + wind.end(); + wind.show(); + + let mut that = Self { + time_popped_up: Some(Local::now()), + editor, + status, + wind, + }; + + that.refresh_status(); + + that + } + + fn refresh_status(&mut self) { + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("(???)"); + let time_popped_up = self + .time_popped_up + .map(|x| x.to_rfc3339_opts(SecondsFormat::Secs, true)) + .unwrap_or_else(|| "(???)".to_string()); + + self.status + .set_label(&format!("v{}, popped up at {}", version, time_popped_up)); + } + + fn pop_up(&mut self) -> Result<(), Error> { + if self.time_popped_up.is_some() { + eprintln!("O4U6E36V Ignoring pop-up, already popped up"); + return Ok(()); + } + + self.time_popped_up = Some(Local::now()); + self.refresh_status(); + self.wind.set_label("ANNOYING JOURNAL"); + self.wind.show(); + self.editor.take_focus()?; + + Ok(()) + } + + async fn submit(&mut self) -> Result<(), Error> { + let buffer = match self.editor.buffer() { + None => return Err(Error::EditorHasNoBuffer), + Some(x) => x, + }; + + let jl = JournalLine { + text: buffer.text(), + time_popped_up: self + .time_popped_up + .map(|x| x.to_rfc3339_opts(SecondsFormat::Secs, true)), + time_submitted: Local::now().to_rfc3339_opts(SecondsFormat::Secs, true), + }; + + let s = serde_json::to_string(&jl)?; + + fs::create_dir_all("annoying_journal").await?; + + let mut f = fs::OpenOptions::new() + .append(true) + .create(true) + .open("annoying_journal/journal.jsonl") + .await?; + f.write_all(s.as_bytes()).await?; + f.write_all(b"\n").await?; + + println!("{}", s); + self.editor.set_buffer(text::TextBuffer::default()); + self.wind.iconize(); + self.time_popped_up = None; + self.wind.set_label("annoying journal"); + + Ok(()) + } } diff --git a/src/offset.rs b/src/offset.rs index 60fb9ba..186f6b1 100644 --- a/src/offset.rs +++ b/src/offset.rs @@ -1,33 +1,39 @@ -use std::{ - time::{ - Duration, - Instant, - SystemTime, - } -}; +use std::time::{Duration, Instant, SystemTime}; -pub fn get_next_tick (interval_secs: u64, offset_secs: u64) -> Instant { - let now_sys = SystemTime::now (); - let now_mono = Instant::now (); - - let phase = get_phase (now_sys, interval_secs, offset_secs); - now_mono.checked_add (Duration::from_millis (interval_secs * 1000 - u64::try_from (phase).unwrap ())).unwrap () +pub fn get_next_tick(interval_secs: u64, offset_secs: u64) -> Instant { + let now_sys = SystemTime::now(); + let now_mono = Instant::now(); + + let phase = get_phase(now_sys, interval_secs, offset_secs); + now_mono + .checked_add(Duration::from_millis( + interval_secs * 1000 - u64::try_from(phase).unwrap(), + )) + .unwrap() } -fn get_phase (now: SystemTime, interval_secs: u64, offset_secs: u64) -> u64 { - let ms_since_epoch = now.duration_since (SystemTime::UNIX_EPOCH).unwrap ().as_millis (); - u64::try_from ((ms_since_epoch + u128::from (offset_secs) * 1000) % (u128::from (interval_secs) * 1000)).unwrap () +fn get_phase(now: SystemTime, interval_secs: u64, offset_secs: u64) -> u64 { + let ms_since_epoch = now + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis(); + u64::try_from( + (ms_since_epoch + u128::from(offset_secs) * 1000) % (u128::from(interval_secs) * 1000), + ) + .unwrap() } -#[cfg (test)] +#[cfg(test)] mod test { - use super::*; - - #[test] - fn test () { - let now = SystemTime::UNIX_EPOCH.checked_add (Duration::from_secs (1649201544)).unwrap (); - - assert_eq! (get_phase (now, 2225, 0), 394000); - assert_eq! (get_phase (now, 2225, 30), 424000); - } + use super::*; + + #[test] + fn test() { + let now = SystemTime::UNIX_EPOCH + .checked_add(Duration::from_secs(1649201544)) + .unwrap(); + + assert_eq!(get_phase(now, 2225, 0), 394000); + assert_eq!(get_phase(now, 2225, 30), 424000); + } }