Skip to content

Instantly share code, notes, and snippets.

@h0rs3r4dish
Created December 16, 2009 02:35
Show Gist options
  • Save h0rs3r4dish/257537 to your computer and use it in GitHub Desktop.
Save h0rs3r4dish/257537 to your computer and use it in GitHub Desktop.
RubyTask, a todo sort of app
#!/usr/bin/ruby
# Viewing as a Gist? See rubytask.md at the bottom
ARGV.push 'help' if ARGV.empty?
command = ARGV.shift.downcase
Taskfile = '/home/'+ENV['USER']+'/.rtasks'
system "touch #{Taskfile}" if !File.exist? Taskfile
case command
when 'add'
f = File.open(Taskfile,"a"); f.puts ARGV.join(" "); f.close
when 'remove'
if not ARGV[0] =~ /^\d+$/ then
desc = ARGV.join ' '
o = IO.readlines(Taskfile).map { |line|
if line =~ /^(.+\s|)#{desc}(\[.+|)$/ then
nil
else
line
end
}
f = File.new(Taskfile,"w"); f.puts((o - [nil]).join); f.close
else
ind = IO.readlines(Taskfile)
ARGV.each { |arg|
ln = arg.to_i
if ln > ind.length then
puts "No such task %s" % ln; next
end
ind[ln-1] = nil
}
f = File.new(Taskfile,"w"); f.puts((ind - [nil]).join); f.close
end
when 'context'
if ARGV.empty? then
contexts = {};
IO.readlines(Taskfile).each { |line|
if line =~ /^(@[^\s]+)\s/ then
contexts[$1] = 0 if !contexts.key?($1)
contexts[$1] += 1
end
}
puts "%s contexts:" % contexts.length;
puts contexts.keys.sort.map { |k| k+" "+contexts[k].to_s }.join("\n");
Process.exit
end
regexp = /^(@|)#{ARGV.join(' ')}/
match = false;
IO.readlines(Taskfile).each_with_index { |l,i| line = l.strip; i=(i+1).to_s
(puts i+": "+line; match = true) if line =~ regexp;
}
puts "No contexts matched '%s'" % ARGV.join(' ') if !match
when 'date'
regexp = /\[#{ARGV.join(' ')}\]$/
IO.readlines(Taskfile).each_with_index { |l,i| line = l.strip; i=(i+1).to_s
puts i+": "+line if line =~ regexp
}
when 'desc'
regexp = /#{ARGV.join(' ')}/
IO.readlines(Taskfile).each_with_index { |l,i| line = l.strip; i=(i+1).to_s
puts i+": "+line if line =~ regexp
}
when 'help', /.*/
if not ARGV.empty? then
case ARGV.shift
when 'add'
puts <<EOF
Usage: rt add [@context] description [[date/time]]
The add command creates a new task. Everything but the description are
optional, and must be presented in the order given above.
Arguments:
@context The context for the current task. Must start with @ and
contain no spaces (all other characters are fine)
description Not optional. The text for the task (reminder, title, etc)
[date/time] Must be contained within brackets []. Can be a time or a
date (the former relative to the current day)
EOF
when 'remove'
puts <<EOF
Usage: rt remove task1 [task2 ...]
rt remove pattern
The remove command deletes task(s) from the list. There are two ways to go
about doing so: either find the unwanted task by number, and enter it in
(multiple may be accepted at a time) or search as you go and delete all that
match the critera given (all words must be present)
Arguments
taskn The task number(s) to delete, found via one of the searches
pattern Word(s) to look for; all tasks containing the filter will be
deleted
EOF
when 'context'
puts <<EOF
Usage: rt context [pattern]
This command searches contexts for a given pattern an returns them. If the
pattern is omitted, then the contexts are listed along with the number of
tasks assigned to each
Arguments:
pattern The pattern to search for, read as a regular expression
EOF
when 'date'
puts <<EOF
Usage: rt date pattern
This command searches dates for a given pattern and displays matches.
Arguments:
pattern The pattern to search for, read as a regular expression
EOF
when 'desc'
puts <<EOF
Usage: rt desc pattern
The desc command searches through the task descriptions for any that match
the given pattern. If the pattern is omitted, then all tasks are printed,
similar to 'cat ~/.rtasks'
Arguments:
pattern The pattern to search for, read as a regular expression
EOF
end
Process.exit
end
puts <<EOF
Usage: rt command [arguments]
Commands:
add Add a task to the task list
remove Delete a task from the list
context Look up all tasks in the given context(s)
date Find tasks by date or time
desc Search for tasks via their descriptions
help This message (or detailed usages for commands)
The task file is, by default, ~/.rtasks but can be changed by altering
the value in rt/rt-daemon
rt & daemon.rb are free software released under the MIT license.
EOF
end
#!/usr/bin/ruby
require 'time' # for the Time.parse() method
$config = {
:log => '/dev/null',
:assert => true,
:sleep => 30, # refresh wait (seconds)
:file => ".rtasks", # file to store tasks in
:alert => "notify-send" # program to alert with
}
ARGV.each do |l|
$config[:log] = $2 if l =~ /^l(og|)=(.+)$/
$config[:assert] = false if l =~ /^noassert$/
$config[:sleep] = $1.to_i if l =~ /^sleep=(.+)$/
$config[:alert] = $1 if l =~ /^alert=(.+)$/
$config[:file] = $1 if l =~ /^file=(.+)$/
end
Logfile = $config[:log]
def log(*text)
system "echo #{text.join(' ')} >> #{Logfile}"
end
if $config[:assert] then
def assert(test,message)
print message+"... "
if !test then
puts "No"
Process.exit
end
puts "Yes"
end
# run tests to make sure it's all okay
assert RUBY_PLATFORM =~ /linux/i, "running linux"
assert Dir['/home/*'].length > 0, "home directories"
assert $config[:alert], "alert program"
end
$tasks = {}
$mtime = Time.at(0)
def task(desc,context="",time=nil)
{ :desc => desc, :context => context, :time => time }
end
def task_to_s(task)
(task[:context] == '' ? '' : task[:context]+' ') + task[:desc] + (task[:time] != nil ? ' '+task[:time].to_s : '')
end
def alert(task)
system $config[:alert]+' "'+task[:desc]+'"'+(task[:context] == '' ? '' : ' "Context: '+task[:context]+'"')
end
def parse_tasks(file)
log "parsing task list"
IO.readlines(file).map { |line|
next if line =~ /^[\s\t\n\r]*$/
l = line.strip
c, t = '', nil
if l =~ /^(@[^\s]+)\s/ then
c = $1; l.sub!($1+' ','')
end
if l =~ /\[(.+)\]$/ then
t = Time.parse($1); l.sub!('['+$1+']','')
end
task l, c, t
} - [nil]
end
def write_tasks(user)
file = '/home/'+user+'/'+$config[:file]
log "writing task list"
f = File.new(file,"w");
$tasks[user].each { |task|
s = task_to_s(task); f.puts s if !s.nil?
}
f.close
end
p = fork {
begin
loop do
log "rt-daemon woke up"
t = Time.now
taskfiles = []
Dir['/home/*'].each { |user|
path = user+'/'+$config[:file]
next if !File.exist?(path)
log "Found file:", path
taskfiles.push [user.split("/")[-1],path]
}
taskfiles.each do |data|
user = data[0]; taskfile = data[1]
log "user:", user
if $mtime <= File.new(taskfile,"r").mtime then
$tasks[user] = parse_tasks taskfile
$mtime = t
end
ch = false
log "user has %s tasks" % $tasks[user].length
$tasks[user].each { |task|
if task[:time] != nil && task[:time] <= t then
alert task
log "alerting"
$tasks[user].delete task
ch = true
end
}
write_tasks user if ch
end
log "rt-daemon went to sleep"
sleep $config[:sleep]
end
catch Exception => e
log e.message
log "\t"+e.backtrace.join("\n\t")
end
}
Process.detach p

RubyTask

RubyTask is my version of todo.sh (http://github.com/ginatrapani/todo.txt-cli/), a command-line todo app thing. I'd probably find all the various todo apps infinitely more useful if they actually told me to do stuff.

Enter rt, or RubyTask. It tells you to do things by reminding you of dates. Think of it like a todo list with a hacked-up cron added to spruce things up. A time/date comes up? You get a reminder.

So, there are two distinct parts of RubyTask, one of which isn't really needed. The rt script is the one that actually manages the task file; this you probably want to drop in your $PATH somewhere for use. Most of the script is actually documentation, so take advantage of it: ~$ rt help

You'll get a nice long message about how to use it. Explore the help command to get info on the other stuff it does.

Now, what about the reminders? This is where the rt-daemon script comes into play. Run it and it will run some tests (unless you configure it -- see below) to make sure you've got a compatible system, then prep and daemonize itself, running in the background & free of any terminals. If it sees a past date or time, it won't be long before it alerts you about it (using the $config[:alert] command). Note that the daemon is multi-user; it will look through all of the users who have a /home/<user> directory. Just use rt as normal, and the daemon will take care of itself.

rt-daemon configuration

Command-line options that can be set (in any order)

  • l=<file>, log=<file> => Sets a log file to write to (default is /dev/null, so no log)

  • noassert => Forces the script to run, without running any of pre-operation tests.

  • sleep=<seconds> => Sets the wait between scans of .rtasks files (and alerts)

  • alert=<command> => Determines the command to alert users. Defaults to notify-send

  • file=<name> => Names the file to use for the task list. By default it uses the same one as rt, which is .rtasks

Footnote: I hate Github's Markdown interpretation sometimes. A lot. It hates my lists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment