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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 00076ae6856ab08c117ae349cddb9612fecf323e
4
- data.tar.gz: 2984d5491b6ad7522e37ae98b1e406c90cb835e5
3
+ metadata.gz: 8ef953c22fdb0728f89e601513e196f11b889b31
4
+ data.tar.gz: 3b90592c3f4c0f1c0718e9d78ca1c0db5e5a3ab6
5
5
  SHA512:
6
- metadata.gz: d3b4cd2a1337400909ab272e2116afc5d8b43f3b5256eec464e9f567cc12a88d25a3b07679d878adec4fc462e341f59498f99be59d55fcb6546e88e99ec8f19c
7
- data.tar.gz: 6b314ee1180f075de75ac480c70c633d7d19c66a0a75689d066ceeeffd36f6eb5126c1a96d4a07d799067829d23efb827649fa8c3cb7c2aadfea1e1d9c216316
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
@@ -11,4 +11,6 @@ require 'redmine_cli'
11
11
  # Pry.start
12
12
 
13
13
  require 'irb'
14
+
15
+ ActiveResource::Base.logger = Logger.new(STDOUT)
14
16
  IRB.start
@@ -2,3 +2,4 @@
2
2
  user: nil
3
3
  password: nil
4
4
  site: nil
5
+ locale: en
@@ -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/*.rb', __FILE__)].each { |f| require f }
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'] || :en
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 }
@@ -0,0 +1,5 @@
1
+ module RedmineCLI
2
+ class UserNotFound < StandardError; end
3
+ class BadInputTime < StandardError; end
4
+ class EmptyList < StandardError; end
5
+ end
@@ -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
- fail('limit should be Array or Regexp') unless limit.is_a?(Array) || limit.is_a?(Regexp)
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
- return limit.include?(input) if limit.is_a? Array
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
- # WARNING: it can be raised by associations in template
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('../../assets/templates', __FILE__)
11
+ @template_directory = File.expand_path("../../assets/templates/#{Config.locale}", __FILE__)
10
12
 
11
13
  #
12
14
  # finds template and renders it
@@ -1,3 +1,3 @@
1
1
  module RedmineCLI
2
- VERSION = '0.2.2'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
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.2.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.2.2
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-07 00:00:00.000000000 Z
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.2.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.2.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
-