travis-client 0.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -2
  3. data/.rspec +3 -2
  4. data/.travis.yml +2 -9
  5. data/.yardopts +2 -0
  6. data/Gemfile +1 -3
  7. data/Gemfile.lock +59 -0
  8. data/lib/travis/client.rb +20 -104
  9. data/lib/travis/client/action.rb +128 -0
  10. data/lib/travis/client/collection.rb +66 -0
  11. data/lib/travis/client/connection.rb +104 -0
  12. data/lib/travis/client/context.rb +52 -0
  13. data/lib/travis/client/entity.rb +77 -0
  14. data/lib/travis/client/error.rb +47 -0
  15. data/lib/travis/client/generated.rb +2073 -0
  16. data/lib/travis/client/link.rb +34 -0
  17. data/lib/travis/client/session.rb +101 -0
  18. data/lib/travis/client/unknown.rb +5 -0
  19. data/lib/travis/client/version.rb +6 -0
  20. data/readme.md +187 -0
  21. data/script/generate +44 -0
  22. data/spec/http_responses/default/branches.http +715 -0
  23. data/spec/http_responses/default/find_owner.http +23 -0
  24. data/spec/http_responses/default/index.http +1518 -0
  25. data/spec/http_responses/default/repo.http +44 -0
  26. data/spec/http_responses/default/repo_branches_10.http +347 -0
  27. data/spec/http_responses/default/repo_branches_10_0.http +347 -0
  28. data/spec/http_responses/default/repo_branches_10_10.http +351 -0
  29. data/spec/http_responses/default/repo_branches_10_20.http +99 -0
  30. data/spec/http_responses/default/sync.http +12 -0
  31. data/spec/http_responses/default/user_repos.http +6315 -0
  32. data/spec/spec_helper.rb +4 -2
  33. data/spec/support/coverage.rb +8 -0
  34. data/spec/support/generate_http_response.rb +16 -0
  35. data/spec/support/http.rb +54 -0
  36. data/spec/travis/collection_spec.rb +19 -0
  37. data/spec/travis/connection_spec.rb +26 -0
  38. data/spec/travis/entity_spec.rb +27 -0
  39. data/spec/travis_spec.rb +18 -0
  40. data/travis-client.gemspec +21 -21
  41. metadata +103 -75
  42. data/README.textile +0 -111
  43. data/Rakefile +0 -8
  44. data/bin/travis +0 -21
  45. data/features/repositories.feature +0 -117
  46. data/features/step_definitions/repositories_steps.rb +0 -51
  47. data/features/support/env.rb +0 -2
  48. data/lib/travis.rb +0 -2
  49. data/lib/travis/api.rb +0 -3
  50. data/lib/travis/api/client.rb +0 -95
  51. data/lib/travis/api/client/repositories.rb +0 -234
  52. data/lib/travis/api/entity.rb +0 -44
  53. data/lib/travis/api/entity/build.rb +0 -129
  54. data/lib/travis/api/entity/repository.rb +0 -79
  55. data/lib/travis/client/repositories.rb +0 -345
  56. data/spec/travis/api/client/repositories_spec.rb +0 -2
  57. data/spec/travis/api/client_spec.rb +0 -15
  58. data/spec/travis/api/entity/build_spec.rb +0 -84
  59. data/spec/travis/api/entity/repository_spec.rb +0 -79
  60. data/spec/travis/api/entity_spec.rb +0 -46
  61. data/spec/travis/api/shared_client_examples.rb +0 -3
  62. data/spec/travis/api/shared_entity_examples.rb +0 -16
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0a0bddd722e4ae995e3de90e49acbddf490ad585
4
+ data.tar.gz: 9999fab5a24471c4ee087b9fa316280bc8013eba
5
+ SHA512:
6
+ metadata.gz: 2b4b77d90db34513776340d0b194c9c6cf80feacfbba2cb60f1e0f003e5ef9c38e6095c8620e1ae43a8ddcddef1458ac12aff06491890464af857201b0a5dc70
7
+ data.tar.gz: 31fd825d35fb2df20bcefc8928b2a7f218d753580cf7e0a0f04a2d44533c7ca8dedd9dad56a1eed32b5c98e2b018e14cf2fa141cdf795f56894292273119b1ed
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
- Gemfile.lock
2
- *.gem
1
+ .yardoc
2
+ .coverage
3
+ .DS_Store
4
+ doc
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
- --color
2
- --format nested
1
+ --require spec_helper
2
+ --colour
3
+ --tty
@@ -1,9 +1,2 @@
1
- rvm:
2
- - 1.8.7
3
- - 1.9.2
4
- - 1.9.3
5
- - ree
6
- - rbx
7
- - jruby
8
- - ruby-head
9
-
1
+ rvm: 2.3
2
+ script: bundle exec rspec
@@ -0,0 +1,2 @@
1
+ --charset utf-8
2
+ lib/**/*.rb
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
- source :gemcutter
2
-
1
+ source 'https://rubygems.org'
3
2
  gemspec
