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.
Files changed (105) hide show
  1. data/CHANGELOG +17 -0
  2. data/README +36 -504
  3. data/RELEASE_NOTES +126 -45
  4. data/Rakefile +215 -86
  5. data/TODO +8 -3
  6. data/doc/configuration.html +485 -0
  7. data/doc/directives.html +1085 -0
  8. data/doc/guide.html +243 -0
  9. data/doc/index.html +287 -0
  10. data/doc/installation.html +376 -0
  11. data/doc/stylesheets/masterview.css +206 -0
  12. data/doc/stylesheets/mv-config.css +23 -0
  13. data/doc/stylesheets/mv-directives.css +18 -0
  14. data/doc/stylesheets/mv-installation.css +10 -0
  15. data/doc/troubleshooting.html +18 -0
  16. data/examples/product.html +256 -0
  17. data/examples/product.html.old +107 -0
  18. data/examples/rails_app_config/masterview/environment/development.rb +22 -0
  19. data/examples/rails_app_config/masterview/environment/production.rb +9 -0
  20. data/examples/rails_app_config/masterview/settings.rb +59 -0
  21. data/examples/test.import +80 -0
  22. data/init.rb +26 -12
  23. data/lib/masterview/analyzer.rb +25 -15
  24. data/lib/masterview/directive_base.rb +4 -0
  25. data/lib/masterview/directive_helpers.rb +7 -5
  26. data/lib/masterview/directives/import_render.rb +6 -0
  27. data/lib/masterview/directives/insert_generated_comment.rb +8 -8
  28. data/lib/masterview/extras/app/controllers/masterview_controller.rb +154 -2
  29. data/lib/masterview/extras/app/views/masterview/admin/create.rhtml +4 -4
  30. data/lib/masterview/extras/app/views/masterview/admin/empty.rhtml +1 -1
  31. data/lib/masterview/extras/app/views/masterview/admin/list.rhtml +14 -9
  32. data/lib/masterview/extras/app/views/masterview/admin/view_rhtml.rhtml +70 -0
  33. data/lib/masterview/extras/init_logger.rb +102 -0
  34. data/lib/masterview/extras/init_rails_erb_mv_direct.rb +117 -0
  35. data/lib/masterview/extras/init_rails_reparse_checking.rb +59 -0
  36. data/lib/masterview/extras/watcher.rb +17 -23
  37. data/lib/masterview/filter_helpers.rb +26 -0
  38. data/lib/masterview/initializer.rb +912 -0
  39. data/lib/masterview/io.rb +352 -0
  40. data/lib/masterview/keyword_expander.rb +116 -0
  41. data/lib/masterview/masterview_version.rb +2 -2
  42. data/lib/masterview/mtime_tracking_hash.rb +44 -0
  43. data/lib/masterview/parser.rb +64 -92
  44. data/lib/masterview/pathname_extensions.rb +33 -0
  45. data/lib/masterview/template_spec.rb +49 -56
  46. data/lib/masterview.rb +40 -85
  47. data/test/fixtures/configs/fake_rails_app_with_config/config/masterview/environments/development.rb +12 -0
  48. data/test/fixtures/configs/fake_rails_app_with_config/config/masterview/environments/production.rb +11 -0
  49. data/test/fixtures/configs/fake_rails_app_with_config/config/masterview/settings.rb +23 -0
  50. data/test/fixtures/templates/product.html +256 -0
  51. data/test/fixtures/templates/test.import +80 -0
  52. data/test/test_helper.rb +5 -3
  53. data/test/tmp/template/foo.txt +1 -0
  54. data/test/tmp/templates_src/product.html +256 -0
  55. data/test/tmp/views/layouts/product.rhtml +35 -0
  56. data/test/tmp/views/product/_form.rhtml +30 -0
  57. data/test/tmp/views/product/_product.rhtml +14 -0
  58. data/test/tmp/views/product/_show.rhtml +27 -0
  59. data/test/tmp/views/product/destroy.rhtml +27 -0
  60. data/test/tmp/views/product/edit.rhtml +26 -0
  61. data/test/tmp/views/product/list.rhtml +31 -0
  62. data/test/tmp/views/product/new.rhtml +29 -0
  63. data/test/tmp/views/product/show.rhtml +16 -0
  64. data/test/unit/config_settings_test.rb +172 -0
  65. data/test/{attr_test.rb → unit/directive_attr_test.rb} +2 -2
  66. data/test/{block_test.rb → unit/directive_block_test.rb} +2 -2
  67. data/test/{content_test.rb → unit/directive_content_test.rb} +2 -2
  68. data/test/{else_test.rb → unit/directive_else_test.rb} +2 -2
  69. data/test/{elsif_test.rb → unit/directive_elsif_test.rb} +2 -2
  70. data/test/{form_test.rb → unit/directive_form_test.rb} +2 -2
  71. data/test/{global_inline_erb_test.rb → unit/directive_global_inline_erb_test.rb} +2 -2
  72. data/test/{hidden_field_test.rb → unit/directive_hidden_field_test.rb} +2 -2
  73. data/test/{if_test.rb → unit/directive_if_test.rb} +2 -2
  74. data/test/unit/directive_import_render_test.rb +62 -0
  75. data/test/{import_test.rb → unit/directive_import_test.rb} +2 -2
  76. data/test/{javascript_include_test.rb → unit/directive_javascript_include_test.rb} +2 -2
  77. data/test/{link_to_if_test.rb → unit/directive_link_to_if_test.rb} +2 -2
  78. data/test/{link_to_test.rb → unit/directive_link_to_test.rb} +2 -2
  79. data/test/{omit_tag_test.rb → unit/directive_omit_tag_test.rb} +2 -2
  80. data/test/{password_field_test.rb → unit/directive_password_field_test.rb} +2 -2
  81. data/test/{replace_test.rb → unit/directive_replace_test.rb} +2 -2
  82. data/test/{stylesheet_link_test.rb → unit/directive_stylesheet_link_test.rb} +2 -2
  83. data/test/{submit_test.rb → unit/directive_submit_test.rb} +2 -2
  84. data/test/{text_area_test.rb → unit/directive_text_area_test.rb} +2 -2
  85. data/test/{text_field_test.rb → unit/directive_text_field_test.rb} +2 -2
  86. data/test/{example_test.rb → unit/example_test.rb} +1 -1
  87. data/test/unit/file_mio_test.rb +368 -0
  88. data/test/{filter_helpers_test.rb → unit/filter_helpers_test.rb} +1 -1
  89. data/test/unit/keyword_expander_test.rb +95 -0
  90. data/test/unit/mio_test.rb +110 -0
  91. data/test/unit/mtime_string_hash_mio_tree_test.rb +289 -0
  92. data/test/unit/mtime_tracking_hash_test.rb +38 -0
  93. data/test/{parser_test.rb → unit/parser_test.rb} +19 -1
  94. data/test/unit/pathname_extensions_test.rb +46 -0
  95. data/test/{run_parser_test.rb → unit/run_parser_test.rb} +7 -3
  96. data/test/unit/string_hash_mio_test.rb +320 -0
  97. data/test/unit/template_file_watcher_test.rb +107 -0
  98. data/test/{template_spec_test.rb → unit/template_spec_test.rb} +57 -21
  99. data/test/{template_test.rb → unit/template_test.rb} +123 -22
  100. data/test/xtras/config-mv-logger_config.rb +109 -0
  101. data/test/xtras/config_initialize_standalone.rb +53 -0
  102. metadata +111 -38
  103. data/lib/masterview/extras/rails_init.rb +0 -72
  104. data/test/import_render_test.rb +0 -30
  105. data/test/template_file_watcher_test.rb +0 -50
