abt-cli 0.0.15 → 0.0.16

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +1 -1
  3. data/lib/abt.rb +4 -3
  4. data/lib/abt/cli.rb +70 -48
  5. data/lib/abt/cli/arguments_parser.rb +70 -0
  6. data/lib/abt/cli/base_command.rb +61 -0
  7. data/lib/abt/cli/prompt.rb +2 -2
  8. data/lib/abt/docs.rb +24 -18
  9. data/lib/abt/docs/cli.rb +42 -11
  10. data/lib/abt/docs/markdown.rb +36 -10
  11. data/lib/abt/git_config.rb +11 -0
  12. data/lib/abt/providers/asana/base_command.rb +13 -13
  13. data/lib/abt/providers/asana/commands/add.rb +3 -3
  14. data/lib/abt/providers/asana/commands/{branch-name.rb → branch_name.rb} +3 -3
  15. data/lib/abt/providers/asana/commands/clear.rb +17 -6
  16. data/lib/abt/providers/asana/commands/current.rb +3 -3
  17. data/lib/abt/providers/asana/commands/finalize.rb +3 -3
  18. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -3
  19. data/lib/abt/providers/asana/commands/init.rb +3 -3
  20. data/lib/abt/providers/asana/commands/pick.rb +13 -5
  21. data/lib/abt/providers/asana/commands/projects.rb +3 -3
  22. data/lib/abt/providers/asana/commands/share.rb +5 -5
  23. data/lib/abt/providers/asana/commands/start.rb +13 -7
  24. data/lib/abt/providers/asana/commands/tasks.rb +3 -3
  25. data/lib/abt/providers/asana/configuration.rb +5 -13
  26. data/lib/abt/providers/devops/base_command.rb +14 -15
  27. data/lib/abt/providers/devops/commands/boards.rb +6 -4
  28. data/lib/abt/providers/devops/commands/{branch-name.rb → branch_name.rb} +3 -3
  29. data/lib/abt/providers/devops/commands/clear.rb +17 -6
  30. data/lib/abt/providers/devops/commands/current.rb +3 -3
  31. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +3 -3
  32. data/lib/abt/providers/devops/commands/init.rb +3 -3
  33. data/lib/abt/providers/devops/commands/pick.rb +12 -5
  34. data/lib/abt/providers/devops/commands/share.rb +4 -4
  35. data/lib/abt/providers/devops/commands/work-items.rb +3 -3
  36. data/lib/abt/providers/devops/configuration.rb +5 -13
  37. data/lib/abt/providers/git/commands/branch.rb +15 -21
  38. data/lib/abt/providers/harvest/base_command.rb +13 -13
  39. data/lib/abt/providers/harvest/commands/clear.rb +17 -6
  40. data/lib/abt/providers/harvest/commands/current.rb +3 -3
  41. data/lib/abt/providers/harvest/commands/init.rb +3 -3
  42. data/lib/abt/providers/harvest/commands/pick.rb +13 -5
  43. data/lib/abt/providers/harvest/commands/projects.rb +3 -3
  44. data/lib/abt/providers/harvest/commands/share.rb +5 -5
  45. data/lib/abt/providers/harvest/commands/start.rb +6 -42
  46. data/lib/abt/providers/harvest/commands/stop.rb +3 -3
  47. data/lib/abt/providers/harvest/commands/tasks.rb +3 -3
  48. data/lib/abt/providers/harvest/commands/track.rb +49 -11
  49. data/lib/abt/providers/harvest/configuration.rb +5 -11
  50. data/lib/abt/version.rb +1 -1
  51. metadata +6 -7
  52. data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
  53. data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
  54. data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
data/lib/abt/docs/cli.rb CHANGED
@@ -4,23 +4,54 @@ module Abt
4
4
  module Docs
5
5
  module Cli
6
6
  class << self
7
- def content
7
+ def help
8
8
  <<~TXT
9
- Usage: abt <command> [<provider[:<arguments>]>...]
9
+ Usage: #{usage_line}
10
10
 
11
- #{example_commands}
11
+ <command> Name of command to execute, e.g. start, finalize etc.
12
+ <scheme-argument> A URI-like identifier; scheme:path
13
+ Points to a project/task etc. within a system.
14
+ <options> Optional flags for the command and scheme argument
12
15
 
