abt-cli 0.0.18 → 0.0.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/abt/ari.rb +20 -0
  3. data/lib/abt/ari_list.rb +13 -0
  4. data/lib/abt/base_command.rb +63 -0
  5. data/lib/abt/cli.rb +6 -9
  6. data/lib/abt/cli/arguments_parser.rb +1 -23
  7. data/lib/abt/cli/prompt.rb +5 -4
  8. data/lib/abt/docs.rb +6 -5
  9. data/lib/abt/docs/markdown.rb +1 -1
  10. data/lib/abt/git_config.rb +20 -36
  11. data/lib/abt/providers/asana/base_command.rb +13 -33
  12. data/lib/abt/providers/asana/commands/add.rb +9 -7
  13. data/lib/abt/providers/asana/commands/branch_name.rb +9 -4
  14. data/lib/abt/providers/asana/commands/clear.rb +2 -0
  15. data/lib/abt/providers/asana/commands/current.rb +19 -34
  16. data/lib/abt/providers/asana/commands/finalize.rb +3 -3
  17. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +9 -4
  18. data/lib/abt/providers/asana/commands/init.rb +6 -6
  19. data/lib/abt/providers/asana/commands/pick.rb +16 -11
  20. data/lib/abt/providers/asana/commands/projects.rb +1 -1
  21. data/lib/abt/providers/asana/commands/share.rb +2 -6
  22. data/lib/abt/providers/asana/commands/start.rb +14 -12
  23. data/lib/abt/providers/asana/commands/tasks.rb +4 -3
  24. data/lib/abt/providers/asana/configuration.rb +18 -24
  25. data/lib/abt/providers/asana/path.rb +36 -0
  26. data/lib/abt/providers/devops/api.rb +12 -0
  27. data/lib/abt/providers/devops/base_command.rb +13 -38
  28. data/lib/abt/providers/devops/commands/boards.rb +2 -2
  29. data/lib/abt/providers/devops/commands/branch_name.rb +7 -3
  30. data/lib/abt/providers/devops/commands/clear.rb +2 -0
  31. data/lib/abt/providers/devops/commands/current.rb +14 -38
  32. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +9 -1
  33. data/lib/abt/providers/devops/commands/init.rb +15 -15
  34. data/lib/abt/providers/devops/commands/pick.rb +5 -12
  35. data/lib/abt/providers/devops/commands/share.rb +3 -4
  36. data/lib/abt/providers/devops/commands/work-items.rb +1 -1
  37. data/lib/abt/providers/devops/configuration.rb +17 -46
  38. data/lib/abt/providers/devops/path.rb +50 -0
  39. data/lib/abt/providers/git/commands/branch.rb +14 -8
  40. data/lib/abt/providers/harvest/base_command.rb +14 -32
  41. data/lib/abt/providers/harvest/commands/clear.rb +2 -0
  42. data/lib/abt/providers/harvest/commands/current.rb +24 -31
  43. data/lib/abt/providers/harvest/commands/init.rb +5 -6
  44. data/lib/abt/providers/harvest/commands/pick.rb +3 -4
  45. data/lib/abt/providers/harvest/commands/projects.rb +1 -1
  46. data/lib/abt/providers/harvest/commands/share.rb +4 -8
  47. data/lib/abt/providers/harvest/commands/stop.rb +7 -7
  48. data/lib/abt/providers/harvest/commands/tasks.rb +4 -1
  49. data/lib/abt/providers/harvest/commands/track.rb +25 -18
  50. data/lib/abt/providers/harvest/configuration.rb +20 -29
  51. data/lib/abt/providers/harvest/path.rb +36 -0
  52. data/lib/abt/version.rb +1 -1
  53. metadata +8 -3
  54. data/lib/abt/cli/base_command.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df400d04c979d979ab2ead353d68374b8266c0fff678103991cc6443512b703b
4
- data.tar.gz: 0345141c38eae6904d11f40a3045fccd048d8009b70fe9714f5e0986f1779861
3
+ metadata.gz: 295db35510e60a5b46ea7f20caa01aaad247bb30cb60e61b912cab5f754637ac
4
+ data.tar.gz: e686eb40fc1ac88315c138eac56b96500c29c3f012b4bdb1d97f993a507f80b7
5
5
  SHA512:
