ing 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/GENERATORS.md +2 -0
- data/LICENSE +18 -0
- data/OPTIONS.md +2 -0
- data/README.md +251 -0
- data/TASKS.md +21 -0
- data/bin/ing +5 -0
- data/examples/rspec_convert.rb +102 -0
- data/ing.gemspec +29 -0
- data/ing.rb +102 -0
- data/lib/ing.rb +78 -0
- data/lib/ing/actions/create_file.rb +105 -0
- data/lib/ing/actions/create_link.rb +57 -0
- data/lib/ing/actions/directory.rb +98 -0
- data/lib/ing/actions/empty_directory.rb +155 -0
- data/lib/ing/actions/file_manipulation.rb +308 -0
- data/lib/ing/actions/inject_into_file.rb +109 -0
- data/lib/ing/commands/boot.rb +76 -0
- data/lib/ing/commands/generate.rb +64 -0
- data/lib/ing/commands/help.rb +87 -0
- data/lib/ing/commands/implicit.rb +59 -0
- data/lib/ing/commands/list.rb +108 -0
- data/lib/ing/dispatcher.rb +132 -0
- data/lib/ing/files.rb +190 -0
- data/lib/ing/lib_trollop.rb +782 -0
- data/lib/ing/shell.rb +390 -0
- data/lib/ing/trollop/parser.rb +17 -0
- data/lib/ing/util.rb +61 -0
- data/lib/ing/version.rb +3 -0
- data/lib/thor/actions/file_manipulation.rb +30 -0
- data/lib/thor/shell/basic.rb +44 -0
- data/test/acceptance/ing_run_tests.rb +164 -0
- data/test/actions/create_file_spec.rb +209 -0
- data/test/actions/create_link_spec.rb +90 -0
- data/test/actions/directory_spec.rb +167 -0
- data/test/actions/empty_directory_spec.rb +146 -0
- data/test/actions/file_manipulation_spec.rb +433 -0
- data/test/actions/inject_into_file_spec.rb +147 -0
- data/test/fixtures/application.rb +2 -0
- data/test/fixtures/app{1}/README +3 -0
- data/test/fixtures/bundle/execute.rb +6 -0
- data/test/fixtures/bundle/main.thor +1 -0
- data/test/fixtures/doc/%file_name%.rb.tt +1 -0
- data/test/fixtures/doc/COMMENTER +10 -0
- data/test/fixtures/doc/README +3 -0
- data/test/fixtures/doc/block_helper.rb +3 -0
- data/test/fixtures/doc/components/.empty_directory +0 -0
- data/test/fixtures/doc/config.rb +1 -0
- data/test/fixtures/doc/config.yaml.tt +1 -0
- data/test/fixtures/group.ing.rb +76 -0
- data/test/fixtures/invok.ing.rb +50 -0
- data/test/fixtures/namespace.ing.rb +52 -0
- data/test/fixtures/require.ing.rb +7 -0
- data/test/fixtures/task.ing.rb +36 -0
- data/test/fixtures/task.thor +10 -0
- data/test/spec_helper.rb +2 -0
- data/test/test_helper.rb +41 -0
- data/todo.yml +7 -0
- metadata +147 -0
data/ing.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Usage:
|
2
|
+
# ing rspec:convert which is equivalent to
|
3
|
+
# ing rspec:convert files './{test,spec}/**/*.rb' --convert-dir 'converted'
|
4
|
+
#
|
5
|
+
module Rspec
|
6
|
+
|
7
|
+
class Convert
|
8
|
+
|
9
|
+
GSUBS = \
|
10
|
+
[
|
11
|
+
[ /^(\s*)(.+)\.should\s+be_true/ , '\1assert \2' ],
|
12
|
+
[ /^(\s*)(.+)\.should\s+be_false/ , '\1refute \2' ],
|
13
|
+
[ /^(\s*)(.+)\.should\s*==\s*(.+)$/ , '\1assert_equal \3, \2' ],
|
14
|
+
[ /^(\s*)(.+)\.should_not\s*==\s*(.+)$/ , '\1refute_equal \3, \2' ],
|
15
|
+
[ /^(\s*)(.+)\.should\s*=~\s*(.+)$/ , '\1assert_match(\3, \2)' ],
|
16
|
+
[ /^(\s*)(.+)\.should_not\s*=~\s*(.+)$/ , '\1refute_match(\3, \2)' ],
|
17
|
+
[ /^(\s*)(.+)\.should\s+be_(.+)$/ , '\1assert \2.\3?' ],
|
18
|
+
[ /^(\s*)(.+)\.should_not\s+be_(.+)$/ , '\1refute \2.\3?' ],
|
19
|
+
[ /expect\s+\{(.+)\}\.to\s+raise_error\s*\((.*)\)\s*\Z/m,
|
20
|
+
'assert_raises(\2) {\1}' ],
|
21
|
+
[ /\{(.+)\}\.should raise_error\s*\((.*)\)\s*\Z/m,
|
22
|
+
'assert_raises(\2) {\1}' ],
|
23
|
+
# these next aren't quite right because they need to wrap the next
|
24
|
+
# lines as a lambda. Thus the FIXME notes.
|
25
|
+
[ /\.should_receive\(([^\)]+)\)\.and_return\((.+)\)/,
|
26
|
+
'.stub(\1, \2) do |s| # FIXME' ],
|
27
|
+
[ /.stub\!\(([\w:]+)\)\.and_return\((.+)\)/,
|
28
|
+
'.stub(\1, \2) do |s| # FIXME' ]
|
29
|
+
|
30
|
+
]
|
31
|
+
|
32
|
+
def self.specify_options(expect)
|
33
|
+
expect.banner "Convert rspec 'should/not' matchers to minitest 'assert/refute'"
|
34
|
+
expect.text "It's not magic, you still need to hand edit your test files after running this"
|
35
|
+
expect.text "\nUsage:"
|
36
|
+
expect.text " ing rspec:convert # which is equivalent to"
|
37
|
+
expect.text " ing rspec:convert files './{test,spec}/**/*.rb' --convert-dir 'converted'"
|
38
|
+
expect.text "\nOptions:"
|
39
|
+
expect.opt :pattern, "Directory glob pattern for test files",
|
40
|
+
:type => :string, :default => './{test,spec}/**/*.rb'
|
41
|
+
expect.opt :convert_dir, "Subdirectory to save converted files",
|
42
|
+
:type => :string, :default => 'converted'
|
43
|
+
end
|
44
|
+
|
45
|
+
include Ing::Files
|
46
|
+
|
47
|
+
attr_accessor :shell, :options, :destination_root, :source_root
|
48
|
+
|
49
|
+
def destination_root; @destination_root ||= Dir.pwd; end
|
50
|
+
def source_root; @source_root ||= File.dirname(__FILE__); end
|
51
|
+
|
52
|
+
def input_files
|
53
|
+
@input_files ||= Dir[ File.expand_path(options[:pattern], source_root) ]
|
54
|
+
end
|
55
|
+
|
56
|
+
def converted_files
|
57
|
+
input_files.map {|f|
|
58
|
+
File.join( File.dirname(f), options[:convert_dir], File.basename(f) )
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def conversion_map
|
63
|
+
input_files.zip(converted_files)
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(options)
|
67
|
+
self.options = options
|
68
|
+
end
|
69
|
+
|
70
|
+
def call(pattern=nil)
|
71
|
+
options[:pattern] = pattern || options[:pattern]
|
72
|
+
shell.say "Processing #{input_files.length} input files: #{options[:pattern]}" if verbose?
|
73
|
+
conversion_map.each do |input_f, output_f|
|
74
|
+
new_lines = convert_lines(input_f)
|
75
|
+
create_file output_f, new_lines.join
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias :files :call
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def convert_lines(fname)
|
83
|
+
count = 0; accum = []
|
84
|
+
File.open(fname) do |f|
|
85
|
+
f.each_line do |line|
|
86
|
+
new_line = GSUBS.inject(line) do |str, (rx, replace)|
|
87
|
+
str = str.gsub(rx, replace)
|
88
|
+
end
|
89
|
+
count += 1 if line != new_line
|
90
|
+
accum << new_line
|
91
|
+
end
|
92
|
+
end
|
93
|
+
shell.say_status(:convert,
|
94
|
+
"#{relative_to_original_destination_root(fname)}: " +
|
95
|
+
"#{count} changes",
|
96
|
+
:green) if verbose?
|
97
|
+
accum
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/lib/ing.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
['ing/version',
|
2
|
+
'ing/lib_trollop',
|
3
|
+
'ing/trollop/parser',
|
4
|
+
'ing/util',
|
5
|
+
'ing/dispatcher',
|
6
|
+
'ing/shell',
|
7
|
+
'ing/files',
|
8
|
+
'ing/commands/boot',
|
9
|
+
'ing/commands/implicit',
|
10
|
+
'ing/commands/list',
|
11
|
+
'ing/commands/help',
|
12
|
+
'ing/commands/generate'
|
13
|
+
].each do |f|
|
14
|
+
require_relative f
|
15
|
+
end
|
16
|
+
|
17
|
+
module Ing
|
18
|
+
extend self
|
19
|
+
|
20
|
+
Error = Class.new(StandardError)
|
21
|
+
FileNotFoundError = Class.new(Error)
|
22
|
+
|
23
|
+
attr_writer :shell_class
|
24
|
+
|
25
|
+
def shell_class
|
26
|
+
@shell_class ||= Shell::Basic
|
27
|
+
end
|
28
|
+
|
29
|
+
def implicit_booter
|
30
|
+
["Implicit"]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Dispatch command line to boot class (if specified, or Implicit otherwise),
|
34
|
+
# which in turn dispatches the command after parsing args.
|
35
|
+
#
|
36
|
+
# Note boot dispatch happens within +Ing::Commands+ namespace.
|
37
|
+
#
|
38
|
+
def run(argv=ARGV)
|
39
|
+
booter = extract_boot_class!(argv) || implicit_booter
|
40
|
+
run_boot booter, "call", *argv
|
41
|
+
end
|
42
|
+
|
43
|
+
# Dispatch to the command via +Ing::Boot#call_invoke+
|
44
|
+
# Use this when you want to invoke a command from another command, but only
|
45
|
+
# if it hasn't been run yet. For example,
|
46
|
+
#
|
47
|
+
# invoke Some::Task, :some_instance_method, some_argument, :some_option => true
|
48
|
+
#
|
49
|
+
# You can skip the method and it will assume +#call+ :
|
50
|
+
#
|
51
|
+
# invoke Some::Task, :some_option => true
|
52
|
+
def invoke(klass, *args)
|
53
|
+
run_boot implicit_booter, "call_invoke", klass, *args
|
54
|
+
end
|
55
|
+
|
56
|
+
# Dispatch to the command via +Ing::Boot#call_execute+
|
57
|
+
# Use this when you want to execute a command from another command, and you
|
58
|
+
# don't care if it has been run yet or not. See equivalent examples for
|
59
|
+
# +invoke+.
|
60
|
+
#
|
61
|
+
def execute(klass, *args)
|
62
|
+
run_boot implicit_booter, "call_execute", klass, *args
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def run_boot(booter, *args)
|
68
|
+
Dispatcher.new(["Ing","Commands"], booter, *args).dispatch
|
69
|
+
end
|
70
|
+
|
71
|
+
def extract_boot_class!(args)
|
72
|
+
c = Util.to_class_names(args.first)
|
73
|
+
if (Commands.const_defined?(c.first, false) rescue nil)
|
74
|
+
args.shift; c
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.expand_path('empty_directory', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module Ing
|
4
|
+
module Files
|
5
|
+
|
6
|
+
# Create a new file relative to the destination root with the given data,
|
7
|
+
# which is the return value of a block or a data string.
|
8
|
+
#
|
9
|
+
# ==== Parameters
|
10
|
+
# destination<String>:: the relative path to the destination root.
|
11
|
+
# data<String|NilClass>:: the data to append to the file.
|
12
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
13
|
+
#
|
14
|
+
# ==== Examples
|
15
|
+
#
|
16
|
+
# create_file "lib/fun_party.rb" do
|
17
|
+
# hostname = ask("What is the virtual hostname I should use?")
|
18
|
+
# "vhost.name = #{hostname}"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# create_file "config/apache.conf", "your apache config"
|
22
|
+
#
|
23
|
+
def create_file(destination, *args, &block)
|
24
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
25
|
+
data = args.first
|
26
|
+
action CreateFile.new(self, destination, block || data.to_s, config)
|
27
|
+
end
|
28
|
+
alias :add_file :create_file
|
29
|
+
|
30
|
+
# CreateFile is a subset of Template, which instead of rendering a file with
|
31
|
+
# ERB, it gets the content from the user.
|
32
|
+
#
|
33
|
+
class CreateFile < EmptyDirectory #:nodoc:
|
34
|
+
attr_reader :data
|
35
|
+
|
36
|
+
def initialize(base, destination, data, config={})
|
37
|
+
@data = data
|
38
|
+
super(base, destination, config)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks if the content of the file at the destination is identical to the rendered result.
|
42
|
+
#
|
43
|
+
# ==== Returns
|
44
|
+
# Boolean:: true if it is identical, false otherwise.
|
45
|
+
#
|
46
|
+
def identical?
|
47
|
+
exists? && File.binread(destination) == render
|
48
|
+
end
|
49
|
+
|
50
|
+
# Holds the content to be added to the file.
|
51
|
+
#
|
52
|
+
def render
|
53
|
+
@render ||= if data.is_a?(Proc)
|
54
|
+
data.call
|
55
|
+
else
|
56
|
+
data
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def invoke!
|
61
|
+
invoke_with_conflict_check do
|
62
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
63
|
+
File.open(destination, 'wb') { |f| f.write render }
|
64
|
+
end
|
65
|
+
given_destination
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
# Now on conflict we check if the file is identical or not.
|
71
|
+
#
|
72
|
+
def on_conflict_behavior(&block)
|
73
|
+
if identical?
|
74
|
+
say_status :identical, :blue
|
75
|
+
else
|
76
|
+
options = base.options.merge(config)
|
77
|
+
force_or_skip_or_conflict(options[:force], options[:skip], &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# If force is true, run the action, otherwise check if it's not being
|
82
|
+
# skipped. If both are false, show the file_collision menu, if the menu
|
83
|
+
# returns true, force it, otherwise skip.
|
84
|
+
#
|
85
|
+
def force_or_skip_or_conflict(force, skip, &block)
|
86
|
+
if force
|
87
|
+
say_status :force, :yellow
|
88
|
+
block.call unless pretend?
|
89
|
+
elsif skip
|
90
|
+
say_status :skip, :yellow
|
91
|
+
else
|
92
|
+
say_status :conflict, :red
|
93
|
+
force_or_skip_or_conflict(force_on_collision?, true, &block)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Shows the file collision menu to the user and gets the result.
|
98
|
+
#
|
99
|
+
def force_on_collision?
|
100
|
+
base.shell.file_collision(destination){ render }
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path('create_file', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module Ing
|
4
|
+
module Files
|
5
|
+
|
6
|
+
# Create a new file relative to the destination root from the given source.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# destination<String>:: the relative path to the destination root.
|
10
|
+
# source<String|NilClass>:: the relative path to the source root.
|
11
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
12
|
+
# :: give :symbolic => false for hard link.
|
13
|
+
#
|
14
|
+
# ==== Examples
|
15
|
+
#
|
16
|
+
# create_link "config/apache.conf", "/etc/apache.conf"
|
17
|
+
#
|
18
|
+
def create_link(destination, *args, &block)
|
19
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
20
|
+
source = args.first
|
21
|
+
action CreateLink.new(self, destination, source, config)
|
22
|
+
end
|
23
|
+
alias :add_link :create_link
|
24
|
+
|
25
|
+
# CreateLink is a subset of CreateFile, which instead of taking a block of
|
26
|
+
# data, just takes a source string from the user.
|
27
|
+
#
|
28
|
+
class CreateLink < CreateFile #:nodoc:
|
29
|
+
attr_reader :data
|
30
|
+
|
31
|
+
# Checks if the content of the file at the destination is identical to the rendered result.
|
32
|
+
#
|
33
|
+
# ==== Returns
|
34
|
+
# Boolean:: true if it is identical, false otherwise.
|
35
|
+
#
|
36
|
+
def identical?
|
37
|
+
exists? && File.identical?(render, destination)
|
38
|
+
end
|
39
|
+
|
40
|
+
def invoke!
|
41
|
+
invoke_with_conflict_check do
|
42
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
43
|
+
# Create a symlink by default
|
44
|
+
config[:symbolic] = true if config[:symbolic].nil?
|
45
|
+
File.unlink(destination) if exists?
|
46
|
+
if config[:symbolic]
|
47
|
+
File.symlink(render, destination)
|
48
|
+
else
|
49
|
+
File.link(render, destination)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
given_destination
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.expand_path('empty_directory', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module Ing
|
4
|
+
module Files
|
5
|
+
# Copies recursively the files from source directory to root directory.
|
6
|
+
# If any of the files finishes with .tt, it's considered to be a template
|
7
|
+
# and is placed in the destination without the extension .tt. If any
|
8
|
+
# empty directory is found, it's copied and all .empty_directory files are
|
9
|
+
# ignored. If any file name is wrapped within % signs, the text within
|
10
|
+
# the % signs will be executed as a method and replaced with the returned
|
11
|
+
# value. Let's suppose a doc directory with the following files:
|
12
|
+
#
|
13
|
+
# doc/
|
14
|
+
# components/.empty_directory
|
15
|
+
# README
|
16
|
+
# rdoc.rb.tt
|
17
|
+
# %app_name%.rb
|
18
|
+
#
|
19
|
+
# When invoked as:
|
20
|
+
#
|
21
|
+
# directory "doc"
|
22
|
+
#
|
23
|
+
# It will create a doc directory in the destination with the following
|
24
|
+
# files (assuming that the `app_name` method returns the value "blog"):
|
25
|
+
#
|
26
|
+
# doc/
|
27
|
+
# components/
|
28
|
+
# README
|
29
|
+
# rdoc.rb
|
30
|
+
# blog.rb
|
31
|
+
#
|
32
|
+
# <b>Encoded path note:</b> Since Thor internals use Object#respond_to? to check if it can
|
33
|
+
# expand %something%, this `something` should be a public method in the class calling
|
34
|
+
# #directory. If a method is private, Thor stack raises PrivateMethodEncodedError.
|
35
|
+
#
|
36
|
+
# ==== Parameters
|
37
|
+
# source<String>:: the relative path to the source root.
|
38
|
+
# destination<String>:: the relative path to the destination root.
|
39
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
40
|
+
# If :recursive => false, does not look for paths recursively.
|
41
|
+
#
|
42
|
+
# ==== Examples
|
43
|
+
#
|
44
|
+
# directory "doc"
|
45
|
+
# directory "doc", "docs", :recursive => false
|
46
|
+
#
|
47
|
+
def directory(source, *args, &block)
|
48
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
49
|
+
destination = args.first || source
|
50
|
+
action Directory.new(self, source, destination || source, config, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
class Directory < EmptyDirectory #:nodoc:
|
54
|
+
attr_reader :source
|
55
|
+
|
56
|
+
def initialize(base, source, destination=nil, config={}, &block)
|
57
|
+
@source = File.expand_path(base.find_in_source_paths(source.to_s))
|
58
|
+
@block = block
|
59
|
+
super(base, destination, { :recursive => true }.merge(config))
|
60
|
+
end
|
61
|
+
|
62
|
+
def invoke!
|
63
|
+
base.empty_directory given_destination, config
|
64
|
+
execute!
|
65
|
+
end
|
66
|
+
|
67
|
+
def revoke!
|
68
|
+
execute!
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def execute!
|
74
|
+
lookup = Util.escape_globs(source)
|
75
|
+
lookup = config[:recursive] ? File.join(lookup, '**') : lookup
|
76
|
+
lookup = File.join(lookup, '{*,.[a-z]*}')
|
77
|
+
|
78
|
+
Dir[lookup].sort.each do |file_source|
|
79
|
+
next if File.directory?(file_source)
|
80
|
+
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
|
81
|
+
file_destination.gsub!('/./', '/')
|
82
|
+
|
83
|
+
case file_source
|
84
|
+
when /\.empty_directory$/
|
85
|
+
dirname = File.dirname(file_destination).gsub(/\/\.$/, '')
|
86
|
+
next if dirname == given_destination
|
87
|
+
base.empty_directory(dirname, config)
|
88
|
+
when /\.tt$/
|
89
|
+
destination = base.template(file_source, file_destination[0..-4], config, &@block)
|
90
|
+
else
|
91
|
+
destination = base.copy_file(file_source, file_destination, config, &@block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Ing
|
2
|
+
module Files
|
3
|
+
|
4
|
+
# Creates an empty directory.
|
5
|
+
#
|
6
|
+
# ==== Parameters
|
7
|
+
# destination<String>:: the relative path to the destination root.
|
8
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
9
|
+
#
|
10
|
+
# ==== Examples
|
11
|
+
#
|
12
|
+
# empty_directory "doc"
|
13
|
+
#
|
14
|
+
def empty_directory(destination, config={})
|
15
|
+
action EmptyDirectory.new(self, destination, config)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Class which holds create directory logic. This is the base class for
|
19
|
+
# other actions like create_file and directory.
|
20
|
+
#
|
21
|
+
# This implementation is based in Templater actions, created by Jonas Nicklas
|
22
|
+
# and Michael S. Klishin under MIT LICENSE.
|
23
|
+
#
|
24
|
+
class EmptyDirectory #:nodoc:
|
25
|
+
attr_reader :base, :destination, :given_destination, :relative_destination, :config
|
26
|
+
|
27
|
+
# Initializes given the source and destination.
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
# base<Thor::Base>:: A Thor::Base instance
|
31
|
+
# source<String>:: Relative path to the source of this file
|
32
|
+
# destination<String>:: Relative path to the destination of this file
|
33
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
34
|
+
#
|
35
|
+
def initialize(base, destination, config={})
|
36
|
+
@base, @config = base, { :verbose => true }.merge(config)
|
37
|
+
self.destination = destination
|
38
|
+
end
|
39
|
+
|
40
|
+
# Checks if the destination file already exists.
|
41
|
+
#
|
42
|
+
# ==== Returns
|
43
|
+
# Boolean:: true if the file exists, false otherwise.
|
44
|
+
#
|
45
|
+
def exists?
|
46
|
+
::File.exists?(destination)
|
47
|
+
end
|
48
|
+
|
49
|
+
def invoke!
|
50
|
+
invoke_with_conflict_check do
|
51
|
+
::FileUtils.mkdir_p(destination)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def revoke!
|
56
|
+
say_status :remove, :red
|
57
|
+
::FileUtils.rm_rf(destination) if !pretend? && exists?
|
58
|
+
given_destination
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
# Shortcut for pretend.
|
64
|
+
#
|
65
|
+
def pretend?
|
66
|
+
base.options[:pretend]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the absolute destination value from a relative destination value.
|
70
|
+
# It also stores the given and relative destination. Let's suppose our
|
71
|
+
# script is being executed on "dest", it sets the destination root to
|
72
|
+
# "dest". The destination, given_destination and relative_destination
|
73
|
+
# are related in the following way:
|
74
|
+
#
|
75
|
+
# inside "bar" do
|
76
|
+
# empty_directory "baz"
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# destination #=> dest/bar/baz
|
80
|
+
# relative_destination #=> bar/baz
|
81
|
+
# given_destination #=> baz
|
82
|
+
#
|
83
|
+
def destination=(destination)
|
84
|
+
if destination
|
85
|
+
@given_destination = convert_encoded_instructions(destination.to_s)
|
86
|
+
@destination = ::File.expand_path(@given_destination, base.current_destination)
|
87
|
+
@relative_destination = base.relative_to_original_destination_root(@destination)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Filenames in the encoded form are converted. If you have a file:
|
92
|
+
#
|
93
|
+
# %file_name%.rb
|
94
|
+
#
|
95
|
+
# It calls #file_name from the base and replaces %-string with the
|
96
|
+
# return value (should be String) of #file_name:
|
97
|
+
#
|
98
|
+
# user.rb
|
99
|
+
#
|
100
|
+
# The method referenced by %-string SHOULD be public. Otherwise you
|
101
|
+
# get the exception with the corresponding error message.
|
102
|
+
#
|
103
|
+
def convert_encoded_instructions(filename)
|
104
|
+
filename.gsub(/%(.*?)%/) do |initial_string|
|
105
|
+
call_public_method($1.strip) or initial_string
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#TODO: base.public_send(sym) rescue nil instead?
|
110
|
+
|
111
|
+
# Calls `base`'s public method `sym`.
|
112
|
+
# Returns:: result of `base.sym` or `nil` if `sym` wasn't found in
|
113
|
+
# `base`
|
114
|
+
# Raises:: NoMethodError if `sym` references
|
115
|
+
# a private method.
|
116
|
+
def call_public_method(sym)
|
117
|
+
if base.respond_to?(sym)
|
118
|
+
base.send(sym)
|
119
|
+
elsif base.respond_to?(sym, true)
|
120
|
+
raise NoMethodError,
|
121
|
+
"Method #{base.class}##{sym} should be public, not private"
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Receives a hash of options and just execute the block if some
|
128
|
+
# conditions are met.
|
129
|
+
#
|
130
|
+
def invoke_with_conflict_check(&block)
|
131
|
+
if exists?
|
132
|
+
on_conflict_behavior(&block)
|
133
|
+
else
|
134
|
+
say_status :create, :green
|
135
|
+
block.call unless pretend?
|
136
|
+
end
|
137
|
+
|
138
|
+
destination
|
139
|
+
end
|
140
|
+
|
141
|
+
# What to do when the destination file already exists.
|
142
|
+
#
|
143
|
+
def on_conflict_behavior(&block)
|
144
|
+
say_status :exist, :blue
|
145
|
+
end
|
146
|
+
|
147
|
+
# Shortcut to say_status shell method.
|
148
|
+
#
|
149
|
+
def say_status(status, color)
|
150
|
+
base.shell.say_status status, relative_destination, color if config[:verbose]
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|