4
-
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ travis-client (3.0.0)
5
+ addressable (~> 2.5)
6
+ http (~> 2.1)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.5.0)
12
+ public_suffix (~> 2.0, >= 2.0.2)
13
+ diff-lcs (1.2.5)
14
+ docile (1.1.5)
15
+ domain_name (0.5.20170223)
16
+ unf (>= 0.0.5, < 1.0.0)
17
+ http (2.2.1)
18
+ addressable (~> 2.3)
19
+ http-cookie (~> 1.0)
20
+ http-form_data (~> 1.0.1)
21
+ http_parser.rb (~> 0.6.0)
22
+ http-cookie (1.0.3)
23
+ domain_name (~> 0.5)
24
+ http-form_data (1.0.1)
25
+ http_parser.rb (0.6.0)
26
+ json (2.0.2)
27
+ public_suffix (2.0.5)
28
+ rspec (3.5.0)
29
+ rspec-core (~> 3.5.0)
30
+ rspec-expectations (~> 3.5.0)
31
+ rspec-mocks (~> 3.5.0)
32
+ rspec-core (3.5.4)
33
+ rspec-support (~> 3.5.0)
34
+ rspec-expectations (3.5.0)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.5.0)
37
+ rspec-mocks (3.5.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.5.0)
40
+ rspec-support (3.5.0)
41
+ simplecov (0.12.0)
42
+ docile (~> 1.1.0)
43
+ json (>= 1.8, < 3)
44
+ simplecov-html (~> 0.10.0)
45
+ simplecov-html (0.10.0)
46
+ unf (0.1.4)
47
+ unf_ext
48
+ unf_ext (0.0.7.2)
49
+
50
+ PLATFORMS
51
+ ruby
52
+
53
+ DEPENDENCIES
54
+ rspec (~> 3.5)
55
+ simplecov (~> 0.12)
56
+ travis-client!
57
+
58
+ BUNDLED WITH
59
+ 1.14.6
@@ -1,109 +1,25 @@
1
- require 'ostruct'
2
- require 'optparse'
3
- require 'hirb'
4
-
5
- require 'travis/api'
6
- require 'travis/client/repositories'
1
+ # frozen_string_literal: true
2
+ require 'addressable/uri'
3
+ require 'addressable/template'
4
+ require 'http'
5
+ require 'json'
7
6
 
8
7
  module Travis
