gwtf 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/bin/gwtf +68 -0
- data/lib/gwtf.rb +40 -0
- data/lib/gwtf/commands/done_command.rb +13 -0
- data/lib/gwtf/commands/edit_command.rb +35 -0
- data/lib/gwtf/commands/list_command.rb +24 -0
- data/lib/gwtf/commands/log_command.rb +36 -0
- data/lib/gwtf/commands/new_command.rb +31 -0
- data/lib/gwtf/commands/open_command.rb +18 -0
- data/lib/gwtf/commands/shell_command.rb +43 -0
- data/lib/gwtf/commands/show_command.rb +44 -0
- data/lib/gwtf/item.rb +157 -0
- data/lib/gwtf/items.rb +71 -0
- data/lib/gwtf/version.rb +3 -0
- metadata +107 -0
data/bin/gwtf
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# 1.9 adds realpath to resolve symlinks; 1.8 doesn't
|
|
3
|
+
# have this method, so we add it so we get resolved symlinks
|
|
4
|
+
# and compatibility
|
|
5
|
+
unless File.respond_to? :realpath
|
|
6
|
+
class File #:nodoc:
|
|
7
|
+
def self.realpath path
|
|
8
|
+
return realpath(File.readlink(path)) if symlink?(path)
|
|
9
|
+
path
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
$: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
|
14
|
+
|
|
15
|
+
require 'rubygems'
|
|
16
|
+
require 'gli'
|
|
17
|
+
require 'gwtf'
|
|
18
|
+
|
|
19
|
+
include GLI
|
|
20
|
+
|
|
21
|
+
program_desc 'Go With The Flow'
|
|
22
|
+
|
|
23
|
+
version Gwtf::VERSION
|
|
24
|
+
|
|
25
|
+
config_file '.gwtf'
|
|
26
|
+
|
|
27
|
+
desc 'Path to storage directory'
|
|
28
|
+
long_desc "Where to store the database of items"
|
|
29
|
+
default_value File.join(Etc.getpwuid.dir, ".gwtf.d")
|
|
30
|
+
arg_name "data_dir"
|
|
31
|
+
flag [:data, :d]
|
|
32
|
+
|
|
33
|
+
desc 'Active project'
|
|
34
|
+
long_desc "Change the active project"
|
|
35
|
+
default_value "default"
|
|
36
|
+
arg_name "project"
|
|
37
|
+
flag [:project, :p]
|
|
38
|
+
|
|
39
|
+
Gwtf.each_command do |command|
|
|
40
|
+
load command
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
pre do |global,command,options,args|
|
|
44
|
+
project_dir = File.join(global[:data], global[:project])
|
|
45
|
+
|
|
46
|
+
unless File.directory?(project_dir)
|
|
47
|
+
puts "Created a new project %s in %s" % [global[:project], project_dir]
|
|
48
|
+
Gwtf::Items.setup(project_dir)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@items = Gwtf::Items.new(project_dir)
|
|
52
|
+
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
post do |global,command,options,args|
|
|
57
|
+
# Post logic here
|
|
58
|
+
# Use skips_post before a command to skip this
|
|
59
|
+
# block on that command only
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
on_error do |exception|
|
|
63
|
+
# Error logic here
|
|
64
|
+
# return false to skip default error handling
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
exit GLI.run(ARGV)
|
data/lib/gwtf.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Gwtf
|
|
2
|
+
require 'gwtf/items'
|
|
3
|
+
require 'gwtf/item'
|
|
4
|
+
require 'gwtf/version'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
require 'tempfile'
|
|
9
|
+
|
|
10
|
+
def self.each_command
|
|
11
|
+
commands_dir = File.join(File.dirname(__FILE__), "gwtf", "commands")
|
|
12
|
+
Dir.entries(commands_dir).grep(/_command.rb$/).sort.each do |command|
|
|
13
|
+
yield File.join(commands_dir, command)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# borrowed from ohai, thanks Adam.
|
|
18
|
+
def self.seconds_to_human(seconds)
|
|
19
|
+
days = seconds.to_i / 86400
|
|
20
|
+
seconds -= 86400 * days
|
|
21
|
+
|
|
22
|
+
hours = seconds.to_i / 3600
|
|
23
|
+
seconds -= 3600 * hours
|
|
24
|
+
|
|
25
|
+
minutes = seconds.to_i / 60
|
|
26
|
+
seconds -= 60 * minutes
|
|
27
|
+
|
|
28
|
+
if days > 1
|
|
29
|
+
return sprintf("%d days %d hours %d minutes %d seconds", days, hours, minutes, seconds)
|
|
30
|
+
elsif days == 1
|
|
31
|
+
return sprintf("%d day %d hours %d minutes %d seconds", days, hours, minutes, seconds)
|
|
32
|
+
elsif hours > 0
|
|
33
|
+
return sprintf("%d hours %d minutes %d seconds", hours, minutes, seconds)
|
|
34
|
+
elsif minutes > 0
|
|
35
|
+
return sprintf("%d minutes %d seconds", minutes, seconds)
|
|
36
|
+
else
|
|
37
|
+
return sprintf("%d seconds", seconds)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
desc 'Mark an item as done'
|
|
2
|
+
arg_name 'Item id'
|
|
3
|
+
command [:done, :d] do |c|
|
|
4
|
+
c.action do |global_options,options,args|
|
|
5
|
+
raise "Please specify an item ID to mark as done" if args.empty?
|
|
6
|
+
|
|
7
|
+
item = @items.load_item(args.first)
|
|
8
|
+
item.close
|
|
9
|
+
item.save
|
|
10
|
+
|
|
11
|
+
puts "Marked item #{args.first} as done"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
desc 'Edit an item using EDITOR'
|
|
2
|
+
arg_name 'Item id'
|
|
3
|
+
command [:edit, :vi, :e] do |c|
|
|
4
|
+
c.action do |global_options,options,args|
|
|
5
|
+
raise "Please specify an item ID to edit" if args.empty?
|
|
6
|
+
raise "EDITOR environment variable should be set" unless ENV.include?("EDITOR")
|
|
7
|
+
|
|
8
|
+
item = @items.load_item(args.first)
|
|
9
|
+
|
|
10
|
+
descr_sep = "== EDIT BETWEEN THESE LINES =="
|
|
11
|
+
|
|
12
|
+
temp_item = {"description" => "#{descr_sep}\n#{item.description}\n#{descr_sep}", "subject" => item.subject}
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
tmp = Tempfile.new("gwtf")
|
|
16
|
+
tmp.write(temp_item.to_yaml)
|
|
17
|
+
tmp.rewind
|
|
18
|
+
system("%s %s" % [ENV["EDITOR"], tmp.path])
|
|
19
|
+
edited_item = YAML.load_file(tmp.path)
|
|
20
|
+
ensure
|
|
21
|
+
tmp.close
|
|
22
|
+
tmp.unlink
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
item.subject = edited_item["subject"] if edited_item["subject"]
|
|
26
|
+
|
|
27
|
+
if edited_item["description"] =~ /#{descr_sep}\n(.+)\n#{descr_sep}/m
|
|
28
|
+
item.description = $1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
item.save
|
|
32
|
+
|
|
33
|
+
puts "Item #{item.item_id} has been saved"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
desc 'List active items'
|
|
2
|
+
command [:list, :ls, :l] do |c|
|
|
3
|
+
c.desc 'Also show closed items'
|
|
4
|
+
c.default_value false
|
|
5
|
+
c.switch [:all, :a]
|
|
6
|
+
|
|
7
|
+
c.action do |global_options,options,args|
|
|
8
|
+
count = {"open" => 0, "closed" => 0}
|
|
9
|
+
|
|
10
|
+
@items.each_item do |item|
|
|
11
|
+
count[ item[:status] ] += 1
|
|
12
|
+
|
|
13
|
+
item.has_description ? id = "*#{item.item_id.to_s}" : id = item.item_id.to_s
|
|
14
|
+
|
|
15
|
+
puts "%5s %-3s%8s" % [ id, "", item.subject] if item.open?
|
|
16
|
+
puts "%5s %-3s%8s" % [ id, "C", item.subject] if (item.closed? && options[:all])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
puts
|
|
20
|
+
puts "Items: %d / %d" % [count["open"], count["open"] + count["closed"]]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
desc 'Log work performed against an item'
|
|
2
|
+
arg_name 'id log text'
|
|
3
|
+
command :log do |c|
|
|
4
|
+
c.desc 'Days spent'
|
|
5
|
+
c.default_value 0
|
|
6
|
+
c.flag [:days, :d]
|
|
7
|
+
|
|
8
|
+
c.desc 'Hours spent'
|
|
9
|
+
c.default_value 0
|
|
10
|
+
c.flag [:hour, :h]
|
|
11
|
+
|
|
12
|
+
c.desc 'Minutes spent'
|
|
13
|
+
c.default_value 0
|
|
14
|
+
c.flag [:min, :m]
|
|
15
|
+
|
|
16
|
+
c.action do |global_options,options,args|
|
|
17
|
+
raise "Please specify an item ID to work on" if args.empty?
|
|
18
|
+
raise "Please supply log text" if args.size == 1
|
|
19
|
+
|
|
20
|
+
elapsed_time = Float(options[:days]) * 60 * 60 * 24
|
|
21
|
+
elapsed_time += Float(options[:hour]) * 60 * 60
|
|
22
|
+
elapsed_time += Float(options[:min]) * 60
|
|
23
|
+
|
|
24
|
+
item = @items.load_item(args.first)
|
|
25
|
+
|
|
26
|
+
description = args[1..-1].join(" ")
|
|
27
|
+
|
|
28
|
+
item.record_work(description, elapsed_time)
|
|
29
|
+
|
|
30
|
+
item.save
|
|
31
|
+
|
|
32
|
+
puts "Logged '#{description}' against item #{item.item_id} for #{Gwtf.seconds_to_human(elapsed_time)}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
desc 'Create an item'
|
|
2
|
+
arg_name 'Short item description'
|
|
3
|
+
command [:new, :n] do |c|
|
|
4
|
+
c.desc 'Invoke EDITOR to provide a long form description'
|
|
5
|
+
c.default_value false
|
|
6
|
+
c.switch [:edit, :e]
|
|
7
|
+
|
|
8
|
+
c.action do |global_options,options,args|
|
|
9
|
+
if options[:edit]
|
|
10
|
+
raise "EDITOR is not set" unless ENV.include?("EDITOR")
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
tmp = Tempfile.new("gwtf")
|
|
14
|
+
system("%s %s" % [ENV["EDITOR"], tmp.path])
|
|
15
|
+
description = tmp.read.chomp
|
|
16
|
+
ensure
|
|
17
|
+
tmp.close
|
|
18
|
+
tmp.unlink
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
description = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
item = @items.new_item
|
|
25
|
+
item.subject = args.join(" ")
|
|
26
|
+
item.description = description if description
|
|
27
|
+
item.save
|
|
28
|
+
|
|
29
|
+
puts "Item #{item.item_id} saved"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
desc 'Re-open a previously closed item'
|
|
2
|
+
arg_name 'Item id'
|
|
3
|
+
command [:open, :o] do |c|
|
|
4
|
+
c.action do |global_options,options,args|
|
|
5
|
+
raise "Please specify an item ID to mark re-open" if args.empty?
|
|
6
|
+
|
|
7
|
+
item = @items.load_item(args.first)
|
|
8
|
+
|
|
9
|
+
raise "Item #{args.first} was already open" if item.open?
|
|
10
|
+
|
|
11
|
+
item.open
|
|
12
|
+
item.save
|
|
13
|
+
|
|
14
|
+
puts "Item #{args.first} as been re-opened"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
desc 'Start work on an item in a subshell'
|
|
2
|
+
arg_name 'Item id'
|
|
3
|
+
command :shell do |c|
|
|
4
|
+
c.action do |global_options,options,args|
|
|
5
|
+
raise "Please specify an item ID to work on" if args.empty?
|
|
6
|
+
raise "SHELL is not set, cannot create sub shell" unless ENV.include?("SHELL")
|
|
7
|
+
|
|
8
|
+
start_time = Time.now
|
|
9
|
+
item = @items.load_item(args.first)
|
|
10
|
+
|
|
11
|
+
puts "Starting work on item #{item.item_id}, exit to record the action and time"
|
|
12
|
+
|
|
13
|
+
ENV["GWTF_ITEM"] = item.item_id.to_s
|
|
14
|
+
ENV["GWTF_PROJECT"] = global_options[:project]
|
|
15
|
+
ENV["GWTF_SUBJECT"] = item.subject
|
|
16
|
+
|
|
17
|
+
system(ENV["SHELL"])
|
|
18
|
+
|
|
19
|
+
elapsed_time = Time.now - start_time
|
|
20
|
+
|
|
21
|
+
STDOUT.sync = true
|
|
22
|
+
|
|
23
|
+
print "Optional description for work log: "
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
description = STDIN.gets.chomp
|
|
27
|
+
rescue Exception
|
|
28
|
+
puts
|
|
29
|
+
description = ""
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
description = "Worked in a subshell" if description == ""
|
|
33
|
+
description = description + " (#{Gwtf.seconds_to_human(elapsed_time.round)})"
|
|
34
|
+
|
|
35
|
+
item.record_work(description, elapsed_time.round)
|
|
36
|
+
|
|
37
|
+
item.save
|
|
38
|
+
|
|
39
|
+
puts "Recorded #{elapsed_time} seconds of work against item #{item.item_id}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
desc 'Show an item'
|
|
2
|
+
arg_name 'Item ID'
|
|
3
|
+
command [:show, :s] do |c|
|
|
4
|
+
c.action do |global_options,options,args|
|
|
5
|
+
raise "Please supply an item ID to show" if args.empty?
|
|
6
|
+
|
|
7
|
+
item = @items.load_item(args.first)
|
|
8
|
+
|
|
9
|
+
time_worked = item.work_log.inject(0) do |result, log|
|
|
10
|
+
begin
|
|
11
|
+
result + log["elapsed"]
|
|
12
|
+
rescue
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
puts " ID: %s" % [ item.item_id ]
|
|
18
|
+
puts " Subject: %s" % [ item.subject ]
|
|
19
|
+
puts " Status: %s" % [ item.status ]
|
|
20
|
+
puts "Time Worked: %s" % [ Gwtf.seconds_to_human(time_worked) ]
|
|
21
|
+
puts " Created: %s" % [ Time.parse(item.created_at).strftime("%D %R") ]
|
|
22
|
+
puts " Closed: %s" % [ Time.parse(item.closed_at).strftime("%D %R") ] if item.closed?
|
|
23
|
+
|
|
24
|
+
if item.has_description?
|
|
25
|
+
puts
|
|
26
|
+
puts "Description:"
|
|
27
|
+
|
|
28
|
+
item.description.split("\n").each do |line|
|
|
29
|
+
puts "%13s%s" % [ "", line]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
time_spent = 0
|
|
36
|
+
|
|
37
|
+
item.work_log.each_with_index do |log, idx|
|
|
38
|
+
puts
|
|
39
|
+
puts "Work Log: " if idx == 0
|
|
40
|
+
|
|
41
|
+
puts "%27s %s" % [Time.parse(log["time"]).strftime("%D %R"), log["text"]]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/gwtf/item.rb
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
module Gwtf
|
|
2
|
+
class Item
|
|
3
|
+
attr_accessor :file
|
|
4
|
+
|
|
5
|
+
def initialize(file=nil)
|
|
6
|
+
@item = default_item
|
|
7
|
+
@file = file
|
|
8
|
+
|
|
9
|
+
load_item if file
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def open?
|
|
13
|
+
@item["status"] == "open"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def closed?
|
|
17
|
+
!open?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def load_item
|
|
21
|
+
raise "A file to read from has not been specified" unless @file
|
|
22
|
+
|
|
23
|
+
read_item = JSON.parse(File.read(@file))
|
|
24
|
+
|
|
25
|
+
@item.merge!(read_item)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def backup_dir
|
|
29
|
+
File.join(File.dirname(file), "backups")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def save(backup=true)
|
|
33
|
+
raise "No item_id set, cannot save item" unless @item["item_id"]
|
|
34
|
+
|
|
35
|
+
if backup && File.exist?(@file)
|
|
36
|
+
backup_name = File.basename(@file) + "-" + Time.now.to_f.to_s
|
|
37
|
+
|
|
38
|
+
FileUtils.mv(@file, File.join(backup_dir, backup_name))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
File.open(@file, "w") do |f|
|
|
42
|
+
f.print to_json
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@file
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def default_item
|
|
49
|
+
{"description" => nil,
|
|
50
|
+
"subject" => nil,
|
|
51
|
+
"created_at" => Time.now,
|
|
52
|
+
"edited_at" => nil,
|
|
53
|
+
"closed_at" => nil,
|
|
54
|
+
"status" => "open",
|
|
55
|
+
"item_id" => nil,
|
|
56
|
+
"work_log" => []}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_hash
|
|
60
|
+
@item
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def record_work(text, elapsed=0)
|
|
64
|
+
update_property(:edited_at, Time.now)
|
|
65
|
+
|
|
66
|
+
@item["work_log"] << {"text" => text, "time" => Time.now, "elapsed" => elapsed}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def open
|
|
70
|
+
update_property(:closed_at, nil)
|
|
71
|
+
update_property(:status, "open")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def close
|
|
75
|
+
update_property(:closed_at, Time.now)
|
|
76
|
+
update_property(:status, "closed")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_json
|
|
80
|
+
JSON.pretty_generate(@item)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_yaml
|
|
84
|
+
@item.to_yaml
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def update_property(property, value)
|
|
88
|
+
property = property.to_s
|
|
89
|
+
|
|
90
|
+
raise "No such property: #{property}" unless @item.include?(property)
|
|
91
|
+
|
|
92
|
+
@item["edited_at"] = Time.now
|
|
93
|
+
@item[property] = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def [](property)
|
|
97
|
+
property = property.to_s
|
|
98
|
+
|
|
99
|
+
raise "No such property: #{property}" unless @item.include?(property)
|
|
100
|
+
|
|
101
|
+
@item[property]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def []=(property, value)
|
|
105
|
+
update_property(property, value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# simple read from the class:
|
|
109
|
+
#
|
|
110
|
+
# >> i.description
|
|
111
|
+
# => "Sample Item"
|
|
112
|
+
#
|
|
113
|
+
# method like writes:
|
|
114
|
+
#
|
|
115
|
+
# >> i.description "This is a test"
|
|
116
|
+
# => "This is a test"
|
|
117
|
+
#
|
|
118
|
+
# assignment
|
|
119
|
+
#
|
|
120
|
+
# >> i.description = "This is a test"
|
|
121
|
+
# => "This is a test"
|
|
122
|
+
#
|
|
123
|
+
# boolean
|
|
124
|
+
#
|
|
125
|
+
# >> i.description?
|
|
126
|
+
# => false
|
|
127
|
+
# >> i.description "foo"
|
|
128
|
+
# => foo
|
|
129
|
+
# >> i.has_description?
|
|
130
|
+
# => true
|
|
131
|
+
# >> i.has_description
|
|
132
|
+
# => true
|
|
133
|
+
def method_missing(method, *args)
|
|
134
|
+
method = method.to_s
|
|
135
|
+
|
|
136
|
+
if @item.include?(method)
|
|
137
|
+
if args.empty?
|
|
138
|
+
return @item[method]
|
|
139
|
+
else
|
|
140
|
+
return update_property(method, args.first)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
elsif method =~ /^has_(.+?)\?*$/
|
|
144
|
+
return !@item[$1].nil?
|
|
145
|
+
|
|
146
|
+
elsif method =~ /^(.+)\?$/
|
|
147
|
+
return !@item[$1].nil?
|
|
148
|
+
|
|
149
|
+
elsif method =~ /^(.+)=$/
|
|
150
|
+
property = $1
|
|
151
|
+
return update_property(property, args.first) if @item.include?(property)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
raise NameError, "undefined local variable or method `#{method}'"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
data/lib/gwtf/items.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Gwtf
|
|
2
|
+
class Items
|
|
3
|
+
def self.config_file(data_dir)
|
|
4
|
+
File.expand_path(File.join(data_dir, "..", "gwtf.json"))
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.setup(data_dir)
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
|
|
10
|
+
raise "#{data_dir} already exist" if File.exist?(data_dir)
|
|
11
|
+
|
|
12
|
+
FileUtils.mkdir_p(File.join(data_dir, "backups"))
|
|
13
|
+
FileUtils.mkdir_p(File.join(data_dir, "archive"))
|
|
14
|
+
FileUtils.mkdir_p(File.join(data_dir, "garbage"))
|
|
15
|
+
|
|
16
|
+
unless File.exist?(config_file(data_dir))
|
|
17
|
+
File.open(config_file(data_dir), "w") do |f|
|
|
18
|
+
f.print({"next_item" => 0}.to_json)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(data_dir)
|
|
24
|
+
raise "Data directory #{data_dir} does not exist" unless File.directory?(data_dir)
|
|
25
|
+
|
|
26
|
+
@data_dir = data_dir
|
|
27
|
+
@config = read_config
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def new_item
|
|
31
|
+
item = Item.new
|
|
32
|
+
item.item_id = @config["next_item"]
|
|
33
|
+
item.file = File.join(@data_dir, "#{item.item_id}.gwtf")
|
|
34
|
+
|
|
35
|
+
@config["next_item"] += 1
|
|
36
|
+
save_config
|
|
37
|
+
|
|
38
|
+
item
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def load_item(item)
|
|
42
|
+
raise "Item #{item} does not exist" unless File.exist?(file_for_item(item))
|
|
43
|
+
|
|
44
|
+
Item.new(file_for_item(item))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def read_config
|
|
48
|
+
JSON.parse(File.read(Items.config_file(@data_dir)))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def save_config
|
|
52
|
+
raise "Config has not been loaded" unless @config
|
|
53
|
+
|
|
54
|
+
File.open(Items.config_file(@data_dir), "w") do |f|
|
|
55
|
+
f.print(@config.to_json)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def items
|
|
60
|
+
Dir.entries(@data_dir).grep(/\.gwtf$/).sort.map{|i| File.basename(i, ".gwtf")}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def file_for_item(item)
|
|
64
|
+
File.join(@data_dir, "#{item}.gwtf")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def each_item
|
|
68
|
+
items.each {|item| yield load_item(item) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/gwtf/version.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gwtf
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 29
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- R.I.Pienaar
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2012-03-10 00:00:00 +00:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies:
|
|
21
|
+
- !ruby/object:Gem::Dependency
|
|
22
|
+
name: rake
|
|
23
|
+
prerelease: false
|
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
hash: 3
|
|
30
|
+
segments:
|
|
31
|
+
- 0
|
|
32
|
+
version: "0"
|
|
33
|
+
type: :development
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: rdoc
|
|
37
|
+
prerelease: false
|
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
hash: 3
|
|
44
|
+
segments:
|
|
45
|
+
- 0
|
|
46
|
+
version: "0"
|
|
47
|
+
type: :development
|
|
48
|
+
version_requirements: *id002
|
|
49
|
+
description: A Unix cli centric todo manager
|
|
50
|
+
email: rip@devco.net
|
|
51
|
+
executables:
|
|
52
|
+
- gwtf
|
|
53
|
+
extensions: []
|
|
54
|
+
|
|
55
|
+
extra_rdoc_files: []
|
|
56
|
+
|
|
57
|
+
files:
|
|
58
|
+
- bin/gwtf
|
|
59
|
+
- lib/gwtf/commands/show_command.rb
|
|
60
|
+
- lib/gwtf/commands/open_command.rb
|
|
61
|
+
- lib/gwtf/commands/shell_command.rb
|
|
62
|
+
- lib/gwtf/commands/done_command.rb
|
|
63
|
+
- lib/gwtf/commands/log_command.rb
|
|
64
|
+
- lib/gwtf/commands/edit_command.rb
|
|
65
|
+
- lib/gwtf/commands/new_command.rb
|
|
66
|
+
- lib/gwtf/commands/list_command.rb
|
|
67
|
+
- lib/gwtf/version.rb
|
|
68
|
+
- lib/gwtf/item.rb
|
|
69
|
+
- lib/gwtf/items.rb
|
|
70
|
+
- lib/gwtf.rb
|
|
71
|
+
has_rdoc: true
|
|
72
|
+
homepage: http://devco.net/
|
|
73
|
+
licenses: []
|
|
74
|
+
|
|
75
|
+
post_install_message:
|
|
76
|
+
rdoc_options: []
|
|
77
|
+
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
- lib
|
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
82
|
+
none: false
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
hash: 3
|
|
87
|
+
segments:
|
|
88
|
+
- 0
|
|
89
|
+
version: "0"
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
none: false
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
hash: 3
|
|
96
|
+
segments:
|
|
97
|
+
- 0
|
|
98
|
+
version: "0"
|
|
99
|
+
requirements: []
|
|
100
|
+
|
|
101
|
+
rubyforge_project:
|
|
102
|
+
rubygems_version: 1.3.7
|
|
103
|
+
signing_key:
|
|
104
|
+
specification_version: 3
|
|
105
|
+
summary: Go With The Flow
|
|
106
|
+
test_files: []
|
|
107
|
+
|