abt-cli 0.0.13 → 0.0.18
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 -1
- data/lib/abt.rb +6 -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/{dialogs.rb → prompt.rb} +37 -18
- data/lib/abt/docs.rb +30 -24
- data/lib/abt/docs/cli.rb +42 -11
- data/lib/abt/docs/markdown.rb +38 -11
- data/lib/abt/git_config.rb +25 -14
- data/lib/abt/helpers.rb +1 -1
- data/lib/abt/providers/asana/base_command.rb +13 -13
- data/lib/abt/providers/asana/commands/add.rb +6 -6
- 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 +3 -3
- data/lib/abt/providers/asana/commands/finalize.rb +3 -3
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -3
- data/lib/abt/providers/asana/commands/init.rb +5 -5
- data/lib/abt/providers/asana/commands/pick.rb +16 -8
- data/lib/abt/providers/asana/commands/projects.rb +3 -3
- data/lib/abt/providers/asana/commands/share.rb +5 -5
- data/lib/abt/providers/asana/commands/start.rb +14 -8
- data/lib/abt/providers/asana/commands/tasks.rb +3 -3
- data/lib/abt/providers/asana/configuration.rb +8 -16
- data/lib/abt/providers/devops/base_command.rb +14 -15
- data/lib/abt/providers/devops/commands/boards.rb +6 -4
- 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 +3 -3
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +3 -3
- data/lib/abt/providers/devops/commands/init.rb +5 -5
- data/lib/abt/providers/devops/commands/pick.rb +14 -7
- data/lib/abt/providers/devops/commands/share.rb +4 -4
- data/lib/abt/providers/devops/commands/work-items.rb +3 -3
- 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 +13 -13
- data/lib/abt/providers/harvest/commands/clear.rb +17 -6
- data/lib/abt/providers/harvest/commands/current.rb +3 -3
- data/lib/abt/providers/harvest/commands/init.rb +5 -5
- data/lib/abt/providers/harvest/commands/pick.rb +15 -7
- 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 -42
- data/lib/abt/providers/harvest/commands/stop.rb +3 -3
- data/lib/abt/providers/harvest/commands/tasks.rb +3 -3
- data/lib/abt/providers/harvest/commands/track.rb +49 -11
- data/lib/abt/providers/harvest/configuration.rb +7 -13
- data/lib/abt/version.rb +1 -1
- metadata +9 -7
- 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,21 +7,26 @@ 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
|
-
'abt init asana harvest' => 'Setup asana and harvest project git repo
|
14
|
-
'abt pick harvest' => 'Pick harvest
|
15
|
-
'abt pick asana | abt start harvest' => 'Pick asana task and start
|
13
|
+
'abt init asana harvest' => 'Setup asana and harvest project for local git repo',
|
14
|
+
'abt pick harvest' => 'Pick harvest task. This will likely stay the same throughout the project',
|
15
|
+
'abt pick asana | abt start harvest' => 'Pick asana task and start tracking time',
|
16
16
|
'abt stop harvest' => 'Stop time tracker',
|
17
|
-
'abt start asana harvest' => 'Continue working, e.g
|
17
|
+
'abt start asana harvest' => 'Continue working, e.g., after a break',
|
18
18
|
'abt finalize asana' => 'Finalize the selected asana task'
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def extended_examples
|
24
|
+
{
|
25
|
+
'Tracking meetings (without switching current task setting):' => {
|
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'
|
19
28
|
},
|
20
|
-
'
|
21
|
-
'abt tasks asana | grep -i standup | abt track harvest' => 'Track on asana meeting task without changing any configuration',
|
22
|
-
'abt tasks harvest | grep -i comment | abt track harvest' => 'Track on harvest "Comment"-task (will prompt for a comment)'
|
23
|
-
},
|
24
|
-
'Command output can be piped, e.g.:' => {
|
29
|
+
'Command output can be piped:' => {
|
25
30
|
'abt tasks asana | grep -i <name of task>' => nil,
|
26
31
|
'abt tasks asana | grep -i <name of task> | abt start' => nil
|
27
32
|
},
|
@@ -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 ARIs',
|
40
|
+
'abt start harvest -c "comment" -- asana' => 'Use -- to end a list of flags, so that it can be followed by another ARI',
|
41
|
+
'abt pick harvest | abt start -c "comment"' => 'Flags placed directly after a command applies to the piped in ARI'
|
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
|
+
<ARI> A URI-like resource identifier with a scheme and an optional path
|
13
|
+
in the format: <scheme>[:<path>]. E.g., harvest:11111111/22222222
|
14
|
+
<options> Optional flags for the command and ARI
|
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> [<ARI>] [<options> --] [<ARI>] ...'
|
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,44 @@ 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 is a hybrid af 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 with a single command:
|
23
|
+
- `start xxxx/yyyy aaaa/bbbb`
|
24
|
+
|
25
|
+
Abt looks like one command, but works like a bunch of light scripts:
|
26
|
+
- `abt start asana:xxxx/yyyy harvest:aaaa/bbbb`
|
11
27
|
|
12
28
|
## Usage
|
13
|
-
`abt <command> [<
|
29
|
+
`abt <command> [<ARI>] [<options> --] [<ARI>] ...`
|
30
|
+
|
31
|
+
Definitions:
|
32
|
+
- `<command>`: Name of command to execute, e.g. `start`, `finalize` etc.
|
33
|
+
- `<ARI>`: A URI-like resource identifier with a scheme and an optional path in the format: `<scheme>[:<path>]`. E.g., `harvest:11111111/22222222`
|
34
|
+
- `<options>`: Optional flags for the command and ARI
|
14
35
|
|
15
36
|
#{example_commands}
|
16
37
|
|
17
|
-
##
|
38
|
+
## Commands:
|
39
|
+
|
40
|
+
Some commands have `[options]`. Run such a command with `--help` flag to view supported flags, e.g: `abt track harvest -h`
|
41
|
+
|
18
42
|
#{provider_commands}
|
43
|
+
|
44
|
+
#### This readme was generated with `abt readme > README.md`
|
19
45
|
MD
|
20
46
|
end
|
21
47
|
|
@@ -24,7 +50,8 @@ module Abt
|
|
24
50
|
def example_commands
|
25
51
|
lines = []
|
26
52
|
|
27
|
-
Docs.
|
53
|
+
examples = Docs.basic_examples.merge(Docs.extended_examples)
|
54
|
+
examples.each_with_index do |(title, commands), index|
|
28
55
|
lines << '' unless index.zero?
|
29
56
|
lines << title
|
30
57
|
|
@@ -40,17 +67,17 @@ module Abt
|
|
40
67
|
def provider_commands # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
41
68
|
lines = []
|
42
69
|
|
43
|
-
Docs.providers.each_with_index do |(
|
70
|
+
Docs.providers.each_with_index do |(scheme, commands), index|
|
44
71
|
lines << '' unless index.zero?
|
45
|
-
lines << "### #{inflector.humanize(
|
72
|
+
lines << "### #{inflector.humanize(scheme)}"
|
46
73
|
lines << '| Command | Description |'
|
47
74
|
lines << '| :------ | :---------- |'
|
48
75
|
|
49
|
-
max_length = commands.
|
76
|
+
max_length = commands.values.map(&:first).map(&:length).max
|
50
77
|
|
51
|
-
commands.each do |(
|
52
|
-
|
53
|
-
lines << "| #{
|
78
|
+
commands.each do |(_command, (usage, description))|
|
79
|
+
adjusted_usage = "`#{usage}`".ljust(max_length + 2)
|
80
|
+
lines << "| #{adjusted_usage} | #{description} |"
|
54
81
|
end
|
55
82
|
end
|
56
83
|
|
data/lib/abt/git_config.rb
CHANGED
@@ -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?
|
@@ -36,19 +38,17 @@ module Abt
|
|
36
38
|
set(key, value)
|
37
39
|
end
|
38
40
|
|
39
|
-
def full_keys
|
40
|
-
if scope == 'local' && !self.class.local_available?
|
41
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
42
|
-
end
|
43
|
-
|
44
|
-
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
45
|
-
end
|
46
|
-
|
47
41
|
def keys
|
48
42
|
offset = namespace.length + 1
|
49
43
|
full_keys.map { |key| key[offset..-1] }
|
50
44
|
end
|
51
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
|
+
|
52
52
|
def local
|
53
53
|
@local ||= begin
|
54
54
|
if scope == 'local'
|
@@ -69,25 +69,36 @@ module Abt
|
|
69
69
|
end
|
70
70
|
end
|
71
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
|
+
|
72
81
|
private
|
73
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
|
+
|
74
89
|
def key_with_namespace(key)
|
75
90
|
namespace.empty? ? key : "#{namespace}.#{key}"
|
76
91
|
end
|
77
92
|
|
78
93
|
def get(key)
|
79
|
-
|
80
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
81
|
-
end
|
94
|
+
ensure_scope_available!
|
82
95
|
|
83
96
|
git_value = `git config --#{scope} --get #{key_with_namespace(key).inspect}`.strip
|
84
97
|
git_value.empty? ? nil : git_value
|
85
98
|
end
|
86
99
|
|
87
100
|
def set(key, value)
|
88
|
-
|
89
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
90
|
-
end
|
101
|
+
ensure_scope_available!
|
91
102
|
|
92
103
|
if value.nil? || value.empty?
|
93
104
|
`git config --#{scope} --unset #{key_with_namespace(key).inspect}`
|
data/lib/abt/helpers.rb
CHANGED
@@ -3,19 +3,19 @@
|
|
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
|
@@ -36,23 +36,23 @@ module Abt
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def print_project(project)
|
39
|
-
cli.
|
39
|
+
cli.print_ari('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.
|
45
|
+
cli.print_ari('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
|
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
|
55
|
-
args =
|
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.
|
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
|
16
|
+
def perform
|
17
17
|
require_project!
|
18
18
|
|
19
19
|
task
|
@@ -45,11 +45,11 @@ module Abt
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def name
|
48
|
-
@name ||= cli.prompt 'Enter task description'
|
48
|
+
@name ||= cli.prompt.text 'Enter task description'
|
49
49
|
end
|
50
50
|
|
51
51
|
def notes
|
52
|
-
@notes ||= cli.prompt 'Enter task notes'
|
52
|
+
@notes ||= cli.prompt.text 'Enter task notes'
|
53
53
|
end
|
54
54
|
|
55
55
|
def project
|
@@ -57,7 +57,7 @@ module Abt
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def section
|
60
|
-
@section ||= cli.
|
60
|
+
@section ||= cli.prompt.choice 'Add to section?', sections, ['q', 'Don\'t add to section']
|
61
61
|
end
|
62
62
|
|
63
63
|
def sections
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class BranchName < BaseCommand
|
8
|
+
def self.usage
|
9
|
+
'abt branch-name asana[:<project-gid>/<task-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Suggest a git branch name for the current/specified task.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
require_task!
|
18
|
+
ensure_current_is_valid!
|
19
|
+
|
20
|
+
cli.puts name
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def name
|
26
|
+
task['name'].downcase.gsub(/[^\w]+/, '-')
|
27
|
+
end
|
28
|
+
|
29
|
+
def ensure_current_is_valid!
|
30
|
+
cli.abort "Invalid task gid: #{task_gid}" if task.nil?
|
31
|
+
|
32
|
+
return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
|
33
|
+
|
34
|
+
cli.abort "Invalid project gid: #{project_gid}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def task
|
38
|
+
@task ||= api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.project')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|