9
-
10
- # Travis Command Line Client
11
-
12
- class Client
13
-
14
- # Tries to guess the repository based on the git remote urls
15
- # and prints its status.
16
- # If several remote repositories are defined it will display
17
- # the status of each of them.
18
- def self.status
19
- ARGV[0] = 'repositories'
20
- slugs = target_repository_slugs()
21
- if slugs.length > 1
22
- ARGV << "--slugs=#{target_repository_slugs().join(',')}"
23
- else
24
- ARGV << "--slug=#{target_repository_slugs().first}"
25
- end
26
- Repositories.new.run
27
- end
28
-
29
- # Handles the given options and executes the requested command
30
- def run
31
- handle_options()
32
- execute_operation()
33
- end
34
-
35
- private
36
-
37
- # Returns the list of repository slugs based on the current
38
- # directory git remote repositories.
39
- #
40
- # @return [Array<String>]
41
- def self.target_repository_slugs
42
- %x[git remote -v].scan(/(?:\:|\/)([^\:\/]+\/[^\:\/]+)\.git/m).flatten.uniq
43
- end
44
-
45
- # Initializes and return the options as an OpenStruct instance
46
- #
47
- # @return [OpenStruct]
48
- def options
49
- @options ||= OpenStruct.new
50
- end
51
-
52
- # Sets the options and creates the help layout.
53
- def handle_options
54
- OptionParser.new do |opts|
55
- opts.banner = 'Travis CI Command Line Client'
56
-
57
- setup_help(opts) {|opts|
58
- opts.separator ''
59
- opts.separator 'Supported Options:'
60
- client_options().each {|option| opts.on(*option)}
61
- opts.on_tail('--help', '-h', '-H', 'display this help message.') do
62
- $stdout.print opts
63
- exit
64
- end
65
- }
66
- end.parse!
67
- end
68
-
69
- # Sets up the custom help sections
70
- #
71
- # @param [OpenParser] opts The options parser
72
- def setup_help(opts)
73
- opts.separator ''
74
- opts.separator <<-USAGE
75
- Usage:
76
- travis repositories|repos|rep|r {options}
77
- travis status|stat|s {options}
78
- USAGE
79
- opts.separator ''
80
- opts.separator <<-FURTHER_HELP
81
- Furhter Help:
82
- travis {command} --help
83
- FURTHER_HELP
84
-
85
- yield(opts)
86
- end
87
-
88
- # Retuns the default options
89
- #
90
- # @return [Array<String>]
91
- def client_options
92
- []
93
- end
94
-
95
- # Starts the execution of the previousy requested
96
- # command or rise and exception if the target operation
97
- # to be executed can not be identified.
98
- def execute_operation
99
- unless options().target
100
- raise 'Nothing to do ...'
101
- end
102
-
103
- self.send(options().target)
104
- end
105
-
8
+ module Client
9
+ require 'travis/client/version'
10
+
11
+ require 'travis/client/action'
12
+ require 'travis/client/connection'
13
+ require 'travis/client/context'
14
+ require 'travis/client/entity'
15
+ require 'travis/client/error'
16
+ require 'travis/client/link'
17
+ require 'travis/client/session'
18
+
19
+ require 'travis/client/collection'
20
+ require 'travis/client/unknown'
106
21
  end
107
22
 
23
+ extend Client::Context
24
+ self.default_endpoint = 'https://api.travis-ci.org'
108
25
  end
