class RubiGen::Commands::Create
Create is the premier generator command. It copies files, creates directories, renders templates, and more.
Constants
- SYNONYM_LOOKUP_URI
Public Instance Methods
Check whether the given class names are already taken. In the future, expand to check other namespaces such as the rest of the user's app.
# File lib/rubigen/commands.rb, line 180 def class_collisions(*class_names) class_names.flatten.each do |class_name| # Convert to string to allow symbol arguments. class_name = class_name.to_s # Skip empty strings. next if class_name.strip.empty? # Split the class from its module nesting. nesting = class_name.split('::') name = nesting.pop # Extract the last Module in the nesting. last = nesting.inject(Object) { |last, nest| break unless last.const_defined?(nest) last.const_get(nest) } # If the last Module exists, check whether the given # class exists and raise a collision if so. if last and last.const_defined?(name.camelize) raise_class_collision(class_name) end end end
# File lib/rubigen/commands.rb, line 335 def complex_template(relative_source, relative_destination, template_options = {}) options = template_options.dup options[:assigns] ||= {} options[:assigns]['template_for_inclusion'] = render_template_part(template_options) template(relative_source, relative_destination, options) end
Create a directory including any missing parent directories. Always skips directories which exist.
# File lib/rubigen/commands.rb, line 344 def directory(relative_path) path = destination_path(relative_path) if File.exist?(path) logger.exists relative_path else logger.create relative_path unless options[:pretend] FileUtils.mkdir_p(path) # git doesn't require adding the paths, adding the files later will # automatically do a path add. # Subversion doesn't do path adds, so we need to add # each directory individually. # So stack up the directory tree and add the paths to # subversion in order without recursion. if options[:svn] stack = [relative_path] until File.dirname(stack.last) == stack.last # dirname('.') == '.' stack.push File.dirname(stack.last) end stack.reverse_each do |rel_path| svn_path = destination_path(rel_path) system("svn add -N #{svn_path}") unless File.directory?(File.join(svn_path, '.svn')) end end end end end
Copy a file from source to destination with collision checking.
The file_options hash accepts :chmod and :shebang and :collision options. :chmod sets the permissions of the destination file:
file 'config/empty.log', 'log/test.log', :chmod => 0664
:shebang sets the #!/usr/bin/ruby line for scripts
file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby'
:collision sets the collision option only for the destination file:
file 'settings/server.yml', 'config/server.yml', :collision => :skip
Collisions are handled by checking whether the destination file exists and either skipping the file, forcing overwrite, or asking the user what to do.
# File lib/rubigen/commands.rb, line 219 def file(relative_source, relative_destination, file_options = {}, &block) # Determine full paths for source and destination files. source = source_path(relative_source) destination = destination_path(relative_destination) destination_exists = File.exist?(destination) # If source and destination are identical then we're done. if destination_exists and identical?(source, destination, &block) return logger.identical(relative_destination) end # Check for and resolve file collisions. if destination_exists # Make a choice whether to overwrite the file. :force and # :skip already have their mind made up, but give :ask a shot. choice = case (file_options[:collision] || options[:collision]).to_sym #|| :ask when :ask then force_file_collision?(relative_destination, source, destination, file_options, &block) when :force then :force when :skip then :skip else raise "Invalid collision option: #{options[:collision].inspect}" end # Take action based on our choice. Bail out if we chose to # skip the file; otherwise, log our transgression and continue. case choice when :force then logger.force(relative_destination) when :skip then return(logger.skip(relative_destination)) else raise "Invalid collision choice: #{choice}.inspect" end # File doesn't exist so log its unbesmirched creation. else logger.create relative_destination end # If we're pretending, back off now. return if options[:pretend] # Write destination file with optional shebang. Yield for content # if block given so templaters may render the source file. If a # shebang is requested, replace the existing shebang or insert a # new one. File.open(destination, 'wb') do |dest| dest.write render_file(source, file_options, &block) end # Optionally change permissions. if file_options[:chmod] FileUtils.chmod(file_options[:chmod], destination) end # Optionally add file to subversion or git system("svn add #{destination}") if options[:svn] end
# File lib/rubigen/commands.rb, line 275 def file_copy_each(files, path=nil, options = {}) path = path ? "#{path}/" : "" files.each do |file_name| file "#{path}#{file_name}", "#{path}#{file_name}", options end end
# File lib/rubigen/commands.rb, line 282 def folder(template_path, path=nil, options = {}) template_path = "/" if template_path.blank? source = source_path(template_path) files = Dir[source + '/*'].select { |file| File.file? file }.map { |file| file.sub(/^#{source}/,"") } files.each do |file_name| file "#{template_path}#{file_name}", "#{path}#{file_name}", options end system("git add -v #{relative_destination}") if options[:git] end
Checks if the source and the destination file are identical. If passed a block then the source file is a template that needs to first be evaluated before being compared to the destination.
# File lib/rubigen/commands.rb, line 295 def identical?(source, destination, &block) return false if File.directory? destination source = block_given? ? File.open(source) {|sf| yield(sf)} : IO.read(source) destination = IO.read(destination) source == destination end
When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template.
# File lib/rubigen/commands.rb, line 419 def migration_template(relative_source, relative_destination, template_options = {}) migration_directory relative_destination migration_file_name = template_options[:migration_file_name] || file_name raise "Another migration is already named #{migration_file_name}: #{existing_migrations(migration_file_name).first}" if migration_exists?(migration_file_name) template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options) end
Display a README.
# File lib/rubigen/commands.rb, line 374 def readme(*relative_sources) relative_sources.flatten.each do |relative_source| logger.readme relative_source stdout.puts File.read(source_path(relative_source)) unless options[:pretend] end end
# File lib/rubigen/commands.rb, line 426 def route_resources(*resources) resource_list = resources.map { |r| r.to_sym.inspect }.join(', ') sentinel = 'ActionController::Routing::Routes.draw do |map|' logger.route "map.resources #{resource_list}" unless options[:pretend] gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match| "#{match}\n map.resources #{resource_list}\n" end end end
Generate a file using an ERuby template. Looks up and evaluates a template by name and writes the result.
The ERB template uses explicit trim mode to best control the proliferation of whitespace in generated code. <%- trims leading whitespace; -%> trims trailing whitespace including one newline.
A hash of template options may be passed as the last argument. The options accepted by the file are accepted as well as :assigns, a hash of variable bindings. Example:
template 'foo', 'bar', :assigns => { :action => 'view' }
Template is implemented in terms of file. It calls file with a block which takes a file handle and returns its rendered contents.
# File lib/rubigen/commands.rb, line 316 def template(relative_source, relative_destination, template_options = {}) file(relative_source, relative_destination, template_options) do |file| # Evaluate any assignments in a temporary, throwaway binding. vars = template_options[:assigns] || {} b = binding vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b } # Render the source file with the temporary binding. ERB.new(file.read, nil, '-').result(b) end end
# File lib/rubigen/commands.rb, line 328 def template_copy_each(files, path = nil, options = {}) path = path ? "#{path}/" : "" files.each do |file_name| template "#{path}#{file_name}", "#{path}#{file_name.gsub(/\.erb$/,'')}", options end end
# File lib/rubigen/commands.rb, line 381 def write_manifest(relative_destination) files = ([relative_destination] + Dir["#{destination_root}/**/*"]) files.reject! { |file| File.directory?(file) } files.map! { |path| path.sub("#{destination_root}/","") } files = files.uniq.sort destination = destination_path(relative_destination) destination_exists = File.exists?(destination) # Check for and resolve file collisions. if destination_exists # Always recreate the Manifest (perhaps we need to give the option... like normal files) choice = :force logger.force(relative_destination) # File doesn't exist so log its unbesmirched creation. else logger.create relative_destination end # If we're pretending, back off now. return if options[:pretend] # Write destination file with optional shebang. Yield for content # if block given so templaters may render the source file. If a # shebang is requested, replace the existing shebang or insert a # new one. File.open(destination, 'wb') do |dest| dest.write files.join("\n") dest.write "\n" end # Optionally add file to subversion system("svn add #{destination}") if options[:svn] end
Private Instance Methods
Look up synonyms on WordNet. Thanks to Florian Gross (flgr).
# File lib/rubigen/commands.rb, line 475 def find_synonyms(word) require 'open-uri' require 'timeout' timeout(5) do open(SYNONYM_LOOKUP_URI % word) do |stream| # Grab words linked to dictionary entries as possible synonyms data = stream.read.gsub(" ", " ").scan(/<a href="webwn.*?">([\w ]*?)<\/a>/s).uniq end end rescue Exception return nil end
Raise a usage error with an informative WordNet suggestion. Thanks to Florian Gross (flgr).
# File lib/rubigen/commands.rb, line 458 def raise_class_collision(class_name) message = <<-end_message The name '#{class_name}' is either already used in your application or reserved. Please choose an alternative and run this generator again. end_message if suggest = find_synonyms(class_name) if suggest.any? message << "\n Suggestions: \n\n" message << suggest.join("\n") end end raise UsageError, message end
# File lib/rubigen/commands.rb, line 439 def render_file(path, options = {}) File.open(path, 'rb') do |file| if block_given? yield file else content = '' if shebang = options[:shebang] content << "#!#{shebang}\n" if line = file.gets content << "line\n" if line !~ /^#!/ end end content << file.read end end end