Skip to content

Commit

Permalink
Merge pull request #4 from jkallio/timer-format
Browse files Browse the repository at this point in the history
Add support for digital time format
  • Loading branch information
jkallio authored Feb 24, 2024
2 parents 189fabc + 7a36f84 commit 6cce30b
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 56 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pomodoro-cli"
version = "1.2.3"
version = "1.2.4"
authors = ["Jussi Kallio <jkallio@gmail.com>"]
license = "MIT"
description = "A simple command line Pomodoro timer."
Expand Down
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align="center">

# pomodoro-cli
# Pomodoro Timer Command Line Interface
# ![](./assets/icon_small.png) pomodoro-cli
### Pomodoro Timer Command Line Interface

Pomodoro timer is a simple timer that helps you to stay focused on your tasks.

Expand All @@ -27,7 +27,7 @@ $ cargo install pomodoro-cli

# Features

- [x] Start/Stop the Timer
- [x] Start/Stop/Pause the Timer
- [x] Query the Timer status
- [x] Add more time to a running timer.
- [x] Wait for the Timer to finish
Expand All @@ -40,8 +40,8 @@ $ cargo install pomodoro-cli
# Usage

Options for `start`:
- `--duration` Set the duration for the timer (format: `1h 30m 15s`)
- `--add` Add more time to a running timer instead of starting a new timer (format: `1h 30m 15s`)
- `--duration` Set the duration for the timer (format: `1h 30m 15s` or `10:30`)
- `--add` Add more time to a running timer instead of starting a new timer
- `--message` Add a custom message to the timer status
- `--resume` Resume a paused timer (default: disabled)
- `--notify` Triggers system notification when the timer is finished (default: disabled)
Expand Down Expand Up @@ -81,14 +81,20 @@ $ pomodoro-cli start -add 10m
### Query the timer status

```bash
# Get remaining time in seconds (This is the default behavior for `status`)
$ pomodoro-cli status --format seconds

## Get remaining time in human readable format
$ pomodoro-cli status --format human

# Get the timer status in JSON format (for Waybar integration)
$ pomodoro-cli status --format json

# Specify the timer format in digital format (10:30) -- default
$ pomodoro-cli status --format human --time-format digital

# Specify the time format in segmented format (1h 30m 15s)
$ pomodoro-cli status --format human --time-format segmented

# Specify the time format in seconds
$ pomodoro-cli status --format human --time-format seconds
```

# Waybar integration
Expand All @@ -100,7 +106,7 @@ Add the following module to your waybar configuration:
```json
"custom/pomo": {
"format": "  {}",
"exec": "pomodoro-cli status --format json",
"exec": "pomodoro-cli status --format json --time-format digital",
"return-type": "json",
"on-click": "pomodoro-cli start --add 5m --notify",
"on-click-middle": "pomodoro-cli pause",
Expand All @@ -111,20 +117,20 @@ Add the following module to your waybar configuration:

### CSS styling

The module supports three different states: `running`, `paused` and `stopped`. You can customize the styling of each state by adding the following CSS rules to your Waybar configuration:
The module supports three different states: `running`, `paused` and `finished`. You can customize the styling of each state by adding the following CSS rules to your Waybar configuration:

```css
#custom-pomo.finished {
background: #8F0808;
}

#custom-pomo.running {
background: #304D30;
}

#custom-pomo.paused {
background: #AB730A;
}

