module Bundler::Thor::Actions

Constants

WARNINGS

Injects the given content into a file. Different from gsub_file, this method is reversible.

Parameters

destination<String>

Relative path to the destination root

data<String>

Data to add to the file. Can be given as a block.

config<Hash>

give :verbose => false to not log the status and the flag for injection (:after or :before) or :force => true for insert two or more times the same content.

Examples

insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"

insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
  gems = ask "Which gems would you like to add?"
  gems.split(" ").map{ |gem| "  config.gem :#{gem}" }.join("\n")
end

Attributes

behavior[RW]
output_buffer[RW]

Public Class Methods

new(args = [], options = {}, config = {}) click to toggle source

Extends initializer to add more configuration options.

Configuration

behavior<Symbol>

The actions default behavior. Can be :invoke or :revoke. It also accepts :force, :skip and :pretend to set the behavior and the respective option.

destination_root<String>

The root directory needed for some actions.

Calls superclass method
# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 74
def initialize(args = [], options = {}, config = {})
  self.behavior = case config[:behavior].to_s
  when "force", "skip"
    _cleanup_options_and_set(options, config[:behavior])
    :invoke
  when "revoke"
    :revoke
  else
    :invoke
  end

  super
  self.destination_root = config[:destination_root]
end

Public Instance Methods

