geet 0.1.9 → 0.1.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b893cf44b85d8bfeedc77128b96c5bc921abb701
4
- data.tar.gz: e50482a1691bedde73ef4c1c195693a57c60d9ce
3
+ metadata.gz: c59d2f426846e6efe8e86311fdaa40c2d492c551
4
+ data.tar.gz: ad031d1e06cb881481c6cd8e72581e3069b365e2
5
5
  SHA512:
6
- metadata.gz: 2733412c618b5d39cdeeb3b900364c69fcc551fa9238c30e743d6deb1b6a9083fc3cbf279df09027692a685f9f47fddb2d8eecb6beda7f79f03617f3dce31814
7
- data.tar.gz: 4cb03d50785c4645e0d206337340d84bd53f8569c667c20787a2507a4579264fc4d13be4c2bffa5109c1a46ac2c79d8011917c50d7e580f0de72e7d207a36611
6
+ metadata.gz: 44015551198b6a04525a17079b065b85a81f5086ae2a22febcc3466596700035201da645ed2b7cb5093ddc6e4585d4fff105cf7fd806227eeb64f089f3e4e853
7
+ data.tar.gz: 64e7d77b762b34d4444e443ac447b9c0a9ad201724daee2eb4f5745f83598853e8dd4d5674e48bc68815756d7132530103b5e7ee1ec3756a0837d38398f056da
data/README.md CHANGED
@@ -8,13 +8,18 @@ This tool is very similar to [Hub](https://github.com/github/hub), but it suppor
8
8
 
9
9
  Please see the [development status](#development-status) section for informations about the current development.
10
10
 
11
+ ## Providers support
12
+
13
+ Currently, there are many functionalities implemented for GitHub (see help), and only issues listing for GitLab.
14
+
11
15
  ## Samples
12
16
 
13
17
  ### Prerequisite(s)
14
18
 
15
- Geet requires the `GITHUB_API_TOKEN` environment variable to be set, eg:
19
+ Geet requires the API token environment variable to be set, eg:
16
20
 
17
- export GITHUB_API_TOKEN=0123456789abcdef0123456789abcdef
21
+ export GITHUB_API_TOKEN=0123456789abcdef0123456789abcdef # for GitHub
22
+ export GITLAB_API_TOKEN=0123456789abcd-ef0-1 # for GitLab
18
23
 
19
24
  All the commands need to be run from the git repository.
20
25
 
data/bin/geet CHANGED
@@ -14,10 +14,9 @@ class GeetLauncher
14
14
  commandline_configuration = Commandline::Configuration.new
15
15
 
16
16
  command, options = commandline_configuration.decode_argv || exit
17
- api_token = commandline_configuration.api_token
18
17
 
19
18
  # `:upstream` is always false in the gist command case.
20
- repository = Git::Repository.new(api_token, upstream: !!options[:upstream])
19
+ repository = Git::Repository.new(upstream: !!options[:upstream])
21
20
 
22
21
  case command
23
22
  when GIST_CREATE_COMMAND
data/geet.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.required_ruby_version = '>= 2.2.0'
12
12
  s.authors = ['Saverio Miroddi']
13
- s.date = '2017-11-21'
13
+ s.date = '2017-11-26'
14
14
  s.email = ['saverio.pub2@gmail.com']
15
15
  s.homepage = 'https://github.com/saveriomiroddi/geet'
16
16
  s.summary = 'Commandline interface for performing SCM (eg. GitHub) operations (eg. PR creation).'
@@ -80,10 +80,6 @@ module Geet
80
80
  },
81
81
  )
82
82
  end
83
-
84
- def api_token
85
- ENV['GITHUB_API_TOKEN'] || raise('Missing $GITHUB_API_TOKEN')
86
- end
87
83
  end
88
84
  end
89
85
  end
@@ -19,58 +19,58 @@ module Geet
19
19
  ORIGIN_NAME = 'origin'
20
20
  UPSTREAM_NAME = 'upstream'
21
21
 
22
- def initialize(api_token, upstream: false, location: nil)
23
- @api_token = api_token
22
+ def initialize(upstream: false, location: nil)
24
23
  @upstream = upstream
25
24
  @location = location
25
+ @api_token = extract_env_api_token
26
26
  end
27
27
 
28
28
  # REMOTE FUNCTIONALITIES (REPOSITORY)
29
29
 
30
30
  def collaborators
31
- provider_module::Collaborator.list(api_interface)
31
+ attempt_provider_call(:Collaborator, :list, api_interface)
32
32
  end
33
33
 
34
34
  def labels
35
- provider_module::Label.list(api_interface)
35
+ attempt_provider_call(:Label, :list, api_interface)
36
36
  end
37
37
 
38
38
  def create_gist(filename, content, description: nil, publik: false)
39
- provider_module::Gist.create(filename, content, api_interface, description: description, publik: publik)
39
+ attempt_provider_call(:Gist, :create, filename, content, api_interface, description: description, publik: publik)
40
40
  end
41
41
 
42
42
  def create_issue(title, description)
43
- provider_module::Issue.create(title, description, api_interface)
43
+ attempt_provider_call(:Issue, :create, title, description, api_interface)
44
44
  end
45
45
 
46
46
  def abstract_issues(milestone: nil)
47
- provider_module::AbstractIssue.list(api_interface, milestone: milestone)
47
+ attempt_provider_call(:AbstractIssue, :list, api_interface, milestone: milestone)
48
48
  end
49
49
 
50
50
  def issues
51
- provider_module::Issue.list(api_interface)
51
+ attempt_provider_call(:Issue, :list, api_interface)
52
52
  end
53
53
 
54
54
  def milestone(number)
55
- provider_module::Milestone.find(number, api_interface)
55
+ attempt_provider_call(:Milestone, :find, number, api_interface)
56
56
  end
57
57
 
58
58
  def milestones
59
- provider_module::Milestone.list(api_interface)
59
+ attempt_provider_call(:Milestone, :list, api_interface)
60
60
  end
61
61
 
62
62
  def create_pr(title, description, head)
63
- provider_module::PR.create(title, description, head, api_interface)
63
+ attempt_provider_call(:PR, :create, title, description, head, api_interface)
64
64
  end
65
65
 
66
66
  def prs(head: nil)
67
- provider_module::PR.list(api_interface, head: head)
67
+ attempt_provider_call(:PR, :list, api_interface, head: head)
68
68
  end
69
69
 
70
70
  # REMOTE FUNCTIONALITIES (ACCOUNT)
71
71
 
72
72
  def authenticated_user
73
- provider_module::Account.new(api_interface).authenticated_user
73
+ attempt_provider_call(:Account, :new, api_interface).authenticated_user
74
74
  end
75
75
 
76
76
  # OTHER/CONVENIENCE FUNCTIONALITIES
@@ -105,6 +105,12 @@ module Geet
105
105
 
106
106
  # PROVIDER
107
107
 
108
+ def extract_env_api_token
109
+ env_variable_name = "#{provider_domain[/(.*)\.\w+/, 1].upcase}_API_TOKEN"
110
+
111
+ ENV[env_variable_name] || raise("#{env_variable_name} not set!")
112
+ end
113
+
108
114
  def provider_domain
109
115
  # We assume that it's not possible to have origin and upstream on different providers.
110
116
  #
@@ -117,12 +123,26 @@ module Geet
117
123
  domain
118
124
  end
119
125
 
120
- def provider_module
126
+ # Attempt to find the provider class and send the specified method, returning a friendly
127
+ # error (functionality X [Y] is missing) when a class/method is missing.
128
+ def attempt_provider_call(class_name, meth, *args)
121
129
  module_name = provider_domain[/(.*)\.\w+/, 1].capitalize
122
130
 
123
131
  require_provider_modules
124
132
 
125
- Kernel.const_get("Geet::#{module_name}")
133
+ full_class_name = "Geet::#{module_name}::#{class_name}"
134
+
135
+ if Kernel.const_defined?(full_class_name)
136
+ klass = Kernel.const_get(full_class_name)
137
+
138
+ if ! klass.respond_to?(meth)
139
+ raise "The functionality invoked (#{class_name} #{meth}) is not currently supported!"
140
+ end
141
+
142
+ klass.send(meth, *args)
143
+ else
144
+ raise "The functionality (#{class_name}) invoked is not currently supported!"
145
+ end
126
146
  end
127
147
 
128
148
  def require_provider_modules
@@ -135,7 +155,7 @@ module Geet
135
155
  # OTHER HELPERS
136
156
 
137
157
  def api_interface
138
- provider_module::ApiInterface.new(@api_token, path(upstream: @upstream), @upstream)
158
+ attempt_provider_call(:ApiInterface, :new, @api_token, path(upstream: @upstream), @upstream)
139
159
  end
140
160
 
141
161
  # Example: `donaldduck/geet`
@@ -93,7 +93,7 @@ module Geet
93
93
  end
94
94
 