#custom-pomo.finished {
background: #8F0808;
}
```

### Update Waybar module immediately
Expand Down
Binary file added assets/icon_small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 41 additions & 22 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use crate::utils::*;
use crossterm::cursor::{MoveToColumn, MoveToPreviousLine};
use crossterm::execute;
use crossterm::terminal::{Clear, ClearType};
use notify_rust::Notification;
use rodio::{Decoder, Source};
use notify_rust::{Notification, Timeout};
use rodio::{Decoder, OutputStream, Sink};
use std::thread;
use std::time::Duration;

/// Run the application with the given arguments
pub fn run(args: &Cli) -> AppResult<()> {
Expand Down Expand Up @@ -40,8 +41,11 @@ pub fn run(args: &Cli) -> AppResult<()> {
SubCommand::Stop => {
stop_timer()?;
}
SubCommand::Status { format } => {
let status = get_status(*format)?;
SubCommand::Status {
format,
time_format,
} => {
let status = get_status(*format, *time_format)?;
println!("{}", status);
}
}
Expand Down Expand Up @@ -91,20 +95,20 @@ pub fn start_timer(
/// Pause the timer. If the timer is already paused, the timer is resumed.
pub fn pause_timer() -> AppResult<()> {
let mut timer_info = TimerInfo::from_file_or_default()?;
if timer_info.is_running() {
let now = chrono::Utc::now().timestamp();
timer_info.pause_time = now;
timer_info.state = TimerState::Paused;
timer_info.write_to_file()?;
} else {
if timer_info.is_paused() {
start_timer(
Some(timer_info.duration),
None,
timer_info.message,
timer_info.silent,
timer_info.notify,
false,
true,
)?;
} else if timer_info.is_running() {
let now = chrono::Utc::now().timestamp();
timer_info.pause_time = now;
timer_info.state = TimerState::Paused;
timer_info.write_to_file()?;
}
Ok(())
}
Expand All @@ -131,33 +135,48 @@ pub fn trigger_alarm(timer_info: &TimerInfo) -> AppResult<()> {
.body("Time is up!")
.icon(&path)
.appname("pomodoro-cli")
.timeout(Timeout::from(Duration::from_secs(300)))
.show()?;
}

if !timer_info.silent {
let (_stream, stream_handle) = rodio::OutputStream::try_default()?;
let (_stream, stream_handle) = OutputStream::try_default()?;
let sink = Sink::try_new(&stream_handle).unwrap();
if let Some(path) = get_custom_alarm_file() {
let file = std::fs::File::open(path)?;
let source = Decoder::new(file)?;
stream_handle.play_raw(source.convert_samples()).unwrap();
sink.append(source);
} else {
let mp3 = include_bytes!("../assets/ding.mp3");
let cursor = std::io::Cursor::new(mp3);
let source = Decoder::new_mp3(cursor)?;
stream_handle.play_raw(source.convert_samples()).unwrap();
let source = Decoder::new(std::io::Cursor::new(mp3))?;
sink.append(source);
}
std::thread::sleep(std::time::Duration::from_millis(2000));
sink.set_volume(1.0);
sink.sleep_until_end();
sink.clear();
}
return Ok(());
}

/// Return the status of the timer in the given format.
pub fn get_status(format: Option<StatusFormat>) -> AppResult<String> {
pub fn get_status(
format: Option<StatusFormat>,
time_format: Option<TimeFormat>,
) -> AppResult<String> {
let timer_info = TimerInfo::from_file_or_default()?;
let status: String = match format {
Some(StatusFormat::Human) => format!("{}", timer_info.get_human_readable()),
Some(StatusFormat::Json) => format!("{}", timer_info.get_json_info()?),
_ => format!("{}", timer_info.get_time_left()),
Some(StatusFormat::Json) => {
format!(
"{}",
timer_info.get_json_info(time_format.unwrap_or_default())?
)
}
_ => {
format!(
"{}",
timer_info.get_human_readable(time_format.unwrap_or_default())
)
}
};

if timer_info.is_running() && !timer_info.wait && timer_info.is_time_run_out() {
Expand All @@ -182,7 +201,7 @@ pub fn wait_for_timer() -> AppResult<()> {
for _ in 0..(25 - percentage) {
print!("-");
}
println!("| {}", timer_info.get_human_readable());
println!("| {}", timer_info.get_human_readable(TimeFormat::default()));

thread::sleep(std::time::Duration::from_millis(1000));
execute!(
Expand Down
18 changes: 15 additions & 3 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub enum SubCommand {
short,
long,
conflicts_with_all = &["add", "resume"],
help = "Duration of the timer in format 1h 30m 20s"
help = "Duration of the timer ('10m 30s' or '10:30')"
)]
duration: Option<String>,

Expand Down Expand Up @@ -55,8 +55,11 @@ pub enum SubCommand {
Pause,
/// Get the current status of the timer
Status {
#[arg(short, long, help = "Status format [seconds/human-readable/JSON]")]
#[arg(short, long, help = "Status format")]
format: Option<StatusFormat>,

#[arg(short, long, help = "Time format")]
time_format: Option<TimeFormat>,
},
}

Expand All @@ -65,7 +68,16 @@ pub enum SubCommand {
#[serde(rename_all = "lowercase")]
pub enum StatusFormat {
#[default]
Seconds,
Human,
Json,
}

/// Defines the time format for the status command
#[derive(clap::ValueEnum, Clone, Default, Debug, Serialize, Copy)]
#[serde(rename_all = "lowercase")]
pub enum TimeFormat {
#[default]
Digital, // 10:30
Segmented, // 1h 10m 30s
Seconds, // 630
}
19 changes: 10 additions & 9 deletions src/timer_info.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::args::TimeFormat;
use crate::error::*;
use crate::utils::*;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -83,7 +84,7 @@ impl TimerInfo {
}

pub fn is_time_run_out(&self) -> bool {
self.get_time_left() <= 0
self.get_time_left() < 0
}

/// Returns the time left in the timer in seconds.
Expand All @@ -96,8 +97,8 @@ impl TimerInfo {
}

/// Returns the info in human readable format.
pub fn get_human_readable(&self) -> String {
let mut text = convert_to_text_format(self.get_time_left());
pub fn get_human_readable(&self, time_format: TimeFormat) -> String {
let mut text = convert_to_time_format(self.get_time_left(), time_format);
if !self.message.is_empty() {
match self.state {
TimerState::Running => text = format!("{} - {}", text, self.message),
Expand All @@ -109,18 +110,18 @@ impl TimerInfo {
}

/// Returns the info in Waybar JSON format.
pub fn get_json_info(&self) -> AppResult<String> {
let text = self.get_human_readable();
pub fn get_json_info(&self, time_format: TimeFormat) -> AppResult<String> {
let text = self.get_human_readable(time_format);
let tooltip = match self.state {
TimerState::Running => format!(
"Running\nLeft: {}\nElapsed: {} ",
convert_to_text_format(self.get_time_left()),
convert_to_text_format(self.get_time_elapsed())
convert_to_time_format(self.get_time_left(), time_format),
convert_to_time_format(self.get_time_elapsed(), time_format)
),
TimerState::Paused => format!(
"Paused\nLeft: {}\nElapsed: {} ",
convert_to_text_format(self.get_time_left()),
convert_to_text_format(self.get_time_elapsed())
convert_to_time_format(self.get_time_left(), time_format),
convert_to_time_format(self.get_time_elapsed(), time_format)
),
TimerState::Finished => "Finished".to_string(),
}
Expand Down
Loading

0 comments on commit 6cce30b

Please sign in to comment.