circle-cli 0.0.2 → 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.
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