13
- Available commands:
14
- #{providers_commands}
16
+ #{formatted_examples(Docs.basic_examples)}
17
+
18
+ For detailed examples/commands try:
19
+ abt examples
20
+ abt commands
21
+ TXT
22
+ end
23
+
24
+ def examples
25
+ <<~TXT
26
+ Printing examples
27
+
28
+ #{formatted_examples(Docs.basic_examples)}
29
+
30
+ #{formatted_examples(Docs.extended_examples)}
31
+ TXT
32
+ end
33
+
34
+ def commands
35
+ <<~TXT
36
+ Printing commands
37
+
38
+ Run commands with --help flag to see detailed usage and flags, e.g.:
39
+ abt track harvest -h
40
+
41
+ #{commands_per_provider}
15
42
  TXT
16
43
  end
17
44
 
18
45
  private
19
46
 
20
- def example_commands
47
+ def usage_line
48
+ 'abt <command> [<scheme-argument>] [<options> --] [<scheme-argument>] ...'
49
+ end
50
+
51
+ def formatted_examples(example_groups)
21
52
  lines = []
22
53
 
23
- Docs.examples.each_with_index do |(title, examples), index|
54
+ example_groups.each_with_index do |(title, examples), index|
24
55
  lines << '' unless index.zero?
25
56
  lines << title
26
57
 
@@ -33,16 +64,16 @@ module Abt
33
64
  lines.join("\n")
34
65
  end
35
66
 
36
- def providers_commands
67
+ def commands_per_provider
37
68
  lines = []
38
69
 
39
- Docs.providers.each_with_index do |(provider_name, commands_definition), index|
70
+ Docs.providers.each_with_index do |(scheme, commands_definition), index|
40
71
  lines << '' unless index.zero?
41
- lines << "#{inflector.humanize(provider_name)}:"
72
+ lines << "#{inflector.humanize(scheme)}:"
42
73
 
43
74
  max_length = commands_definition.keys.map(&:length).max
44
75
 
45
- commands_definition.each do |(command, description)|
76
+ commands_definition.each do |(command, (_usage, description))|
46
77
  lines << " #{command.ljust(max_length)} #{description}"
47
78
  end
48
79
  end
@@ -4,18 +4,43 @@ module Abt
4
4
  module Docs
5
5
  module Markdown
6
6
  class << self
7
- def content
7
+ def readme
8
8
  <<~MD
9
9
  # Abt
10
- This readme was generated with `abt help-md > README.md`
10
+
11
+ Abt makes re-occuring tasks easily accessible from the terminal:
12
+ - Moving asana tasks around
13
+ - Tracking work/meetings in harvest
14
+ - Consistently naming branches
15
+
16
+ ## How does abt work?
17
+
18
+ Abt uses a hybrid approach between having small scripts each doing one thing:
19
+ - `start-asana --project-gid xxxx --task-gid yyyy`
20
+ - `start-harvest --project-id aaaa --task-id bbbb`
21
+
22
+ And having a single highly advanced script that does everything:
23
+ - `start xxxx/yyyy aaaa/bbbb`
24
+
25
+ Abt looks like one script, but works like a bunch of light independent scripts:
26
+ - `abt start asana:xxxx/yyyy harvest:aaaa/bbbb`
11
27
 
12
28
  ## Usage
13
- `abt <command> [<provider[:<arguments>]>...]`
29
+ `abt <command> [<scheme-argument>] [<options> --] [<scheme-argument>] ...`
30
+
31
+ Definitions:
32
+ - `<command>`: Name of command to execute, e.g. `start`, `finalize` etc.
33
+ - `<scheme-argument>`: A URI-like identifier, `scheme:path`, pointing to a project/task etc. within a system.
34
+ - `<options>`: Optional flags for the command and scheme argument
14
35
 
15
36
  #{example_commands}
16
37
 
17
38
  ## Available commands:
39
+ Some commands have `[options]`. Run such a command with `--help` flag to view supported flags, e.g: `abt track harvest -h`
40
+
18
41
  #{provider_commands}
42
+
43
+ #### This readme was generated with `abt readme > README.md`
19
44
  MD
20
45
  end
21
46
 
@@ -24,7 +49,8 @@ module Abt
24
49
  def example_commands
25
50
  lines = []
26
51
 
