gwtf 0.2.0 → 0.3.0
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/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
|