travis-client 0.1.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.
@@ -0,0 +1,44 @@
1
+ require 'travis/api/entity/repository'
2
+ require 'travis/api/entity/build'
3
+
4
+ module Travis
5
+
6
+ module API
7
+
8
+ # Travis API element representation
9
+
10
+ class Entity
11
+
12
+ # @param [Hash{String=>String,Fixnum}] attributes
13
+ def initialize(attributes = {})
14
+ @attributes = attributes
15
+ end
16
+
17
+ # Any of the attributes elements previously set on the initialization
18
+ # will be accessible via its analog method.
19
+ #
20
+ # Example:
21
+ # Entity.new({:something => 'Hey There!'}).something #=> 'Hey There!'
22
+ def method_missing(method, *args, &block)
23
+ return @attributes[method.to_s] if @attributes.has_key?(method.to_s)
24
+ super
25
+ end
26
+
27
+ # Adds the attributes elements to the list of methods that
28
+ # the Entity will respond to.
29
+ def respond_to?(method, include_private=false)
30
+ @attributes.has_key?(method.to_s) || super(method, include_private)
31
+ end
32
+
33
+ protected
34
+
35
+ def attributes
36
+ @attributes
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
@@ -0,0 +1,129 @@
1
+ module Travis
2
+
3
+ module API
4
+
5
+ class Entity
6
+
7
+ # Travis API Build representation
8
+
9
+ class Build < Entity
10
+
11
+ # @param [Hash{String=>String,Fixnum}] attributes
12
+ # @param [String, NilClass] repository_owner The repository owner's name
13
+ # @param [String, NilClass] repository_name The repository's name
14
+ def initialize(attributes = {}, repository_owner = nil, repository_name = nil)
15
+ @repository_owner = repository_owner
16
+ @repository_name = repository_name
17
+ super(attributes)
18
+ end
19
+
20
+ # Returns the hash representation of the Build
21
+ #
22
+ # @return [Hash{String=>String,Fixnum}]
23
+ def to_hash
24
+ return attributes()
25
+ end
26
+
27
+ # Fetches and returns the parent build based on the build {#partner_id}.
28
+ # Returns nil if the current buils is actually a parent.
29
+ # Once the parent is fetched it will remain cached until {#reload!} is called.
30
+ #
31
+ # @return [Build, NilClass]
32
+ def parent
33
+ return nil if self.matrix
34
+ @parent ||= Client::Repositories.owner(repository_owner()).name(repository_name()).build!(self.partner_id)
35
+ end
36
+
37
+ # Fetches and returns its repository based on the build {#repository_id}.
38
+ # Once the repository is fetched it will remain cached until {#reload!} is called.
39
+ #
40
+ # @return [Repository]
41
+ def repository
42
+ @repository ||= repository!()
43
+ end
44
+
45
+ # Returns the collection of its children builds or nil if the current
46
+ # build is actually a child.
47
+ #
48
+ # @return [Array<Build>, NilClass]
49
+ def matrix
50
+ return nil unless @attributes.has_key?('matrix')
51
+ @matrix ||= @attributes['matrix'].map {|build_data| Build.new(build_data, repository_owner(), repository_name())}
52
+ end
53
+
54
+ # Erases the cached results and updates its attributes.
55
+ #
56
+ # @return [Boolean]
57
+ def reload!
58
+ @repository = nil
59
+ @parent = nil
60
+ @matrix = nil
61
+ @attributes = Client::Repositories.owner(repository_owner()).name(repository_name()).build!(self.id).attributes
62
+ true
63
+ end
64
+
65
+ # Retrurns the build id
66
+ # We were forced to define this method since on ruby versions < 1.9.2
67
+ # {Object#id} is preventing the {Build#id} reflection.
68
+ #
69
+ # @return [Fixnum] Build ID
70
+ def id
71
+ @attributes['id']
72
+ end
73
+
74
+ # Returns its execution time
75
+ # We were forced to define this method since started_at may be not
76
+ # be present on the response, until the build starts its execution,
77
+ # causing an exception.
78
+ #
79
+ # @return [String,NilClass]
80
+ def started_at
81
+ @attributes['started_at']
82
+ end
83
+
84
+ # Returns its end of execution time
85
+ # We were forced to define this method since finished_at may be not
86
+ # be present on the response, until the build starts its execution,
87
+ # causing an exception.
88
+ #
89
+ # @return [String,NilClass]
90
+ def finished_at
91
+ @attributes['finished_at']
92
+ end
93
+
94
+ private
95
+
96
+ # Fetches its repository by name and owner. If any of them is
97
+ # not present it will try to fetch the repository based on the {#repository_id}.
98
+ #
99
+ # @return [Repository]
100
+ def repository!
101
+ if @repository_owner && @repository_name
102
+ Client::Repositories.owner(@repository_owner).name(@repository_name).fetch!
103
+ else
104
+ Client::Repositories.id(self.repository_id).fetch!
105
+ end
106
+ end
107
+
108
+ # Returns the repository owner's name.
109
+ #
110
+ # @return [String]
111
+ def repository_owner
112
+ @repository_owner ||= self.repository.owner
113
+ end
114
+
115
+ # Returns the repository's name.
116
+ #
117
+ # @return [String]
118
+ def repository_name
119
+ @repository_name ||= self.repository.name
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+
@@ -0,0 +1,79 @@
1
+ module Travis
2
+
3
+ module API
4
+
5
+ class Entity
6
+
7
+ # Travis API Repository representation
8
+
9
+ class Repository < Entity
10
+
11
+ # Repository's name
12
+ #
13
+ # @return [String, NilClass]
14
+ attr_reader :name
15
+
16
+ # Repository owner's name
17
+ #
18
+ # @return [String, NilClass]
19
+ attr_reader :owner
20
+
21
+ # @param [Hash{String=>String,Fixnum}] attributes
22
+ # @param [String, NilClass] name Repository's name
23
+ # @param [String, NilClass] owner Repository owner's name
24
+ def initialize(attributes = {}, owner = nil, name = nil)
25
+ @owner = owner || attributes['slug'] && attributes['slug'].split('/').first
26
+ @name = name || attributes['slug'] && attributes['slug'].split('/').last
27
+ super(attributes)
28
+ end
29
+
30
+ # Returns the hash representation of the Repository
31
+ #
32
+ # @return [Hash{String=>String,Fixnum}]
33
+ def to_hash
34
+ return attributes()
35
+ end
36
+
37
+ # Fetches and returns its builds.
38
+ # Once the collection is fetched it will remain cached until {#reload!} is called.
39
+ #
40
+ # @return [Array<Build>]
41
+ def builds
42
+ @builds ||= Client::Repositories.owner(self.owner).name(self.name).builds!
43
+ end
44
+
45
+ # Fetches and returns its latest build.
46
+ # Once the build is fetched it will remain cached until {#reload!# is called.
47
+ #
48
+ # @return [Build]
49
+ def last_build
50
+ @last_build ||= Client::Repositories.owner(self.owner).name(self.name).build!(self.last_build_id)
51
+ end
52
+
53
+ # Erases the cached results and updates the repository attributes.
54
+ #
55
+ # @return [Boolean]
56
+ def reload!
57
+ @builds = nil
58
+ @last_build = nil
59
+ @attributes = Client::Repositories.owner(self.owner).name(self.name).fetch!.attributes
60
+ true
61
+ end
62
+
63
+ # Retrurns the repository id
64
+ # We were forced to define this method since on ruby versions < 1.9.2
65
+ # {Object#id} is preventing the {Repository#id} reflection.
66
+ #
67
+ # @return [Fixnum] Repository ID
68
+ def id
69
+ @attributes['id']
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
@@ -0,0 +1,109 @@
1
+ require 'ostruct'
2
+ require 'optparse'
3
+ require 'hirb'
4
+
5
+ require 'travis/api'
6
+ require 'travis/client/repositories'
7
+
8
+ 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
+
106
+ end
107
+
108
+ end
109
+
@@ -0,0 +1,345 @@
1
+ module Travis
2
+
3
+ class Client
4
+
5
+ # Travis Command Line Client for Repositories
6
+ class Repositories < Client
7
+
8
+ # Display a table with the list of the recent processed repositories
9
+ def recent
10
+ $stdout.print("Fetching recent repositories ...\n\n")
11
+ $stdout.print(repositories_table_for(API::Client::Repositories.all!))
12
+ $stdout.print("\n")
13
+ end
14
+
15
+ # Display detailed information of the requested repository
16
+ def fetch
17
+ $stdout.print("Fetching repository #{options().owner}/#{options().name} ...\n\n")
18
+ if repository = API::Client::Repositories.owner(options().owner).name(options().name).fetch!
19
+ $stdout.print(repository_view_for(repository))
20
+ else
21
+ $stdout.print("\033\[33mCould not find the #{options().owner}/#{options().name} repository.\033\[0m")
22
+ end
23
+ $stdout.print("\n")
24
+ end
25
+
26
+ # Display a table with the list of the requested repositories
27
+ def fetch_group
28
+ $stdout.print("Fetching repositories #{options().slugs.join(', ')} ...\n")
29
+ repositories = options().slugs.map {|slug| API::Client::Repositories.slug(slug).fetch! || $stdout.print("\033\[33mCould not find the #{slug} repository\033\[0m\n")}.compact
30
+ $stdout.print("\n")
31
+ $stdout.print(repositories_table_for(repositories))
32
+ $stdout.print("\n")
33
+ end
34
+
35
+ # Disaply a table with the list of recent proccessed repository builds
36
+ def builds
37
+ $stdout.print("Fetching #{options().owner}/#{options().name} builds ...\n\n")
38
+ if builds = API::Client::Repositories.owner(options().owner).name(options().name).builds!
39
+ $stdout.print(builds_table_for(builds))
40
+ else
41
+ $stdout.print("\033\[33mCould not find the #{options().owner}/#{options().name} repository.\033\[0m")
42
+ end
43
+ $stdout.print("\n")
44
+ end
45
+
46
+ # Display detailed information of the requested repository build
47
+ def build
48
+ $stdout.print("Fetching #{options().owner}/#{options().name} build ##{options().build_id} ...\n\n")
49
+ if build = API::Client::Repositories.owner(options().owner).name(options().name).build!(options().build_id)
50
+ $stdout.print(build_view_for(build))
51
+ else
52
+ $stdout.print("\033\[33mCould not find the #{options().owner}/#{options().name} build with ID = #{oprions().build_id}.\033\[0m")
53
+ end
54
+ $stdout.print("\n")
55
+ end
56
+
57
+ private
58
+
59
+ # Labels for the repository tables
60
+ # This also limits the information to be displayed.
61
+ #
62
+ # @return [Array<String>]
63
+ REPOSITORY_FIELDS = ['ID', 'Slug', 'Status', 'Started At', 'Finished At', 'Build ID', 'Build Number']
64
+
65
+ # Maps the repository attribute names with the table labels
66
+ #
67
+ # @return [Hash{String=>String}]
68
+ REPOSITORY_FIELD_NAMES = {
69
+ 'id' => 'ID',
70
+ 'slug' => 'Slug',
71
+ 'last_build_status' => 'Status',
72
+ 'last_build_started_at' => 'Started At',
73
+ 'last_build_finished_at' => 'Finished At',
74
+ 'last_build_id' => 'Build ID',
75
+ 'last_build_number' => 'Build Number'
76
+ }
77
+
78
+ # Maps the status code from the API to its human translation.
79
+ #
80
+ # @return [Hash{NilClass,Fixnum=>String}]
81
+ BUILD_STATUS = {
82
+ nil => 'Running',
83
+ 0 => 'Passing',
84
+ 1 => 'Failing'
85
+ }
86
+
87
+ # Maps the status code from the API to its human translation.
88
+ #
89
+ # @return [Hash]
90
+ BUILD_COLORED_STATUS = {
91
+ nil => "\033\[33mRunning\033\[0m",
92
+ 0 => "\033\[32mPassing\033\[0m",
93
+ 1 => "\033\[31mFailing\033\[0m"
94
+ }
95
+
96
+ # Labels for the builds tables.
97
+ # This also limits the information to be displayed.
98
+ #
99
+ # @return [Array<String>]
100
+ BUILD_FIELDS = ['ID', 'Branch', 'Status', 'Started At', 'Finished At', 'Author', 'Commit', 'Repo ID']
101
+
102
+ # Labels for the child builds tables.
103
+ # This also limits the information to be displayed.
104
+ #
105
+ # @return [Array<String>]
106
+ CHILD_BUILD_FIELDS = ['ID', 'Status', 'Started At', 'Finished At']
107
+
108
+ # Maps the build attribute names with the table labels
109
+ #
110
+ # @return [Hash{String=>String}]
111
+ BUILD_FIELD_NAMES = {
112
+ 'id' => 'ID',
113
+ 'branch' => 'Branch',
114
+ 'status' => 'Status',
115
+ 'started_at' => 'Started At',
116
+ 'finished_at' => 'Finished At',
117
+ 'author_name' => 'Author',
118
+ 'commit' => 'Commit',
119
+ 'message' => 'Message',
120
+ 'compare_url' => 'Compare Url',
121
+ 'repository_id' => 'Repo ID',
122
+ 'number' => 'Numbre'
123
+ }
124
+
125
+ # Organize the present options and sets some defaults
126
+ def handle_options
127
+ set_default_options()
128
+ super
129
+ end
130
+
131
+ # Sets the default options
132
+ def set_default_options
133
+ options().target = :recent
134
+ end
135
+
136
+ # Sets up the custom sections of the help message
137
+ def setup_help(opts)
138
+ opts.separator ''
139
+ opts.separator <<-USAGE
140
+ Commands:
141
+ travis status|stat|s {options}
142
+ travis repositories|repos|repo|r {options}
143
+
144
+ Usage:
145
+ travis status
146
+ travis repositories [--recent]
147
+ travis repositories --slugs={repository_slug}[,{repository_slug}[,...]]
148
+ travis repositories --name={repository_name} --owner={owner_name}
149
+ travis repositories --slug={repository_slug}
150
+ travis repositories --builds
151
+ travis repositories --name={repository_name} --owner={owner_name} --build_id={build_id}
152
+ travis repositories --slug={repository_slug} --build_id={build_id}
153
+ USAGE
154
+
155
+ yield(opts)
156
+ end
157
+
158
+ # Declares the list of available options that can be used.
159
+ #
160
+ # @return [Arrar<Array<String, Proc>>]
161
+ def client_options
162
+ super + [
163
+ ['--recent', 'lists the recent processed repositories.',
164
+ Proc.new {
165
+ options().target = :recent
166
+ }
167
+ ],
168
+ ['--builds', '-B', 'lists the recent builds for a repository.',
169
+ Proc.new {
170
+ options().target = :builds
171
+ }
172
+ ],
173
+ ['--owner=', '-o', 'sets the target repository owner\'s name.',
174
+ Proc.new { |value|
175
+ options().target = :fetch
176
+ options().owner = value
177
+ }
178
+ ],
179
+ ['--name=', '-n', 'sets the target repository name.',
180
+ Proc.new { |value|
181
+ options().target = :fetch
182
+ options().name = value
183
+ }
184
+ ],
185
+ ['--slug=', '-s', 'sets the target repositorys slug.',
186
+ Proc.new { |value|
187
+ options().target = :fetch
188
+ owner, name = value.split('/')
189
+ options().owner = owner
190
+ options().name = name
191
+ }
192
+ ],
193
+ ['--slugs=', '-S', 'sets the target repositories slugs (comma separated).',
194
+ Proc.new { |value|
195
+ options().target = :fetch_group
196
+ options().slugs = value.split(',')
197
+ }
198
+ ],
199
+ ['--build_id=', '-b', 'sets the target repository build id.',
200
+ Proc.new { |value|
201
+ options().target = :build
202
+ options().build_id = value
203
+ }
204
+ ]
205
+ ]
206
+ end
207
+
208
+ # Returns the formatted view of a repository
209
+ #
210
+ # @return [String]
211
+ def repository_view_for(repository)
212
+ last_build = repository.last_build
213
+ <<-REPOSITORY
214
+ #{repository.slug} - #{BUILD_COLORED_STATUS[repository.last_build_status]}
215
+
216
+ Repository ID: #{repository.id}
217
+
218
+ Last Build:
219
+ ID: #{last_build.id}
220
+ Number: #{last_build.number}
221
+
222
+ Started at: #{last_build.started_at ? DateTime.parse(last_build.started_at).strftime('%D %T'): '-----------------'}
223
+ Finished at: #{last_build.finished_at ? DateTime.parse(last_build.finished_at).strftime('%D %T'): '-----------------'}
224
+
225
+ Commit: #{last_build.commit[0...7]}(#{last_build.branch}) - http://github.com/#{repository.slug}/commit/#{last_build.commit[0...7]}
226
+ Author: #{last_build.author_name} (#{last_build.author_email})
227
+ Compare Url: #{last_build.compare_url}
228
+
229
+ Message:
230
+ #{last_build.message.gsub("\n", "\n ")}
231
+ #{last_build.matrix ? "\n Build Matrix:\n #{child_builds_table_for(last_build.matrix).gsub("\n", "\n ")}": ''}
232
+ REPOSITORY
233
+ end
234
+
235
+ # Returns the formatted table of repositories
236
+ #
237
+ # @return [String]
238
+ def repositories_table_for(repositories)
239
+ Hirb::Helpers::Table.render(
240
+ repositories.map{|repo| repo.to_hash},
241
+ {
242
+ :description => false,
243
+ :fields => REPOSITORY_FIELDS,
244
+ :change_fields => REPOSITORY_FIELD_NAMES,
245
+ :filters => {
246
+ 'Status' => lambda { |status|
247
+ BUILD_STATUS[status]
248
+ },
249
+ 'Started At' => lambda { |started_at|
250
+ started_at ? DateTime.parse(started_at).strftime('%D %T') : '-----------------'
251
+ },
252
+ 'Finished At' => lambda { |finished_at|
253
+ finished_at ? DateTime.parse(finished_at).strftime('%D %T') : '-----------------'
254
+ }
255
+ }
256
+ }
257
+ )
258
+ end
259
+
260
+ # Returns the formatted table of builds
261
+ #
262
+ # @return [String]
263
+ def builds_table_for(builds)
264
+ Hirb::Helpers::Table.render(
265
+ builds.map{|build| build.to_hash},
266
+ {
267
+ :description => false,
268
+ :fields => BUILD_FIELDS,
269
+ :change_fields => BUILD_FIELD_NAMES,
270
+ :max_fields => {
271
+ 'Commit' => 10
272
+ },
273
+ :filters => {
274
+ 'Status' => lambda { |status|
275
+ BUILD_STATUS[status]
276
+ },
277
+ 'Started At' => lambda { |started_at|
278
+ started_at ? DateTime.parse(started_at).strftime('%D %T') : '-----------------'
279
+ },
280
+ 'Finished At' => lambda { |finished_at|
281
+ finished_at ? DateTime.parse(finished_at).strftime('%D %T') : '-----------------'
282
+ }
283
+ }
284
+ }
285
+ )
286
+ end
287
+
288
+ # Returns the formatted table of builds
289
+ #
290
+ # @return [String]
291
+ def child_builds_table_for(builds)
292
+ Hirb::Helpers::Table.render(
293
+ builds.map{|build| build.to_hash},
294
+ {
295
+ :description => false,
296
+ :fields => CHILD_BUILD_FIELDS,
297
+ :change_fields => BUILD_FIELD_NAMES,
298
+ :max_fields => {
299
+ 'Commit' => 10
300
+ },
301
+ :filters => {
302
+ 'Status' => lambda { |status|
303
+ BUILD_STATUS[status]
304
+ },
305
+ 'Started At' => lambda { |started_at|
306
+ started_at ? DateTime.parse(started_at).strftime('%D %T') : '-----------------'
307
+ },
308
+ 'Finished At' => lambda { |finished_at|
309
+ finished_at ? DateTime.parse(finished_at).strftime('%D %T') : '-----------------'
310
+ }
311
+ }
312
+ }
313
+ )
314
+ end
315
+
316
+ # Returns the formatted view of a build
317
+ #
318
+ # @return [String]
319
+ def build_view_for(build)
320
+ <<-BUILD
321
+ #{build.repository.slug} - #{BUILD_COLORED_STATUS[build.status]}
322
+
323
+ ID: #{build.id}
324
+ Number: #{build.number}
325
+ Repository ID: #{build.repository.id}
326
+
327
+ Started at: #{build.started_at ? DateTime.parse(build.started_at).strftime('%D %T'): '-----------------'}
328
+ Finished at: #{build.finished_at ? DateTime.parse(build.finished_at).strftime('%D %T'): '-----------------'}
329
+
330
+ Commit: #{build.commit[0...7]}(#{build.branch}) - http://github.com/#{build.repository.slug}/commit/#{build.commit[0...7]}
331
+ Author: #{build.author_name} (#{build.author_email})
332
+ Compare Url: #{build.compare_url}
333
+
334
+ Message:
335
+ #{build.message.gsub("\n", "\n ")}
336
+ #{build.matrix ? "\nBuild Matrix:\n#{child_builds_table_for(build.matrix)}": ''}
337
+ BUILD
338
+ end
339
+
340
+ end
341
+
342
+ end
343
+
344
+ end
345
+