gwtf 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/gwtf +1 -1
- data/lib/gwtf.rb +9 -0
- data/lib/gwtf/commands/list_command.rb +31 -10
- data/lib/gwtf/commands/new_command.rb +28 -1
- data/lib/gwtf/commands/remind_command.rb +31 -18
- data/lib/gwtf/commands/shell_command.rb +5 -1
- data/lib/gwtf/item.rb +22 -4
- data/lib/gwtf/items.rb +45 -3
- data/lib/gwtf/version.rb +1 -1
- metadata +4 -4
data/bin/gwtf
CHANGED
data/lib/gwtf.rb
CHANGED
@@ -14,6 +14,15 @@ module Gwtf
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
def self.projects(root_dir)
|
18
|
+
Dir.entries(root_dir).map do |entry|
|
19
|
+
next if entry =~ /^\./
|
20
|
+
next unless File.directory?(File.join(root_dir, entry))
|
21
|
+
|
22
|
+
entry
|
23
|
+
end.compact.sort
|
24
|
+
end
|
25
|
+
|
17
26
|
# borrowed from ohai, thanks Adam.
|
18
27
|
def self.seconds_to_human(seconds)
|
19
28
|
days = seconds.to_i / 86400
|
@@ -4,21 +4,42 @@ command [:list, :ls, :l] do |c|
|
|
4
4
|
c.default_value false
|
5
5
|
c.switch [:all, :a]
|
6
6
|
|
7
|
+
c.desc 'Show a short summary for all projects'
|
8
|
+
c.default_value false
|
9
|
+
c.switch [:summary, :s]
|
10
|
+
|
11
|
+
c.desc 'Show a overview for all projects'
|
12
|
+
c.default_value false
|
13
|
+
c.switch [:overview, :o]
|
14
|
+
|
7
15
|
c.action do |global_options,options,args|
|
8
|
-
|
16
|
+
if options[:summary]
|
17
|
+
projects = Gwtf.projects(global_options[:data])
|
18
|
+
longest_name = projects.map{|p| p.length}.max
|
9
19
|
|
10
|
-
|
11
|
-
|
20
|
+
projects.each_with_index do |project, idx|
|
21
|
+
puts "Items in all projects:\n\n" if idx == 0
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
flags << "C" if item.closed? && options[:all]
|
23
|
+
items = Gwtf::Items.new(File.join(global_options[:data], project), project)
|
24
|
+
stats = items.stats
|
16
25
|
|
17
|
-
|
18
|
-
|
26
|
+
puts "%#{longest_name + 3}s: open: %3d: closed %3d: total: %3d" % [ project, stats["open"], stats["closed"], stats["total"] ] unless stats["total"] == 0
|
27
|
+
|
28
|
+
end
|
19
29
|
|
20
|
-
|
21
|
-
|
30
|
+
puts
|
31
|
+
elsif options[:overview]
|
32
|
+
Gwtf.projects(global_options[:data]).each do |project|
|
33
|
+
items = Gwtf::Items.new(File.join(global_options[:data], project), project)
|
34
|
+
|
35
|
+
puts items.list_text(options[:all]) if items.item_ids.size > 0
|
36
|
+
puts
|
37
|
+
end
|
38
|
+
else
|
39
|
+
puts
|
40
|
+
puts @items.list_text(options[:all])
|
41
|
+
puts
|
42
|
+
end
|
22
43
|
end
|
23
44
|
end
|
24
45
|
|
@@ -5,7 +5,26 @@ command [:new, :add, :n, :a, :c] do |c|
|
|
5
5
|
c.default_value false
|
6
6
|
c.switch [:edit, :e]
|
7
7
|
|
8
|
+
c.desc 'Schedule a reminder about this task'
|
9
|
+
c.default_value false
|
10
|
+
c.flag [:remind]
|
11
|
+
|
12
|
+
c.desc 'Mark item done after sending reminder'
|
13
|
+
c.default_value false
|
14
|
+
c.switch [:done]
|
15
|
+
|
16
|
+
c.desc 'Only alert if the item is still marked open'
|
17
|
+
c.default_value false
|
18
|
+
c.switch [:ifopen]
|
19
|
+
|
20
|
+
c.desc 'Email address to send to'
|
21
|
+
c.default_value Etc.getlogin
|
22
|
+
c.flag [:recipient, :r]
|
23
|
+
|
8
24
|
c.action do |global_options,options,args|
|
25
|
+
subject = args.join(" ")
|
26
|
+
raise "Please supply a short desciption for the item on the command line" if subject == ""
|
27
|
+
|
9
28
|
if options[:edit]
|
10
29
|
raise "EDITOR is not set" unless ENV.include?("EDITOR")
|
11
30
|
|
@@ -22,10 +41,18 @@ command [:new, :add, :n, :a, :c] do |c|
|
|
22
41
|
end
|
23
42
|
|
24
43
|
item = @items.new_item
|
25
|
-
item.subject =
|
44
|
+
item.subject = subject
|
26
45
|
item.description = description if description
|
27
46
|
item.save
|
28
47
|
|
48
|
+
if options[:remind]
|
49
|
+
STDOUT.sync
|
50
|
+
|
51
|
+
print "Creating reminder at job for item #{item.item_id}: "
|
52
|
+
|
53
|
+
item.schedule_reminer(options[:remind], options[:recipient], options[:done], options[:ifopen])
|
54
|
+
end
|
55
|
+
|
29
56
|
puts item
|
30
57
|
end
|
31
58
|
end
|
@@ -19,39 +19,52 @@ command [:remind, :rem] do |c|
|
|
19
19
|
c.default_value false
|
20
20
|
c.switch [:send]
|
21
21
|
|
22
|
+
c.desc 'Mark item done after sending reminder'
|
23
|
+
c.default_value false
|
24
|
+
c.switch [:done]
|
25
|
+
|
26
|
+
c.desc 'Only alert if the item is still marked open'
|
27
|
+
c.default_value false
|
28
|
+
c.switch [:ifopen]
|
29
|
+
|
22
30
|
c.action do |global_options,options,args|
|
23
31
|
raise "Please supply an item ID to remind about" if args.empty?
|
24
32
|
|
25
33
|
unless options[:send]
|
26
34
|
raise "Please specify a valid at() time specification" unless args.size > 2
|
27
35
|
|
28
|
-
at = args[1..-1].join(" ")
|
29
|
-
|
30
36
|
STDOUT.sync
|
31
37
|
|
32
38
|
print "Creating reminder at job for item #{args.first}: "
|
33
|
-
|
39
|
+
|
40
|
+
@items.load_item(args.first).schedule_reminer(args[1..-1].join(" "), options[:recipient], options[:done], options[:ifopen])
|
34
41
|
else
|
35
42
|
item = @items.load_item(args.first)
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
unless options[:ifopen] && item.closed?
|
45
|
+
begin
|
46
|
+
tmp = Tempfile.new("gwtf")
|
47
|
+
tmp.write(item.summary)
|
48
|
+
tmp.rewind
|
41
49
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
50
|
+
if global_options[:project] == "default"
|
51
|
+
subject = "Reminder for item %s" % [ args.first ]
|
52
|
+
else
|
53
|
+
subject = "Reminder for item %s in %s project" % [ args.first, global_options[:project] ]
|
54
|
+
end
|
55
|
+
|
56
|
+
system("cat #{tmp.path} | mail -s '#{subject}' '#{options[:recipient]}'")
|
47
57
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
58
|
+
if options[:done]
|
59
|
+
item.record_work("Closing item as part of scheduled reminder")
|
60
|
+
item.close
|
61
|
+
item.save
|
62
|
+
end
|
63
|
+
ensure
|
64
|
+
tmp.close
|
65
|
+
tmp.unlink
|
66
|
+
end
|
52
67
|
end
|
53
68
|
end
|
54
69
|
end
|
55
70
|
end
|
56
|
-
|
57
|
-
|
@@ -20,10 +20,14 @@ command :shell do |c|
|
|
20
20
|
|
21
21
|
STDOUT.sync = true
|
22
22
|
|
23
|
-
print "Optional description for work log: "
|
23
|
+
print "Optional description for work log (start with done! to close): "
|
24
24
|
|
25
25
|
begin
|
26
26
|
description = STDIN.gets.chomp
|
27
|
+
if description =~ /^(done|close)!\s*(.+)/
|
28
|
+
description = $1
|
29
|
+
item.close
|
30
|
+
end
|
27
31
|
rescue Exception
|
28
32
|
puts
|
29
33
|
description = ""
|
data/lib/gwtf/item.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module Gwtf
|
2
2
|
class Item
|
3
3
|
attr_accessor :file
|
4
|
+
attr_reader :project
|
4
5
|
|
5
|
-
def initialize(file=nil)
|
6
|
+
def initialize(file=nil, project=nil)
|
6
7
|
@item = default_item
|
7
8
|
@file = file
|
9
|
+
@project = project
|
8
10
|
|
9
11
|
load_item if file
|
10
12
|
end
|
@@ -76,6 +78,15 @@ module Gwtf
|
|
76
78
|
flag
|
77
79
|
end
|
78
80
|
|
81
|
+
def compact_flags
|
82
|
+
flags = []
|
83
|
+
flags << "D" if has_description?
|
84
|
+
flags << "C" if closed?
|
85
|
+
flags << "L" unless work_log.empty?
|
86
|
+
|
87
|
+
flags
|
88
|
+
end
|
89
|
+
|
79
90
|
def summary
|
80
91
|
summary = StringIO.new
|
81
92
|
|
@@ -113,9 +124,7 @@ module Gwtf
|
|
113
124
|
end
|
114
125
|
|
115
126
|
def to_s
|
116
|
-
|
117
|
-
|
118
|
-
"%s%s: %s" % [item_id, flag, subject]
|
127
|
+
"%5s %-4s%-12s%8s" % [ item_id, compact_flags.join, Time.parse(created_at.to_s).strftime("%F"), subject ]
|
119
128
|
end
|
120
129
|
|
121
130
|
def to_hash
|
@@ -167,6 +176,15 @@ module Gwtf
|
|
167
176
|
update_property(property, value)
|
168
177
|
end
|
169
178
|
|
179
|
+
def schedule_reminer(timespec, recipient, done=false, ifopen=false)
|
180
|
+
command_args = ["--send"]
|
181
|
+
command_args << "--recipient=%s" % [ recipient ]
|
182
|
+
command_args << "--done" if done
|
183
|
+
command_args << "--ifopen" if ifopen
|
184
|
+
|
185
|
+
system "echo gwtf --project='%s' remind %s %s | at %s" % [ @project, command_args.join(" "), item_id, timespec]
|
186
|
+
end
|
187
|
+
|
170
188
|
# simple read from the class:
|
171
189
|
#
|
172
190
|
# >> i.description
|
data/lib/gwtf/items.rb
CHANGED
@@ -20,15 +20,18 @@ module Gwtf
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def initialize(data_dir)
|
23
|
+
def initialize(data_dir, project)
|
24
24
|
raise "Data directory #{data_dir} does not exist" unless File.directory?(data_dir)
|
25
25
|
|
26
|
+
@project = project
|
26
27
|
@data_dir = data_dir
|
27
28
|
@config = read_config
|
28
29
|
end
|
29
30
|
|
31
|
+
# Creates a new blank item with the next available ID, saves it and saves
|
32
|
+
# the global config with the next id that should be assigned
|
30
33
|
def new_item
|
31
|
-
item = Item.new
|
34
|
+
item = Item.new(nil, @project)
|
32
35
|
item.item_id = @config["next_item"]
|
33
36
|
item.file = File.join(@data_dir, "#{item.item_id}.gwtf")
|
34
37
|
|
@@ -38,16 +41,19 @@ module Gwtf
|
|
38
41
|
item
|
39
42
|
end
|
40
43
|
|
44
|
+
# Loads an item from disk
|
41
45
|
def load_item(item)
|
42
46
|
raise "Item #{item} does not exist" unless File.exist?(file_for_item(item))
|
43
47
|
|
44
|
-
Item.new(file_for_item(item))
|
48
|
+
Item.new(file_for_item(item), @project)
|
45
49
|
end
|
46
50
|
|
51
|
+
# Reads the overall gwtf config file
|
47
52
|
def read_config
|
48
53
|
JSON.parse(File.read(Items.config_file(@data_dir)))
|
49
54
|
end
|
50
55
|
|
56
|
+
# Saves the overall gwtf config
|
51
57
|
def save_config
|
52
58
|
raise "Config has not been loaded" unless @config
|
53
59
|
|
@@ -56,16 +62,52 @@ module Gwtf
|
|
56
62
|
end
|
57
63
|
end
|
58
64
|
|
65
|
+
# Array of integer IDs that belong to this project in ascending order
|
59
66
|
def item_ids
|
60
67
|
Dir.entries(@data_dir).grep(/\.gwtf$/).map{|i| File.basename(i, ".gwtf")}.map{|i| Integer(i)}.sort
|
61
68
|
end
|
62
69
|
|
70
|
+
# Returns the path where the JSON data for a item might be found in the current project
|
63
71
|
def file_for_item(item)
|
64
72
|
File.join(@data_dir, "#{item}.gwtf")
|
65
73
|
end
|
66
74
|
|
75
|
+
# Iterates over all items in a project
|
67
76
|
def each_item
|
68
77
|
item_ids.each {|item| yield load_item(item) }
|
69
78
|
end
|
79
|
+
|
80
|
+
# Returns an array of total, open and closed items for the current project
|
81
|
+
def stats
|
82
|
+
count = {"open" => 0, "closed" => 0}
|
83
|
+
|
84
|
+
each_item do |item|
|
85
|
+
count[ item.status ] += 1
|
86
|
+
end
|
87
|
+
|
88
|
+
count["total"] = count["open"] + count["closed"]
|
89
|
+
|
90
|
+
count
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a blob of text that represents a list of items in a project
|
94
|
+
def list_text(all=false)
|
95
|
+
list = StringIO.new
|
96
|
+
items = StringIO.new
|
97
|
+
|
98
|
+
count = {"open" => 0, "closed" => 0}
|
99
|
+
|
100
|
+
each_item do |item|
|
101
|
+
count[ item[:status] ] += 1
|
102
|
+
|
103
|
+
items.puts item if (all || item.open?)
|
104
|
+
end
|
105
|
+
|
106
|
+
list.puts "Project %s items: %d / %d" % [ @project, count["open"], count["open"] + count["closed"] ]
|
107
|
+
list.puts
|
108
|
+
list.puts items.string
|
109
|
+
|
110
|
+
list.string
|
111
|
+
end
|
70
112
|
end
|
71
113
|
end
|
data/lib/gwtf/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gwtf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- R.I.Pienaar
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-03-
|
18
|
+
date: 2012-03-13 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|