109
-
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+ module Travis::Client
3
+ class Action
4
+ class Template < Struct.new(:method, :uri_template, :mandatory, :optional, :body_params)
5
+ def map_params(params, prefix)
6
+ params.map { |key, value| map_pair(key.to_s, value, prefix).to_h }.inject({}, &:merge)
7
+ end
8
+
9
+ def map_pair(key, value, prefix)
10
+ return if value.nil?
11
+ return entity_params(key, value) if value.is_a? Entity
12
+ return map_pair("#{prefix}.#{key}", value, prefix) if !params.include?(key) and params.include?("#{prefix}.#{key}")
13
+
14
+ if value.is_a? Hash and !body_params.include? key
15
+ return value.map { |k,v| map_pair("#{prefix}.#{k}", v, prefix).to_h }.inject(&:merge)
16
+ end
17
+
18
+ if mandatory.include? key or optional.include? key
19
+ value = prepare_segment(value)
20
+ else
21
+ value = prepare_json(value)
22
+ end
23
+
24
+ { key => value }
25
+ end
26
+
27
+ def prepare_segment(value)
28
+ case value
29
+ when Array then value.map { |v| prepare_segment(v) }.join(',')
30
+ else value.to_s
31
+ end
32
+ end
33
+
34
+ def prepare_json(value)
35
+ value
36
+ end
37
+
38
+ def entity_params(key, value)
39
+ result = {}
40
+ params.each do |param|
41
+ next unless param =~ /^#{key}\.(.+)$/
42
+ next unless value.respond_to? $1
43
+ result[param] = value.public_send($1)
44
+ end
45
+ result[key] = value if result.empty?
46
+ result
47
+ end
48
+
49
+ def uri(params)
50
+ uri_template.expand(params)
51
+ end
52
+
53
+ def payload(params)
54
+ params.reject { |k,_| mandatory.include? k or optional.include? k }
55
+ end
56
+
57
+ def params
58
+ @params ||= mandatory + optional + body_params
59
+ end
60
+
61
+ def accept_everything?
62
+ method != 'GET' and body_params.empty?
63
+ end
64
+
65
+ def possible_params(prefix)
66
+ [mandatory, optional + body_params].
67
+ map { |list| list.map { |p| p.sub(/^#{prefix}\./, '').inspect }.join(',') if list.any? }.
68
+ compact.join(', optionally ')
69
+ end
70
+ end
71
+
72
+ METHOD_ORDER = ['GET', 'PATCH', 'DELETE', 'PUT', 'POST']
73
+
74
+ attr_reader :resource_type, :name, :templates
75
+
76
+ def initialize(base_href, resource_type, name)
77
+ @resource_type, @name = resource_type, name
78
+ @base_href = Addressable::URI.parse(base_href)
79
+ @templates = []
80
+ end
81
+
82
+ def call(session, params)
83
+ method, url, payload = request_for(params)
84
+ raise ArgumentError, "parameters don't match action, possible parameters: #{possible_params}, given: #{params.keys.map { |k| k.to_s.inspect }.join(', ')}" unless method
85
+ session.request(method, url, payload)
86
+ end
87
+
88
+ def possible_params
89
+ templates.map { |t| t.possible_params(resource_type) }.join('; or ')
90
+ end
91
+
92
+ def accepted_types
93
+ templates.flat_map { |t| t.mandatory.map { |k| k.split('.', 2).first if k.include? '.' }.compact }.uniq
94
+ end
95
+
96
+ def instance_action?(prefix)
97
+ prefix = "#{prefix}."
98
+ templates.any? do |template|
99
+ template.mandatory.any? { |key| key.start_with? prefix }
100
+ end
101
+ end
102
+
103
+ def request_for(params)
104
+ params = { params['@type'] => params } if params.is_a? Entity and params['@type']
105
+ params = params.to_h
106
+
107
+ templates.each do |template|
108
+ mapped = template.map_params(params, resource_type)
109
+ next unless template.mandatory.all? { |k| mapped.include? k }
110
+ next unless template.accept_everything? or mapped.keys.all? { |k| template.params.include? k }
111
+ return [template.method, template.uri(mapped), template.payload(mapped)]
112
+ end
113
+ false
114
+ end
115
+
116
+ def add_template(method, pattern, accepted_params = nil)
117
+ uri_template = Addressable::Template.new(@base_href.join(pattern).to_s)
118
+ template = Template.new(method, uri_template, [], [], Array(accepted_params))
119
+ pattern.scan(/\{(\W?)(?:([^\}]+))\}/) do |prefix, params|
120
+ list = prefix == '?' ? template.optional : template.mandatory
121
+ list.concat(params.split(','))
122
+ end
123
+ templates << template
124
+ templates.sort_by { |t| [METHOD_ORDER.index(t.method) || METHOD_ORDER.size, -t.mandatory.size, t.optional.size] }
125
+ self
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ module Travis::Client
3
+ class Collection < Entity
4
+ include Enumerable
5
+
6
+ def paginates?
7
+ !!@payload['@pagination']
8
+ end
9
+
10
+ def offset
11
+ pagination_info['offset'] || 0
12
+ end
13
+
14
+ def on_page
15
+ @on_page ||= Array(@payload[collection_key])
16
+ end
17
+
18
+ def last_page?
19
+ pagination_info.fetch('is_last', true)
20
+ end
21
+
22
+ def first_page?
23
+ pagination_info.fetch('is_first', true)
24
+ end
25
+
26
+ def collection_key
27
+ @collection_key ||= begin
28
+ session.fetch(@href) if @payload.keys.all? { |k| k.start_with? '@' }
29
+ @payload.keys.detect { |k| !k.start_with?('@') and self[k].is_a? Array }
30
+ end
31
+ end
32
+
33
+ def size
34
+ pagination_info['count'] || on_page.size
35
+ end
36
+
37
+ def next_page
38
+ pagination_info['next']&.fetch
39
+ end
40
+
41
+ def previous_page
42
+ pagination_info['prev']&.fetch
43
+ end
44
+
45
+ def first_page
46
+ return self if first_page?
47
+ pagination_info['first']&.fetch
48
+ end
49
+
50
+ def last_page
51
+ return self if last_page?
52
+ pagination_info['last']&.fetch
53
+ end
54
+
55
+ def each(&block)
56
+ return enum_for(:each) unless block
57
+ on_page.each(&block)
58
+ next_page&.each(&block)
59
+ self
60
+ end
61
+
62
+ def pagination_info
63
+ @pagination_info ||= @payload['@pagination'] || {}
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+ module Travis::Client
3
+ class Connection
4
+ Env = Struct.new(:request_method, :uri, :params, :response, :meta_data)
5
+ DEFAULT_HEADERS = {
6
+ "Travis-API-Version" => "3",
7
+ "Accept" => "application/json",
8
+ "User-Agent" => "Travis/#{VERSION} Ruby/#{RUBY_VERSION}" }
9
+ PREDEFINED = {
10
+ 'home' => { '@type' => 'resource', 'attributes' => ['config', 'errors', 'resources'] },
11
+ 'resource' => { '@type' => 'resource', 'attributes' => ['actions', 'attributes', 'representations', 'permissions'] },
12
+ 'template' => { '@type' => 'resource', 'attributes' => ['request_method', 'uri_template', 'accepted_params'] }}
13
+
14
+ private_constant :DEFAULT_HEADERS, :PREDEFINED
15
+
16
+ attr_reader :request_headers, :service_index, :resource_types, :error_types, :mixin, :actions, :http_factory, :access_token
17
+
18
+ def initialize(endpoint: Travis.default_endpoint, request_headers: {}, access_token: nil, http_factory: HTTP)
19
+ @request_headers, @mixin = DEFAULT_HEADERS.merge(request_headers).freeze, Module.new
20
+ @error_types, @resource_types, @actions = {}, {}, {}
21
+ @before_callbacks, @after_callbacks = [], []
22
+ @session_factory, @http_factory = Class.new(Session), http_factory
23
+ @access_token = access_token
24
+ @default_session = create_session
25
+
26
+ yield self if block_given?
27
+
28
+ load_resource(PREDEFINED, endpoint)
29
+ @service_index = @default_session.request(:get, endpoint)
30
+ load_resource(service_index.resources, endpoint)
31
+ load_errors(service_index.errors)
32
+ define_actions
33
+
34
+ @session_factory.include(@mixin)
35
+ end
36
+
37
+ def create_session(**options)
38
+ options[:access_token] ||= access_token
39
+ @session_factory.new(self, **options)
40
+ end
41
+
42
+ def before_request(callback = Proc.new)
43
+ @before_callbacks << callback
44
+ end
45
+
46
+ def after_request(callback = Proc.new)
47
+ @after_callbacks << callback
48
+ end
49
+
50
+ def notify(method, uri, params)
51
+ env = Env.new(method, uri, params, nil, {})
52
+ @before_callbacks.each { |c| c.call(env) }
53
+ env.response = yield
54
+ @after_callbacks.each { |c| c.call(env) }
55
+ env.response
56
+ end
57
+
58
+ def action(resource_type, action_name)
59
+ actions.
60
+ fetch(resource_type.to_s) { raise ArgumentError, 'unknown resource type' }.
61
+ fetch(action_name.to_s) { raise ArgumentError, 'unknown action' }
62
+ end
63
+
64
+ private def add_action(base_href, type, name, templates)
65
+ action = Action.new(base_href, type, name)
66
+ actions[type] ||= {}
67
+ actions[type][name] = action
68
+ templates.each { |t| action.add_template(t.request_method, t.uri_template, t.accepted_params)}
69
+ end
70
+
71
+ private def load_resource(resources, base_href)
72
+ resources.each do |type, definition|
73
+ factory = resource_types.fetch(type) { resource_types[type] = Class.new(superclass_for(type, definition)) }
74
+ Array(definition['attributes']).each { |attribute| factory.add_attribute(attribute) }
75
+ Hash(definition['actions']).each { |key, value| add_action(base_href, type, key, Array(value)) }
76
+ end
77
+ end
78
+
79
+ private def superclass_for(type, definition)
80
+ return Error if type == 'error'
81
+ definition['attributes'] == [type] ? Collection : Entity
82
+ end
83
+
84
+ private def load_errors(errors)
85
+ errors.each do |type, definition|
86
+ factory = error_types.fetch(type) { error_types[type] = Class.new(resource_types.fetch('error')) }
87
+ Array(definition['additional_attributes']).each { |attribute| factory.add_attribute(attribute) }
88
+ factory.default_message = definition['default_message']
89
+ end
90
+ end
91
+
92
+ private def define_actions
93
+ actions.each do |resource_type, mapping|
94
+ factory = resource_types[resource_type]
95
+ mapping.each do |name, action|
96
+ factory.add_action(resource_type, name, action)
97
+ action.accepted_types.each { |t| resource_types[t].add_related_action(t, resource_type, name, action) if t != resource_type and resource_types.include? t }
98
+ @mixin.module_eval { define_method("#{resource_type}_#{name}") { |params={}| action.call(session, params) }} unless @mixin.method_defined? "#{resource_type}_#{name}"
99
+ @mixin.module_eval { define_method("#{name}_#{resource_type}") { |params={}| action.call(session, params) }} unless @mixin.method_defined? "#{name}_#{resource_type}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end