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