@@ -178,37 +178,22 @@ module MasterView
178
178
  end
179
179
  end
180
180
 
181
- class InvalidPathException < Exception
182
- def initialize(msg)
183
- super(msg)
181
+
182
+
183
+ class MIOSerializer
184
+ def initialize(options)
185
+ raise RequiredArgumentMissingError.new("Required argument is missing, specify the MasterViewIO object in options[:output_mio_tree]") unless options[:output_mio_tree]
186
+ @options = options
187
+ @mio_tree = options[:output_mio_tree]
184
188
  end
185
- end
186
189
 
187
- # Serializer which can serialize output to file system.
188
- # It will create any directories that are necessary before writing the file.
189
- # It will overwrite any existing file that existed.
190
- class FileSerializer
191
190
  def serialize(render_mode, tag)
192
- Log.debug { "outputting mode=#{render_mode.mode_type} to file=#{render_mode.output}".indent(2*(Renderer.last_renderer.render_levels.size - 1)) }
193
- dir_name = File.dirname render_mode.output
194
- FileUtils.makedirs(dir_name) unless File.exist?(dir_name) #ensure path exists
195
-
196
191
  data_to_write = tag.data.join
197
- if File.exist? render_mode.output
198
- existing_file_contents = File.readlines(render_mode.output).join
199
- if data_to_write == existing_file_contents
200
- Log.debug { "file identical, skipping output of #{render_mode.output}" }
201
- return false
202
- end
203
- end
204
-
205
- File.open(render_mode.output, 'w') do |io|
206
- io << data_to_write
207
- end
208
- true
192
+ @mio_tree.path(render_mode.output).write(data_to_write, @options)
209
193
  end
