module Bundler::Thor::Actions
def self.included(base) #:nodoc:
def self.included(base) #:nodoc: super(base) base.extend ClassMethods end
def _cleanup_options_and_set(options, key) #:nodoc:
def _cleanup_options_and_set(options, key) #:nodoc: case options when Array %w(--force -f --skip -s).each { |i| options.delete(i) } options << "--#{key}" when Hash [:force, :skip, "force", "skip"].each { |i| options.delete(i) } options.merge!(key => true) end end
def _shared_configuration #:nodoc:
Allow current root to be shared between invocations.
def _shared_configuration #:nodoc: super.merge!(destination_root: destination_root) end
def action(instance) #:nodoc:
Wraps an action object and call it accordingly to the thor class behavior.
def action(instance) #:nodoc: if behavior == :revoke instance.revoke! else instance.invoke! end end
def append_to_file(path, *args, &block)
end
'config.gem "rspec"'
append_to_file 'config/environments/test.rb' do
append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
==== Example
config
data
path
==== Parameters
Append text to a file. Since it depends on insert_into_file, it's reversible.
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
def apply(path, config = {})
apply "recipes/jquery.rb"
apply "http://gist.github.com/103208"
==== Examples
a relative path from the source root.
path
==== Parameters
Loads an external file and execute it in the instance binding.
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" URI.open(path, "Accept" => "application/x-thor-template", &:read) else File.open(path, &:read) end instance_eval(contents, path) shell.padding -= 1 if verbose end
def capture(*args)
def capture(*args) with_output_buffer { yield(*args) } end
def chmod(path, mode, config = {})
chmod "script/server", 0755
==== Example
config
path
mode
==== Parameters
Changes the mode of the given file or directory.
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
def comment_lines(path, flag, *args)
comment_lines 'config/initializers/session_store.rb', /cookie_store/
==== Example
config
flag
path
==== Parameters
a single space after the comment hash.
which existed before the beginning of the line in tact and will insert
Comment all lines matching a given regex. It will leave the space
def comment_lines(path, flag, *args) flag = flag.respond_to?(:source) ? flag.source : flag gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args) end
def concat(string)
def concat(string) @output_buffer.concat(string) end
def copy_file(source, *args, &block)
copy_file "doc/README"
copy_file "README", "doc/README"
==== Examples
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
def create_file(destination, *args, &block)
create_file "config/apache.conf", "your apache config"
end
"vhost.name = #{hostname}"
hostname = ask("What is the virtual hostname I should use?")
create_file "lib/fun_party.rb" do
==== Examples
config
data
destination
==== Parameters
which is the return value of a block or a data string.
Create a new file relative to the destination root with the given data,
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
def create_link(destination, *args)
create_link "config/apache.conf", "/etc/apache.conf"
==== Examples
:: give :symbolic => false for hard link.
config
source
destination
==== Parameters
Create a new file relative to the destination root from the given source.
def create_link(destination, *args) config = args.last.is_a?(Hash) ? args.pop : {} source = args.first action CreateLink.new(self, destination, source, config) end
def destination_root
Returns the root for this thor class (also aliased as destination root).
def destination_root @destination_stack.last end
def destination_root=(root)
directory where the script was invoked and expanded.
Sets the root for this thor class. Relatives path are added to the
def destination_root=(root) @destination_stack ||= [] @destination_stack[0] = File.expand_path(root || "") end
def directory(source, *args, &block)
directory "doc", "docs", :recursive => false
directory "doc"
==== Examples
If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
If :mode => :preserve, preserve the file mode from the source.
If :recursive => false, does not look for paths recursively.
config
destination
source
==== Parameters
#directory. If a method is private, Bundler::Thor stack raises PrivateMethodEncodedError.
expand %something%, this `something` should be a public method in the class calling
Encoded path note: Since Bundler::Thor internals use Object#respond_to? to check if it can
blog.rb
rdoc.rb
README
components/
doc/
files (assuming that the `app_name` method returns the value "blog"):
It will create a doc directory in the destination with the following
directory "doc"
When invoked as:
%app_name%.rb
rdoc.rb.tt
README
components/.empty_directory
doc/
value. Let's suppose a doc directory with the following files:
the % signs will be executed as a method and replaced with the returned
ignored. If any file name is wrapped within % signs, the text within
empty directory is found, it's copied and all .empty_directory files are
and is placed in the destination without the extension .tt. If any
If any of the files finishes with .tt, it's considered to be a template
Copies recursively the files from source directory to root directory.
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
def empty_directory(destination, config = {})
empty_directory "doc"
==== Examples
config
destination
==== Parameters
Creates an empty directory.
def empty_directory(destination, config = {}) action EmptyDirectory.new(self, destination, config) end
def find_in_source_paths(file)
Receives a file or directory and search for it in the source paths.
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
def get(source, *args, &block)
end
content.split("\n").first
get "http://gist.github.com/103208" do |content|
get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"}
get "http://gist.github.com/103208", "doc/README"
==== Examples
:http_headers =>
config
destination
source
==== Parameters
a command injection attack vector.
+get+ relies on open-uri, so passing application user input would provide
the url is yielded and used as location.
destination. If a block is given instead of destination, the content of
Gets the content at the given address and places it at the given relative
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, config.fetch(:http_headers, {})) { |input| input.binmode.read } else source = File.expand_path(find_in_source_paths(source.to_s)) File.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
def gsub_file(path, flag, *args, &block)
end
match << " no more. Use thor!"
gsub_file 'README', /rake/, :green do |match|
gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
==== Example
:force => true, to force the replacement regardless of runner behavior.
config
replacement
flag
path
==== Parameters
Run a regular expression replacement on a file.
def gsub_file(path, flag, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} return unless behavior == :invoke || config.fetch(:force, false) 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
def in_root
Goes to the root and execute the given block.
def in_root inside(@destination_stack.first) { yield } end
def initialize(args = [], options = {}, config = {})
destination_root
and the respective option.
It also accepts :force, :skip and :pretend to set the behavior
behavior
==== Configuration
Extends initializer to add more configuration options.
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
def inject_into_class(path, klass, *args, &block)
end
" filter_parameter :password\n"
inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n"
==== Examples
config
data
klass
path
==== Parameters
insert_into_file, it's reversible.
Injects text right after the class definition. Since it depends on
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
def inject_into_module(path, module_name, *args, &block)
end
" def help; 'help'; end\n"
inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do
inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper", " def help; 'help'; end\n"
==== Examples
config
data
module_name
path
==== Parameters
insert_into_file, it's reversible.
Injects text right after the module definition. Since it depends on
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
def insert_into_file(destination, *args, &block)
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
def inside(dir = "", config = {}, &block)
config
dir
==== Parameters
Returns the value yielded by the block.
the method exits.
to the block you provide. The path is set back to the previous path when
is given it's referenced from the current root. The full path is yielded
Do something in the root or on a provided subfolder. If a relative path
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 doesn't exist and we're not pretending if !File.exist?(destination_root) && !pretend require "fileutils" FileUtils.mkdir_p(destination_root) end result = nil if pretend # In pretend mode, just yield down to the block result = block.arity == 1 ? yield(destination_root) : yield else require "fileutils" FileUtils.cd(destination_root) { result = block.arity == 1 ? yield(destination_root) : yield } end @destination_stack.pop shell.padding -= 1 if verbose result end
def link_file(source, *args)
link_file "doc/README"
link_file "README", "doc/README"
==== Examples
config
destination
source
==== Parameters
the destination is not given it's assumed to be equal to the source.
Links the file from the relative source to the relative destination. If
def link_file(source, *args) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source source = File.expand_path(find_in_source_paths(source.to_s)) create_link destination, source, config end
def prepend_to_file(path, *args, &block)
end
'config.gem "rspec"'
prepend_to_file 'config/environments/test.rb' do
prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
==== Example
config
data
path
==== Parameters
Prepend text to a file. Since it depends on insert_into_file, it's reversible.
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
def relative_to_original_destination_root(path, remove_dot = true)
the script started).
Returns the given path relative to the absolute root (ie, root where
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
def remove_file(path, config = {})
remove_file 'app/controllers/application_controller.rb'
remove_file 'README'
==== Example
config
path
==== Parameters
Removes a file at the given location.
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) || File.symlink?(path)) require "fileutils" ::FileUtils.rm_rf(path) end end
def run(command, config = {})
end
run('ln -s ~/edge rails')
inside('vendor') do
==== Example
to append an executable to command execution.
config
command
==== Parameters
Executes a command returning the contents of the command.
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] env_splat = [config[:env]] if config[:env] if config[:capture] require "open3" result, status = Open3.capture2e(*env_splat, command.to_s) success = status.success? else result = system(*env_splat, command.to_s) success = result end abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?) result end
def run_ruby_script(command, config = {})
config
command
==== Parameters
Executes a ruby script (taking into account WIN32 platform quirks).
def run_ruby_script(command, config = {}) return unless behavior == :invoke run command, config.merge(with: Bundler::Thor::Util.ruby_command) end
def source_paths
Holds source paths in instance so they can be manipulated.
def source_paths @source_paths ||= self.class.source_paths_for_search end
def template(source, *args, &block)
template "doc/README"
template "README", "doc/README"
==== Examples
config
destination
source
==== Parameters
to be equal to the source removing .tt from the filename.
at the relative destination. If the destination is not given it's assumed
Gets an ERB template at the relative source, executes it and makes a copy
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 capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer") content = capturable_erb.tap do |erb| erb.filename = source end.result(context) content = yield(content) if block content end end
def thor(command, *args)
#=> thor list --all --substring=rails
thor :list, :all => true, :substring => 'rails'
#=> thor install http://gist.github.com/103208
thor :install, "http://gist.github.com/103208"
==== Examples
Other options are given as parameter to Bundler::Thor.
config
args
command
==== Parameters
switches.
Run a thor command. A hash of options can be given and it's converted to
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
def uncomment_lines(path, flag, *args)
uncomment_lines 'config/initializers/session_store.rb', /active_record/
==== Example
config
flag
path
==== Parameters
between the comment hash and the beginning of the line.
which existed before the comment hash in tact but will remove any spacing
Uncomment all lines matching a given regex. It will leave the space
def uncomment_lines(path, flag, *args) flag = flag.respond_to?(:source) ? flag.source : flag gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args) end
def with_output_buffer(buf = "".dup) #:nodoc:
def with_output_buffer(buf = "".dup) #:nodoc: raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen? old_buffer = output_buffer self.output_buffer = buf yield output_buffer ensure self.output_buffer = old_buffer end