dude-cli 0.5.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +4 -3
- data/CHANGELOG.md +25 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +55 -50
- data/README.md +41 -25
- data/bin/dude +3 -2
- data/dude.gemspec +18 -18
- data/lib/dude.rb +12 -2
- data/lib/dude/commands.rb +24 -0
- data/lib/dude/commands/checkout.rb +21 -0
- data/lib/dude/commands/install.rb +57 -0
- data/lib/dude/commands/move.rb +15 -0
- data/lib/dude/commands/start.rb +29 -0
- data/lib/dude/commands/stop.rb +15 -0
- data/lib/dude/commands/tasks.rb +31 -0
- data/lib/dude/commands/track.rb +28 -0
- data/lib/dude/commands/version.rb +11 -0
- data/lib/dude/git.rb +3 -18
- data/lib/dude/git/checkout.rb +9 -0
- data/lib/dude/git/current_branch_name.rb +9 -0
- data/lib/dude/project_management/client.rb +25 -0
- data/lib/dude/project_management/entities/board.rb +9 -0
- data/lib/dude/project_management/entities/issue.rb +37 -0
- data/lib/dude/project_management/jira.rb +9 -0
- data/lib/dude/project_management/jira/client.rb +49 -0
- data/lib/dude/project_management/jira/get_current_tasks.rb +40 -0
- data/lib/dude/project_management/jira/get_task_name_by_id.rb +24 -0
- data/lib/dude/project_management/jira/move_task_to_list.rb +47 -0
- data/lib/dude/project_management/trello.rb +6 -0
- data/lib/dude/project_management/trello/checkout.rb +4 -0
- data/lib/dude/project_management/trello/client.rb +33 -0
- data/lib/dude/project_management/trello/get_current_tasks.rb +24 -0
- data/lib/dude/project_management/trello/get_lists.rb +15 -0
- data/lib/dude/project_management/trello/move_tasks_to_list.rb +14 -0
- data/lib/dude/settings.rb +6 -2
- data/lib/dude/time_trackers/toggl.rb +5 -0
- data/lib/dude/time_trackers/toggl/base.rb +18 -0
- data/lib/dude/time_trackers/toggl/start_time_entry.rb +53 -0
- data/lib/dude/time_trackers/toggl/stop_time_entry.rb +30 -0
- data/lib/dude/version.rb +1 -1
- metadata +59 -90
- data/LICENSE.txt +0 -21
- data/lib/dude/cli.rb +0 -114
- data/lib/dude/gitlab.rb +0 -82
- data/lib/dude/interface.rb +0 -98
- data/lib/dude/report.rb +0 -38
- data/lib/dude/toggl.rb +0 -81
data/LICENSE.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
3
|
-
Copyright (c) 2017 Nikita Pupko
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
THE SOFTWARE.
|
data/lib/dude/cli.rb
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
require 'thor'
|
2
|
-
require "dude/version"
|
3
|
-
require 'colorize'
|
4
|
-
require_relative 'gitlab'
|
5
|
-
require_relative 'git'
|
6
|
-
require_relative 'toggl'
|
7
|
-
require_relative 'settings'
|
8
|
-
require_relative 'interface'
|
9
|
-
|
10
|
-
module Dude
|
11
|
-
class CLI < Thor
|
12
|
-
include Settings
|
13
|
-
desc 'install', 'create .duderc file in your home directory'
|
14
|
-
def install
|
15
|
-
path = File.join(Dir.home, Settings::CONFIG_FILE)
|
16
|
-
if File.exist?(path)
|
17
|
-
puts "Config file already exists"
|
18
|
-
else
|
19
|
-
File.open(path, 'w') {|f| f.write(duderc_file_content) }
|
20
|
-
puts ".duderc created in your HOME directory"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
desc 'checkout [ISSUE_ID]', 'checkout to branch with name "ID-issue-title"'
|
25
|
-
def checkout(issue_id, project_title = folder_name)
|
26
|
-
issue_title = get_issue_title(issue_id, project_title)
|
27
|
-
branch_name = git_branch_name(issue_title, issue_id)
|
28
|
-
git(branch_name).call
|
29
|
-
puts "Branch changed to '#{branch_name}'".colorize(:green)
|
30
|
-
end
|
31
|
-
|
32
|
-
desc 'start [ISSUE_ID]', 'do checkout, track and move actions'
|
33
|
-
def start(issue_id, project_title = folder_name)
|
34
|
-
checkout(issue_id, project_title)
|
35
|
-
track(issue_id, project_title)
|
36
|
-
move('Doing', issue_id, project_title)
|
37
|
-
end
|
38
|
-
|
39
|
-
desc 'track [ISSUE_ID]', 'start task in Toggl with issue title'
|
40
|
-
def track(issue_id, project_title = folder_name)
|
41
|
-
issue_title = get_issue_title(issue_id, project_title)
|
42
|
-
Toggl.new(title: "##{issue_id} #{issue_title}", project_title: project_title).start_time_entry
|
43
|
-
puts "Toggl task '#{get_issue_title(issue_id, project_title)}' is started".colorize(:green)
|
44
|
-
end
|
45
|
-
|
46
|
-
desc 'tasks', 'Show issues in current project assigned to you'
|
47
|
-
def tasks(project_title = folder_name)
|
48
|
-
issues = Gitlab.new(project_title: project_title).my_issues
|
49
|
-
Interface.new.issues_list(issues)
|
50
|
-
end
|
51
|
-
|
52
|
-
desc 'estimate [DURATION] [ISSUE_ID]', 'estimate time'
|
53
|
-
def estimate(duration, issue_id = current_issue_id, project_title = folder_name)
|
54
|
-
Gitlab.new(issue_id: issue_id, project_title: project_title).estimate_time(duration)
|
55
|
-
puts "Changed time estimate to #{duration}".colorize(:green)
|
56
|
-
end
|
57
|
-
|
58
|
-
desc 'stop', 'stop current time entry in Toggl, move issue to `To Do`'
|
59
|
-
def stop(project_title = folder_name)
|
60
|
-
Toggl.new.stop_current_time_entry
|
61
|
-
move('To Do', current_issue_id, project_title)
|
62
|
-
puts 'Work suspended'
|
63
|
-
end
|
64
|
-
|
65
|
-
desc 'issue', 'Information about issue'
|
66
|
-
def issue(issue_id = current_issue_id, project_title = folder_name)
|
67
|
-
Gitlab.new(issue_id: issue_id, project_title: project_title).issue_info
|
68
|
-
end
|
69
|
-
|
70
|
-
desc 'stats', 'display your stats from Toggl'
|
71
|
-
def stats
|
72
|
-
Toggl.new.report
|
73
|
-
end
|
74
|
-
|
75
|
-
desc 'move', 'move issue to another column'
|
76
|
-
def move(label, issue_id = current_issue_id, project_title = folder_name)
|
77
|
-
Gitlab.new(issue_id: issue_id, project_title: project_title, label: label).move_issue
|
78
|
-
puts "Issue ##{issue_id} moved to '#{label}'".colorize(:green)
|
79
|
-
end
|
80
|
-
|
81
|
-
desc 'version', 'Show version'
|
82
|
-
def version
|
83
|
-
puts "Dude CLI v#{Dude::VERSION}"
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
def git(branch_name)
|
89
|
-
Git.new(branch_name: branch_name)
|
90
|
-
end
|
91
|
-
|
92
|
-
def current_issue_id
|
93
|
-
Git.new.current_branch_name.chomp.split('-').first
|
94
|
-
end
|
95
|
-
|
96
|
-
def get_issue_title(issue_id, project_title)
|
97
|
-
Gitlab.new(issue_id: issue_id, project_title: project_title).issue_title
|
98
|
-
end
|
99
|
-
|
100
|
-
def duderc_file_content
|
101
|
-
"TOGGL_EMAIL=\nTOGGL_TOKEN=\nTOGGL_WORKSPACE_ID=\n" \
|
102
|
-
"GITLAB_ENDPOINT=https://gitlab.yoursite.com/api/v4/\n" \
|
103
|
-
"GITLAB_TOKEN=\nHOURS_PER_DAY=8\nHOURS_PER_WEEK=40"
|
104
|
-
end
|
105
|
-
|
106
|
-
def folder_name
|
107
|
-
@folder_name ||= File.basename(Dir.getwd)
|
108
|
-
end
|
109
|
-
|
110
|
-
def git_branch_name(issue_title, issue_id)
|
111
|
-
issue_title.downcase.gsub(/[^a-z0-9\-_]+/, '-').prepend("#{issue_id}-")
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
data/lib/dude/gitlab.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'gitlab'
|
2
|
-
require 'colorize'
|
3
|
-
require_relative 'settings'
|
4
|
-
require_relative 'interface'
|
5
|
-
|
6
|
-
module Dude
|
7
|
-
class Gitlab
|
8
|
-
include Dude::Settings
|
9
|
-
attr_accessor :options
|
10
|
-
|
11
|
-
def initialize(options = {})
|
12
|
-
@options = options
|
13
|
-
::Gitlab.configure do |config|
|
14
|
-
config.endpoint = settings['GITLAB_ENDPOINT']
|
15
|
-
config.private_token = settings['GITLAB_TOKEN']
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def issue_title
|
20
|
-
test_input_data
|
21
|
-
::Gitlab.issue(project_id, options[:issue_id]).title
|
22
|
-
end
|
23
|
-
|
24
|
-
def my_issues
|
25
|
-
all_issues_on_project.select {|a| a.last.eql?(user.id) }
|
26
|
-
end
|
27
|
-
|
28
|
-
def estimate_time(duration)
|
29
|
-
test_input_data
|
30
|
-
::Gitlab.estimate_time_of_issue(project_id, options[:issue_id], duration)
|
31
|
-
time = ::Gitlab.issue(project_id, options[:issue_id]).
|
32
|
-
to_h['time_stats']['human_time_estimate']
|
33
|
-
Interface.new.draw_time_estimate(time)
|
34
|
-
end
|
35
|
-
|
36
|
-
def issue_info
|
37
|
-
test_input_data
|
38
|
-
issue_info = ::Gitlab.issue(project_id, options[:issue_id]).to_h
|
39
|
-
Interface.new.draw_issue_info(issue_info)
|
40
|
-
end
|
41
|
-
|
42
|
-
def move_issue
|
43
|
-
test_input_data
|
44
|
-
labels = ::Gitlab.issue(project_id, options[:issue_id]).labels
|
45
|
-
labels = labels.reject {|e| ['To Do', 'Doing', 'To Verify'].include?(e)}.
|
46
|
-
push(options[:label]).join(',')
|
47
|
-
::Gitlab.edit_issue(project_id, options[:issue_id], { labels: labels })
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def test_input_data
|
53
|
-
if options[:issue_id].to_i.zero? || !issue_exists?
|
54
|
-
Interface.new.throw_error(options[:issue_id], options[:project_title])
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def issue_exists?
|
59
|
-
!::Gitlab.issue(project_id, options[:issue_id]).nil?
|
60
|
-
rescue
|
61
|
-
nil
|
62
|
-
end
|
63
|
-
|
64
|
-
def all_issues_on_project
|
65
|
-
::Gitlab.issues(project_id).map {|a| [a.iid, a.title, a.labels, assignees_id(a)]}
|
66
|
-
end
|
67
|
-
|
68
|
-
def assignees_id(issue)
|
69
|
-
issue&.assignees&.first['id']
|
70
|
-
rescue NoMethodError
|
71
|
-
nil
|
72
|
-
end
|
73
|
-
|
74
|
-
def user
|
75
|
-
@my_id ||= ::Gitlab.user
|
76
|
-
end
|
77
|
-
|
78
|
-
def project_id
|
79
|
-
@project_id ||= ::Gitlab.project_search(options[:project_title])[0]&.id
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
data/lib/dude/interface.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
require 'colorize'
|
2
|
-
require_relative 'settings'
|
3
|
-
|
4
|
-
module Dude
|
5
|
-
class Interface
|
6
|
-
include Settings
|
7
|
-
|
8
|
-
def draw_report(report)
|
9
|
-
@report = report
|
10
|
-
report_weekly
|
11
|
-
puts ''
|
12
|
-
report_daily
|
13
|
-
end
|
14
|
-
|
15
|
-
def throw_error(issue_id, project_title)
|
16
|
-
puts "Something went wrong. Please, check input data\n".colorize(:red) +
|
17
|
-
"Project: #{project_title.to_s.bold}\n" +
|
18
|
-
"Issue ID: #{issue_id.to_s.bold}"
|
19
|
-
exit
|
20
|
-
end
|
21
|
-
|
22
|
-
def draw_time_estimate(time)
|
23
|
-
puts "Changed time estimate to #{time.colorize(:green)}"
|
24
|
-
end
|
25
|
-
|
26
|
-
def draw_issue_info(info)
|
27
|
-
issue_label = info['labels'].find do |label|
|
28
|
-
['To Do', 'Doing', 'To Verify'].include? label
|
29
|
-
end
|
30
|
-
|
31
|
-
issue_color = case issue_label
|
32
|
-
when 'To Do'
|
33
|
-
:yellow
|
34
|
-
when 'Doing'
|
35
|
-
:green
|
36
|
-
when 'To Verify'
|
37
|
-
:blue
|
38
|
-
end
|
39
|
-
|
40
|
-
puts "#{info['title']} ##{info['iid']}".colorize(issue_color).bold
|
41
|
-
puts "Status: ".colorize(:yellow) + "#{issue_label}"
|
42
|
-
puts "Estimated time: ".colorize(:yellow) +
|
43
|
-
"#{info['time_stats']['human_time_estimate']}"
|
44
|
-
puts "Spent time: ".colorize(:yellow) +
|
45
|
-
"#{info['time_stats']['human_total_time_spent'] || '0h'}"
|
46
|
-
puts "Link: ".colorize(:yellow) + "#{info['web_url']}"
|
47
|
-
end
|
48
|
-
|
49
|
-
def issues_list(issues)
|
50
|
-
[['To Do', :yellow], ['Doing', :green], ['To Verify', :blue]].each do |group, color|
|
51
|
-
puts "#{group}:".colorize(color).bold
|
52
|
-
grouped = issues.select {|i| i[2].include?(group)}
|
53
|
-
if grouped.length.zero?
|
54
|
-
puts 'Nothing'
|
55
|
-
else
|
56
|
-
grouped.each do |id, issue, labels|
|
57
|
-
puts "#{id}:".colorize(color) + " #{issue} #{labels.compact.to_s.gsub('"', "")}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
puts ''
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def report_weekly
|
67
|
-
puts "Week".colorize(:green).bold
|
68
|
-
# puts '-' * 15
|
69
|
-
puts " Worked:".colorize(:yellow).bold +
|
70
|
-
" #{seconds_to_time(@report.week_time_worked)} / " \
|
71
|
-
"#{@report.hours_without_weekends}:00:00 " \
|
72
|
-
"(#{@report.week_time_worked * 100 / 144000}%)"
|
73
|
-
puts " Time left:".colorize(:yellow).bold +
|
74
|
-
" #{seconds_to_time(144000 - @report.week_time_worked)}"
|
75
|
-
end
|
76
|
-
|
77
|
-
def report_daily
|
78
|
-
puts "Today".colorize(:green).bold
|
79
|
-
# puts '-' * 15
|
80
|
-
puts " Worked:".colorize(:yellow).bold +
|
81
|
-
" #{seconds_to_time(@report.today_time_worked)} / " \
|
82
|
-
" #{settings['HOURS_PER_DAY']}:00:00 " \
|
83
|
-
"(#{@report.today_time_worked * 100 / 28800}%)"
|
84
|
-
puts " Time left:".colorize(:yellow).bold +
|
85
|
-
" #{seconds_to_time(@report.seconds_for_today - @report.week_time_worked)}"
|
86
|
-
end
|
87
|
-
|
88
|
-
def seconds_to_time(s)
|
89
|
-
hms = [60,60].reduce([s]) { |m,o| m.unshift(m.shift.divmod(o)).flatten }
|
90
|
-
"#{sprintf '%02d', hms[0]}:#{sprintf '%02d', hms[1]}:#{sprintf '%02d', hms[2]}"
|
91
|
-
end
|
92
|
-
|
93
|
-
def term
|
94
|
-
term_size = IO.console.winsize
|
95
|
-
@terminal = OpenStruct.new(h: term_size[0], w: term_size[1])
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
data/lib/dude/report.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'date'
|
2
|
-
require_relative 'settings'
|
3
|
-
|
4
|
-
module Dude
|
5
|
-
class Report
|
6
|
-
include Settings
|
7
|
-
attr_reader :report
|
8
|
-
|
9
|
-
def initialize(report_json)
|
10
|
-
@report = JSON.parse(report_json.body)
|
11
|
-
end
|
12
|
-
|
13
|
-
def week_time_worked
|
14
|
-
@time_worked ||= report['total_grand'] / 1000
|
15
|
-
end
|
16
|
-
|
17
|
-
def today_time_worked
|
18
|
-
@today_time_worked ||= report['week_totals'].map {|a| a.nil? ? 0 : a / 1000}[Time.now.wday - 1]
|
19
|
-
end
|
20
|
-
|
21
|
-
def days_without_weekends
|
22
|
-
@days_without_weekends ||= report['week_totals'][0..Time.now.wday - 1].compact.count
|
23
|
-
end
|
24
|
-
|
25
|
-
def weekends
|
26
|
-
@weekends ||= report['week_totals'][0..Time.now.wday - 1].
|
27
|
-
select { |a| a.nil? }.count
|
28
|
-
end
|
29
|
-
|
30
|
-
def hours_without_weekends
|
31
|
-
@hours_without_weekends ||= settings['HOURS_PER_WEEK'].to_i - weekends * settings['HOURS_PER_DAY'].to_i
|
32
|
-
end
|
33
|
-
|
34
|
-
def seconds_for_today
|
35
|
-
days_without_weekends * settings['HOURS_PER_DAY'].to_i * 3600
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
data/lib/dude/toggl.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require 'rest-client'
|
2
|
-
require 'date'
|
3
|
-
require_relative 'settings'
|
4
|
-
require_relative 'report'
|
5
|
-
|
6
|
-
module Dude
|
7
|
-
class Toggl
|
8
|
-
include Settings
|
9
|
-
attr_accessor :options
|
10
|
-
|
11
|
-
def initialize(options = {})
|
12
|
-
@options = options
|
13
|
-
end
|
14
|
-
|
15
|
-
def report
|
16
|
-
report = Report.new(toggl_report.get params: report_params)
|
17
|
-
Interface.new.draw_report(report)
|
18
|
-
end
|
19
|
-
|
20
|
-
def start_time_entry
|
21
|
-
toggl_api['time_entries/start'].post time_entry_params(options[:title]).to_json, content_type: :json
|
22
|
-
end
|
23
|
-
|
24
|
-
def stop_current_time_entry
|
25
|
-
toggl_api["time_entries/#{current_time_entry['id']}/stop"].put ''
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def report_params
|
31
|
-
{
|
32
|
-
workspace_id: settings['TOGGL_WORKSPACE_ID'],
|
33
|
-
user_agent: settings['TOGGL_EMAIL'],
|
34
|
-
since: Date.parse('monday').strftime('%Y-%m-%d')
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
def time_entry_params(title)
|
39
|
-
{
|
40
|
-
time_entry: {
|
41
|
-
description: title,
|
42
|
-
pid: project_id,
|
43
|
-
created_with: "dude"
|
44
|
-
}
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
def project_id
|
49
|
-
projects_array.each do |arr|
|
50
|
-
return arr.last if arr.first.eql?(options[:project_title])
|
51
|
-
end
|
52
|
-
nil
|
53
|
-
end
|
54
|
-
|
55
|
-
def current_time_entry
|
56
|
-
JSON.parse(toggl_api['time_entries/current'].get)['data']
|
57
|
-
end
|
58
|
-
|
59
|
-
def projects_array
|
60
|
-
JSON.parse(
|
61
|
-
toggl_api["workspaces/#{settings['TOGGL_WORKSPACE_ID']}/projects"].get
|
62
|
-
).map { |a| [a['name'].downcase.gsub(/\s/, '-'), a['id']] }
|
63
|
-
end
|
64
|
-
|
65
|
-
def toggl_api
|
66
|
-
@toggl_api ||= RestClient::Resource.new(
|
67
|
-
'https://www.toggl.com/api/v8',
|
68
|
-
settings['TOGGL_TOKEN'],
|
69
|
-
'api_token'
|
70
|
-
)
|
71
|
-
end
|
72
|
-
|
73
|
-
def toggl_report
|
74
|
-
@toggl_report ||= RestClient::Resource.new(
|
75
|
-
'https://www.toggl.com/reports/api/v2/weekly',
|
76
|
-
settings['TOGGL_TOKEN'],
|
77
|
-
'api_token'
|
78
|
-
)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|