210
194
  end
211
195
 
196
+ =begin
212
197
  # Serializer which simply serializes output to the console
213
198
  class ConsoleSerializer
214
199
  def serialize(render_mode, tag)
@@ -234,11 +219,20 @@ module MasterView
234
219
  true
235
220
  end
236
221
  end
222
+ =end
237
223
 
238
224
  class Renderer
239
225
  include DirectiveHelpers
240
- attr_reader :restrict_output_to_directory, :directive_load_paths, :mv_ns
241
- attr_accessor :render_levels, :directive_classes, :default_render_handler, :serializer, :template_path
226
+
227
+ # Set of element names that can be simplified. Used in simplify_empty_elements.
228
+ # Only simplify elements that are specified in the DTD as being empty, collapsing others can cause parsing
229
+ # or rendering problems in browsers.
230
+ # http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_XHTML-1.0-Strict
231
+ # base, meta, link, hr, br, param, img, area, input, col
232
+ XHTMLEmptyElementNameSet = %w{ base meta link hr br param img area input col }.to_set
233
+
234
+ attr_reader :directive_load_paths, :mv_ns, :keyword_expander, :default_extension
235
+ attr_accessor :render_levels, :directive_classes, :default_render_handler, :serializer, :template_pathname, :template_full_pathname
242
236
 
243
237
  def self.last_renderer; @@last_renderer; end
244
238
 
@@ -250,11 +244,14 @@ module MasterView
250
244
  ]
251
245
 
252
246
  serializer = options[:serializer] || DefaultSerializer
253
- self.serializer = serializer.is_a?(Class) ? serializer.new : serializer #one can pass in Serializer class or an instance
254
- self.restrict_output_to_directory = options[:output_dir] || nil
247
+ self.serializer = serializer.is_a?(Class) ? serializer.new(options) : serializer #one can pass in Serializer class or an instance
255
248
  self.mv_ns = options[:namespace] || NamespacePrefix
249
+ self.template_pathname = options[:template_pathname]
250
+ self.template_full_pathname = IOMgr.template.path(self.template_pathname).full_pathname if self.template_pathname
251
+ @default_extension = (options[:output_mio_tree]) ? options[:output_mio_tree].default_extension : IOMgr.erb.default_extension
252
+ @keyword_expander = KeywordExpander.new
253
+ @keyword_expander.set_template_pathname(self.template_pathname, @default_extension)
256
254
  self.directive_load_paths = ( DefaultDirectiveLoadPaths << options[:additional_directive_paths] ).flatten
257
- self.template_path = options[:template_path] || ''
258
255
  end
259
256
 
260
257
  def mv_ns=(namespace_prefix)
@@ -262,11 +259,6 @@ module MasterView
262
259
  Log.debug { 'namespace_prefix set to '+namespace_prefix }
263
260
  end
264
261
 
265
- def restrict_output_to_directory=(dir)
266
- @restrict_output_to_directory = (!dir.nil?) ? File.expand_path(dir) : nil
267
- Log.debug { 'restrict_output_to_directory set to '+@restrict_output_to_directory.to_s }
268
- end
269
-
270
262
  # Sets directive_load_paths, re-requiring all the new load paths, however any directives that were
271
263
  # already required (and loaded) will still be in memory because these are not reset.
272
264
  def directive_load_paths=( directive_paths )
@@ -274,7 +266,7 @@ module MasterView
274
266
  @auto_directives = []
275
267
  directive_paths.each do |directive_path|
276
268
  next if directive_path.nil?
