masterview 0.0.17 → 0.1.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 +4 -0
- data/README +22 -6
- data/RELEASE_NOTES +26 -3
- data/Rakefile +1 -0
- data/TODO +2 -0
- data/init.rb +1 -0
- data/lib/masterview/analyzer.rb +252 -0
- data/lib/masterview/directive_helpers.rb +10 -0
- data/lib/masterview/directives/import.rb +22 -0
- data/lib/masterview/directives/import_render.rb +23 -0
- data/lib/masterview/extras/app/controllers/masterview_controller.rb +78 -0
- data/lib/masterview/extras/app/views/masterview/admin/create.rhtml +96 -0
- data/lib/masterview/extras/app/views/masterview/admin/empty.rhtml +15 -0
- data/lib/masterview/extras/app/views/masterview/admin/list.rhtml +97 -0
- data/lib/masterview/extras/rails_init.rb +5 -0
- data/lib/masterview/extras/watcher.rb +12 -11
- data/lib/masterview/masterview_version.rb +2 -2
- data/lib/masterview/parser.rb +29 -18
- data/lib/masterview/template_spec.rb +226 -0
- data/lib/masterview.rb +16 -1
- data/test/import_render_test.rb +30 -0
- data/test/import_test.rb +29 -0
- data/test/template_file_watcher_test.rb +1 -1
- data/test/template_spec_test.rb +344 -0
- data/test/template_test.rb +76 -0
- metadata +18 -2
@@ -0,0 +1,96 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Masterview Admin</title>
|
4
|
+
|
5
|
+
<%= stylesheet_link_tag 'scaffold' %>
|
6
|
+
<%= stylesheet_link_tag 'sidebox' %>
|
7
|
+
<%= stylesheet_link_tag 'color-scheme' %>
|
8
|
+
<%= javascript_include_tag :defaults %>
|
9
|
+
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
|
13
|
+
<!-- ###### Header ###### -->
|
14
|
+
|
15
|
+
<div id="header">
|
16
|
+
<span class="headerTitle">Admin</span>
|
17
|
+
<div class="menuBar">
|
18
|
+
<%= link_to 'Home', :action => :index %>
|
19
|
+
<!-- | <a href="">Another link</a> -->
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
|
24
|
+
<div class="main">
|
25
|
+
|
26
|
+
<div class="create_div" id="mv_admin_create"><!-- ###### create ###### -->
|
27
|
+
<div class="mv_admin_create sidebar LHS">
|
28
|
+
<h2>Tasks:</h2>
|
29
|
+
<ul>
|
30
|
+
<li><%= link_to 'Back to overview', :action => 'list' %></li>
|
31
|
+
</ul>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<div class="mv_admin_create content">
|
35
|
+
<h1>Create Empty Shell Template</h1>
|
36
|
+
|
37
|
+
<% if @flash[:notice] %>
|
38
|
+
<div class="messages" id="admin_messages">
|
39
|
+
<%= h @flash[:notice] %>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
42
|
+
|
43
|
+
<div class="form_div mv_admin_create_form_div">
|
44
|
+
|
45
|
+
<form action="<%= url_for :controller => 'masterview', :action => 'create' %>" method="post">
|
46
|
+
|
47
|
+
<p>Submitting this form will copy/import the layout from the source MasterView template file and will
|
48
|
+
create a new MasterView template file (controller_action.html) containing the imported layout. By providing
|
49
|
+
a action name, it will create some default placeholder content in the file. It will
|
50
|
+
not create any controller class methods or models.
|
51
|
+
</p>
|
52
|
+
|
53
|
+
<div class="record">
|
54
|
+
<div class="label"><label for="id">Source:</label></div>
|
55
|
+
<div class="field">
|
56
|
+
<%= File.basename(params[:id], '.html') %> (Template to copy layout from)
|
57
|
+
<input type="hidden" name="id" value="<%= params[:id] %>"
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
|
61
|
+
<div class="record">
|
62
|
+
<div class="label"><label for="action_name">Action:</label></div>
|
63
|
+
<div class="field"><input type="text" name="action_name"/> (example: list)</div>
|
64
|
+
</div>
|
65
|
+
|
66
|
+
<input type="submit" value="Create"/>
|
67
|
+
|
68
|
+
</form>
|
69
|
+
|
70
|
+
</div>
|
71
|
+
|
72
|
+
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
|
76
|
+
</div>
|
77
|
+
|
78
|
+
<!-- ###### Footer ###### -->
|
79
|
+
|
80
|
+
<div id="footer">
|
81
|
+
<div class="footerLHS">
|
82
|
+
<a href="http://validator.w3.org/check/referer">Valid XHTML 1.0 Strict</a>
|
83
|
+
</div>
|
84
|
+
|
85
|
+
<div class="footerLHS">
|
86
|
+
<a href="http://jigsaw.w3.org/css-validator/check/referer">Valid CSS 2</a>
|
87
|
+
</div>
|
88
|
+
|
89
|
+
<div>
|
90
|
+
Powered by MasterView
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
|
94
|
+
|
95
|
+
</body>
|
96
|
+
</html>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="<%= @action_name %>_div" id="<%= @controller_file_name %>_<%= @action_name %>" mv:generate="<%= @controller_view_dir_name %>/<%= @action_name %>.rhtml"> <!-- ###### <%= @action_name %> ###### -->
|
2
|
+
<div class="<%= @controller_file_name %>_<%= @action_name %> sidebar LHS">
|
3
|
+
<h2>Tasks:</h2>
|
4
|
+
<ul>
|
5
|
+
<li><a class="list_link" href="#" mv:link_to=":action => 'list'">Back to overview</a></li>
|
6
|
+
</ul>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="<%= @controller_file_name %>_<%= @action_name %> content">
|
10
|
+
<h1><%= @action_name %></h1>
|
11
|
+
|
12
|
+
<!-- <%= @action_name %> view rhtml goes here -->
|
13
|
+
|
14
|
+
</div>
|
15
|
+
</div>
|
@@ -0,0 +1,97 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Masterview Admin</title>
|
4
|
+
|
5
|
+
<%= stylesheet_link_tag 'scaffold' %>
|
6
|
+
<%= stylesheet_link_tag 'sidebox' %>
|
7
|
+
<%= stylesheet_link_tag 'color-scheme' %>
|
8
|
+
<%= javascript_include_tag :defaults %>
|
9
|
+
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
|
13
|
+
<!-- ###### Header ###### -->
|
14
|
+
|
15
|
+
<div id="header">
|
16
|
+
<span class="headerTitle">Admin</span>
|
17
|
+
<div class="menuBar">
|
18
|
+
<%= link_to 'Home', :action => :index %>
|
19
|
+
<!-- | <a href="">Another link</a> -->
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
|
24
|
+
<div class="main">
|
25
|
+
|
26
|
+
<div class="mv_admin_list sidebar LHS">
|
27
|
+
<h2>Tasks:</h2>
|
28
|
+
<ul>
|
29
|
+
<li><%= link_to 'Rebuild all outdated templates', :action => 'rebuild_all' %></li>
|
30
|
+
</ul>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div class="mv_admin_list content">
|
34
|
+
<h1>MasterView Admin</h1>
|
35
|
+
|
36
|
+
<% if @flash[:notice] %>
|
37
|
+
<div class="messages" id="admin_messages">
|
38
|
+
<%= h @flash[:notice] %>
|
39
|
+
</div>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
|
43
|
+
<table border="1">
|
44
|
+
<tr>
|
45
|
+
<th>Template</th>
|
46
|
+
<th>Status</th>
|
47
|
+
<th>Operations</th>
|
48
|
+
<th>Messages</th>
|
49
|
+
<th>Generated Files</th>
|
50
|
+
</tr>
|
51
|
+
<% @template_specs_sorted.each do |path, template_spec| %>
|
52
|
+
<tr>
|
53
|
+
<td><%= template_spec.basename %></td>
|
54
|
+
<td>
|
55
|
+
<%= template_spec.status %>
|
56
|
+
</td>
|
57
|
+
<td>
|
58
|
+
<% if template_spec.status == MasterView::TemplateSpec::Status::ImportsOutdated %>
|
59
|
+
<%= link_to 'Rebuild', :action => :rebuild, :id => path %>
|
60
|
+
<% end %>
|
61
|
+
|
62
|
+
<% if template_spec.status == MasterView::TemplateSpec::Status::OK %>
|
63
|
+
<%= link_to 'Copy', :action => 'create', :id => path %>
|
64
|
+
<% end %>
|
65
|
+
</td>
|
66
|
+
<td><%= template_spec.message %></td>
|
67
|
+
<td>
|
68
|
+
<% template_spec.gen_parts.each do |part| %>
|
69
|
+
<%= part %>
|
70
|
+
<% end %>
|
71
|
+
</td>
|
72
|
+
</tr>
|
73
|
+
<% end %>
|
74
|
+
</table>
|
75
|
+
</div>
|
76
|
+
|
77
|
+
</div>
|
78
|
+
|
79
|
+
<!-- ###### Footer ###### -->
|
80
|
+
|
81
|
+
<div id="footer">
|
82
|
+
<div class="footerLHS">
|
83
|
+
<a href="http://validator.w3.org/check/referer">Valid XHTML 1.0 Strict</a>
|
84
|
+
</div>
|
85
|
+
|
86
|
+
<div class="footerLHS">
|
87
|
+
<a href="http://jigsaw.w3.org/css-validator/check/referer">Valid CSS 2</a>
|
88
|
+
</div>
|
89
|
+
|
90
|
+
<div>
|
91
|
+
Powered by MasterView
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
|
95
|
+
|
96
|
+
</body>
|
97
|
+
</html>
|
@@ -36,6 +36,11 @@
|
|
36
36
|
require 'masterview'
|
37
37
|
require 'masterview/extras/watcher'
|
38
38
|
|
39
|
+
if MasterView::EnableMasterViewAdminPages
|
40
|
+
# add our app directories to the load path
|
41
|
+
Dir[File.join(File.dirname(__FILE__), 'app/**/*')].select{ |d| File.directory?(d) }.each{|d| $LOAD_PATH.push d }
|
42
|
+
end
|
43
|
+
|
39
44
|
# only run if auto parsing and when launching server, check if need to update mv files
|
40
45
|
if MasterView::AutoParseMasterViewFiles && $PROGRAM_NAME =~ /server|dispatch/
|
41
46
|
mvtemplate_src_root = File.join( ActionController::Base.template_root, MasterView::TemplateSrcRelativePath)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
1
3
|
module MasterView
|
2
4
|
class TemplateFileWatcher
|
3
5
|
# Check path for filenames matching filename_pattern and if found
|
@@ -8,18 +10,17 @@ module MasterView
|
|
8
10
|
# returns an array of files that were modified and the block was run for
|
9
11
|
def self.check(path, filename_pattern, check_subdirectories, &block)
|
10
12
|
files_run = []
|
11
|
-
return unless File.exist?(path)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
mtime = File.mtime(full_name)
|
13
|
+
return files_run unless File.exist?(path)
|
14
|
+
|
15
|
+
full_path = File.expand_path(path)
|
16
|
+
Find.find(full_path) do |f|
|
17
|
+
if !File.directory?(f) && File.fnmatch?(filename_pattern, f)
|
18
|
+
mtime = File.mtime(f)
|
18
19
|
@@file_mtimes ||= {}
|
19
|
-
unless @@file_mtimes[
|
20
|
-
yield
|
21
|
-
@@file_mtimes[
|
22
|
-
files_run <<
|
20
|
+
unless @@file_mtimes[f] == mtime
|
21
|
+
yield f
|
22
|
+
@@file_mtimes[f] = File.mtime(f)
|
23
|
+
files_run << f
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
data/lib/masterview/parser.rb
CHANGED
@@ -481,30 +481,29 @@ module MasterView
|
|
481
481
|
# Parameters
|
482
482
|
# value = attribute value for gen_render
|
483
483
|
# attributes = all remaining attributes hash
|
484
|
-
def
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
484
|
+
def handle_gen_render(attributes)
|
485
|
+
value = attributes.delete(@renderer.mv_ns+'gen_render')
|
486
|
+
if value
|
487
|
+
prepend_dir = find_string_val_in_string_hash(value, :dir) #only used for masterview
|
488
|
+
partial = find_string_val_in_string_hash(value, :partial)
|
489
|
+
return if partial.nil?
|
490
|
+
path = render_partial_name_to_file_name(partial)
|
491
|
+
path = File.join(prepend_dir, path) if prepend_dir
|
492
|
+
generate_attribute = attributes[@renderer.mv_ns+'generate'] || '' # check if we need to add to existing generate
|
493
|
+
generate_attribute = path + (generate_attribute.blank? ? '' : ', '+generate_attribute)
|
494
|
+
attributes[@renderer.mv_ns+'generate'] = generate_attribute
|
495
|
+
@renderer.append_raw( ERB_EVAL+'render( '+value+' )'+ERB_END )
|
496
|
+
end
|
497
497
|
end
|
498
498
|
|
499
499
|
def push_levels(attributes)
|
500
|
-
|
501
|
-
generate_render( gen_render, attributes ) unless gen_render.nil?
|
500
|
+
handle_gen_render(attributes)
|
502
501
|
|
503
502
|
gen_replace = attributes.delete(@renderer.mv_ns+'gen_replace') #get and delete from map
|
504
503
|
generate_replace( gen_replace ) unless gen_replace.nil?
|
505
504
|
|
506
505
|
gen = attributes.delete(@renderer.mv_ns+'generate') #get and delete from map
|
507
|
-
|
506
|
+
if gen
|
508
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
|
509
508
|
render_level = nil
|
510
509
|
gen_values = parse_eval_into_hash(gen, :normal)
|
@@ -523,6 +522,7 @@ module MasterView
|
|
523
522
|
end
|
524
523
|
@renderer.push_level(render_level) unless render_level.nil?
|
525
524
|
end
|
525
|
+
|
526
526
|
end
|
527
527
|
|
528
528
|
end
|
@@ -532,6 +532,7 @@ module MasterView
|
|
532
532
|
# template_file_path param is file path to template
|
533
533
|
# options are the optional parameters which control the output (:output_dir, :namespace)
|
534
534
|
def self.parse_file( template_file_path, output_dir, options = DefaultParserOptions.clone)
|
535
|
+
output_dir ||= '.'
|
535
536
|
Log.debug { "Parsing file=#{File.expand_path(template_file_path)} output_dir=#{File.expand_path(output_dir)}" }
|
536
537
|
options[:template_path]=File.expand_path(template_file_path)
|
537
538
|
options[:output_dir] = output_dir
|
@@ -544,17 +545,27 @@ module MasterView
|
|
544
545
|
# template param is actual template source passed in as string or array.
|
545
546
|
# options are the optional parameters which control the output (:output_dir, :namespace, :serializer)
|
546
547
|
def self.parse( template, options = DefaultParserOptions.clone)
|
548
|
+
options[:listeners] ||= [MasterViewListener]
|
549
|
+
options[:rescue_exceptions] = RescueExceptions unless options.include?(:rescue_exceptions)
|
550
|
+
|
547
551
|
begin
|
548
552
|
if options[:tidy]
|
549
553
|
template = self.tidy(template)
|
550
554
|
elsif options[:escape_erb]
|
551
555
|
template = self.escape_erb(template)
|
552
556
|
end
|
557
|
+
|
553
558
|
parser = REXML::Parsers::SAX2Parser.new( template )
|
554
|
-
|
559
|
+
options[:listeners].each do |listener|
|
560
|
+
if listener.is_a? Class
|
561
|
+
parser.listen( listener.new(options) )
|
562
|
+
else
|
563
|
+
parser.listen(listener)
|
564
|
+
end
|
565
|
+
end
|
555
566
|
parser.parse
|
556
567
|
rescue Exception => e
|
557
|
-
if
|
568
|
+
if options[:rescue_exceptions]
|
558
569
|
Log.error { "Failure to parse template. Exception="+e }
|
559
570
|
Log.debug { e.backtrace.join("\n") }
|
560
571
|
else
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module MasterView
|
2
|
+
|
3
|
+
# TemplateSpec is a class which contains the information about how to build a
|
4
|
+
# template. It contains the information on all the parts that make up the template
|
5
|
+
# and the status of them (whether they are up to date or out of sync).
|
6
|
+
class TemplateSpec
|
7
|
+
attr_accessor :path, :status, :message, :gen_parts, :build_list
|
8
|
+
|
9
|
+
class Status
|
10
|
+
OK = 'OK'
|
11
|
+
ImportsOutdated = 'Imports(s) outdated'
|
12
|
+
Conflicts = 'Conflict(s)'
|
13
|
+
InvalidXHTML = 'Invalid XHTML'
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(path = nil)
|
17
|
+
@path = path
|
18
|
+
@status = ''
|
19
|
+
@message = ''
|
20
|
+
@gen_parts = []
|
21
|
+
@build_list = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# get short base name of path
|
25
|
+
def basename
|
26
|
+
File.basename @path
|
27
|
+
end
|
28
|
+
|
29
|
+
# scan the directory of templates, building template_specs and the content_hash
|
30
|
+
def self.scan(options = {}, &block)
|
31
|
+
content_hash = {}
|
32
|
+
template_specs = {}
|
33
|
+
files = []
|
34
|
+
Find.find(File.join('app/views', TemplateSrcRelativePath)){ |f| files << f }
|
35
|
+
files.sort!
|
36
|
+
|
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
|
41
|
+
end
|
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
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return template_specs, content_hash
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.scan_template_file(path, content_hash = {} )
|
58
|
+
template = File.new(path)
|
59
|
+
self.scan_template(template, path, content_hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_status_from_invalid_parts(invalid_parts)
|
63
|
+
unless invalid_parts.empty?
|
64
|
+
@status = Status::ImportsOutdated
|
65
|
+
@message = "Outdated parts: "+invalid_parts.join(', ')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# create a template_spec
|
70
|
+
def self.scan_template(template, path, content_hash = {})
|
71
|
+
template_spec = TemplateSpec.new(path)
|
72
|
+
listener = MasterView::Analyzer::Listener.new
|
73
|
+
begin
|
74
|
+
MasterView::Parser.parse( template, :rescue_exceptions => false, :listeners => [listener])
|
75
|
+
template_spec.build_list = listener.list
|
76
|
+
conflicts = content_hash.keys & listener.content.keys
|
77
|
+
content_hash.merge! listener.content
|
78
|
+
if conflicts.empty?
|
79
|
+
template_spec.status = Status::OK
|
80
|
+
else
|
81
|
+
template_spec.status = Status::Conflicts
|
82
|
+
template_spec.message = 'Duplicated generation of: ' + conflicts.sort.join(', ')
|
83
|
+
end
|
84
|
+
template_spec.gen_parts = listener.content.keys.sort
|
85
|
+
rescue REXML::ParseException => e
|
86
|
+
template_spec.status = Status::InvalidXHTML
|
87
|
+
template_spec.message = e.to_s
|
88
|
+
end
|
89
|
+
template_spec
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.template_file_out_of_sync?(path, content_hash)
|
93
|
+
self.template_out_of_sync?(File.new(path), content_hash)
|
94
|
+
end
|
95
|
+
|
96
|
+
# 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
|
+
invalid = []
|
99
|
+
listener = MasterView::Analyzer::Listener.new(:content_hash => content_hash, :only_check_hash => true)
|
100
|
+
Parser.parse( template, :rescue_exceptions => false, :listeners => [listener] )
|
101
|
+
invalid_list_items = listener.list.find_all { |li| li.hash_invalid? }
|
102
|
+
invalid_with_dups = invalid_list_items.collect { |li| li.name }
|
103
|
+
invalid = invalid_with_dups.uniq.sort
|
104
|
+
invalid
|
105
|
+
end
|
106
|
+
|
107
|
+
# rebuild template updating all imports, returns the string contents,
|
108
|
+
# 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
|
+
# 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.
|
112
|
+
def rebuild_template(content_hash, options = {} )
|
113
|
+
out = []
|
114
|
+
builder = MasterView::Analyzer::Builder.new(content_hash)
|
115
|
+
@build_list.each do |li|
|
116
|
+
#Log.debug { li.inspect }
|
117
|
+
con = builder.data(li.name, li.index)
|
118
|
+
if li.import
|
119
|
+
con = con.gsub NamespacePrefix+'generate', NamespacePrefix+'import'
|
120
|
+
con.gsub! NamespacePrefix+'gen_render', NamespacePrefix+'import_render'
|
121
|
+
end
|
122
|
+
out << con
|
123
|
+
end
|
124
|
+
template = out.join
|
125
|
+
if options[:write_to_file]
|
126
|
+
backup = !options[:backup].nil? ? options[:backup] : DirectoryForRebuildBackups
|
127
|
+
orig = File.readlines(self.path).join
|
128
|
+
file_written = false
|
129
|
+
unless template == orig
|
130
|
+
self.backup_file if backup
|
131
|
+
File.open(self.path, 'w') do |io|
|
132
|
+
io << template
|
133
|
+
end
|
134
|
+
file_written = true
|
135
|
+
end
|
136
|
+
return file_written
|
137
|
+
end
|
138
|
+
template
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# create backup file by appending secs since epoch to filename
|
143
|
+
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
|
149
|
+
end
|
150
|
+
|
151
|
+
# create empty shell file consisting of layout and a comment for where to insert new content, return contents
|
152
|
+
def self.create_empty_shell(template_spec_to_copy, content_hash, content_to_insert, options = {} )
|
153
|
+
from_spec = template_spec_to_copy
|
154
|
+
content_hash['empty_shell_contents'] = []
|
155
|
+
content_hash['empty_shell_contents'] << MasterView::Analyzer::ContentEntry.new(content_to_insert)
|
156
|
+
to_spec = TemplateSpec.new
|
157
|
+
to_spec.build_list = []
|
158
|
+
li = from_spec.build_list.first
|
159
|
+
li.import = true
|
160
|
+
to_spec.build_list << li
|
161
|
+
to_spec.build_list << MasterView::Analyzer::ListEntry.new('empty_shell_contents', -1, false)
|
162
|
+
li = from_spec.build_list.last
|
163
|
+
li.import = true
|
164
|
+
to_spec.build_list << li
|
165
|
+
to_spec.rebuild_template(content_hash)
|
166
|
+
end
|
167
|
+
|
168
|
+
class CreateShellERBValues
|
169
|
+
attr_reader :controller_name, :action_name, :controller_file_name, :controller_view_dir_name
|
170
|
+
def initialize(controller_name, action_name)
|
171
|
+
@controller_name = Inflector.underscore(Inflector.singularize(controller_name))
|
172
|
+
@action_name = Inflector.underscore(Inflector.singularize(action_name))
|
173
|
+
@controller_file_name = @controller_name
|
174
|
+
@controller_view_dir_name = @controller_file_name
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_binding
|
178
|
+
binding
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# create empty shell file consisting of layout and a comment for where to insert new content. Use action_to_create
|
183
|
+
# to infer the destination name controller_action.html and to customize the inserted place holder. Pass the
|
184
|
+
# empty insert erb (rhtml) content in which will be rendered with appropriate controller and action values.
|
185
|
+
# Valid options:
|
186
|
+
# :write_to_file => true (write to file and return filename, if false then simply return generated contents)
|
187
|
+
# :template_source => source_for_template (use this source instead of reading from file)
|
188
|
+
# :content_hash => use this content_hash, otherwise it will scan the masterview template directory to create content_hash
|
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
|
+
|
200
|
+
erb_values = CreateShellERBValues.new(controller_name, action_to_create)
|
201
|
+
template = ERB.new(empty_insert_erb)
|
202
|
+
content_to_insert = template.result(erb_values.get_binding).strip #clear off surrounding whitespace that makes it difficult to debug
|
203
|
+
|
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
|
207
|
+
|
208
|
+
template_specs = {}
|
209
|
+
content_hash = options[:content_hash]
|
210
|
+
template_specs, content_hash = TemplateSpec.scan unless content_hash
|
211
|
+
template_spec_to_copy = template_specs[src_file] || TemplateSpec.scan_template(src, src_file)
|
212
|
+
|
213
|
+
result = MasterView::TemplateSpec.create_empty_shell( template_spec_to_copy, content_hash, content_to_insert)
|
214
|
+
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
|
220
|
+
end
|
221
|
+
result
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
data/lib/masterview.rb
CHANGED
@@ -34,7 +34,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
34
34
|
|
35
35
|
module MasterView
|
36
36
|
# set to true to have exceptions rescued
|
37
|
-
RescueExceptions =
|
37
|
+
RescueExceptions = true
|
38
38
|
|
39
39
|
# :tidy - run tidy before parsing (set TidyPath), :escape_erb - escapes <% %> before parsing
|
40
40
|
DefaultParserOptions = { :tidy => false, :escape_erb => true }
|
@@ -51,6 +51,9 @@ module MasterView
|
|
51
51
|
# masterview template filename pattern
|
52
52
|
TemplateFilenamePattern = '*.html'
|
53
53
|
|
54
|
+
# extension to use for partials if not provided
|
55
|
+
PartialExtension = '.rhtml'
|
56
|
+
|
54
57
|
# relative path to RAILS_ROOT/app/views where erb (rhtml) will be generated
|
55
58
|
TemplateDestRelativePath = ''
|
56
59
|
|
@@ -58,6 +61,12 @@ module MasterView
|
|
58
61
|
# the generated file should not be manually edited, false = generate the warning comments
|
59
62
|
OmitGeneratedComments = false
|
60
63
|
|
64
|
+
# relative path from RAILS_ROOT to where backup files should be created before rebuilding/updating a template file, use nil to suppress backups
|
65
|
+
DirectoryForRebuildBackups = 'tmp/masterview/rebuild/backups'
|
66
|
+
|
67
|
+
# enable MasterView Admin pages
|
68
|
+
EnableMasterViewAdminPages = true
|
69
|
+
|
61
70
|
# boolean which determines whether masterview files will be parsed during rails load and watched on requests.
|
62
71
|
# If true then masterview files will be parsed on rails loading. Additionally if
|
63
72
|
# this is true and the ActionController::Base.perform_caching is false (normally in development mode)
|
@@ -89,11 +98,15 @@ require 'rexml/sax2listener'
|
|
89
98
|
require 'cgi'
|
90
99
|
require 'fileutils'
|
91
100
|
require 'strscan'
|
101
|
+
require 'find'
|
102
|
+
require 'erb'
|
103
|
+
require 'stringio'
|
92
104
|
|
93
105
|
#external gem requires
|
94
106
|
require 'facets/core/string/blank'
|
95
107
|
require 'facets/core/string/indent'
|
96
108
|
require 'facets/core/string/starts_with'
|
109
|
+
require 'active_support/inflector'
|
97
110
|
|
98
111
|
#optional external gem requires, added functionality when available
|
99
112
|
begin
|
@@ -111,6 +124,8 @@ require 'masterview/directive_helpers'
|
|
111
124
|
require 'masterview/directive_base'
|
112
125
|
require 'masterview/runtime_helpers'
|
113
126
|
require 'masterview/parser'
|
127
|
+
require 'masterview/analyzer'
|
128
|
+
require 'masterview/template_spec'
|
114
129
|
include MasterView::RuntimeHelpers
|
115
130
|
|
116
131
|
# set these constants after everything has been required
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
currentPath = File.dirname(__FILE__)
|
5
|
+
require File.join( currentPath, '../lib/masterview' )
|
6
|
+
require File.join( currentPath, '../lib/masterview/directives/import_render')
|
7
|
+
|
8
|
+
class TestImportRender < Test::Unit::TestCase
|
9
|
+
include MasterView::Directives
|
10
|
+
include MasterView::DirectiveHelpers
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@directives = MasterView::DirectiveSet.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_import_render
|
17
|
+
import_render_tag = MasterView::Tag.new(@directives, 'bar', {}, :normal, nil)
|
18
|
+
@directives.directives = []
|
19
|
+
attr_value = ':partial => foo/bar'
|
20
|
+
@directives << Import_render.new(attr_value)
|
21
|
+
dcs = @directives.determine_dcs(:stag)
|
22
|
+
dcs.context = import_render_tag.create_context
|
23
|
+
assert_equal nil, dcs.render
|
24
|
+
|
25
|
+
dcs = @directives.determine_dcs(:etag)
|
26
|
+
dcs.context = import_render_tag.create_context
|
27
|
+
assert_equal ERB_EVAL+'render( '+attr_value+' )'+ERB_END, dcs.render.join
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|