95
95
  def error?(response)
96
- !response['Status'].start_with?('2')
96
+ !response.code.start_with?('2')
97
97
  end
98
98
 
99
99
  def decode_and_format_error(parsed_response)
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'uri'
5
+ require 'net/http'
6
+ require 'json'
7
+ require 'shellwords'
8
+
9
+ module Geet
10
+ module Gitlab
11
+ class ApiInterface
12
+ API_BASE_URL = 'https://gitlab.com/api/v4'
13
+
14
+ def initialize(api_token, path_with_namespace, upstream)
15
+ @api_token = api_token
16
+ @path_with_namespace = path_with_namespace
17
+ @upstream = upstream
18
+ end
19
+
20
+ def upstream?
21
+ @upstream
22
+ end
23
+
24
+ def path_with_namespace(encoded: false)
25
+ encoded ? CGI.escape(@path_with_namespace) : @path_with_namespace
26
+ end
27
+
28
+ # Send a request.
29
+ #
30
+ # Returns the parsed response, or an Array, in case of multipage.
31
+ #
32
+ # params:
33
+ # :api_path: api path, will be appended to the API URL.
34
+ # for root path, prepend a `/`:
35
+ # - use `/gists` for `https://api.github.com/gists`
36
+ # when owner/project/repos is included, don't prepend `/`:
37
+ # - use `issues` for `https://api.github.com/myowner/myproject/repos/issues`
38
+ # :params: (Hash)
39
+ # :data: (Hash) if present, will generate a POST request, otherwise, a GET
40
+ # :multipage: set true for paged Github responses (eg. issues); it will make the method
41
+ # return an array, with the concatenated (parsed) responses
42
+ # :http_method: :get, :patch, :post and :put are accepted, but only :patch/:put are meaningful,
43
+ # since the others are automatically inferred by :data.
44
+ #
45
+ def send_request(api_path, params: nil, data: nil, multipage: false, http_method: nil)
46
+ address = api_url(api_path)
47
+ # filled only on :multipage
48
+ parsed_responses = []
49
+
50
+ loop do
51
+ response = send_http_request(address, params: params, data: data, http_method: http_method)
52
+
53
+ parsed_response = JSON.parse(response.body)
54
+
55
+ if error?(response)
56
+ formatted_error = decode_and_format_error(parsed_response)
57
+ raise(formatted_error)
58
+ end
59
+
60
+ return parsed_response if !multipage
61
+
62
+ parsed_responses.concat(parsed_response)
63
+
64
+ address = link_next_page(response.to_hash)
65
+
66
+ return parsed_responses if address.nil?
67
+
68
+ # Gitlab's next link address already includes all the params, so we remove
69
+ # the passed ones (if there's any).
70
+ params = nil
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def api_url(api_path)
77
+ "#{API_BASE_URL}/#{api_path}"
78
+ end
79
+
80
+ def send_http_request(address, params: nil, data: nil, http_method: nil)
81
+ uri = encode_uri(address, params)
82
+ http_class = find_http_class(http_method, data)
83
+
84
+ Net::HTTP.start(uri.host, use_ssl: true) do |http|
85
+ request = http_class.new(uri)
86
+
87
+ request['Private-Token'] = @api_token
88
+ request.body = data.to_json if data
89
+
90
+ http.request(request)
91
+ end
92
+ end
93
+
94
+ def encode_uri(address, params)
95
+ address += '?' + URI.encode_www_form(params) if params
96
+
97
+ URI(address)
98
+ end
99
+
100
+ def error?(response)
101
+ !response.code.start_with?('2')
102
+ end
103
+
104
+ def decode_and_format_error(parsed_response)
105
+ parsed_response.fetch('error')
106
+ end
107
+
108
+ def link_next_page(response_headers)
109
+ # An array (or nil) is returned.
110
+ link_header = Array(response_headers['link'])
111
+
112
+ return nil if link_header.empty?
113
+
114
+ link_header[0][/<(\S+)>; rel="next"/, 1]
115
+ end
116
+
117
+ def find_http_class(http_method, data)
118
+ http_method ||= data ? :post : :get
119
+
120
+ case http_method
121
+ when :get
122
+ Net::HTTP::Get
123
+ when :patch
124
+ Net::HTTP::Patch
125
+ when :put
126
+ Net::HTTP::Put
127
+ when :post
128
+ Net::HTTP::Post
129
+ else
130
+ raise "Unsupported HTTP method: #{http_method.inspect}"
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geet
4
+ module Gitlab
5
+ class Issue
6
+ attr_reader :number, :title, :link
7
+
8
+ def initialize(number, title, link)
9
+ @number = number
10
+ @title = title
11
+ @link = link
12
+ end
13
+
14
+ def self.list(api_interface)
15
+ api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/issues"
16
+
17
+ response = api_interface.send_request(api_path, multipage: true)
18
+
19
+ response.each_with_object([]) do |issue_data, result|
20
+ number = issue_data.fetch('iid')
21
+ title = issue_data.fetch('title')
22
+ link = issue_data.fetch('web_url')
23
+
24
+ result << new(number, title, link)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ 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.1.9'
4
+ VERSION = '0.1.10'
5
5
  end