add_file(destination, *args, &block)
Alias for: create_file
append_file(path, *args, &block)
Alias for: append_to_file
append_to_file(path, *args, &block) click to toggle source

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/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 195
def append_to_file(path, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  config[:before] = /\z/
  insert_into_file(path, *(args << config), &block)
end
Also aliased as: append_file
apply(path, config = {}) click to toggle source

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/bundler/vendor/thor/lib/thor/actions.rb, line 214
def apply(path, config = {})
  verbose = config.fetch(:verbose, true)
  is_uri  = path =~ %r{^https?\://}
  path    = find_in_source_paths(path) unless is_uri

  say_status :apply, path, verbose
  shell.padding += 1 if verbose

  contents = if is_uri
    require "open-uri"
    open(path, "Accept" => "application/x-thor-template", &:read)
  else
    open(path, &:read)
  end

  instance_eval(contents, path)
  shell.padding -= 1 if verbose
end
chmod(path, mode, config = {}) click to toggle source

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

config<Hash>

give :verbose => false to not log the status.

Example

chmod "script/server", 0755
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 148
def chmod(path, mode, config = {})
  return unless behavior == :invoke
  path = File.expand_path(path, destination_root)
  say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
  unless options[:pretend]
    require "fileutils"
    FileUtils.chmod_R(mode, path)
  end
end
comment_lines(path, flag, *args) click to toggle source

Comment all lines matching a given regex. It will leave the space which existed before the beginning of the line in tact and will insert a single space after the comment hash.

Parameters

path<String>

path of the file to be changed

flag<Regexp|String>

the regexp or string used to decide which lines to comment

config<Hash>

give :verbose => false to not log the status.

Example

comment_lines 'config/initializers/session_store.rb', /cookie_store/
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 310
def comment_lines(path, flag, *args)
  flag = flag.respond_to?(:source) ? flag.source : flag

  gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args)
end
copy_file(source, *args) { |content| ... } click to toggle source

Examples

copy_file "README", "doc/README"

copy_file "doc/README"
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 21
def copy_file(source, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  destination = args.first || source
  source = File.expand_path(find_in_source_paths(source.to_s))

  resulting_destination = create_file destination, nil, config do
    content = File.binread(source)
    content = yield(content) if block
    content
  end
  if config[:mode] == :preserve
    mode = File.stat(source).mode
    chmod(resulting_destination, mode, config)
  end
end
create_file(destination, *args, &block) click to toggle source

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

destination<String>

the relative path to the destination root.

data<String|NilClass>

the data to append to the file.

config<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/bundler/vendor/thor/lib/thor/actions/create_file.rb, line 22
def create_file(destination, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  data = args.first
  action CreateFile.new(self, destination, block || data.to_s, config)
end
Also aliased as: add_file
destination_root() click to toggle source

Returns the root for this thor class (also aliased as destination root).

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 101
def destination_root
  @destination_stack.last
end
destination_root=(root) click to toggle source

Sets the root for this thor class. Relatives path are added to the directory where the script was invoked and expanded.

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 108
def destination_root=(root)
  @destination_stack ||= []
  @destination_stack[0] = File.expand_path(root || "")
end
directory(source, *args, &block) click to toggle source

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

Encoded path note: Since Bundler::Thor internals use Object#respond_to? to check if it can expand %something%, this `something` should be a public method in the class calling directory. If a method is private, Bundler::Thor stack raises PrivateMethodEncodedError.

Parameters

source<String>

the relative path to the source root.

destination<String>

the relative path to the destination root.

config<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/bundler/vendor/thor/lib/thor/actions/directory.rb, line 49
def directory(source, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  destination = args.first || source
  action Directory.new(self, source, destination || source, config, &block)
end
empty_directory(destination, config = {}) click to toggle source

Creates an empty directory.

Parameters

destination<String>

the relative path to the destination root.

config<Hash>

give :verbose => false to not log the status.

Examples

empty_directory "doc"
# File lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb, line 13
def empty_directory(destination, config = {})
  action EmptyDirectory.new(self, destination, config)
end
find_in_source_paths(file) click to toggle source

Receives a file or directory and search for it in the source paths.

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 135
def find_in_source_paths(file)
  possible_files = [file, file + TEMPLATE_EXTNAME]
  relative_root = relative_to_original_destination_root(destination_root, false)

  source_paths.each do |source|
    possible_files.each do |f|
      source_file = File.expand_path(f, File.join(source, relative_root))
      return source_file if File.exist?(source_file)
    end
  end

  message = "Could not find #{file.inspect} in any of your source paths. ".dup

  unless self.class.source_root
    message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
  end

  message << if source_paths.empty?
               "Currently you have no source paths."
             else
               "Your current source paths are: \n#{source_paths.join("\n")}"
             end

  raise Error, message
end
get(source, *args) { |render| ... } click to toggle source

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.

get relies on open-uri, so passing application user input would provide a command injection attack vector.

Parameters

source<String>

the address of the given content.

destination<String>

the relative path to the destination root.

config<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/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 79
def get(source, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  destination = args.first

  render = if source =~ %r{^https?\://}
    require "open-uri"
    URI.send(:open, source) { |input| input.binmode.read }
  else
    source = File.expand_path(find_in_source_paths(source.to_s))
    open(source) { |input| input.binmode.read }
  end

  destination ||= if block_given?
    block.arity == 1 ? yield(render) : yield
  else
    File.basename(source)
  end

  create_file destination, render, config
end
gsub_file(path, flag, *args, &block) click to toggle source

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/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 264
def gsub_file(path, flag, *args, &block)
  return unless behavior == :invoke
  config = args.last.is_a?(Hash) ? args.pop : {}

  path = File.expand_path(path, destination_root)
  say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)

  unless options[:pretend]
    content = File.binread(path)
    content.gsub!(flag, *args, &block)
    File.open(path, "wb") { |file| file.write(content) }
  end
end
in_root() { || ... } click to toggle source

Goes to the root and execute the given block.

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 198
def in_root
  inside(@destination_stack.first) { yield }
end
inject_into_class(path, klass, *args, &block) click to toggle source

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

inject_into_class "app/controllers/application_controller.rb", ApplicationController, "  filter_parameter :password\n"

inject_into_class "app/controllers/application_controller.rb", ApplicationController do
  "  filter_parameter :password\n"
end
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 219
def inject_into_class(path, klass, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  config[:after] = /class #{klass}\n|class #{klass} .*\n/
  insert_into_file(path, *(args << config), &block)
end
inject_into_module(path, module_name, *args, &block) click to toggle source

Injects text right after the module definition. Since it depends on insert_into_file, it's reversible.

Parameters

path<String>

path of the file to be changed

module_name<String|Class>

the module 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

inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, "  def help; 'help'; end\n"

inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
  "  def help; 'help'; end\n"
end
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 242
def inject_into_module(path, module_name, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  config[:after] = /module #{module_name}\n|module #{module_name} .*\n/
  insert_into_file(path, *(args << config), &block)
end
insert_into_file(destination, *args, &block) click to toggle source
# File lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb, line 26
  def insert_into_file(destination, *args, &block)
    data = block_given? ? block : args.shift

    config = args.shift || {}
    config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)

    action InjectIntoFile.new(self, destination, data, config)
  end
  alias_method :inject_into_file, :insert_into_file

  class InjectIntoFile < EmptyDirectory #:nodoc:
    attr_reader :replacement, :flag, :behavior

    def initialize(base, destination, data, config)
      super(base, destination, {:verbose => true}.merge(config))

      @behavior, @flag = if @config.key?(:after)
        [:after, @config.delete(:after)]
      else
        [:before, @config.delete(:before)]
      end

      @replacement = data.is_a?(Proc) ? data.call : data
      @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
    end

    def invoke!
      content = if @behavior == :after
        '\0' + replacement
      else
        replacement + '\0'
      end

      if exists?
        if replace!(/#{flag}/, content, config[:force])
          say_status(:invoke)
        else
          say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red)
        end
      else
        unless pretend?
          raise Bundler::Thor::Error, "The file #{ destination } does not appear to exist"
        end
      end
    end

    def revoke!
      say_status :revoke

      regexp = if @behavior == :after
        content = '\1\2'
        /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
      else
        content = '\2\3'
        /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
      end

      replace!(regexp, content, true)
    end

  protected

    def say_status(behavior, warning: nil, color: nil)
      status = if behavior == :invoke
        if flag == /\A/
          :prepend
        elsif flag == /\z/
          :append
        else
          :insert
        end
      elsif warning
        warning
      else
        :subtract
      end

      super(status, (color || config[:verbose]))
    end

    # Adds the content to the file.
    #
    def replace!(regexp, string, force)
      return if pretend?
      content = File.read(destination)
      if force || !content.include?(replacement)
        success = content.gsub!(regexp, string)

        File.open(destination, "wb") { |file| file.write(content) }
        success
      end
    end
  end
end
inside(dir = "", config = {}) { |destination_root| ... } click to toggle source

Do something in the root or on a provided subfolder. If a relative path is given it's referenced from the current root. The full path 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.

config<Hash>

give :verbose => true to log and use padding.

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 170
def inside(dir = "", config = {}, &block)
  verbose = config.fetch(:verbose, false)
  pretend = options[:pretend]

  say_status :inside, dir, verbose
  shell.padding += 1 if verbose
  @destination_stack.push File.expand_path(dir, destination_root)

  # If the directory doesnt exist and we're not pretending
  if !File.exist?(destination_root) && !pretend
    require "fileutils"
    FileUtils.mkdir_p(destination_root)
  end

  if pretend
    # In pretend mode, just yield down to the block
    block.arity == 1 ? yield(destination_root) : yield
  else
    require "fileutils"
    FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
  end

  @destination_stack.pop
  shell.padding -= 1 if verbose
end
prepend_file(path, *args, &block)
Alias for: prepend_to_file
prepend_to_file(path, *args, &block) click to toggle source

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

data<String>

the data to prepend to the file, can be also given as a block.

config<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/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 173
def prepend_to_file(path, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  config[:after] = /\A/
  insert_into_file(path, *(args << config), &block)
end
Also aliased as: prepend_file
relative_to_original_destination_root(path, remove_dot = true) click to toggle source

Returns the given path relative to the absolute root (ie, root where the script started).

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 116
def relative_to_original_destination_root(path, remove_dot = true)
  root = @destination_stack[0]
  if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size])
    path = path.dup
    path[0...root.size] = '.'
    remove_dot ? (path[2..-1] || "") : path
  else
    path
  end
end
remove_dir(path, config = {})
Alias for: remove_file
remove_file(path, config = {}) click to toggle source

Removes a file at the given location.

Parameters

path<String>

path of the file to be changed

config<Hash>

give :verbose => false to not log the status.

Example

remove_file 'README'
remove_file 'app/controllers/application_controller.rb'
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 327
def remove_file(path, config = {})
  return unless behavior == :invoke
  path = File.expand_path(path, destination_root)

  say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
  if !options[:pretend] && File.exist?(path)
    require "fileutils"
    ::FileUtils.rm_rf(path)
  end
end
Also aliased as: remove_dir
run(command, config = {}) click to toggle source

Executes a command returning the contents of the command.

Parameters

command<String>

the command to be executed.

config<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
  run('ln -s ~/edge rails')
end
# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 246
def run(command, config = {})
  return unless behavior == :invoke

  destination = relative_to_original_destination_root(destination_root, false)
  desc = "#{command} from #{destination.inspect}"

  if config[:with]
    desc = "#{File.basename(config[:with].to_s)} #{desc}"
    command = "#{config[:with]} #{command}"
  end

  say_status :run, desc, config.fetch(:verbose, true)

  return if options[:pretend]

  result = config[:capture] ? `#{command}` : system(command.to_s)

  if config[:abort_on_failure]
    success = config[:capture] ? $?.success? : result
    abort unless success
  end

  result
end
run_ruby_script(command, config = {}) click to toggle source

Executes a ruby script (taking into account WIN32 platform quirks).

Parameters

command<String>

the command to be executed.

config<Hash>

give :verbose => false to not log the status.

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 277
def run_ruby_script(command, config = {})
  return unless behavior == :invoke
  run command, config.merge(:with => Bundler::Thor::Util.ruby_command)
end
source_paths() click to toggle source

Holds source paths in instance so they can be manipulated.

# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 129
def source_paths
  @source_paths ||= self.class.source_paths_for_search
end
template(source, *args) { |content| ... } click to toggle source

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.

destination<String>

the relative path to the destination root.

config<Hash>

give :verbose => false to not log the status.

Examples

template "README", "doc/README"

template "doc/README"
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 115
def template(source, *args, &block)
  config = args.last.is_a?(Hash) ? args.pop : {}
  destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "")

  source  = File.expand_path(find_in_source_paths(source.to_s))
  context = config.delete(:context) || instance_eval("binding")

  create_file destination, nil, config do
    match = ERB.version.match(/(\d+\.\d+\.\d+)/)
    capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+
      CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer")
    else
      CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer")
    end
    content = capturable_erb.tap do |erb|
      erb.filename = source
    end.result(context)
    content = yield(content) if block
    content
  end
end
thor(command, *args) click to toggle source

Run a thor command. A hash of options can be given and it's converted to switches.

Parameters

command<String>

the command to be invoked

args<Array>

arguments to the command

config<Hash>

give :verbose => false to not log the status, :capture => true to hide to output. Other options are given as parameter to Bundler::Thor.

Examples

thor :install, "http://gist.github.com/103208"
#=> thor install http://gist.github.com/103208

thor :list, :all => true, :substring => 'rails'
#=> thor list --all --substring=rails
# File lib/bundler/vendor/thor/lib/thor/actions.rb, line 300
def thor(command, *args)
  config  = args.last.is_a?(Hash) ? args.pop : {}
  verbose = config.key?(:verbose) ? config.delete(:verbose) : true
  pretend = config.key?(:pretend) ? config.delete(:pretend) : false
  capture = config.key?(:capture) ? config.delete(:capture) : false

  args.unshift(command)
  args.push Bundler::Thor::Options.to_switches(config)
  command = args.join(" ").strip

  run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
end
uncomment_lines(path, flag, *args) click to toggle source

Uncomment all lines matching a given regex. It will leave the space which existed before the comment hash in tact but will remove any spacing between the comment hash and the beginning of the line.

Parameters

path<String>

path of the file to be changed

flag<Regexp|String>

the regexp or string used to decide which lines to uncomment

config<Hash>

give :verbose => false to not log the status.

Example

uncomment_lines 'config/initializers/session_store.rb', /active_record/
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 291
def uncomment_lines(path, flag, *args)
  flag = flag.respond_to?(:source) ? flag.source : flag

  gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
end

Private Instance Methods

capture(*args) { |*args| ... } click to toggle source
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 348
def capture(*args)
  with_output_buffer { yield(*args) }
end
concat(string) click to toggle source
# File lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb, line 344
def concat(string)
  @output_buffer.concat(string)
end