travis-async-listener 1.8.3

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 (184) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +2486 -0
  4. data/Rakefile +63 -0
  5. data/assets/cacert.pem +69 -0
  6. data/assets/init/c.yml +4 -0
  7. data/assets/init/clojure.yml +1 -0
  8. data/assets/init/cpp.yml +4 -0
  9. data/assets/init/erlang.yml +3 -0
  10. data/assets/init/go.yml +4 -0
  11. data/assets/init/groovy.yml +1 -0
  12. data/assets/init/haskell.yml +1 -0
  13. data/assets/init/java.yml +4 -0
  14. data/assets/init/node_js.yml +4 -0
  15. data/assets/init/objective-c.yml +1 -0
  16. data/assets/init/perl.yml +4 -0
  17. data/assets/init/php.yml +4 -0
  18. data/assets/init/python.yml +5 -0
  19. data/assets/init/ruby.yml +6 -0
  20. data/assets/init/scala.yml +4 -0
  21. data/assets/notifications/Travis CI.app/Contents/Info.plist +52 -0
  22. data/assets/notifications/Travis CI.app/Contents/MacOS/Travis CI +0 -0
  23. data/assets/notifications/Travis CI.app/Contents/PkgInfo +1 -0
  24. data/assets/notifications/Travis CI.app/Contents/Resources/Travis CI.icns +0 -0
  25. data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
  26. data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  27. data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
  28. data/assets/notifications/Travis CI.app/Contents/_CodeSignature/CodeResources +173 -0
  29. data/assets/notifications/Travis CI.app/Contents/embedded.provisionprofile +0 -0
  30. data/assets/notifications/icon.png +0 -0
  31. data/assets/travis.sh +163 -0
  32. data/assets/travis.sh.erb +64 -0
  33. data/bin/travis +18 -0
  34. data/examples/org_overview.rb +3 -0
  35. data/examples/pro_auth.rb +23 -0
  36. data/examples/stream.rb +6 -0
  37. data/lib/travis.rb +8 -0
  38. data/lib/travis/auto_login.rb +3 -0
  39. data/lib/travis/cli.rb +126 -0
  40. data/lib/travis/cli/accounts.rb +31 -0
  41. data/lib/travis/cli/api_command.rb +180 -0
  42. data/lib/travis/cli/branches.rb +25 -0
  43. data/lib/travis/cli/cache.rb +76 -0
  44. data/lib/travis/cli/cancel.rb +18 -0
  45. data/lib/travis/cli/command.rb +422 -0
  46. data/lib/travis/cli/console.rb +31 -0
  47. data/lib/travis/cli/disable.rb +15 -0
  48. data/lib/travis/cli/enable.rb +31 -0
  49. data/lib/travis/cli/encrypt.rb +108 -0
  50. data/lib/travis/cli/encrypt_file.rb +140 -0
  51. data/lib/travis/cli/endpoint.rb +35 -0
  52. data/lib/travis/cli/env.rb +66 -0
  53. data/lib/travis/cli/help.rb +23 -0
  54. data/lib/travis/cli/history.rb +49 -0
  55. data/lib/travis/cli/init.rb +82 -0
  56. data/lib/travis/cli/lint.rb +49 -0
  57. data/lib/travis/cli/login.rb +76 -0
  58. data/lib/travis/cli/logout.rb +14 -0
  59. data/lib/travis/cli/logs.rb +65 -0
  60. data/lib/travis/cli/monitor.rb +110 -0
  61. data/lib/travis/cli/open.rb +39 -0
  62. data/lib/travis/cli/parser.rb +43 -0
  63. data/lib/travis/cli/pubkey.rb +30 -0
  64. data/lib/travis/cli/raw.rb +20 -0
  65. data/lib/travis/cli/repo_command.rb +154 -0
  66. data/lib/travis/cli/report.rb +101 -0
  67. data/lib/travis/cli/repos.rb +53 -0
  68. data/lib/travis/cli/requests.rb +47 -0
  69. data/lib/travis/cli/restart.rb +18 -0
  70. data/lib/travis/cli/settings.rb +77 -0
  71. data/lib/travis/cli/setup.rb +66 -0
  72. data/lib/travis/cli/setup/anynines.rb +21 -0
  73. data/lib/travis/cli/setup/appfog.rb +19 -0
  74. data/lib/travis/cli/setup/artifacts.rb +23 -0
  75. data/lib/travis/cli/setup/biicode.rb +19 -0
  76. data/lib/travis/cli/setup/cloud_66.rb +20 -0
  77. data/lib/travis/cli/setup/cloud_control.rb +21 -0
  78. data/lib/travis/cli/setup/cloud_files.rb +20 -0
  79. data/lib/travis/cli/setup/cloud_foundry.rb +23 -0
  80. data/lib/travis/cli/setup/code_deploy.rb +55 -0
  81. data/lib/travis/cli/setup/deis.rb +20 -0
  82. data/lib/travis/cli/setup/divshot.rb +18 -0
  83. data/lib/travis/cli/setup/elastic_beanstalk.rb +23 -0
  84. data/lib/travis/cli/setup/engine_yard.rb +24 -0
  85. data/lib/travis/cli/setup/gcs.rb +22 -0
  86. data/lib/travis/cli/setup/hackage.rb +18 -0
  87. data/lib/travis/cli/setup/heroku.rb +20 -0
  88. data/lib/travis/cli/setup/modulus.rb +18 -0
  89. data/lib/travis/cli/setup/ninefold.rb +20 -0
  90. data/lib/travis/cli/setup/nodejitsu.rb +27 -0
  91. data/lib/travis/cli/setup/npm.rb +20 -0
  92. data/lib/travis/cli/setup/open_shift.rb +20 -0
  93. data/lib/travis/cli/setup/opsworks.rb +22 -0
  94. data/lib/travis/cli/setup/pypi.rb +22 -0
  95. data/lib/travis/cli/setup/releases.rb +35 -0
  96. data/lib/travis/cli/setup/ruby_gems.rb +25 -0
  97. data/lib/travis/cli/setup/s3.rb +25 -0
  98. data/lib/travis/cli/setup/sauce_connect.rb +21 -0
  99. data/lib/travis/cli/setup/service.rb +73 -0
  100. data/lib/travis/cli/show.rb +69 -0
  101. data/lib/travis/cli/sshkey.rb +118 -0
  102. data/lib/travis/cli/status.rb +19 -0
  103. data/lib/travis/cli/sync.rb +30 -0
  104. data/lib/travis/cli/token.rb +14 -0
  105. data/lib/travis/cli/version.rb +17 -0
  106. data/lib/travis/cli/whatsup.rb +30 -0
  107. data/lib/travis/cli/whoami.rb +15 -0
  108. data/lib/travis/client.rb +39 -0
  109. data/lib/travis/client/account.rb +56 -0
  110. data/lib/travis/client/annotation.rb +21 -0
  111. data/lib/travis/client/artifact.rb +88 -0
  112. data/lib/travis/client/auto_login.rb +45 -0
  113. data/lib/travis/client/broadcast.rb +14 -0
  114. data/lib/travis/client/build.rb +47 -0
  115. data/lib/travis/client/cache.rb +25 -0
  116. data/lib/travis/client/commit.rb +28 -0
  117. data/lib/travis/client/entity.rb +238 -0
  118. data/lib/travis/client/env_var.rb +102 -0
  119. data/lib/travis/client/error.rb +38 -0
  120. data/lib/travis/client/has_uuid.rb +13 -0
  121. data/lib/travis/client/job.rb +61 -0
  122. data/lib/travis/client/lint_result.rb +25 -0
  123. data/lib/travis/client/listener.rb +184 -0
  124. data/lib/travis/client/methods.rb +104 -0
  125. data/lib/travis/client/namespace.rb +85 -0
  126. data/lib/travis/client/not_loadable.rb +13 -0
  127. data/lib/travis/client/repository.rb +224 -0
  128. data/lib/travis/client/request.rb +36 -0
  129. data/lib/travis/client/restartable.rb +23 -0
  130. data/lib/travis/client/session.rb +339 -0
  131. data/lib/travis/client/settings.rb +25 -0
  132. data/lib/travis/client/singleton_setting.rb +36 -0
  133. data/lib/travis/client/ssh_key.rb +11 -0
  134. data/lib/travis/client/states.rb +98 -0
  135. data/lib/travis/client/user.rb +67 -0
  136. data/lib/travis/client/weak_entity.rb +26 -0
  137. data/lib/travis/pro.rb +5 -0
  138. data/lib/travis/pro/auto_login.rb +3 -0
  139. data/lib/travis/tools/assets.rb +21 -0
  140. data/lib/travis/tools/completion.rb +54 -0
  141. data/lib/travis/tools/formatter.rb +50 -0
  142. data/lib/travis/tools/github.rb +279 -0
  143. data/lib/travis/tools/notification.rb +69 -0
  144. data/lib/travis/tools/safe_string.rb +22 -0
  145. data/lib/travis/tools/ssl_key.rb +48 -0
  146. data/lib/travis/tools/system.rb +88 -0
  147. data/lib/travis/version.rb +3 -0
  148. data/spec/cli/cancel_spec.rb +15 -0
  149. data/spec/cli/encrypt_spec.rb +43 -0
  150. data/spec/cli/endpoint_spec.rb +34 -0
  151. data/spec/cli/help_spec.rb +33 -0
  152. data/spec/cli/history_spec.rb +38 -0
  153. data/spec/cli/init_spec.rb +226 -0
  154. data/spec/cli/login_spec.rb +13 -0
  155. data/spec/cli/logs_spec.rb +8 -0
  156. data/spec/cli/open_spec.rb +33 -0
  157. data/spec/cli/repo_command_spec.rb +25 -0
  158. data/spec/cli/restart_spec.rb +15 -0
  159. data/spec/cli/setup_spec.rb +5 -0
  160. data/spec/cli/show_spec.rb +9 -0
  161. data/spec/cli/status_spec.rb +28 -0
  162. data/spec/cli/token_spec.rb +22 -0
  163. data/spec/cli/version_spec.rb +18 -0
  164. data/spec/cli/whoami_spec.rb +34 -0
  165. data/spec/client/account_spec.rb +32 -0
  166. data/spec/client/annotation_spec.rb +14 -0
  167. data/spec/client/broadcast_spec.rb +10 -0
  168. data/spec/client/build_spec.rb +31 -0
  169. data/spec/client/commit_spec.rb +22 -0
  170. data/spec/client/job_spec.rb +30 -0
  171. data/spec/client/methods_spec.rb +15 -0
  172. data/spec/client/namespace_spec.rb +19 -0
  173. data/spec/client/repository_spec.rb +39 -0
  174. data/spec/client/session_spec.rb +165 -0
  175. data/spec/client/user_spec.rb +16 -0
  176. data/spec/client_spec.rb +17 -0
  177. data/spec/pro_spec.rb +10 -0
  178. data/spec/spec_helper.rb +29 -0
  179. data/spec/support/fake_api.rb +738 -0
  180. data/spec/support/fake_github.rb +24 -0
  181. data/spec/support/helpers.rb +45 -0
  182. data/spec/travis_spec.rb +10 -0
  183. data/travis.gemspec +323 -0
  184. metadata +489 -0
