ing 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|