6
- metadata.gz: 1979110cbd58f0bc71b83ad7211b86e17c5b078e842d4f99ca839c88a88d627ab92506736cd83b6e414cbea2ec2a82592f6e8b3f1effdbd2b57df7decd63793e
7
- data.tar.gz: 6af0e9bb436b304cda9d3acf6c86733050c9a7acffb691017d968e89e3f4e8e51b9fa4cdf5e3a85373ff2f684fa61e41afce0c1b9011d2e6b5551935b40530ea
6
+ metadata.gz: f18aa2b4f33bb6b51689522bca357ddb0fbe7a2d2e968bcc6754959f01bf6d54b9f93c387e8a050d165063cad26e6b360f245d2e7fa317bc1fbad8c0fa0d3379
7
+ data.tar.gz: ab46d17a29d4e447472c8c4d9242aa2e96bd5195bad4083d0476054f312d358ad5555526b44edacdf2512713e646435200689f82e8a097c1dfa306ff2ae1c4df
data/lib/abt/ari.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ class Ari
5
+ attr_reader :scheme, :path, :flags
6
+
7
+ def initialize(scheme:, path: nil, flags: [])
8
+ @scheme = scheme
9
+ @path = path
10
+ @flags = flags
11
+ end
12
+
13
+ def to_s
14
+ str = scheme
15
+ str += ":#{path}" if path
16
+
17
+ [str, *flags].join(' ')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ class AriList < Array
5
+ def to_s
6
+ map(&:to_s).join(' -- ')
7
+ end
8
+
9
+ def -(other)
10
+ AriList.new(to_a - other)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ class BaseCommand
5
+ extend Forwardable
6
+
7
+ def self.usage
8
+ raise NotImplementedError, 'Command classes must implement .usage'
9
+ end
10
+
11
+ def self.description
12
+ raise NotImplementedError, 'Command classes must implement .description'
13
+ end
14
+
15
+ def self.flags
16
+ []
17
+ end
18
+
19
+ attr_reader :ari, :cli, :flags
20
+
21
+ def_delegators(:@cli, :warn, :puts, :print, :abort, :exit_with_message)
22
+
23
+ def initialize(ari:, cli:)
24
+ @cli = cli
25
+ @ari = ari
26
+ @flags = parse_flags(ari.flags)
27
+ end
28
+
29
+ def perform
30
+ raise NotImplementedError, 'Command classes must implement #perform'
31
+ end
32
+
33
+ private
34
+
35
+ def parse_flags(flags)
36
+ result = {}
37
+
38
+ flag_parser.parse!(flags.dup, into: result)
39
+
40
+ exit_with_message(flag_parser.help) if result[:help]
41
+
42
+ result
43
+ rescue OptionParser::InvalidOption => e
44
+ abort e.message
45
+ end
46
+
47
+ def flag_parser
48
+ @flag_parser ||= OptionParser.new do |opts|
49
+ opts.banner = <<~TXT
50
+ #{self.class.description}
51
+
52
+ Usage: #{self.class.usage}
53
+ TXT
54
+
55
+ opts.on('-h', '--help')
56
+
57
+ self.class.flags.each do |(*flag)|
58
+ opts.on(*flag)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/abt/cli.rb CHANGED
@@ -17,7 +17,6 @@ module Abt
17
17
  @output = output
18
18
  @err_output = err_output
19
19
  @prompt = Abt::Cli::Prompt.new(output: err_output)
20
-
21
20
  @aris = ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
22
21
  end
23
22
 
@@ -105,26 +104,24 @@ module Abt
105
104
 
106
105
  def process_aris
107
106
  used_schemes = []
108
- aris.each do |ari|
109
- scheme = ari.scheme
110
- path = ari.path
111
107
 
112
- if used_schemes.include?(scheme)
108
+ aris.each do |ari|
109
+ if used_schemes.include?(ari.scheme)
113
110
  warn "Dropping command for already used scheme: #{ari}"
114
111
  next
115
112
  end
116
113
 
117
- command_class = get_command_class(scheme)
114
+ command_class = get_command_class(ari.scheme)
118
115
  next if command_class.nil?
119
116
 
120
117
  print_command(command, ari) if output.isatty
121
118
  begin
122
- command_class.new(path: path, cli: self, flags: ari.flags).perform
119
+ command_class.new(ari: ari, cli: self).perform
123
120
  rescue Exit => e
124
121
  puts e.message
125
122
  end
126
123
 
127
- used_schemes << scheme
124
+ used_schemes << ari.scheme
128
125
  end
129
126
 
130
127
  return unless used_schemes.empty? && output.isatty
@@ -140,7 +137,7 @@ module Abt
140
137
  end
141
138
 
142
139
  def print_command(name, ari)
143
- warn "===== #{name} #{ari} =====".upcase
140
+ warn "===== #{name.upcase} #{ari} ====="
144
141
  end
145
142
  end
146
143
  end
@@ -3,28 +3,6 @@
3
3
  module Abt
4
4
  class Cli
5
5
  class ArgumentsParser
