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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +205 -4
- data/bin/rbytes +13 -0
- data/lib/rbytes.rb +3 -1
- data/lib/ruby_bytes/cli.rb +117 -0
- data/lib/ruby_bytes/compiler.rb +60 -0
- data/lib/ruby_bytes/publisher.rb +38 -0
- data/lib/ruby_bytes/test_case.rb +187 -0
- data/lib/ruby_bytes/thor.rb +458 -0
- data/lib/ruby_bytes/version.rb +5 -0
- data/templates/rbytes/core_ext.rb +21 -0
- data/templates/rbytes/rails_actions.rb +387 -0
- data/templates/rbytes/rails_application_stub.rb +21 -0
- data/templates/rbytes/rbytes.rb +29 -0
- metadata +27 -3
- data/lib/rbytes/version.rb +0 -5
@@ -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,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
|