1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
class Thor
module Actions
# This is the base class for templater actions, ie. that copies something
# from some directory (source) to another (destination).
#
# This implementation is completely based in Templater actions, created
# by Jonas Nicklas and Michael S. Klishin under MIT LICENSE.
#
class Templater #:nodoc:
attr_reader :base, :source, :destination, :given_destination, :relative_destination
# Initializes given the source and destination.
#
# ==== Parameters
# base<Thor::Base>:: A Thor::Base instance
# source<String>:: Relative path to the source of this file
# destination<String>:: Relative path to the destination of this file
# log_status<Boolean>:: If false, does not log the status. True by default.
# Templater log status does not accept color.
#
def initialize(base, source, destination, log_status=true)
@base, @log_status = base, log_status
self.source = source
self.destination = destination
end
# Returns the contents of the source file as a String. If render is
# available, a diff option is shown in the file collision menu.
#
# ==== Returns
# String:: The source file.
#
# def render
# end
# Checks if the destination file already exists.
#
# ==== Returns
# Boolean:: true if the file exists, false otherwise.
#
def exists?
::File.exists?(destination)
end
# Checks if the content of the file at the destination is identical to the rendered result.
#
# ==== Returns
# Boolean:: true if it is identical, false otherwise.
#
def identical?
exists? && (is_not_comparable? || ::File.read(destination) == render)
end
# Invokes the action. By default it adds to the file the content rendered,
# but you can modify in the subclass.
#
def invoke!
invoke_with_options!(base.options) do
::FileUtils.mkdir_p(::File.dirname(destination))
::File.open(destination, 'w'){ |f| f.write render }
end
end
# Revokes the action.
#
def revoke!
say_status :remove, :red
::FileUtils.rm_rf(destination) if !pretend? && exists?
end
protected
# Shortcut for pretend.
#
def pretend?
base.options[:pretend]
end
# A templater is comparable if responds to render. In such cases, we have
# to show the conflict menu to the user unless the files are identical.
#
def is_not_comparable?
!respond_to?(:render)
end
# Sets the absolute source value from a relative source value. Notice
# that we need to take into consideration both the source_root as the
# relative_root.
#
# Let's suppose that we are on the directory "dest", with source root set
# to "source" and with the following scenario:
#
# inside "bar" do
# copy_file "baz.rb"
# end
#
# In this case, the user wants to copy the file at "source/bar/baz.rb"
# to "dest/bar/baz.rb". If we don't take into account the relative_root
# (in this case, "bar"), it would copy the contents at "source/baz.rb".
#
def source=(source)
if source
@source = ::File.expand_path(base.find_in_source_paths(source.to_s))
end
end
# Sets the absolute destination value from a relative destination value.
# It also stores the given and relative destination. Let's suppose our
# script is being executed on "dest", it sets the destination root to
# "dest". The destination, given_destination and relative_destination
# are related in the following way:
#
# inside "bar" do
# empty_directory "baz"
# end
#
# destination #=> dest/bar/baz
# relative_destination #=> bar/baz
# given_destination #=> baz
#
def destination=(destination)
if destination
@given_destination = convert_encoded_instructions(destination.to_s)
@destination = ::File.expand_path(@given_destination, base.destination_root)
@relative_destination = base.relative_to_original_destination_root(@destination)
end
end
# Filenames in the encoded form are converted. If you have a file:
#
# %class_name%.rb
#
# It gets the class name from the base and replace it:
#
# user.rb
#
def convert_encoded_instructions(filename)
filename.gsub(/%(.*?)%/) do |string|
instruction = $1.strip
base.respond_to?(instruction) ? base.send(instruction) : string
end
end
# Receives a hash of options and just execute the block if some
# conditions are met.
#
def invoke_with_options!(options, &block)
if exists?
if is_not_comparable?
say_status :exist, :blue
elsif identical?
say_status :identical, :blue
else
force_or_skip_or_conflict(options[:force], options[:skip], &block)
end
else
say_status :create, :green
block.call unless pretend?
end
destination
end
# If force is true, run the action, otherwise check if it's not being
# skipped. If both are false, show the file_collision menu, if the menu
# returns true, force it, otherwise skip.
#
def force_or_skip_or_conflict(force, skip, &block)
if force
say_status :force, :yellow
block.call unless pretend?
elsif skip
say_status :skip, :yellow
else
say_status :conflict, :red
force_or_skip_or_conflict(force_on_collision?, true, &block)
end
end
# Shows the file collision menu to the user and gets the result.
#
def force_on_collision?
base.shell.file_collision(destination){ render }
end
# Shortcut to say_status shell method.
#
def say_status(status, color)
base.shell.say_status status, relative_destination, color if @log_status
end
end
end
end
|