abt-cli 0.0.26 → 0.0.31

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.rb +0 -4
  3. data/lib/abt/cli.rb +5 -1
  4. data/lib/abt/directory_config.rb +28 -10
  5. data/lib/abt/docs.rb +10 -6
  6. data/lib/abt/providers/asana.rb +1 -0
  7. data/lib/abt/providers/asana/base_command.rb +33 -3
  8. data/lib/abt/providers/asana/commands/add.rb +0 -4
  9. data/lib/abt/providers/asana/commands/branch_name.rb +0 -13
  10. data/lib/abt/providers/asana/commands/clear.rb +1 -1
  11. data/lib/abt/providers/asana/commands/current.rb +0 -18
  12. data/lib/abt/providers/asana/commands/finalize.rb +2 -4
  13. data/lib/abt/providers/asana/commands/pick.rb +11 -41
  14. data/lib/abt/providers/asana/commands/tasks.rb +2 -7
  15. data/lib/abt/providers/asana/commands/write_config.rb +73 -0
  16. data/lib/abt/providers/asana/configuration.rb +1 -1
  17. data/lib/abt/providers/asana/path.rb +2 -2
  18. data/lib/abt/providers/asana/services/project_picker.rb +54 -0
  19. data/lib/abt/providers/asana/services/task_picker.rb +83 -0
  20. data/lib/abt/providers/devops.rb +1 -0
  21. data/lib/abt/providers/devops/api.rb +27 -20
  22. data/lib/abt/providers/devops/base_command.rb +42 -25
  23. data/lib/abt/providers/devops/commands/branch_name.rb +8 -16
  24. data/lib/abt/providers/devops/commands/clear.rb +1 -1
  25. data/lib/abt/providers/devops/commands/current.rb +2 -21
  26. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +8 -16
  27. data/lib/abt/providers/devops/commands/pick.rb +11 -60
  28. data/lib/abt/providers/devops/commands/work_items.rb +3 -7
  29. data/lib/abt/providers/devops/commands/write_config.rb +47 -0
  30. data/lib/abt/providers/devops/configuration.rb +1 -1
  31. data/lib/abt/providers/devops/path.rb +24 -8
  32. data/lib/abt/providers/devops/services/board_picker.rb +69 -0
  33. data/lib/abt/providers/devops/services/project_picker.rb +73 -0
  34. data/lib/abt/providers/devops/services/work_item_picker.rb +99 -0
  35. data/lib/abt/providers/harvest.rb +1 -0
  36. data/lib/abt/providers/harvest/base_command.rb +45 -3
  37. data/lib/abt/providers/harvest/commands/clear.rb +1 -1
  38. data/lib/abt/providers/harvest/commands/current.rb +0 -28
  39. data/lib/abt/providers/harvest/commands/pick.rb +12 -27
  40. data/lib/abt/providers/harvest/commands/projects.rb +2 -9
  41. data/lib/abt/providers/harvest/commands/tasks.rb +2 -19
  42. data/lib/abt/providers/harvest/commands/track.rb +72 -39
  43. data/lib/abt/providers/harvest/commands/write_config.rb +41 -0
  44. data/lib/abt/providers/harvest/configuration.rb +1 -1
  45. data/lib/abt/providers/harvest/harvest_helpers.rb +25 -0
  46. data/lib/abt/providers/harvest/path.rb +1 -1
  47. data/lib/abt/providers/harvest/services/project_picker.rb +53 -0
  48. data/lib/abt/providers/harvest/services/task_picker.rb +50 -0
  49. data/lib/abt/version.rb +1 -1
  50. metadata +13 -6
  51. data/lib/abt/providers/asana/commands/init.rb +0 -42
  52. data/lib/abt/providers/devops/commands/boards.rb +0 -34
  53. data/lib/abt/providers/devops/commands/init.rb +0 -79
  54. data/lib/abt/providers/harvest/commands/init.rb +0 -53
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed527a89749355f223b94daa60d63b91c78ae98ab6976c3470ec6ca42cbcff27
4
- data.tar.gz: aed24bd58e30bfbbdc574532eb97044cd8e108851c2586ac407c0eb42a2deb28
3
+ metadata.gz: 9b0f6705f90f844f11a301d2aee417ded8524de882bba8cd114d62ceee77ee92
4
+ data.tar.gz: d51120f9529853801fefd8b557102e1cac058685da5208f5a42577726e3c871d
5
5
  SHA512:
6
- metadata.gz: 6d615f241ac406df5f89cc79ad36f19b0554602f6eb3e20e2f01ba0143595ea6e748c727ccedfeecd1a826c46e031acc7160a3b0ee7462027157a357d730de35
7
- data.tar.gz: 2b9e500f77e181f4198ac17939fc1753f9a7105682783b005d780ec4da61c8af43aa3e986bba34e1df4f5173bde47bd0e97e042f293edb713a364d2a9c1c71b6
6
+ metadata.gz: f2e08dd8e61a8d0271661af7e653b4da5d26f5ba0790cf657e2ec0c8524d784af707e1cd9bb2914192ba6943ae1d39f845aedd80c3dbffc2f529a46b9a0dba5d
7
+ data.tar.gz: 728e3b2cfbe503ab9188b789510c112e3f2c569e32a3a541f1f30e454bfdd80381f0a35185fc0b10ad9ff9751141dc168bdb84422b09657b46621f04df982df1
data/lib/abt.rb CHANGED
@@ -23,8 +23,4 @@ module Abt
23
23
  const_name = Helpers.command_to_const(scheme)
24
24
  Providers.const_get(const_name) if Providers.const_defined?(const_name)
25
25
  end
26
-
27
- def self.directory_config
28
- @directory_config ||= Abt::DirectoryConfig.new
29
- end
30
26
  end
data/lib/abt/cli.rb CHANGED
@@ -62,6 +62,10 @@ module Abt
62
62
  @aris ||= ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
63
63
  end
64
64
 
65
+ def directory_config
66
+ @directory_config ||= Abt::DirectoryConfig.new
67
+ end
68
+
65
69
  private
66
70
 
67
71
  def alias?
@@ -69,7 +73,7 @@ module Abt
69
73
  end
70
74
 
71
75
  def process_alias
72
- matching_alias = Abt.directory_config.dig("aliases", command[1..-1])
76
+ matching_alias = directory_config.dig("aliases", command[1..-1])
73
77
 
74
78
  abort("No such alias #{command}") if matching_alias.nil?
75
79
 
@@ -2,24 +2,42 @@
2
2
 
3
3
  module Abt
4
4
  class DirectoryConfig < Hash
5
+ FILE_NAME = ".abt.yml"
6
+
5
7
  def initialize
6
8
  super
7
- merge!(YAML.load_file(config_file_path)) if config_file_path
9
+ load! if config_file_path && File.exist?(config_file_path)
8
10
  end
9
11
 
10
- private
12
+ def available?
13
+ !config_file_path.nil?
14
+ end
11
15
 
12
- def config_file_path
13
- dir = Dir.pwd
16
+ def load!
17
+ merge!(YAML.load_file(config_file_path))
18
+ end
14
19
 
15
- until File.exist?(File.join(dir, ".abt.yml"))
16
- next_dir = File.expand_path("..", dir)
17
- return if next_dir == dir
20
+ def save!
21
+ raise Abt::Cli::Abort("Configuration files are not available outside of git repositories") unless available?
18
22
 
19
- dir = next_dir
20
- end
23
+ config_file = File.open(config_file_path, "w")
24
+ YAML.dump(to_h, config_file)
25
+ config_file.close
26
+ end
27
+
28
+ private
21
29
 
22
- File.join(dir, ".abt.yml")
30
+ def config_file_path
31
+ @config_file_path ||= begin
32
+ path = nil
33
+ Open3.popen3("git rev-parse --show-toplevel") do |_i, output, _e, thread|
34
+ if thread.value.success?
35
+ repo_root = output.read.chomp
36
+ path = File.join(repo_root, FILE_NAME)
37
+ end
38
+ end
39
+ path
40
+ end
23
41
  end
24
42
  end
25
43
  end
data/lib/abt/docs.rb CHANGED
@@ -10,11 +10,10 @@ module Abt
10
10
  def basic_examples
11
11
  {
12
12
  "Getting started:" => {
13
- "abt init asana harvest" => "Setup asana and harvest project for local git repo",
14
13
  "abt pick harvest" => "Pick harvest task. This will likely stay the same throughout the project",
15
14
  "abt pick asana | abt start harvest" => "Pick asana task and start tracking time",
16
15
  "abt stop harvest" => "Stop time tracker",
17
- "abt start asana harvest" => "Continue working, e.g., after a break",
16
+ "abt track asana harvest" => "Continue tracking time, e.g., after a break",
18
17
  "abt finalize asana" => "Finalize the selected asana task"
19
18
  }
20
19
  }
@@ -31,10 +30,15 @@ module Abt
31
30
  "abt tasks asana | grep -i <name of task> | abt start" => nil
32
31
  },
33
32
  "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"
33
+ "abt share" => "Print current asana and harvest ARIs on a single line",
34
+ "abt share | pbcopy" => "Copy ARIs to clipboard (mac only)",
35
+ "abt track <ARIs from coworker>" => "Start tracking on the task your coworker shared with you",
36
+ "abt current <ARIs from coworker> | abt track" => "Set task as current, then start tracking"
37
+ },
38
+ "One-off tracking on any project": {
39
+ "abt pick asana -dc -- harvest -dc | abt track" =>
40
+ "Find a track any task on any project, without reusing/affecting previous settings",
41
+ "abt pick asana harvest | abt track" => "Can be used instead of the above when outside a git repo"
38
42
  },