@@ -5,7 +5,7 @@ require_relative '../../lib/geet/git/repository'
5
5
  require_relative '../../lib/geet/services/create_gist'
6
6
 
7
7
  describe Geet::Services::CreateGist do
8
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
8
+ let(:repository) { Geet::Git::Repository.new }
9
9
  let(:tempfile) { Tempfile.open('geet_gist') { |file| file << "testcontent" } }
10
10
 
11
11
  it 'should create a public gist' do
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/create_issue'
5
5
 
6
6
  describe Geet::Services::CreateIssue do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
8
- let(:upstream_repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN'), upstream: true) }
7
+ let(:repository) { Geet::Git::Repository.new }
8
+ let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
9
9
 
10
10
  context 'with labels, assignees and milestones' do
11
11
  it 'should create an issue' do
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/create_pr'
5
5
 
6
6
  describe Geet::Services::CreatePr do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
8
- let(:upstream_repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN'), upstream: true) }
7
+ let(:repository) { Geet::Git::Repository.new() }
8
+ let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
9
9
 
10
10
  context 'with labels, reviewers and milestones' do
11
11
  it 'should create a PR' do
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/list_issues'
5
5
 
6
6
  describe Geet::Services::ListIssues do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
8
- let(:upstream_repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN'), upstream: true) }
7
+ let(:repository) { Geet::Git::Repository.new() }
8
+ let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
9
9
 
10
10
  it 'should list the issues' do
11
11
  allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
@@ -4,7 +4,7 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/list_labels'
5
5
 
6
6
  describe Geet::Services::ListLabels do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
7
+ let(:repository) { Geet::Git::Repository.new() }
8
8
 
9
9
  it 'should list the labels' do
10
10
  allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/geet')
@@ -4,7 +4,7 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/list_milestones'
5
5
 
6
6
  describe Geet::Services::ListMilestones do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
7
+ let(:repository) { Geet::Git::Repository.new() }
8
8
 
9
9
  it 'should list the milestones' do
10
10
  allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/geet')
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/list_prs'
5
5
 
6
6
  describe Geet::Services::ListPrs do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
8
- let(:upstream_repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN'), upstream: true) }
7
+ let(:repository) { Geet::Git::Repository.new() }
8
+ let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
9
9
 
10
10
  it 'should list the PRs' do
11
11
  allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
@@ -4,7 +4,7 @@ require_relative '../../lib/geet/git/repository'
4
4
  require_relative '../../lib/geet/services/merge_pr'
5
5
 
6
6
  describe Geet::Services::MergePr do
7
- let(:repository) { Geet::Git::Repository.new(ENV.fetch('GITHUB_API_TOKEN')) }
7
+ let(:repository) { Geet::Git::Repository.new() }
8
8
 
9
9
  it 'should merge the PR for the current branch' do
10
10
  allow(repository).to receive(:current_branch).and_return('mybranch1')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saverio Miroddi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-21 00:00:00.000000000 Z
11
+ date: 2017-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simple_scripting
@@ -55,6 +55,8 @@ files:
55
55
  - lib/geet/github/label.rb
56
56
  - lib/geet/github/milestone.rb
57
57
  - lib/geet/github/pr.rb
58
+ - lib/geet/gitlab/api_interface.rb
59
+ - lib/geet/gitlab/issue.rb
58
60
  - lib/geet/helpers/os_helper.rb
59
61
  - lib/geet/services/create_gist.rb
60
62
  - lib/geet/services/create_issue.rb
@@ -107,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
109
  version: '0'
108
110
  requirements: []
109
111
  rubyforge_project:
110
- rubygems_version: 2.6.13
112
+ rubygems_version: 2.6.11
111
113
  signing_key:
112
114
  specification_version: 4
113
115
  summary: Commandline interface for performing SCM (eg. GitHub) operations (eg. PR