@@ -0,0 +1,180 @@
1
+ require 'travis/cli'
2
+
3
+ module Travis
4
+ module CLI
5
+ class ApiCommand < Command
6
+ include Travis::Client::Methods
7
+ attr_accessor :enterprise_name
8
+ attr_reader :session
9
+ abstract
10
+
11
+ on('-e', '--api-endpoint URL', 'Travis API server to talk to')
12
+ on('-I', '--[no-]insecure', 'do not verify SSL certificate of API endpoint')
13
+ on('--pro', "short-cut for --api-endpoint '#{Travis::Client::PRO_URI}'") { |c,_| c.api_endpoint = Travis::Client::PRO_URI }
14
+ on('--org', "short-cut for --api-endpoint '#{Travis::Client::ORG_URI}'") { |c,_| c.api_endpoint = Travis::Client::ORG_URI }
15
+ on('--staging', 'talks to staging system') { |c,_| c.api_endpoint = c.api_endpoint.gsub(/api/, 'api-staging') }
16
+ on('-t', '--token [ACCESS_TOKEN]', 'access token to use') { |c, t| c.access_token = t }
17
+
18
+ on('--debug', 'show API requests') do |c,_|
19
+ c.debug = true
20
+ c.session.instrument do |info, request|
21
+ c.time(info, request)
22
+ end
23
+ end
24
+
25
+ on('--debug-http', 'show HTTP(S) exchange') do |c,_|
26
+ c.session.debug_http = true
27
+ end
28
+
29
+ on('-X', '--enterprise [NAME]', 'use enterprise setup (optionally takes name for multiple setups)') do |c, name|
30
+ c.enterprise_name = name || 'default'
31
+ end
32
+
33
+ on('--adapter ADAPTER', 'Faraday adapter to use for HTTP requests') do |c, adapter|
34
+ adapter.gsub! '-', '_'
35
+ require "faraday/adapter/#{adapter}"
36
+ require 'typhoeus/adapters/faraday' if adapter == 'typhoeus'
37
+ c.session.faraday_adapter = adapter.to_sym
38
+ end
39
+
40
+ def initialize(*)
41
+ @session = Travis::Client.new(:agent_info => "command #{command_name}")
42
+ super
43
+ end
44
+
45
+ def endpoint_config
46
+ config['endpoints'] ||= {}
47
+ config['endpoints'][api_endpoint] ||= {}
48
+ end
49
+
50
+ def setup
51
+ setup_enterprise
52
+ self.api_endpoint = default_endpoint if default_endpoint and not explicit_api_endpoint?
53
+ self.access_token ||= fetch_token
54
+ endpoint_config['access_token'] ||= access_token
55
+ endpoint_config['insecure'] = insecure unless insecure.nil?
56
+ self.insecure = endpoint_config['insecure']
57
+ session.ssl = { :verify => false } if insecure?
58
+ authenticate if pro? or enterprise?
59
+ end
60
+
61
+ def enterprise?
62
+ !!endpoint_config['enterprise']
63
+ end
64
+
65
+ def pro?
66
+ api_endpoint == Travis::Client::PRO_URI
67
+ end
68
+
69
+ def org?
70
+ api_endpoint == Travis::Client::ORG_URI
71
+ end
72
+
73
+ def detected_endpoint?
74
+ api_endpoint == detected_endpoint
75
+ end
76
+
77
+ def authenticate
78
+ error "not logged in, please run #{command("login#{endpoint_option}")}" if access_token.nil?
79
+ end
80
+
81
+ def sync(block = true, dot = '.')
82
+ user.sync
83
+
84
+ steps = count = 1
85
+ while block and user.reload.syncing?
86
+ count += 1
87
+ sleep(1)
88
+
89
+ if count % steps == 0
90
+ steps = count/10 + 1
91
+ output.print dot
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def setup_enterprise
99
+ return unless setup_enterprise?
100
+ c = config['enterprise'] ||= {}
101
+ c[enterprise_name] = api_endpoint if explicit_api_endpoint?
102
+ c[enterprise_name] ||= write_to($stderr) do
103
+ error "enterprise setup not configured" unless interactive?
104
+ user_input = ask(color("Enterprise domain: ", :bold)).to_s
105
+ domain = user_input[%r{^(?:https?://)?(.*?)/?(?:/api/?)?$}, 1]
106
+ endpoint = "https://#{domain}/api"
107
+ config['default_endpoint'] = endpoint if agree("Use #{color domain, :bold} as default endpoint? ") { |q| q.default = 'yes' }
108
+ endpoint
109
+ end
110
+ self.api_endpoint = c[enterprise_name]
111
+ self.insecure = true if insecure.nil?
112
+ endpoint_config['enterprise'] = true
113
+ @setup_ennterpise = true
114
+ end
115
+
116
+ def setup_enterprise?
117
+ @setup_ennterpise ||= false
118
+ !!enterprise_name and not @setup_ennterpise
119
+ end
120
+
121
+ def load_gh
122
+ return if defined? GH
123
+ debug "Loading gh"
124
+ require 'gh'
125
+
126
+ gh_config = session.config['github']
127
+ gh_config &&= gh_config.inject({}) { |h,(k,v)| h.update(k.to_sym => v) }
128
+ gh_config ||= {}
129
+ gh_config[:ssl] = Travis::Client::Session::SSL_OPTIONS
130
+ gh_config[:ssl] = { :verify => false } if gh_config[:api_url] and gh_config[:api_url] != "https://api.github.com"
131
+ gh_config.delete :scopes
132
+
133
+ gh_config[:instrumenter] = proc do |type, payload, &block|
134
+ next block.call unless type == 'http.gh'
135
+ time("GitHub API: #{payload[:verb].to_s.upcase} #{payload[:url]}", block)
136
+ end if debug?
137
+
138
+ GH.set(gh_config)
139
+ end
140
+
141
+ def github_endpoint
142
+ load_gh
143
+ GH.with({}).api_host
144
+ end
145
+
146
+ def listen(*args)
147
+ super(*args) do |listener|
148
+ on_signal { listener.disconnect }
149
+ yield listener
150
+ end
151
+ end
152
+
153
+ def default_endpoint
154
+ ENV['TRAVIS_ENDPOINT'] || config['default_endpoint']
155
+ end
156
+
157
+ def detected_endpoint
158
+ default_endpoint || Travis::Client::ORG_URI
159
+ end
160
+
161
+ def endpoint_option
162
+ return "" if org? and detected_endpoint?
163
+ return " --org" if org?
164
+ return " --pro" if pro?
165
+
166
+ if config['enterprise']
167
+ key, _ = config['enterprise'].detect { |k,v| v.start_with? api_endpoint }
168
+ return " -X" if key == "default"
169
+ return " -X #{key}" if key
170
+ end
171
+
172
+ " -e %p" % api_endpoint
173
+ end
174
+
175
+ def fetch_token
176
+ ENV['TRAVIS_TOKEN'] || endpoint_config['access_token']
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,25 @@
1
+ require 'travis/cli'
2
+
3
+ module Travis
4
+ module CLI
5
+ class Branches < RepoCommand
6
+ description "displays the most recent build for each branch"
7
+
8
+ def run
9
+ repository.last_on_branch.each do |build|
10
+ say [
11
+ color("#{build.branch_info}:".ljust(longest + 2), [:info, :bold]),
12
+ color("##{build.number.to_s.ljust(4)} #{build.state}".ljust(16), build.color),
13
+ build.commit.subject
14
+ ].join(" ").strip + "\n"
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def longest
21
+ repository.branches.keys.map { |b| b.size }.max
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ require 'travis/cli'
2
+
3
+ module Travis
4
+ module CLI
5
+ class Cache < RepoCommand
6
+ description 'lists or deletes repository caches'
7
+ on '-d', '--delete', 'delete listed caches'
8
+ on '-b', '--branch BRANCH', 'only list/delete caches on given branch'
9
+ on '-m', '--match STRING', 'only list/delete caches where slug matches given string'
10
+ on '-f', '--force', 'do not ask user to confirm deleting the caches'
11
+
12
+ def run
13
+ error "not allowed to access caches for #{color(repository.slug, :bold)}" unless repository.push?
14
+ branches = caches.group_by(&:branch)
15
+ check_caches
16
+
17
+ warn "Deleted the following caches:\n" if delete?
18
+ branches.each { |name, list| display_branch(name, list) }
19
+ size = caches.inject(0) { |s,c| s + c.size }
20
+ say "Overall size of above caches: " << formatter.file_size(size)
21
+ end
22
+
23
+ private
24
+
25
+ def check_caches
26
+ return if caches.any?
27
+ say "no caches found"
28
+ exit
29
+ end
30
+
31
+ def display_branch(name, list)
32
+ say color(name ? "On branch #{name}:" : "Global:", :important)
33
+ list.each { |c| display_cache(c) }
34
+ puts
35
+ end
36
+
37
+ def display_cache(cache)
38
+ say [
39
+ color(cache.slug.ljust(space), :bold),
40
+ "last modified: " << formatter.time(cache.last_modified),
41
+ "size: " << formatter.file_size(cache.size)
42
+ ].join(" ") << "\n"
43
+ end
44
+
45
+ def params
46
+ params = {}
47
+ params[:branch] = branch if branch?
48
+ params[:match] = match if match?
49
+ params
50
+ end
51
+
52
+ def caches
53
+ @caches ||= drop? ? repository.delete_caches(params) : repository.caches(params)
54
+ end
55
+
56
+ def space
57
+ @space ||= caches.map(&:slug).map(&:size).max
58
+ end
59
+
60
+ def drop?
61
+ return false unless delete?
62
+ return true if force?
63
+ error "not deleting caches without --force" unless interactive?
64
+ error "aborted" unless danger_zone? "Do you really want to delete #{description}?"
65
+ true
66
+ end
67
+
68
+ def description
69
+ description = color("all caches", :important)
70
+ description << " on branch #{color(branch, :important)}" if branch?
71
+ description << " that match #{color(match, :important)}" if match?
72
+ description
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,18 @@
1
+ require 'travis/cli'
2
+
3
+ module Travis
4
+ module CLI
5
+ class Cancel < RepoCommand
6
+ description "cancels a job or build"
7
+
8
+ def run(number = last_build.number)
9
+ authenticate
10
+ entity = job(number) || build(number)
11
+ error "could not find job or build #{repository.slug}##{number}" unless entity
12
+ entity.cancel
13
+
14
+ say "canceled", "#{entity.class.one} ##{entity.number} has been %s"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,422 @@
1
+ require 'travis/cli'
2
+ require 'travis/tools/system'
3
+ require 'travis/tools/formatter'
4
+ require 'travis/tools/assets'
5
+ require 'travis/tools/completion'
6
+ require 'travis/version'
7
+
8
+ require 'highline'
9
+ require 'forwardable'
10
+ require 'yaml'
11
+ require 'timeout'
12
+
13
+ module Travis
14
+ module CLI
15
+ class Command
16
+ MINUTE = 60
17
+ HOUR = 3600
18
+ DAY = 86400
19
+ WEEK = 604800
20
+
21
+ include Tools::Assets
22
+ extend Parser, Forwardable, Tools::Assets
23
+ def_delegators :terminal, :agree, :ask, :choose
24
+
25
+ HighLine.use_color = Tools::System.unix? && $stdout.tty?
26
+ HighLine.color_scheme = HighLine::ColorScheme.new do |cs|
27
+ cs[:command] = [ :bold ]
28
+ cs[:error] = [ :red ]
29
+ cs[:important] = [ :bold, :underline ]
30
+ cs[:success] = [ :green ]
31
+ cs[:info] = [ :yellow ]
32
+ cs[:debug] = [ :magenta ]
33
+ end
34
+
35
+ on('-h', '--help', 'Display help') do |c, _|
36
+ c.say c.help
37
+ exit
38
+ end
39
+
40
+ on('-i', '--[no-]interactive', "be interactive and colorful") do |c, v|
41
+ HighLine.use_color = v if Tools::System.unix?
42
+ c.force_interactive = v
43
+ end
44
+
45
+ on('-E', '--[no-]explode', "don't rescue exceptions")
46
+ on('--skip-version-check', "don't check if travis client is up to date")
47
+ on('--skip-completion-check', "don't check if auto-completion is set up")
48
+
49
+ def self.command_name
50
+ name[/[^:]*$/].split(/(?=[A-Z])/).map(&:downcase).join('-')
51
+ end
52
+
53
+ @@abstract ||= [Command] # ignore the man behind the courtains!
54
+ def self.abstract?
55
+ @@abstract.include? self
56
+ end
57
+
58
+ def self.abstract
59
+ @@abstract << self
60
+ end
61
+
62
+ def self.skip(*names)
63
+ names.each { |n| define_method(n) {} }
64
+ end
65
+
66
+ def self.description(description = nil)
67
+ @description = description if description
68
+ @description ||= ""
69
+ end
70
+
71
+ def self.subcommands(*list)
72
+ return @subcommands ||= [] if list.empty?
73
+ @subcommands = list
74
+
75
+ define_method :run do |subcommand, *args|
76
+ error "Unknown subcommand. Available: #{list.join(', ')}." unless list.include? subcommand.to_sym
77
+ send(subcommand, *args)
78
+ end
79
+
80
+ define_method :usage do
81
+ usages = list.map { |c| color(usage_for("#{command_name} #{c}", c), :command) }
82
+ "\nUsage: #{usages.join("\n ")}\n\n"
83
+ end
84
+ end
85
+
86
+ attr_accessor :arguments, :config, :force_interactive, :formatter, :debug
87
+ attr_reader :input, :output
88
+ alias_method :debug?, :debug
89
+
90
+ def initialize(options = {})
91
+ @on_signal = []
92
+ @formatter = Travis::Tools::Formatter.new
93
+ self.output = $stdout
94
+ self.input = $stdin
95
+ options.each do |key, value|
96
+ public_send("#{key}=", value) if respond_to? "#{key}="
97
+ end
98
+ @arguments ||= []
99
+ end
100
+
101
+ def terminal
102
+ @terminal ||= HighLine.new(input, output)
103
+ end
104
+
105
+ def input=(io)
106
+ @terminal = nil
107
+ @input = io
108
+ end
109
+
110
+ def output=(io)
111
+ @terminal = nil
112
+ @output = io
113
+ end
114
+
115
+ def write_to(io)
116
+ io_was, self.output = output, io
117
+ yield
118
+ ensure
119
+ self.output = io_was if io_was
120
+ end
121
+
122
+ def parse(args)
123
+ rest = parser.parse(args)
124
+ arguments.concat(rest)
125
+ rescue OptionParser::ParseError => e
126
+ error e.message
127
+ end
128
+
129
+ def setup
130
+ end
131
+
132
+ def last_check
133
+ config['last_check'] ||= {
134
+ # migrate from old values
135
+ 'at' => config.delete('last_version_check'),
136
+ 'etag' => config.delete('etag')
137
+ }
138
+ end
139
+
140
+ def check_version
141
+ last_check.clear if last_check['version'] != Travis::VERSION
142
+ seconds_since = Time.now.to_i - last_check['at'].to_i
143
+
144
+ return if skip_version_check?
145
+ return if seconds_since < MINUTE
146
+
147
+ case seconds_since
148
+ when MINUTE .. HOUR then timeout = 0.5
149
+ when HOUR .. DAY then timeout = 1.0
150
+ when DAY .. WEEK then timeout = 2.0
151
+ else timeout = 10.0
152
+ end
153
+
154
+ Timeout.timeout(timeout) do
155
+ response = Faraday.get('https://rubygems.org/api/v1/gems/travis.json', {}, 'If-None-Match' => last_check['etag'].to_s)
156
+ last_check['etag'] = response.headers['etag']
157
+ last_check['version'] = JSON.parse(response.body)['version'] if response.status == 200
158
+ end
159
+
160
+ last_check['at'] = Time.now.to_i
161
+ unless Tools::System.recent_version? Travis::VERSION, last_check['version']
162
+ warn "Outdated CLI version, run `gem install travis`."
163
+ end
164
+ rescue Timeout::Error, Faraday::Error::ClientError => error
165
+ debug "#{error.class}: #{error.message}"
166
+ end
167
+
168
+ def check_completion
169
+ return if skip_completion_check? or !interactive?
170
+
171
+ if config['checked_completion']
172
+ Tools::Completion.update_completion if config['completion_version'] != Travis::VERSION
173
+ else
174
+ write_to($stderr) do
175
+ next Tools::Completion.update_completion if Tools::Completion.completion_installed?
176
+ next unless agree('Shell completion not installed. Would you like to install it now? ') { |q| q.default = "y" }
177
+ Tools::Completion.install_completion
178
+ end
179
+ end
180
+
181
+ config['checked_completion'] = true
182
+ config['completion_version'] = Travis::VERSION
183
+ end
184
+
185
+ def check_ruby
186
+ return if RUBY_VERSION > '1.9.2' or skip_version_check?
187
+ warn "Your Ruby version is outdated, please consider upgrading, as we will drop support for #{RUBY_VERSION} soon!"
188
+ end
189
+
190
+ def execute
191
+ setup_trap
192
+ check_ruby
193
+ check_arity(method(:run), *arguments)
194
+ load_config
195
+ check_version
196
+ check_completion
197
+ setup
198
+ run(*arguments)
199
+ clear_error
200
+ store_config
201
+ rescue Travis::Client::NotLoggedIn => e
202
+ raise(e) if explode?
203
+ error "#{e.message} - try running #{command("login#{endpoint_option}")}"
204
+ rescue Travis::Client::NotFound => e
205
+ raise(e) if explode?
206
+ error "resource not found (#{e.message})"
207
+ rescue Travis::Client::Error => e
208
+ raise(e) if explode?
209
+ error e.message
210
+ rescue StandardError => e
211
+ raise(e) if explode?
212
+ message = e.message
213
+ message += color("\nfor a full error report, run #{command("report#{endpoint_option}")}", :error) if interactive?
214
+ store_error(e)
215
+ error(message)
216
+ end
217
+
218
+ def command_name
219
+ self.class.command_name
220
+ end
221
+
222
+ def usage
223
+ "Usage: " << color(usage_for(command_name, :run), :command)
224
+ end
225
+
226
+ def usage_for(prefix, method)
227
+ usage = "travis #{prefix}"
228
+ method = method(method)
229
+ if method.respond_to? :parameters
230
+ method.parameters.each do |type, name|
231
+ name = name.upcase
232
+ name = "[#{name}]" if type == :opt
233
+ name = "[#{name}..]" if type == :rest
234
+ usage << " #{name}"
235
+ end
236
+ elsif method.arity != 0
237
+ usage << " ..."
238
+ end
239
+ usage << " [OPTIONS]"
240
+ end
241
+
242
+ def help(info = "")
243
+ parser.banner = usage
244
+ self.class.description.sub(/./) { |c| c.upcase } + ".\n" + info + parser.to_s
245
+ end
246
+
247
+ def say(data, format = nil, style = nil)
248
+ terminal.say format(data, format, style)
249
+ end
250
+
251
+ def debug(line)
252
+ return unless debug?
253
+ write_to($stderr) do
254
+ say color("** #{line}", :debug)
255
+ end
256
+ end
257
+
258
+ def time(info, callback = Proc.new)
259
+ return callback.call unless debug?
260
+ start = Time.now
261
+ debug(info)
262
+ callback.call
263
+ duration = Time.now - start
264
+ debug(" took %.2g seconds" % duration)
265
+ end
266
+
267
+ def info(line)
268
+ write_to($stderr) do
269
+ say color(line, :info)
270
+ end
271
+ end
272
+
273
+ def on_signal(&block)
274
+ @on_signal << block
275
+ end
276
+
277
+ private
278
+
279
+ def store_error(exception)
280
+ message = "An error occurred running `travis %s%s`:\n %p: %s\n" % [command_name, endpoint_option, exception.class, exception.message]
281
+ exception.backtrace.each { |l| message << " from #{l}\n" }
282
+ save_file("error.log", message)
283
+ end
284
+
285
+ def clear_error
286
+ delete_file("error.log")
287
+ end
288
+
289
+ def setup_trap
290
+ [:INT, :TERM].each do |signal|
291
+ trap signal do
292
+ @on_signal.each { |c| c.call }
293
+ exit 1
294
+ end
295
+ end
296
+ end
297
+
298
+ def format(data, format = nil, style = nil)
299
+ style ||= :important
300
+ data = format % color(data, style) if format and interactive?
301
+ data = data.gsub(/<\[\[/, '<%=').gsub(/\]\]>/, '%>')
302
+ data.encode! 'utf-8' if data.respond_to? :encode!
303
+ data
304
+ end
305
+
306
+ def template(*args)
307
+ File.read(*args).split('__END__', 2)[1].strip
308
+ end
309
+
310
+ def color(line, style)
311
+ return line.to_s unless interactive?
312
+ terminal.color(line || '???', Array(style).map(&:to_sym))
313
+ end
314
+
315
+ def interactive?(io = output)
316
+ return io.tty? if force_interactive.nil?
317
+ force_interactive
318
+ end
319
+
320
+ def empty_line
321
+ say "\n"
322
+ end
323
+
324
+ def warn(message)
325
+ write_to($stderr) do
326
+ say color(message, :error)
327
+ yield if block_given?
328
+ end
329
+ end
330
+
331
+ def error(message, &block)
332
+ warn(message, &block)
333
+ exit 1
334
+ end
335
+
336
+ def command(name)
337
+ color("#{File.basename($0)} #{name}", :command)
338
+ end
339
+
340
+ def success(line)
341
+ say color(line, :success) if interactive?
342
+ end
343
+
344
+ def config_path(name)
345
+ path = ENV.fetch('TRAVIS_CONFIG_PATH') { File.expand_path('.travis', Dir.home) }
346
+ Dir.mkdir(path, 0700) unless File.directory? path
347
+ File.join(path, name)
348
+ end
349
+
350
+ def load_file(name, default = nil)
351
+ return default unless path = config_path(name) and File.exist? path
352
+ debug "Loading %p" % path
353
+ File.read(path)
354
+ end
355
+
356
+ def delete_file(name)
357
+ return unless path = config_path(name) and File.exist? path
358
+ debug "Deleting %p" % path
359
+ File.delete(path)
360
+ end
361
+
362
+ def save_file(name, content, read_only = false)
363
+ path = config_path(name)
364
+ debug "Storing %p" % path
365
+ File.open(path, 'w') do |file|
366
+ file.write(content.to_s)
367
+ file.chmod(0600) if read_only
368
+ end
369
+ end
370
+
371
+ YAML_ERROR = defined?(Psych::SyntaxError) ? Psych::SyntaxError : ArgumentError
372
+ def load_config
373
+ @config = YAML.load load_file('config.yml', '{}')
374
+ @config ||= {}
375
+ @original_config = @config.dup
376
+ rescue YAML_ERROR => error
377
+ raise error if explode?
378
+ warn "Broken config file: #{color config_path('config.yml'), :bold}"
379
+ exit 1 unless interactive? and agree("Remove config file? ") { |q| q.default = "no" }
380
+ @original_config, @config = {}, {}
381
+ end
382
+
383
+ def store_config
384
+ save_file('config.yml', @config.to_yaml, true)
385
+ end
386
+
387
+ def check_arity(method, *args)
388
+ return unless method.respond_to? :parameters
389
+ method.parameters.each do |type, name|
390
+ return if type == :rest
391
+ wrong_args("few") unless args.shift or type == :opt or type == :block
392
+ end
393
+ wrong_args("many") if args.any?
394
+ end
395
+
396
+ def danger_zone?(message)
397
+ agree(color("DANGER ZONE: ", [:red, :bold]) << message << " ") { |q| q.default = "no" }
398
+ end
399
+
400
+ def write_file(file, content, force = false)
401
+ error "#{file} already exists" unless write_file?(file, force)
402
+ File.write(file, content)
403
+ end
404
+
405
+ def write_file?(file, force)
406
+ return true if force or not File.exist?(file)
407
+ return false unless interactive?
408
+ danger_zone? "Override existing #{color(file, :info)}?"
409
+ end
410
+
411
+ def wrong_args(quantity)
412
+ error "too #{quantity} arguments" do
413
+ say help
414
+ end
415
+ end
416
+
417
+ def endpoint_option
418
+ ""
419
+ end
420
+ end
421
+ end
422
+ end