redmine_cli 0.2.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/README.md +2 -0
- data/bin/console +2 -0
- data/lib/assets/default_config.yml +1 -0
- data/lib/assets/messages/en.yml +19 -0
- data/lib/assets/templates/{issue → en/issue}/list.erb +0 -0
- data/lib/assets/templates/en/issue/show.erb +69 -0
- data/lib/assets/templates/{user → en/user}/find.erb +0 -0
- data/lib/redmine_cli.rb +3 -2
- data/lib/redmine_cli/exceptions.rb +5 -0
- data/lib/redmine_cli/helpers/input.rb +60 -4
- data/lib/redmine_cli/helpers/issue/update.rb +86 -0
- data/lib/redmine_cli/helpers/output.rb +12 -0
- data/lib/redmine_cli/subcommands/issue.rb +33 -2
- data/lib/redmine_cli/template_renderer.rb +3 -1
- data/lib/redmine_cli/version.rb +1 -1
- data/redmine_cli.gemspec +1 -1
- metadata +9 -7
- data/lib/assets/templates/issue/show.erb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ef953c22fdb0728f89e601513e196f11b889b31
|
4
|
+
data.tar.gz: 3b90592c3f4c0f1c0718e9d78ca1c0db5e5a3ab6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01555ab7dbf899ab495bb2ab252bf48f3f925650d4b76e738065e67cc10fb3f89715bd865c7fd645c94a87dd985a500fdd1f630c684fef0f3f6b95c32943ae51
|
7
|
+
data.tar.gz: f0721ac52ad8029dcc6fe08c4d7b5cc846c860bb0bd8c6dc6f6485b3bfb1fc10ac396f08f862086e0ff2614059eaad0be3463082fb23dfec5179561ea0506ebf
|
data/README.md
CHANGED
@@ -15,6 +15,8 @@ Why? Because web-browser + mouse sucks
|
|
15
15
|
redmine conf init
|
16
16
|
redmine issue list [user id]
|
17
17
|
redmine issue show <issue id>
|
18
|
+
redmine issue update <issue id> --comment --done 80 --status progress --time 00:30
|
19
|
+
|
18
20
|
redmine user find vasya
|
19
21
|
redmine user find 123
|
20
22
|
redmine user find pupkin@yet.another.mail.com
|
data/bin/console
CHANGED
data/lib/assets/messages/en.yml
CHANGED
@@ -7,6 +7,17 @@ en:
|
|
7
7
|
issue:
|
8
8
|
list: 'Lists issues of user. By default it is you'
|
9
9
|
show: 'More info about issue'
|
10
|
+
update: 'Change some issue params, add time entry, comment'
|
11
|
+
options:
|
12
|
+
show:
|
13
|
+
limit: 'Amount of comments (last journals).'
|
14
|
+
update:
|
15
|
+
done: 'Readiness percentage'
|
16
|
+
assign: 'ID of user issue will be assigned to'
|
17
|
+
time: 'Add time entry. Formats: HH:MM; M (minutes); H.h (hours in float)'
|
18
|
+
status: 'Change status of issue. Search by status name substring'
|
19
|
+
comment: 'Leave comment. It will open your text editor.'
|
20
|
+
description: 'Change description with your text editor.'
|
10
21
|
conf:
|
11
22
|
init: 'Asks you few question to make your config file useful'
|
12
23
|
user:
|
@@ -18,8 +29,16 @@ en:
|
|
18
29
|
enter_user: 'Enter your login to Redmine: '
|
19
30
|
enter_password: 'Enter your password: '
|
20
31
|
enter_site: 'Enter URL to your Redmine: '
|
32
|
+
issue:
|
33
|
+
update:
|
34
|
+
type_comment_here: 'Type your comment here, save file and close'
|
21
35
|
|
22
36
|
thank_you: 'Thank you!'
|
23
37
|
error_try_again: 'Error! Try again.'
|
24
38
|
error_input_required: 'Input cant be empty.'
|
25
39
|
not_found: 'Not found'
|
40
|
+
success: 'success'
|
41
|
+
error: 'error'
|
42
|
+
wrong_format: 'Wrong format'
|
43
|
+
creation_error: 'Creation error'
|
44
|
+
enter_object_number: 'Enter object number: '
|
File without changes
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
% ljust_value = 15
|
3
|
+
% info = { 'ID' => issue.id,
|
4
|
+
% 'Subject' => issue.subject,
|
5
|
+
% 'Status' => issue.status.name,
|
6
|
+
% 'Priority' => issue.priority.name,
|
7
|
+
% 'Readiness' => issue.done_ratio + '%',
|
8
|
+
% 'Author' => "#{issue.author.name} (#{issue.author.id})" }
|
9
|
+
%
|
10
|
+
% info['Assigned to'] = "#{issue.assigned_to.name} (#{issue.assigned_to.id})" if issue.assigned_to?
|
11
|
+
% info['Project'] = issue.project.name if issue.project?
|
12
|
+
% info['Version'] = issue.version.name if issue.version?
|
13
|
+
% info['Parent'] = "#{issue.parent.id} - #{issue.parent.reload.subject.cut(80)}" if issue.parent?
|
14
|
+
%
|
15
|
+
% info.each do |title, value|
|
16
|
+
<%= "#{title}:".ljust(ljust_value).yellow %> <%= value + "\n" %>
|
17
|
+
% end
|
18
|
+
%
|
19
|
+
% if issue.children?
|
20
|
+
<%= 'Children'.yellow %>:
|
21
|
+
% issue.children.each do |i|
|
22
|
+
* <%= i.id %> - <%= i.reload.subject.cut(70) %>
|
23
|
+
% end
|
24
|
+
% end
|
25
|
+
%
|
26
|
+
% if issue.relations?
|
27
|
+
<%= 'Relations'.yellow %>:
|
28
|
+
% issue.relations.each do |r|
|
29
|
+
% issue_id = r.prefix_options[:issue_id]
|
30
|
+
% issue_to_id = r.issue_to_id
|
31
|
+
% relation_type = r.relation_type
|
32
|
+
* <%= issue_id == issue.id ? 'this' : issue_id %> <%= relation_type %> <%= issue_to_id == issue.id ? 'this' : issue_to_id %>
|
33
|
+
% end
|
34
|
+
|
35
|
+
% end
|
36
|
+
%
|
37
|
+
% if issue.description? && !issue.description.empty?
|
38
|
+
|
39
|
+
<%= issue.description %>
|
40
|
+
|
41
|
+
|
42
|
+
% end
|
43
|
+
%
|
44
|
+
% if issue.journals? && journals_limit > 0
|
45
|
+
% journals = []
|
46
|
+
% issue.journals.reverse.each do |j|
|
47
|
+
% break if journals.size > journals_limit
|
48
|
+
% next unless j.notes?
|
49
|
+
% journals.push j
|
50
|
+
% end
|
51
|
+
|
52
|
+
% journals.reverse.each do |j|
|
53
|
+
|
54
|
+
<%= "----------\n".yellow %>
|
55
|
+
% journal_info = { 'From' => "#{j.user.name} (#{j.user.id})",
|
56
|
+
% 'Created' => j.created_on }
|
57
|
+
%
|
58
|
+
% journal_info.each do |k, v|
|
59
|
+
<%= "#{k}:".ljust(ljust_value).yellow %> <%= v %>
|
60
|
+
|
61
|
+
% end
|
62
|
+
|
63
|
+
|
64
|
+
<%= j.notes %>
|
65
|
+
|
66
|
+
|
67
|
+
% end
|
68
|
+
|
69
|
+
% end
|
File without changes
|
data/lib/redmine_cli.rb
CHANGED
@@ -5,11 +5,12 @@ require 'i18n'
|
|
5
5
|
I18n.load_path = Dir["#{File.dirname __FILE__}/assets/messages/*"]
|
6
6
|
|
7
7
|
# helpers
|
8
|
-
Dir[File.expand_path('../redmine_cli/helpers
|
8
|
+
Dir[File.expand_path('../redmine_cli/helpers/**/*.rb', __FILE__)].each { |f| require f }
|
9
9
|
|
10
10
|
require 'redmine_cli/version'
|
11
|
+
require 'redmine_cli/exceptions'
|
11
12
|
require 'redmine_cli/config'
|
12
|
-
I18n.locale = RedmineCLI::Config['locale']
|
13
|
+
I18n.locale = RedmineCLI::Config['locale']
|
13
14
|
|
14
15
|
require 'redmine_cli/template_renderer'
|
15
16
|
Dir[File.expand_path('../redmine_cli/subcommands/*.rb', __FILE__)].each { |f| require f }
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'uri'
|
2
|
+
require 'tempfile'
|
2
3
|
|
3
4
|
require_relative 'output'
|
4
5
|
|
@@ -10,6 +11,59 @@ module RedmineCLI
|
|
10
11
|
module Input
|
11
12
|
include Helpers::Output
|
12
13
|
|
14
|
+
def ask_from_text_editor(welcome_message = '')
|
15
|
+
file = Tempfile.open('redmine_cli') do |f|
|
16
|
+
f.write welcome_message
|
17
|
+
f
|
18
|
+
end
|
19
|
+
|
20
|
+
editor = Config['editor'] || ENV['EDITOR'] || 'nano'
|
21
|
+
|
22
|
+
system(editor + ' ' + file.path)
|
23
|
+
result = File.read(file)
|
24
|
+
file.unlink
|
25
|
+
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# prints names as enumerable list and asks user for element
|
31
|
+
#
|
32
|
+
def ask_for_object(object_list)
|
33
|
+
fail EmptyList if object_list.size.zero?
|
34
|
+
|
35
|
+
# From 1
|
36
|
+
i = 0
|
37
|
+
object_list = object_list.map { |obj| [(i += 1), obj] }.to_h
|
38
|
+
|
39
|
+
print_object_list(object_list)
|
40
|
+
puts
|
41
|
+
input = ask m(:enter_object_number),
|
42
|
+
default: '1',
|
43
|
+
limited_to: ->(str) { (1..i).cover? str.to_i }
|
44
|
+
|
45
|
+
object_list[input.to_i]
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Parses time from user's input.
|
50
|
+
# Formats: HH:MM; M; H.h
|
51
|
+
#
|
52
|
+
# @param input [String]
|
53
|
+
#
|
54
|
+
def parse_time(input)
|
55
|
+
fail(BadInputTime) unless input =~ /^\d+[\:\.]?\d*/
|
56
|
+
|
57
|
+
if input.include?(':')
|
58
|
+
h, m = input.split(':').map(&:to_i)
|
59
|
+
(60 * h + m) / 60.0
|
60
|
+
elsif input.include?('.')
|
61
|
+
input.to_f
|
62
|
+
else
|
63
|
+
input.to_i
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
13
67
|
#
|
14
68
|
# #ask with :limited_to set to url regexp
|
15
69
|
#
|
@@ -73,11 +127,13 @@ module RedmineCLI
|
|
73
127
|
end
|
74
128
|
|
75
129
|
def fit_in_limit?(input, limit)
|
76
|
-
|
130
|
+
case limit
|
131
|
+
when Array then limit.include?(input)
|
132
|
+
when Proc then limit.call(input)
|
133
|
+
when Regexp then limit =~ input
|
77
134
|
|
78
|
-
|
79
|
-
|
80
|
-
limit =~ input
|
135
|
+
else fail('limit should be Array or Regexp or Proc')
|
136
|
+
end
|
81
137
|
end
|
82
138
|
|
83
139
|
def read_line
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RedmineCLI
|
2
|
+
module Helpers
|
3
|
+
module Issue
|
4
|
+
#
|
5
|
+
# some methods for `issue update`
|
6
|
+
#
|
7
|
+
module Update
|
8
|
+
include RedmineRest
|
9
|
+
include Helpers::Input
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def update_issue(issue)
|
14
|
+
@errors = []
|
15
|
+
|
16
|
+
update_description(issue)
|
17
|
+
leave_comment(issue)
|
18
|
+
update_done_ratio(issue)
|
19
|
+
update_assigned_to(issue)
|
20
|
+
update_status(issue)
|
21
|
+
|
22
|
+
# it should be last, because it creates new object
|
23
|
+
add_time_entry_to_issue(issue) if @errors.empty?
|
24
|
+
|
25
|
+
@errors.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_done_ratio(issue)
|
29
|
+
return unless options[:done]
|
30
|
+
issue.done_ratio = options[:done]
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_assigned_to(issue)
|
34
|
+
return unless options[:assign]
|
35
|
+
|
36
|
+
# it can raise exception if there's no such user
|
37
|
+
Models::User.find(options[:assign])
|
38
|
+
issue.assigned_to_id = options[:assign]
|
39
|
+
|
40
|
+
rescue ActiveResource::ResourceNotFound
|
41
|
+
@errors.push "Assigned: #{m(:not_found)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_status(issue)
|
45
|
+
return unless options[:status]
|
46
|
+
|
47
|
+
found_statuses = Models::IssueStatus.all.filter_by_name_substring(options[:status])
|
48
|
+
case found_statuses.size
|
49
|
+
when 0 then @errors.push "Status: #{m(:not_found)}"
|
50
|
+
when 1 then issue.status_id = found_statuses.first.id
|
51
|
+
else issue.status_id = ask_for_object(found_statuses).id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_description(issue)
|
56
|
+
return unless options[:description]
|
57
|
+
|
58
|
+
issue.description = ask_from_text_editor(issue.description || '')
|
59
|
+
end
|
60
|
+
|
61
|
+
def leave_comment(issue)
|
62
|
+
return unless options[:comment]
|
63
|
+
|
64
|
+
comment = ask_from_text_editor(m('commands.issue.update.type_comment_here'))
|
65
|
+
return if comment.strip.empty?
|
66
|
+
|
67
|
+
issue.notes = comment
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_time_entry_to_issue(issue)
|
71
|
+
return unless options[:time]
|
72
|
+
|
73
|
+
hours = parse_time(options[:time])
|
74
|
+
entry = Models::TimeEntry.create issue_id: issue.id,
|
75
|
+
hours: hours
|
76
|
+
|
77
|
+
return if entry.persisted?
|
78
|
+
@errors.push "Time: #{m(:creation_error)}"
|
79
|
+
|
80
|
+
rescue BadInputTime
|
81
|
+
@errors.push "Time: #{m(:wrong_format)}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -4,6 +4,18 @@ module RedmineCLI
|
|
4
4
|
# Helpers for output
|
5
5
|
#
|
6
6
|
module Output
|
7
|
+
#
|
8
|
+
# for Helpers::Input.
|
9
|
+
#
|
10
|
+
# Prints keys of `list` and `value.name`
|
11
|
+
#
|
12
|
+
# @param list [Hash]
|
13
|
+
#
|
14
|
+
def print_object_list(list)
|
15
|
+
key_max_len = list.keys.map { |key| key.to_s.size }.max
|
16
|
+
list.each { |k, v| puts "#{k.to_s.ljust(key_max_len)} - #{v.name}" }
|
17
|
+
end
|
18
|
+
|
7
19
|
#
|
8
20
|
# for Helpers::Input
|
9
21
|
#
|
@@ -20,11 +20,42 @@ module RedmineCLI
|
|
20
20
|
end
|
21
21
|
|
22
22
|
desc 'show <id>', m('desc.issue.show')
|
23
|
+
option :limit, aliases: ['-l'], type: :numeric, default: 5, desc: m('desc.issue.options.show.limit')
|
23
24
|
def show(id)
|
24
|
-
puts erb('issue/show', issue: Models::Issue.find(id))
|
25
|
+
puts erb('issue/show', issue: Models::Issue.find(id), journals_limit: options[:limit])
|
26
|
+
|
27
|
+
rescue ActiveResource::ResourceNotFound # WARNING: it can be raised by associations in template
|
28
|
+
puts m(:not_found)
|
29
|
+
end
|
30
|
+
|
25
31
|
#
|
26
|
-
#
|
32
|
+
# TODO:
|
33
|
+
# * estimated time
|
34
|
+
# * priority
|
35
|
+
# * subject
|
36
|
+
# * tracker
|
37
|
+
# * project
|
38
|
+
# * version
|
39
|
+
# * parent (?) - mb it will be in command for relations
|
27
40
|
#
|
41
|
+
desc 'update <id>', m('desc.issue.update')
|
42
|
+
option :done, type: :numeric, aliases: '-d', desc: m('desc.issue.options.update.done')
|
43
|
+
option :assign, type: :numeric, aliases: '-a', desc: m('desc.issue.options.update.assign')
|
44
|
+
option :time, type: :string, aliases: '-t', desc: m('desc.issue.options.update.time')
|
45
|
+
option :status, type: :string, aliases: '-s', desc: m('desc.issue.options.update.status')
|
46
|
+
option :comment, type: :boolean, aliases: '-c', desc: m('desc.issue.options.update.comment')
|
47
|
+
option :description, type: :boolean, desc: m('desc.issue.options.update.description')
|
48
|
+
def update(id)
|
49
|
+
# load helpers inside instance method for better performance
|
50
|
+
self.class.include Helpers::Issue::Update
|
51
|
+
|
52
|
+
issue = Models::Issue.find(id)
|
53
|
+
if update_issue(issue) # update_issue will return nil/false if something goes wrong
|
54
|
+
puts m(issue.save ? :success : :error)
|
55
|
+
else
|
56
|
+
@errors.each { |e| puts e }
|
57
|
+
end
|
58
|
+
|
28
59
|
rescue ActiveResource::ResourceNotFound
|
29
60
|
puts m(:not_found)
|
30
61
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'erb'
|
2
2
|
require 'colorize'
|
3
3
|
|
4
|
+
require_relative 'config'
|
5
|
+
|
4
6
|
module RedmineCLI
|
5
7
|
#
|
6
8
|
# Renders templates
|
7
9
|
#
|
8
10
|
module TemplateRenderer
|
9
|
-
@template_directory = File.expand_path(
|
11
|
+
@template_directory = File.expand_path("../../assets/templates/#{Config.locale}", __FILE__)
|
10
12
|
|
11
13
|
#
|
12
14
|
# finds template and renders it
|
data/lib/redmine_cli/version.rb
CHANGED
data/redmine_cli.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
|
28
28
|
spec.add_dependency 'thor', '~> 0.19'
|
29
29
|
spec.add_dependency 'i18n', '~> 0.7'
|
30
|
-
spec.add_dependency 'redmine_rest', '0.
|
30
|
+
spec.add_dependency 'redmine_rest', '0.4.0'
|
31
31
|
spec.add_dependency 'non_config', '0.1.2'
|
32
32
|
spec.add_dependency 'colorize', '~> 0.7'
|
33
33
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redmine_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitriy Non
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - '='
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.
|
103
|
+
version: 0.4.0
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - '='
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
110
|
+
version: 0.4.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: non_config
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,12 +157,14 @@ files:
|
|
157
157
|
- exe/redmine
|
158
158
|
- lib/assets/default_config.yml
|
159
159
|
- lib/assets/messages/en.yml
|
160
|
-
- lib/assets/templates/issue/list.erb
|
161
|
-
- lib/assets/templates/issue/show.erb
|
162
|
-
- lib/assets/templates/user/find.erb
|
160
|
+
- lib/assets/templates/en/issue/list.erb
|
161
|
+
- lib/assets/templates/en/issue/show.erb
|
162
|
+
- lib/assets/templates/en/user/find.erb
|
163
163
|
- lib/redmine_cli.rb
|
164
164
|
- lib/redmine_cli/config.rb
|
165
|
+
- lib/redmine_cli/exceptions.rb
|
165
166
|
- lib/redmine_cli/helpers/input.rb
|
167
|
+
- lib/redmine_cli/helpers/issue/update.rb
|
166
168
|
- lib/redmine_cli/helpers/monkey_patching.rb
|
167
169
|
- lib/redmine_cli/helpers/output.rb
|
168
170
|
- lib/redmine_cli/subcommands/conf.rb
|
@@ -1,26 +0,0 @@
|
|
1
|
-
|
2
|
-
ID: <%= issue.id %>
|
3
|
-
Subject: <%= issue.subject %>
|
4
|
-
Status: <%= issue.status.name %>
|
5
|
-
Priority: <%= issue.priority.name %>
|
6
|
-
Readiness: <%= issue.done_ratio + '%' %>
|
7
|
-
% if issue.project?
|
8
|
-
Project: <%= issue.project.name %>
|
9
|
-
% end
|
10
|
-
% if issue.version?
|
11
|
-
Version: <%= issue.version.name %>
|
12
|
-
% end
|
13
|
-
% if issue.assigned_to?
|
14
|
-
Assigned to: <%= issue.assigned_to.name %> (<%= issue.assigned_to.id %>)
|
15
|
-
% end
|
16
|
-
Author: <%= issue.author.name %> (<%= issue.author.id %>)
|
17
|
-
% if issue.description? && !issue.description.empty?
|
18
|
-
|
19
|
-
*****
|
20
|
-
|
21
|
-
<%= issue.description %>
|
22
|
-
|
23
|
-
|
24
|
-
*****
|
25
|
-
% end
|
26
|
-
|