module Pineapples::Actions
Constants
- DEFAULT_COLOR
- FILE_WITH_RUBY_CODE
- TEMPLATE_EXTNAME
Attributes
Public Class Methods
# File lib/pineapples/actions.rb, line 35 def initialize(options) super @behaviour = options.behaviour || :invoke end
Public Instance Methods
Append text to a file. Since it depends on insert_into_file
, it's reversible.
Parameters¶ ↑
- path<String>
-
path of the file to be changed
- data<String>
-
the data to append to the file, can be also given as a block.
- config<Hash>
-
give :verbose => false to not log the status.
Example¶ ↑
append_to_file 'config/environments/test.rb', 'config.gem "rspec"' append_to_file 'config/environments/test.rb' do 'config.gem "rspec"' end
# File lib/pineapples/actions/insert_into_file.rb, line 24 def append_to_file(path, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} options.merge!(before: /\z/) insert_into_file(path, *(args << options), &block) end
Loads an external file and execute it in the instance binding.
Parameters¶ ↑
- path<String>
-
The path to the file to execute. Can be a web address or a relative path from the source root.
Examples¶ ↑
apply "http://gist.github.com/103208" apply "recipes/jquery.rb"
# File lib/pineapples/actions/apply.rb, line 15 def apply(path, options = {}) verbose = options.fetch(:verbose, verbose?) is_uri = path =~ %r{^https?\://} path = find_in_source_paths(path) if !is_uri color = options.fetch(:color, DEFAULT_COLOR) say_status(:apply, path, color, verbose) indent(verbose) do if is_uri contents = open(path, 'Accept' => 'application/x-thor-template') { |io| io.read } else contents = open(path) { |io| io.read } end instance_eval(contents, path) end end
# File lib/pineapples/actions/create_file.rb, line 28 def ask_file_collision(target) # rubocop:disable MethodLength return true if @always_force options = '[Ynaq]' question_string = "Overwrite #{target}?" options = ['Yes', 'No', 'Always', 'Quit'] colors = [:light_green, :light_red, :light_yellow, :light_red] options_with_color = options.map.with_index { |option, index| option.send(colors[index])} loop do answer_index = Ask.list(question_string, options_with_color, {clear: false, response: false}) answer = options[answer_index] case answer when 'Yes' return true when 'No' return false when 'Always' return @always_force = true when 'Quit' say 'Aborting' raise SystemExit end end end
# File lib/pineapples/actions/bundle.rb, line 3 def bundle(command, options = {}) command = "#{command.first.first} #{command.first.last}" if command.is_a?(Hash) say_status :bundle, "#{command}" _bundle_command = Gem.bin_path('bundler', 'bundle') require 'bundler' Bundler.with_clean_env do output = `"#{Gem.ruby}" "#{_bundle_command}" #{command} #{subcommands}` print output if !options[:quiet] end end
Changes the mode of the given file or directory.
Parameters¶ ↑
- mode<Integer>
-
the file mode
- path<String>
-
the name of the file to change mode
- options<Hash>
-
give :verbose => false to not log the status.
Example¶ ↑
chmod "script/server", 0755
# File lib/pineapples/actions/chmod.rb, line 14 def chmod(path, mode, options = {}) return unless behaviour == :invoke verbose = options.fetch(:verbose, verbose?) execute = !options.fetch(:pretend, pretend?) color = options.fetch(:color, DEFAULT_COLOR) full_path = File.join(app_root, path) relative_path = relative_to_app_root(full_path) say_status(:chmod, relative_path, color, verbose) FileUtils.chmod_R(mode, full_path) if execute end
# File lib/pineapples/actions/rails/new_hash_syntax_converter.rb, line 4 def convert_directory_to_new_hash_syntax(target, options = {}) recursive = options.delete(:recursive) || true target_fullpath = File.expand_path(target, app_root) target_fullpath = File.join(target_fullpath, '**') if recursive excluded_files = Array(options[:exclude]) exclude_pattern = options[:exclude_pattern] files = Dir.glob(target_fullpath, File::FNM_DOTMATCH) files.sort.each do |file| next if File.directory?(file) next if exclude_pattern && file.match(exclude_pattern) next if excluded_files.any? do |excluded_file| File.basename(excluded_file) == File.basename(file_source) end case file when /#{FILE_WITH_RUBY_CODE}$/ convert_file_to_new_hash_syntax(file) end end end
Converts file to new Ruby hash syntax, cause that is what I prefer Existing solutions on web usually use perl and shell, but we have power of Ruby and cool gsub_file
action at our disposal!
# File lib/pineapples/actions/rails/new_hash_syntax_converter.rb, line 31 def convert_file_to_new_hash_syntax(path) regex = /:(\w+)(\s{1})(\s*)=>/ gsub_file(path, regex, '\1:\3') end
Create a new file relative to the destination root with the given data, which is the return value of a block or a data string.
Parameters¶ ↑
- target<String>
-
the relative path to the destination root.
- content<String|NilClass>
-
the data to append to the file.
- options<Hash>
-
give :verbose => false to not log the status.
Examples¶ ↑
create_file "lib/fun_party.rb" do hostname = ask("What is the virtual hostname I should use?") "vhost.name = #{hostname}" end create_file "config/apache.conf", "your apache config"
# File lib/pineapples/actions/create_file.rb, line 22 def create_file(target, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} content = args.first action CreateFile.new(self, target, block || content, options) end
# File lib/pineapples/actions/inside.rb, line 7 def current_app_dir File.expand_path(File.join(*dir_stack), app_root) end
# File lib/pineapples/actions/inside.rb, line 3 def dir_stack @dir_stack ||= [] end
Copies recursively the files from source directory to root directory. If any of the files finishes with .tt, it's considered to be a template and is placed in the destination without the extension .tt. If any empty directory is found, it's copied and all .empty_directory files are ignored. If any file name is wrapped within % signs, the text within the % signs will be executed as a method and replaced with the returned value. Let's suppose a doc directory with the following files:
doc/ components/.empty_directory README rdoc.rb.tt %app_name%.rb
When invoked as:
directory "doc"
It will create a doc directory in the destination with the following files (assuming that the `app_name` method returns the value “blog”):
doc/ components/ README rdoc.rb blog.rb
Parameters¶ ↑
- source<String>
-
the relative path to the source root.
- target<String>
-
the relative path to the destination root.
- options<Hash>
-
give :verbose => false to not log the status.
If :recursive => false, does not look for paths recursively. If :mode => :preserve, preserve the file mode from the source. If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
Examples¶ ↑
directory "doc" directory "doc", "docs", :recursive => false
# File lib/pineapples/actions/directory.rb, line 43 def directory(source, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} target = args.first || source action Directory.new(self, source, target, options, &block) end
Creates an empty directory.
Parameters¶ ↑
- target<String>
-
the relative path to the app root.
- options<Hash>
-
give :verbose => false to not log the status.
Examples¶ ↑
empty_directory "doc"
# File lib/pineapples/actions/empty_directory.rb, line 13 def empty_directory(target, options = {}) action EmptyDirectory.new(self, target, options) end
# File lib/pineapples/actions/keep_file.rb, line 5 def empty_directory_with_keep_file(target, options = {}) empty_directory(target, options) keep_file(target) end
# File lib/pineapples/actions.rb, line 79 def find_in_source_paths(file) files_to_search = [file, file + TEMPLATE_EXTNAME] sources = source_paths_for_search sources.each do |source_path| files_to_search.each do |file| source_file = File.expand_path(file, source_path) return source_file if File.exist?(source_file) end end message = "Could not find #{file.inspect} in any of your source paths. " unless self.templates_root message << "Please set your generator instance template_root to PATH with the PATH containing your templates." end if sources.empty? message << "Currently you have no source paths." else message << "Your current source paths are: \n#{sources.join("\n")}" end raise Error, message end
Gets the content at the given address and places it at the given relative destination. If a block is given instead of destination, the content of the url is yielded and used as location.
Parameters¶ ↑
- source<String>
-
the address of the given content.
- target<String>
-
the relative path to the destination root.
- options<Hash>
-
give :verbose => false to not log the status.
Examples¶ ↑
get "http://gist.github.com/103208", "doc/README" get "http://gist.github.com/103208" do |content| content.split("\n").first end
# File lib/pineapples/actions/get.rb, line 22 def get(source, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} target = args.first source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ %r{^https?\://} render = open(source) { |input| input.binmode.read } target ||= if block block.arity == 1 ? block.call(render) : block.call else File.basename(source) end create_file(target, render, options) end
Run a command in git.
git :init git add: "this.file that.rb" git add: "onefile.rb", rm: "badfile.cxx"
# File lib/pineapples/actions/git.rb, line 8 def git(commands = {}) if commands.is_a?(Symbol) shell "git #{commands}" else commands.each { |cmd, options| shell "git #{cmd} #{options}" } end end
Run a regular expression replacement on a file.
Parameters¶ ↑
- path<String>
-
path of the file to be changed
- flag<Regexp|String>
-
the regexp or string to be replaced
- replacement<String>
-
the replacement, can be also given as a block
- config<Hash>
-
give :verbose => false to not log the status.
Example¶ ↑
gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' gsub_file 'README', /rake/, :green do |match| match << " no more. Use thor!" end
# File lib/pineapples/actions/gsub_file.rb, line 19 def gsub_file(path, flag, *args, &block) return unless behaviour == :invoke options = args.last.is_a?(Hash) ? args.pop : {} verbose = options.fetch(:verbose, verbose?) execute = options.fetch(:pretend, execute?) fullpath = File.expand_path(path, app_root) raise Error, "File #{path} doesn't exist!" if !File.exist?(fullpath) say_status :gsub, relative_to_app_root(path), :light_yellow, verbose if execute content = File.binread(fullpath) content.gsub!(flag, *args, &block) File.open(fullpath, 'wb') { |file| file.write(content) } end end
# File lib/pineapples/actions/inside.rb, line 53 def in_app_root(&block) Dir.chdir(app_root, &block) end
Convenience method to call inside in_root
# File lib/pineapples/actions/inside.rb, line 49 def in_root(options = {}) inside(:root, options) { yield } end
# File lib/pineapples/actions.rb, line 123 def indent(verbose = true, level_increment = 1) $terminal.indent_level += level_increment if verbose yield $terminal.indent_level -= level_increment if verbose end
# File lib/pineapples/actions/insert_into_file.rb, line 3 def insert_into_file(target, *args, &block) content = block_given? ? block : args.shift options = args.shift action InsertIntoFile.new(self, target, content, options) end
Do something in the root or on a provided subfolder. The full path to the directory is yielded to the block you provide. The path is set back to the previous path when the method exits.
Parameters¶ ↑
- dir<String>
-
the directory to move to, relative to the app root
- config<Hash>
-
give :verbose => true to log and use padding.
# File lib/pineapples/actions/inside.rb, line 19 def inside(dir = :root, options = {}, &block) verbose = options.fetch(:verbose, verbose?) color = options.fetch(:color, :light_green) status_dir = if dir == :root relative_to_app_root(app_root) else relative_to_app_root(File.join(current_app_dir, dir)) end say_status(:inside, status_dir, color, verbose) indent(verbose) do with_directory(dir) do |target_dir| if !File.exist?(target_dir) && !pretend? FileUtils.mkdir_p(target_dir) end if pretend? block.arity == 1 ? yield(target_dir) : yield else FileUtils.cd(target_dir) do block.arity == 1 ? yield(target_dir) : yield end end end end end
# File lib/pineapples/actions/keep_file.rb, line 10 def keep_file(target) create_file("#{target}/.keep") end
Injects text right after the class definition. Since it depends on insert_into_file
, it's reversible.
Parameters¶ ↑
- path<String>
-
path of the file to be changed
- klass<String|Class>
-
the class to be manipulated
- data<String>
-
the data to append to the class, can be also given as a block.
- config<Hash>
-
give :verbose => false to not log the status.
Examples¶ ↑
prepend_to_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n" prepend_to_class "app/controllers/application_controller.rb", ApplicationController do " filter_parameter :password\n" end
# File lib/pineapples/actions/prepend_to_class.rb, line 20 def prepend_to_class(path, klass, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} options.merge!(after: /class #{klass}\n|class #{klass} .*\n/) insert_into_file(path, *(args << options), &block) end
Prepend text to a file. Since it depends on insert_into_file
, it's reversible.
Parameters¶ ↑
- path<String>
-
path of the file to be changed
- content<String>
-
the content to prepend to the file, can be also given as a block.
- options<Hash>
-
give :verbose => false to not log the status.
Example¶ ↑
prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"' prepend_to_file 'config/environments/test.rb' do 'config.gem "rspec"' end
# File lib/pineapples/actions/insert_into_file.rb, line 46 def prepend_to_file(path, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} options.merge!(after: /\A/) insert_into_file(path, *(args << options), &block) end
dir argument must me absolute path
# File lib/pineapples/actions.rb, line 67 def relative_to_app_root(dir) @app_root_pathname ||= Pathname.new(app_root) path = Pathname.new(dir) path.relative_path_from(@app_root_pathname).to_s end
# File lib/pineapples/actions.rb, line 73 def relative_to_current_app_dir(dir) current_app_path = Pathname.new(current_app_dir) path = Pathname.new(dir) path.relative_path_from(current_app_path).to_s end
Removes a file at the given location.
Parameters¶ ↑
- path<String>
-
path of the file to be changed
- options<Hash>
-
give :verbose => false to not log the status.
Example¶ ↑
remove_file 'README' remove_file 'app/controllers/application_controller.rb'
# File lib/pineapples/actions/remove_file.rb, line 14 def remove_file(path, options = {}) return unless behaviour == :invoke path = File.expand_path(path, app_root) message = relative_to_app_root(path) verbose = options.fetch(:verbose, verbose?) color = options.fetch(:color, :light_red) execute = options[:pretend] || execute? say_status :remove, message, color, verbose ::FileUtils.rm_rf(path) if execute && File.exist?(path) end
Executes a ruby script (taking into account WIN32 platform quirks).
Parameters¶ ↑
- command<String>
-
the command to be executed.
- options<Hash>
-
give :verbose => false to not log the status.
# File lib/pineapples/actions/ruby.rb, line 10 def ruby(command, options = {}) return if behaviour != :inboke shell(command, options.merge(with: ruby_command)) end
# File lib/pineapples/actions.rb, line 105 def say_status(status, message, color = :light_green, log = true) return if !log spaces = ' ' * $terminal.indent_size * 2 status = status.to_s.rjust(12) status = status.colorize(color) output = "#{status}#{spaces}#{message}" say(output) end
# File lib/pineapples/actions.rb, line 118 def say_title(title) puts puts "== #{title} ==".light_yellow end
Executes a command returning the contents of the command.
Parameters¶ ↑
- command<String>
-
the command to be executed.
- options<Hash>
-
give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
to append an executable to command execution.
Example¶ ↑
inside 'vendor' do shell 'ln -s ~/edge rails' end
# File lib/pineapples/actions/shell.rb, line 16 def shell(command, options = {}) return if behaviour == :revoke verbose = options.fetch(:verbose, verbose?) execute = !options.fetch(:pretend, pretend?) executable = options[:with].to_s command = "#{executable} #{command}" if executable.present? say_status(:shell, command, DEFAULT_COLOR, verbose) if execute options[:capture] ? `#{command}` : system("#{command}") raise Error, "#{command} failed with status #{$CHILD_STATUS.exitstatus}." if not $CHILD_STATUS.success? end end
# File lib/pineapples/actions/shell.rb, line 41 def shell_with_app_gemset(command, options = {}) # we have to use PTY pseudo terminal to able to read shell subprocess output asynchronously. # Backticks and Kernel#system buffer output, and since bundle install takes forever to complete # it's not very user-friendly and program would seem to hang. # We just cd into project directory and invoke RVM in binary mode ('do' command) # TODO: add support for rbenv gemsets return if behaviour == :revoke verbose = options.fetch(:verbose, verbose?) execute = !options.fetch(:pretend, pretend?) full_command = "export DISABLE_SPRING=0; cd #{File.basename(app_root)}; rvm . do #{command}" say_status :shell, full_command, DEFAULT_COLOR, verbose if execute begin PTY.spawn(full_command) do |reader, writer, pid| loop do line = reader.gets break if !line puts line end Process.wait(pid) end rescue Errno::EIO => error nil rescue PTY::ChildExited => error puts 'The child process exited!' end end end
# File lib/pineapples/actions/shell.rb, line 34 def shell_with_clean_bundler_env(command, options = {}) return shell(command, options) if !defined?(Bundler) Bundler.with_clean_env do shell(command, options) end end
# File lib/pineapples/actions.rb, line 55 def source_paths @source_paths ||= [] end
# File lib/pineapples/actions.rb, line 59 def source_paths_for_search paths = [] paths += source_paths paths << templates_root if templates_root paths end
Gets an ERB template at the relative source, executes it and makes a copy at the relative destination. If the destination is not given it's assumed to be equal to the source removing .tt from the filename.
Parameters¶ ↑
- source<String>
-
the relative path to the source root.
- target<String>
-
the relative path to the destination root.
- options<Hash>
-
give :verbose => false to not log the status.
Examples¶ ↑
template "README", "doc/README" template "doc/README"
# File lib/pineapples/actions/template.rb, line 20 def template(source, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} target = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, '') source = File.expand_path(find_in_source_paths(source.to_s)) context = options.delete(:context) || binding #instance_eval('binding') create_file(target, nil, options) do template = File.binread(source) content = ERB.new(template, nil, '-').result(context) content = block.call(content) if block content end end
# File lib/pineapples/actions.rb, line 51 def templates_root @templates_root ||= File.join(__dir__, 'templates') end
Private Instance Methods
Return the path to the ruby interpreter taking into account multiple installations and windows extensions.
# File lib/pineapples/actions/ruby.rb, line 19 def ruby_command # rubocop:disable MethodLength @ruby_command ||= begin ruby_name = RbConfig::CONFIG['ruby_install_name'] ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name) ruby << RbConfig::CONFIG['EXEEXT'] # avoid using different name than ruby (on platforms supporting links) if ruby_name != 'ruby' && File.respond_to?(:readlink) begin alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby') alternate_ruby << RbConfig::CONFIG['EXEEXT'] # ruby is a symlink if File.symlink? alternate_ruby linked_ruby = File.readlink alternate_ruby # symlink points to 'ruby_install_name' ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby end rescue NotImplementedError # rubocop:disable HandleExceptions # just ignore on windows end end # escape string in case path to ruby executable contain spaces. ruby.sub!(/.*\s.*/m, '"\&"') ruby end end
helper method to abstract directory stack management in the inside
action
# File lib/pineapples/actions/inside.rb, line 60 def with_directory(dir) not_in_root = (dir != :root) dir_stack.push(dir) if not_in_root target_dir = not_in_root ? current_app_dir : app_root yield target_dir dir_stack.pop if not_in_root end