require 'masterview'
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
def to_input_field_tag(field_type, options={})
field_meth = "#{field_type}_field"
case field_meth
when 'text_field'
%Q[]
when 'hidden_field'
%Q[]
when 'password_field'
%Q[]
end
end
def to_text_area_tag(options = {})
%Q[]
end
def to_date_select_tag(options = {})
%Q[todo render date select]
end
def to_datetime_select_tag(options = {})
%Q[todo render date select]
end
end
module Rails
module Generator
module Commands
class Create < Base
def string_to_file(content, relative_destination, file_options = {}, &block)
# Determine full paths for source and destination files.
destination = destination_path(relative_destination)
destination_exists = File.exists?(destination)
# If source and destination are identical then we're done.
if destination_exists and content == File.readlines(destination).join
return logger.identical(relative_destination)
end
# Check for and resolve file collisions.
if destination_exists
# 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 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]
# Write destination file with optional shebang. Yield for content
# if block given so templaters may render the source file. If a
# shebang is requested, replace the existing shebang or insert a
# new one.
File.open(destination, 'wb') do |df|
if block_given?
df.write(yield(content))
else
if file_options[:shebang]
df.puts("#!#{file_options[:shebang]}")
df.puts(line) if content !~ /^#!/
end
df.write(content)
end
end
# 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
template(relative_source, relative_destination, 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, nil, 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 = File.readlines(dest_file).join
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
File.open(dest_file, 'w') { |file| file.write(source_to_update) }
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 = ''
if full_options[:single_file]
dtj << %Q[ \n]
dtj << %Q[ \n]
else
dtj << %Q[ \n]
dtj << %Q[ \n]
end
end
def stylesheets
ss = ''
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[: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 = <<-END
messages here - Note: Parts of these pages can be imported from other pages to allow for design time editing in full page context. View the MasterView Admin page, run the rake mv:list_all command, or view the source to learn which parts this file 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 'app/views/masterview'
m.directory 'app/views/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","app/views/masterview/#{controller_masterview_name}.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 => "app/views/masterview/#{controller_masterview_name}_list.html",
:new => "app/views/masterview/#{controller_masterview_name}_new.html",
:edit => "app/views/masterview/#{controller_masterview_name}_edit.html",
:show => "app/views/masterview/#{controller_masterview_name}_show.html",
:destroy => "app/views/masterview/#{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.template 'style.css', 'public/stylesheets/scaffold.css'
m.template 'sidebox.css', 'public/stylesheets/sidebox.css'
m.template 'color-scheme.css', 'public/stylesheets/color-scheme.css'
# design time files
m.template 'mvpreview.js', 'app/views/masterview/extra/mvpreview.js'
m.template 'show_only_new.css', 'app/views/masterview/extra/show_only_new.css'
m.template 'show_only_edit.css', 'app/views/masterview/extra/show_only_edit.css'
m.template 'show_only_show.css', 'app/views/masterview/extra/show_only_show.css'
m.template 'show_only_list.css', 'app/views/masterview/extra/show_only_list.css'
m.template 'show_only_destroy.css', 'app/views/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