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 CHANGED
@@ -48,7 +48,7 @@ pre do |global,command,options,args|
48
48
  Gwtf::Items.setup(project_dir)
49
49
  end
50
50
 
51
- @items = Gwtf::Items.new(project_dir)
51
+ @items = Gwtf::Items.new(project_dir, global[:project])
52
52
 
53
53
  true
54
54
  end
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
- count = {"open" => 0, "closed" => 0}
16
+ if options[:summary]
17
+ projects = Gwtf.projects(global_options[:data])
18
+ longest_name = projects.map{|p| p.length}.max
9
19
 
10
- @items.each_item do |item|
11
- count[ item[:status] ] += 1
20
+ projects.each_with_index do |project, idx|
21
+ puts "Items in all projects:\n\n" if idx == 0
12
22
 
13
- flags = []
14
- flags << "D" if item.has_description?
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
- puts "%5s %-4s%-12s%8s" % [ item.item_id, flags.join, Time.parse(item.created_at).strftime("%F"), item.subject ] if (options[:all] || item.open?)
18
- end
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
- puts
21
- puts "Project %s items: %d / %d" % [ global_options[:project], count["open"], count["open"] + count["closed"] ]
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 = args.join(" ")
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
- system "echo gwtf --project='%s' remind --recipient='%s' --send %s|at %s" % [ global_options[:project], options[:recipient], args.first, at]
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
- begin
38
- tmp = Tempfile.new("gwtf")
39
- tmp.write(item.summary)
40
- tmp.rewind
44
+ unless options[:ifopen] && item.closed?
45
+ begin
46
+ tmp = Tempfile.new("gwtf")
47
+ tmp.write(item.summary)
48
+ tmp.rewind
41
49
 
42
- if global_options[:project] == "default"
43
- subject = "Reminder for item %s" % [ args.first ]
44
- else
45
- subject = "Reminder for item %s in %s project" % [ args.first, global_options[:project] ]
46
- end
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
- system("cat #{tmp.path}|mail -s '#{subject}' '#{options[:recipient]}'")
49
- ensure
50
- tmp.close
51
- tmp.unlink
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
- flag = " (#{flags.join(',')})" unless flags.empty?
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
@@ -1,3 +1,3 @@
1
1
  module Gwtf
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
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: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.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-12 00:00:00 +00:00
18
+ date: 2012-03-13 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency