geet 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -3
  3. data/README.md +25 -24
  4. data/bin/geet +21 -3
  5. data/geet.gemspec +2 -2
  6. data/lib/geet/commandline/configuration.rb +5 -5
  7. data/lib/geet/git/repository.rb +34 -15
  8. data/lib/geet/services/abstract_create_issue.rb +33 -0
  9. data/lib/geet/services/create_gist.rb +6 -4
  10. data/lib/geet/services/create_issue.rb +33 -87
  11. data/lib/geet/services/create_label.rb +7 -6
  12. data/lib/geet/services/create_pr.rb +42 -86
  13. data/lib/geet/services/list_issues.rb +13 -14
  14. data/lib/geet/services/list_labels.rb +4 -3
  15. data/lib/geet/services/list_milestones.rb +12 -11
  16. data/lib/geet/services/list_prs.rb +4 -3
  17. data/lib/geet/services/merge_pr.rb +12 -11
  18. data/lib/geet/shared/constants.rb +9 -0
  19. data/lib/geet/utils/attributes_selection_manager.rb +87 -0
  20. data/lib/geet/utils/manual_list_selection.rb +41 -27
  21. data/lib/geet/utils/string_matching_selection.rb +32 -0
  22. data/lib/geet/version.rb +1 -1
  23. data/spec/integration/create_gist_spec.rb +4 -4
  24. data/spec/integration/create_issue_spec.rb +6 -6
  25. data/spec/integration/create_label_spec.rb +3 -3
  26. data/spec/integration/create_pr_spec.rb +13 -13
  27. data/spec/integration/list_issues_spec.rb +5 -5
  28. data/spec/integration/list_labels_spec.rb +3 -3
  29. data/spec/integration/list_milestones_spec.rb +2 -2
  30. data/spec/integration/list_prs_spec.rb +2 -2
  31. data/spec/integration/merge_pr_spec.rb +2 -2
  32. data/spec/vcr_cassettes/create_issue_upstream.yml +1 -1
  33. data/spec/vcr_cassettes/create_pr.yml +181 -102
  34. data/spec/vcr_cassettes/create_pr_in_auto_mode_create_upstream.yml +1 -1
  35. data/spec/vcr_cassettes/create_pr_in_auto_mode_with_push.yml +1 -1
  36. data/spec/vcr_cassettes/create_pr_upstream.yml +2 -2
  37. metadata +8 -5
  38. data/lib/geet/utils/pattern_matching_selection.rb +0 -27
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'manual_list_selection'
4
+ require_relative 'string_matching_selection'
5
+ require_relative '../shared/constants'
6
+
7
+ module Geet
8
+ module Utils
9
+ # Manages the retrieval and selection of attributes.
10
+ #
11
+ # Selecting an attribute happens in two steps: retrieval and selection.
12
+ #
13
+ # With this structure, the retrieval happens in parallel, cutting the time considerably when
14
+ # multiple attributes are required (typically, three).
15
+ #
16
+ class AttributesSelectionManager
17
+ include Geet::Shared::Constants
18
+
19
+ # Initialize the instance, and starts the background threads.
20
+ #
21
+ def initialize(repository, out: output)
22
+ @repository = repository
23
+ @out = out
24
+ @selections_data = []
25
+ end
26
+
27
+ def add_attribute(repository_call, description, pattern, selection_type, name_method: nil, &pre_selection_hook)
28
+ raise "Unrecognized selection type #{selection_type.inspect}" if ![:single, :multiple].include?(selection_type)
29
+
30
+ finder_thread = find_attribute_entries(repository_call)
31
+
32
+ @selections_data << [finder_thread, description, pattern, selection_type, name_method, pre_selection_hook]
33
+ end
34
+
35
+ # Select and return the attributes, in the same order they've been added.
36
+ #
37
+ def select_attributes
38
+ @selections_data.map do |finder_thread, description, pattern, selection_type, name_method, pre_selection_hook|
39
+ entries = finder_thread.value
40
+
41
+ entries = pre_selection_hook.(entries) if pre_selection_hook
42
+
43
+ case selection_type
44
+ when :single
45
+ select_entry(description, entries, pattern, name_method)
46
+ when :multiple
47
+ select_entries(description, entries, pattern, name_method)
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def find_attribute_entries(repository_call)
55
+ @out.puts "Finding #{repository_call}..."
56
+
57
+ Thread.new do
58
+ @repository.send(repository_call)
59
+ end
60
+ end
61
+
62
+ # Sample call:
63
+ #
64
+ # select_entry('milestone', all_milestones, '0.1.0', :title)
65
+ #
66
+ def select_entry(entry_type, entries, pattern, name_method)
67
+ if pattern == MANUAL_LIST_SELECTION_FLAG
68
+ Geet::Utils::ManualListSelection.new.select_entry(entry_type, entries, name_method: name_method)
69
+ else
70
+ Geet::Utils::StringMatchingSelection.new.select_entry(entry_type, entries, pattern, name_method: name_method)
71
+ end
72
+ end
73
+
74
+ # Sample call:
75
+ #
76
+ # select_entries('reviewer', all_collaborators, 'donaldduck', nil)
77
+ #
78
+ def select_entries(entry_type, entries, pattern, name_method)
79
+ if pattern == MANUAL_LIST_SELECTION_FLAG
80
+ Geet::Utils::ManualListSelection.new.select_entries(entry_type, entries, name_method: name_method)
81
+ else
82
+ Geet::Utils::StringMatchingSelection.new.select_entries(entry_type, entries, pattern, name_method: name_method)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -9,56 +9,70 @@ module Geet
9
9
 
10
10
  PAGER_SIZE = 16
11
11
 
12
+ # Shows a prompt for selecting an entry from a list.
13
+ #
14
+ # Returns nil, without showing the prompt, if there are no entries.
15
+ #
12
16
  # entry_type: description of the entries type.
13
- # entries: array of objects; if they're not strings, must also pass :instance_method.
17
+ # entries: array of objects; if they're not strings, must also pass :name_method.
14
18
  # this value must not be empty.
15
- # selection_type: :single or :multiple
16
- # instance_method: required when non-string objects are passed as entries; its invocation on
19
+ # name_method: required when non-string objects are passed as entries; its invocation on
17
20
  # each object must return a string, which is used as key.
18
21
  #
19
- # returns: the selected entry or array of entries. for single selection, if no entries are
20
- # chosen, nil is returned.
22
+ # returns: the selected entry. if no entries are nil is returned.
23
+ #
24
+ def select_entry(entry_type, entries, name_method: nil)
25
+ return nil if entries.empty?
26
+
27
+ check_entries(entries, entry_type)
28
+
29
+ entries = create_entries_map(entries, name_method)
30
+ entries = add_no_selection_entry(entries)
31
+
32
+ show_prompt(:select, entry_type, entries)
33
+ end
34
+
35
+ # Shows a prompt for selecting an entry from a list.
36
+ #
37
+ # Returns an empty array, without showing the prompt, if there are no entries.
38
+ #
39
+ # See #select_entry for the parameters.
21
40
  #
22
- def select(entry_type, entries, selection_type, instance_method: nil)
23
- check_entries(entries)
41
+ # returns: array of entries.
42
+ #
43
+ def select_entries(entry_type, entries, name_method: nil)
44
+ return [] if entries.empty?
24
45
 
25
- entries = create_entries_map(entries, instance_method)
46
+ check_entries(entries, entry_type)
26
47
 
27
- result = show_prompt(entry_type, selection_type, entries)
48
+ entries = create_entries_map(entries, name_method)
28
49
 
29
- result
50
+ show_prompt(:multi_select, entry_type, entries)
30
51
  end
31
52
 
32
53
  private
33
54
 
34
- def check_entries(entries)
55
+ def check_entries(entries, entry_type)
35
56
  raise "No #{entry_type} provided!" if entries.empty?
36
57
  end
37
58
 
38
- def create_entries_map(entries, instance_method)
59
+ def create_entries_map(entries, name_method)
39
60
  entries.each_with_object({}) do |entry, current_map|
40
- key = instance_method ? entry.send(instance_method) : entry
61
+ key = name_method ? entry.send(name_method) : entry
41
62
  current_map[key] = entry
42
63
  end
43
64
  end
44
65
 
45
- def show_prompt(entry_type, selection_type, entries)
46
- prompt_title = "Please select the #{entry_type}(s):"
47
-
48
- case selection_type
49
- when :single
50
- entries = add_no_selection_entry(entries)
51
- TTY::Prompt.new.select(prompt_title, entries, filter: true, per_page: PAGER_SIZE)
52
- when :multiple
53
- TTY::Prompt.new.multi_select(prompt_title, entries, filter: true, per_page: PAGER_SIZE)
54
- else
55
- raise "Unexpected selection type: #{selection_type.inspect}"
56
- end
57
- end
58
-
59
66
  def add_no_selection_entry(entries)
60
67
  {NO_SELECTION_KEY => nil}.merge(entries)
61
68
  end
69
+
70
+ def show_prompt(invocation_method, entry_type, entries)
71
+ # Arguably inexact phrasing for avoiding language complexities.
72
+ prompt_title = "Please select the #{entry_type}(s):"
73
+
74
+ TTY::Prompt.new.send(invocation_method, prompt_title, entries, filter: true, per_page: PAGER_SIZE)
75
+ end
62
76
  end
63
77
  end
64
78
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geet
4
+ module Utils
5
+ class StringMatchingSelection
6
+ def select_entry(entry_type, entries, pattern, name_method: nil)
7
+ entries_found = entries.select do |entry|
8
+ entry = entry.send(name_method) if name_method
9
+ entry.downcase == pattern.downcase
10
+ end
11
+
12
+ case entries_found.size
13
+ when 1
14
+ entries_found.first
15
+ when 0
16
+ raise "No entry found for #{entry_type} pattern: #{pattern.inspect}"
17
+ else
18
+ raise "Multiple entries found for #{entry_type} pattern #{pattern.inspect}: #{entries_found}"
19
+ end
20
+ end
21
+
22
+ def select_entries(entry_type, entries, raw_patterns, name_method: nil)
23
+ patterns = raw_patterns.split(',')
24
+
25
+ patterns.map do |pattern|
26
+ # Haha.
27
+ select_entry(entry_type, entries, pattern, name_method: name_method)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/geet/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Geet
4
- VERSION = '0.3.1'
4
+ VERSION = '0.3.2'
5
5
  end
@@ -18,8 +18,8 @@ describe Geet::Services::CreateGist do
18
18
  actual_output = StringIO.new
19
19
 
20
20
  VCR.use_cassette('create_gist_public') do
