raygun 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,43 +1,47 @@
1
- require 'optparse'
2
- require 'ostruct'
3
- require 'fileutils'
4
- require 'securerandom'
5
- require 'net/http'
6
- require 'json'
7
- require 'colorize'
8
-
9
- require_relative 'version'
1
+ require "optparse"
2
+ require "ostruct"
3
+ require "fileutils"
4
+ require "securerandom"
5
+ require "net/http"
6
+ require "open-uri"
7
+ require "json"
8
+ require "colorize"
9
+
10
+ require_relative "version"
11
+ require_relative "template_repo"
10
12
 
11
13
  module Raygun
12
14
  class Runner
13
- CARBONFIVE_REPO = 'carbonfive/raygun-rails'
15
+ CARBONFIVE_REPO = "carbonfive/raygun-rails"
16
+ C5_CONVENTIONS_REPO = "carbonfive/c5-conventions"
14
17
 
15
- attr_accessor :target_dir, :app_dir, :app_name, :dash_name, :snake_name, :camel_name, :title_name, :prototype_repo,
18
+ attr_accessor :target_dir, :app_dir, :app_name, :dash_name, :snake_name,
19
+ :camel_name, :title_name, :prototype_repo,
16
20
  :current_ruby_version, :current_ruby_patch_level
17
21
 
18
22
  def initialize(target_dir, prototype_repo)
19
23
  @target_dir = target_dir
20
24
  @app_dir = File.expand_path(target_dir.strip.to_s)
21
- @app_name = File.basename(app_dir).gsub(/\s+/, '-')
22
- @dash_name = app_name.gsub('_', '-')
23
- @snake_name = app_name.gsub('-', '_')
25
+ @app_name = File.basename(app_dir).gsub(/\s+/, "-")
26
+ @dash_name = app_name.tr("_", "-")
27
+ @snake_name = app_name.tr("-", "_")
24
28
  @camel_name = camelize(snake_name)
25
29
  @title_name = titleize(snake_name)
26
30
  @prototype_repo = prototype_repo
27
31
 
28
32
  @current_ruby_version = RUBY_VERSION
29
- @current_ruby_patch_level = if RUBY_VERSION < '2.1.0' # Ruby adopted semver starting with 2.1.0.
30
- "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
33
+ @current_ruby_patch_level = if RUBY_VERSION < "2.1.0" # Ruby adopted semver starting with 2.1.0.
34
+ "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
31
35
  else
32
- "#{RUBY_VERSION}"
36
+ RUBY_VERSION.to_s
33
37
  end
34
38
  end
35
39
 
36
40
  def check_target
37
- unless Dir["#{@app_dir}/*"].empty?
38
- puts "Misfire! The target directory isn't empty... aim elsewhere."
39
- exit 1
40
- end
41
+ return if Dir["#{@app_dir}/*"].empty?
42
+
43
+ puts "Misfire! The target directory isn't empty... aim elsewhere.".colorize(:light_red)
44
+ exit 1
41
45
  end
42
46
 
43
47
  def fetch_prototype
@@ -45,26 +49,26 @@ module Raygun
45
49
  $stdout.flush
46
50
 
47
51
  # Check if we can connect, or fail gracefully and use the latest cached version.
48
- latest_tag_obj = fetch_latest_tag(prototype_repo)
49
- latest_tag = latest_tag_obj['name']
50
- tarball_url = latest_tag_obj['tarball_url']
52
+ repo = TemplateRepo.new(prototype_repo)
53
+ name = repo.name
54
+ tarball_url = repo.tarball
55
+ sha = repo.sha
51
56
 
52
- print " #{latest_tag}.".colorize(:white)
57
+ print " #{name}.".colorize(:white)
53
58
  $stdout.flush
54
59
 
55
60
  cached_prototypes_dir = File.join(Dir.home, ".raygun")
56
- @prototype = "#{cached_prototypes_dir}/#{prototype_repo.sub('/', '--')}-#{latest_tag}.tar.gz"
61
+ @prototype = "#{cached_prototypes_dir}/#{name.gsub("/", "--")}-#{sha}.tar.gz"
57
62
 
58
63
  # Do we already have the tarball cached under ~/.raygun?
59
- if File.exists?(@prototype)
64
+ if File.exist?(@prototype)
60
65
  puts " Using cached version.".colorize(:yellow)
61
66
  else
62
67
  print " Downloading...".colorize(:yellow)
63
68
  $stdout.flush
64
69
 
65
70
  # Download the tarball and install in the cache.
66
- Dir.mkdir(cached_prototypes_dir, 0755) unless Dir.exists?(cached_prototypes_dir)
67
-
71
+ Dir.mkdir(cached_prototypes_dir, 0o755) unless Dir.exist?(cached_prototypes_dir)
68
72
  shell "curl -s -L #{tarball_url} -o #{@prototype}"
69
73
  puts " done!".colorize(:yellow)
70
74
  end
@@ -72,50 +76,56 @@ module Raygun
72
76
 
73
77
  def check_raygun_version
74
78
  required_raygun_version =
75
- %x{tar xfz #{@prototype} --include "*.raygun-version" -O 2> /dev/null}.chomp ||
76
- ::Raygun::VERSION
77
-
78
- if Gem::Version.new(required_raygun_version) > Gem::Version.new(::Raygun::VERSION)
79
- puts ""
80
- print "Hold up!".colorize(:red)
81
- print " This version of the raygun gem (".colorize(:light_red)
82
- print "#{::Raygun::VERSION})".colorize(:white)
83
- print " is too old to generate this application (needs ".colorize(:light_red)
84
- print "#{required_raygun_version}".colorize(:white)
85
- puts " or newer).".colorize(:light_red)
86
- puts ""
87
- print "Please update the gem by running ".colorize(:light_red)
88
- print "gem update raygun".colorize(:white)
89
- puts ", and try again. Thanks!".colorize(:light_red)
90
- puts ""
91
- exit 1
92
- end
79
+ `tar xfz #{@prototype} --include "*.raygun-version" -O 2> /dev/null`.chomp ||
80
+ ::Raygun::VERSION
81
+ return unless Gem::Version.new(required_raygun_version) > Gem::Version.new(::Raygun::VERSION)
82
+
83
+ puts ""
84
+ print "Hold up!".colorize(:red)
85
+ print " This version of the raygun gem (".colorize(:light_red)
86
+ print "#{::Raygun::VERSION})".colorize(:white)
87
+ print " is too old to generate this application (needs ".colorize(:light_red)
88
+ print required_raygun_version.to_s.colorize(:white)
89
+ puts " or newer).".colorize(:light_red)
90
+ puts ""
91
+ print "Please update the gem by running ".colorize(:light_red)
92
+ print "gem update raygun".colorize(:white)
93
+ puts ", and try again. Thanks!".colorize(:light_red)
94
+ puts ""
95
+ exit 1
93
96
  end
94
97
 
95
98
  def copy_prototype
96
99
  FileUtils.mkdir_p(app_dir)
97
100
 
98
- shell "tar xfz #{@prototype} -C #{app_dir}"
101
+ shell "tar xfz #{@prototype} -C \"#{app_dir}\""
99
102
 
100
103
  # Github includes an extra directory layer in the tag tarball.
101
104
  extraneous_dir = Dir.glob("#{app_dir}/*").first
102
105
  dirs_to_move = Dir.glob("#{extraneous_dir}/*", File::FNM_DOTMATCH)
103
- .reject { |d| %w{. ..}.include?(File.basename(d)) }
106
+ .reject { |d| %w[. ..].include?(File.basename(d)) }
104
107
 
105
108
  FileUtils.mv dirs_to_move, app_dir
106
109
  FileUtils.remove_dir extraneous_dir
110
+
111
+ fetch_rubocop_file if @prototype_repo == CARBONFIVE_REPO
107
112
  end
108
113
 
109
114
  def rename_new_app
110
115
  Dir.chdir(app_dir) do
111
116
  {
112
- 'AppPrototype' => camel_name,
113
- 'app-prototype' => dash_name,
114
- 'app_prototype' => snake_name,
115
- 'App Prototype' => title_name
117
+ "AppPrototype" => camel_name,
118
+ "app-prototype" => dash_name,
119
+ "app_prototype" => snake_name,
120
+ "App Prototype" => title_name
116
121
  }.each do |proto_name, new_name|
117
122
  shell "find . -type f -print | xargs #{sed_i} 's/#{proto_name}/#{new_name}/g'"
118
123
  end
124
+
125
+ %w[d f].each do |find_type|
126
+ shell "find . -depth -type #{find_type} -name '*app_prototype*' " \
127
+ "-exec bash -c 'mv $0 ${0/app_prototype/#{snake_name}}' {} \\;"
128
+ end
119
129
  end
120
130
  end
121
131
 
@@ -144,26 +154,33 @@ module Raygun
144
154
  def initialize_git
145
155
  Dir.chdir(app_dir) do
146
156
  shell "git init"
157
+ shell "git checkout -q -b main"
147
158
  shell "git add -A ."
148
159
  shell "git commit -m 'Raygun-zapped skeleton.'"
149
160
  end
150
161
  end
151
162
 
163
+ # rubocop:disable Metrics/AbcSize
152
164
  def print_plan
153
- puts ' ____ '.colorize(:light_yellow)
165
+ puts " ____ ".colorize(:light_yellow)
154
166
  puts ' / __ \____ ___ ______ ___ ______ '.colorize(:light_yellow)
155
167
  puts ' / /_/ / __ `/ / / / __ `/ / / / __ \ '.colorize(:light_yellow)
156
- puts ' / _, _/ /_/ / /_/ / /_/ / /_/ / / / / '.colorize(:light_yellow)
168
+ puts " / _, _/ /_/ / /_/ / /_/ / /_/ / / / / ".colorize(:light_yellow)
157
169
  puts ' /_/ |_|\__,_/\__, /\__, /\__,_/_/ /_/ '.colorize(:light_yellow)
158
- puts ' /____//____/ '.colorize(:light_yellow)
170
+ puts " /____//____/ ".colorize(:light_yellow)
159
171
  puts
160
- puts "Raygun will create new app in directory:".colorize(:yellow) + " #{target_dir}".colorize(:yellow) + "...".colorize(:yellow)
172
+ puts "Raygun will create new app in directory:".colorize(:yellow) +
173
+ " #{target_dir}".colorize(:yellow) + "...".colorize(:yellow)
161
174
  puts
162
- puts "-".colorize(:blue) + " Application Name:".colorize(:light_blue) + " #{title_name}".colorize(:light_reen)
163
- puts "-".colorize(:blue) + " Project Template:".colorize(:light_blue) + " #{prototype_repo}".colorize(:light_reen)
164
- puts "-".colorize(:blue) + " Ruby Version: ".colorize(:light_blue) + " #{@current_ruby_patch_level}".colorize(:light_reen)
175
+ puts "-".colorize(:blue) + " Application Name:".colorize(:light_blue) +
176
+ " #{title_name}".colorize(:light_reen)
177
+ puts "-".colorize(:blue) + " Project Template:".colorize(:light_blue) +
178
+ " #{prototype_repo}".colorize(:light_reen)
179
+ puts "-".colorize(:blue) + " Ruby Version: ".colorize(:light_blue) +
180
+ " #{@current_ruby_patch_level}".colorize(:light_reen)
165
181
  puts
166
182
  end
183
+ # rubocop:enable Metrics/AbcSize
167
184
 
168
185
  def print_next_steps
169
186
  if @prototype_repo == CARBONFIVE_REPO
@@ -172,29 +189,30 @@ module Raygun
172
189
  print_next_steps_for_custom_repo
173
190
  end
174
191
  end
175
-
192
+
193
+ # rubocop:disable Metrics/AbcSize
176
194
  def print_next_steps_carbon_five
177
195
  puts ""
178
196
  puts "Zap! Your application is ready. Next steps...".colorize(:yellow)
179
197
  puts ""
180
- puts "# Install updated dependencies".colorize(:light_green)
198
+ puts "# Install updated dependencies and prepare the database".colorize(:light_green)
181
199
  puts "$".colorize(:blue) + " cd #{target_dir}".colorize(:light_blue)
182
- puts "$".colorize(:blue) + " gem install bundler".colorize(:light_blue)
183
- puts "$".colorize(:blue) + " bundle".colorize(:light_blue)
184
- puts ""
185
- puts "# Prepare the database: schema and reference / sample data".colorize(:light_green)
186
- puts "$".colorize(:blue) + " rake db:setup db:sample_data".colorize(:light_blue)
200
+ puts "$".colorize(:blue) + " bin/setup".colorize(:light_blue)
187
201
  puts ""
188
202
  puts "# Run the specs (they should all pass)".colorize(:light_green)
189
- puts "$".colorize(:blue) + " rake".colorize(:light_blue)
203
+ puts "$".colorize(:blue) + " bin/rake".colorize(:light_blue)
190
204
  puts ""
191
205
  puts "# Run the app and check things out".colorize(:light_green)
192
- puts "$".colorize(:blue) + " foreman start".colorize(:light_blue)
206
+ puts "$".colorize(:blue) + " heroku local".colorize(:light_blue)
193
207
  puts "$".colorize(:blue) + " open http://localhost:3000".colorize(:light_blue)
194
208
  puts ""
209
+ puts "# For some suggested next steps, check out the raygun README".colorize(:light_green)
210
+ puts "$".colorize(:blue) + " open https://github.com/carbonfive/raygun/#next-steps".colorize(:light_blue)
211
+ puts ""
195
212
  puts "Enjoy your Carbon Five flavored Rails application!".colorize(:yellow)
196
213
  end
197
-
214
+ # rubocop:enable Metrics/AbcSize
215
+
198
216
  def print_next_steps_for_custom_repo
199
217
  puts ""
200
218
  puts "Zap! Your application is ready.".colorize(:yellow)
@@ -204,111 +222,104 @@ module Raygun
204
222
 
205
223
  protected
206
224
 
207
- # Fetch the tags for the repo (e.g. 'carbonfive/raygun-rails') and return the latest as JSON.
208
- def fetch_latest_tag(repo)
209
- url = "https://api.github.com/repos/#{repo}/tags"
210
- uri = URI.parse(url)
211
- http = Net::HTTP.new(uri.host, uri.port)
212
- http.use_ssl = true
213
- request = Net::HTTP::Get.new(URI.encode(url))
214
-
215
- response = http.request(request)
216
-
217
- unless response.code == "200"
218
- puts ""
219
- print "Whoops - need to try again!".colorize(:red)
220
- puts ""
221
- print "We could not find (".colorize(:light_red)
222
- print "#{repo}".colorize(:white)
223
- print ") on github.".colorize(:light_red)
224
- puts ""
225
- print "The response from github was a (".colorize(:light_red)
226
- print "#{response.code}".colorize(:white)
227
- puts ") which I'm sure you can fix right up!".colorize(:light_red)
228
- puts ""
229
- exit 1
230
- end
225
+ def fetch_rubocop_file
226
+ sha = shell("git ls-remote https://github.com/#{C5_CONVENTIONS_REPO} master") || ""
227
+ sha = sha.slice(0..6)
231
228
 
232
- result = JSON.parse(response.body).first
233
- unless result
234
- puts ""
235
- print "Whoops - need to try again!".colorize(:red)
236
- puts ""
237
- print "We could not find any tags in the repo (".colorize(:light_red)
238
- print "#{repo}".colorize(:white)
239
- print ") on github.".colorize(:light_red)
240
- puts ""
241
- print "Raygun uses the 'largest' tag in a repository, where tags are sorted alphanumerically.".colorize(:light_red)
242
- puts ""
243
- print "E.g., tag 'v.0.10.0' > 'v.0.9.9' and 'x' > 'a'.".colorize(:light_red)
244
- print ""
245
- puts ""
246
- exit 1
229
+ rubocop_file = "https://raw.githubusercontent.com/#{C5_CONVENTIONS_REPO}/master/rubocop/rubocop.yml"
230
+ begin
231
+ rubocop_contents = URI.open(rubocop_file)
232
+ IO.write("#{@app_dir}/.rubocop.yml", <<~RUBOCOP_YML)
233
+ # Sourced from #{C5_CONVENTIONS_REPO} @ #{sha}
234
+ #
235
+ # If you make changes to this file, consider opening
236
+ # a PR to backport them to the c5-conventions repo:
237
+ # https://github.com/#{C5_CONVENTIONS_REPO}/blob/master/rubocop/rubocop.yml
238
+
239
+ #{rubocop_contents.string}
240
+ RUBOCOP_YML
241
+ rescue Errno::ENOENT, OpenURI::HTTPError => e
242
+ puts ""
243
+ puts "Failed to find the CarbonFive conventions rubocop file at #{rubocop_file}".colorize(:light_red)
244
+ puts "Error: #{e}".colorize(:light_red)
245
+ puts "You'll have to manage you're own `.rubocop.yml` setup".colorize(:light_red)
247
246
  end
248
-
249
- result
250
247
  end
251
248
 
252
249
  def camelize(string)
253
250
  result = string.sub(/^[a-z\d]*/) { $&.capitalize }
254
- result.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }
251
+ result.gsub(%r{(?:_|(/))([a-z\d]*)}) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
255
252
  end
256
253
 
257
254
  def titleize(underscored_string)
258
- result = underscored_string.gsub(/_/, ' ')
259
- result.gsub(/\b('?[a-z])/) { $1.capitalize }
255
+ result = underscored_string.tr("_", " ")
256
+ result.gsub(/\b('?[a-z])/) { Regexp.last_match(1).capitalize }
260
257
  end
261
258
 
262
259
  # Distinguish BSD vs GNU sed with the --version flag (only present in GNU sed).
263
260
  def sed_i
264
- @sed_format ||= begin
265
- %x{sed --version &> /dev/null}
266
- $?.success? ? "sed -i" : "sed -i ''"
267
- end
261
+ @sed_i ||= begin
262
+ `sed --version &> /dev/null`
263
+ $?.success? ? "sed -i" : "sed -i ''"
264
+ end
268
265
  end
269
266
 
270
267
  # Run a shell command and raise an exception if it fails.
271
268
  def shell(command)
272
- %x{#{command}}
269
+ output = `#{command}`
273
270
  raise "#{command} failed with status #{$?.exitstatus}." unless $?.success?
274
- end
275
271
 
276
- def self.parse(args)
277
- raygun = nil
278
-
279
- options = OpenStruct.new
280
- options.target_dir = nil
281
- options.prototype_repo = CARBONFIVE_REPO
282
-
283
- parser = OptionParser.new do |opts|
284
- opts.banner = "Usage: raygun [options] NEW_APP_DIRECTORY"
272
+ output
273
+ end
285
274
 
286
- opts.on('-h', '--help', "Show raygun usage") do
287
- usage_and_exit(opts)
288
- end
289
- opts.on('-p', '--prototype [github_repo]', "Prototype github repo (e.g. carbonfive/raygun-rails).") do |prototype|
290
- options.prototype_repo = prototype
275
+ class << self
276
+ # rubocop:disable Metrics/MethodLength
277
+ def parse(_args)
278
+ raygun = nil
279
+
280
+ options = OpenStruct.new
281
+ options.target_dir = nil
282
+ options.prototype_repo = CARBONFIVE_REPO
283
+
284
+ parser = OptionParser.new do |opts|
285
+ opts.banner = "Usage: raygun [options] NEW_APP_DIRECTORY"
286
+
287
+ opts.on("-h", "--help", "Show raygun usage") do
288
+ usage_and_exit(opts)
289
+ end
290
+ opts.on(
291
+ "-p",
292
+ "--prototype [github_repo]",
293
+ "Prototype github repo (e.g. carbonfive/raygun-rails)."
294
+ ) do |prototype|
295
+ options.prototype_repo = prototype
296
+ end
297
+
298
+ opts.on("-v", "--version", "Print the version number") do
299
+ puts Raygun::VERSION
300
+ exit 1
301
+ end
291
302
  end
292
- end
293
303
 
294
- begin
295
- parser.parse!
296
- options.target_dir = ARGV.first
304
+ begin
305
+ parser.parse!
306
+ options.target_dir = ARGV.first
297
307
 
298
- raise OptionParser::InvalidOption if options.target_dir.nil?
308
+ raise OptionParser::InvalidOption if options.target_dir.nil?
299
309
 
300
- raygun = Raygun::Runner.new(options.target_dir, options.prototype_repo)
310
+ raygun = Raygun::Runner.new(options.target_dir, options.prototype_repo)
311
+ rescue OptionParser::InvalidOption
312
+ usage_and_exit(parser)
313
+ end
301
314
 
302
- rescue OptionParser::InvalidOption
303
- usage_and_exit(parser)
315
+ raygun
304
316
  end
317
+ # rubocop:enable Metrics/MethodLength
305
318
 
306
- raygun
307
- end
308
-
309
- def self.usage_and_exit(parser)
310
- puts parser
311
- exit 1
319
+ def usage_and_exit(parser)
320
+ puts parser
321
+ exit 1
322
+ end
312
323
  end
313
324
  end
314
325
  end
@@ -0,0 +1,78 @@
1
+ module Raygun
2
+ class TemplateRepo
3
+ attr_reader :name, :branch, :tarball, :sha
4
+
5
+ def initialize(repo)
6
+ @name, @branch = repo.split("#").map(&:strip)
7
+ fetch
8
+ end
9
+
10
+ private
11
+
12
+ def fetch
13
+ return if @branch && @sha
14
+
15
+ @branch ? fetch_branches : fetch_tags
16
+ end
17
+
18
+ def handle_github_error(response)
19
+ puts ""
20
+ print "Whoops - need to try again!".colorize(:red)
21
+ puts ""
22
+ print "We could not find (".colorize(:light_red)
23
+ print name.to_s.colorize(:white)
24
+ print "##{branch}".colorize(:white) if @branch
25
+ print ") on github.".colorize(:light_red)
26
+ puts ""
27
+ print "The response from github was a (".colorize(:light_red)
28
+ print response.code.to_s.colorize(:white)
29
+ puts ") which I'm sure you can fix right up!".colorize(:light_red)
30
+ puts ""
31
+ exit 1
32
+ end
33
+
34
+ def handle_missing_tag_error
35
+ puts ""
36
+ print "Whoops - need to try again!".colorize(:red)
37
+ puts ""
38
+ print "We could not find any tags in the repo (".colorize(:light_red)
39
+ print name.to_s.colorize(:white)
40
+ print ") on github.".colorize(:light_red)
41
+ puts ""
42
+ print "Raygun uses the 'largest' tag in a repository, " \
43
+ "where tags are sorted alphanumerically.".colorize(:light_red)
44
+ puts ""
45
+ print "E.g., tag 'v.0.10.0' > 'v.0.9.9' and 'x' > 'a'.".colorize(:light_red)
46
+ print ""
47
+ puts ""
48
+ exit 1
49
+ end
50
+
51
+ def fetch_branches
52
+ response = http_get("https://api.github.com/repos/#{name}/branches/#{branch}")
53
+ handle_github_error(response) unless response.code == "200"
54
+ result = JSON.parse(response.body)
55
+ @sha = result["commit"]["sha"]
56
+ @tarball = result["_links"]["html"].gsub(%r{/tree/#{branch}}, "/archive/#{branch}.tar.gz")
57
+ end
58
+
59
+ def fetch_tags
60
+ response = http_get("https://api.github.com/repos/#{name}/tags")
61
+ handle_github_error(response) unless response.code == "200"
62
+
63
+ result = JSON.parse(response.body).first
64
+ handle_missing_tag_error unless result
65
+ @sha = result["commit"]["sha"]
66
+ @tarball = result["tarball_url"]
67
+ end
68
+
69
+ def http_get(url)
70
+ uri = URI(url)
71
+ http = Net::HTTP.new(uri.host, uri.port)
72
+ http.use_ssl = true
73
+ request = Net::HTTP::Get.new(uri)
74
+
75
+ http.request(request)
76
+ end
77
+ end
78
+ end