plan 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/plan'
4
+ Plan::CLI.run(ARGV)
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/plan/version'
2
+ require File.dirname(__FILE__) + '/plan/advice'
3
+ require File.dirname(__FILE__) + '/plan/item'
4
+
5
+ module Plan
6
+ autoload :CLI, File.dirname(__FILE__) + '/plan/cli'
7
+ end
@@ -0,0 +1,17 @@
1
+ module Plan
2
+
3
+ class Advice < Exception
4
+
5
+ attr_reader :lines
6
+
7
+ def initialize(*lines)
8
+ @lines = lines
9
+ end
10
+
11
+ def message
12
+ @lines.join("\n")
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,168 @@
1
+ require 'json'
2
+
3
+ module Plan
4
+
5
+ class CLI
6
+
7
+ class << self
8
+
9
+ def run(args)
10
+ begin
11
+ command args.first, args[1..-1]
12
+ rescue Plan::Advice => e
13
+ e.lines.each do |line|
14
+ puts "\e[31m[uh-oh]\e[0m #{line}"
15
+ end
16
+ end
17
+ end
18
+
19
+ # decide what to do
20
+ def command(command, paths)
21
+ # default is list
22
+ return list([]) if command.nil?
23
+ # choose other command
24
+ case command
25
+ when 'create' then create paths
26
+ when 'list' then list paths
27
+ when 'finish' then finish paths
28
+ when 'unfinish' then unfinish paths
29
+ when 'cleanup' then cleanup paths
30
+ when 'help' then help
31
+ else unknown_command(command)
32
+ end
33
+ end
34
+
35
+ COMMAND_GLOSSARY = {
36
+ 'create' => 'create a new item',
37
+ 'list' => 'list items',
38
+ 'finish' => 'mark an item finished',
39
+ 'unfinish' => 'mark an item unfinished',
40
+ 'cleanup' => 'remove finished items from view',
41
+ 'help' => 'display a list of commands'
42
+ }
43
+
44
+ # display a list of help
45
+ def help
46
+ puts "plan #{Plan::VERSION} - john crepezzi - http://github.com/seejohnrun/plan"
47
+ COMMAND_GLOSSARY.each do |cmd, description|
48
+ puts "\e[0;33m#{cmd}\e[0m - #{description}"
49
+ end
50
+ end
51
+
52
+ # Remove all finished items that are descendents
53
+ def cleanup(paths)
54
+ item = path_tree.descend(paths)
55
+ item.cleanup
56
+ save_path_tree
57
+ # print what happened here
58
+ print_depth item
59
+ end
60
+
61
+ # Mark a task or group of tasks as "unfinished"
62
+ def unfinish(paths)
63
+ if paths.empty?
64
+ raise Plan::Advice.new 'please drill down to a level to unfinish'
65
+ end
66
+ # go to the right depth and unfinish
67
+ item = path_tree.descend(paths)
68
+ item.unfinish!
69
+ save_path_tree
70
+ # print what happened here
71
+ print_depth item
72
+ end
73
+
74
+ # Mark a task or group of tasks as "finished"
75
+ def finish(paths)
76
+ if paths.empty?
77
+ raise Plan::Advice.new 'please drill down to a level to finish'
78
+ end
79
+ # descend and finish
80
+ item = path_tree.descend(paths)
81
+ item.finish!
82
+ save_path_tree
83
+ # print what happened here
84
+ print_depth item
85
+ end
86
+
87
+ # list things at a certain depth
88
+ def list(paths)
89
+ item = path_tree.descend(paths)
90
+ if item.visible_child_count == 0
91
+ raise Plan::Advice.new 'no events here - create some with `plan create`'
92
+ end
93
+ print_depth item
94
+ end
95
+
96
+ # create a new todo
97
+ def create(paths)
98
+ if paths.empty?
99
+ raise Plan::Advice.new 'please provide something to create'
100
+ end
101
+ # descend to the right depth
102
+ item = path_tree.descend(paths[0..-2])
103
+ # and then create
104
+ if item.children.any? { |c| !c.hidden? && c.has_label?(paths[-1]) }
105
+ raise Plan::Advice.new "duplicate entry at level: #{paths[-1]}"
106
+ else
107
+ item.children << Item.new(paths[-1])
108
+ save_path_tree
109
+ # and say what happened
110
+ print_depth item
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ DATA_STORE = ENV['PLAN_DATA_PATH'] || "#{ENV['HOME']}/plan"
117
+ DATE_FORMAT = '%b. %e, %Y %l:%M %P'
118
+
119
+ def unknown_command(cmd)
120
+ raise Plan::Advice.new "unknown command: #{cmd}. try `plan help` for options."
121
+ end
122
+
123
+ # print the item and its descendents
124
+ def print_depth(item)
125
+ return if item.hidden?
126
+ print_item item, 0
127
+ list_recur_print item, 2
128
+ end
129
+
130
+ # Used by #print_depth to print its tree
131
+ def list_recur_print(item, desc = 0)
132
+ item.children.each do |child|
133
+ next if child.hidden?
134
+ print_item child, desc
135
+ list_recur_print(child, desc + 2)
136
+ end
137
+ end
138
+
139
+ # output an individual item
140
+ def print_item(item, desc = 0)
141
+ if item.finished?
142
+ puts "\e[1;30m#{'-' * desc}#{desc > 0 ? " #{item.label}" : item.label} \e[1;31m(finished @ #{item.finished.strftime(DATE_FORMAT)})\e[0m"
143
+ else
144
+ puts "\e[1;30m#{'-' * desc}\e[0m#{desc > 0 ? " #{item.label}" : item.label}"
145
+ end
146
+ end
147
+
148
+ # Save any changes to the tree
149
+ def save_path_tree
150
+ file = File.open(DATA_STORE, 'w')
151
+ file.write path_tree.dump.to_json
152
+ file.close
153
+ end
154
+
155
+ # Get the path tree from the data file
156
+ def path_tree
157
+ @path_tree ||= if File.exists?(DATA_STORE)
158
+ Item.load JSON.parse(File.read(DATA_STORE))
159
+ else
160
+ Item.new 'plan'
161
+ end
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+
168
+ end
@@ -0,0 +1,114 @@
1
+ module Plan
2
+
3
+ class Item
4
+
5
+ attr_reader :label, :finished, :children
6
+
7
+ # Create a new item
8
+ def initialize(label, finished = nil, hidden = nil)
9
+ @label = label.strip
10
+ @finished = finished.is_a?(Fixnum) ? Time.at(finished) : nil
11
+ @hidden = hidden
12
+ @children = []
13
+ end
14
+
15
+ # determine whether a label is a duplicate of this item's label
16
+ # downcases to get rid of basic mistakes
17
+ def has_label?(other_label)
18
+ label.downcase == other_label.downcase
19
+ end
20
+
21
+ # for fuzzy matching on descending - so you can do things like
22
+ # todo create so hi - instead of
23
+ # todo create something hi
24
+ def has_label_like?(other_label)
25
+ label.downcase.include? other_label.downcase
26
+ end
27
+
28
+ # count of the visible children
29
+ def visible_child_count
30
+ children.count { |c| !c.hidden? }
31
+ end
32
+
33
+ # remove all finished items from this tree, by hiding them
34
+ def cleanup
35
+ @hidden = true if finished?
36
+ children.each { |c| c.cleanup }
37
+ end
38
+
39
+ # mark a finish date for this item
40
+ def finish!(at = Time.now)
41
+ @finished = at unless finished? # don't overwrite
42
+ children.each { |c| c.finish!(at) unless c.hidden? } # and finish each child
43
+ end
44
+
45
+ # mark a finished item as unfinished
46
+ # and all descendents
47
+ def unfinish!
48
+ @finished = nil
49
+ children.each { |c| c.unfinish! unless c.hidden? }
50
+ end
51
+
52
+ # return a boolean indicating whether or not this item is hidden
53
+ def hidden?
54
+ !!@hidden
55
+ end
56
+
57
+ # return whether this item is finished
58
+ def finished?
59
+ !!@finished && children.all? { |c| c.finished? || c.hidden? }
60
+ end
61
+
62
+ # recursively descent through children until you find the given item
63
+ def descend(paths)
64
+ return self if paths.empty?
65
+ # prefer exact matches
66
+ next_items = children.select { |c| !c.hidden? && c.has_label?(paths.first) }
67
+ # fall back on approximates
68
+ if next_items.empty?
69
+ next_items = children.select { |c| !c.hidden? && c.has_label_like?(paths.first) }
70
+ end
71
+ # give an error if we have no matches
72
+ if next_items.empty?
73
+ lines = []
74
+ lines << "no match for #{paths.first}"
75
+ unless children.empty?
76
+ lines << "available options are: #{children.reject(&:hidden?).map(&:label).join(', ')}"
77
+ end
78
+ raise Plan::Advice.new *lines
79
+ end
80
+ # give an error if we have too many matches
81
+ if next_items.count > 1
82
+ lines = []
83
+ lines << "ambiguous match for '#{paths.first}' - please choose one of:"
84
+ next_items.each do |np|
85
+ lines << "* #{np.label}"
86
+ end
87
+ raise Plan::Advice.new *lines
88
+ end
89
+ # and off we go, continuing to descent
90
+ next_items.first.descend(paths[1..-1])
91
+ end
92
+
93
+ # Load a Item from a data hash
94
+ def self.load(data)
95
+ item = Item.new data['label'], data['finished'], data['hidden']
96
+ data['children'] && data['children'].each do |child|
97
+ item.children << Item.load(child)
98
+ end
99
+ item
100
+ end
101
+
102
+ # dump a nested representation of this item
103
+ def dump
104
+ data = {}
105
+ data['label'] = label
106
+ data['finished'] = finished.nil? ? nil : finished.to_i
107
+ data['children'] = children.map(&:dump)
108
+ data['hidden'] = true if hidden?
109
+ data
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,5 @@
1
+ module Plan
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../lib/plan'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plan
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Crepezzi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-27 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70151867057320 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70151867057320
25
+ description: plan is a simple command-line todo-list manager
26
+ email: john.crepezzi@gmail.com
27
+ executables:
28
+ - plan
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/plan/advice.rb
33
+ - lib/plan/cli.rb
34
+ - lib/plan/item.rb
35
+ - lib/plan/version.rb
36
+ - lib/plan.rb
37
+ - bin/plan
38
+ - spec/spec_helper.rb
39
+ homepage: http://github.com/seejohnrun/plan
40
+ licenses: []
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project: plan
59
+ rubygems_version: 1.8.10
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: simple command-line todo-list manager
63
+ test_files:
64
+ - spec/spec_helper.rb