21
- described_class.new.execute(
22
- tempfile.path, description: 'testdescription', publik: true, no_browse: true, output: actual_output
21
+ described_class.new(out: actual_output).execute(
22
+ tempfile.path, description: 'testdescription', publik: true, no_browse: true
23
23
  )
24
24
  end
25
25
 
@@ -35,8 +35,8 @@ describe Geet::Services::CreateGist do
35
35
  actual_output = StringIO.new
36
36
 
37
37
  VCR.use_cassette('create_gist_private') do
38
- described_class.new.execute(
39
- tempfile.path, description: 'testdescription', no_browse: true, output: actual_output
38
+ described_class.new(out: actual_output).execute(
39
+ tempfile.path, description: 'testdescription', no_browse: true
40
40
  )
41
41
  end
42
42
 
@@ -16,7 +16,7 @@ describe Geet::Services::CreateIssue do
16
16
 
17
17
  expected_output = <<~STR
18
18
  Finding labels...
19
- Finding milestone...
19
+ Finding milestones...
20
20
  Finding collaborators...
21
21
  Creating the issue...
22
22
  Adding labels bug, invalid...
@@ -28,10 +28,10 @@ describe Geet::Services::CreateIssue do
28
28
  actual_output = StringIO.new
29
29
 
30
30
  actual_created_issue = VCR.use_cassette('create_issue') do
31
- described_class.new(repository).execute(
31
+ described_class.new(repository, out: actual_output).execute(
32
32
  'Title', 'Description',
33
- label_patterns: 'bug,invalid', milestone_pattern: '0.0.1', assignee_patterns: 'nald-ts,nald-fr',
34
- no_open_issue: true, output: actual_output
33
+ labels: 'bug,invalid', milestone: '0.0.1', assignees: 'donald-ts,donald-fr',
34
+ no_open_issue: true
35
35
  )
36
36
  end
37
37
 
@@ -56,8 +56,8 @@ describe Geet::Services::CreateIssue do
56
56
  actual_output = StringIO.new
57
57
 
58
58
  actual_created_issue = VCR.use_cassette('create_issue_upstream') do
59
- create_options = { no_open_issue: true, output: actual_output }
60
- described_class.new(upstream_repository).execute('Title', 'Description', create_options)
59
+ create_options = { no_open_issue: true, out: actual_output }
60
+ described_class.new(upstream_repository, out: actual_output).execute('Title', 'Description', create_options)
61
61
  end
62
62
 
63
63
  expect(actual_output.string).to eql(expected_output)
@@ -22,7 +22,7 @@ describe Geet::Services::CreateLabel do
22
22
  actual_output = StringIO.new
23
23
 
24
24
  actual_created_label = VCR.use_cassette('create_label') do
25
- described_class.new(repository).execute('my_label', color: 'c64c64', output: actual_output)
25
+ described_class.new(repository, out: actual_output).execute('my_label', color: 'c64c64')
26
26
  end
27
27
 
28
28
  expect(actual_output.string).to eql(expected_output)
@@ -44,7 +44,7 @@ describe Geet::Services::CreateLabel do
44
44
  actual_output = StringIO.new
45
45
 
46
46
  actual_created_label = VCR.use_cassette('create_label_upstream') do
47
- described_class.new(upstream_repository).execute('my_label', color: 'c64c64', output: actual_output)
47
+ described_class.new(upstream_repository, out: actual_output).execute('my_label', color: 'c64c64')
48
48
  end
49
49
 
50
50
  expect(actual_output.string).to eql(expected_output)
@@ -67,7 +67,7 @@ describe Geet::Services::CreateLabel do
67
67
  actual_output = StringIO.new
68
68
 
69
69
  actual_created_label = VCR.use_cassette('create_label_with_random_color') do
70
- described_class.new(repository).execute('my_label', output: actual_output)
70
+ described_class.new(repository, out: actual_output).execute('my_label')
71
71
  end
72
72
 
73
73
  expected_output = format(expected_output_template, color: actual_created_label.color)
@@ -17,32 +17,32 @@ describe Geet::Services::CreatePr do
17
17
 
18
18
  expected_output = <<~STR
19
19
  Finding labels...
20
- Finding milestone...
20
+ Finding milestones...
21
21
  Finding collaborators...
22
22
  Creating PR...
23
23
  Assigning authenticated user...
24
- Adding labels other_bug, invalid...
25
- Setting milestone 0.0.1...
26
- Requesting review from donald-ts, donald-fr...
27
- PR address: https://github.com/donaldduck/testrepo/pull/3
24
+ Adding labels bug, invalid...
25
+ Setting milestone milestone 1...
26
+ Requesting review from donald-fr...
27
+ PR address: https://github.com/donaldduck/testrepo/pull/39
28
28
  STR
29
29
 
30
30
  actual_output = StringIO.new
31
31
 
32
32
  actual_created_pr = VCR.use_cassette('create_pr') do
33
- service_instance = described_class.new(repository, git_client: git_client)
33
+ service_instance = described_class.new(repository, out: actual_output, git_client: git_client)
34
34
  service_instance.execute(
35
35
  'Title', 'Description',
36
- label_patterns: '_bug,invalid', milestone_pattern: '0.0.1', reviewer_patterns: 'nald-ts,nald-fr',
36
+ labels: 'bug,invalid', milestone: 'milestone 1', reviewers: 'donald-fr',
37
37
  no_open_pr: true, output: actual_output
38
38
  )
39
39
  end
40
40
 
41
41
  expect(actual_output.string).to eql(expected_output)
42
42
 
43
- expect(actual_created_pr.number).to eql(3)
43
+ expect(actual_created_pr.number).to eql(39)
44
44
  expect(actual_created_pr.title).to eql('Title')
45
- expect(actual_created_pr.link).to eql('https://github.com/donaldduck/testrepo/pull/3')
45
+ expect(actual_created_pr.link).to eql('https://github.com/donaldduck/testrepo/pull/39')
46
46
  end
47
47
  end
48
48
 
@@ -60,7 +60,7 @@ describe Geet::Services::CreatePr do
60
60
  actual_output = StringIO.new
61
61
 
62
62
  actual_created_pr = VCR.use_cassette('create_pr_upstream') do
63
- service_instance = described_class.new(upstream_repository, git_client: git_client)
63
+ service_instance = described_class.new(upstream_repository, out: actual_output, git_client: git_client)
64
64
  service_instance.execute('Title', 'Description', no_open_pr: true, output: actual_output)
65
65
  end
66
66
 
@@ -83,7 +83,7 @@ describe Geet::Services::CreatePr do
83
83
  actual_output = StringIO.new
84
84
 
85
85
  operation = -> do
86
- service_instance = described_class.new(repository, git_client: git_client)
86
+ service_instance = described_class.new(repository, out: actual_output, git_client: git_client)
87
87
  service_instance.execute('Title', 'Description', output: actual_output, automated_mode: true, no_open_pr: true)
88
88
  end
89
89
 
@@ -110,7 +110,7 @@ describe Geet::Services::CreatePr do
110
110
  actual_output = StringIO.new
111
111
 
112
112
  actual_created_pr = VCR.use_cassette('create_pr_in_auto_mode_with_push') do
113
- service_instance = described_class.new(repository, git_client: git_client)
113
+ service_instance = described_class.new(repository, out: actual_output, git_client: git_client)
114
114
  service_instance.execute('Title', 'Description', output: actual_output, automated_mode: true, no_open_pr: true)
115
115
  end
116
116
 
@@ -135,7 +135,7 @@ describe Geet::Services::CreatePr do
135
135
  actual_output = StringIO.new
136
136
 
137
137
  actual_created_pr = VCR.use_cassette('create_pr_in_auto_mode_create_upstream') do
138
- service_instance = described_class.new(repository, git_client: git_client)
138
+ service_instance = described_class.new(repository, out: actual_output, git_client: git_client)
139
139
  service_instance.execute('Title', 'Description', output: actual_output, automated_mode: true, no_open_pr: true)
140
140
  end
141
141
 
@@ -23,7 +23,7 @@ describe Geet::Services::ListIssues do
23
23
  actual_output = StringIO.new
24
24
 
25
25
  service_result = VCR.use_cassette('github_com/list_issues') do
26
- described_class.new(repository).execute(output: actual_output)
26
+ described_class.new(repository, out: actual_output).execute
27
27
  end
28
28
 
29
29
  actual_issue_numbers = service_result.map(&:number)
@@ -37,7 +37,7 @@ describe Geet::Services::ListIssues do
37
37
  allow(git_client).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo_gh')
38
38
 
39
39
  expected_output = <<~STR
40
- Finding assignee...
40
+ Finding collaborators...
41
41
  12. test issue 3 (https://github.com/donaldduck/testrepo_gh/issues/12)
42
42
  10. test issue 1 (https://github.com/donaldduck/testrepo_gh/issues/10)
43
43
  STR
@@ -46,7 +46,7 @@ describe Geet::Services::ListIssues do
46
46
  actual_output = StringIO.new
47
47
 
48
48
  service_result = VCR.use_cassette('github_com/list_issues_with_assignee') do
49
- described_class.new(repository).execute(assignee_pattern: 'donald-fr', output: actual_output)
49
+ described_class.new(repository, out: actual_output).execute(assignee: 'donald-fr', )
50
50
  end
51
51
 
52
52
  actual_issue_numbers = service_result.map(&:number)
@@ -69,7 +69,7 @@ describe Geet::Services::ListIssues do
69
69
  actual_output = StringIO.new
70
70
 
71
71
  service_result = VCR.use_cassette('github_com/list_issues_upstream') do
72
- described_class.new(upstream_repository).execute(output: actual_output)
72
+ described_class.new(upstream_repository, out: actual_output).execute
73
73
  end
74
74
 
75
75
  actual_issue_numbers = service_result.map(&:number)
@@ -92,7 +92,7 @@ describe Geet::Services::ListIssues do
92
92
  actual_output = StringIO.new
93
93
 
94
94
  service_result = VCR.use_cassette('gitlab_com/list_issues') do
95
- described_class.new(repository).execute(output: actual_output)
95
+ described_class.new(repository, out: actual_output).execute
96
96
  end
97
97
 
98
98
  actual_issue_numbers = service_result.map(&:number)
@@ -24,7 +24,7 @@ describe Geet::Services::ListLabels do
24
24
 
25
25
  actual_output = StringIO.new
26
26
  actual_labels = VCR.use_cassette('github.com/list_labels') do
27
- described_class.new(repository).execute(output: actual_output)
27
+ described_class.new(repository, out: actual_output).execute
28
28
  end
29
29
 
30
30
  actual_label_names = actual_labels.map(&:name)
@@ -45,7 +45,7 @@ describe Geet::Services::ListLabels do
45
45
 
46
46
  actual_output = StringIO.new
47
47
  actual_labels = VCR.use_cassette('github.com/list_labels_upstream') do
48
- described_class.new(upstream_repository).execute(output: actual_output)
48
+ described_class.new(upstream_repository, out: actual_output).execute
49
49
  end
50
50
 
51
51
  actual_label_names = actual_labels.map(&:name)
@@ -73,7 +73,7 @@ describe Geet::Services::ListLabels do
73
73
 
74
74
  actual_output = StringIO.new
75
75
  actual_labels = VCR.use_cassette('gitlab.com/list_labels') do
76
- described_class.new(repository).execute(output: actual_output)
76
+ described_class.new(repository, out: actual_output).execute
77
77
  end
78
78
 
79
79
  actual_label_names = actual_labels.map(&:name)