277
- raise InvalidPathException.new('directive_path does not exist, path='+directive_path) unless File.exist? directive_path
269
+ raise InvalidPathError.new('directive_path does not exist, path='+directive_path) unless File.exist? directive_path
278
270
  Dir.open( directive_path ).each { |fn| require "#{directive_path}/#{fn}" if fn =~ /[.]rb$/ }
279
271
 
280
272
  end
@@ -305,23 +297,9 @@ module MasterView
305
297
  @render_levels.last.render_modes
306
298
  end
307
299
  def push_level(render_level)
308
- render_level.render_modes.each do |mode|
309
- enforce_sandbox!(mode.output)
310
- end
311
300
  @render_levels.push render_level
312
301
  end
313
302
 
314
- def enforce_sandbox!(path)
315
- unless @restrict_output_to_directory.nil?
316
- expanded_path = File.expand_path(path, @restrict_output_to_directory)
317
- unless expanded_path.starts_with? @restrict_output_to_directory
318
- raise InvalidPathException.new( "invalid path=#{path} resticted to path=#{@restrict_output_to_directory}")
319
- end
320
- path.replace expanded_path
321
- end
322
- end
323
-
324
-
325
303
  def push_tag(tag_name, attributes)
326
304
  modes.each do |mode|
327
305
  attributes_copy = attributes.clone #these get changed in select_active_directives
@@ -373,19 +351,35 @@ module MasterView
373
351
  pop_level if need_to_pop_level
374
352
  end
375
353
 
354
+
355
+ # Simplify (collapse) empty elements so that <foo></foo> from rexml parsing ends up being <foo/> .
356
+ # Only simplify elements that are specified in the DTD as being empty, collapsing others can cause parsing
357
+ # or rendering problems in browsers. Uses constant XHTMLEmptyElementNameSet to find elements that should be
358
+ # collapsed.
359
+ # http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_XHTML-1.0-Strict
360
+ # xhtml-1.0-Strict empty elements are:
361
+ # base, meta, link, hr, br, param, img, area, input, col
376
362
  def simplify_empty_elements(content) #relies on the fact that > and </ are individual strings and are back to back with nothing in between
377
363
  ret = []
364
+ current_element_name = nil
378
365
  next_to_last = nil
379
366
  last = nil
380
367
  content.flatten!
381
368
  content.each do |item|
382
- if next_to_last == '>' && last == '</'
369
+ if next_to_last == '>' && last == '</' && XHTMLEmptyElementNameSet.include?(current_element_name)
383
370
  ret.pop #remove '>'
384
371
  ret.pop #remove '</'
385
- ret << '/>'
372
+ ret << ' />' # adding in a space to make xhtml more compatible with html editors and older browsers
386
373
  else
387
374
  ret << item
388
375
  end
376
+ unless item.nil?
377
+ if !item.starts_with?('</') && item.starts_with?('<')
378
+ current_element_name = /^<(\S*)/.match(item)[1] # depending on what is after < this might be empty string
379
+ elsif last && last.starts_with?('</')
380
+ current_element_name = nil
381
+ end
382
+ end
389
383
  next_to_last = last
390
384
  last = item
391
385
  end
@@ -474,20 +468,20 @@ module MasterView
474
468
  @renderer.append_raw ERB_EVAL+value+ERB_END
475
469
  end
476
470
 
477
- # handle a mv:gen_render attribute, which calls generate and outputs a token
471
+ # handle a mv:gen_partial attribute, which calls generate and outputs a token
478
472
  # it takes an optional :dir => 'foo/bar' which is prepended to partial path,
479
473
  # otherwise it just uses what is in partial.
480
474
  # This creates a generate attribute value which will be used later.
481
475
  # Parameters
482
- # value = attribute value for gen_render
476
+ # value = attribute value for gen_partial
483
477
  # attributes = all remaining attributes hash
484
- def handle_gen_render(attributes)
485
- value = attributes.delete(@renderer.mv_ns+'gen_render')
478
+ def handle_gen_partial(attributes)
479
+ value = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.mv_ns+'gen_partial')
486
480
  if value
487
481
  prepend_dir = find_string_val_in_string_hash(value, :dir) #only used for masterview
488
482
  partial = find_string_val_in_string_hash(value, :partial)
489
483
  return if partial.nil?
490
- path = render_partial_name_to_file_name(partial)
484
+ path = render_partial_name_to_file_name(partial, @renderer.default_extension)
491
485
  path = File.join(prepend_dir, path) if prepend_dir
