require 'masterview'
require File.join( File.dirname(__FILE__), 'actionview_helper' )
class ScaffoldingSandbox
include ActionView::Helpers::ActiveRecordHelper
attr_accessor :singular_name, :controller_file_name, :controller_view_dir_name, :suffix, :model_instance
def sandbox_binding
binding
end
def default_input_block
Proc.new { |record, column|
"
\n
\n
#{input(record, column.name)}
\n
\n" }
end
end
class ListHeadScaffoldingSandbox < ScaffoldingSandbox
def default_input_block
Proc.new { |record, column| "#{column.human_name} | " }
end
end
class ListLineScaffoldingSandbox < ScaffoldingSandbox
def default_input_block
Proc.new { |record, column| "#{record} #{column.human_name} | " }
end
end
class ShowScaffoldingSandbox < ScaffoldingSandbox
def default_input_block
Proc.new { |record, column|
" \n
\n
#{record} #{column.human_name}
\n
\n" }
end
end
class ActionView::Helpers::InstanceTag
include ::MasterView::Generator::ActionViewHelper
def to_input_field_tag(field_type, options={})
field_meth = "#{field_type}_field"
case field_meth
when 'text_field'
text_field options
when 'hidden_field'
hidden_field options
when 'password_field'
password_field options
end
end
def to_text_area_tag(options = {})
text_area options
end
def to_date_select_tag(options = {})
date_select options
end
def to_datetime_select_tag(options = {})
datetime_select options
end
end
module Rails
module Generator
module Commands
class Create < Base
# Ask the user interactively whether to force collision.
def mv_force_file_collision?(destination)
$stdout.print "overwrite #{destination}? [Ynaq] "
case $stdin.gets
when /a/i
$stdout.puts "forcing #{spec.name}"
options[:collision] = :force
when /q/i
$stdout.puts "aborting #{spec.name}"
raise SystemExit
when /n/i then :skip
else :force
end
rescue
retry
end
def string_to_file(content, relative_destination, file_options = {}, &block)
template_mio = MasterView::IOMgr.template.path(relative_destination)
# If source and destination are identical then we're done.
if template_mio.identical?(content)
return logger.identical(relative_destination)
end
# Check for and resolve file collisions.
if template_mio.exist?
# Make a choice whether to overwrite the file. :force and
# :skip already have their mind made up, but give :ask a shot.
choice = case (file_options[:collision] || options[:collision]).to_sym #|| :ask
when :ask then mv_force_file_collision?(relative_destination)
when :force then :force
when :skip then :skip
else raise "Invalid collision option: #{options[:collision].inspect}"
end
# Take action based on our choice. Bail out if we chose to
# skip the file; otherwise, log our transgression and continue.
case choice
when :force then logger.force(relative_destination)
when :skip then return(logger.skip(relative_destination))
else raise "Invalid collision choice: #{choice}.inspect"
end
# File doesn't exist so log its unbesmirched creation.
else
logger.create relative_destination
end
# If we're pretending, back off now.
return if options[:pretend]
template_mio.write(content, :disable_logging => true) # it is already being logged
# Optionally change permissions.
#if file_options[:chmod]
# FileUtils.chmod(file_options[:chmod], destination)
#end
# Optionally add file to subversion
#system("svn add #{destination}") if options[:svn]
end
def multi_include_template(relative_source, relative_destination, template_options = {}, multi_assign_options = {})
options = template_options.dup
options[:assigns] ||= {}
multi_assign_options.each do |k,v|
options[:assigns][k] = render_template_part(v)
end
# Render the source file with the temporary binding.
src_io = File.new( source_path(relative_source) )
vars = options[:assigns] || {}
b = binding
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
combined_contents = ERB.new(src_io.read, nil, '-').result(b)
string_to_file(combined_contents, relative_destination, template_options)
end
def multi_file_multi_include_template(relative_source, relative_destination_map, template_options = {}, multi_assign_options = {})
options = template_options.dup
options[:assigns] ||= {}
multi_assign_options.each do |k,v|
options[:assigns][k] = render_template_part(v)
end
vars = options[:assigns] || {}
b = binding
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
# Render the source file with the temporary binding.
src_io = File.new( source_path(relative_source) )
combined_contents = ERB.new(src_io.read, nil, '-').result(b)
content_hash = {}
MasterView::TemplateSpec.scan_template(combined_contents, 'MasterViewTemporaryCombinedSource', content_hash)
relative_destination_map.each do |page_type, relative_destination|
template_spec = MasterView::TemplateSpec.new(relative_destination)
yield(page_type, template_spec) #allow the template_spec to be modified
content = template_spec.rebuild_template(content_hash)
string_to_file(content, relative_destination, template_options)
end
end
end
class Destroy < RewindBase
def multi_include_template(relative_source, relative_destination, template_options = {}, multi_assign_options = {})
end
def multi_file_multi_include_template(relative_source, relative_destination_map, template_options = {}, multi_assign_options = {})
end
end
class List < Base
def multi_include_template(relative_source, relative_destination, template_options = {}, multi_assign_options = {})
str = ""
multi_assign_options.each do |k,v|
str << v[:insert] << ' '
end
logger.template "#{str} inside #{relative_destination.inspect}"
end
def multi_file_multi_include_template(relative_source, relative_destination_map, template_options = {}, multi_assign_options = {})
str = ""
multi_assign_options.each do |k,v|
str << v[:insert] << ' '
end
logger.template "#{str} inside #{relative_destination_map.inspect}"
end
end
class Update < Create
def multi_include_template(relative_source, relative_destination, template_options = {}, multi_assign_options = {})
return if relative_source.is_a?(StringIO) && relative_destination.is_a?(StringIO)
begin
dest_file = destination_path(relative_destination)
source_to_update = MasterView::IOMgr.template.path(dest_file).read
rescue Errno::ENOENT
logger.missing relative_destination
return
end
multi_assign_options.each do |k,v|
template_options = v
logger.refreshing "#{template_options[:insert].gsub(/\.rhtml/,'')} inside #{relative_destination}"
begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id]))
end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id]))
# Refreshing inner part of the template with freshly rendered part.
rendered_part = render_template_part(template_options)
source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part)
end
MasterView::IOMgr.template.path(dest_file).write(source_to_update, :disable_logging => true ) # already logging
end
def multi_file_multi_include_template(relative_source, relative_destination_map, template_options = {}, multi_assign_options = {})
end
end
end
end
end
class MasterviewGenerator < Rails::Generator::NamedBase
attr_reader :controller_name,
:controller_class_path,
:controller_file_path,
:controller_class_nesting,
:controller_class_nesting_depth,
:controller_class_name,
:controller_singular_name,
:controller_plural_name
alias_method :controller_file_name, :controller_singular_name
alias_method :controller_view_dir_name, :controller_singular_name
alias_method :controller_masterview_name, :controller_singular_name
alias_method :controller_table_name, :controller_plural_name
def initialize(runtime_args, runtime_options = {})
super
@controller_name = args.shift || @name
base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
@controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
if @controller_class_nesting.empty?
@controller_class_name = @controller_class_name_without_nesting
else
@controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
end
default_options[:showSection] = :new
end
def design_time_stylesheets
dts = ''
if full_options[:single_file]
com = <<-END
END
dts << com
dts << html_comment( '',
full_options[:showSection] != :new )
dts << html_comment( '', true)
dts << html_comment( '', true)
dts << html_comment( '',
full_options[:showSection] != :list )
dts << html_comment( '', true)
end
end
def html_comment(orig, comment_out)
t = ' '
t << '' if comment_out
t << "\n"
end
def design_time_javascript
dtj = ''
# todo make this relative to whereever IOMgr.template is
if full_options[:single_file]
dtj << %Q[ \n]
dtj << %Q[ \n]
else
dtj << %Q[ \n]
dtj << %Q[ \n]
end
end
def stylesheets
ss = ''
# todo make this relative to whereever IOMgr.template is
if full_options[:stylesheets]
full_options[:stylesheets].each do |stylesheet|
ss << %Q( \n)
end
else # unless --style option was present we output the default stylesheets
ss << %Q{ \n}
ss << %Q{ \n}
ss << %Q{ \n}
end
ss
end
def messages
if full_options[:single_file]
unless full_options[:stylesheets]
mess = <<-END
messages here - Note: Other page sections in this MasterView file are hidden at design time, disable the stylesheet 'extra/show_only_new.css' or uncomment different extra/*.css to work with other page sections (other stylesheets are show_only_edit.css, show_only_show.css, show_only_list.css, show_only_destroy.css and they are commented out in this masterview file's head section)
END
else
mess = 'messages here'
end
else # multi-file
mess = <<-END
messages here - Note: Modify list.html template to edit layout, and message partials. Modify new.html if you want to edit the _form partial. Other templates import these partials to allow for design time editing in a full page context. View the MasterView Admin page, run the rake mv:list_all command, or view the source to learn which parts this particular template controls.
END
end
mess
end
def manifest
record do |m|
# Depend on model generator but skip if the model exists.
m.dependency 'model', [singular_name], :collision => :skip, :skip_migration => true
# Check for class naming collisions.
m.class_collisions controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}ControllerTest", "#{controller_class_name}Helper"
# Controller, helper, views, and test directories.
m.directory File.join('app/controllers', controller_class_path)
m.directory File.join('app/helpers', controller_class_path)
m.directory File.join('app/views', controller_class_path, controller_view_dir_name)
m.directory 'public/stylesheets/masterview'
m.directory 'app/masterview'
m.directory 'app/masterview/extra'
m.directory File.join('test/functional', controller_class_path)
# Controller class, functional test, helper, and views.
m.template 'controller.rb',
File.join('app/controllers',
controller_class_path,
"#{controller_file_name}_controller.rb")
m.template 'functional_test.rb',
File.join('test/functional',
controller_class_path,
"#{controller_file_name}_controller_test.rb")
m.template 'helper.rb',
File.join('app/helpers',
controller_class_path,
"#{controller_file_name}_helper.rb")
# MasterView
if full_options[:single_file]
m.multi_include_template "masterview.rhtml","#{controller_masterview_name}/masterview.html", {},
{
'form_inclusion' =>
{ :insert => 'form_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ScaffoldingSandbox') },
:begin_mark => 'form',
:end_mark => 'eoform',
:mark_id => singular_name
},
'list_head_inclusion' =>
{ :insert => 'list_head_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ListHeadScaffoldingSandbox') },
:begin_mark => 'listhead',
:end_mark => 'eolisthead',
:mark_id => singular_name
},
'list_line_inclusion' =>
{ :insert => 'list_line_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ListLineScaffoldingSandbox') },
:begin_mark => 'listline',
:end_mark => 'eolistline',
:mark_id => singular_name
},
'show_inclusion' =>
{ :insert => 'show_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ShowScaffoldingSandbox') },
:begin_mark => 'show',
:end_mark => 'eoshow',
:mark_id => singular_name
}
}
else #multi file
m.multi_file_multi_include_template "masterview.rhtml",
{
:list => "#{controller_masterview_name}/list.html",
:new => "#{controller_masterview_name}/new.html",
:edit => "#{controller_masterview_name}/edit.html",
:show => "#{controller_masterview_name}/show.html",
:destroy => "#{controller_masterview_name}/destroy.html",
},
{},
{
'form_inclusion' =>
{ :insert => 'form_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ScaffoldingSandbox') },
:begin_mark => 'form',
:end_mark => 'eoform',
:mark_id => singular_name
},
'list_head_inclusion' =>
{ :insert => 'list_head_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ListHeadScaffoldingSandbox') },
:begin_mark => 'listhead',
:end_mark => 'eolisthead',
:mark_id => singular_name
},
'list_line_inclusion' =>
{ :insert => 'list_line_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ListLineScaffoldingSandbox') },
:begin_mark => 'listline',
:end_mark => 'eolistline',
:mark_id => singular_name
},
'show_inclusion' =>
{ :insert => 'show_scaffold.rhtml',
:sandbox => lambda { create_multi_sandbox('ShowScaffoldingSandbox') },
:begin_mark => 'show',
:end_mark => 'eoshow',
:mark_id => singular_name
}
} do |page_type, template_spec|
case page_type
when :list
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", 0, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/list.rhtml", 0, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_messages.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/list.rhtml", 1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_#{singular_name}.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/list.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", -1, false)
when :new
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", 0, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/new.rhtml", 0, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_messages.rhtml", -1, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/new.rhtml", 1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_form.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/new.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", -1, true)
when :edit
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", 0, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/edit.rhtml", 0, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_messages.rhtml", -1, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/edit.rhtml", 1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_form.rhtml", -1, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/edit.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", -1, true)
when :show
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", 0, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/show.rhtml", 0, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_messages.rhtml", -1, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/show.rhtml", 1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_show.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/show.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", -1, true)
when :destroy
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", 0, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/destroy.rhtml", 0, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_messages.rhtml", -1, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/destroy.rhtml", 1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/_show.rhtml", -1, true)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("#{controller_view_dir_name}/destroy.rhtml", -1, false)
template_spec.build_list << MasterView::Analyzer::ListEntry.new("layouts/#{controller_file_name}.rhtml", -1, true)
end
end
end
m.file 'style.css', 'public/stylesheets/masterview/style.css'
m.file 'sidebox.css', 'public/stylesheets/masterview/sidebox.css'
m.file 'color-scheme.css', 'public/stylesheets/masterview/color-scheme.css'
# design time files
m.file 'mvpreview.js', 'app/masterview/extra/mvpreview.js'
m.file 'show_only_new.css', 'app/masterview/extra/show_only_new.css'
m.file 'show_only_edit.css', 'app/masterview/extra/show_only_edit.css'
m.file 'show_only_show.css', 'app/masterview/extra/show_only_show.css'
m.file 'show_only_list.css', 'app/masterview/extra/show_only_list.css'
m.file 'show_only_destroy.css', 'app/masterview/extra/show_only_destroy.css'
end
end
protected
# Override with your own usage banner.
def banner
"Usage: #{$0} masterview ModelName [ControllerName] [--style [cssStylesheet]] [--single-file] [--show-all | --show-only list]"
end
def add_options!(opt)
opt.on('-C', '--single-file', 'Generate masterview template as a single combined file'){ options[:single_file] = true }
opt.on('-A', '--show-all', 'Generate with ALL sections visible at design time (no css hiding)',
'(valid only in conjunction with --single-file') { options[:showSection] = :all }
opt.on("-O", "--show-only SECTION_NAME",
"Generate with only sectionName section visible at design time (rest hidden with css)",
" (Valid options are: new, list) (Only valid if used with --single-file)",
" (example: --showOnly list - would generate to show only 'list' section at design time)" ) do |section|
options[:showSection] = section.to_sym
end
opt.on("-S", "--style [CSS_STYLESHEET]",
"Add this stylesheet instead of defaults",
" (You may add this option multiple times for each stylesheet to add)",
" (If you omit the cssStylesheet then no default stylesheets will be added)" ) do |stylesheet|
options[:stylesheets] ||= []
options[:stylesheets] << File.basename(stylesheet, File.extname(stylesheet)) if stylesheet #get only the base part, so if /home/foo.css get foo
end
end
def scaffold_views
%w(list show new edit destroy)
end
def scaffold_actions
scaffold_views + %w(index)
end
def unscaffolded_actions
args - scaffold_actions
end
def suffix
"_#{singular_name}" if options[:suffix]
end
def model_name
class_name.demodulize
end
def create_sandbox
sandbox = ScaffoldingSandbox.new
sandbox.singular_name = singular_name
begin
sandbox.model_instance = model_instance
sandbox.instance_variable_set("@#{singular_name}", sandbox.model_instance)
rescue ActiveRecord::StatementInvalid => e
logger.error "Before updating scaffolding from new DB schema, try creating a table for your model (#{class_name})"
raise SystemExit
end
sandbox.suffix = suffix
sandbox
end
def create_multi_sandbox( sandbox_class_name )
sandbox = eval("#{sandbox_class_name}.new")
sandbox.singular_name = singular_name
sandbox.controller_file_name = controller_file_name
sandbox.controller_view_dir_name = controller_view_dir_name
begin
sandbox.model_instance = model_instance
sandbox.instance_variable_set("@#{singular_name}", sandbox.model_instance)
rescue ActiveRecord::StatementInvalid => e
logger.error "Before updating scaffolding from new DB schema, try creating a table for your model (#{class_name})"
raise SystemExit
end
sandbox.suffix = suffix
sandbox
end
def model_instance
base = class_nesting.split('::').inject(Object) do |base, nested|
break base.const_get(nested) if base.const_defined?(nested)
base.const_set(nested, Module.new)
end
unless base.const_defined?(@class_name_without_nesting)
base.const_set(@class_name_without_nesting, Class.new(ActiveRecord::Base))
end
class_name.constantize.new
end
end