abt-cli 0.0.11 → 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.
- checksums.yaml +4 -4
- data/bin/abt +1 -7
- data/lib/abt.rb +12 -3
- data/lib/abt/cli.rb +91 -53
- data/lib/abt/cli/arguments_parser.rb +70 -0
- data/lib/abt/cli/base_command.rb +61 -0
- data/lib/abt/cli/prompt.rb +124 -0
- data/lib/abt/docs.rb +24 -18
- data/lib/abt/docs/cli.rb +42 -11
- data/lib/abt/docs/markdown.rb +36 -10
- data/lib/abt/git_config.rb +34 -19
- data/lib/abt/helpers.rb +1 -1
- data/lib/abt/providers/asana/base_command.rb +24 -13
- data/lib/abt/providers/asana/commands/add.rb +75 -0
- data/lib/abt/providers/asana/commands/branch_name.rb +44 -0
- data/lib/abt/providers/asana/commands/clear.rb +17 -6
- data/lib/abt/providers/asana/commands/current.rb +6 -6
- data/lib/abt/providers/asana/commands/finalize.rb +4 -4
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +4 -3
- data/lib/abt/providers/asana/commands/init.rb +5 -5
- data/lib/abt/providers/asana/commands/pick.rb +16 -7
- data/lib/abt/providers/asana/commands/projects.rb +3 -3
- data/lib/abt/providers/asana/commands/share.rb +8 -8
- data/lib/abt/providers/asana/commands/start.rb +15 -9
- data/lib/abt/providers/asana/commands/tasks.rb +5 -3
- data/lib/abt/providers/asana/configuration.rb +8 -16
- data/lib/abt/providers/devops/api.rb +32 -2
- data/lib/abt/providers/devops/base_command.rb +32 -16
- data/lib/abt/providers/devops/commands/boards.rb +36 -0
- data/lib/abt/providers/devops/commands/branch_name.rb +45 -0
- data/lib/abt/providers/devops/commands/clear.rb +17 -6
- data/lib/abt/providers/devops/commands/current.rb +6 -10
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +5 -3
- data/lib/abt/providers/devops/commands/init.rb +5 -5
- data/lib/abt/providers/devops/commands/pick.rb +29 -20
- data/lib/abt/providers/devops/commands/share.rb +7 -13
- data/lib/abt/providers/devops/commands/work-items.rb +46 -0
- data/lib/abt/providers/devops/configuration.rb +7 -15
- data/lib/abt/providers/git.rb +19 -0
- data/lib/abt/providers/git/commands/branch.rb +74 -0
- data/lib/abt/providers/harvest/base_command.rb +24 -13
- data/lib/abt/providers/harvest/commands/clear.rb +17 -6
- data/lib/abt/providers/harvest/commands/current.rb +6 -6
- data/lib/abt/providers/harvest/commands/init.rb +5 -5
- data/lib/abt/providers/harvest/commands/pick.rb +15 -6
- data/lib/abt/providers/harvest/commands/projects.rb +3 -3
- data/lib/abt/providers/harvest/commands/share.rb +5 -5
- data/lib/abt/providers/harvest/commands/start.rb +6 -44
- data/lib/abt/providers/harvest/commands/stop.rb +3 -3
- data/lib/abt/providers/harvest/commands/tasks.rb +5 -3
- data/lib/abt/providers/harvest/commands/track.rb +50 -13
- data/lib/abt/providers/harvest/configuration.rb +7 -13
- data/lib/abt/version.rb +1 -1
- metadata +12 -7
- data/lib/abt/cli/dialogs.rb +0 -86
- data/lib/abt/cli/io.rb +0 -23
- data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
- data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
- data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
data/lib/abt/docs.rb
CHANGED
@@ -7,7 +7,7 @@ end
|
|
7
7
|
module Abt
|
8
8
|
module Docs
|
9
9
|
class << self
|
10
|
-
def
|
10
|
+
def basic_examples
|
11
11
|
{
|
12
12
|
'Getting started:' => {
|
13
13
|
'abt init asana harvest' => 'Setup asana and harvest project git repo in working dir',
|
@@ -16,10 +16,15 @@ module Abt
|
|
16
16
|
'abt stop harvest' => 'Stop time tracker',
|
17
17
|
'abt start asana harvest' => 'Continue working, e.g. after a break',
|
18
18
|
'abt finalize asana' => 'Finalize the selected asana task'
|
19
|
-
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def extended_examples
|
24
|
+
{
|
20
25
|
'Tracking meetings (without changing the config):' => {
|
21
|
-
'abt
|
22
|
-
'abt
|
26
|
+
'abt pick asana -d | abt track harvest' => 'Track on asana meeting task',
|
27
|
+
'abt pick harvest -d | abt track harvest -c "Name of meeting"' => 'Track on separate harvest-task'
|
23
28
|
},
|
24
29
|
'Command output can be piped, e.g.:' => {
|
25
30
|
'abt tasks asana | grep -i <name of task>' => nil,
|
@@ -29,30 +34,31 @@ module Abt
|
|
29
34
|
'abt share asana harvest | tr "\n" " "' => 'Print current configuration',
|
30
35
|
'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy configuration (mac only)',
|
31
36
|
'abt start <shared configuration>' => 'Start a shared configuration'
|
37
|
+
},
|
38
|
+
'Flags:' => {
|
39
|
+
'abt start harvest -c "comment"' => 'Add command flags after <scheme>:<path>',
|
40
|
+
'abt start harvest -c "comment" -- asana' => 'Use -- to mark the end of a flag list if it\'s to be followed by a <scheme-argument>',
|
41
|
+
'abt pick harvest | abt start -c "comment"' => 'Flags placed directly after a command applies to piped in <scheme-argument>'
|
32
42
|
}
|
33
43
|
}
|
34
44
|
end
|
35
45
|
|
36
46
|
def providers
|
37
|
-
|
47
|
+
@providers ||= Abt.schemes.sort.each_with_object({}) do |scheme, definition|
|
48
|
+
definition[scheme] = command_definitions(scheme)
|
49
|
+
end
|
38
50
|
end
|
39
51
|
|
40
52
|
private
|
41
53
|
|
42
|
-
def
|
43
|
-
Abt.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def command_definitions(provider_module)
|
51
|
-
provider_module.command_names.each_with_object({}) do |name, definition|
|
52
|
-
command_class = provider_module.command_class(name)
|
54
|
+
def command_definitions(scheme)
|
55
|
+
provider = Abt.scheme_provider(scheme)
|
56
|
+
provider.command_names.each_with_object({}) do |name, definition|
|
57
|
+
command_class = provider.command_class(name)
|
58
|
+
full_name = "abt #{name} #{scheme}"
|
53
59
|
|
54
|
-
if command_class.respond_to?(:
|
55
|
-
definition[
|
60
|
+
if command_class.respond_to?(:usage) && command_class.respond_to?(:description)
|
61
|
+
definition[full_name] = [command_class.usage, command_class.description]
|
56
62
|
end
|
57
63
|
end
|
58
64
|
end
|
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
|
7
|
+
def help
|
8
8
|
<<~TXT
|
9
|
-
Usage:
|
9
|
+
Usage: #{usage_line}
|
10
10
|
|
11
|
-
|
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
|
-
|
14
|
-
|
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
|
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
|
-
|
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
|
67
|
+
def commands_per_provider
|
37
68
|
lines = []
|
38
69
|
|
39
|
-
Docs.providers.each_with_index do |(
|
70
|
+
Docs.providers.each_with_index do |(scheme, commands_definition), index|
|
40
71
|
lines << '' unless index.zero?
|
41
|
-
lines << "#{inflector.humanize(
|
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
|
data/lib/abt/docs/markdown.rb
CHANGED
@@ -4,18 +4,43 @@ module Abt
|
|
4
4
|
module Docs
|
5
5
|
module Markdown
|
6
6
|
class << self
|
7
|
-
def
|
7
|
+
def readme
|
8
8
|
<<~MD
|
9
9
|
# Abt
|
10
|
-
|
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> [<
|
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.
|
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 |(
|
69
|
+
Docs.providers.each_with_index do |(scheme, commands), index|
|
44
70
|
lines << '' unless index.zero?
|
45
|
-
lines << "### #{inflector.humanize(
|
71
|
+
lines << "### #{inflector.humanize(scheme)}"
|
46
72
|
lines << '| Command | Description |'
|
47
73
|
lines << '| :------ | :---------- |'
|
48
74
|
|
49
|
-
max_length = commands.
|
75
|
+
max_length = commands.values.map(&:first).map(&:length).max
|
50
76
|
|
51
|
-
commands.each do |(
|
52
|
-
|
53
|
-
lines << "| #{
|
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
|
|
data/lib/abt/git_config.rb
CHANGED
@@ -4,13 +4,19 @@ module Abt
|
|
4
4
|
class GitConfig
|
5
5
|
attr_reader :namespace, :scope
|
6
6
|
|
7
|
+
class UnsafeNamespaceError < StandardError; end
|
8
|
+
|
9
|
+
LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND = 'git config --local -l'
|
10
|
+
|
7
11
|
def self.local_available?
|
8
|
-
@local_available
|
9
|
-
|
10
|
-
|
11
|
-
|
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?
|
12
18
|
end
|
13
|
-
|
19
|
+
success
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|
@@ -32,19 +38,17 @@ module Abt
|
|
32
38
|
set(key, value)
|
33
39
|
end
|
34
40
|
|
35
|
-
def full_keys
|
36
|
-
if scope == 'local' && !self.class.local_available?
|
37
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
38
|
-
end
|
39
|
-
|
40
|
-
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
41
|
-
end
|
42
|
-
|
43
41
|
def keys
|
44
42
|
offset = namespace.length + 1
|
45
43
|
full_keys.map { |key| key[offset..-1] }
|
46
44
|
end
|
47
45
|
|
46
|
+
def full_keys
|
47
|
+
ensure_scope_available!
|
48
|
+
|
49
|
+
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
50
|
+
end
|
51
|
+
|
48
52
|
def local
|
49
53
|
@local ||= begin
|
50
54
|
if scope == 'local'
|
@@ -65,25 +69,36 @@ module Abt
|
|
65
69
|
end
|
66
70
|
end
|
67
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
|
+
|
68
81
|
private
|
69
82
|
|
83
|
+
def ensure_scope_available!
|
84
|
+
return if scope != 'local' || self.class.local_available?
|
85
|
+
|
86
|
+
raise StandardError, 'Local configuration is not available outside a git repository'
|
87
|
+
end
|
88
|
+
|
70
89
|
def key_with_namespace(key)
|
71
90
|
namespace.empty? ? key : "#{namespace}.#{key}"
|
72
91
|
end
|
73
92
|
|
74
93
|
def get(key)
|
75
|
-
|
76
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
77
|
-
end
|
94
|
+
ensure_scope_available!
|
78
95
|
|
79
96
|
git_value = `git config --#{scope} --get #{key_with_namespace(key).inspect}`.strip
|
80
97
|
git_value.empty? ? nil : git_value
|
81
98
|
end
|
82
99
|
|
83
100
|
def set(key, value)
|
84
|
-
|
85
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
86
|
-
end
|
101
|
+
ensure_scope_available!
|
87
102
|
|
88
103
|
if value.nil? || value.empty?
|
89
104
|
`git config --#{scope} --unset #{key_with_namespace(key).inspect}`
|
data/lib/abt/helpers.rb
CHANGED
@@ -3,45 +3,56 @@
|
|
3
3
|
module Abt
|
4
4
|
module Providers
|
5
5
|
module Asana
|
6
|
-
class BaseCommand
|
7
|
-
attr_reader :
|
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
|
14
|
-
|
14
|
+
if path.nil?
|
15
|
+
use_current_path
|
15
16
|
else
|
16
|
-
|
17
|
+
use_path(path)
|
17
18
|
end
|
18
|
-
@cli = cli
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
def require_project!
|
24
|
+
cli.abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def require_task!
|
28
|
+
if project_gid.nil?
|
29
|
+
cli.abort 'No current/specified project. Did you initialize Asana and pick a task?'
|
30
|
+
end
|
31
|
+
cli.abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
|
32
|
+
end
|
33
|
+
|
23
34
|
def same_args_as_config?
|
24
35
|
project_gid == config.project_gid && task_gid == config.task_gid
|
25
36
|
end
|
26
37
|
|
27
38
|
def print_project(project)
|
28
|
-
cli.
|
39
|
+
cli.print_scheme_argument('asana', project['gid'], project['name'])
|
29
40
|
cli.warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
|
30
41
|
end
|
31
42
|
|
32
43
|
def print_task(project, task)
|
33
44
|
project = { 'gid' => project } if project.is_a?(String)
|
34
|
-
cli.
|
45
|
+
cli.print_scheme_argument('asana', "#{project['gid']}/#{task['gid']}", task['name'])
|
35
46
|
cli.warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
|
36
47
|
end
|
37
48
|
|
38
|
-
def
|
49
|
+
def use_current_path
|
39
50
|
@project_gid = config.project_gid
|
40
51
|
@task_gid = config.task_gid
|
41
52
|
end
|
42
53
|
|
43
|
-
def
|
44
|
-
args =
|
54
|
+
def use_path(path)
|
55
|
+
args = path.to_s.split('/')
|
45
56
|
@project_gid = args[0].to_s
|
46
57
|
@project_gid = nil if project_gid.empty?
|
47
58
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Add < BaseCommand
|
8
|
+
def self.usage
|
9
|
+
'abt add asana[:<project-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Create a new task for the current/specified Asana project'
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
require_project!
|
18
|
+
|
19
|
+
task
|
20
|
+
print_task(project, task)
|
21
|
+
|
22
|
+
move_task if section
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def task
|
28
|
+
@task ||= begin
|
29
|
+
body = {
|
30
|
+
data: {
|
31
|
+
name: name,
|
32
|
+
notes: notes,
|
33
|
+
projects: [project_gid]
|
34
|
+
}
|
35
|
+
}
|
36
|
+
cli.warn 'Creating task'
|
37
|
+
api.post('tasks', Oj.dump(body, mode: :json))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def move_task
|
42
|
+
body = { data: { task: task['gid'] } }
|
43
|
+
body_json = Oj.dump(body, mode: :json)
|
44
|
+
api.post("sections/#{section['gid']}/addTask", body_json)
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
@name ||= cli.prompt.text 'Enter task description'
|
49
|
+
end
|
50
|
+
|
51
|
+
def notes
|
52
|
+
@notes ||= cli.prompt.text 'Enter task notes'
|
53
|
+
end
|
54
|
+
|
55
|
+
def project
|
56
|
+
@project ||= api.get("projects/#{project_gid}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def section
|
60
|
+
@section ||= cli.prompt.choice 'Add to section?', sections, ['q', 'Don\'t add to section']
|
61
|
+
end
|
62
|
+
|
63
|
+
def sections
|
64
|
+
@sections ||= begin
|
65
|
+
cli.warn 'Fetching sections...'
|
66
|
+
api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
|
67
|
+
rescue Abt::HttpError::HttpError
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|