492
486
  generate_attribute = attributes[@renderer.mv_ns+'generate'] || '' # check if we need to add to existing generate
493
487
  generate_attribute = path + (generate_attribute.blank? ? '' : ', '+generate_attribute)
@@ -497,14 +491,15 @@ module MasterView
497
491
  end
498
492
 
499
493
  def push_levels(attributes)
500
- handle_gen_render(attributes)
494
+ handle_gen_partial(attributes)
501
495
 
502
- gen_replace = attributes.delete(@renderer.mv_ns+'gen_replace') #get and delete from map
496
+ gen_replace = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.mv_ns+'gen_replace') #get and delete from map
503
497
  generate_replace( gen_replace ) unless gen_replace.nil?
504
498
 
505
- gen = attributes.delete(@renderer.mv_ns+'generate') #get and delete from map
499
+ gen = @renderer.keyword_expander.resolveAttrAndDelete(attributes, @renderer.mv_ns+'generate') #get and delete from map
506
500
  if gen
507
- attributes[@renderer.mv_ns+'insert_generated_comment'] = @renderer.template_path unless OmitGeneratedComments #add the comment directive, so it will be written to each gen'd file
501
+ omit_comment = OmitGeneratedComments || File.extname(gen) != MasterView::IOMgr.erb.default_extension
502
+ attributes[@renderer.mv_ns+'insert_generated_comment'] = @renderer.template_full_pathname.to_s unless omit_comment #add the comment directive, so it will be written to each gen'd file
508
503
  render_level = nil
509
504
  gen_values = parse_eval_into_hash(gen, :normal)
510
505
 
@@ -515,7 +510,7 @@ module MasterView
515
510
  arr_values = (value.is_a?(Enumerable)) ? value : [value] #if not enumerable add it to array
516
511
  value.each do |path|
517
512
  path.strip!
518
- Log.debug { ('pushing mode='+mode_type.to_s+' path='+path).indent(2*@renderer.render_levels.size) }
513
+ #Log.debug { ('pushing mode='+mode_type.to_s+' path='+path).indent(2*@renderer.render_levels.size) }
519
514
  render_level ||= RenderLevel.new
520
515
  render_level.push RenderMode.new(path, mode_type)
521
516
  end
@@ -528,31 +523,26 @@ module MasterView
528
523
  end
529
524
 
530
525
  class Parser
531
- # parse a MasterView template by first reading from file and render output.
532
- # template_file_path param is file path to template
533
- # options are the optional parameters which control the output (:output_dir, :namespace)
534
- def self.parse_file( template_file_path, output_dir, options = DefaultParserOptions.clone)
535
- output_dir ||= '.'
536
- Log.debug { "Parsing file=#{File.expand_path(template_file_path)} output_dir=#{File.expand_path(output_dir)}" }
537
- options[:template_path]=File.expand_path(template_file_path)
538
- options[:output_dir] = output_dir
539
- template = File.new( template_file_path )
540
- template = template.readlines.join if options[:tidy] || options[:escape_erb]
526
+ def self.parse_mio( template_mio, output_mio_tree, options = {})
527
+ options[:template_pathname]= template_mio.pathname
528
+ options[:output_mio_tree] = output_mio_tree
529
+ template = template_mio.read
541
530
  self.parse( template, options )
542
531
  end
543
532
 
533
+
544
534
  # parse a MasterView template and render output.
545
535
  # template param is actual template source passed in as string or array.
546
- # options are the optional parameters which control the output (:output_dir, :namespace, :serializer)
536
+ # options are the optional parameters which control the output (:output_mio_tree, :namespace, :serializer)
547
537
  def self.parse( template, options = DefaultParserOptions.clone)
548
538
  options[:listeners] ||= [MasterViewListener]
549
539
  options[:rescue_exceptions] = RescueExceptions unless options.include?(:rescue_exceptions)
550
540
 
551
541
  begin
552
542
  if options[:tidy]
553
- template = self.tidy(template)
543
+ template = TidyHelper.tidy(template)
554
544
  elsif options[:escape_erb]
555
- template = self.escape_erb(template)
545
+ template = EscapeErbHelper.escape_erb(template)
556
546
  end
557
547
 
558
548
  parser = REXML::Parsers::SAX2Parser.new( template )
@@ -574,24 +564,6 @@ module MasterView
574
564
  end
575
565
  end
576
566
 