6
- class Ari
7
- attr_reader :scheme, :path, :flags
8
-
9
- def initialize(scheme:, path:, flags:)
10
- @scheme = scheme
11
- @path = path
12
- @flags = flags
13
- end
14
-
15
- def to_s
16
- str = scheme
17
- str += ":#{path}" if path
18
-
19
- [str, *flags].join(' ')
20
- end
21
- end
22
- class Aris < Array
23
- def to_s
24
- map(&:to_s).join(' -- ')
25
- end
26
- end
27
-
28
6
  attr_reader :arguments
29
7
 
30
8
  def initialize(arguments)
@@ -32,7 +10,7 @@ module Abt
32
10
  end
33
11
 
34
12
  def parse
35
- result = Aris.new
13
+ result = AriList.new
36
14
  rest = arguments.dup
37
15
 
38
16
  until rest.empty?
@@ -69,10 +69,11 @@ module Abt
69
69
  end
70
70
 
71
71
  def read_option_number(options_length, nil_option)
72
- output.print '('
73
- output.print options_length > 1 ? "1-#{options_length}" : '1'
74
- output.print nil_option_string(nil_option)
75
- output.print '): '
72
+ str = '('
73
+ str += options_length > 1 ? "1-#{options_length}" : '1'
74
+ str += nil_option_string(nil_option)
75
+ str += '): '
76
+ output.print str
76
77
 
77
78
  input = read_user_input
78
79
 
data/lib/abt/docs.rb CHANGED
@@ -26,14 +26,15 @@ module Abt
26
26
  'abt pick asana -d | abt track harvest' => 'Track on asana meeting task',
27
27
  'abt pick harvest -d | abt track harvest -c "Name of meeting"' => 'Track on separate harvest-task'
28
28
  },
