circle-cli 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84f8d9396d38131c1ac6a9a5fce0817658d5a8c3
4
- data.tar.gz: d6202342ace41bb63992d03b37d6d6a1f0ff0d17
3
+ metadata.gz: 8ec32b3c7353d7ccd09c6f94ff30f62860a529b2
4
+ data.tar.gz: 9bd7e701530d2cc98374ed6af4d918a0d7edf85f
5
5
  SHA512:
6
- metadata.gz: 2ce5fbfea58c54cc30105a1e07bf42a3cfa722d60789f89f7624631e3a606d380d57a9f0018efdf862cc79b74355127c33aaf521a04d9b55a3964981f63c564d
7
- data.tar.gz: 453f17a3fafb3c96eaa477f0cb8cd6a0ae935331fa100d408f33cd6dcc421d157daf794a17f823fd32d93ae818a6407ef6b5bdda2c88d661278c93d5b8e0ecc9
6
+ metadata.gz: c2e59afcad27182a8db025642bccb4781ae31099983f6ff4c7db216383c3b902463c1a101eb88fcd7a787bd6ae679534af30d0736ec005354a898b5036c617ae
7
+ data.tar.gz: 8575aeb713849ecb4c4692c14935d3efc02801918911f4f7c5cb0e0de04e1213b091553e8298d6ed8c468109fac5472d4a92e2ab47f37d16194631a888ac5690
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Jean Boussier
1
+ Copyright (c) 2014-2016 Derek Keith, Jean Boussier
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # Circle CLI
2
-
3
2
  Command line tools for Circle CI
4
3
 
