taco_it 1.5.2 → 1.5.3
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.
- checksums.yaml +15 -0
- data/bin/taco +2 -2
- data/lib/taco/change.rb +18 -18
- data/lib/taco/cli.rb +56 -35
- data/lib/taco/issue.rb +46 -46
- data/lib/taco/schema.rb +42 -35
- data/lib/taco/taco.rb +33 -32
- data/lib/taco/taco_profile.rb +19 -9
- data/lib/taco/tacorc.rb +4 -5
- metadata +5 -9
checksums.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
!binary "U0hBMQ==":
|
|
3
|
+
metadata.gz: !binary |-
|
|
4
|
+
M2UwODA4YTVhOTlhZThhOTM1MjIyZjJhY2RhMWVkNmYyY2IyNDQ4Yg==
|
|
5
|
+
data.tar.gz: !binary |-
|
|
6
|
+
ZTljZGJhYTMwNTdmMmI4YzQ3YWU0MWI1ZTY3NjlkYzhkZWZhOWYwYg==
|
|
7
|
+
SHA512:
|
|
8
|
+
metadata.gz: !binary |-
|
|
9
|
+
ZWQzNTc4NGMwZmZiNDFlOWJjZmJjYWZjN2ZlMjgzOThhOGE0OWJhMDYyNmJm
|
|
10
|
+
NzhkMzEyNDg2ZTgxZDBkOWFlYTIyZjdkZmE0YTU2MDkyNmNkYWU5YjUyZDEw
|
|
11
|
+
YmNmOTI2MTdiN2EyOWIzZjc3MDZjOTMxZjI3OGE5MGNlZTA1ZGI=
|
|
12
|
+
data.tar.gz: !binary |-
|
|
13
|
+
ODQ2Y2I4YTEyNTUwYTczYTUyMzgwMjRlMGMyMzY1NTI5MDc3OTIyNWVlMGY2
|
|
14
|
+
YzY3YTA1ZDQ1MjNjNWMwNzUyY2M3MWNmMDE0MDQyYzcyYWJlMWM5ZGUzODY3
|
|
15
|
+
MzM1NzM2MWNhZjRlYzEyNDk0MjI4MGIxZmM0NTczNTU5NzU5ZGU=
|
data/bin/taco
CHANGED
|
@@ -12,7 +12,7 @@ end
|
|
|
12
12
|
require 'taco/commander/import'
|
|
13
13
|
|
|
14
14
|
program :name, 'taco'
|
|
15
|
-
program :version, '1.5.
|
|
15
|
+
program :version, '1.5.3'
|
|
16
16
|
program :description, 'simple command line issue tracking'
|
|
17
17
|
|
|
18
18
|
command :init do |c|
|
|
@@ -49,7 +49,7 @@ command :new do |c|
|
|
|
49
49
|
|
|
50
50
|
c.option '--retry', nil, 'retry a failed Issue creation'
|
|
51
51
|
|
|
52
|
-
c.arguments lambda { |args| args.size <= 1 }
|
|
52
|
+
c.arguments lambda { |args| true } #lambda { |args| args.size <= 1 }
|
|
53
53
|
|
|
54
54
|
c.action do |args, options|
|
|
55
55
|
begin
|
data/lib/taco/change.rb
CHANGED
|
@@ -4,32 +4,32 @@ require 'time'
|
|
|
4
4
|
# FIXME: put this in a namespace
|
|
5
5
|
class Change
|
|
6
6
|
class Invalid < Exception; end
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
attr_reader :created_at
|
|
9
9
|
attr_accessor :attribute
|
|
10
10
|
attr_accessor :old_value
|
|
11
|
-
attr_accessor :new_value
|
|
12
|
-
|
|
11
|
+
attr_accessor :new_value
|
|
12
|
+
|
|
13
13
|
def initialize(args={})
|
|
14
14
|
args.each do |attr, value|
|
|
15
15
|
raise ArgumentError.new("Unknown attribute #{attr}") unless self.respond_to?(attr)
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
case attr.to_sym
|
|
18
18
|
when :created_at
|
|
19
19
|
value = Time.parse(value) unless value.is_a?(Time)
|
|
20
20
|
when :attribute
|
|
21
21
|
value = value.to_sym
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
instance_variable_set("@#{attr.to_s}", value)
|
|
25
25
|
end
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
@created_at = Time.parse(@created_at) if @created_at.is_a?(String)
|
|
28
28
|
@created_at = timescrub(@created_at || Time.now)
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
self
|
|
31
|
-
end
|
|
32
|
-
|
|
31
|
+
end
|
|
32
|
+
|
|
33
33
|
def self.from_json(the_json)
|
|
34
34
|
begin
|
|
35
35
|
hash = JSON.parse(the_json)
|
|
@@ -37,9 +37,9 @@ class Change
|
|
|
37
37
|
raise Change::Invalid.new(e.to_s)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
Change.new(hash)
|
|
40
|
+
Change.new(hash)
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
def valid?(opts={})
|
|
44
44
|
# old_value is optional!
|
|
45
45
|
#
|
|
@@ -47,29 +47,29 @@ class Change
|
|
|
47
47
|
raise Invalid if opts[:raise] && !valid
|
|
48
48
|
valid
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
def to_json(state=nil)
|
|
52
52
|
valid? :raise => true
|
|
53
53
|
hash = { :created_at => created_at, :attribute => attribute, :old_value => old_value, :new_value => new_value }
|
|
54
54
|
JSON.pretty_generate(hash)
|
|
55
55
|
end
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
def to_s(opts={})
|
|
58
58
|
if opts[:simple]
|
|
59
59
|
"#{attribute} : #{old_value} => #{new_value}"
|
|
60
60
|
else
|
|
61
|
-
fields = [ date(created_at), attribute, old_value || '[nil]', new_value ]
|
|
62
|
-
"%10s : %12s : %s => %s" % fields
|
|
61
|
+
fields = [ date(created_at), attribute, old_value || '[nil]', new_value ]
|
|
62
|
+
"%10s : %12s : %s => %s" % fields
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
private
|
|
67
67
|
def date(t)
|
|
68
68
|
t.strftime "%Y/%m/%d %H:%M:%S"
|
|
69
69
|
end
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
def timescrub(t)
|
|
72
72
|
Time.new t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset
|
|
73
73
|
end
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
end
|
data/lib/taco/cli.rb
CHANGED
|
@@ -11,16 +11,16 @@ class TacoCLI
|
|
|
11
11
|
TACORC_NAME = '.tacorc'
|
|
12
12
|
TACO_PROFILE_NAME = '.taco_profile'
|
|
13
13
|
INDEX_ERB_NAME = '.index.html.erb'
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
DEFAULT_TACORC_NAME = 'tacorc'
|
|
16
16
|
DEFAULT_TACO_PROFILE_NAME = 'taco_profile'
|
|
17
17
|
DEFAULT_INDEX_ERB_NAME = 'index.html.erb'
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
DEFAULTS_HOME = File.realpath(File.join(File.dirname(__FILE__), 'defaults/'))
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def initialize
|
|
22
22
|
@taco = Taco.new
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
@retry_path = File.join(@taco.home, RETRY_NAME)
|
|
25
25
|
|
|
26
26
|
@tacorc_path = File.join(@taco.home, TACORC_NAME)
|
|
@@ -33,24 +33,24 @@ class TacoCLI
|
|
|
33
33
|
rc = TacoRc.new @tacorc_path
|
|
34
34
|
rc.update_schema! Issue
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
# FIXME: do this elsewhere. pass in an initialized TacoProfile object
|
|
38
38
|
#
|
|
39
39
|
if File.exist? @taco_profile_path
|
|
40
40
|
profile_text = open(@taco_profile_path) { |f| f.read }
|
|
41
41
|
end
|
|
42
|
-
|
|
43
|
-
@profile = TacoProfile.new profile_text
|
|
42
|
+
|
|
43
|
+
@profile = TacoProfile.new profile_text
|
|
44
44
|
end
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
def init!
|
|
47
47
|
out = @taco.init!
|
|
48
48
|
|
|
49
|
-
FileUtils.copy(File.join(DEFAULTS_HOME, DEFAULT_TACORC_NAME), @tacorc_path)
|
|
50
|
-
FileUtils.copy(File.join(DEFAULTS_HOME, DEFAULT_TACO_PROFILE_NAME), @taco_profile_path)
|
|
49
|
+
FileUtils.copy(File.join(DEFAULTS_HOME, DEFAULT_TACORC_NAME), @tacorc_path)
|
|
50
|
+
FileUtils.copy(File.join(DEFAULTS_HOME, DEFAULT_TACO_PROFILE_NAME), @taco_profile_path)
|
|
51
51
|
|
|
52
52
|
FileUtils.copy(File.join(DEFAULTS_HOME, DEFAULT_INDEX_ERB_NAME), @index_erb_path)
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
out + "\nPlease edit the config files at:\n #{@tacorc_path}\n #{@taco_profile_path}"
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -58,40 +58,61 @@ class TacoCLI
|
|
|
58
58
|
filters = args.size > 0 ? args : @profile.filters
|
|
59
59
|
|
|
60
60
|
the_list = @taco.list :filters => filters
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
if opts[:sort]
|
|
63
|
-
attrs = opts[:sort].split(',').map(&:
|
|
63
|
+
attrs = opts[:sort].split(',').map(&:to_sym)
|
|
64
|
+
attrs.each do |attr|
|
|
65
|
+
# FIXME: don't hardcode :short_id
|
|
66
|
+
raise ArgumentError.new("Unknown Issue attribute for sort: #{attr}") unless attr == :short_id || Issue.schema_attributes.include?(attr)
|
|
67
|
+
end
|
|
64
68
|
else
|
|
65
69
|
attrs = @profile.sort_order
|
|
66
70
|
end
|
|
67
|
-
|
|
71
|
+
|
|
68
72
|
the_list.sort! do |issue_a, issue_b|
|
|
69
73
|
order = 0
|
|
70
|
-
|
|
74
|
+
|
|
71
75
|
attrs.take_while do |attr|
|
|
72
76
|
order = issue_a.send(attr) <=> issue_b.send(attr)
|
|
73
77
|
order == 0
|
|
74
78
|
end
|
|
75
|
-
|
|
79
|
+
|
|
76
80
|
order
|
|
77
81
|
end
|
|
78
|
-
|
|
82
|
+
|
|
79
83
|
the_list.map! do |issue|
|
|
80
84
|
@profile.columns.map { |col| issue.send(col) }.join(' : ')
|
|
81
85
|
end
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
return "Found no issues." unless the_list.size > 0
|
|
84
88
|
the_list.join("\n")
|
|
85
89
|
end
|
|
86
|
-
|
|
90
|
+
|
|
87
91
|
def new!(args, opts)
|
|
88
92
|
editor_opts = if opts[:retry]
|
|
89
|
-
raise ArgumentError.new("No previous Issue edit session was found.") unless File.exist?(@retry_path)
|
|
93
|
+
raise ArgumentError.new("No previous Issue edit session was found.") unless File.exist?(@retry_path)
|
|
90
94
|
{ :template => open(@retry_path) { |f| f.read } }
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
else
|
|
96
|
+
file, defaults = nil, {}
|
|
97
|
+
|
|
98
|
+
args.each do |arg|
|
|
99
|
+
if arg.include? ':'
|
|
100
|
+
k, v = arg.split(':', 2)
|
|
101
|
+
defaults[Issue.schema_attr_expand(k)] = v
|
|
102
|
+
elsif file
|
|
103
|
+
raise ArgumentError.new("Multiple filenames given.")
|
|
104
|
+
else
|
|
105
|
+
file = arg
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if file && defaults.size > 0
|
|
110
|
+
raise ArgumentError.new("Cannot set defaults when creating Issue from file.")
|
|
111
|
+
elsif file
|
|
112
|
+
{ :from_file => file, :defaults => defaults }
|
|
113
|
+
else
|
|
114
|
+
{ :template => Issue.new.to_template(:defaults => defaults) }
|
|
115
|
+
end
|
|
95
116
|
end
|
|
96
117
|
|
|
97
118
|
if issue = IssueEditor.new(@taco, @retry_path).new_issue!(editor_opts)
|
|
@@ -100,31 +121,31 @@ class TacoCLI
|
|
|
100
121
|
"Aborted."
|
|
101
122
|
end
|
|
102
123
|
end
|
|
103
|
-
|
|
124
|
+
|
|
104
125
|
def show(args, opts)
|
|
105
126
|
if opts[:all]
|
|
106
127
|
filters = args.select { |arg| arg.include? ':' }
|
|
107
128
|
args = @taco.list(:filters => filters).map(&:id)
|
|
108
129
|
end
|
|
109
|
-
|
|
130
|
+
|
|
110
131
|
args.map { |id| @taco.read(id).to_s(opts) }.join("\n\n")
|
|
111
132
|
end
|
|
112
|
-
|
|
133
|
+
|
|
113
134
|
def edit!(args, opts)
|
|
114
135
|
ie = IssueEditor.new @taco, @retry_path
|
|
115
|
-
|
|
136
|
+
|
|
116
137
|
if opts[:retry]
|
|
117
|
-
raise ArgumentError.new("No previous Issue edit session was found.") unless File.exist?(@retry_path)
|
|
138
|
+
raise ArgumentError.new("No previous Issue edit session was found.") unless File.exist?(@retry_path)
|
|
118
139
|
template = open(@retry_path) { |f| f.read }
|
|
119
140
|
end
|
|
120
|
-
|
|
141
|
+
|
|
121
142
|
if issue = ie.edit_issue!(@taco.read(args[0]), :template => template)
|
|
122
143
|
"Updated Issue #{issue.id}"
|
|
123
144
|
else
|
|
124
145
|
"Aborted."
|
|
125
146
|
end
|
|
126
147
|
end
|
|
127
|
-
|
|
148
|
+
|
|
128
149
|
def template(opts)
|
|
129
150
|
if opts[:defaults]
|
|
130
151
|
(Issue::TEMPLATE % Issue.new.to_hash).strip
|
|
@@ -132,14 +153,14 @@ class TacoCLI
|
|
|
132
153
|
Issue::TEMPLATE.gsub(/%{.*?}/, '').strip
|
|
133
154
|
end
|
|
134
155
|
end
|
|
135
|
-
|
|
156
|
+
|
|
136
157
|
def html
|
|
137
158
|
require 'erb'
|
|
138
|
-
|
|
159
|
+
|
|
139
160
|
issues = @taco.list
|
|
140
|
-
ERB.new(open(@index_erb_path) { |f| f.read }).result(binding)
|
|
161
|
+
ERB.new(open(@index_erb_path) { |f| f.read }).result(binding)
|
|
141
162
|
end
|
|
142
|
-
|
|
163
|
+
|
|
143
164
|
def push(opts)
|
|
144
165
|
opts[:message] ||= 'turn and face the strange'
|
|
145
166
|
cmd = "git add . && git commit -am '#{opts[:message]}' && git push"
|
data/lib/taco/issue.rb
CHANGED
|
@@ -2,25 +2,25 @@ require 'taco/schema'
|
|
|
2
2
|
require 'taco/change'
|
|
3
3
|
require 'securerandom'
|
|
4
4
|
|
|
5
|
-
class Issue
|
|
5
|
+
class Issue
|
|
6
6
|
include Comparable
|
|
7
7
|
include Schema
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
alias_method :schema_valid?, :valid?
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
attr_reader :changelog
|
|
12
12
|
|
|
13
13
|
schema_attr :id, class: String, settable: false
|
|
14
14
|
schema_attr :created_at, class: Time, settable: false
|
|
15
15
|
schema_attr :updated_at, class: Time, settable: false
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
schema_attr :summary, class: String, settable: true
|
|
18
18
|
schema_attr :kind, class: String, settable: true
|
|
19
19
|
schema_attr :status, class: String, settable: true
|
|
20
20
|
schema_attr :owner, class: String, settable: true
|
|
21
21
|
schema_attr :priority, class: Fixnum, settable: true
|
|
22
22
|
schema_attr :description, class: String, settable: true
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
TEMPLATE =<<-EOT.strip
|
|
25
25
|
# Lines beginning with # will be ignored.
|
|
26
26
|
Summary : %{summary}
|
|
@@ -37,14 +37,14 @@ EOT
|
|
|
37
37
|
|
|
38
38
|
class Invalid < Exception; end
|
|
39
39
|
class NotFound < Exception; end
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def initialize(attributes={}, changelog=[])
|
|
42
42
|
attributes = Hash[attributes.map { |k, v| [ k.to_sym, v ] }]
|
|
43
43
|
|
|
44
|
-
@new = attributes[:created_at].nil? && attributes[:id].nil?
|
|
44
|
+
@new = attributes[:created_at].nil? && attributes[:id].nil?
|
|
45
45
|
|
|
46
46
|
@changelog = []
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
attributes.each do |attr, value|
|
|
49
49
|
schema_attr = self.class.schema_attributes[attr]
|
|
50
50
|
raise ArgumentError.new("unknown attribute: #{attr}") unless schema_attr
|
|
@@ -52,11 +52,11 @@ EOT
|
|
|
52
52
|
self.send "#{attr}=", attributes[attr]
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
self.id = attributes[:id] || SecureRandom.uuid.gsub('-', '')
|
|
57
57
|
self.created_at = attributes[:created_at] || Time.now
|
|
58
58
|
self.updated_at = attributes[:updated_at] || Time.now
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
if changelog.size > 0
|
|
61
61
|
@changelog = changelog.map do |thing|
|
|
62
62
|
if thing.is_a? Change
|
|
@@ -66,21 +66,21 @@ EOT
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
self
|
|
71
71
|
end
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
def schema_attribute_change(attribute, old_value, new_value)
|
|
74
74
|
if self.class.schema_attributes[attribute][:settable]
|
|
75
75
|
self.updated_at = Time.now
|
|
76
76
|
@changelog << Change.new(:attribute => attribute, :old_value => old_value, :new_value => new_value)
|
|
77
77
|
end
|
|
78
|
-
end
|
|
79
|
-
|
|
78
|
+
end
|
|
79
|
+
|
|
80
80
|
def new?
|
|
81
81
|
@new
|
|
82
82
|
end
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
def <=>(other)
|
|
85
85
|
if self.class.schema_attributes.all? { |attr, opts| self.send(attr) == other.send(attr) }
|
|
86
86
|
r = 0
|
|
@@ -92,10 +92,10 @@ EOT
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# this clause should not return 0, we've already established inequality
|
|
95
|
-
#
|
|
95
|
+
#
|
|
96
96
|
r = -1 if r == 0
|
|
97
97
|
end
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
r
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -103,10 +103,10 @@ EOT
|
|
|
103
103
|
fields = self.class.schema_attributes.map do |attr, opts|
|
|
104
104
|
"@#{attr}=#{self.send(attr).inspect}"
|
|
105
105
|
end.join ', '
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
"#<#{self.class}:0x%016x %s>" % [ object_id, fields ]
|
|
108
108
|
end
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
def to_s(opts={})
|
|
111
111
|
text = <<-EOT.strip
|
|
112
112
|
ID : #{id}
|
|
@@ -124,30 +124,30 @@ Owner : #{owner}
|
|
|
124
124
|
EOT
|
|
125
125
|
|
|
126
126
|
if opts[:changelog]
|
|
127
|
-
changelog_str = changelog.map { |c| %Q|# #{c.to_s.strip.gsub(/\n/, "\n# ")}| }.join("\n")
|
|
127
|
+
changelog_str = changelog.map { |c| %Q|# #{c.to_s.strip.gsub(/\n/, "\n# ")}| }.join("\n")
|
|
128
128
|
text << %Q|\n---\n\n#{changelog_str}|
|
|
129
129
|
end
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
text
|
|
132
132
|
end
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
def valid?(opts={})
|
|
135
135
|
valid = schema_valid?
|
|
136
136
|
error = schema_errors.first
|
|
137
137
|
raise Invalid.new("attribute #{error.first}: #{error[1].inspect} is not a valid value") if !valid && opts[:raise]
|
|
138
138
|
valid
|
|
139
139
|
end
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
def to_json(state=nil)
|
|
142
142
|
valid? :raise => true
|
|
143
143
|
hash = { :issue => self.to_hash, :changelog => changelog }
|
|
144
144
|
JSON.pretty_generate(hash)
|
|
145
145
|
end
|
|
146
|
-
|
|
147
|
-
def to_template
|
|
146
|
+
|
|
147
|
+
def to_template(opts={})
|
|
148
148
|
if new?
|
|
149
149
|
header = "# New Issue\n#"
|
|
150
|
-
body = TEMPLATE % self.to_hash
|
|
150
|
+
body = TEMPLATE % self.to_hash.merge(opts.fetch(:defaults, {}))
|
|
151
151
|
footer = ""
|
|
152
152
|
else
|
|
153
153
|
header =<<-EOT
|
|
@@ -159,44 +159,44 @@ EOT
|
|
|
159
159
|
#
|
|
160
160
|
EOT
|
|
161
161
|
body = TEMPLATE % self.to_hash
|
|
162
|
-
|
|
163
|
-
footer =<<-EOT
|
|
162
|
+
|
|
163
|
+
footer =<<-EOT
|
|
164
164
|
# ChangeLog
|
|
165
165
|
#
|
|
166
|
-
#{changelog.map { |c| %Q|# #{c.to_s.strip.gsub(/\n/, "\n# ")}| }.join("\n")}
|
|
166
|
+
#{changelog.map { |c| %Q|# #{c.to_s.strip.gsub(/\n/, "\n# ")}| }.join("\n")}
|
|
167
167
|
EOT
|
|
168
168
|
end
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
(header + "\n" + body + "\n\n" + footer).strip
|
|
171
171
|
end
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
def self.from_json(the_json)
|
|
174
174
|
begin
|
|
175
175
|
hash = JSON.parse(the_json)
|
|
176
176
|
rescue JSON::ParserError => e
|
|
177
177
|
raise Issue::Invalid.new(e.to_s)
|
|
178
178
|
end
|
|
179
|
-
|
|
180
|
-
Issue.new(hash['issue'], hash['changelog'])
|
|
181
|
-
end
|
|
182
|
-
|
|
179
|
+
|
|
180
|
+
Issue.new(hash['issue'], hash['changelog'])
|
|
181
|
+
end
|
|
182
|
+
|
|
183
183
|
def self.from_template(text)
|
|
184
184
|
issue = { :description => '' }
|
|
185
185
|
reading_description = false
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
text.lines.each_with_index do |line, index|
|
|
188
188
|
next if line =~ /^#/ || (!reading_description && line =~ /^\s*$/)
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
if line =~ /^---$/
|
|
191
191
|
# FIXME: this means that there can be multiple description blocks in the template!
|
|
192
192
|
#
|
|
193
193
|
reading_description = !reading_description
|
|
194
194
|
next
|
|
195
195
|
end
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
if !reading_description && line =~ /^(\w+)\s*:\s*(.*)$/
|
|
198
198
|
key, value = $1.downcase.to_sym, $2.strip
|
|
199
|
-
|
|
199
|
+
|
|
200
200
|
if schema_attributes.include?(key) && schema_attributes[key][:settable]
|
|
201
201
|
issue[key] = value
|
|
202
202
|
else
|
|
@@ -209,13 +209,13 @@ EOT
|
|
|
209
209
|
raise ArgumentError.new("Cannot parse line #{index+1}")
|
|
210
210
|
end
|
|
211
211
|
end
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
Issue.new(issue)
|
|
214
214
|
end
|
|
215
|
-
|
|
215
|
+
|
|
216
216
|
def update_from_template!(text)
|
|
217
217
|
new_issue = Issue.from_template(text)
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
attrs = self.class.schema_attributes.map do |attr, opts|
|
|
220
220
|
if opts[:settable] && self.send(attr) != (new_value = new_issue.send(attr))
|
|
221
221
|
self.send("#{attr}=", new_value)
|
|
@@ -223,17 +223,17 @@ EOT
|
|
|
223
223
|
end
|
|
224
224
|
|
|
225
225
|
self
|
|
226
|
-
end
|
|
227
|
-
|
|
226
|
+
end
|
|
227
|
+
|
|
228
228
|
private
|
|
229
229
|
def date(t)
|
|
230
230
|
t.strftime "%Y/%m/%d %H:%M:%S"
|
|
231
231
|
end
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
def dup
|
|
234
234
|
raise NoMethodError.new
|
|
235
235
|
end
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
def clone
|
|
238
238
|
raise NoMethodError.new
|
|
239
239
|
end
|
data/lib/taco/schema.rb
CHANGED
|
@@ -10,20 +10,20 @@ module Schema
|
|
|
10
10
|
def self.included(base)
|
|
11
11
|
base.extend(ClassMethods)
|
|
12
12
|
end
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
def to_hash
|
|
15
15
|
Hash[self.class.schema_attributes.map do |attr, opts|
|
|
16
16
|
[ attr, send(attr) ]
|
|
17
17
|
end]
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
def schema_errors
|
|
21
21
|
@errors || []
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
def valid?
|
|
25
25
|
@errors = nil
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
self.class.schema_attributes.each do |attr, opts|
|
|
28
28
|
if opts[:validate].nil?
|
|
29
29
|
case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
|
|
@@ -31,55 +31,62 @@ module Schema
|
|
|
31
31
|
opts[:validate] = lambda { |v| v !~ /\A\s*\Z/ }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
if opts[:validate]
|
|
36
36
|
value = eval(attr.to_s)
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
valid = if opts[:validate].is_a?(Array)
|
|
39
39
|
opts[:validate].include? value
|
|
40
40
|
elsif opts[:validate].is_a?(Proc)
|
|
41
41
|
opts[:validate].call(value)
|
|
42
42
|
end
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
unless valid
|
|
45
45
|
@errors = [ [ attr, value ] ]
|
|
46
46
|
return false
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
true
|
|
52
52
|
end
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
module ClassMethods
|
|
55
55
|
def schema_attributes
|
|
56
56
|
@schema_attrs
|
|
57
57
|
end
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
def schema_attr_expand(prefix)
|
|
60
|
+
candidates = @schema_attrs.keys.select { |a| a.to_s.start_with? prefix }
|
|
61
|
+
raise KeyError.new("no attribute is prefixed with #{prefix}") if candidates.size == 0
|
|
62
|
+
raise KeyError.new("prefix #{prefix} is not unique") if candidates.size > 1
|
|
63
|
+
candidates[0]
|
|
64
|
+
end
|
|
65
|
+
|
|
59
66
|
def schema_attr_remove(name)
|
|
60
|
-
raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
|
|
67
|
+
raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
|
|
61
68
|
@schema_attrs.delete(name)
|
|
62
69
|
self.send(:remove_method, name)
|
|
63
70
|
self.send(:remove_method, "#{name}=".to_s)
|
|
64
71
|
end
|
|
65
|
-
|
|
72
|
+
|
|
66
73
|
def schema_attr_replace(name, opts)
|
|
67
|
-
raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
|
|
74
|
+
raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
|
|
68
75
|
schema_attr_remove(name)
|
|
69
76
|
schema_attr(name, opts)
|
|
70
77
|
end
|
|
71
|
-
|
|
78
|
+
|
|
72
79
|
def schema_attr_update(name, opts)
|
|
73
|
-
raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
|
|
80
|
+
raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
|
|
74
81
|
raise KeyError.new("attribute #{name}: cannot update non-settable attribute") unless @schema_attrs[name][:settable]
|
|
75
82
|
schema_attr_replace(name, @schema_attrs[name].merge(opts))
|
|
76
83
|
end
|
|
77
|
-
|
|
78
|
-
def schema_attr(name, opts)
|
|
84
|
+
|
|
85
|
+
def schema_attr(name, opts)
|
|
79
86
|
@schema_attrs ||= {}
|
|
80
87
|
|
|
81
88
|
raise TypeError.new("attribute #{name}: missing or invalid :class") unless opts[:class].is_a?(Class)
|
|
82
|
-
|
|
89
|
+
|
|
83
90
|
if opts[:default].nil?
|
|
84
91
|
opts[:default] = case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
|
|
85
92
|
when 'String'
|
|
@@ -92,31 +99,31 @@ module Schema
|
|
|
92
99
|
raise ArgumentError.new("Sorry, no default default exists for #{opts[:class]}")
|
|
93
100
|
end
|
|
94
101
|
end
|
|
95
|
-
|
|
102
|
+
|
|
96
103
|
unless opts[:default].is_a?(opts[:class]) || opts[:default].is_a?(Proc)
|
|
97
104
|
raise TypeError.new("attribute #{name}: invalid :default")
|
|
98
105
|
end
|
|
99
|
-
|
|
106
|
+
|
|
100
107
|
if opts[:validate]
|
|
101
108
|
unless opts[:validate].is_a?(Array) || opts[:validate].is_a?(Proc)
|
|
102
109
|
raise ArgumentError.new("attribute #{name}: expecting Array or Proc for :validate")
|
|
103
110
|
end
|
|
104
|
-
|
|
111
|
+
|
|
105
112
|
if opts[:validate].is_a?(Array)
|
|
106
113
|
raise TypeError.new("attribute #{name}: wrong type in :validate Array") unless opts[:validate].all? { |v| v.is_a?(opts[:class]) }
|
|
107
114
|
end
|
|
108
115
|
end
|
|
109
116
|
|
|
110
117
|
raise ArgumentError.new("attribute #{name}: already exists") if @schema_attrs[name]
|
|
111
|
-
|
|
118
|
+
|
|
112
119
|
@schema_attrs[name] = opts
|
|
113
|
-
|
|
120
|
+
|
|
114
121
|
value_getter = if opts[:default].is_a?(Proc)
|
|
115
122
|
%Q(
|
|
116
123
|
opts = self.class.schema_attributes[:#{name}]
|
|
117
124
|
value = opts[:default].call
|
|
118
|
-
raise TypeError.new("attribute #{name}: expected type #{opts[:class]}, received \#{value.class}") unless opts[:class] == value.class
|
|
119
|
-
)
|
|
125
|
+
raise TypeError.new("attribute #{name}: expected type #{opts[:class]}, received \#{value.class}") unless opts[:class] == value.class
|
|
126
|
+
)
|
|
120
127
|
else
|
|
121
128
|
%Q(value = #{opts[:default].inspect})
|
|
122
129
|
end
|
|
@@ -129,7 +136,7 @@ module Schema
|
|
|
129
136
|
@#{name}
|
|
130
137
|
end
|
|
131
138
|
)
|
|
132
|
-
|
|
139
|
+
|
|
133
140
|
unless opts[:coerce] == false # possible values are false=no-coerce, nil=default-coerce, Proc=custom-coerce
|
|
134
141
|
case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
|
|
135
142
|
when 'Fixnum'
|
|
@@ -155,16 +162,16 @@ module Schema
|
|
|
155
162
|
value = Time.parse(value)
|
|
156
163
|
rescue ArgumentError
|
|
157
164
|
raise TypeError.new("attribute #{name}: cannot coerce from \#{value.class}")
|
|
158
|
-
end
|
|
165
|
+
end
|
|
159
166
|
end
|
|
160
167
|
value
|
|
161
168
|
end
|
|
162
|
-
end
|
|
169
|
+
end
|
|
163
170
|
end
|
|
164
|
-
|
|
171
|
+
|
|
165
172
|
coerce = 'value = opts[:coerce].call(value)' if opts[:coerce]
|
|
166
173
|
end
|
|
167
|
-
|
|
174
|
+
|
|
168
175
|
unless opts[:transform] == false # possible values are false=no-transform, nil=default-transform, Proc=custom-transform
|
|
169
176
|
case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
|
|
170
177
|
when 'String'
|
|
@@ -179,25 +186,25 @@ module Schema
|
|
|
179
186
|
# foo.a_time = Time.new
|
|
180
187
|
# Time.parse(foo.a_time.to_s) == foo.a_time # returns false most of the time!
|
|
181
188
|
opts[:transform] = lambda { |t| Time.new t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset }
|
|
182
|
-
end
|
|
189
|
+
end
|
|
183
190
|
end
|
|
184
|
-
|
|
191
|
+
|
|
185
192
|
transform = 'value = opts[:transform].call(value)' if opts[:transform]
|
|
186
193
|
end
|
|
187
|
-
|
|
194
|
+
|
|
188
195
|
callback = %Q(
|
|
189
196
|
if self.respond_to? :schema_attribute_change
|
|
190
197
|
self.schema_attribute_change(:#{name}, @#{name}, value)
|
|
191
198
|
end
|
|
192
199
|
)
|
|
193
|
-
|
|
200
|
+
|
|
194
201
|
setter_method = %Q(
|
|
195
202
|
def #{name}=(value)
|
|
196
203
|
opts = self.class.schema_attributes[:#{name}]
|
|
197
204
|
#{coerce}
|
|
198
205
|
raise TypeError.new("attribute #{name}: expected type #{opts[:class]}, received \#{value.class}") unless opts[:class] == value.class
|
|
199
206
|
#{transform}
|
|
200
|
-
#{callback}
|
|
207
|
+
#{callback}
|
|
201
208
|
@#{name} = value
|
|
202
209
|
end
|
|
203
210
|
)
|
data/lib/taco/taco.rb
CHANGED
|
@@ -8,33 +8,33 @@ class Taco
|
|
|
8
8
|
HOME_DIR = '.taco'
|
|
9
9
|
|
|
10
10
|
attr_accessor :home
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
class NotFound < Exception; end
|
|
13
13
|
class Ambiguous < Exception; end
|
|
14
|
-
|
|
15
|
-
def initialize(root_path=nil)
|
|
14
|
+
|
|
15
|
+
def initialize(root_path=nil)
|
|
16
16
|
@home = File.join(root_path || Dir.getwd, HOME_DIR)
|
|
17
|
-
end
|
|
18
|
-
|
|
17
|
+
end
|
|
18
|
+
|
|
19
19
|
def init!
|
|
20
20
|
raise IOError.new("Could not create #{@home}\nDirectory already exists.") if File.exists?(@home)
|
|
21
21
|
|
|
22
22
|
FileUtils.mkdir_p(@home)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
"Initialized #{@home}"
|
|
25
25
|
end
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
def write!(issue_or_issues)
|
|
28
28
|
issues = issue_or_issues.is_a?(Array) ? issue_or_issues : [ issue_or_issues ]
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
issues.each do |issue|
|
|
31
|
-
the_json = issue.to_json # do this first so we don't bother the filesystem if the issue is invalid
|
|
31
|
+
the_json = issue.to_json # do this first so we don't bother the filesystem if the issue is invalid
|
|
32
32
|
open(File.join(@home, issue.id), 'w') { |f| f.write(the_json) }
|
|
33
33
|
end
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
issue_or_issues
|
|
36
|
-
end
|
|
37
|
-
|
|
36
|
+
end
|
|
37
|
+
|
|
38
38
|
def read(issue_id)
|
|
39
39
|
issue_path = File.join(@home, issue_id)
|
|
40
40
|
|
|
@@ -47,19 +47,19 @@ class Taco
|
|
|
47
47
|
issue = read(File.basename(entry))
|
|
48
48
|
"#{issue.id} : #{issue.summary}"
|
|
49
49
|
end
|
|
50
|
-
raise Ambiguous.new("Found several matching issues:\n%s" % issue_list.join("\n"))
|
|
50
|
+
raise Ambiguous.new("Found several matching issues:\n%s" % issue_list.join("\n"))
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
issue_path = entries[0]
|
|
54
54
|
issue_id = File.basename entries[0]
|
|
55
55
|
end
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
the_json = open(issue_path) { |f| f.read }
|
|
58
58
|
|
|
59
59
|
issue = Issue.from_json the_json
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
raise Issue::Invalid.new("Issue ID does not match filename: #{issue.id} != #{issue_id}") unless issue.id == issue_id
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
issue
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -67,45 +67,46 @@ class Taco
|
|
|
67
67
|
filter_match = if opts.fetch(:filters, []).size > 0
|
|
68
68
|
conditions = opts[:filters].map do |filter|
|
|
69
69
|
attr, val = filter.split(':')
|
|
70
|
+
attr = Issue.schema_attr_expand(attr)
|
|
70
71
|
%Q|i.send("#{attr}").to_s.downcase == "#{val.downcase}"|
|
|
71
72
|
end.join ' && '
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
# FIXME: eval-ing user input? madness!
|
|
74
75
|
eval "Proc.new { |i| #{conditions} }"
|
|
75
76
|
else
|
|
76
77
|
nil
|
|
77
78
|
end
|
|
78
|
-
|
|
79
|
+
|
|
79
80
|
ids = Dir.glob("#{@home}/*")
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
ids.map do |name|
|
|
82
83
|
id = File.basename name
|
|
83
84
|
issue = Issue.from_json(open(name) { |f| f.read })
|
|
84
85
|
|
|
85
86
|
next unless filter_match.nil? || filter_match.call(issue)
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
raise Issue::Invalid.new("Issue ID does not match filename: #{issue.id} != #{id}") unless issue.id == id
|
|
88
|
-
|
|
89
|
+
|
|
89
90
|
the_short_id = 8.upto(id.size).each do |n|
|
|
90
91
|
the_short_id = id[0...n]
|
|
91
92
|
break the_short_id unless ids.count { |i| i.include? the_short_id } > 1
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
# because the length of the short_id is determinable only within the context of a group of issues
|
|
95
|
+
# because the length of the short_id is determinable only within the context of a group of issues
|
|
95
96
|
# (because it must long enough to be unique), we can only define it on Issue in the context of a group
|
|
96
97
|
#
|
|
97
98
|
issue.instance_eval "def short_id; #{the_short_id.inspect}; end"
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
issue
|
|
100
101
|
end.reject(&:nil?).sort
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
class IssueEditor
|
|
105
|
-
def initialize(taco, retry_path)
|
|
106
|
+
def initialize(taco, retry_path)
|
|
106
107
|
@taco, @retry_path = taco, retry_path
|
|
107
108
|
end
|
|
108
|
-
|
|
109
|
+
|
|
109
110
|
def new_issue!(opts={})
|
|
110
111
|
if opts[:from_file]
|
|
111
112
|
text = open(opts[:from_file]) { |f| f.read }
|
|
@@ -116,13 +117,13 @@ class IssueEditor
|
|
|
116
117
|
|
|
117
118
|
write_issue!(Issue.from_template(text), text) if text
|
|
118
119
|
end
|
|
119
|
-
|
|
120
|
+
|
|
120
121
|
def edit_issue!(issue, opts={})
|
|
121
122
|
if text = invoke_editor(opts[:template] || issue.to_template)
|
|
122
123
|
write_issue!(issue.update_from_template!(text), text)
|
|
123
124
|
end
|
|
124
125
|
end
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
private
|
|
127
128
|
def write_issue!(issue, text)
|
|
128
129
|
begin
|
|
@@ -131,14 +132,14 @@ class IssueEditor
|
|
|
131
132
|
open(@retry_path, 'w') { |f| f.write(text) } if text
|
|
132
133
|
raise e
|
|
133
134
|
end
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
File.unlink @retry_path rescue nil
|
|
136
|
-
issue
|
|
137
|
+
issue
|
|
137
138
|
end
|
|
138
|
-
|
|
139
|
+
|
|
139
140
|
def invoke_editor(template)
|
|
140
141
|
text = nil
|
|
141
|
-
file = Tempfile.new('taco')
|
|
142
|
+
file = Tempfile.new('taco')
|
|
142
143
|
|
|
143
144
|
begin
|
|
144
145
|
file.write(template)
|
|
@@ -147,7 +148,7 @@ class IssueEditor
|
|
|
147
148
|
cmd = "$EDITOR #{file.path}"
|
|
148
149
|
system(cmd)
|
|
149
150
|
|
|
150
|
-
open(file.path) do |f|
|
|
151
|
+
open(file.path) do |f|
|
|
151
152
|
text = f.read
|
|
152
153
|
end
|
|
153
154
|
ensure
|
data/lib/taco/taco_profile.rb
CHANGED
|
@@ -4,30 +4,40 @@
|
|
|
4
4
|
|
|
5
5
|
class TacoProfile
|
|
6
6
|
attr_reader :sort_order, :filters, :columns
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
def initialize(text)
|
|
9
9
|
text ||= ''
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@sort_order = [ :created_at, :id ]
|
|
14
|
-
@columns = [ :short_id, :priority, :summary ]
|
|
15
|
-
@filters = []
|
|
16
|
-
|
|
10
|
+
|
|
11
|
+
@sort_order, @columns, @filters = nil, nil, nil
|
|
12
|
+
|
|
17
13
|
text.lines.each_with_index do |line, index|
|
|
18
14
|
next if line =~ /^#/ || line =~ /^\s*$/
|
|
19
|
-
|
|
15
|
+
|
|
20
16
|
key, value = line.split(':', 2).map(&:strip)
|
|
21
17
|
case key
|
|
22
18
|
when 'sort'
|
|
19
|
+
raise ArgumentError.new("sort defined more than once on line #{index+1}") if @sort_order
|
|
23
20
|
@sort_order = value.split(',').map(&:to_sym)
|
|
21
|
+
raise ArgumentError.new("Unknown Issue attribute in sort on line #{index+1}") unless @sort_order.all? { |attr| Issue.schema_attributes.include?(attr) }
|
|
24
22
|
when 'filters'
|
|
23
|
+
raise ArgumentError.new("filters defined more than once on line #{index+1}") if @filters
|
|
25
24
|
@filters = value.split(/\s/)
|
|
25
|
+
raise ArgumentError.new("Unknown Issue attribute in filters on line #{index+1}") unless @filters.all? { |token| attr, value = token.split(':'); Issue.schema_attributes.include?(attr.to_sym) }
|
|
26
26
|
when 'columns'
|
|
27
|
+
raise ArgumentError.new("columns defined more than once on line #{index+1}") if @columns
|
|
27
28
|
@columns = value.split(',').map(&:to_sym)
|
|
29
|
+
raise ArgumentError.new("Unknown Issue attribute in columns on line #{index+1}") unless @columns.all? do |attr|
|
|
30
|
+
# FIXME: really? hard-code :short_id? there's got to be a better way.
|
|
31
|
+
#
|
|
32
|
+
attr == :short_id || Issue.schema_attributes.include?(attr)
|
|
33
|
+
end
|
|
28
34
|
else
|
|
29
35
|
raise ArgumentError.new("Parse error on line #{index+1}")
|
|
30
36
|
end
|
|
31
37
|
end
|
|
38
|
+
|
|
39
|
+
@sort_order ||= [ :created_at, :id ]
|
|
40
|
+
@columns ||= [ :short_id, :priority, :summary ]
|
|
41
|
+
@filters ||= []
|
|
32
42
|
end
|
|
33
43
|
end
|
data/lib/taco/tacorc.rb
CHANGED
|
@@ -3,23 +3,22 @@
|
|
|
3
3
|
|
|
4
4
|
class TacoRc
|
|
5
5
|
class ParseError < Exception; end
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
def initialize(path)
|
|
8
8
|
raise ArgumentError.new("no such file: #{path}") unless File.exists? path
|
|
9
9
|
@path = path
|
|
10
10
|
end
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
def update_schema!(schema)
|
|
13
13
|
open(@path) do |f|
|
|
14
14
|
f.readlines.each_with_index do |line, index|
|
|
15
15
|
next if line =~ /^#/ || line =~ /^\s*$/
|
|
16
|
-
|
|
17
16
|
raise ParseError.new("Parse error on line #{index+1} of #{@path}: line does not begin with schema_attr_update") unless line =~ /^\s*schema_attr_update /
|
|
18
|
-
|
|
17
|
+
|
|
19
18
|
begin
|
|
20
19
|
eval "#{schema}.#{line.strip}"
|
|
21
20
|
rescue KeyError, TypeError, NameError => e
|
|
22
|
-
raise ParseError.new("Parse error on line #{index+1} of #{@path}: #{e.to_s}")
|
|
21
|
+
raise ParseError.new("Parse error on line #{index+1} of #{@path}: #{e.to_s}")
|
|
23
22
|
end
|
|
24
23
|
end
|
|
25
24
|
end
|
metadata
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: taco_it
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.5.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 1.5.3
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Mike Partelow
|
|
9
8
|
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date: 2013-
|
|
11
|
+
date: 2013-10-28 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: commander
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
none: false
|
|
18
16
|
requirements:
|
|
19
17
|
- - ! '>='
|
|
20
18
|
- !ruby/object:Gem::Version
|
|
@@ -22,7 +20,6 @@ dependencies:
|
|
|
22
20
|
type: :runtime
|
|
23
21
|
prerelease: false
|
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
-
none: false
|
|
26
23
|
requirements:
|
|
27
24
|
- - ! '>='
|
|
28
25
|
- !ruby/object:Gem::Version
|
|
@@ -68,26 +65,25 @@ files:
|
|
|
68
65
|
- bin/taco
|
|
69
66
|
homepage: http://github.com/mikepartelow/taco
|
|
70
67
|
licenses: []
|
|
68
|
+
metadata: {}
|
|
71
69
|
post_install_message:
|
|
72
70
|
rdoc_options: []
|
|
73
71
|
require_paths:
|
|
74
72
|
- lib
|
|
75
73
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
-
none: false
|
|
77
74
|
requirements:
|
|
78
75
|
- - ! '>='
|
|
79
76
|
- !ruby/object:Gem::Version
|
|
80
77
|
version: 1.9.3
|
|
81
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
|
-
none: false
|
|
83
79
|
requirements:
|
|
84
80
|
- - ! '>='
|
|
85
81
|
- !ruby/object:Gem::Version
|
|
86
82
|
version: '0'
|
|
87
83
|
requirements: []
|
|
88
84
|
rubyforge_project:
|
|
89
|
-
rubygems_version: 1.
|
|
85
|
+
rubygems_version: 2.1.4
|
|
90
86
|
signing_key:
|
|
91
|
-
specification_version:
|
|
87
|
+
specification_version: 4
|
|
92
88
|
summary: ! 'Taco Issue Tracker: A CLI Issue Tracker with JSON/filesystem backend'
|
|
93
89
|
test_files: []
|