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