jirify 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -3
- data/lib/jirify/cli/issue.rb +10 -4
- data/lib/jirify/cli/sprint.rb +8 -7
- data/lib/jirify/config.rb +2 -2
- data/lib/jirify/ui/issue_renderer.rb +100 -0
- data/lib/jirify/ui/sprint_table.rb +57 -32
- data/lib/jirify/version.rb +1 -1
- data/lib/jirify.rb +1 -1
- metadata +3 -23
- data/lib/jirify/ui/sprint_cell.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07d9648da98d02b0aea77481668a386b9182bdf777ca242ad61c6e5c7e793602
|
4
|
+
data.tar.gz: 494f816f3e49f388343949ea025fd615065e895f0f8036801b85f120206197d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bff2d97e9fe24848423aa4b3f4108e53bba6953603a04b3fe703c9aeba4ccec242a0f3d5308d3889b7efae71cecc2a119ba3003c186d78b663ec55a6ec4738a
|
7
|
+
data.tar.gz: 71c8810f34cd135794a64a492b0510d8a220e7ef9868e3abde4c1a601129c43e66182fbb17a0ad899baabae186a450d3cc11fdb2c43e69fbe91c35de5c2933c9
|
data/README.md
CHANGED
@@ -8,14 +8,17 @@ A simple ruby gem that helps me work with jira
|
|
8
8
|
## Installation
|
9
9
|
1. Run `gem install jirify`.
|
10
10
|
1. Execute `jira setup` and go through the setup process.
|
11
|
-
1. Optionally source `$HOME/.jirify/jirify.bash_completion.sh` to have
|
11
|
+
1. Optionally source `$HOME/.jirify/jirify.bash_completion.sh` to have
|
12
|
+
autocomplete in bash.
|
12
13
|
1. Execute `jira` and `jira <command> help` to learn about available commands.
|
13
14
|
|
14
15
|
## Config Explained
|
15
16
|
Currently, the config structure of `jirify` is:
|
16
17
|
- `$HOME/.jirify` folder that contains:
|
17
18
|
- `.jirify` - yaml file generated by `jira setup`
|
18
|
-
- `jirify.bash_completion.sh` - bash completion script you can source.
|
19
|
+
- `jirify.bash_completion.sh` - bash completion script you can source.
|
20
|
+
This is placed here by `jira setup`, so if you don't see it or you want to
|
21
|
+
refresh it, run `jira setup` again.
|
19
22
|
- `.cache` - cache for completion script
|
20
23
|
|
21
24
|
### Config file: `$HOME/.jirify/.jirify`
|
@@ -31,4 +34,18 @@ options:
|
|
31
34
|
```
|
32
35
|
|
33
36
|
## To Do
|
34
|
-
-
|
37
|
+
- Tests:
|
38
|
+
- [ ] Test CLI classes.
|
39
|
+
- [ ] Test UI classes.
|
40
|
+
- [ ] Test `Models::Issue`.
|
41
|
+
- [ ] Test `Models::Transition`, `Models::BaseList` (?).
|
42
|
+
- [ ] Test `Config::copy_bash_completion!`.
|
43
|
+
- [ ] Refactor Models - `BaseList` doesn't make much sense.
|
44
|
+
- [x] ~~Refactor `SprintCell`:~~
|
45
|
+
- [x] ~~It should be a more general `IssueRenderer`.~~
|
46
|
+
- [x] ~~Use it for `jira issues describe` with additional option to draw border.~~
|
47
|
+
- [ ] Adjust `jira issues mine` to terminal width.
|
48
|
+
- [ ] Add ability to define mapping between custom statuses and custom
|
49
|
+
transitions in config.
|
50
|
+
- [ ] Add ability to specify custom filters when listing issues (both
|
51
|
+
`jira sprint` and `jira issues`)
|
data/lib/jirify/cli/issue.rb
CHANGED
@@ -47,14 +47,20 @@ module Jirify
|
|
47
47
|
desc 'describe [ISSUE]', 'Describes an issue'
|
48
48
|
def describe(issue_id)
|
49
49
|
issue = get_issue_or_exit issue_id
|
50
|
-
|
50
|
+
|
51
|
+
issue_renderer = UI::IssueRenderer.new(issue)
|
51
52
|
|
52
53
|
options = {}
|
54
|
+
|
53
55
|
options[:url] = true
|
54
56
|
options[:summary] = true
|
55
57
|
options[:assignee] = true
|
56
|
-
|
57
|
-
|
58
|
+
begin
|
59
|
+
say issue_renderer.as_card(options)
|
60
|
+
rescue UI::WindowTooNarrow
|
61
|
+
say ColorizedString['ERROR: Your terminal window is too narrow to print the sprint table!']
|
62
|
+
.white.on_red.bold
|
63
|
+
end
|
58
64
|
end
|
59
65
|
|
60
66
|
#-------------------------#
|
@@ -214,7 +220,7 @@ module Jirify
|
|
214
220
|
issue = Models::Issue.find_by_id(issue_id)
|
215
221
|
|
216
222
|
if issue.nil?
|
217
|
-
say ColorizedString['
|
223
|
+
say ColorizedString['ERROR: Issue not found'].white.on_red.bold
|
218
224
|
exit(0)
|
219
225
|
else
|
220
226
|
issue
|
data/lib/jirify/cli/sprint.rb
CHANGED
@@ -10,15 +10,16 @@ module Jirify
|
|
10
10
|
verbose = Config.always_verbose || options[:verbose]
|
11
11
|
issues = Models::Sprint.issues_in_current_sprint(options[:mine])
|
12
12
|
|
13
|
-
|
13
|
+
modified_options = options.dup
|
14
14
|
if verbose
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
modified_options[:assignee] = true
|
16
|
+
modified_options[:url] = true
|
17
|
+
modified_options[:summary] = true
|
18
18
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
|
20
|
+
say UI::SprintTable.new(issues).to_table(modified_options)
|
21
|
+
rescue UI::WindowTooNarrow
|
22
|
+
say ColorizedString['ERROR: Your terminal window is too narrow to print the sprint table!']
|
22
23
|
.white.on_red.bold
|
23
24
|
end
|
24
25
|
end
|
data/lib/jirify/config.rb
CHANGED
@@ -40,7 +40,7 @@ module Jirify
|
|
40
40
|
|
41
41
|
def verbose=(value)
|
42
42
|
unless initialized?
|
43
|
-
puts ColorizedString['
|
43
|
+
puts ColorizedString['ERROR: You must initialize Jirify first!'].white.on_red.bold
|
44
44
|
exit(0)
|
45
45
|
end
|
46
46
|
|
@@ -51,7 +51,7 @@ module Jirify
|
|
51
51
|
|
52
52
|
def options
|
53
53
|
unless initialized?
|
54
|
-
puts ColorizedString['
|
54
|
+
puts ColorizedString['ERROR: You must initialize Jirify first!'].white.on_red.bold
|
55
55
|
exit(0)
|
56
56
|
end
|
57
57
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Jirify
|
2
|
+
module UI
|
3
|
+
class WindowTooNarrow < StandardError; end
|
4
|
+
|
5
|
+
class IssueRenderer
|
6
|
+
attr_reader :issue
|
7
|
+
|
8
|
+
def initialize(issue)
|
9
|
+
@issue = issue
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_table_cell(options)
|
13
|
+
options[:max_length] ||= IO.console.winsize[1] - 4
|
14
|
+
options[:summary] ||= false
|
15
|
+
options[:url] ||= false
|
16
|
+
options[:assignee] ||= false
|
17
|
+
|
18
|
+
raise UI::WindowTooNarrow, 'The terminal window is too narrow.' if options[:max_length] <= key.size
|
19
|
+
|
20
|
+
row = ''
|
21
|
+
row << "#{ColorizedString[key].bold}"
|
22
|
+
row << summary_line(options) if show_summary?(options)
|
23
|
+
row << url_line if show_url?(options)
|
24
|
+
row << assignee_line(options) if show_assignee?(options)
|
25
|
+
row
|
26
|
+
end
|
27
|
+
|
28
|
+
def as_card(options)
|
29
|
+
card = as_table_cell(options)
|
30
|
+
Terminal::Table.new(rows: [[card]])
|
31
|
+
rescue RuntimeError
|
32
|
+
raise UI::WindowTooNarrow, 'The terminal window is too narrow.'
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def key
|
38
|
+
issue.key
|
39
|
+
end
|
40
|
+
|
41
|
+
def summary
|
42
|
+
issue.summary
|
43
|
+
end
|
44
|
+
|
45
|
+
def show_assignee?(options)
|
46
|
+
options[:assignee] && !options[:mine]
|
47
|
+
end
|
48
|
+
|
49
|
+
def assignee
|
50
|
+
if issue.assignee.nil?
|
51
|
+
'Unassigned'
|
52
|
+
else
|
53
|
+
issue.assignee.displayName
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def assignee_line(options)
|
58
|
+
max_length = options[:max_length]
|
59
|
+
assignee_line = assignee
|
60
|
+
assignee_line = "#{assignee[0...max_length - 3]}..." if assignee_line.size >= max_length
|
61
|
+
|
62
|
+
"\n#{assignee_line.magenta}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def show_url?(options)
|
66
|
+
url.size <= options[:max_length] && options[:url]
|
67
|
+
end
|
68
|
+
|
69
|
+
def url
|
70
|
+
"#{Config.issue_browse_url}#{issue.key}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def url_line
|
74
|
+
"\n#{ColorizedString[url].blue.underline}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def show_summary?(options)
|
78
|
+
options[:summary]
|
79
|
+
end
|
80
|
+
|
81
|
+
def summary_line(options)
|
82
|
+
"\n#{wrap(summary.strip, options[:max_length])}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def wrap(string, columns, character = "\n")
|
86
|
+
return '' if string.nil?
|
87
|
+
|
88
|
+
start_pos = columns
|
89
|
+
while start_pos < string.length
|
90
|
+
space = string.rindex(' ', start_pos) || start_pos - 1
|
91
|
+
string.slice!(space)
|
92
|
+
string.insert(space, character)
|
93
|
+
start_pos = space + columns + 1
|
94
|
+
end
|
95
|
+
|
96
|
+
string
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -9,11 +9,18 @@ module Jirify
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def to_table(options)
|
12
|
+
return '' if issues.empty?
|
13
|
+
|
14
|
+
# group issues by status name
|
12
15
|
grouped_issues = issues.group_by do |issue|
|
13
16
|
options[:all_columns] ? issue.status.name : issue.status.statusCategory['name']
|
14
17
|
end
|
15
18
|
|
16
|
-
|
19
|
+
Terminal::Table.new(
|
20
|
+
headings: headings(grouped_issues),
|
21
|
+
rows: issues_as_rows(grouped_issues.values, options),
|
22
|
+
style: table_style
|
23
|
+
)
|
17
24
|
end
|
18
25
|
|
19
26
|
protected
|
@@ -26,63 +33,81 @@ module Jirify
|
|
26
33
|
{ width: terminal_width, border_x: '-', border_i: 'x' }
|
27
34
|
end
|
28
35
|
|
36
|
+
def get_max_cell_length(grouped_issues)
|
37
|
+
col_padding_per_row = grouped_issues.size * 3 + 1
|
38
|
+
(terminal_width - col_padding_per_row) / grouped_issues.size
|
39
|
+
end
|
40
|
+
|
29
41
|
def headings(grouped_issues)
|
30
|
-
|
42
|
+
max_cell_length = get_max_cell_length(grouped_issues)
|
43
|
+
|
44
|
+
sorted_headings = sort_headings(grouped_issues.keys)
|
45
|
+
sorted_headings.map do |heading|
|
46
|
+
original_heading = heading
|
47
|
+
heading = fit_heading_to_cell(heading, max_cell_length)
|
48
|
+
|
49
|
+
case original_heading
|
50
|
+
when Config.statuses['todo'] then ColorizedString[heading].white.on_black.bold
|
51
|
+
when Config.statuses['in_progress'] then ColorizedString[heading].white.on_blue.bold
|
52
|
+
when Config.statuses['in_review'] then ColorizedString[heading].white.on_yellow.bold
|
53
|
+
when Config.statuses['blocked'] then ColorizedString[heading].white.on_red.bold
|
54
|
+
when Config.statuses['done'] then ColorizedString[heading].white.on_green.bold
|
55
|
+
else ColorizedString[heading].white.on_green.bold
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def sort_headings(heading_names)
|
61
|
+
heading_names.sort_by! do |name|
|
31
62
|
status_index = Models::Status.status_order.index(name)
|
32
63
|
if status_index.nil?
|
64
|
+
# If status is not defined in order, put it at the end
|
33
65
|
grouped_issues.keys.length
|
34
66
|
else
|
35
67
|
status_index
|
36
68
|
end
|
37
69
|
end
|
38
|
-
|
39
|
-
sorted_headings.map! do |name|
|
40
|
-
case name
|
41
|
-
when Config.statuses['todo'] then ColorizedString[name].white.on_black.bold
|
42
|
-
when Config.statuses['in_progress'] then ColorizedString[name].white.on_blue.bold
|
43
|
-
when Config.statuses['in_review'] then ColorizedString[name].white.on_yellow.bold
|
44
|
-
when Config.statuses['blocked'] then ColorizedString[name].white.on_red.bold
|
45
|
-
when Config.statuses['done'] then ColorizedString[name].white.on_green.bold
|
46
|
-
else ColorizedString[name].white.on_green.bold
|
47
|
-
end
|
48
|
-
end
|
49
70
|
end
|
50
71
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
72
|
+
def fit_heading_to_cell(name, max_cell_length)
|
73
|
+
if name.length >= max_cell_length
|
74
|
+
# If the heading name is longer than the max length, add ellipsis.
|
75
|
+
"#{name[0...max_cell_length - 3]}..."
|
76
|
+
else
|
77
|
+
# Add spaces around the heading name to center it.
|
78
|
+
buffer = max_cell_length - name.length
|
79
|
+
left = ' ' * (buffer / 2)
|
80
|
+
right = ' ' * (buffer / 2)
|
81
|
+
right += ' ' if buffer.odd?
|
82
|
+
|
83
|
+
"#{left}#{name}#{right}"
|
84
|
+
end
|
60
85
|
end
|
61
86
|
|
62
|
-
def
|
63
|
-
|
64
|
-
max_cell_length = (terminal_width - col_padding_per_row) / grouped_issues.size
|
87
|
+
def issues_as_rows(grouped_issues, options)
|
88
|
+
options[:max_length] = get_max_cell_length(grouped_issues)
|
65
89
|
|
66
|
-
#
|
90
|
+
# Workaround - not all groups have the same size.
|
67
91
|
l = grouped_issues.map(&:length).max
|
68
92
|
grouped_as_array = grouped_issues.map { |e| e.values_at(0...l) }
|
69
93
|
|
70
|
-
|
71
|
-
|
72
|
-
issue.status
|
73
|
-
end
|
74
|
-
|
94
|
+
# Sort columns by status and transpose them to become rows.
|
95
|
+
grouped_as_array.sort_by! { |row| row.first.status }
|
75
96
|
transposed = grouped_as_array.transpose
|
76
97
|
|
98
|
+
# Map every Issue in every row to its display representation.
|
77
99
|
transposed.map! do |row|
|
78
100
|
row.map do |issue|
|
79
101
|
next if issue.nil?
|
80
|
-
|
102
|
+
IssueRenderer.new(issue).as_table_cell(options)
|
81
103
|
end
|
82
104
|
end
|
83
105
|
|
106
|
+
# add separators after each row
|
84
107
|
transposed = transposed.zip([:separator] * transposed.size).flatten(1)
|
108
|
+
# remove the separator after the last row, it's not needed
|
85
109
|
transposed.pop
|
110
|
+
|
86
111
|
transposed
|
87
112
|
end
|
88
113
|
end
|
data/lib/jirify/version.rb
CHANGED
data/lib/jirify.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jirify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Georgi Gardev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-07-
|
11
|
+
date: 2018-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -166,26 +166,6 @@ dependencies:
|
|
166
166
|
- - "~>"
|
167
167
|
- !ruby/object:Gem::Version
|
168
168
|
version: '12.1'
|
169
|
-
- !ruby/object:Gem::Dependency
|
170
|
-
name: rake-release
|
171
|
-
requirement: !ruby/object:Gem::Requirement
|
172
|
-
requirements:
|
173
|
-
- - "~>"
|
174
|
-
- !ruby/object:Gem::Version
|
175
|
-
version: '1.0'
|
176
|
-
- - ">="
|
177
|
-
- !ruby/object:Gem::Version
|
178
|
-
version: 1.0.1
|
179
|
-
type: :development
|
180
|
-
prerelease: false
|
181
|
-
version_requirements: !ruby/object:Gem::Requirement
|
182
|
-
requirements:
|
183
|
-
- - "~>"
|
184
|
-
- !ruby/object:Gem::Version
|
185
|
-
version: '1.0'
|
186
|
-
- - ">="
|
187
|
-
- !ruby/object:Gem::Version
|
188
|
-
version: 1.0.1
|
189
169
|
- !ruby/object:Gem::Dependency
|
190
170
|
name: rspec
|
191
171
|
requirement: !ruby/object:Gem::Requirement
|
@@ -280,7 +260,7 @@ files:
|
|
280
260
|
- lib/jirify/models/transition.rb
|
281
261
|
- lib/jirify/models/transition_list.rb
|
282
262
|
- lib/jirify/monkey_patches/jira_issue.rb
|
283
|
-
- lib/jirify/ui/
|
263
|
+
- lib/jirify/ui/issue_renderer.rb
|
284
264
|
- lib/jirify/ui/sprint_table.rb
|
285
265
|
- lib/jirify/version.rb
|
286
266
|
homepage: https://github.com/GeorgeSG/jirify
|
@@ -1,84 +0,0 @@
|
|
1
|
-
module Jirify
|
2
|
-
module UI
|
3
|
-
class WindowTooNarrow < StandardError; end
|
4
|
-
|
5
|
-
class SprintCell
|
6
|
-
attr_reader :issue, :max_cell_length
|
7
|
-
|
8
|
-
def initialize(issue, max_cell_length)
|
9
|
-
@issue = issue
|
10
|
-
@max_cell_length = max_cell_length
|
11
|
-
end
|
12
|
-
|
13
|
-
def to_s(options)
|
14
|
-
raise UI::WindowTooNarrow, 'The terminal window is too narrow.' if max_cell_length <= key.size
|
15
|
-
|
16
|
-
row = ''
|
17
|
-
row << key_and_summary_lines(options, max_cell_length)
|
18
|
-
row << "\n#{ColorizedString[url].blue.underline}" if display_url?(options)
|
19
|
-
row << assignee_line(max_cell_length) if show_assignee?(options)
|
20
|
-
row
|
21
|
-
end
|
22
|
-
|
23
|
-
protected
|
24
|
-
|
25
|
-
def key
|
26
|
-
issue.key
|
27
|
-
end
|
28
|
-
|
29
|
-
def summary
|
30
|
-
issue.summary
|
31
|
-
end
|
32
|
-
|
33
|
-
def show_assignee?(options)
|
34
|
-
options[:assignee] && !options[:mine]
|
35
|
-
end
|
36
|
-
|
37
|
-
def assignee
|
38
|
-
if issue.assignee.nil?
|
39
|
-
'Unassigned'
|
40
|
-
else
|
41
|
-
issue.assignee.displayName
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def url
|
46
|
-
"#{Config.issue_browse_url}#{issue.key}"
|
47
|
-
end
|
48
|
-
|
49
|
-
def display_url?(options)
|
50
|
-
url.size <= max_cell_length && options[:url]
|
51
|
-
end
|
52
|
-
|
53
|
-
def wrap(string, columns, character = "\n")
|
54
|
-
return '' if string.nil?
|
55
|
-
|
56
|
-
start_pos = columns
|
57
|
-
while start_pos < string.length
|
58
|
-
space = string.rindex(' ', start_pos) || start_pos + 1
|
59
|
-
string.slice!(space)
|
60
|
-
string.insert(space, character)
|
61
|
-
start_pos = space + columns + 1
|
62
|
-
end
|
63
|
-
|
64
|
-
string
|
65
|
-
end
|
66
|
-
|
67
|
-
def key_and_summary_lines(options, max_cell_length)
|
68
|
-
bold_key = ColorizedString[key].bold
|
69
|
-
return bold_key unless options[:summary]
|
70
|
-
|
71
|
-
row = "#{bold_key}\n"
|
72
|
-
row << wrap(summary.strip, max_cell_length)
|
73
|
-
row
|
74
|
-
end
|
75
|
-
|
76
|
-
def assignee_line(max_cell_length)
|
77
|
-
assignee_line = assignee
|
78
|
-
assignee_line = "#{assignee[0...max_cell_length - 3]}..." if assignee_line.size >= max_cell_length
|
79
|
-
|
80
|
-
"\n#{assignee_line.magenta}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|