39
43
  "Flags:" => {
40
44
  'abt start harvest -c "comment"' => "Add command flags after ARIs",
@@ -2,6 +2,7 @@
2
2
 
3
3
  Dir.glob("#{File.expand_path(__dir__)}/asana/*.rb").sort.each { |file| require file }
4
4
  Dir.glob("#{File.expand_path(__dir__)}/asana/commands/*.rb").sort.each { |file| require file }
5
+ Dir.glob("#{File.expand_path(__dir__)}/asana/services/*.rb").sort.each { |file| require file }
5
6
 
6
7
  module Abt
7
8
  module Providers
@@ -25,12 +25,42 @@ module Abt
25
25
  end
26
26
 
27
27
  def require_project!
28
- abort("No current/specified project. Did you initialize Asana?") if project_gid.nil?
28
+ abort("No current/specified project. Did you forget to run `pick`?") if project_gid.nil?
29
29
  end
30
30
 
31
31
  def require_task!
32
- abort("No current/specified project. Did you initialize Asana and pick a task?") if project_gid.nil?
33
- abort("No current/specified task. Did you pick an Asana task?") if task_gid.nil?
32
+ require_project!
33
+ abort("No current/specified task. Did you forget to run `pick`?") if task_gid.nil?
34
+ end
35
+
36
+ def prompt_project!
37
+ result = Services::ProjectPicker.call(cli: cli, config: config)
38
+ @path = result.path
39
+ @project = result.project
40
+ end
41
+
42
+ def prompt_task!
43
+ result = Services::TaskPicker.call(cli: cli, path: path, config: config, project: project)
44
+ @path = result.path
45
+ @task = result.task
46
+ end
47
+
48
+ def task
49
+ @task ||= begin
50
+ warn("Fetching task...")
51
+ api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url")
52
+ rescue Abt::HttpError::NotFoundError
53
+ nil
54
+ end
55
+ end
56
+
57
+ def project
58
+ @project ||= begin
59
+ warn("Fetching project...")
60
+ api.get("projects/#{project_gid}", opt_fields: "name,permalink_url")
61
+ rescue Abt::HttpError::NotFoundError
62
+ nil
63
+ end
34
64
  end
35
65
 
36
66
  def print_project(project)
@@ -56,10 +56,6 @@ module Abt
56
56
  @notes ||= cli.prompt.text("Enter task notes")
57
57
  end
58
58
 
59
- def project
60
- @project ||= api.get("projects/#{project_gid}", opt_fields: "name")
61
- end
62
-
63
59
  def section
64
60
  @section ||= cli.prompt.choice("Add to section?", sections,
65
61
  nil_option: ["q", "Don't add to section"])
@@ -28,19 +28,6 @@ module Abt
28
28
 
29
29
  def ensure_current_is_valid!
30
30
  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
- abort("Invalid or unmatching project gid: #{project_gid}")
35
- end
36
-
37
- def task
38
- @task ||= begin
39
- warn("Fetching task...")
40
- api.get("tasks/#{task_gid}", opt_fields: "name,memberships.project")
41
- rescue Abt::HttpError::NotFoundError
42
- nil
43
- end
44
31
  end
45
32
  end
46
33
  end
@@ -22,7 +22,7 @@ module Abt
22
22
  end
23
23
 
24
24
  def perform
25
- abort("Flags --global and --all cannot be used at the same time") if flags[:global] && flags[:all]
25
+ abort("Flags --global and --all cannot be used together") if flags[:global] && flags[:all]
26
26
 
27
27
  config.clear_local unless flags[:global]
28
28
  config.clear_global if flags[:global] || flags[:all]
@@ -36,24 +36,6 @@ module Abt
36
36
  abort("Invalid project: #{project_gid}") if project.nil?
37
37
  abort("Invalid task: #{task_gid}") if task_gid && task.nil?
38
38
  end
39
-
40
- def project
41
- @project ||= begin
42
- warn("Fetching project...")
43
- api.get("projects/#{project_gid}", opt_fields: "name,permalink_url")
44
- rescue Abt::HttpError::NotFoundError
45
- nil
46
- end
47
- end
48
-
49
- def task
50
- @task ||= begin
51
- warn("Fetching task...")
52
- api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url")
53
- rescue Abt::HttpError::NotFoundError
54
- nil
55
- end
56
- end
57
39
  end
58
40
  end
59
41
  end
@@ -58,10 +58,8 @@ module Abt
58
58
  end
59
59
 
60
60
  def task
61
- @task ||= begin
62
- api.get("tasks/#{task_gid}",
63
- opt_fields: "name,memberships.section.name,permalink_url")
64
- end
61
+ @task ||= api.get("tasks/#{task_gid}",
62
+ opt_fields: "name,memberships.section.name,permalink_url")
65
63
  end
66
64
  end
67
65
  end
@@ -10,65 +10,35 @@ module Abt
10
10
  end
11
11
 
12
12
  def self.description
13
- "Pick task for current git repository"
13
+ "Pick a task and - unless told not to - make it current"
14
14
  end
15
15
 
16
16
  def self.flags
17
17
  [
18
- ["-d", "--dry-run", "Keep existing configuration"]
18
+ ["-d", "--dry-run", "Keep existing configuration"],
19
+ ["-c", "--clean", "Don't reuse project configuration"]
19
20
  ]
20
21
  end
21
22
 
22
23
  def perform
23
- require_local_config!
24
- require_project!
25
-
26
- warn(project["name"])
27
- task = select_task
24
+ pick!
28
25
 
29
26
  print_task(project, task)
30
27
 
31
28
  return if flags[:"dry-run"]
32
29
 
33
- config.path = Path.from_ids(project_gid: project_gid, task_gid: task["gid"])
34
- end
35
-
36
- private
37
-
38
- def project
39
- @project ||= api.get("projects/#{project_gid}", opt_fields: "name")
40
- end
41
-
42
- def select_task
43
- section = cli.prompt.choice("Which section?", sections)
44
- warn("Fetching tasks...")
45
- tasks = tasks_in_section(section)
46
-
47
- if tasks.length.zero?
48
- warn("Section is empty")
49
- select_task
30
+ if config.local_available?
31
+ config.path = path
50
32
  else
51
- cli.prompt.choice("Select a task", tasks, nil_option: true) || select_task
33
+ warn("No local configuration to update - will function as dry run")
52
34
  end
53
35
  end
54
36
 
55
- def tasks_in_section(section)
56
- tasks = api.get_paged(
57
- "tasks",
58
- section: section["gid"],
59
- opt_fields: "name,completed,permalink_url"
60
- )
61
-
62
- # The below filtering is the best we can do with Asanas api, see this:
63
- # https://forum.asana.com/t/tasks-query-completed-since-is-broken-for-sections/21461
64
- tasks.reject { |task| task["completed"] }
65
- end
37
+ private
66
38
 
67
- def sections
68
- @sections ||= begin
69
- warn("Fetching sections...")
70
- api.get_paged("projects/#{project_gid}/sections", opt_fields: "name")
71
- end
39
+ def pick!
40
+ prompt_project! if project_gid.nil? || flags[:clean]
41
+ prompt_task!
72
42
  end
73
43
  end
74
44
  end
@@ -14,7 +14,7 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- require_project!
17
+ prompt_project! unless project_gid
18
18
 
19
19
  tasks.each do |task|
20
20
  print_task(project, task)
@@ -23,14 +23,9 @@ module Abt
23
23
 
24
24
  private
25
25
 
26
- def project
27
- @project ||= begin
28
- api.get("projects/#{project_gid}", opt_fields: "name")
29
- end
30
- end
31
-
32
26
  def tasks
33
27
  @tasks ||= begin
28
+ project
34
29
  warn("Fetching tasks...")
35
30
  tasks = api.get_paged("tasks", project: project["gid"], opt_fields: "name,completed")
36
31
  tasks.reject { |task| task["completed"] }
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Asana
6
+ module Commands
7
+ class WriteConfig < BaseCommand
8
+ def self.usage
9
+ "abt write-config asana[:<project-gid>]"
10
+ end
11
+
12
+ def self.description
13
+ "Write Asana settings to .abt.yml"
14
+ end
15
+
16
+ def self.flags
17
+ [
18
+ ["-c", "--clean", "Don't reuse project configuration"]
19
+ ]
20
+ end
21
+
22
+ def perform
23
+ cli.directory_config["asana"] = config_data
24
+ cli.directory_config.save!
25
+
26
+ warn("Asana configuration written to #{Abt::DirectoryConfig::FILE_NAME}")
27
+ end
28
+
29
+ private
30
+
31
+ def config_data
32
+ {
33
+ "path" => project_gid,
34
+ "wip_section_gid" => wip_section_gid,
35
+ "finalized_section_gid" => finalized_section_gid
36
+ }
37
+ end
38
+
39
+ def project_gid
40
+ @project_gid ||= begin
41
+ prompt_project! if super.nil? || flags[:clean]
42
+
43
+ super
44
+ end
45
+ end
46
+
47
+ def wip_section_gid
48
+ return config.wip_section_gid if use_previous_config?
49
+
50
+ cli.prompt.choice("Select WIP (Work In Progress) section", sections)["gid"]
51
+ end
52
+
53
+ def finalized_section_gid
54
+ return config.finalized_section_gid if use_previous_config?
55
+
56
+ cli.prompt.choice('Select section for finalized tasks (E.g. "Merged")', sections)["gid"]
57
+ end
58
+
59
+ def use_previous_config?
60
+ project_gid == config.path.project_gid
61
+ end
62
+
63
+ def sections
64
+ @sections ||= begin
65
+ warn("Fetching sections...")
66
+ api.get_paged("projects/#{project_gid}/sections", opt_fields: "name")
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end