577
- def self.tidy(html)
578
- Tidy.path = TidyPath unless Tidy.path
579
- xml = Tidy.open do |tidy|
580
- tidy.options.output_xml = true
581
- tidy.options.indent = true
582
- tidy.options.wrap = 0
583
- xml = tidy.clean(html)
584
- end
585
- xml = self.escape_erb(xml)
586
- Log.debug { 'tidy corrected xml='+xml }
587
- xml
588
- end
589
-
590
- def self.escape_erb(html)
591
- html = html.gsub(/<%/, InlineErbStart)
592
- html.gsub!(/%>/, InlineErbEnd)
593
- html
594
- end
595
567
  end
596
568
 
597
569
  end
@@ -0,0 +1,33 @@
1
+ require 'pathname'
2
+
3
+ class Pathname
4
+ # create a new Pathname object for path, any backslashes are converted to /,
5
+ # if Pathname object is passed in then returns this existing pathname object
6
+ def self.for_path(path)
7
+ return path if path.is_a? Pathname
8
+ Pathname.new(path.gsub('\\','/'))
9
+ end
10
+
11
+ # return the concatenated path, cleaning up any backslashes, ignores nulls or empty pathnames
12
+ def self.join(*args)
13
+ pathnames = args.collect { |a| Pathname.for_path(a) }
14
+ pathnames.inject { |fullpath, p| self.safe_concat(fullpath, p) }
15
+ end
16
+
17
+ # prepends a pathname to existing, ignores nulls or empty pathnames
18
+ def self.safe_concat(prepend_pn, pathname)
19
+ return nil if ((prepend_pn.nil? || prepend_pn.to_s.empty?) && (pathname.nil? || pathname.to_s.empty?)) #both are nil or empty
20
+ return prepend_pn if (pathname.nil? || pathname.to_s.empty?)
21
+ return pathname if (prepend_pn.nil? || prepend_pn.to_s.empty?)
22
+ prepend_pn+pathname
23
+ end
24
+
25
+ # return the path string without the extension
26
+ def path_no_ext
27
+ dirname = self.dirname
28
+ ext = self.extname
29
+ base = self.basename(ext)
30
+ presult = (dirname.to_s.empty?) ? base : dirname+base
31
+ presult.to_s
32
+ end
33
+ end
@@ -26,36 +26,35 @@ module MasterView
26
26
  File.basename @path
27
27
  end
28
28
 
29
+ # get path relative to working dir
30
+ def long_path
31
+ IOMgr.template.path(@path).full_pathname.to_s
32
+ end
33
+
29
34
  # scan the directory of templates, building template_specs and the content_hash
30
35
  def self.scan(options = {}, &block)
31
36
  content_hash = {}
32
37
  template_specs = {}
33
- files = []
34
- Find.find(File.join('app/views', TemplateSrcRelativePath)){ |f| files << f }
35
- files.sort!
36
38
 
37
- files.each do |path|
38
- if !File.directory?(path) && File.fnmatch?(TemplateFilenamePattern, path)
39
- template_specs[path] = scan_template_file(path, content_hash)
40
- end
39
+ IOMgr.template.find(:pattern => TemplateFilenamePattern) do |mio|
40
+ template_specs[mio.pathname.to_s] = scan_template_mio(mio, content_hash)
41
41
  end
42
42
 
43
- files.each do |path|
44
- if !File.directory?(path) && File.fnmatch?(TemplateFilenamePattern, path)
45
- if template_specs[path].status == Status::OK
46
- invalid_parts = template_file_out_of_sync?(path, content_hash)
47
- template_spec = template_specs[path]
48
- template_spec.update_status_from_invalid_parts(invalid_parts)
49
- end
50
- yield template_specs[path], content_hash if block
43
+ IOMgr.template.find(:pattern => TemplateFilenamePattern) do |mio|
44
+ path = mio.pathname.to_s
45
+ if template_specs[path].status == Status::OK
46
+ invalid_parts = template_mio_out_of_sync?(path, content_hash)
47
+ template_spec = template_specs[path]
48
+ template_spec.update_status_from_invalid_parts(invalid_parts)
51
49
  end
50
+ yield template_specs[path], content_hash if block_given?
52
51
  end
53
-
54
52
  return template_specs, content_hash
55
53
  end
56
54
 
