Add todo.txt pom and its dependencies
This commit is contained in:
parent
1e7adfa51c
commit
af5315c5b9
10 changed files with 403 additions and 0 deletions
7
.config/yadm/bootstrap.d/11-ruby
Executable file
7
.config/yadm/bootstrap.d/11-ruby
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
if command -v ruby >/dev/null 2>&1; then
|
||||
exit 0
|
||||
else
|
||||
sudo pacman -S ruby
|
||||
gem install rainbow terminal-notifier
|
||||
fi
|
36
.todo.actions.d/pom/README.md
Normal file
36
.todo.actions.d/pom/README.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
## Pomodori-todo.txt
|
||||
|
||||
A pomodoro counter implementation for [Todo.txt](http://todotxt.com/).
|
||||
|
||||
## What it features
|
||||
|
||||
* easily plan, run timers, and show current tasks status via the command line
|
||||
* allows tmux integration to display the remaining pomodoro timer in your tmux statusbar
|
||||
* logs the pomodori start time and end time to a log file for an easy history
|
||||
* uses the terminal-notifier gem to notify you when your pomodoro starts and ends
|
||||
|
||||
## Installation
|
||||
|
||||
* Make sure you have ruby installed
|
||||
* run `gem install rainbow terminal-notifier`
|
||||
* Download this repo somewhere on your filesystem
|
||||
* symlink the `pom` executable into your TODO_ACTIONS_DIR
|
||||
|
||||
## Usage:
|
||||
|
||||
todo.sh pom action [task_number] [args]
|
||||
Actions:
|
||||
ls
|
||||
lists tasks with pomodori count and estimates
|
||||
log
|
||||
displays a log of your pomodori
|
||||
start ITEM#
|
||||
start a pomodoro timer for item ITEM#
|
||||
add ITEM#
|
||||
add one pomodoro to task on line ITEM# without running a timer
|
||||
plan ITEM# PLANNED_POMODORI
|
||||
estimate ITEM# will take PLANNED_POMODORI to complete
|
||||
|
||||
## Example:
|
||||
|
||||
<img src="https://raw.github.com/metalelf0/pomodori-todo.txt/master/screenshot.png">
|
39
.todo.actions.d/pom/lib/countdown.rb
Normal file
39
.todo.actions.d/pom/lib/countdown.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
class Countdown
|
||||
|
||||
def run seconds, options={}
|
||||
seconds.downto(0) do |current_seconds|
|
||||
sleep 1
|
||||
write_tmux(current_seconds) if (options[:services] || []).include?(:tmux)
|
||||
set_window_title(current_seconds) if (options[:services] || []).include?(:iterm2)
|
||||
STDOUT.write "[RUNNING] #{to_minutes(current_seconds)}\r"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def write_tmux(current_seconds)
|
||||
`echo #{to_minutes(current_seconds, :tmux)} > ~/.pomo.txt.tmux`
|
||||
end
|
||||
|
||||
def set_window_title(current_seconds)
|
||||
title = to_minutes(current_seconds, :tmux)
|
||||
`echo -ne "\e]1;#{title}\a"`
|
||||
end
|
||||
|
||||
def to_minutes seconds, format=:standard
|
||||
minutes = seconds / 60
|
||||
mins = sprintf("%02d", minutes)
|
||||
secs = sprintf("%02d", seconds % 60)
|
||||
if format == :standard
|
||||
"#{mins}:#{secs} [#{dots_for(minutes)}]"
|
||||
else
|
||||
"#{mins}:#{secs}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def dots_for mins
|
||||
"#{'.' * (25 - mins)}#{' ' * mins}"
|
||||
end
|
||||
|
||||
end
|
29
.todo.actions.d/pom/lib/file_logger.rb
Normal file
29
.todo.actions.d/pom/lib/file_logger.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require 'fileutils'
|
||||
|
||||
class FileLogger
|
||||
|
||||
attr_accessor :pomodoro_log_file
|
||||
|
||||
def initialize log_file_path
|
||||
@pomodoro_log_file = log_file_path
|
||||
FileUtils.touch pomodoro_log_file
|
||||
end
|
||||
|
||||
def notify_start task
|
||||
add_separator_if_new_day
|
||||
File.open(pomodoro_log_file, "a") { |file| file.puts "#{Time.now.strftime('%Y/%m/%d %H:%M')} Pomodoro nr. #{sprintf("% 2d", task.pomodori + 1)} started: #{task.text}" }
|
||||
end
|
||||
|
||||
def notify_completed task
|
||||
File.open(pomodoro_log_file, "a") { |file| file.puts "#{Time.now.strftime('%Y/%m/%d %H:%M')} Pomodoro nr. #{sprintf("% 2d", task.pomodori + 1)} completed: #{task.text}" }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_separator_if_new_day
|
||||
return if File.zero?(pomodoro_log_file)
|
||||
last_day = File.open(pomodoro_log_file, "r") { |file| file.readlines.last.split(" ")[0]}
|
||||
today = Time.now.strftime('%Y/%m/%d')
|
||||
File.open(pomodoro_log_file, "a") { |file| file.puts "\n-----------\n\n" } if last_day != today
|
||||
end
|
||||
end
|
19
.todo.actions.d/pom/lib/pomo_logger.rb
Normal file
19
.todo.actions.d/pom/lib/pomo_logger.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class PomoLogger
|
||||
|
||||
attr_accessor :options
|
||||
|
||||
def initialize opts
|
||||
@options = opts
|
||||
end
|
||||
|
||||
def log_pomodoro_started task
|
||||
TerminalNotifierLogger.new.notify_start(task)
|
||||
FileLogger.new(options[:pomodoro_log_file]).notify_start(task)
|
||||
end
|
||||
|
||||
def log_pomodoro_completed task
|
||||
TerminalNotifierLogger.new.notify_completed(task)
|
||||
FileLogger.new(options[:pomodoro_log_file]).notify_completed(task)
|
||||
end
|
||||
|
||||
end
|
63
.todo.actions.d/pom/lib/task.rb
Normal file
63
.todo.actions.d/pom/lib/task.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
require 'rainbow'
|
||||
|
||||
class Task
|
||||
|
||||
attr_accessor :text
|
||||
attr_accessor :index
|
||||
attr_accessor :pomodori
|
||||
attr_accessor :planned
|
||||
|
||||
POMO_REGEXP = / \(#pomo: (\d+)\/(\d+)\)$/
|
||||
PRIORITY_REGEXP = /^\([A-Z]+\) /
|
||||
STATUS_COLORS = {
|
||||
new: :white,
|
||||
planned: :green,
|
||||
in_progress: :yellow,
|
||||
completed: :blue,
|
||||
underestimated: :red
|
||||
}
|
||||
|
||||
def initialize index, text
|
||||
@index = index.to_i
|
||||
if text =~ POMO_REGEXP
|
||||
@pomodori, @planned = text.match(POMO_REGEXP)[1].to_i, text.match(POMO_REGEXP)[2].to_i
|
||||
else
|
||||
@pomodori, @planned = 0, 0
|
||||
end
|
||||
@text = text.gsub(POMO_REGEXP, '').gsub(PRIORITY_REGEXP, '')
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{text} (#pomo: #{pomodori}/#{planned})"
|
||||
end
|
||||
|
||||
def add_pomo
|
||||
@pomodori += 1
|
||||
end
|
||||
|
||||
def plan planned
|
||||
@planned = planned.to_i
|
||||
end
|
||||
|
||||
def puts_highlighted
|
||||
return if blank?(text)
|
||||
print "#{index} #{text} "
|
||||
color = STATUS_COLORS[self.status]
|
||||
puts Rainbow("(#pomo: #{pomodori}/#{planned})").send(color)
|
||||
end
|
||||
|
||||
def status
|
||||
return :new if pomodori == 0 && planned == 0
|
||||
return :planned if pomodori == 0 && planned != 0
|
||||
return :completed if pomodori == planned
|
||||
return :in_progress if pomodori != 0 && planned != 0 && pomodori < planned
|
||||
return :underestimated if pomodori != 0 && pomodori > planned
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blank?(string)
|
||||
string == nil || string == ""
|
||||
end
|
||||
|
||||
end
|
9
.todo.actions.d/pom/lib/terminal_notifier_logger.rb
Normal file
9
.todo.actions.d/pom/lib/terminal_notifier_logger.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class TerminalNotifierLogger
|
||||
def notify_start task
|
||||
TerminalNotifier.notify('Pomodoro started', title: 'Pomotxt', sound: 'Glass')
|
||||
end
|
||||
|
||||
def notify_completed task
|
||||
TerminalNotifier.notify('Pomodoro completed!', title: 'Pomotxt', sound: 'Glass')
|
||||
end
|
||||
end
|
94
.todo.actions.d/pom/pom
Executable file
94
.todo.actions.d/pom/pom
Executable file
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'date'
|
||||
require 'rainbow'
|
||||
require 'terminal-notifier'
|
||||
require_relative 'lib/task'
|
||||
require_relative 'lib/countdown'
|
||||
require_relative 'lib/pomo_logger'
|
||||
require_relative 'lib/terminal_notifier_logger'
|
||||
require_relative 'lib/file_logger'
|
||||
|
||||
POMODORO_SECONDS = 25 * 60
|
||||
# TODO: either move this to a env variable or a yml configuration file
|
||||
POMODORO_LOG_FILE = "#{ENV['HOME']}/Documents/pomodoro_log.txt"
|
||||
|
||||
def logger
|
||||
PomoLogger.new(
|
||||
:pomodoro_log_file => POMODORO_LOG_FILE
|
||||
)
|
||||
end
|
||||
|
||||
def read_todos
|
||||
todos = File.read(ENV['TODO_FILE'])
|
||||
todos.split(/\r?\n/)
|
||||
end
|
||||
|
||||
def increase_pomos todos, index
|
||||
todo_to_update = todos[index]
|
||||
task = Task.new(index + 1, todo_to_update)
|
||||
task.add_pomo
|
||||
task.puts_highlighted
|
||||
`todo.sh replace #{index + 1} "#{task.to_s}"`
|
||||
end
|
||||
|
||||
def plan_task todos, index, planned
|
||||
todo_to_update = todos[index]
|
||||
task = Task.new(index + 1, todo_to_update)
|
||||
task.plan(planned)
|
||||
task.puts_highlighted
|
||||
`todo.sh replace #{index + 1} "#{task.to_s}"`
|
||||
end
|
||||
|
||||
def puts_and_exit string
|
||||
puts string
|
||||
exit
|
||||
end
|
||||
|
||||
|
||||
case ARGV[1]
|
||||
when 'ls'
|
||||
todos = read_todos
|
||||
todos.each_with_index do |todo, index|
|
||||
Task.new(index + 1, todo).puts_highlighted
|
||||
end
|
||||
when 'log'
|
||||
puts `cat #{POMODORO_LOG_FILE}`
|
||||
when 'add'
|
||||
puts_and_exit "You must insert the task number" unless index = ARGV[2]
|
||||
puts_and_exit "Task number must be... a number" unless index =~ /\d+/
|
||||
index = index.to_i - 1
|
||||
todos = read_todos
|
||||
increase_pomos todos, index
|
||||
when 'plan'
|
||||
puts_and_exit "You must insert the task number" unless index = ARGV[2]
|
||||
puts_and_exit "Task number must be... a number" unless index =~ /\d+/
|
||||
puts_and_exit "You must insert the task pomodori estimate" unless planned = ARGV[3]
|
||||
puts_and_exit "The task pomodori estimate must be a number" unless planned =~ /\d+/
|
||||
index = index.to_i - 1
|
||||
todos = read_todos
|
||||
plan_task todos, index, planned
|
||||
when 'start'
|
||||
puts_and_exit "You must insert the task number" unless index = ARGV[2]
|
||||
puts_and_exit "Task number must be... a number" unless index =~ /\d+/
|
||||
index = index.to_i - 1
|
||||
todos = read_todos
|
||||
task_being_worked = Task.new(index + 1, todos[index])
|
||||
logger.log_pomodoro_started(task_being_worked)
|
||||
Countdown.new.run(POMODORO_SECONDS, services: [ :tmux, :iterm2 ])
|
||||
logger.log_pomodoro_completed(task_being_worked)
|
||||
increase_pomos todos, index
|
||||
when 'help'
|
||||
puts "Usage: todo.sh pomo action [task_number] [args]\n"
|
||||
puts "Actions:"
|
||||
puts " ls"
|
||||
puts " lists tasks with pomodori count and estimates"
|
||||
puts " log"
|
||||
puts " displays a log of your pomodori"
|
||||
puts " start ITEM#"
|
||||
puts " start a pomodoro timer for item ITEM#"
|
||||
puts " add ITEM#"
|
||||
puts " add one pomodoro to task on line ITEM# without running a timer"
|
||||
puts " plan ITEM# PLANNED_POMODORI"
|
||||
puts " estimate ITEM# will take PLANNED_POMODORI to complete"
|
||||
end
|
BIN
.todo.actions.d/pom/screenshot.png
Normal file
BIN
.todo.actions.d/pom/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
107
.todo.actions.d/pom/spec/task_spec.rb
Normal file
107
.todo.actions.d/pom/spec/task_spec.rb
Normal file
|
@ -0,0 +1,107 @@
|
|||
require_relative '../lib/task'
|
||||
|
||||
describe Task do
|
||||
|
||||
context "Task#new" do
|
||||
context "When task already includes pomodori" do
|
||||
subject { Task.new(1, "Write Task spec (#pomo: 3/5)")}
|
||||
specify { expect(subject.pomodori).to eq(3) }
|
||||
specify { expect(subject.planned).to eq(5) }
|
||||
specify { expect(subject.text).to eq("Write Task spec") }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 3/5)") }
|
||||
end
|
||||
|
||||
context "When task does not include pomodori" do
|
||||
subject { Task.new(1, "Write Task spec")}
|
||||
specify { expect(subject.pomodori).to eq(0) }
|
||||
specify { expect(subject.planned).to eq(0) }
|
||||
specify { expect(subject.text).to eq("Write Task spec") }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 0/0)") }
|
||||
end
|
||||
end
|
||||
|
||||
context "Task#add_pomo" do
|
||||
context "Prioritized task" do
|
||||
subject { Task.new(1, "(A) Write Task spec (#pomo: 3/5)")}
|
||||
specify { expect(subject.pomodori).to eq(3) }
|
||||
|
||||
context "Adding pomo to a tagged task" do
|
||||
before { subject.add_pomo }
|
||||
specify { expect(subject.pomodori).to eq(4) }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 4/5)") }
|
||||
specify { subject.add_pomo; expect(subject.to_s).to eq("Write Task spec (#pomo: 5/5)") }
|
||||
end
|
||||
end
|
||||
|
||||
context "Already tagged task" do
|
||||
subject { Task.new(1, "Write Task spec (#pomo: 3/5)")}
|
||||
specify { expect(subject.pomodori).to eq(3) }
|
||||
|
||||
context "Adding pomo to a tagged task" do
|
||||
before { subject.add_pomo }
|
||||
specify { expect(subject.pomodori).to eq(4) }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 4/5)") }
|
||||
end
|
||||
end
|
||||
|
||||
context "Normal task" do
|
||||
subject { Task.new(1, "Write Task spec")}
|
||||
specify { expect(subject.pomodori).to eq(0) }
|
||||
|
||||
context "Adding pomo to a normal task" do
|
||||
before { subject.add_pomo }
|
||||
specify { expect(subject.pomodori).to eq(1) }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 1/0)") }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "Task#plan" do
|
||||
context "When task already includes pomodori" do
|
||||
subject { Task.new(1, "Write Task spec (#pomo: 3/5)")}
|
||||
before { subject.plan(10) }
|
||||
specify { expect(subject.pomodori).to eq(3) }
|
||||
specify { expect(subject.planned).to eq(10) }
|
||||
specify { expect(subject.text).to eq("Write Task spec") }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 3/10)") }
|
||||
end
|
||||
|
||||
context "When task does not include pomodori" do
|
||||
subject { Task.new(1, "Write Task spec")}
|
||||
before { subject.plan(10) }
|
||||
specify { expect(subject.pomodori).to eq(0) }
|
||||
specify { expect(subject.planned).to eq(10) }
|
||||
specify { expect(subject.text).to eq("Write Task spec") }
|
||||
specify { expect(subject.to_s).to eq("Write Task spec (#pomo: 0/10)") }
|
||||
end
|
||||
end
|
||||
|
||||
context "Task#status" do
|
||||
subject { Task.new(1, "Write Task spec")}
|
||||
|
||||
context "new task" do
|
||||
specify { expect(subject.status).to eq :new }
|
||||
end
|
||||
|
||||
context "planned task" do
|
||||
before { subject.plan(10) }
|
||||
specify { expect(subject.status).to eq :planned }
|
||||
end
|
||||
|
||||
context "in progress task" do
|
||||
before { subject.plan(10); subject.add_pomo }
|
||||
specify { expect(subject.status).to eq :in_progress }
|
||||
end
|
||||
|
||||
context "completed task" do
|
||||
before { subject.plan(1); subject.add_pomo }
|
||||
specify { expect(subject.status).to eq :completed }
|
||||
end
|
||||
|
||||
context "underestimated task" do
|
||||
before { subject.plan(1); 2.times { subject.add_pomo } }
|
||||
specify { expect(subject.status).to eq :underestimated }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue