raygun 1.0.0 → 1.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.
@@ -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