29
- 'Command output can be piped:' => {
29
+ 'Many commands output ARIs that can be piped into other commands:' => {
30
30
  'abt tasks asana | grep -i <name of task>' => nil,
31
31
  'abt tasks asana | grep -i <name of task> | abt start' => nil
32
32
  },
33
- 'Sharing configuration:' => {
34
- 'abt share asana harvest | tr "\n" " "' => 'Print current configuration',
35
- 'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy configuration (mac only)',
36
- 'abt start <shared configuration>' => 'Start a shared configuration'
33
+ 'Sharing ARIs:' => {
34
+ 'abt share asana harvest | tr "\n" " "' => 'Print current asana and harvest ARIs on a single line',
35
+ 'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy ARIs to clipboard (mac only)',
36
+ 'abt start <ARIs from coworker>' => 'Work on a task your coworker shared with you',
37
+ 'abt current <ARIs from coworker> | abt start' => 'Set task as current, then start it'
37
38
  },
38
39
  'Flags:' => {
39
40
  'abt start harvest -c "comment"' => 'Add command flags after ARIs',
@@ -15,7 +15,7 @@ module Abt
15
15
 
16
16
  ## How does abt work?
17
17
 
18
- Abt is a hybrid af having small scripts each doing one thing:
18
+ Abt is a hybrid of having small scripts each doing one thing:
19
19
  - `start-asana --project-gid xxxx --task-gid yyyy`
20
20
  - `start-harvest --project-id aaaa --task-id bbbb`
21
21
 
@@ -6,21 +6,7 @@ module Abt
6
6
 
7
7
  class UnsafeNamespaceError < StandardError; end
8
8
 
9
- LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND = 'git config --local -l'
10
-
11
- def self.local_available?
12
- return @local_available if instance_variables.include?(:@local_available)
13
-
14
- @local_available = begin
15
- success = false
16
- Open3.popen3(LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND) do |_i, _o, _e, thread|
17
- success = thread.value.success?
18
- end
19
- success
20
- end
21
- end
22
-
23
- def initialize(namespace: '', scope: 'local')
9
+ def initialize(scope = 'local', namespace = '')
24
10
  @namespace = namespace
25
11
 
26
12
  unless %w[local global].include? scope
@@ -30,6 +16,20 @@ module Abt
30
16
  @scope = scope
31
17
  end
32
18
 
19
+ def available?
20
+ unless instance_variables.include?(:available)
21
+ @available = begin
22
+ success = false
23
+ Open3.popen3(availability_check_call) do |_i, _o, _e, thread|
24
+ success = thread.value.success?
25
+ end
26
+ success
27
+ end
28
+ end
29
+
30
+ @available
31
+ end
32
+
33
33
  def [](key)
34
34
  get(key)
35
35
  end
@@ -49,26 +49,6 @@ module Abt
49
49
  `git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
50
50
  end
51
51
 
52
- def local
53
- @local ||= begin
54
- if scope == 'local'
55
- self
56
- else
57
- self.class.new(namespace: namespace, scope: 'local')
58
- end
59
- end
60
- end
61
-
62
- def global
63
- @global ||= begin
64
- if scope == 'global'
65
- self
66
- else
67
- self.class.new(namespace: namespace, scope: 'global')
68
- end
69
- end
70
- end
71
-
72
52
  def clear(output: nil)
73
53
  raise UnsafeNamespaceError, 'Keys can only be cleared within a namespace' if namespace.empty?
74
54
 
@@ -80,8 +60,12 @@ module Abt
80
60
 
81
61
  private
82
62
 
63
+ def availability_check_call
64
+ "git config --#{scope} -l"
65
+ end
66
+
83
67
  def ensure_scope_available!
84
- return if scope != 'local' || self.class.local_available?
68
+ return if available?
85
69
 
86
70
  raise StandardError, 'Local configuration is not available outside a git repository'
87
71
  end
@@ -3,63 +3,43 @@
3
3
  module Abt
4
4
  module Providers
5
5
  module Asana
6
- class BaseCommand < Abt::Cli::BaseCommand
7
- attr_reader :project_gid, :task_gid, :config
6
+ class BaseCommand < Abt::BaseCommand
7
+ extend Forwardable
8
8
 
9
- def initialize(path:, cli:, **)
9
+ attr_reader :path, :config
10
+
11
+ def_delegators(:@path, :project_gid, :task_gid)
12
+
13
+ def initialize(ari:, cli:)
10
14
  super
11
15
 
12
16
  @config = Configuration.new(cli: cli)
13
17
 
14
- if path.nil?
15
- use_current_path
16
- else
17
- use_path(path)
18
- end
18
+ @path = ari.path ? Path.new(ari.path) : config.path
19
19
  end
20
20
 
21
21
  private
22
22
 
23
23
  def require_project!
24
- cli.abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
24
+ abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
25
25
  end
26
26
 
27
27
  def require_task!
28
28
  if project_gid.nil?
29
- cli.abort 'No current/specified project. Did you initialize Asana and pick a task?'
29
+ abort 'No current/specified project. Did you initialize Asana and pick a task?'
30
30
  end
31
- cli.abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
32
- end
33
-
34
- def same_args_as_config?
35
- project_gid == config.project_gid && task_gid == config.task_gid
31
+ abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
36
32
  end
37
33
 
38
34
  def print_project(project)
39
35
  cli.print_ari('asana', project['gid'], project['name'])
40
- cli.warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
36
+ warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
41
37
  end
42
38
 
43
39
  def print_task(project, task)
44
40
  project = { 'gid' => project } if project.is_a?(String)
45
41
  cli.print_ari('asana', "#{project['gid']}/#{task['gid']}", task['name'])
46
- cli.warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
47
- end
48
-
49
- def use_current_path
50
- @project_gid = config.project_gid
51
- @task_gid = config.task_gid
52
- end
53
-
54
- def use_path(path)
55
- args = path.to_s.split('/')
56
- @project_gid = args[0].to_s
57
- @project_gid = nil if project_gid.empty?
58
-
59
- return if project_gid.nil?
60
-
61
- @task_gid = args[1].to_s
62
- @task_gid = nil if @task_gid.empty?
42
+ warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
63
43
  end
64
44
 
65
45
  def api
@@ -17,9 +17,14 @@ module Abt
17
17
  require_project!
18
18
 
19
19
  task
20
- print_task(project, task)
20
+ warn 'Task created'
21
+
22
+ if section
23
+ move_task
24
+ warn "Moved to section: #{section['name']}"
25
+ end
21
26
 
22
- move_task if section
27
+ print_task(project, task)
23
28
  end
24
29
 
25
30
  private
@@ -33,7 +38,6 @@ module Abt
33
38
  projects: [project_gid]
34
39
  }
35
40
  }
36
- cli.warn 'Creating task'
37
41
  api.post('tasks', Oj.dump(body, mode: :json))
38
42
  end
39
43
  end
@@ -53,7 +57,7 @@ module Abt
53
57
  end
54
58
 
55
59
  def project
56
- @project ||= api.get("projects/#{project_gid}")
60
+ @project ||= api.get("projects/#{project_gid}", opt_fields: 'name')
57
61
  end
58
62
 
59
63
  def section
@@ -62,10 +66,8 @@ module Abt
62
66
 
63
67
  def sections
64
68
  @sections ||= begin
65
- cli.warn 'Fetching sections...'
69
+ warn 'Fetching sections...'
66
70
  api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
67
- rescue Abt::HttpError::HttpError
68
- []
69
71
  end
70
72
  end
71
73
  end