4
+ [![Gem Version](https://badge.fury.io/rb/circle-cli.svg)](https://badge.fury.io/rb/circle-cli)
5
+ [![CircleCI Status](https://circleci.com/gh/circle-cli/circle-cli.svg?style=shield&circle-token=e24d4c43a7437111a6ee5915901017a8419ddbf4)](https://circleci.com/gh/circle-cli/circle-cli)
6
+ [![Dependency Status](https://gemnasium.com/codeurge/circle-cli.svg)](https://gemnasium.com/codeurge/circle-cli)
7
+ [![Code Climate](https://codeclimate.com/github/codeurge/circle-cli/badges/gpa.svg)](https://codeclimate.com/github/codeurge/circle-cli)
8
+ [![Test Coverage](https://codeclimate.com/github/codeurge/circle-cli/badges/coverage.svg)](https://codeclimate.com/github/codeurge/circle-cli/coverage)
9
+
10
+ ![circle-cli demo](https://cloud.githubusercontent.com/assets/306238/13765850/b410ea98-ea22-11e5-8cb9-4942d6071654.gif)
11
+
5
12
  ## Installation
6
13
 
7
14
  $ gem install circle-cli
@@ -14,36 +21,54 @@ Command line tools for Circle CI
14
21
  $ gem install circle-cli
15
22
  ```
16
23
 
17
- 2. Add your GitHub token
24
+ 2. Add your CircleCI token
18
25
 
19
26
  ```
20
- $ circle github-token <github-token>
27
+ $ circle token <your token>
21
28
  ```
22
29
 
23
- 3. Add your CircleCI token
30
+ ## Usage
31
+
32
+ - Get the CI status for the HEAD of your current branch
24
33
 
25
34
  ```
26
- $ circle token <circle-ci-token>
35
+ $ circle
27
36
  ```
28
-
29
- ## Usage
30
37
 
31
- - Get the CI status for the HEAD of your current branch
38
+ - Start a new CI build for the HEAD of your current branch
32
39
 
33
40
  ```
34
- $ circle status
41
+ $ circle build
42
+ ```
43
+
44
+ - Watch the most recent CI build for your current branch
45
+
35
46
  ```
36
-
47
+ $ circle watch
48
+ ```
49
+
50
+ - Cancel the most recent CI build for your current branch
51
+
52
+ ```
53
+ $ circle cancel
54
+ ```
55
+
37
56
  - Open the results page for the latest CI run
38
57
 
39
58
  ```
40
59
  $ circle open
41
60
  ```
42
61
 
62
+ - For additional help
63
+
64
+ ```
65
+ $ circle help
66
+ ```
67
+
43
68
  ## Contributing
44
69
 
45
- 1. Fork it ( http://github.com/byroot/circle-cli/fork )
46
- 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 1. Fork it ( http://github.com/codeurge/circle-cli/fork )
71
+ 2. Create your feature branch with your initials (`git checkout -b dsk/my-new-feature`)
47
72
  3. Commit your changes (`git commit -am 'Add some feature'`)
48
73
  4. Push to the branch (`git push origin my-new-feature`)
49
74
  5. Create new Pull Request
data/bin/circle CHANGED
@@ -1,9 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
-
6
- require 'optparse'
7
3
  require 'circle-cli'
8
-
9
4
  Circle::CLI.run(ARGV)
@@ -6,8 +6,8 @@ require 'circle/cli/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'circle-cli'
8
8
  spec.version = Circle::CLI::VERSION
9
- spec.authors = ['Jean Boussier']
10
- spec.email = ['jean.boussier@gmail.com']
9
+ spec.authors = ['Derek Keith', 'Jean Boussier']
10
+ spec.email = ['derek@codeurge.com', 'jean.boussier@gmail.com']
11
11
  spec.summary = %q{Command line tools for Circle CI}
12
12
  spec.description = %q{A bunch of commands useful to deal with Circle CI without leaving your terminal.}
13
13
  spec.homepage = ''
@@ -20,9 +20,14 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency 'circleci'
22
22
  spec.add_dependency 'rugged', '>= 0.23'
23
- spec.add_dependency 'octokit'
23
+ spec.add_dependency 'gitable'
24
+ spec.add_dependency 'launchy'
25
+ spec.add_dependency 'thor'
24
26
 
25
27
  spec.add_development_dependency 'bundler', '~> 1.5'
26
28
  spec.add_development_dependency 'rake'
27
29
  spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'vcr'
31
+ spec.add_development_dependency 'webmock'
32
+ spec.add_development_dependency 'simplecov'
28
33
  end
@@ -1,135 +1,11 @@
1
+ require 'thor'
1
2
  require 'circle/cli/version'
2
- require 'uri'
3
- require 'rugged'
4
- require 'octokit'
5
- require 'circleci'
3
+ require 'circle/cli/app'
6
4
 
7
5
  module Circle
8
- class CLI
9
-
10
- class << self
11
-
12
- def run(*args)
13
- new.dispatch!(*args)
14
- end
15
-
16
- end
17
-
18
- def initialize
19
- @options = {}
20
- @parser ||= OptionParser.new do |opts|
21
- # future options will go here
22
- end
23
- end
24
-
25
- def dispatch!(argv)
26
- @parser.parse!
27
- command, *args = argv
28
- command = 'status' unless command
29
-
30
- method = "run_#{command.gsub('-', '_')}"
31
- if respond_to?(method)
32
- send(method, *args)
33
- else
34
- abort!("Unknown command: #{command}")
35
- end
36
- end
37
-
38
- def run_status
39
- if last_status && last_status.state == 'success'
40
- puts last_status.state
41
- exit(0)
42
- elsif last_status
43
- puts last_status.state
44
- else
45
- puts 'unknown'
46
- end
47
-
48
- exit(1)
6
+ module CLI
7
+ def self.run(*args)
8
+ App.start(*args)
49
9
  end
50
-
51
- def run_open
52
- unless last_status
53
- puts 'No CI run found'
54
- end
55
- open(last_status.rels[:target].href)
56
- end
57
-
58
- def run_token(token=nil)
59
- if token
60
- repository.config['circleci.token'] = token
61
- else
62
- puts circle_token
63
- end
64
- end
65
-
66
- def run_github_token(token=nil)
67
- if token
68
- repository.config['github.token'] = token
69
- else
70
- puts github_token
71
- end
72
- end
73
-
74
- private
75
-
76
- def open(url)
77
- `open '#{url}'`
78
- end
79
-
80
- def last_status
81
- unless defined?(@last_status)
82
- @last_status = github_client.statuses(github_repo, head).first
83
- end
84
- @last_status
85
- end
86
-
87
- def github_repo
88
- if origin.url =~ %r{git@github.com:(\w+/[^.]*)\.git}
89
- $1
90
- else
91
- raise "Unsupported repo url format #{origin.url.inspect}" # TODO: support other formats, mainly https
92
- end
93
- end
94
-
95
- def origin
96
- @origin ||= repository.remotes.find { |r| r.name == 'origin' }
97
- end
98
-
99
- def head
100
- repository.head.target_id
101
- end
102
-
103
- def repository
104
- @repository ||= Rugged::Repository.new('.') # TODO: allow to be called from a subdirectory
105
- end
106
-
107
- def github_client
108
- @github_client ||= Octokit::Client.new(access_token: github_token)
109
- end
110
-
111
- def configure_circle_ci_client
112
- CircleCi.configure do |config|
113
- config.token = circle_token
114
- end
115
- end
116
-
117
- def github_token # TODO: github-token should probably be in a global config
118
- repository.config['github.token'] || abort!(%{Missing GitHub token.
119
- You can create one here: https://github.com/settings/tokens/new
120
- And add it with the following command: $ circle github-token YOUR_TOKEN})
121
- end
122
-
123
- def circle_token
124
- repository.config['circleci.token'] || abort!(%{Missing CircleCI token.
125
- You can create one here: https://circleci.com/account/api
126
- And add it with the following command: $ circle token YOUR_TOKEN})
127
- end
128
-
129
- def abort!(message, code=1)
130
- STDERR.puts(message)
131
- exit(code)
132
- end
133
-
134
10
  end
135
11
  end
@@ -0,0 +1,192 @@
1
+ require 'launchy'
2
+ require 'circle/cli/repo'
3
+ require 'circle/cli/project'
4
+
5
+ module Circle
6
+ module CLI
7
+ class App < Thor
8
+ CIRCLE_URL = 'https://circleci.com/account/api'
9
+
10
+ LOGIN_HELP = <<-EOMSG
11
+ 1. Press [enter], and you'll be taken CircleCI.
12
+ 2. Enter a name for your new token.
13
+ 3. Click 'Create new token'.
14
+ 4. Come back to your prompt and paste in your new token.
15
+ 5. Press enter to complete the process.
16
+ EOMSG
17
+
18
+ NO_TOKEN_MESSAGE = <<-EOMSG
19
+ CircleCI token hasn't been configured. Run the following command to login:
20
+
21
+ $ circle login
22
+ EOMSG
23
+
24
+ default_task :status
25
+ class_option :repo, default: '.', desc: 'path to repo'
26
+
27
+ desc 'status', 'show CircleCI build result'
28
+ method_option :branch, desc: 'branch name'
29
+ def status
30
+ validate_repo!
31
+ validate_latest!
32
+ display_status
33
+ end
34
+
35
+ desc 'watch', 'watch your build'
36
+ method_option :branch, desc: 'branch name'
37
+ method_option :poll, default: 5, desc: 'polling frequency', type: :numeric
38
+ def watch
39
+ validate_repo!
40
+ validate_latest!
41
+ watching -> { project.latest.preload } do
42
+ display_status
43
+ end
44
+ end
45
+
46
+ desc 'overview', 'list recent builds and their statuses for all branches'
47
+ method_option :watch, desc: 'watch the list of builds'
48
+ method_option :poll, default: 5, desc: 'polling frequency', type: :numeric
49
+ def overview
50
+ validate_repo!
51
+ abort! 'No recent builds.' if project.recent_builds.empty?
52
+ show_overview = -> { display_builds(project.recent_builds) }
53
+
54
+ if options[:watch]
55
+ watching(-> { project.recent_builds }, &show_overview)
56
+ else
57
+ show_overview
58
+ end
59
+ end
60
+
61
+ desc 'open', 'open CircleCI build'
62
+ method_option :branch, desc: 'branch name'
63
+ def open
64
+ validate_repo!
65
+ validate_latest!
66
+ Launchy.open latest[:build_url]
67
+ end
68
+
69
+ desc 'build', 'trigger a build on circle ci'
70
+ method_option :branch, desc: 'branch name'
71
+ def build
72
+ validate_repo!
73
+ project.build!
74
+ say "A build has been triggered.\n\n", :green
75
+ invoke :watch
76
+ end
77
+
78
+ desc 'cancel', 'cancel most recent build'
79
+ method_option :branch, desc: 'branch name'
80
+ def cancel
81
+ validate_repo!
82
+ validate_latest!
83
+ latest.cancel! unless latest.finished?
84
+ invoke :status
85
+ say "\nThe build has been cancelled.", :red unless latest.finished?
86
+ end
87
+
88
+ desc 'token', 'view or edit CircleCI token'
89
+ def token(value = nil)
90
+ if value
91
+ repo.circle_token = value
92
+ elsif value = repo.circle_token
93
+ say value
94
+ else
95
+ say NO_TOKEN_MESSAGE, :yellow
96
+ end
97
+ end
98
+
99
+ desc 'login', 'login to Circle CI'
100
+ def login
101
+ say LOGIN_HELP, :yellow
102
+ ask set_color("\nPress [enter] to open CircleCI", :blue)
103
+ Launchy.open(CIRCLE_URL)
104
+ value = ask set_color('Enter your token:', :blue)
105
+ repo.circle_token = value
106
+ say "\nYour token has been set to '#{value}'.", :green
107
+ end
108
+
109
+ private
110
+
111
+ def repo
112
+ @repo ||= Repo.new(options)
113
+ end
114
+
115
+ def project
116
+ @project ||= Project.new(repo)
117
+ end
118
+
119
+ def latest
120
+ project.latest
121
+ end
122
+
123
+ def validate_repo!
124
+ abort! "Unsupported repo url format #{repo.uri}" unless repo.uri.github?
125
+ abort! NO_TOKEN_MESSAGE unless repo.circle_token
126
+ end
127
+
128
+ def validate_latest!
129
+ abort! 'No CircleCI builds found.' unless project.latest
130
+ end
131
+
132
+ def abort!(message)
133
+ abort set_color(message, :red)
134
+ end
135
+
136
+ def watching(preloader)
137
+ loop do
138
+ yield
139
+ sleep options[:poll]
140
+ project.clear_cache!
141
+ preloader.call
142
+ system('clear') || system('cls')
143
+ end
144
+ rescue Interrupt
145
+ exit 0
146
+ end
147
+
148
+ def display(description, value, color)
149
+ status = set_color description.ljust(15), :bold
150
+ result = set_color value.to_s, color
151
+ say "#{status} #{result}"
152
+ end
153
+
154
+ def display_status
155
+ say "#{latest[:subject]}\n\n", :cyan if latest[:subject]
156
+ display 'Build status', latest.status, latest.color
157
+ display 'Started at', latest.formatted_start_time, latest.color
158
+ display 'Finished at', latest.formatted_stop_time, latest.color
159
+ display 'Compare', latest[:compare], latest.color if latest[:compare]
160
+ display_steps latest.steps unless latest.steps.empty?
161
+ display_failures latest.failing_tests unless latest.failing_tests.empty?
162
+ exit 1 if latest.failed?
163
+ exit 0 if latest.finished?
164
+ end
165
+
166
+ def display_steps(steps)
167
+ say "\nSteps:", :bold
168
+
169
+ print_table steps.map { |step|
170
+ [set_color(step[:name], step.color), step.duration]
171
+ }
172
+ end
173
+
174
+ def display_failures(failures)
175
+ say "\nFailing specs:", :bold
176
+
177
+ print_table failures.map { |spec|
178
+ [set_color(spec['file'], :red), spec['name']]
179
+ }
180
+ end
181
+
182
+ def display_builds(builds)
183
+ print_table builds.map { |build|
184
+ branch = set_color(build[:branch], :bold)
185
+ status = set_color(build.status, build.color)
186
+ started = build.formatted_start_time
187
+ [branch, status, build.subject, started]
188
+ }
189
+ end
190
+ end
191
+ end
192
+ end