Skip to content

Commit

Permalink
Added inject_into_class.
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Aug 30, 2009
1 parent 5f8eb9f commit 9deed54
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 50 deletions.
46 changes: 33 additions & 13 deletions lib/thor/actions/file_manipulation.rb
Expand Up @@ -100,7 +100,7 @@ def chmod(path, mode, config={})
FileUtils.chmod_R(mode, path) unless options[:pretend]
end

# Prepend text to a file.
# Prepend text to a file. Since it depends on inject_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
Expand All @@ -112,18 +112,16 @@ def chmod(path, mode, config={})
# prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
#
def prepend_file(path, data=nil, config={}, &block)
return unless behavior == :invoke
path = File.expand_path(path, destination_root)
say_status :prepend, relative_to_original_destination_root(path), config.fetch(:verbose, true)
config.merge!(:after => /\A/)

unless options[:pretend]
content = data || block.call
content << File.read(path)
File.open(path, 'wb') { |file| file.write(content) }
if block_given?
inject_into_file(path, config, &block)
else
inject_into_file(path, data, config)
end
end

# Append text to a file.
# Append text to a file. Since it depends on inject_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
Expand All @@ -135,10 +133,32 @@ def prepend_file(path, data=nil, config={}, &block)
# append_file 'config/environments/test.rb', 'config.gem "rspec"'
#
def append_file(path, data=nil, config={}, &block)
return unless behavior == :invoke
path = File.expand_path(path, destination_root)
say_status :append, relative_to_original_destination_root(path), config.fetch(:verbose, true)
File.open(path, 'ab') { |file| file.write(data || block.call) } unless options[:pretend]
config.merge!(:before => /\z/)

if block_given?
inject_into_file(path, config, &block)
else
inject_into_file(path, data, config)
end
end

# Injects text right after the class definition. Since it depends on
# inject_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.
#
def inject_into_class(path, klass, data=nil, config={}, &block)
config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)

if block_given?
inject_into_file(path, config, &block)
else
inject_into_file(path, data, config)
end
end

# Run a regular expression replacement on a file.
Expand Down
48 changes: 32 additions & 16 deletions lib/thor/actions/inject_into_file.rb
Expand Up @@ -3,10 +3,8 @@
class Thor
module Actions

# Injects the given content into a file. Different from append_file,
# prepend_file and gsub_file, this method is reversible. By this reason,
# the flag can only be strings. gsub_file is your friend if you need to
# deal with more complex cases.
# Injects the given content into a file. Different from gsub_file, this
# method is reversible.
#
# ==== Parameters
# destination<String>:: Relative path to the destination root
Expand Down Expand Up @@ -35,43 +33,61 @@ def inject_into_file(destination, *args, &block)
end

class InjectIntoFile < EmptyDirectory #:nodoc:
attr_reader :replacement
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!
say_status :inject, config[:verbose]
say_status :invoke

flag = if @config.key?(:after)
content = '\0' + replacement
@config[:after]
content = if @behavior == :after
'\0' + replacement
else
content = replacement + '\0'
@config[:before]
replacement + '\0'
end

replace!(flag, content)
end

def revoke!
say_status :deinject, config[:verbose]
say_status :revoke

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

replace!(flag, content)
replace!(regexp, content)
end

protected

def say_status(behavior)
status = if flag == /\A/
behavior == :invoke ? :prepend : :unprepend
elsif flag == /\z/
behavior == :invoke ? :append : :unappend
else
behavior == :invoke ? :inject : :deinject
end

super(status, config[:verbose])
end

# Adds the content to the file.
#
def replace!(regexp, string)
Expand Down
43 changes: 25 additions & 18 deletions spec/actions/file_manipulation_spec.rb
@@ -1,5 +1,7 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

class Application; end

describe Thor::Actions do
def runner(options={})
@runner ||= MyCounter.new([1], options, { :destination_root => destination_root })
Expand Down Expand Up @@ -193,12 +195,6 @@ def file
File.open(file).read.must == "__start__\nREADME\n__end__\nEND\n"
end

it "does not append if pretending" do
runner(:pretend => true)
action :append_file, "doc/README", "END\n"
File.open(file).read.must == "__start__\nREADME\n__end__\n"
end