27
- Docs.examples.each_with_index do |(title, commands), index|
52
+ examples = Docs.basic_examples.merge(Docs.extended_examples)
53
+ examples.each_with_index do |(title, commands), index|
28
54
  lines << '' unless index.zero?
29
55
  lines << title
30
56
 
@@ -40,17 +66,17 @@ module Abt
40
66
  def provider_commands # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
41
67
  lines = []
42
68
 
43
- Docs.providers.each_with_index do |(provider_name, commands), index|
69
+ Docs.providers.each_with_index do |(scheme, commands), index|
44
70
  lines << '' unless index.zero?
45
- lines << "### #{inflector.humanize(provider_name)}"
71
+ lines << "### #{inflector.humanize(scheme)}"
46
72
  lines << '| Command | Description |'
47
73
  lines << '| :------ | :---------- |'
48
74
 
49
- max_length = commands.keys.map(&:length).max
75
+ max_length = commands.values.map(&:first).map(&:length).max
50
76
 
51
- commands.each do |(command, description)|
52
- adjusted_command = "`#{command}`".ljust(max_length + 2)
53
- lines << "| #{adjusted_command} | #{description} |"
77
+ commands.each do |(_command, (usage, description))|
78
+ adjusted_usage = "`#{usage}`".ljust(max_length + 2)
79
+ lines << "| #{adjusted_usage} | #{description} |"
54
80
  end
55
81
  end
56
82
 
@@ -4,6 +4,8 @@ module Abt
4
4
  class GitConfig
5
5
  attr_reader :namespace, :scope
6
6
 
7
+ class UnsafeNamespaceError < StandardError; end
8
+
7
9
  LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND = 'git config --local -l'
8
10
 
9
11
  def self.local_available?
@@ -67,6 +69,15 @@ module Abt
67
69
  end
68
70
  end
69
71
 
72
+ def clear(output: nil)
73
+ raise UnsafeNamespaceError, 'Keys can only be cleared within a namespace' if namespace.empty?
74
+
75
+ keys.each do |key|
76
+ output&.puts "Clearing #{scope}: #{key_with_namespace(key)}"
77
+ self[key] = nil
78
+ end
79
+ end
80
+
70
81
  private
71
82
 
72
83
  def ensure_scope_available!
@@ -3,19 +3,19 @@
3
3
  module Abt
4
4
  module Providers
5
5
  module Asana
6
- class BaseCommand
7
- attr_reader :arg_str, :project_gid, :task_gid, :cli, :config
6
+ class BaseCommand < Abt::Cli::BaseCommand
7
+ attr_reader :project_gid, :task_gid, :config
8
+
9
+ def initialize(path:, cli:, **)
10
+ super
8
11
 
9
- def initialize(arg_str:, cli:)
10
- @arg_str = arg_str
11
12
  @config = Configuration.new(cli: cli)
12
13
 
13
- if arg_str.nil?
14
- use_current_args
14
+ if path.nil?
15
+ use_current_path
15
16
  else
16
- use_arg_str(arg_str)
17
+ use_path(path)
17
18
  end
18
- @cli = cli
19
19
  end
20
20
 
21
21
  private
@@ -36,23 +36,23 @@ module Abt
36
36
  end
37
37
 
38
38
  def print_project(project)
39
- cli.print_provider_command('asana', project['gid'], project['name'])
39
+ cli.print_scheme_argument('asana', project['gid'], project['name'])
40
40
  cli.warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
41
41
  end
42
42
 
43
43
  def print_task(project, task)
44
44
  project = { 'gid' => project } if project.is_a?(String)
45
- cli.print_provider_command('asana', "#{project['gid']}/#{task['gid']}", task['name'])
45
+ cli.print_scheme_argument('asana', "#{project['gid']}/#{task['gid']}", task['name'])
46
46
  cli.warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
47
47
  end
48
48
 
49
- def use_current_args
49
+ def use_current_path
50
50
  @project_gid = config.project_gid
51
51
  @task_gid = config.task_gid
52
52
  end
53
53
 
54
- def use_arg_str(arg_str)
55
- args = arg_str.to_s.split('/')
54
+ def use_path(path)
55
+ args = path.to_s.split('/')
56
56
  @project_gid = args[0].to_s
57
57
  @project_gid = nil if project_gid.empty?
58
58
 
@@ -5,15 +5,15 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Add < BaseCommand
8
- def self.command
9
- 'add asana[:<project-gid>]'
8
+ def self.usage
9
+ 'abt add asana[:<project-gid>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Create a new task for the current/specified Asana project'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_project!
18
18
 
