todo.rb 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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'