geet 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
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