jiraMule 0.1.2 → 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.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/README.md +2 -2
- data/Rakefile +20 -3
- data/TODO.taskpaper +0 -1
- data/jiraMule.gemspec +4 -2
- data/lib/JiraMule.rb +4 -2
- data/lib/JiraMule/StyleLoader.rb +323 -0
- data/lib/JiraMule/commands.rb +11 -0
- data/lib/{jiraMule → JiraMule}/commands/kanban.rb +30 -40
- data/lib/JiraMule/commands/open.rb +27 -0
- data/lib/JiraMule/commands/query.rb +74 -0
- data/lib/JiraMule/commands/statuses.rb +30 -0
- data/lib/{jiraMule → JiraMule}/jiraUtils.rb +19 -10
- data/lib/JiraMule/renderMagic.rb +30 -0
- data/lib/jiraMule/commands/goto.rb +0 -55
- data/lib/jiraMule/commands/logWork.rb +12 -8
- data/lib/jiraMule/http.rb +1 -1
- data/lib/jiraMule/version.rb +1 -1
- metadata +72 -46
- data/lib/JiraMule/Tempo.rb +0 -75
- data/lib/JiraMule/commands/githubImport.rb +0 -46
- data/lib/JiraMule/commands/timesheet.rb +0 -116
- data/lib/jiraMule/commands.rb +0 -20
- data/lib/jiraMule/commands/progress.rb +0 -68
- data/lib/jiraMule/commands/query.rb +0 -80
- data/lib/jiraMule/commands/release.rb +0 -44
- data/lib/jiraMule/commands/testReady.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbbaa7c560ccffc684631d28f866e682e8b16e22
|
4
|
+
data.tar.gz: 806095be43a2c49ffdeda778ea4e66a78510e006
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82151664ad86051f89463bebae844a54081373bd203d98fec2e92899bc4c044bd29aff46f2ee47007d73cd51f19acd399c2e5e858383f97b8ea0a32c39e10fc7
|
7
|
+
data.tar.gz: bdd85768fb65763a3dc03269fa715beef2d6c2a7e9c7db571b1e8c4e9a5ace6ce0286378ab1068e9776ef2de216d16343f035e893f500b4abe23d1453cfddc27
|
data/Gemfile
CHANGED
@@ -2,6 +2,7 @@ source 'http://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
+
gem 'chronic'
|
5
6
|
gem 'chronic_duration'
|
6
7
|
gem 'commander'
|
7
8
|
gem 'inifile', '~> 3.0'
|
@@ -9,7 +10,7 @@ gem 'mime-types', '~> 1.25.1'
|
|
9
10
|
gem 'mime-types-data', '~> 3.2016'
|
10
11
|
gem 'multipart-post'
|
11
12
|
gem 'mustache'
|
12
|
-
gem "octokit", "~> 4.0"
|
13
|
+
# gem "octokit", "~> 4.0"
|
13
14
|
gem 'terminal-table'
|
14
15
|
gem 'vine'
|
15
16
|
gem 'zip'
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
|
16
16
|
## Setup
|
17
17
|
|
18
|
-
You need to set a few config keys before
|
18
|
+
You need to set a few config keys before anything really works.
|
19
19
|
|
20
20
|
Globally:
|
21
21
|
```
|
@@ -33,5 +33,5 @@ jm config jira.project <Project ID>
|
|
33
33
|
|
34
34
|
#### Jira API Docs
|
35
35
|
|
36
|
-
[https://docs.atlassian.com/jira/REST/
|
36
|
+
[https://docs.atlassian.com/jira/REST/7.2.4/][]
|
37
37
|
|
data/Rakefile
CHANGED
@@ -42,14 +42,31 @@ namespace :git do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
task :gemit do
|
45
|
-
|
46
|
-
sh %{git checkout v#{mrt}}
|
45
|
+
sh %{git checkout #{tagName}}
|
47
46
|
Rake::Task[:build].invoke
|
48
|
-
Rake::Task[:bob].invoke
|
47
|
+
# Rake::Task[:bob].invoke
|
49
48
|
Rake::Task['push:gem'].invoke
|
50
49
|
sh %{git checkout develop}
|
51
50
|
end
|
52
51
|
|
52
|
+
namespace :release do
|
53
|
+
desc "Open a release branch"
|
54
|
+
task :open, [:version] do |t, args|
|
55
|
+
sh %{git checkout master}
|
56
|
+
sh %{git checkout -b release/v#{args[:version]}}
|
57
|
+
# - update version.rb
|
58
|
+
sh %{git commit lib/JiraMule/version.rb}
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Close a release branch"
|
62
|
+
task :close, [:version] do |t, args|
|
63
|
+
sh %{git checkout develop && git merge release/v#{args[:version]}}
|
64
|
+
sh %{git checkout master && git merge release/v#{args[:version]}}
|
65
|
+
sh %{git tag "v#{args[:version]}" -m "Release v#{args[:version]}"}
|
66
|
+
sh %{git branch -d release/v#{args[:version]}}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
53
70
|
namespace :push do
|
54
71
|
desc 'Push gem up to RubyGems'
|
55
72
|
task :gem do
|
data/TODO.taskpaper
CHANGED
data/jiraMule.gemspec
CHANGED
@@ -25,6 +25,7 @@ already. Rather this is specific to things I need.
|
|
25
25
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
26
26
|
s.require_paths = ['lib']
|
27
27
|
|
28
|
+
s.add_runtime_dependency('chronic', '~> 0.10.2')
|
28
29
|
s.add_runtime_dependency('chronic_duration', '~> 0.10.6')
|
29
30
|
s.add_runtime_dependency('commander', '~> 4.4.0')
|
30
31
|
s.add_runtime_dependency('inifile', '~> 3.0')
|
@@ -32,12 +33,13 @@ already. Rather this is specific to things I need.
|
|
32
33
|
s.add_runtime_dependency('mime-types-data', '~> 3.2016')
|
33
34
|
s.add_runtime_dependency('multipart-post', '~> 2.0.0')
|
34
35
|
s.add_runtime_dependency('mustache', '~> 1.0')
|
36
|
+
s.add_runtime_dependency('octokit', '~> 4.0')
|
35
37
|
s.add_runtime_dependency('terminal-table', '~> 1.4.5')
|
36
38
|
s.add_runtime_dependency('vine', '~> 0.2')
|
37
39
|
s.add_runtime_dependency('zip', '~> 2.0.0')
|
38
40
|
|
39
|
-
s.add_development_dependency('bundler'
|
40
|
-
s.add_development_dependency('rspec'
|
41
|
+
s.add_development_dependency('bundler')
|
42
|
+
s.add_development_dependency('rspec')
|
41
43
|
s.add_development_dependency('rake')
|
42
44
|
end
|
43
45
|
|
data/lib/JiraMule.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'JiraMule/version'
|
2
2
|
require 'JiraMule/Config'
|
3
|
-
|
3
|
+
|
4
4
|
require 'JiraMule/gitUtils'
|
5
|
-
require 'JiraMule/
|
5
|
+
require 'JiraMule/jiraUtils'
|
6
|
+
require 'JiraMule/renderMagic'
|
7
|
+
require 'JiraMule/StyleLoader'
|
6
8
|
|
7
9
|
require 'JiraMule/commands'
|
@@ -0,0 +1,323 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module JiraMule
|
4
|
+
# Style is a formula for formatting the results of a query.
|
5
|
+
# This currently only works with single queries.
|
6
|
+
class Style
|
7
|
+
def initialize(name, &block)
|
8
|
+
@name = name.to_sym
|
9
|
+
@fields = [:key, :summary]
|
10
|
+
@header = [:key, :summary]
|
11
|
+
@footer = nil
|
12
|
+
@format_type = :strings
|
13
|
+
@format = %{{{key}} {{summary}}}
|
14
|
+
|
15
|
+
@custom_tags = {}
|
16
|
+
|
17
|
+
@prefix_query = nil
|
18
|
+
@default_query = nil
|
19
|
+
@suffix_query = nil
|
20
|
+
|
21
|
+
@bolden_tag = :bolden
|
22
|
+
|
23
|
+
block.call(self) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
######################################################
|
27
|
+
def self.add(style, &block)
|
28
|
+
@@styles = {} unless defined?(@@styles)
|
29
|
+
|
30
|
+
if style.kind_of?(String) or style.kind_of?(Symbol) then
|
31
|
+
style = Style.new(style, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
@@styles[style.name] = style
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.fetch(name)
|
38
|
+
@@styles[name.to_sym]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.list
|
42
|
+
@@styles.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
######################################################
|
46
|
+
# Apply this style to Issues
|
47
|
+
# @param issues [Array] Issues from the Jira query to format
|
48
|
+
# @return formatted string
|
49
|
+
def apply(issues)
|
50
|
+
if @format_type == :strings then
|
51
|
+
keys = issues.map do |issue|
|
52
|
+
fmt = @format
|
53
|
+
fmt = fmt.join(' ') if fmt.kind_of? Array
|
54
|
+
res = JiraMule::IssueRender.render(fmt, issue.merge(issue[:fields]), @custom_tags)
|
55
|
+
bolden(issue, res)
|
56
|
+
end
|
57
|
+
(@header or '').to_s + keys.join("\n") + (@footer or '').to_s
|
58
|
+
|
59
|
+
elsif [:table, :table_rows, :table_columns].include? @format_type then
|
60
|
+
@format = [@format] unless @format.kind_of? Array
|
61
|
+
rows = issues.map do |issue|
|
62
|
+
issue = issue.merge(issue[:fields])
|
63
|
+
@format.map do |col|
|
64
|
+
if col.kind_of? Hash then
|
65
|
+
col = col.dup
|
66
|
+
str = col[:value] or ""
|
67
|
+
res = JiraMule::IssueRender.render(str, issue, @custom_tags)
|
68
|
+
col[:value] = bolden(issue, res)
|
69
|
+
col
|
70
|
+
else
|
71
|
+
res = JiraMule::IssueRender.render(col, issue, @custom_tags)
|
72
|
+
bolden(issue, res)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
if @format_type == :table_columns then
|
77
|
+
rows = rows.transpose
|
78
|
+
end
|
79
|
+
header = (@header or [])
|
80
|
+
header = [header] unless header.kind_of? Array
|
81
|
+
Terminal::Table.new :headings => header, :rows=>rows
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# If this issue should be bolded or not.
|
86
|
+
def bolden(issue, row, color=:bold)
|
87
|
+
bld = issue[@bolden_tag]
|
88
|
+
bld = @custom_tags[@bolden_tag] if bld.nil?
|
89
|
+
bld = bld.call(issue.dup) if bld.kind_of? Proc
|
90
|
+
# ? truthy other than Ruby default?
|
91
|
+
return row unless bld
|
92
|
+
if row.kind_of? Array then
|
93
|
+
row.map{|r| HighLine.color(r, color)}
|
94
|
+
elsif row.kind_of? Hash then
|
95
|
+
hsh={}
|
96
|
+
row.each_pair{|k,v| hsh[k] = HighLine.color(v, color)}
|
97
|
+
else
|
98
|
+
HighLine.color(row.to_s, color)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# TODO: Dump method that outputs Ruby
|
103
|
+
|
104
|
+
# Build a query based on this Style and other bits from command line
|
105
|
+
# @param args [Array<String>] Other bits of JQL to use instead of default_query
|
106
|
+
def build_query(*args)
|
107
|
+
opts = {}
|
108
|
+
opts = args.pop if args.last.kind_of? Hash
|
109
|
+
|
110
|
+
# Add the default query, if there is one
|
111
|
+
if not @default_query.nil? then
|
112
|
+
# make sure there is an AND if working with a cmdline supplied part
|
113
|
+
args.push('AND') unless args.empty?
|
114
|
+
case @default_query
|
115
|
+
when Array
|
116
|
+
args.push @default_query.join(' AND ')
|
117
|
+
when Proc
|
118
|
+
args.push @default_query.call()
|
119
|
+
else
|
120
|
+
args.push @default_query.to_s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get prefix as a String.
|
125
|
+
case @prefix_query
|
126
|
+
when Array
|
127
|
+
prefix = @prefix_query.join(' AND ') + ' AND'
|
128
|
+
when Proc
|
129
|
+
prefix = @prefix_query.call()
|
130
|
+
else
|
131
|
+
prefix = @prefix_query.to_s
|
132
|
+
end
|
133
|
+
args.unshift(prefix) unless opts.has_key? :noprefix
|
134
|
+
|
135
|
+
# Get suffix as a String.
|
136
|
+
case @suffix_query
|
137
|
+
when Array
|
138
|
+
suffix = 'AND ' + @suffix_query.join(' AND ')
|
139
|
+
when Proc
|
140
|
+
suffix = @suffix_query.call()
|
141
|
+
else
|
142
|
+
suffix = @suffix_query.to_s
|
143
|
+
end
|
144
|
+
args.push(suffix) unless opts.has_key? :nosuffix
|
145
|
+
|
146
|
+
args.flatten.compact.join(' ')
|
147
|
+
end
|
148
|
+
|
149
|
+
# May need to split this into two classes. One that is the above methods
|
150
|
+
# and one that is the below methods. The below one is used just for the
|
151
|
+
# construction of a Style. While the above is the usage of a style.
|
152
|
+
#
|
153
|
+
# Maybe the above are in a Module, that is included as part of fetch?
|
154
|
+
######################################################
|
155
|
+
|
156
|
+
attr_accessor :prefix_query, :suffix_query, :default_query
|
157
|
+
attr_accessor :header
|
158
|
+
|
159
|
+
def name
|
160
|
+
@name
|
161
|
+
end
|
162
|
+
|
163
|
+
# takes a single flat array of key names.
|
164
|
+
def fields(*args)
|
165
|
+
return @fields if args.empty?
|
166
|
+
@fields = args.flatten.compact.map{|i| i.to_sym}
|
167
|
+
end
|
168
|
+
alias_method :fields=, :fields
|
169
|
+
|
170
|
+
FORMAT_TYPES = [:strings, :table_rows, :table_columns, :table].freeze
|
171
|
+
def format_type(type)
|
172
|
+
return @format_type if type.nil?
|
173
|
+
raise "Unknown format type: \"#{type}\"" unless FORMAT_TYPES.include? type
|
174
|
+
@format_type = type
|
175
|
+
end
|
176
|
+
alias_method :format_type=, :format_type
|
177
|
+
|
178
|
+
def format(*args)
|
179
|
+
return @format if args.empty?
|
180
|
+
args.flatten! if args.kind_of? Array
|
181
|
+
@format = args
|
182
|
+
end
|
183
|
+
alias_method :format=, :format
|
184
|
+
|
185
|
+
# Create a custom tag for formatted output
|
186
|
+
def add_tag(name, &block)
|
187
|
+
@custom_tags[name.to_sym] = block
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
##############################################################################
|
193
|
+
##############################################################################
|
194
|
+
##############################################################################
|
195
|
+
|
196
|
+
Style.add(:basic) do |s|
|
197
|
+
s.fields [:key, :summary]
|
198
|
+
s.format %{{{key}} {{summary}}}
|
199
|
+
s.header = nil
|
200
|
+
end
|
201
|
+
|
202
|
+
Style.add(:pr) do |s|
|
203
|
+
s.fields [:key, :summary]
|
204
|
+
s.format %{{{summary}} ({{key}})}
|
205
|
+
s.header = nil
|
206
|
+
end
|
207
|
+
|
208
|
+
Style.add(:info) do |s|
|
209
|
+
s.fields [:key, :summary, :description, :assignee, :reporter, :priority,
|
210
|
+
:issuetype, :status, :resolution, :votes, :watches]
|
211
|
+
s.header = nil
|
212
|
+
s.format %{ Key: {{key}}
|
213
|
+
Summary: {{summary}}
|
214
|
+
Reporter: {{reporter.displayName}}
|
215
|
+
Assignee: {{assignee.displayName}}
|
216
|
+
Type: {{issuetype.name}} ({{priority.name}})
|
217
|
+
Status: {{status.name}} (Resolution: {{resolution.name}})
|
218
|
+
Watches: {{watches.watchCount}} Votes: {{votes.votes}}
|
219
|
+
Description: {{description}}
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
Style.add(:test_table) do |s|
|
224
|
+
s.fields [:key, :assignee]
|
225
|
+
s.format_type :table_columns
|
226
|
+
s.header = nil
|
227
|
+
s.format [%{{{key}}}, %{{{assignee.displayName}}}]
|
228
|
+
end
|
229
|
+
|
230
|
+
Style.add(:progress) do |s|
|
231
|
+
s.fields [:key, :workratio, :aggregatetimespent, :duedate,
|
232
|
+
:aggregatetimeoriginalestimate]
|
233
|
+
s.format_type :table_rows
|
234
|
+
s.header = [:key, :estimated, :progress, :percent, :due]
|
235
|
+
s.format [%{{{key}}},
|
236
|
+
{:value=>%{{{estimate}}},:alignment=>:right},
|
237
|
+
{:value=>%{{{progress}}},:alignment=>:right},
|
238
|
+
{:value=>%{{{percent}}%},:alignment=>:right},
|
239
|
+
{:value=>%{{{duedate}}},:alignment=>:center},
|
240
|
+
]
|
241
|
+
# Use lambda when there is logic that needs to be deferred.
|
242
|
+
s.prefix_query = lambda do
|
243
|
+
r = []
|
244
|
+
r << %{assignee = #{$cfg['user.name']}} unless $cfg['user.name'].nil?
|
245
|
+
prjs = $cfg['jira.project']
|
246
|
+
unless prjs.nil? then
|
247
|
+
r << '(' + prjs.split(' ').map{|prj| %{project = #{prj}}}.join(' OR ') + ')'
|
248
|
+
end
|
249
|
+
r.join(' AND ') + ' AND'
|
250
|
+
end
|
251
|
+
s.default_query = %{(status = "In Progress" OR status = "In Design/Estimation")}
|
252
|
+
s.suffix_query = %{ORDER BY Rank}
|
253
|
+
|
254
|
+
s.add_tag(:bolden) do |issue|
|
255
|
+
estimate = (issue[:aggregatetimeoriginalestimate] or 0)
|
256
|
+
progress = (issue[:aggregatetimespent] or 0)
|
257
|
+
due = issue[:duedate]
|
258
|
+
progress > estimate or (not due.nil? and Date.new >= Date.parse(due))
|
259
|
+
end
|
260
|
+
s.add_tag(:estimate) do |issue|
|
261
|
+
"%.2f"%[(issue[:aggregatetimeoriginalestimate] or 0) / 3600.0]
|
262
|
+
end
|
263
|
+
s.add_tag(:progress) do |issue|
|
264
|
+
"%.2f"%[(issue[:aggregatetimespent] or 0) / 3600.0]
|
265
|
+
end
|
266
|
+
s.add_tag(:percent) do |issue|
|
267
|
+
percent = issue[:workratio]
|
268
|
+
if percent < 0 then
|
269
|
+
estimate = (issue[:aggregatetimeoriginalestimate] or 0) / 3600.0
|
270
|
+
if estimate > 0 then
|
271
|
+
progress = (issue[:aggregatetimespent] or 0) / 3600.0
|
272
|
+
percent = (progress / estimate * 100).floor
|
273
|
+
else
|
274
|
+
percent = 100 # XXX ??? what is this line doing? why is it here?
|
275
|
+
end
|
276
|
+
end
|
277
|
+
if percent > 1000 then
|
278
|
+
">1000"
|
279
|
+
else
|
280
|
+
"%.1f"%[percent]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
Style.add(:todo) do |s|
|
286
|
+
s.fields [:key, :summary]
|
287
|
+
s.header = "## Todo\n"
|
288
|
+
s.format %{- {{fixkey}}\t{{summary}}}
|
289
|
+
|
290
|
+
# Use lambda when there is logic that needs to be deferred.
|
291
|
+
s.prefix_query = lambda do
|
292
|
+
r = []
|
293
|
+
r << %{assignee = #{$cfg['user.name']}} unless $cfg['user.name'].nil?
|
294
|
+
prjs = $cfg['jira.project']
|
295
|
+
unless prjs.nil? then
|
296
|
+
r << '(' + prjs.split(' ').map{|prj| %{project = #{prj}}}.join(' OR ') + ')'
|
297
|
+
end
|
298
|
+
r.join(' AND ') + ' AND'
|
299
|
+
end
|
300
|
+
s.default_query = '(' + [
|
301
|
+
%{status = "On Deck"},
|
302
|
+
].join(' OR ') + ')'
|
303
|
+
s.suffix_query = %{ORDER BY Rank}
|
304
|
+
|
305
|
+
s.add_tag(:fixkey) do |issue|
|
306
|
+
"%-10s" % issue[:key]
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
Style.add(:comments) do |s|
|
312
|
+
s.fields [:key, :comment]
|
313
|
+
s.header = nil
|
314
|
+
s.format %{{{#comment.comments}}
|
315
|
+
---------------------------------------------
|
316
|
+
> {{author.displayName}} {{created}} {{editied}}
|
317
|
+
{{body}}
|
318
|
+
{{/comment.comments}}
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
# vim: set ai et sw=2 ts=2 :
|