57
- def self.scan_template_file(path, content_hash = {} )
58
- template = File.new(path)
55
+ def self.scan_template_mio(mio, content_hash = {} )
56
+ template = mio.read(:disable_cache => true)
57
+ path = mio.pathname.to_s
59
58
  self.scan_template(template, path, content_hash)
60
59
  end
61
60
 
@@ -69,7 +68,7 @@ module MasterView
69
68
  # create a template_spec
70
69
  def self.scan_template(template, path, content_hash = {})
71
70
  template_spec = TemplateSpec.new(path)
72
- listener = MasterView::Analyzer::Listener.new
71
+ listener = MasterView::Analyzer::Listener.new( :template_pathname => Pathname.for_path(path) )
73
72
  begin
74
73
  MasterView::Parser.parse( template, :rescue_exceptions => false, :listeners => [listener])
75
74
  template_spec.build_list = listener.list
@@ -89,14 +88,16 @@ module MasterView
89
88
  template_spec
90
89
  end
91
90
 
92
- def self.template_file_out_of_sync?(path, content_hash)
93
- self.template_out_of_sync?(File.new(path), content_hash)
91
+ def self.template_mio_out_of_sync?(path, content_hash)
92
+ mio = IOMgr.template.path(path)
93
+ template = mio.read(:disable_cache => true)
94
+ self.template_out_of_sync?(template, path, content_hash)
94
95
  end
95
96
 
96
97
  # check if the template is out of sync with the source content (check imports), return array of invalid
97
- def self.template_out_of_sync?(template, content_hash)
98
+ def self.template_out_of_sync?(template, path, content_hash)
98
99
  invalid = []
99
- listener = MasterView::Analyzer::Listener.new(:content_hash => content_hash, :only_check_hash => true)
100
+ listener = MasterView::Analyzer::Listener.new(:content_hash => content_hash, :template_pathname => Pathname.for_path(path), :only_check_hash => true)
100
101
  Parser.parse( template, :rescue_exceptions => false, :listeners => [listener] )
101
102
  invalid_list_items = listener.list.find_all { |li| li.hash_invalid? }
102
103
  invalid_with_dups = invalid_list_items.collect { |li| li.name }
@@ -107,8 +108,8 @@ module MasterView
107
108
  # rebuild template updating all imports, returns the string contents,
108
109
  # if options[:write_to_file] = true then it will write the contents to file if different and return true, if identical then returns false
109
110
  # otherwise this method returns the content of the template
110
- # raise error for any other problems. options[:backup] = true to make backup before rebuilding, uses DirectoryForRebuildBackups to determine
111
- # path for where to store backup files. If DirectoryForRebuildBackups is nil, then no backup is created.
111
+ # raise error for any other problems. options[:backup] = true to make backup before rebuilding, uses MasterView::IOMgr.backup to determine
112
+ # path for where to store backup files. If MasterView::IOMgr.backup is nil, then no backup is created.
112
113
  def rebuild_template(content_hash, options = {} )
113
114
  out = []
114
115
  builder = MasterView::Analyzer::Builder.new(content_hash)
@@ -117,20 +118,18 @@ module MasterView
117
118
  con = builder.data(li.name, li.index)
118
119
  if li.import
119
120
  con = con.gsub NamespacePrefix+'generate', NamespacePrefix+'import'
120
- con.gsub! NamespacePrefix+'gen_render', NamespacePrefix+'import_render'
121
+ con.gsub! NamespacePrefix+'gen_partial', NamespacePrefix+'import_render'
121
122
  end
122
123
  out << con
123
124
  end
124
125
  template = out.join
125
126
  if options[:write_to_file]
126
- backup = !options[:backup].nil? ? options[:backup] : DirectoryForRebuildBackups
127
- orig = File.readlines(self.path).join
127
+ backup = !options[:backup].nil? ? options[:backup] : !IOMgr.backup.nil?
128
+ orig = IOMgr.template.path(self.path).read(:disable_cache => true)
128
129
  file_written = false
129
130
  unless template == orig
130
131
  self.backup_file if backup
131
- File.open(self.path, 'w') do |io|
132
- io << template
133
- end
132
+ IOMgr.template.path(self.path).write(template, :force => true) #force write in case tidy had cleaned up
134
133
  file_written = true
135
134
  end
136
135
  return file_written
@@ -141,11 +140,11 @@ module MasterView
141
140
 
142
141
  # create backup file by appending secs since epoch to filename
143
142
  def backup_file(options={} )
