todo.rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+ gem 'highline'
3
+ gem 'color-tools', '~> 1.3', require: 'color'
data/Gemfile.lock ADDED
@@ -0,0 +1,12 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ color-tools (1.3.0)
5
+ highline (1.6.11)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ color-tools (~> 1.3)
12
+ highline
data/LOG ADDED
@@ -0,0 +1,49 @@
1
+ [choi power~/p/todorb]$ todo ,n
2
+ Hello world
3
+ 85
4
+ 1 test
5
+ 2 Hello world
6
+ 3 Hello world
7
+ 4 buy some milk
8
+ 5 first task
9
+ 6 another line
10
+ 85
11
+ [choi power~/p/todorb]$ fg
12
+ vim NOTES
13
+
14
+ [2]+ Stopped vim NOTES
15
+ [choi power~/p/todorb]$ todo ,n
16
+ 85
17
+ 1 test
18
+ 2 Hello world
19
+ 3 Hello world
20
+ 4 buy some milk
21
+ 5 first task
22
+ 6 another line
23
+ 85
24
+ [choi power~/p/todorb]$ todo 1c test changed
25
+ 85
26
+ 93
27
+ [choi power~/p/todorb]$ todo ,n
28
+ 93
29
+ 1 test changed
30
+ 2 Hello world
31
+ 3 Hello world
32
+ 4 buy some milk
33
+ 5 first task
34
+ 6 another line
35
+ 93
36
+ [choi power~/p/todorb]$ todo 1s/ch/sh/
37
+ 93
38
+ 93
39
+ [choi power~/p/todorb]$ todo ,n
40
+ 93
41
+ 1 test shanged
42
+ 2 Hello world
43
+ 3 Hello world
44
+ 4 buy some milk
45
+ 5 first task
46
+ 6 another line
47
+ 93
48
+ [
49
+
data/NOTES ADDED
@@ -0,0 +1,69 @@
1
+
2
+
3
+ Priorities: mark with number of !
4
+
5
+ Instead of writing wrappers around ed commands, why not just forward them directly
6
+
7
+ todo 3m4
8
+ todo 3d
9
+ todo 3a [task text] # => appends a task after 3
10
+ todo 3c [text] # => change task 3
11
+ todo 3i [text] # => insert a task before 3
12
+ todo 3s/test/blah/
13
+ todo ,n # => show numbered tasks
14
+
15
+ todo a reply to customer X +project1
16
+ todo a buy some carrots @centralsq
17
+
18
+ # to filter
19
+
20
+ todo @cambridgeport # => lists all tasks @cambridgeport
21
+
22
+ can also do this
23
+ todo /tea # => general regex search
24
+
25
+ etc.
26
+
27
+ Filters and tags
28
+
29
+ Filters are implemented by piping cat -n todolistfile to
30
+ - a special ruby -n program that colorizes
31
+ - sed to filter by context and project
32
+ - something to sort by priority
33
+ - just a straight grep or sed filter
34
+ Tags
35
+ - no special syntax? you just come up with your own
36
+ - but start with +project @context
37
+ - colorized if in config
38
+ - same with priority. priority is just a another tag
39
+ - !!! is not special, but defined
40
+ http://code.dunae.ca/css_parser/
41
+ - colorizing syntax shouldn't be css, but just /regex/ COLOR
42
+
43
+ Sort order
44
+ - do we really need sort order? no. user can just reorder by ed commands
45
+ - things like !!! should only be used for colorization
46
+
47
+
48
+ Dates
49
+ Put this off to later
50
+
51
+
52
+ Git:
53
+ Let user do this
54
+
55
+
56
+
57
+
58
+
59
+ References
60
+
61
+ If I need to implement Bash programmable completion
62
+ http://www.debian-administration.org/article/An_introduction_to_bash_completion_part_1
63
+
64
+ Codebrawl
65
+ http://codebrawl.com/contests/command-line-todo-lists
66
+
67
+ colors in shell
68
+ http://codesnippets.joyent.com/posts/show/1517
69
+
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # todo.rb
2
+
3
+ **NOTE** this is a work in progress.
4
+
5
+ A command line todo list application, inspired by
6
+ [todo.txt][todo.txt] and written with Ruby.
7
+
8
+ [todo.txt]:http://ginatrapani.github.com/todo.txt-cli/
9
+
10
+ The philosphy behind todo.rb is to fill the gap between existing Unix tools and
11
+ a convenient todo list system.
12
+
13
+
14
+ ## Data format
15
+
16
+ todo.rb keeps all your tasks in two text files, `todo.txt` and `done.txt` in
17
+ the current working directory.
18
+
19
+ ## Add a task
20
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+
data/bin/todo.rb ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'todo'
4
+
5
+ opts = {formatter: :color}
6
+ if ARGV[0] == '-C'
7
+ opts[:formatter] = :nocolor
8
+ ARGV.shift
9
+ elsif ARGV[0] == '--html'
10
+ opts[:formatter] = :html
11
+ ARGV.shift
12
+ end
13
+
14
+ t = Todo.new opts
15
+
16
+ args = ARGV.dup
17
+
18
+ if args.size <= 2 && args.delete('!')
19
+
20
+ exec "#{File.expand_path(__FILE__)} #{args.join(' ')} | grep '!'"
21
+ end
22
+
23
+ command = args.shift
24
+ has_args = !args.empty?
25
+
26
+ tag = command && command[/^(@|\+)\S+$/,0]
27
+
28
+ if tag && has_args
29
+ t.ed_command!('a', [Todo.expand_tag(tag)] + args)
30
+ elsif tag
31
+ t.filter Todo.expand_tag(tag)
32
+ elsif command == 'done' && args[0] =~ /^(@|\+)/
33
+ t.filter_done_file Todo.expand_tag(args[0])
34
+ elsif command == 'done'
35
+ t.filter_done_file nil
36
+ elsif command == 'all'
37
+ t.list_all Todo.expand_tag(args[0])
38
+ elsif command == 'do'
39
+ t.mark_done! args[0]
40
+ elsif command == 'undo'
41
+ t.mark_undone! args[0]
42
+ elsif command == 'ls' && args.empty?
43
+ t.report
44
+ elsif command == 'revert' && args.empty?
45
+ t.revert
46
+ elsif command == 'diff' && args.empty?
47
+ t.diff
48
+ elsif command.nil?
49
+ t.catn
50
+ else
51
+ t.ed_command! command, *args
52
+ end
@@ -0,0 +1,39 @@
1
+ require 'yaml'
2
+ require 'color/css'
3
+
4
+ class ColorConfig
5
+ FILES = ["colors.yml", "#{ENV['HOME']}/.todo.rb/colors.yml"]
6
+
7
+ def initialize
8
+ @dict = {
9
+ 'context' => "cyan",
10
+ 'project' => "DC143C",
11
+ 'priority' => 'FFFF00'
12
+ }
13
+ if (file = FILES.detect {|x| File.exist?(x)})
14
+ @dict.merge!(YAML::load(File.read(file)))
15
+ end
16
+ # correct any non hex color names
17
+ @dict.each {|k, v|
18
+ if v !~ /[A-F0-9]{6}/
19
+ c = Color::CSS[v]
20
+ if c
21
+ @dict[k] = c.html
22
+ end
23
+ end
24
+ }
25
+ end
26
+
27
+ def raw(key)
28
+ s = @dict[key].to_s.sub(/^#/, '').upcase
29
+ s.length == 3 ? (s + s) : s
30
+ end
31
+
32
+ def rgb(key)
33
+ @dict[key] && "RGB_" + raw(key).scan(/[A-F0-9]{2}/).join
34
+ end
35
+
36
+ def html(key)
37
+ @dict[key] && "#" + raw(key).scan(/[A-F0-9]{2}/).join
38
+ end
39
+ end
data/lib/colorizer.rb ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), 'color_config')
4
+
5
+ gem 'highline', '>= 1.6.11'
6
+ require 'highline'
7
+
8
+ if ARGV[0] == '--no-color'
9
+ $no_color = true
10
+ ARGV.shift
11
+ elsif ARGV[0] == '--html'
12
+ $html = true
13
+ ARGV.shift
14
+ end
15
+
16
+ HighLine.color_scheme = HighLine::SampleColorScheme.new
17
+ t = HighLine.new(STDIN, STDOUT)
18
+ terr = HighLine.new(STDIN, STDERR)
19
+
20
+ filter = ARGV.first
21
+
22
+ COLORS = ColorConfig.new
23
+
24
+ def colorize s
25
+ return s if $no_color
26
+ # color @contexts and +projects
27
+ s.gsub(/@\S+/) {|m|
28
+ if COLORS.rgb(m)
29
+ "<%= color '#{m}', #{COLORS.rgb(m)} %>"
30
+ else
31
+ "<%= color '#{m}', #{COLORS.rgb('context')} %>"
32
+ end
33
+ }.
34
+ gsub(/\+[\S]+/) {|m|
35
+ if COLORS.rgb(m)
36
+ "<%= color '#{m}', #{COLORS.rgb(m)} %>"
37
+ else
38
+ "<%= color '#{m}', #{COLORS.rgb('project')} %>"
39
+ end
40
+ }
41
+ end
42
+
43
+ def mark_priority s
44
+ return s if $no_color
45
+ return s unless s =~ /!/
46
+ s.chomp!
47
+ erb_re = Regexp.new "<%=.+%>"
48
+ style = COLORS.rgb('priority')
49
+ if s =~ /!!!/
50
+ style = ":blink, #{style}"
51
+ end
52
+ s.split(erb_re).map {|a|
53
+ [a, "<%= color '#{a}', #{style} %>"]
54
+ }.each {|(old, new)|
55
+ s.sub!(old, new)
56
+ }
57
+ s
58
+ end
59
+
60
+
61
+ while STDIN.gets
62
+ next unless $_
63
+ s = $_
64
+ t.say mark_priority(colorize(s))
65
+ end
66
+
data/lib/html.rb ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require File.join(File.dirname(__FILE__), 'color_config')
4
+
5
+ def color_span s, color
6
+ "<span style='color:#{color}'>#{s}</span>"
7
+ end
8
+
9
+ COLORS = ColorConfig.new
10
+
11
+ def colorize s
12
+ s.gsub(/@\S+/) {|m|
13
+ if COLORS.html(m)
14
+ color_span(m, COLORS.html(m))
15
+ else
16
+ color_span(m, COLORS.html('context'))
17
+ end
18
+ }. gsub(/\+[\S]+/) {|m|
19
+ if COLORS.html(m)
20
+ color_span(m, COLORS.html(m))
21
+ else
22
+ color_span(m, COLORS.html('project'))
23
+ end
24
+ }
25
+ end
26
+
27
+ def mark_priority s
28
+ return s if $no_color
29
+ return s unless s =~ /!/
30
+ s.chomp!
31
+ span = Regexp.new "<span.*span>"
32
+ s.split(span).map {|a|
33
+ [a, color_span(a, COLORS.html('priority'))]
34
+ }.each {|(old, new)|
35
+ s.sub!(old, new)
36
+ }
37
+ s
38
+ end
39
+
40
+ puts "<pre style='color:#08FF08;font-family:Andale Mono;background-color:black'>\n\n"
41
+ while STDIN.gets
42
+ next unless $_
43
+ s = $_
44
+ puts mark_priority(colorize(s))
45
+ end
46
+
47
+ puts "\n</pre>"
data/lib/todo.rb ADDED
@@ -0,0 +1,172 @@
1
+
2
+ COLORIZER = File.join(File.dirname(__FILE__), 'colorizer.rb')
3
+ HTML = File.join(File.dirname(__FILE__), 'html.rb')
4
+
5
+ class Todo
6
+
7
+ attr_accessor :todo_file, :done_file, :backup_file, :formatter
8
+
9
+ def initialize(opts={})
10
+
11
+ defaults = {
12
+ todo_file: 'todo.txt',
13
+ done_file: 'done.txt'
14
+ }
15
+ @opts = defaults.merge opts
16
+ @formatter = {
17
+ color: COLORIZER,
18
+ nocolor: "#{COLORIZER} --no-color",
19
+ html: HTML
20
+ }[@opts[:formatter]]
21
+
22
+ @todo_file = @opts[:todo_file]
23
+ @backup_file = ".#{@todo_file}.bkp"
24
+ @done_file = @opts[:done_file]
25
+ make_files
26
+ end
27
+
28
+ def make_files
29
+ [todo_file, done_file].each do |f|
30
+ if !File.exist?(f)
31
+ $stderr.puts "Missing a #{f} file. Creating."
32
+ `touch #{f}`
33
+ end
34
+ end
35
+ end
36
+
37
+ def backup
38
+ `cp #{todo_file} #{backup_file}`
39
+ end
40
+
41
+ def ed_command! command, *input_text
42
+ backup
43
+ text = input_text.empty? ? nil : "\n#{input_text.join(' ')}\n."
44
+ IO.popen("ed -s #{todo_file}", 'w') {|pipe|
45
+ script = <<END
46
+ #{command}#{text}
47
+ wq
48
+ END
49
+ pipe.puts script
50
+ pipe.close
51
+ }
52
+ exec "diff #{backup_file} #{todo_file}"
53
+ end
54
+
55
+ def revert
56
+ return unless File.exist?(backup_file)
57
+ exec <<END
58
+ mv #{todo_file} #{backup_file}.2
59
+ mv #{backup_file} #{todo_file}
60
+ mv #{backup_file}.2 #{backup_file}
61
+ END
62
+ end
63
+
64
+ def diff
65
+ return unless File.exist?(backup_file)
66
+ exec "diff #{backup_file} #{todo_file}"
67
+ end
68
+
69
+ def catn(list_file = todo_file)
70
+ exec <<END
71
+ cat -n #{list_file} | #{formatter}
72
+ END
73
+ end
74
+
75
+ def filter(context_or_project=nil, list_file=todo_file, no_exec=false)
76
+ s = context_or_project
77
+ # don't put /< before the grep arg
78
+ grep_filter = s ? " | grep -i '#{s}\\>' " : ""
79
+ script = <<END
80
+ cat -n #{list_file} #{grep_filter} | #{formatter} #{s ? "'#{s}'" : ''}
81
+ END
82
+ if no_exec
83
+ script
84
+ else
85
+ exec(script)
86
+ end
87
+ end
88
+
89
+ def filter_done_file(t)
90
+ filter t, done_file
91
+ end
92
+
93
+ def list_all tag=nil
94
+ a = filter tag, todo_file, true
95
+ b = filter tag, done_file, true
96
+ exec ["echo 'todo'", a, "echo 'done'", b].join("\n")
97
+ end
98
+
99
+ def mark_done! range
100
+ return unless range =~ /\S/
101
+ backup
102
+ exec <<END
103
+ cat #{todo_file} | sed -n '#{range}p' |
104
+ awk '{print d " " $0}' "d=$(date +'%Y-%m-%d')" >> #{done_file}
105
+ echo "#{range}d\nwq\n" | ed -s #{todo_file}
106
+ diff #{backup_file} #{todo_file}
107
+ END
108
+ end
109
+
110
+ def mark_undone! range
111
+ return unless range =~ /\S/
112
+ backup
113
+ exec <<END
114
+ cat #{done_file} | sed -n '#{range}p' |
115
+ ruby -n -e 'puts $_.split(" ", 2)[1]' >> #{todo_file}
116
+ echo "#{range}d\nwq\n" | ed -s #{done_file}
117
+ diff #{backup_file} #{todo_file}
118
+ END
119
+ end
120
+
121
+ TAG_REGEX = /[@\+]\S+/
122
+
123
+ def report
124
+ report_data = get_report_data
125
+ # count priority items per tag
126
+ File.readlines(todo_file).inject(report_data) {|report_data, line|
127
+ line.scan(TAG_REGEX).each {|tag|
128
+ report_data[tag][:priority] ||= 0
129
+ if line =~ /!/
130
+ report_data[tag][:priority] = report_data[tag][:priority] + 1
131
+ end
132
+ }; report_data
133
+ }
134
+ longest_tag_len = report_data.keys.reduce(0) {|max, key| [max, key.length].max} + 3
135
+ placeholders = "%-#{longest_tag_len}s %8s %8s %8s"
136
+ headers = %w(tag priority todo done)
137
+ IO.popen(formatter, 'w') {|pipe|
138
+ pipe.puts(placeholders % headers)
139
+ pipe.puts placeholders.scan(/\d+/).map {|a|'-'*(a.to_i)}.join(' ')
140
+ report_data.keys.sort_by {|k| k.downcase}.each {|k|
141
+ pipe.puts placeholders % [k, report_data[k][:priority], report_data[k][:todo], report_data[k][:done]]
142
+ }
143
+ }
144
+ end
145
+
146
+ def get_report_data
147
+ [:todo, :done].
148
+ select {|a| File.exist?(send("#{a}_file"))}.
149
+ inject({}) {|m, list|
150
+ file = "#{list}_file"
151
+ File.read(send(file)).scan(TAG_REGEX).group_by {|t| t}.
152
+ map {|k, v|
153
+ m[k] ||= {todo:0,done:0,priority:0}
154
+ m[k][list] = (m[k][list] || 0) + v.size
155
+ }
156
+ m
157
+ }
158
+ end
159
+
160
+ def self.expand_tag(t)
161
+ return unless t
162
+ re = /^#{Regexp.escape(t)}/
163
+ match = new.get_report_data.keys.detect {|key| key =~ re}
164
+ if match && match != t
165
+ match
166
+ else
167
+ t
168
+ end
169
+ end
170
+ end
171
+
172
+
data/todo.rb.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require 'todo'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "todo.rb"
8
+ s.version = "0.0.1"
9
+ s.platform = Gem::Platform::RUBY
10
+ s.required_ruby_version = '>= 1.9.0'
11
+
12
+ s.authors = ["Daniel Choi"]
13
+ s.email = ["dhchoi@gmail.com"]
14
+ s.homepage = "http://github.com/danchoi/gitfinger"
15
+ s.summary = %q{Finger GitHub users}
16
+ s.description = %q{Finger GitHub users}
17
+
18
+ s.rubyforge_project = "gitfinger"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_dependency 'highline', '>= 1.6.11'
26
+ s.add_dependency 'color-tools', '~> 1.3'
27
+ end
data/todotxt/todo.cfg ADDED
@@ -0,0 +1,80 @@
1
+ # === EDIT FILE LOCATIONS BELOW ===
2
+
3
+ # Your todo.txt directory
4
+ #export TODO_DIR="/Users/gina/Documents/todo"
5
+ export TODO_DIR=`dirname "$0"`
6
+
7
+ # Your todo/done/report.txt locations
8
+ export TODO_FILE="$TODO_DIR/todo.txt"
9
+ export DONE_FILE="$TODO_DIR/done.txt"
10
+ export REPORT_FILE="$TODO_DIR/report.txt"
11
+ export TMP_FILE="$TODO_DIR/todo.tmp"
12
+
13
+ # You can customize your actions directory location
14
+ #export TODO_ACTIONS_DIR="$HOME/.todo.actions.d"
15
+
16
+ # == EDIT FILE LOCATIONS ABOVE ===
17
+
18
+ # === COLOR MAP ===
19
+
20
+ ## Text coloring and formatting is done by inserting ANSI escape codes.
21
+ ## If you have re-mapped your color codes, or use the todo.txt
22
+ ## output in another output system (like Conky), you may need to
23
+ ## over-ride by uncommenting and editing these defaults.
24
+ ## If you change any of these here, you also need to uncomment
25
+ ## the defaults in the COLORS section below. Otherwise, todo.txt
26
+ ## will still use the defaults!
27
+
28
+ # export BLACK='\\033[0;30m'
29
+ # export RED='\\033[0;31m'
30
+ # export GREEN='\\033[0;32m'
31
+ # export BROWN='\\033[0;33m'
32
+ # export BLUE='\\033[0;34m'
33
+ # export PURPLE='\\033[0;35m'
34
+ # export CYAN='\\033[0;36m'
35
+ # export LIGHT_GREY='\\033[0;37m'
36
+ # export DARK_GREY='\\033[1;30m'
37
+ # export LIGHT_RED='\\033[1;31m'
38
+ # export LIGHT_GREEN='\\033[1;32m'
39
+ # export YELLOW='\\033[1;33m'
40
+ # export LIGHT_BLUE='\\033[1;34m'
41
+ # export LIGHT_PURPLE='\\033[1;35m'
42
+ # export LIGHT_CYAN='\\033[1;36m'
43
+ # export WHITE='\\033[1;37m'
44
+ # export DEFAULT='\\033[0m'
45
+
46
+ # === COLORS ===
47
+
48
+ ## Uncomment and edit to override these defaults.
49
+ ## Reference the constants from the color map above,
50
+ ## or use $NONE to disable highlighting.
51
+ #
52
+ # Priorities can be any upper-case letter.
53
+ # A,B,C are highlighted; you can add coloring for more.
54
+ #
55
+ # export PRI_A=$YELLOW # color for A priority
56
+ # export PRI_B=$GREEN # color for B priority
57
+ # export PRI_C=$LIGHT_BLUE # color for C priority
58
+ # export PRI_D=... # define your own
59
+ # export PRI_X=$WHITE # color unless explicitly defined
60
+
61
+ # There is highlighting for tasks that have been done,
62
+ # but haven't been archived yet.
63
+ #
64
+ # export COLOR_DONE=$LIGHT_GREY
65
+
66
+ # === BEHAVIOR ===
67
+
68
+ ## customize list output
69
+ #
70
+ # TODOTXT_SORT_COMMAND will filter after line numbers are
71
+ # inserted, but before colorization, and before hiding of
72
+ # priority, context, and project.
73
+ #
74
+ # export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2'
75
+
76
+ # TODOTXT_FINAL_FILTER will filter list output after colorization,
77
+ # priority hiding, context hiding, and project hiding. That is,
78
+ # just before the list output is displayed.
79
+ #
80
+ # export TODOTXT_FINAL_FILTER='cat'