jirify 0.2.1 → 0.2.2
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/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
|