it "accepts a block" do
action(:append_file, "doc/README"){ "END\n" }
File.open(file).read.must == "__start__\nREADME\n__end__\nEND\n"
Expand All @@ -207,10 +203,6 @@ def file
it "logs status" do
action(:append_file, "doc/README", "END").must == " append doc/README\n"
end

it "does not log status if required" do
action(:append_file, "doc/README", nil, :verbose => false){ "END" }.must be_empty
end
end

describe "#prepend_file" do
Expand All @@ -219,12 +211,6 @@ def file
File.open(file).read.must == "START\n__start__\nREADME\n__end__\n"
end

it "does not prepend if pretending" do
runner(:pretend => true)
action :prepend_file, "doc/README", "START\n"
File.open(file).read.must == "__start__\nREADME\n__end__\n"
end

it "accepts a block" do
action(:prepend_file, "doc/README"){ "START\n" }
File.open(file).read.must == "START\n__start__\nREADME\n__end__\n"
Expand All @@ -233,9 +219,30 @@ def file
it "logs status" do
action(:prepend_file, "doc/README", "START").must == " prepend doc/README\n"
end
end

it "does not log status if required" do
action(:prepend_file, "doc/README", "START", :verbose => false).must be_empty
describe "#inject_into_class" do
def file
File.join(destination_root, "application.rb")
end

it "appends content to a class" do
action :inject_into_class, "application.rb", Application, " filter_parameters :password\n"
File.open(file).read.must == "class Application < Base\n filter_parameters :password\nend\n"
end

it "accepts a block" do
action(:inject_into_class, "application.rb", Application){ " filter_parameters :password\n" }
File.open(file).read.must == "class Application < Base\n filter_parameters :password\nend\n"
end

it "logs status" do
action(:inject_into_class, "application.rb", Application, " filter_parameters :password\n").must == " inject application.rb\n"
end

it "does not append if class name does not match" do
action :inject_into_class, "application.rb", "App", " filter_parameters :password\n"
File.open(file).read.must == "class Application < Base\nend\n"
end
end
end
Expand Down
16 changes: 13 additions & 3 deletions spec/actions/inject_into_file_spec.rb
Expand Up @@ -61,33 +61,43 @@ def file
it "deinjects the destination file after injection" do
invoke! "doc/README", "\nmore content", :after => "__start__"
revoke! "doc/README", "\nmore content", :after => "__start__"

File.read(file).must == "__start__\nREADME\n__end__\n"
end

it "deinjects the destination file before injection" do
invoke! "doc/README", "more content\n", :before => "__start__"
revoke! "doc/README", "more content\n", :before => "__start__"

File.read(file).must == "__start__\nREADME\n__end__\n"
end

it "deinjects even with double after injection" do
invoke! "doc/README", "\nmore content", :after => "__start__"
invoke! "doc/README", "\nanother stuff", :after => "__start__"
revoke! "doc/README", "\nmore content", :after => "__start__"

File.read(file).must == "__start__\nanother stuff\nREADME\n__end__\n"
end

it "deinjects even with double before injection" do
invoke! "doc/README", "more content\n", :before => "__start__"
invoke! "doc/README", "another stuff\n", :before => "__start__"
revoke! "doc/README", "more content\n", :before => "__start__"
File.read(file).must == "another stuff\n__start__\nREADME\n__end__\n"
end

it "deinjects when prepending" do
invoke! "doc/README", "more content\n", :after => /\A/
invoke! "doc/README", "another stuff\n", :after => /\A/
revoke! "doc/README", "more content\n", :after => /\A/
File.read(file).must == "another stuff\n__start__\nREADME\n__end__\n"
end

it "deinjects when appending" do
invoke! "doc/README", "more content\n", :before => /\z/
invoke! "doc/README", "another stuff\n", :before => /\z/
revoke! "doc/README", "more content\n", :before => /\z/
File.read(file).must == "__start__\nREADME\n__end__\nanother stuff\n"
end

it "shows progress information to the user" do
invoke!("doc/README", "\nmore content", :after => "__start__")
revoke!("doc/README", "\nmore content", :after => "__start__").must == " deinject doc/README\n"
Expand Down
2 changes: 2 additions & 0 deletions spec/fixtures/application.rb
@@ -0,0 +1,2 @@
class Application < Base
end

0 comments on commit 9deed54

Please sign in to comment.