rbytes 0.0.1 → 0.1.1

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.
@@ -0,0 +1,458 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rbytes < Thor
4
+ desc "template", "Load and run generator from RailsBytes"
5
+ def template(url)
6
+ puts "Run template from: #{url}"
7
+
8
+ # require gems typically used in templates
9
+ require "bundler"
10
+
11
+ Base.new.apply(url)
12
+ end
13
+
14
+ # Rails core extensions can be used in templates.
15
+ # Let's try to load ActiveSupport first and fallback to our custom set of extensions otherwise.
16
+ begin
17
+ require "active_support"
18
+ require "active_support/core_ext"
19
+ rescue LoadError
20
+ # TODO: Add more extensions
21
+ class ::String
22
+ def parameterize
23
+ gsub("::", "/").gsub(/([a-z])([A-Z])/, '\1-\2').downcase
24
+ end
25
+
26
+ def underscore
27
+ gsub("::", "/").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
28
+ end
29
+ end
30
+
31
+ class ::Object
32
+ alias empty? nil?
33
+ end
34
+ end
35
+
36
+
37
+ class Base < Thor::Group
38
+ # Stub `Rails.application` to have a correct application name
39
+ module Rails
40
+ class << self
41
+ def application
42
+ return unless File.exist?("config/application.rb")
43
+
44
+ File.read("config/application.rb").then do |contents|
45
+ contents.match(/^module (\S+)\s*$/)
46
+ end.then do |matches|
47
+ next unless matches
48
+
49
+ Module.new.then do |mod|
50
+ Object.const_set(matches[1], mod)
51
+ app_class = Class.new
52
+ mod.const_set(:Application, app_class)
53
+ app_class.new
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # Copied from https://github.com/rails/rails/blob/38275763257221e381cd4e37958ce1413fd0433c/railties/lib/rails/generators/actions.rb
61
+ module Rails
62
+ module Actions
63
+ # Adds an entry into +Gemfile+ for the supplied gem.
64
+ #
65
+ # gem "rspec", group: :test
66
+ # gem "technoweenie-restful-authentication", lib: "restful-authentication", source: "http://gems.github.com/"
67
+ # gem "rails", "3.0", git: "https://github.com/rails/rails"
68
+ # gem "RedCloth", ">= 4.1.0", "< 4.2.0"
69
+ # gem "rspec", comment: "Put this comment above the gem declaration"
70
+ def gem(*args, **options)
71
+ name, *versions = args
72
+
73
+ # Set the message to be shown in logs. Uses the git repo if one is given,
74
+ # otherwise use name (version).
75
+ parts, message = [quote(name)], name.dup
76
+
77
+ # Output a comment above the gem declaration.
78
+ comment = options.delete(:comment)
79
+
80
+ if versions = versions.any? ? versions : options.delete(:version)
81
+ _versions = Array(versions)
82
+ _versions.each do |version|
83
+ parts << quote(version)
84
+ end
85
+ message << " (#{_versions.join(", ")})"
86
+ end
87
+ message = options[:git] if options[:git]
88
+
89
+ log :gemfile, message
90
+
91
+ parts << quote(options) unless options.empty?
92
+
93
+ in_root do
94
+ str = []
95
+ if comment
96
+ comment.each_line do |comment_line|
97
+ str << indentation
98
+ str << "# #{comment_line}"
99
+ end
100
+ str << "\n"
101
+ end
102
+ str << indentation
103
+ str << "gem #{parts.join(", ")}"
104
+ append_file_with_newline "Gemfile", str.join, verbose: false
105
+ end
106
+ end
107
+
108
+ # Wraps gem entries inside a group.
109
+ #
110
+ # gem_group :development, :test do
111
+ # gem "rspec-rails"
112
+ # end
113
+ def gem_group(*names, **options, &block)
114
+ str = names.map(&:inspect)
115
+ str << quote(options) unless options.empty?
116
+ str = str.join(", ")
117
+ log :gemfile, "group #{str}"
118
+
119
+ in_root do
120
+ append_file_with_newline "Gemfile", "\ngroup #{str} do", force: true
121
+ with_indentation(&block)
122
+ append_file_with_newline "Gemfile", "end", force: true
123
+ end
124
+ end
125
+
126
+ def github(repo, options = {}, &block)
127
+ str = [quote(repo)]
128
+ str << quote(options) unless options.empty?
129
+ str = str.join(", ")
130
+ log :github, "github #{str}"
131
+
132
+ in_root do
133
+ if @indentation.zero?
134
+ append_file_with_newline "Gemfile", "\ngithub #{str} do", force: true
135
+ else
136
+ append_file_with_newline "Gemfile", "#{indentation}github #{str} do", force: true
137
+ end
138
+ with_indentation(&block)
139
+ append_file_with_newline "Gemfile", "#{indentation}end", force: true
140
+ end
141
+ end
142
+
143
+ # Add the given source to +Gemfile+
144
+ #
145
+ # If block is given, gem entries in block are wrapped into the source group.
146
+ #
147
+ # add_source "http://gems.github.com/"
148
+ #
149
+ # add_source "http://gems.github.com/" do
150
+ # gem "rspec-rails"
151
+ # end
152
+ def add_source(source, options = {}, &block)
153
+ log :source, source
154
+
155
+ in_root do
156
+ if block
157
+ append_file_with_newline "Gemfile", "\nsource #{quote(source)} do", force: true
158
+ with_indentation(&block)
159
+ append_file_with_newline "Gemfile", "end", force: true
160
+ else
161
+ prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false
162
+ end
163
+ end
164
+ end
165
+
166
+ # Adds a line inside the Application class for <tt>config/application.rb</tt>.
167
+ #
168
+ # If options <tt>:env</tt> is specified, the line is appended to the corresponding
169
+ # file in <tt>config/environments</tt>.
170
+ #
171
+ # environment do
172
+ # "config.asset_host = 'cdn.provider.com'"
173
+ # end
174
+ #
175
+ # environment(nil, env: "development") do
176
+ # "config.asset_host = 'localhost:3000'"
177
+ # end
178
+ def environment(data = nil, options = {})
179
+ sentinel = "class Application < Rails::Application\n"
180
+ env_file_sentinel = "Rails.application.configure do\n"
181
+ data ||= yield if block_given?
182
+
183
+ in_root do
184
+ if options[:env].nil?
185
+ inject_into_file "config/application.rb", optimize_indentation(data, 4), after: sentinel, verbose: false
186
+ else
187
+ Array(options[:env]).each do |env|
188
+ inject_into_file "config/environments/#{env}.rb", optimize_indentation(data, 2), after: env_file_sentinel, verbose: false
189
+ end
190
+ end
191
+ end
192
+ end
193
+ alias_method :application, :environment
194
+
195
+ # Run a command in git.
196
+ #
197
+ # git :init
198
+ # git add: "this.file that.rb"
199
+ # git add: "onefile.rb", rm: "badfile.cxx"
200
+ def git(commands = {})
201
+ if commands.is_a?(Symbol)
202
+ run "git #{commands}"
203
+ else
204
+ commands.each do |cmd, options|
205
+ run "git #{cmd} #{options}"
206
+ end
207
+ end
208
+ end
209
+
210
+ # Create a new file in the <tt>vendor/</tt> directory. Code can be specified
211
+ # in a block or a data string can be given.
212
+ #
213
+ # vendor("sekrit.rb") do
214
+ # sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
215
+ # "salt = '#{sekrit_salt}'"
216
+ # end
217
+ #
218
+ # vendor("foreign.rb", "# Foreign code is fun")
219
+ def vendor(filename, data = nil)
220
+ log :vendor, filename
221
+ data ||= yield if block_given?
222
+ create_file("vendor/#{filename}", optimize_indentation(data), verbose: false)
223
+ end
224
+
225
+ # Create a new file in the <tt>lib/</tt> directory. Code can be specified
226
+ # in a block or a data string can be given.
227
+ #
228
+ # lib("crypto.rb") do
229
+ # "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
230
+ # end
231
+ #
232
+ # lib("foreign.rb", "# Foreign code is fun")
233
+ def lib(filename, data = nil)
234
+ log :lib, filename
235
+ data ||= yield if block_given?
236
+ create_file("lib/#{filename}", optimize_indentation(data), verbose: false)
237
+ end
238
+
239
+ # Create a new +Rakefile+ with the provided code (either in a block or a string).
240
+ #
241
+ # rakefile("bootstrap.rake") do
242
+ # project = ask("What is the UNIX name of your project?")
243
+ #
244
+ # <<-TASK
245
+ # namespace :#{project} do
246
+ # task :bootstrap do
247
+ # puts "I like boots!"
248
+ # end
249
+ # end
250
+ # TASK
251
+ # end
252
+ #
253
+ # rakefile('seed.rake', 'puts "Planting seeds"')
254
+ def rakefile(filename, data = nil)
255
+ log :rakefile, filename
256
+ data ||= yield if block_given?
257
+ create_file("lib/tasks/#{filename}", optimize_indentation(data), verbose: false)
258
+ end
259
+
260
+ # Create a new initializer with the provided code (either in a block or a string).
261
+ #
262
+ # initializer("globals.rb") do
263
+ # data = ""
264
+ #
265
+ # ['MY_WORK', 'ADMINS', 'BEST_COMPANY_EVAR'].each do |const|
266
+ # data << "#{const} = :entp\n"
267
+ # end
268
+ #
269
+ # data
270
+ # end
271
+ #
272
+ # initializer("api.rb", "API_KEY = '123456'")
273
+ def initializer(filename, data = nil)
274
+ log :initializer, filename
275
+ data ||= yield if block_given?
276
+ create_file("config/initializers/#{filename}", optimize_indentation(data), verbose: false)
277
+ end
278
+
279
+ # Generate something using a generator from Rails or a plugin.
280
+ # The second parameter is the argument string that is passed to
281
+ # the generator or an Array that is joined.
282
+ #
283
+ # generate(:authenticated, "user session")
284
+ def generate(what, *args)
285
+ log :generate, what
286
+
287
+ options = args.extract_options!
288
+ options[:abort_on_failure] = !options[:inline]
289
+
290
+ rails_command "generate #{what} #{args.join(" ")}", options
291
+ end
292
+
293
+ # Runs the supplied rake task (invoked with 'rake ...')
294
+ #
295
+ # rake("db:migrate")
296
+ # rake("db:migrate", env: "production")
297
+ # rake("gems:install", sudo: true)
298
+ # rake("gems:install", capture: true)
299
+ def rake(command, options = {})
300
+ execute_command :rake, command, options
301
+ end
302
+
303
+ # Runs the supplied rake task (invoked with 'rails ...')
304
+ #
305
+ # rails_command("db:migrate")
306
+ # rails_command("db:migrate", env: "production")
307
+ # rails_command("gems:install", sudo: true)
308
+ # rails_command("gems:install", capture: true)
309
+ def rails_command(command, options = {})
310
+ if options[:inline]
311
+ log :rails, command
312
+ command, *args = Shellwords.split(command)
313
+ in_root do
314
+ silence_warnings do
315
+ ::Rails::Command.invoke(command, args, **options)
316
+ end
317
+ end
318
+ else
319
+ execute_command :rails, command, options
320
+ end
321
+ end
322
+
323
+ # Make an entry in Rails routing file <tt>config/routes.rb</tt>
324
+ #
325
+ # route "root 'welcome#index'"
326
+ # route "root 'admin#index'", namespace: :admin
327
+ def route(routing_code, namespace: nil)
328
+ namespace = Array(namespace)
329
+ namespace_pattern = route_namespace_pattern(namespace)
330
+ routing_code = namespace.reverse.reduce(routing_code) do |code, name|
331
+ "namespace :#{name} do\n#{rebase_indentation(code, 2)}end"
332
+ end
333
+
334
+ log :route, routing_code
335
+
336
+ in_root do
337
+ if namespace_match = match_file("config/routes.rb", namespace_pattern)
338
+ base_indent, *, existing_block_indent = namespace_match.captures.compact.map(&:length)
339
+ existing_line_pattern = /^ {,#{existing_block_indent}}\S.+\n?/
340
+ routing_code = rebase_indentation(routing_code, base_indent + 2).gsub(existing_line_pattern, "")
341
+ namespace_pattern = /#{Regexp.escape namespace_match.to_s}/
342
+ end
343
+
344
+ inject_into_file "config/routes.rb", routing_code, after: namespace_pattern, verbose: false, force: false
345
+
346
+ if behavior == :revoke && namespace.any? && namespace_match
347
+ empty_block_pattern = /(#{namespace_pattern})((?:\s*end\n){1,#{namespace.size}})/
348
+ gsub_file "config/routes.rb", empty_block_pattern, verbose: false, force: true do |matched|
349
+ beginning, ending = empty_block_pattern.match(matched).captures
350
+ ending.sub!(/\A\s*end\n/, "") while !ending.empty? && beginning.sub!(/^ *namespace .+ do\n\s*\z/, "")
351
+ beginning + ending
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ # Reads the given file at the source root and prints it in the console.
358
+ #
359
+ # readme "README"
360
+ def readme(path)
361
+ log File.read(find_in_source_paths(path))
362
+ end
363
+
364
+ private
365
+
366
+ # Define log for backwards compatibility. If just one argument is sent,
367
+ # invoke say, otherwise invoke say_status. Differently from say and
368
+ # similarly to say_status, this method respects the quiet? option given.
369
+ def log(*args) # :doc:
370
+ if args.size == 1
371
+ say args.first.to_s unless options.quiet?
372
+ else
373
+ args << ((behavior == :invoke) ? :green : :red)
374
+ say_status(*args)
375
+ end
376
+ end
377
+
378
+ # Runs the supplied command using either "rake ..." or "rails ..."
379
+ # based on the executor parameter provided.
380
+ def execute_command(executor, command, options = {}) # :doc:
381
+ log executor, command
382
+ sudo = (options[:sudo] && !Gem.win_platform?) ? "sudo " : ""
383
+ config = {
384
+ env: {"RAILS_ENV" => (options[:env] || ENV["RAILS_ENV"] || "development")},
385
+ verbose: false,
386
+ capture: options[:capture],
387
+ abort_on_failure: options[:abort_on_failure]
388
+ }
389
+
390
+ in_root { run("#{sudo}#{Shellwords.escape Gem.ruby} bin/#{executor} #{command}", config) }
391
+ end
392
+
393
+ # Always returns value in double quotes.
394
+ def quote(value) # :doc:
395
+ if value.respond_to? :each_pair
396
+ return value.map do |k, v|
397
+ "#{k}: #{quote(v)}"
398
+ end.join(", ")
399
+ end
400
+ return value.inspect unless value.is_a? String
401
+
402
+ "\"#{value.tr("'", '"')}\""
403
+ end
404
+
405
+ # Returns optimized string with indentation
406
+ def optimize_indentation(value, amount = 0) # :doc:
407
+ return "#{value}\n" unless value.is_a?(String)
408
+ "#{value.strip_heredoc.indent(amount).chomp}\n"
409
+ end
410
+ alias_method :rebase_indentation, :optimize_indentation
411
+
412
+ # Indent the +Gemfile+ to the depth of @indentation
413
+ def indentation # :doc:
414
+ " " * @indentation
415
+ end
416
+
417
+ # Manage +Gemfile+ indentation for a DSL action block
418
+ def with_indentation(&block) # :doc:
419
+ @indentation += 1
420
+ instance_eval(&block)
421
+ ensure
422
+ @indentation -= 1
423
+ end
424
+
425
+ # Append string to a file with a newline if necessary
426
+ def append_file_with_newline(path, str, options = {})
427
+ gsub_file path, /\n?\z/, options do |match|
428
+ match.end_with?("\n") ? "" : "\n#{str}\n"
429
+ end
430
+ end
431
+
432
+ def match_file(path, pattern)
433
+ File.read(path).match(pattern) if File.exist?(path)
434
+ end
435
+
436
+ def route_namespace_pattern(namespace)
437
+ namespace.each_with_index.reverse_each.reduce(nil) do |pattern, (name, i)|
438
+ cummulative_margin = "\\#{i + 1}[ ]{2}"
439
+ blank_or_indented_line = "^[ ]*\n|^#{cummulative_margin}.*\n"
440
+ "(?:(?:#{blank_or_indented_line})*?^(#{cummulative_margin})namespace :#{name} do\n#{pattern})?"
441
+ end.then do |pattern|
442
+ /^( *).+\.routes\.draw do *\n#{pattern}/
443
+ end
444
+ end
445
+ end
446
+ end
447
+
448
+
449
+ include Thor::Actions
450
+ include Rails::Actions
451
+
452
+ # Custom methods defined on AppGenerator
453
+ # https://github.com/rails/rails/blob/38275763257221e381cd4e37958ce1413fd0433c/railties/lib/rails/generators/rails/app/app_generator.rb#L558
454
+ def file(*args, &block)
455
+ create_file(*args, &block)
456
+ end
457
+ end
458
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyBytes # :nodoc:
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,21 @@
1
+ # Rails core extensions can be used in templates.
2
+ # Let's try to load ActiveSupport first and fallback to our custom set of extensions otherwise.
3
+ begin
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+ rescue LoadError
7
+ # TODO: Add more extensions
8
+ class ::String
9
+ def parameterize
10
+ gsub("::", "/").gsub(/([a-z])([A-Z])/, '\1-\2').downcase
11
+ end
12
+
13
+ def underscore
14
+ gsub("::", "/").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
15
+ end
16
+ end
17
+
18
+ class ::Object
19
+ alias empty? nil?
20
+ end
21
+ end