travis-client 0.1.0 → 3.0.0

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.
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