144
- return unless DirectoryForRebuildBackups
145
- FileUtils.makedirs(DirectoryForRebuildBackups) unless File.exist?(DirectoryForRebuildBackups) #ensure path exists
146
- dst = File.join(DirectoryForRebuildBackups, File.basename(self.path)+'.'+Time.new.to_i.to_s)
147
- Log.debug { 'creating backup file '+dst }
148
- FileUtils.cp self.path, dst
143
+ return unless IOMgr.backup
144
+ contents_to_backup = IOMgr.template.path(self.path).read
145
+ backup_path = self.path+'.'+Time.new.to_i.to_s
146
+ Log.debug { 'creating backup file '+backup_path }
147
+ IOMgr.backup.path(backup_path).write(contents_to_backup)
149
148
  end
150
149
 
151
150
  # create empty shell file consisting of layout and a comment for where to insert new content, return contents
@@ -179,6 +178,7 @@ module MasterView
179
178
  end
180
179
  end
181
180
 
181
+
182
182
  # create empty shell file consisting of layout and a comment for where to insert new content. Use action_to_create
183
183
  # to infer the destination name controller_action.html and to customize the inserted place holder. Pass the
184
184
  # empty insert erb (rhtml) content in which will be rendered with appropriate controller and action values.
@@ -187,36 +187,29 @@ module MasterView
187
187
  # :template_source => source_for_template (use this source instead of reading from file)
188
188
  # :content_hash => use this content_hash, otherwise it will scan the masterview template directory to create content_hash
189
189
  def self.create_empty_shell_for_action(path_to_copy_shell_from, action_to_create, empty_insert_erb, options={} )
190
- short_name = File.basename(path_to_copy_shell_from)
191
- controller_name = nil
192
- extension = nil
193
- short_name.scan( /^([^_.]+)_?([^.]*)(\.?\w*)$/ ) do |c, a, e|
194
- controller_name = c
195
- extension = e
196
- end
197
- extension = '.html' if extension.empty?
198
- short_name = File.basename(short_name, extension)+extension #ensure short_name has extension
199
-
190
+ path = IOMgr.template.cleanup_path_get_relative_pathname(path_to_copy_shell_from).to_s
191
+ controller_name = Pathname.for_path(path).dirname.to_s
192
+ extension = IOMgr.template.default_extension
200
193
  erb_values = CreateShellERBValues.new(controller_name, action_to_create)
201
194
  template = ERB.new(empty_insert_erb)
202
195
  content_to_insert = template.result(erb_values.get_binding).strip #clear off surrounding whitespace that makes it difficult to debug
203
196
 
204
- src_file = File.join('app/views', MasterView::TemplateSrcRelativePath, short_name)
205
- dst_file = File.join('app/views', MasterView::TemplateSrcRelativePath, erb_values.controller_file_name+'_'+erb_values.action_name+extension)
206
- src = (tsrc = options[:template_source]) ? tsrc : File.readlines(src_file).join
197
+ basename_with_extension = erb_values.action_name
198
+ basename_with_extension += extension if extension
199
+ dst_path = (Pathname.for_path(path).dirname+(basename_with_extension)).to_s
200
+ src = (tsrc = options[:template_source]) ? tsrc : IOMgr.template.path(path).read
207
201
 
208
202
  template_specs = {}
209
203
  content_hash = options[:content_hash]
210
204
  template_specs, content_hash = TemplateSpec.scan unless content_hash
211
- template_spec_to_copy = template_specs[src_file] || TemplateSpec.scan_template(src, src_file)
205
+ template_spec_to_copy = template_specs[path] || TemplateSpec.scan_template(src, path)
212
206
 
213
- result = MasterView::TemplateSpec.create_empty_shell( template_spec_to_copy, content_hash, content_to_insert)
207
+ result = TemplateSpec.create_empty_shell( template_spec_to_copy, content_hash, content_to_insert)
214
208
  if options[:write_to_file]
215
- raise 'File '+dst_file+' already exists, operation aborted.' if File.exist? dst_file
216
- File.open(dst_file, 'w') do |io|
217
- io << result
218
- end
219
- return dst_file
209
+ template_mio = IOMgr.template.path(dst_path)
210
+ raise 'Template '+dst_path+' already exists, operation aborted.' if template_mio.exist?
211
+ template_mio.write(result)
212
+ return dst_path
220
213
  end
221
214
  result
222
215
  end