19
19
  task
@@ -5,15 +5,15 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class BranchName < BaseCommand
8
- def self.command
9
- 'branch-name asana[:<project-gid>/<task-gid>]'
8
+ def self.usage
9
+ 'abt branch-name asana[:<project-gid>/<task-gid>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Suggest a git branch name for the current/specified task.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_task!
18
18
  ensure_current_is_valid!
19
19
 
@@ -5,17 +5,28 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Clear < BaseCommand
8
- def self.command
9
- 'clear asana'
8
+ def self.usage
9
+ 'abt clear asana'
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Clear project/task for current git repository'
13
+ 'Clear asana configuration'
14
14
  end
15
15
 
16
- def call
17
- cli.warn 'Clearing Asana project configuration'
18
- config.clear_local
16
+ def self.flags
17
+ [
18
+ ['-g', '--global', 'Clear global instead of local asana configuration (credentials etc.)'],
19
+ ['-a', '--all', 'Clear all asana configuration']
20
+ ]
21
+ end
22
+
23
+ def perform
24
+ if flags[:global] && flags[:all]
25
+ abort('Flags --global and --all cannot be used at the same time')
26
+ end
27
+
28
+ config.clear_local unless flags[:global]
29
+ config.clear_global if flags[:global] || flags[:all]
19
30
  end
20
31
  end
21
32
  end
@@ -5,15 +5,15 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Current < BaseCommand
8
- def self.command
9
- 'current asana[:<project-gid>[/<task-gid>]]'
8
+ def self.usage
9
+ 'abt current asana[:<project-gid>[/<task-gid>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Get or set project and or task for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_project!
18
18
 
19
19
  if same_args_as_config? || !config.local_available?
@@ -5,15 +5,15 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Finalize < BaseCommand
8
- def self.command
9
- 'finalize asana[:<project-gid>/<task-gid>]'
8
+ def self.usage
9
+ 'abt finalize asana[:<project-gid>/<task-gid>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Move current/specified task to section (column) for finalized tasks'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  unless config.local_available?
18
18
  cli.abort 'This is a no-op for tasks outside the current project'
19
19
  end
@@ -5,15 +5,15 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class HarvestTimeEntryData < BaseCommand
8
- def self.command
9
- 'harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
8
+ def self.usage
9
+ 'abt harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_task!
18
18
  ensure_current_is_valid!
19
19
 
@@ -5,8 +5,8 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Init < BaseCommand
8
- def self.command
9
- 'init asana'
8
+ def self.usage
9
+ 'abt init asana'
10
10
  end
11
11
 
12
12
  def self.description
@@ -18,7 +18,7 @@ module Abt
18
18
  @cli = cli
19
19
  end
20
20
 
21
- def call
21
+ def perform
22
22
  cli.abort 'Must be run inside a git repository' unless config.local_available?
23
23
 
24
24
  projects # Load projects up front to make it obvious that searches are instant
@@ -5,15 +5,21 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Pick < BaseCommand
8
- def self.command
9
- 'pick asana[:<project-gid>]'
8
+ def self.usage
9
+ 'abt pick asana[:<project-gid>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Pick task for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def self.flags
17
+ [
18
+ ['-d', '--dry-run', 'Keep existing configuration']
19
+ ]
20
+ end
21
+
22
+ def perform
17
23
  cli.abort 'Must be run inside a git repository' unless config.local_available?
18
24
  require_project!
19
25
 
@@ -21,10 +27,12 @@ module Abt
21
27
 
22
28
  task = select_task
23
29
 
30
+ print_task(project, task)
31
+
32
+ return if flags[:"dry-run"]
33
+
24
34
  config.project_gid = project_gid # We might have gotten the project ID as an argument
25
35
  config.task_gid = task['gid']
26
-
27
- print_task(project, task)
28
36
  end
29
37
 
30
38
  private
@@ -5,15 +5,15 @@ module Abt
5
5
  module Asana
6
6
  module Commands
7
7
  class Projects < BaseCommand
8
- def self.command
9
- 'projects asana'
8
+ def self.usage
9
+ 'abt projects asana'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'List all available projects - useful for piping into grep etc.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  projects.map do |project|
18
18
  print_project(project)
19
19
  end