masterview 0.1.5 → 0.2.0
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/CHANGELOG +17 -0
- data/README +36 -504
- data/RELEASE_NOTES +126 -45
- data/Rakefile +215 -86
- data/TODO +8 -3
- data/doc/configuration.html +485 -0
- data/doc/directives.html +1085 -0
- data/doc/guide.html +243 -0
- data/doc/index.html +287 -0
- data/doc/installation.html +376 -0
- data/doc/stylesheets/masterview.css +206 -0
- data/doc/stylesheets/mv-config.css +23 -0
- data/doc/stylesheets/mv-directives.css +18 -0
- data/doc/stylesheets/mv-installation.css +10 -0
- data/doc/troubleshooting.html +18 -0
- data/examples/product.html +256 -0
- data/examples/product.html.old +107 -0
- data/examples/rails_app_config/masterview/environment/development.rb +22 -0
- data/examples/rails_app_config/masterview/environment/production.rb +9 -0
- data/examples/rails_app_config/masterview/settings.rb +59 -0
- data/examples/test.import +80 -0
- data/init.rb +26 -12
- data/lib/masterview/analyzer.rb +25 -15
- data/lib/masterview/directive_base.rb +4 -0
- data/lib/masterview/directive_helpers.rb +7 -5
- data/lib/masterview/directives/import_render.rb +6 -0
- data/lib/masterview/directives/insert_generated_comment.rb +8 -8
- data/lib/masterview/extras/app/controllers/masterview_controller.rb +154 -2
- data/lib/masterview/extras/app/views/masterview/admin/create.rhtml +4 -4
- data/lib/masterview/extras/app/views/masterview/admin/empty.rhtml +1 -1
- data/lib/masterview/extras/app/views/masterview/admin/list.rhtml +14 -9
- data/lib/masterview/extras/app/views/masterview/admin/view_rhtml.rhtml +70 -0
- data/lib/masterview/extras/init_logger.rb +102 -0
- data/lib/masterview/extras/init_rails_erb_mv_direct.rb +117 -0
- data/lib/masterview/extras/init_rails_reparse_checking.rb +59 -0
- data/lib/masterview/extras/watcher.rb +17 -23
- data/lib/masterview/filter_helpers.rb +26 -0
- data/lib/masterview/initializer.rb +912 -0
- data/lib/masterview/io.rb +352 -0
- data/lib/masterview/keyword_expander.rb +116 -0
- data/lib/masterview/masterview_version.rb +2 -2
- data/lib/masterview/mtime_tracking_hash.rb +44 -0
- data/lib/masterview/parser.rb +64 -92
- data/lib/masterview/pathname_extensions.rb +33 -0
- data/lib/masterview/template_spec.rb +49 -56
- data/lib/masterview.rb +40 -85
- data/test/fixtures/configs/fake_rails_app_with_config/config/masterview/environments/development.rb +12 -0
- data/test/fixtures/configs/fake_rails_app_with_config/config/masterview/environments/production.rb +11 -0
- data/test/fixtures/configs/fake_rails_app_with_config/config/masterview/settings.rb +23 -0
- data/test/fixtures/templates/product.html +256 -0
- data/test/fixtures/templates/test.import +80 -0
- data/test/test_helper.rb +5 -3
- data/test/tmp/template/foo.txt +1 -0
- data/test/tmp/templates_src/product.html +256 -0
- data/test/tmp/views/layouts/product.rhtml +35 -0
- data/test/tmp/views/product/_form.rhtml +30 -0
- data/test/tmp/views/product/_product.rhtml +14 -0
- data/test/tmp/views/product/_show.rhtml +27 -0
- data/test/tmp/views/product/destroy.rhtml +27 -0
- data/test/tmp/views/product/edit.rhtml +26 -0
- data/test/tmp/views/product/list.rhtml +31 -0
- data/test/tmp/views/product/new.rhtml +29 -0
- data/test/tmp/views/product/show.rhtml +16 -0
- data/test/unit/config_settings_test.rb +172 -0
- data/test/{attr_test.rb → unit/directive_attr_test.rb} +2 -2
- data/test/{block_test.rb → unit/directive_block_test.rb} +2 -2
- data/test/{content_test.rb → unit/directive_content_test.rb} +2 -2
- data/test/{else_test.rb → unit/directive_else_test.rb} +2 -2
- data/test/{elsif_test.rb → unit/directive_elsif_test.rb} +2 -2
- data/test/{form_test.rb → unit/directive_form_test.rb} +2 -2
- data/test/{global_inline_erb_test.rb → unit/directive_global_inline_erb_test.rb} +2 -2
- data/test/{hidden_field_test.rb → unit/directive_hidden_field_test.rb} +2 -2
- data/test/{if_test.rb → unit/directive_if_test.rb} +2 -2
- data/test/unit/directive_import_render_test.rb +62 -0
- data/test/{import_test.rb → unit/directive_import_test.rb} +2 -2
- data/test/{javascript_include_test.rb → unit/directive_javascript_include_test.rb} +2 -2
- data/test/{link_to_if_test.rb → unit/directive_link_to_if_test.rb} +2 -2
- data/test/{link_to_test.rb → unit/directive_link_to_test.rb} +2 -2
- data/test/{omit_tag_test.rb → unit/directive_omit_tag_test.rb} +2 -2
- data/test/{password_field_test.rb → unit/directive_password_field_test.rb} +2 -2
- data/test/{replace_test.rb → unit/directive_replace_test.rb} +2 -2
- data/test/{stylesheet_link_test.rb → unit/directive_stylesheet_link_test.rb} +2 -2
- data/test/{submit_test.rb → unit/directive_submit_test.rb} +2 -2
- data/test/{text_area_test.rb → unit/directive_text_area_test.rb} +2 -2
- data/test/{text_field_test.rb → unit/directive_text_field_test.rb} +2 -2
- data/test/{example_test.rb → unit/example_test.rb} +1 -1
- data/test/unit/file_mio_test.rb +368 -0
- data/test/{filter_helpers_test.rb → unit/filter_helpers_test.rb} +1 -1
- data/test/unit/keyword_expander_test.rb +95 -0
- data/test/unit/mio_test.rb +110 -0
- data/test/unit/mtime_string_hash_mio_tree_test.rb +289 -0
- data/test/unit/mtime_tracking_hash_test.rb +38 -0
- data/test/{parser_test.rb → unit/parser_test.rb} +19 -1
- data/test/unit/pathname_extensions_test.rb +46 -0
- data/test/{run_parser_test.rb → unit/run_parser_test.rb} +7 -3
- data/test/unit/string_hash_mio_test.rb +320 -0
- data/test/unit/template_file_watcher_test.rb +107 -0
- data/test/{template_spec_test.rb → unit/template_spec_test.rb} +57 -21
- data/test/{template_test.rb → unit/template_test.rb} +123 -22
- data/test/xtras/config-mv-logger_config.rb +109 -0
- data/test/xtras/config_initialize_standalone.rb +53 -0
- metadata +111 -38
- data/lib/masterview/extras/rails_init.rb +0 -72
- data/test/import_render_test.rb +0 -30
- data/test/template_file_watcher_test.rb +0 -50
@@ -0,0 +1,352 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'pathname'
|
3
|
+
require 'stringio'
|
4
|
+
require File.join( File.dirname(__FILE__), 'pathname_extensions' )
|
5
|
+
require File.join( File.dirname(__FILE__), 'mtime_tracking_hash' )
|
6
|
+
require File.join( File.dirname(__FILE__), '../facets/core/string/starts_with' )
|
7
|
+
require File.join( File.dirname(__FILE__), 'filter_helpers' )
|
8
|
+
|
9
|
+
module MasterView
|
10
|
+
# IOManager which retrieves the proper MasterViewIOTree object for the type of object being requested
|
11
|
+
# set and access MasterViewIOTree by using accessors
|
12
|
+
# IOMgr.template = FileMIOTree()
|
13
|
+
# ftree = IOMgr.template
|
14
|
+
# IOMgr.erb = FileMIOTree()
|
15
|
+
# etree = IOMgr.erb
|
16
|
+
# Once the mioTree is obtained it may be used for obtaining mio objects for reading and writing
|
17
|
+
class MIOTrees < OpenStruct
|
18
|
+
end
|
19
|
+
|
20
|
+
# MIOTreeInterface
|
21
|
+
# path(path)
|
22
|
+
# find(options, &block) options[:path] = 'foo' limits find to dir foo
|
23
|
+
# cleanup_path_get_relative_pathname(path)
|
24
|
+
|
25
|
+
# MIOInterface
|
26
|
+
# pathname()
|
27
|
+
# full_pathname() # for FileMIO returns full_pathname otherwise for all others identical to pathname
|
28
|
+
# read(options={}) options[:disable_cache]=true bypasses cache filter, options[:disable_logging] = true turns off log of this event
|
29
|
+
# write(content=nil, options={}, &block) options[:disable_logging]=true turns off log of this event options[:force] = true forces the write even if identical
|
30
|
+
# exist?(path)
|
31
|
+
# identical?(content)
|
32
|
+
# mtime()
|
33
|
+
|
34
|
+
|
35
|
+
# mixin which encapsulates the apply filter to new MIO objects and
|
36
|
+
# the default filter block
|
37
|
+
module MasterViewIOTreeMixin
|
38
|
+
DefaultExtensionInstances = {}
|
39
|
+
|
40
|
+
# apply filters to object, use block if provided otherwise use
|
41
|
+
# default_mio_filter_block which is customized by options hash
|
42
|
+
# options[:escape_erb] = true enables erb escaping
|
43
|
+
# options[:tidy] = true enables tidy processing to cleanup bad xhtml
|
44
|
+
# options[:caching] = true enables caching so that reads are cached
|
45
|
+
# options[:logging] = true enables logging of reads and writes
|
46
|
+
def apply_filters(mio, options, block)
|
47
|
+
if block
|
48
|
+
block.call(mio)
|
49
|
+
else
|
50
|
+
default_mio_filter_block(options).call(mio)
|
51
|
+
end
|
52
|
+
mio
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_mio_filter_block(options)
|
56
|
+
lambda do |mio|
|
57
|
+
mio.extend EscapeErbMIOFilter if options[:escape_erb]
|
58
|
+
mio.extend TidyMIOFilter if options[:tidy]
|
59
|
+
mio.extend CachingMIOFilter if options[:caching]
|
60
|
+
mio.extend ReadWriteLoggingMIOFilter if options[:logging]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_extension
|
65
|
+
DefaultExtensionInstances[object_id]
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_extension=(default_extension)
|
69
|
+
DefaultExtensionInstances[object_id] = default_extension
|
70
|
+
end
|
71
|
+
|
72
|
+
# clean up ../.. check if path starts with root path and if so get relative, otherwise return path, root_path is only
|
73
|
+
# used in FileMIO, so all others just simply clean the path. FileMIO will override this with its own implementation.
|
74
|
+
def cleanup_path_get_relative_pathname(path)
|
75
|
+
pathname = Pathname.for_path(path)
|
76
|
+
pathname = Pathname.for_path(pathname.to_s+self.default_extension) if pathname.extname.empty? && self.default_extension && !self.default_extension.empty?
|
77
|
+
pathname
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class FileMIOTree < Pathname
|
82
|
+
include MasterViewIOTreeMixin
|
83
|
+
def initialize(root_path=Pathname.getwd, default_extension=nil, options = {}, &block)
|
84
|
+
@options = options
|
85
|
+
self.default_extension = default_extension
|
86
|
+
super(Pathname.for_path(root_path).cleanpath)
|
87
|
+
@new_mio_block = block
|
88
|
+
end
|
89
|
+
|
90
|
+
def path(path)
|
91
|
+
raise InvalidPathError.new('Invalid path specified ('+path.to_s+'). Path is limited to directories under root_path ('+self.to_s+')') unless (self+path).expand_path.to_s.starts_with?(self.expand_path.to_s) #ensure sandbox
|
92
|
+
mio = FileMIO.new(path, self + path)
|
93
|
+
apply_filters(mio, @options, @new_mio_block)
|
94
|
+
end
|
95
|
+
|
96
|
+
# returns mio objects, if block_given then it yields passing the mio object to the block
|
97
|
+
def find(options = {}, &block)
|
98
|
+
path = options[:path] || '.'
|
99
|
+
found = []
|
100
|
+
working_path = Pathname.safe_concat(self, path)
|
101
|
+
working_path.find { |p| found << p unless p.directory? }
|
102
|
+
found = found.select { |f| f.basename.fnmatch?(options[:pattern]) } if options[:pattern]
|
103
|
+
found = found.collect{ |f| self.path(f.relative_path_from(self)) } # create mio objects
|
104
|
+
found = found.sort { |a,b| a.pathname.to_s <=> b.pathname.to_s }
|
105
|
+
found.each { |mio| yield mio } if block_given?
|
106
|
+
found
|
107
|
+
end
|
108
|
+
|
109
|
+
# clean up ../.. check if path starts with root path and if so get relative, otherwise return path
|
110
|
+
def cleanup_path_get_relative_pathname(path)
|
111
|
+
pathname = Pathname.for_path(path)
|
112
|
+
pathname = pathname.relative_path_from(self) if pathname.to_s.starts_with?(self.to_s)
|
113
|
+
pathname = Pathname.for_path(pathname.to_s+self.default_extension) if pathname.extname.empty? && self.default_extension && !self.default_extension.empty?
|
114
|
+
pathname
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# MIO implemented as simple hash of path to string content
|
119
|
+
# This MIOTree does not keep track of mtimes and thus will always return new mtimes
|
120
|
+
class StringHashMIOTree
|
121
|
+
include MasterViewIOTreeMixin
|
122
|
+
attr_reader :string_hash
|
123
|
+
def initialize(string_hash = {}, default_extension=nil, options={}, &block)
|
124
|
+
@string_hash = string_hash
|
125
|
+
@options = options
|
126
|
+
self.default_extension = default_extension
|
127
|
+
@new_mio_block = block
|
128
|
+
end
|
129
|
+
|
130
|
+
def string_hash=(string_hash)
|
131
|
+
@string_hash = string_hash
|
132
|
+
end
|
133
|
+
|
134
|
+
def path(path)
|
135
|
+
clean_path = Pathname.for_path(path).cleanpath
|
136
|
+
mio = StringMIO.new(clean_path, @string_hash)
|
137
|
+
mio.pathname = clean_path
|
138
|
+
apply_filters(mio, @options, @new_mio_block)
|
139
|
+
end
|
140
|
+
|
141
|
+
def find(options = {})
|
142
|
+
path = options[:path] || ''
|
143
|
+
path = '' if path == '.'
|
144
|
+
found = []
|
145
|
+
@string_hash.each { |p,v| found << p if p.starts_with?(path) }
|
146
|
+
found = found.select { |p| Pathname.for_path(p).basename.fnmatch?(options[:pattern]) } if options[:pattern]
|
147
|
+
found.sort!
|
148
|
+
found = found.collect{ |p| self.path(p) } # create mio objects
|
149
|
+
found.each { |mio| yield mio } if block_given?
|
150
|
+
found
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
class ActiveRecordMIOTree #todo
|
156
|
+
include MasterViewIOTreeMixin
|
157
|
+
def initialize
|
158
|
+
self.default_extension = default_extension
|
159
|
+
end
|
160
|
+
|
161
|
+
def path(path)
|
162
|
+
end
|
163
|
+
|
164
|
+
def find(options, &block)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# MIOTree which works like StringHashMIO but uses MTimeTrackingHash to track mod times
|
169
|
+
class MTimeStringHashMIOTree < StringHashMIOTree
|
170
|
+
|
171
|
+
def initialize(default_extension=nil, options={}, &block)
|
172
|
+
super(MTimeTrackingHash.new, default_extension, options)
|
173
|
+
end
|
174
|
+
|
175
|
+
def path(path)
|
176
|
+
clean_path = Pathname.for_path(path).cleanpath
|
177
|
+
mio = MTimeStringMIO.new(clean_path, @string_hash)
|
178
|
+
mio.pathname = clean_path
|
179
|
+
apply_filters(mio, @options, @new_mio_block)
|
180
|
+
end
|
181
|
+
|
182
|
+
def string_hash=(mtime_tracking_hash)
|
183
|
+
raise "mtime_tracking_hash passed in does not respond_to mtime method call" unless mtime_tracking_hash.respond_to?(:mtime)
|
184
|
+
@string_hash = mtime_tracking_hash
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
# mio which allows skipping the intermediate erb file generation in rails.
|
190
|
+
# Current implementation inherits its functionality from MTimeStringHashMIOTree
|
191
|
+
class RailsErbCacheMIOTree < MTimeStringHashMIOTree
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
class MIOBase
|
196
|
+
attr_accessor :pathname
|
197
|
+
|
198
|
+
def initialize(path)
|
199
|
+
@pathname = Pathname.for_path(path)
|
200
|
+
end
|
201
|
+
|
202
|
+
# for mio objects other than FileMIO, full_pathname is same as pathname
|
203
|
+
def full_pathname
|
204
|
+
@pathname
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
module MasterViewIOIdenticalMixin
|
209
|
+
def identical?(compare_content)
|
210
|
+
self.exist? && compare_content == self.read(:disable_logging => true ) # eliminate the noise, simply checking if different
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
class FileMIO < Pathname
|
216
|
+
include MasterViewIOIdenticalMixin
|
217
|
+
attr_reader :pathname
|
218
|
+
|
219
|
+
def initialize(path, full_path)
|
220
|
+
super(full_path)
|
221
|
+
@pathname = Pathname.for_path(path)
|
222
|
+
end
|
223
|
+
|
224
|
+
def full_pathname
|
225
|
+
self
|
226
|
+
end
|
227
|
+
|
228
|
+
def read(options = {})
|
229
|
+
super()
|
230
|
+
end
|
231
|
+
|
232
|
+
def write(content = nil, options={}, &block)
|
233
|
+
written = false
|
234
|
+
if block_given?
|
235
|
+
sio = ::StringIO.new
|
236
|
+
yield sio
|
237
|
+
content = sio.string
|
238
|
+
end
|
239
|
+
if options[:force] || !identical?(content)
|
240
|
+
self.dirname.mkpath unless self.dirname.exist?
|
241
|
+
self.open('w') do |io|
|
242
|
+
io << content
|
243
|
+
end
|
244
|
+
written = true
|
245
|
+
end
|
246
|
+
written
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
class StringMIO < MIOBase
|
251
|
+
include MasterViewIOIdenticalMixin
|
252
|
+
attr_accessor :exist
|
253
|
+
attr_reader :mtime
|
254
|
+
|
255
|
+
def initialize(path, string_hash, mtime = Time.new)
|
256
|
+
super(path)
|
257
|
+
@string_hash = string_hash
|
258
|
+
@mtime = mtime
|
259
|
+
end
|
260
|
+
|
261
|
+
def read(options = {})
|
262
|
+
@string_hash[self.pathname.to_s]
|
263
|
+
end
|
264
|
+
|
265
|
+
def write(content = nil, options = {}, &block)
|
266
|
+
written = false
|
267
|
+
if block_given?
|
268
|
+
sio = ::StringIO.new
|
269
|
+
yield sio
|
270
|
+
content = sio.string
|
271
|
+
end
|
272
|
+
|
273
|
+
if options[:force] || !self.identical?(content)
|
274
|
+
@string_hash[self.pathname.to_s] = content
|
275
|
+
@mtime = Time.now
|
276
|
+
written = true
|
277
|
+
end
|
278
|
+
written
|
279
|
+
end
|
280
|
+
|
281
|
+
def exist?
|
282
|
+
@string_hash.has_key?(self.pathname.to_s)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# used by MTimeStringHashMIOTree to create MIO objects that check
|
287
|
+
# their mod time using the MTimeTrackingHash
|
288
|
+
class MTimeStringMIO < StringMIO
|
289
|
+
def initialize(path, mtime_tracking_hash)
|
290
|
+
super(path, mtime_tracking_hash, nil)
|
291
|
+
end
|
292
|
+
|
293
|
+
def mtime
|
294
|
+
@string_hash.mtime(self.pathname.to_s)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
class ActiveRecordMIO < MIOBase
|
300
|
+
include MasterViewIOIdenticalMixin
|
301
|
+
def exist?; end
|
302
|
+
def mtime; end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
|
307
|
+
module CachingMIOFilter #todo
|
308
|
+
def read(options={})
|
309
|
+
if options[:disable_cache]
|
310
|
+
super
|
311
|
+
else #caching on
|
312
|
+
super #todo obtain from cache
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
module TidyMIOFilter
|
318
|
+
def read(options={})
|
319
|
+
content = super
|
320
|
+
::MasterView::TidyHelper.tidy(content)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
module EscapeErbMIOFilter
|
325
|
+
def read(options={})
|
326
|
+
content = super
|
327
|
+
::MasterView::EscapeErbHelper.escape_erb(content)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
module ReadWriteLoggingMIOFilter #todo
|
332
|
+
def read(options={})
|
333
|
+
Log.debug { "reading file="+self.full_pathname } unless options[:disable_logging]
|
334
|
+
super
|
335
|
+
end
|
336
|
+
|
337
|
+
def write(content = nil, options = {}, &block)
|
338
|
+
Log.debug { "writing file="+self.full_pathname } unless options[:disable_logging]
|
339
|
+
super
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
|
344
|
+
# raised when an invalid path is given, or if the path is outside of the root_path
|
345
|
+
class InvalidPathError < RuntimeError
|
346
|
+
end
|
347
|
+
|
348
|
+
# raised when a method is not called with the appropriate arguments
|
349
|
+
class InvalidArgumentError < RuntimeError
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module MasterView
|
2
|
+
# Keyword expander is used to hold variables defined to be used in expansion of
|
3
|
+
# attributes.
|
4
|
+
class KeywordExpander
|
5
|
+
|
6
|
+
#--
|
7
|
+
# keywords related to path information about the template being processed
|
8
|
+
#++
|
9
|
+
# relative path of the template being processed within the template src dir
|
10
|
+
# (does *not* include the template flie extension)
|
11
|
+
KW_TEMPLATE_PATH = '{template_path}'
|
12
|
+
# relative path of the directory containing the template being processed
|
13
|
+
# within the template src dir.
|
14
|
+
# ('' if template is directly contained in the templates src dir)
|
15
|
+
KW_TEMPLATE_DIR_PATH = '{template_dir_path}'
|
16
|
+
# base filename of the template being processed (no file extension)
|
17
|
+
KW_TEMPLATE_BASENAME = '{template_basename}'
|
18
|
+
# filename extension of the template being processed (ordinarily '.html')
|
19
|
+
KW_EXTENSION = '{extension}'
|
20
|
+
# the default extension for generated output (ordinarily '.rhthml')
|
21
|
+
KW_DEFAULT_EXTENSION = '{default_extension}'
|
22
|
+
# list of all supported keywords related to the template being processed
|
23
|
+
TEMPLATE_KEYWORDS = [ KW_TEMPLATE_PATH, KW_TEMPLATE_DIR_PATH, KW_TEMPLATE_BASENAME, KW_EXTENSION ]
|
24
|
+
|
25
|
+
def initialize(hash = {})
|
26
|
+
@hash = hash
|
27
|
+
build_key_value_array_sorted_by_desc_length
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sets a variety of path related keywords:
|
31
|
+
#
|
32
|
+
# MasterView supports the following keyword expansions for
|
33
|
+
# mv:generate, mv:gen_partial, mv:import, and mv:import_render
|
34
|
+
#
|
35
|
+
#--
|
36
|
+
# The default generated output extension is MasterView::GeneratedFileDefaultExtension,
|
37
|
+
# per the masterview config.output_filename_extension setting.
|
38
|
+
#++
|
39
|
+
#
|
40
|
+
# For template file <code>one/foo/bar.html</code> when default generated output extension '.rhtml':
|
41
|
+
# {template_path} == use original masterview template path with default output extension (one/foo/bar.rhtml)
|
42
|
+
# {template_path}.ext == use original masterview template path with the specified extension (one/foo/bar.ext)
|
43
|
+
# {template_dir_path} == direct_parent_dirname (one/foo)
|
44
|
+
# {template_basename} == basename (bar)
|
45
|
+
# {extension} == extension (.html)
|
46
|
+
#
|
47
|
+
# For example:
|
48
|
+
# with MasterView template file <code>one/two/three.html</code> and default generated output extension '.rhtml'
|
49
|
+
# mv:generate="{template_path}" expands to mv:generate="one/two/three.rhtml" (default generation output extension used)
|
50
|
+
# mv:generate="{template_path}.rcss" expands to mv:generate="one/two/three.rcss"
|
51
|
+
# mv:generate="{template_dir_path}/{template_basename}" expands to mv:generate="one/two/three" (no extension)
|
52
|
+
# mv:generate="{template_dir_path}/{template_basename}.rcss" expands to mv:generate="one/two/three.rcss"
|
53
|
+
# mv:generate="{template_dir_path}/{template_basename}-bar.rtxt" expands to mv:generate="one/two/three-bar.rtxt"
|
54
|
+
# mv:generate="somewhere/{template_basename}-bar{extension}" expands to mv:generate="somewhere/three-bar.html"
|
55
|
+
#
|
56
|
+
# Note: All path values use forward slashes
|
57
|
+
#
|
58
|
+
def set_template_pathname(template_pathname, default_erb_extension)
|
59
|
+
return if template_pathname.nil?
|
60
|
+
pn = Pathname.for_path(template_pathname)
|
61
|
+
default_erb_extension = '' if default_erb_extension.nil?
|
62
|
+
@hash[KW_TEMPLATE_PATH] = convert_pathname_to_s(pn.path_no_ext) #defer decision: + default_erb_extension
|
63
|
+
@hash[KW_TEMPLATE_DIR_PATH] = convert_pathname_to_s(pn.dirname)
|
64
|
+
@hash[KW_TEMPLATE_BASENAME] = convert_pathname_to_s(pn.basename(pn.extname))
|
65
|
+
@hash[KW_EXTENSION] = pn.extname
|
66
|
+
@hash[KW_DEFAULT_EXTENSION] = default_erb_extension
|
67
|
+
build_key_value_array_sorted_by_desc_length
|
68
|
+
end
|
69
|
+
|
70
|
+
# set the value, regenerate a reverse sorted hash value array so that it will
|
71
|
+
# replace the longer replacement keys before the shorter ones, so FOO_BAR will be
|
72
|
+
# replaced before FOO
|
73
|
+
def set(key, value)
|
74
|
+
@hash[key] = value
|
75
|
+
build_key_value_array_sorted_by_desc_length
|
76
|
+
end
|
77
|
+
|
78
|
+
def [](key)
|
79
|
+
@hash[key]
|
80
|
+
end
|
81
|
+
|
82
|
+
# expand all keywords, return value
|
83
|
+
def expand_keywords(str)
|
84
|
+
# maybe consider: add parm check_template_path=false that mv:generate processor would turn on
|
85
|
+
# so that ext. defaulting cleverness only applies in that specific context.
|
86
|
+
# Might be overaggressive (or just unnecessary) to this this on *all* substitution mappings
|
87
|
+
# [DJL 18-Jun-2006]
|
88
|
+
return nil if str.nil?
|
89
|
+
append_default_ext = str.ends_with?(KW_TEMPLATE_PATH) and @hash.has_key?(KW_DEFAULT_EXTENSION)
|
90
|
+
val = str.clone
|
91
|
+
@hash_values_by_desc_key_length.each { |k,v| val.gsub!(k,v) }
|
92
|
+
val << @hash[KW_DEFAULT_EXTENSION] if append_default_ext
|
93
|
+
val
|
94
|
+
end
|
95
|
+
|
96
|
+
# finds the attribute using the key deleting it from hash, expands value using current binding
|
97
|
+
# which allows KEYWORD substitutions before returning string, return nil if not found
|
98
|
+
def resolveAttrAndDelete(attributes, key)
|
99
|
+
attr_value = attributes.delete(key)
|
100
|
+
attr_value = expand_keywords(attr_value) unless attr_value.nil?
|
101
|
+
attr_value
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# convert pathname to string, for '.' we want empty string
|
107
|
+
def convert_pathname_to_s(pathname)
|
108
|
+
(pathname.to_s == '.') ? '' : pathname.to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_key_value_array_sorted_by_desc_length
|
112
|
+
@hash_values_by_desc_key_length = @hash.sort { |a,b| b[0].length <=> a[0].length } # longest keys first
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MasterView
|
2
|
+
|
3
|
+
# this hash is designed to track modified times of its values
|
4
|
+
# this behaviour is only supported through using []= and store
|
5
|
+
# mechanisms and thus default semantics have been excluded
|
6
|
+
# other update methods such as merge! have been excluded from
|
7
|
+
# this implementation and will raise errors
|
8
|
+
class MTimeTrackingHash < Hash
|
9
|
+
|
10
|
+
# MTimeTrackingHash does not support defaults and thus does
|
11
|
+
# not take default object or block
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
@mtimes = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
update_mtime(key)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def store(key, value)
|
23
|
+
update_mtime(key)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns the Time object for this key
|
28
|
+
def mtime(key)
|
29
|
+
@mtimes[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def update_mtime(key)
|
35
|
+
@mtimes[key] = Time.now
|
36
|
+
end
|
37
|
+
|
38
|
+
def default=(obj); raise "Not implemented"; end
|
39
|
+
def merge!(obj,&block); raise "Not implemented"; end
|
40
|
+
def reject!(obj,&block); raise "Not implemented"; end
|
41
|
+
def replace(obj); raise "Not implemented"; end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|