travis-client 0.1.0

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