#--
# Copyright (c) 2006 Jeff Barczewski and Deborah Lewis
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
#
# == MasterView Configuration and Initialization
#
# The MasterView template engine supports a number of configuration settings
# which allow a client to easily customize its operation.
#
# A MasterView::Configuration specification is used to allow the application to
# specify custom configuration options during application startup. The configuration options
# are installed in MasterView when its Initializer is run, which handles
# loading and configuring MasterView for operation.
#
#--
# This style of configuration specification + initializer follows the technique
# used by Rails itself. A configuration specification can be customized by the
# application in its startup and is used by the Initialzer to configure
# the MasterView template engine implementation at load time.
# [D. Lewis 31-May-2006]
#++
#
#--
# todo - change configuration to accomodate non-file based storage, use MIOTree objects
# [JJB 13-Jun-2006]
#++
#
require 'date'
# we can't be assured of the load path state if initializer is run standalone
# to construct a configuration prior to full MasterView initialization
currentPath = File.dirname(__FILE__)
require File.join( currentPath, 'directive_metadata' )
require File.join( currentPath, 'directive_load_path' )
DEBUG_DIRECTIVE_PATH = false #:nodoc: # temp debug
module MasterView
# A Configuration holds all the configuration settings used the
# MasterView::Initializer to configure the template engine.
#
# Configuration provides defaults that suit most Rails applications.
# You can change the default settings in the configuration to customize
# MasterView with options suitable for your application.
#
# In the most common usage pattern, a Configuration instance initialized
# with the standard default settings is created implicitly when the
# Initializer is run, which happens automatically
# when Masterview is installed as a plugin in a Rails application.
# Application-specific settings are then loaded from the application's
# +config+ directory and used by the Initializer to configure
# the MasterView template engine for operation.
#
# When using MasterView in a standalone application (i.e., not within rails),
# it is also possible to create the Configuration instance in advance and
# pass it in explicitly to the Initializer. This technique can be useful
# when it is necessary to establish the application root directory location
# that MasterView uses during its operation or to specify to location of the
# application's configuration files.
#
# require 'masterview/initializer'
# config = MasterView::Configuration.new( :app_root_path => '/path/to/my/app' )
# config.template_src_dir_path = "masterview/templates"
# config.template_dst_dir_path = "masterview/output"
# #...set other config options...
# MasterView::Initializer.run( :process, config )
#
class Configuration
# directory in which the MasterView plugin or gem is installed
attr_reader :mv_installation_dir #:nodoc:
# root directory of MasterView itself and the built-in directives
# This is lib/masterview w/in the plugin or gem installation directory.
attr_reader :mv_code_base_dir #:nodoc:
# root directory when operating context is a rails app
attr_reader :rails_root_path #:nodoc:
# directory in a rails app for views (RAILS_ROOT/app/views)
attr_reader :rails_views_dir_path #:nodoc:
# RE spec for scripts which are used to launch rails
attr_accessor :rails_runner_scripts_pattern #:nodoc:
# is RAILS_ROOT defined?
attr_reader :has_rails_context #:nodoc:
# is the root a rails app?
def rails_app? #:nodoc:
@rails_root_path != nil
end
# Set to indicate that we're actually running the rails app (server or console...)
attr_accessor :running_rails #:nodoc:
# are we actually running rails when our root is a rails app?
def on_rails? #:nodoc:
@running_rails
end
# === General Options
# Path to the root location for the application.
# Used for resolving relative references.
#
# For a rails application, this is +RAILS_ROOT+.
#
attr_accessor :root_path
# Path to the directory containing the application's MasterView
# configuration settings files (if any). If no configuration
# directory or settings files are defined, MasterView runs
# with the standard default configuration options.
#
# Specified as a relative path from the application's +root_path+.
#
# Default: for a Rails application, this is RAILS_ROOT/config/masterview.
#
attr_accessor :config_dir_path
# Specify the directory where masterview templates are located
# as a relative path from the application's +root_path+.
def config_dir_path= rel_path #:nodoc:
# bolts down abs path, thanks to our firm roots
@config_dir_path = rel_path.nil? ? nil : ( rel_path.empty? ? root_path : File.join( root_path, rel_path ))
end
# The current execution environment, if applicable
# (e.g., 'development', 'test', 'production').
#
# Used for loading application settings from config directory, if available.
#
# For a Rails application, this is +RAILS_ENV+.
#
attr_accessor :environment
# Paths of directories to load directives from.
#
# The directory containing the built-in MasterView directives
# is installed by default. If the application provides
# a masterview/directives directory in its +root_path+,
# that directory is also automatically added to the directives
# load path.
#
# A directive load path entry specifiess a directory path
# from which masterview directives are loaded.
# Configuration options can optionally be specified to override
# or extend any .metadata specifications in the directory.
#
# Use add_directive_path to append additional directory path(s)
# from which to load custom directives.
#
attr_accessor :directive_load_path
# Deprecated - use directive_load_path
def directive_paths
@directive_load_path
end
# For use by Initializer to reset cleaned path
def directive_load_path=(path) #:nodoc:
@directive_load_path = path
end
# Add an entry to the directive_load_path list for a directory
# containing directive implementation classes to be loaded
# into the MasterView processing configuration.
#
# Optionally specify options for the directives loaded
# from this directory:
#
# :default - metadata defaults
#
# Metadata defaults extend or override any defaults specified
# in the dir_path/.metadata file, if defined, allowing application
# customization of the default defaults.
#
def add_directive_path(dir_path, options=nil)
directive_load_path << DirectiveLoadPath::PathEntry.new( dir_path, options )
end
# Relative path from +root_path+ of the temp directory used for creating
# backup files before rebuilding/updating a template file.
#
# Set to nil to suppress backups.
#
# Default: RAILS_ROOT/tmp/masterview/rebuild/backups
#--
# ISSUE: change this to use IOMgr mapping, how best to do this config??
# Leave existing mechanism here, but drop out of documentation and examples
# until resolved
#++
attr_accessor :rebuild_backups_tmp_dir_path #:nodoc:
# Logger which will be used to record debug, warning, and error messages.
#
# Supported loggers:
# 'log4r' - the Log4r logging library
# 'logger' - Logger in the ruby standard class library
#
# Default: uses Log4r if available, otherwise a standard ruby +Logger+.
attr_accessor :logger
# Logging severity threshold for the logger.
#
# Specify the name of the logging level to use with the configured logger.
# Standard logging levels in both Log4r and ruby Logger are
# +DEBUG+, +INFO+, +WARN+, +ERROR+, +FATAL+.
# Other level names depend on the configured logger.
#
# Default: +INFO+ for development, +WARN+ otherwise
attr_accessor :log_level
# Sets a block which will be executed after MasterView has been fully initialized.
# Useful for per-environment configuration which depends on the plugin being
# fully initialized.
def after_initialize(&after_initialize_block)
@after_initialize_block = after_initialize_block
end
# Returns the block set in Configuration#after_initialize
def after_initialize_block
@after_initialize_block
end
# === Template Source Options
# Path to the directory where masterview templates are located.
#
# Assign the value as a relative path from the application's +root_path+
# or use the +template_src_dir_abs_path+ method to specify an absolute path.
#
# Default: RAILS_ROOT/app/views for a rails application
# or {root_path}/masterview/templates for a non-rails application.
attr_accessor :template_src_dir_path
# Specify the directory where masterview templates are located
# as a relative path from the application's +root_path+.
def template_src_dir_path= rel_path #:nodoc:
# bolts down abs path, thanks to our firm roots
@template_src_dir_path = rel_path.nil? ? nil : ( rel_path.empty? ? root_path : File.join( root_path, rel_path ))
end
# Specify the absolute path to the directory where masterview templates are located.
def template_src_dir_abs_path(abs_path)
@template_src_dir_path = abs_path
end
# Filename pattern for masterview template files
# within the template source directory.
#
# Default: '*.html'
attr_accessor :template_filename_pattern
# Regex pattern specifications for identifying the base directory
# on asset references in a template document to convert
# design-time assert references for images, stylesheets,
# and javascript files into relative references for use
# with the standard Rails asset helper functions.
#
# The patterns are a hash indexed by asset type.
# Asset types are :images, :stylesheets, :javascripts
#
# The standard patterns match path prefixes up through
# public/asset-type. For example,
# an mv:stylesheet_link directive of the form:
#
#
#
# would match the standard base-dir prefix and result in:
#
# <%= stylesheet_link_tag "mystyles" %>
#
attr_accessor :template_asset_base_ref_pattern
# Configuration for locations of files that MasterView will autocopy to a runtime location.
# This autocopy feature makes it easy to have prototypes that display properly for
# WYSIWYG editing and design, using files (images, stylesheets, and javascript) which are not
# in the standard Rails locations but are automatically copied to proper place for runtime.
# This is commonly used in conjunction with template_src_dir_path (which changes the
# location of the source MasterView templates). Developers can have their templates in a non-standard
# location along with images, stylesheets, and javascript, and then still have everything copied to
# the proper runtime locations when Rails is fired up. In debug mode, timestamps are checked on every request,
# in production mode, copy is done only on startup.
#
# Each entry is a hash that needs to contain
# :source => 'path to source folder' # absolute or relative to RAILS_ROOT
# :destination => 'path to place to copy to' # absolute or relative to RAILS_ROOT
#
# and can optionally have a file extension filter
# :extensions => [:gif, :GIF, :jpg, :JPG] # limit files copied to this array of extensions, case sensitive
#
# Default: [] (empty array) - nothing is auto copied
# Example usage showing three paths added to be auto copied:
# config.auto_copy_file_entries << { :source => 'path_to_my_images', :destination => 'public/images' }
# config.auto_copy_file_entries << { :source => 'path_to_my_scripts', :destination => 'public/javascripts', :extensions => [:js, :JS] } # only copy js files
# config.auto_copy_file_entries << { :source => 'path_to_my_css', :destination => 'public/stylesheets' }
attr_accessor :auto_copy_file_entries
# === Template Generation Options
# Path to the directory where Masterview template output (rhtml)
# will be generated.
#
# Assign the value as a relative path from the application's +root_path+
# or use the +template_dst_dir_abs_path+ method to specify an absolute path.
#
# Default: RAILS_ROOT/app/views
attr_accessor :template_dst_dir_path
# Specify the directory where masterview template output is generated
# as a relative path from the application's +root_path+.
def template_dst_dir_path= rel_path #:nodoc:
# bolts down abs path, thanks to our firm roots
@template_dst_dir_path = rel_path.nil? ? nil : ( rel_path.empty? ? root_path : File.join( root_path, rel_path ))
end
# Specify the absolute path to the directory where masterview template output is generated.
def template_dst_dir_abs_path(abs_path)
@template_dst_dir_path = abs_path
end
# Filename extension to use for generated output files if not
# explicitly specified in the +mv:generate+ attribute value.
#
# Default: '.rhtml'
attr_accessor :output_filename_extension
# Filename extension to use for generated files if not explicitly specified.
#
# Default: '.rhtml'
attr_accessor :generated_file_default_extension #FIXME
# Boolean which controls whether to include a comment in template
# output files indicating that the file was generated by MasterView.
#
# By default, a warning comment is generated in template output files
# indicating that the file was generated by MasterView from a template
# and should not be directly edited, as any changes would be lost when the
# output file is regenerated.
#
# Default: +true+
attr_accessor :include_generated_file_comment
# Text for generated-file comment inserted in rhtml template output files
# generated by MasterView.
#
# Standard comment includes the name of the master template file which contains
# the source to edit.
#
# Variable substitution on the comment text will replace a reference
# to {template_path} with the pathname of the source template file.
#
attr_accessor :generated_file_comment
# === Template Parsing Options
# Specify whether MasterView should handle exceptions when
# parsing template files.
#
# Exceptions that occur during template parsing are always
# recorded in the debug log. These are generally problems
# caused by invalid (x)html in the template.
#
# Set to +true+ to have the template parser catch exceptions and
# continue processing after logging a problem report.
#
# Set to +false+ to have exceptions raised to the application.
#
# Default: +true+
#
attr_accessor :handle_parse_exceptions
# Default option settings for template parsing (a hash)
# :tidy => false - run tidy before parsing (tidy_path must be set if enabled)
# :escape_erb => true - escapes <% %> before parsing
# :default_generate => true - adds in mv:generate="{template_path}" if none found
attr_accessor :default_parser_options
# Path on this system to tidy library if :tidy parser option is enabled
# so that masterview templates will be run through tidy before being parsed.
# Allows invalid xhmtl to be corrected before masterview template parsing is performed.
attr_accessor :tidy_path
# XML name space prefix for builtin MasterView directive attributes in template html.
# e.g. mv:generate="target.rhtml".
#
# Default: 'mv:'
attr_accessor :namespace_prefix
# XML name space prefix for MasterView extension directive attributes in template html.
# e.g. mvx:custom_directive="foo".
#
# Default: 'mvx:'
attr_accessor :namespace_prefix_extensions
# Xhtml-safe substitution for '<%' in a masterview template
# NOTE: you must also update inline_erb_substitution_regex if this is changed.
#
# Default: '{{{'
attr_accessor :inline_erb_start
# Xhtml safe substitution for '%>'in a masterview template
# NOTE: you must also update inline_erb_substitution_regex if this is changed.
#
# Default: '}}}'
attr_accessor :inline_erb_end
# regex used to find escaped inline_erb markup.
# Needs to match +inline_erb_start+ and +inline_erb_end+.
attr_accessor :inline_erb_substitution_regex
# boolean to specify whether to use the original (unpatched) rexml sax2parser.
# If this is not true and the REXML version is 3.1.4 -3.1.6 MasterView will use
# a patched version of rexml sax2parser which properly handles doctypes.
# If this is true or the REXML version is outside this range the original
# sax2 parser will be used
#
# Default: false
attr_accessor :use_original_rexml_sax2parser
# === Rails Application Options
# Boolean which specifies whether masterview templates are parsed
# during initial application startup loading.
#
# Set to +false+ to disable all load-time parsing.
# Template parsing must be manually triggered when the auto-parse option is disabled.
#
# Default: +true+
attr_accessor :parse_masterview_templates_at_startup
# Boolean which determines whether masterview templates are automatically
# reparsed if the template changes after initial application loading.
#
# Set to +true+ to monitor masterview templates for file system changes
# during Rails request dispatching.
#
# Set to +false+ to disable changed-template monitoring.
# Template parsing must be manually triggered when the auto-parse option is disabled.
#
# This option is only supported when running as part of a Rails application.
# It is enabled by default in the standard Rails development configuration
# (+parse_masterview_templates_at_startup+ is enabled and
# ActionController::Base.perform_caching is false so that changed classes
# are dynamically reloaded during request dispatching).
#
# Automatic change-detection and reparsing of masterview templates is disabled
# by default in the usual Rails configuration for production mode
# (ActionController::Base.perform_caching is on to enable loaded class caching).
#
# Default: +true+ if parse_masterview_templates_at_startup is enabled
# and ActionController is reloading changed classes (no caching)
attr_accessor :reparse_changed_masterview_templates
# Authorization module which will be mixed into MasterView Admin controller
# to determine whether access should be granted to a user.
#
# The module must implement the method allow_access? which returns a boolean
# indicating whether access is allowed to the MasterView administration pages.
# By default, the module name for a custom mixin is MasterViewAdminAuthMixin.
#
# The admin_auth_check mixin is included in an ApplicationController subclass
# and thus may use any services available to controllers in your application.
#
# Defaults to local machine access only using local_request? check.
# Automatically installs custom mixin from app/masterview/admin_auth_mixin.rb
# if available.
#
# To load a different file from app/masterview or use a different module name:
#
# config.admin_auth_mixin = {
# :file => 'alt_admin_auth_mixin', # module file in #{RAILS_ROOT}/app/masterview dir
# :module => :AltMasterViewAdminAuthMixin, # default is :MasterViewAdminAuthMixin
# }
#
# To load a mixin from the rails app's lib directory:
#
# config.admin_auth_mixin = {
# :file => 'lib/custom/mv_admin_auth_mixin', # module file in rails lib dir
# :file_loc => :RAILS_ROOT, # default location for rel refs is #{RAILS_ROOT}/app/masterview
# :module => :CustomMasterViewAdminAuthMixin, # default is :MasterViewAdminAuthMixin
# }
#
# see examples/rails_app_admin_auth/admin_auth_mixin.rb for more details
attr_accessor :admin_auth_mixin
# Enable MasterView admin pages in the rails application.
#
# Enables the masterview admin controller at http://yourappdomain/masterview.
#
# Default: +false+
attr_accessor :enable_admin_pages
# Enable MasterView admin view rhtml feature
#
# If MasterView admin pages are enabled, then you may set this feature to true and it
# will allow you to get to the generated rhtml from the MasterView admin page which is
# especially useful for debugging or understanding how MasterView is interpretting the
# your templates. When enabled you may click on any of the generated rhtml parts from
# the list view and it will show you the rhtml that is generated. For security purposes
# you will probably want this to disable this feature in production (or even better
# disable admin pages itself). Alternatively there is a rake task called mv:view_rhtml
# which allows you to see the rhtml from the command line (regardless of this setting).
#
# Default +false+
attr_accessor :enable_view_rhtml
# Generate rhtml files if true, rails will load them from there. Otherwise when this
# setting is false, enable rails app to read rhtml(erb) directly from MasterView cache
# bypassing the serialization to the file system.
# Default: +false+
attr_accessor :generate_rhtml_files
# These are the original default parser options, whatever is set in the config
# will be merged with these to arrive at the result. This allows us to easily
# add new defaults in and even if users empty this hash, the defaults will get added
# to disable they specifically set something to false or nil
OriginalDefaultParserOptions = { :tidy => false, :escape_erb => true, :default_generate => true } # :nodoc: save the originals
# list of [ :log_level, msg ] pairs for config initialization/validation messages
# used by the initializer to validate load path and report any problems
attr_accessor :initialization_messages #:nodoc:
# Create a new Configuration instance, initialized with the default
# values.
#
# Optional arguments to the constructor allow for initializing defaults
# for a rails app when not actually running rails or for initializing
# defaults for a standalone app with context anchored at some well-defined
# point in space (other than where we're actually running).
#
# :app_root_path => path to the root directory of the application
# :rails_app_root_path => path to the root directory of a Rails app
# :environment => current environment for loading settings
#
# The +app_root_path+ and +rails_app_root_path+ arguments are mutually exclusive.
# Use +rails_app_root_path+ when operating on a Rails application which
# isn't actually running; use +app_root_path+ for a non-rails application.
#
def initialize( params={} ) #:nodoc:
rails_env = (defined?(RAILS_ENV)) ? RAILS_ENV : nil
# unpack the supported keyword args
app_root_path = params[:app_root_path]
rails_app_root_path = params[:rails_app_root_path]
env = params[:environment] || rails_env
is_development = env == 'development'
program_root_path = File.expand_path( '.' )
#assert program_root_path == Dir.pwd
debug_TRACE_HACK = false #temp dev debug tracking for rails vs. non-rails init
@mv_code_base_dir = File.expand_path( File.dirname(__FILE__) )
builtin_directives_path = File.join( mv_code_base_dir, 'directives')
@mv_installation_dir = File.expand_path( "#{File.dirname(__FILE__)}/../.." )
@auto_copy_file_entries = []
#ISSUE: should probably also detect std console or breakpointer launch scripts [DJL 10-Jun-2006]
@rails_runner_scripts_pattern = /server|dispatch|mongrel_rails|cgi|-e/ #cgi picks up scgi and fcgi, -e for RadRails
@has_rails_context = (defined?(::RAILS_ROOT) != nil)
decide_if_running_rails
# only run if auto parsing and when launching server, check if need to update mv files
# MasterView::ParseMasterViewTemplatesAtStartup
#ISSUE: what about console and breakpointer scripts?
if debug_TRACE_HACK
STDOUT.puts "\n####Initializing MasterView config (default settings)"
STDOUT.puts "...RAILS_ROOT=#{defined?(::RAILS_ROOT) ? ::RAILS_ROOT : '(undefined)'}"
STDOUT.puts "...has_rails_context=#{has_rails_context.nil? ? 'nil' : has_rails_context}"
end
# establish the fundamental roots for anchoring relative references
# Scenarios: running a rails app, operating on a rails app but not running it, standalone
#assert at most one of the two args is supplied, they're mutually exclusive
if has_rails_context
@rails_root_path = File.expand_path( ::RAILS_ROOT )
elsif rails_app_root_path
@rails_root_path = File.expand_path( rails_app_root_path )
elsif app_root_path.nil? && looks_like_rails_app?
# if client hasn't specifically provided an app or rails root anchor parm
# and we aren't actually launching rails, sniff around and make a good guess
# at whether this app has the rails structure so we can set proper defaults
# (e.g., running rake tasks on a rails app)
# ISSUE: Is this just too clever? Maybe the rakefile should establish this?
# [DJL 15-Ajun-2006]
@rails_root_path = program_root_path
else
@rails_root_path = nil
end
@rails_views_dir_path = rails_app? ? File.join( rails_root_path, 'app/views' ) : nil
#if on_rails?
# #ISSUE: ActionController::Base.template_root vs. ActionController::Base.view_root???? significant diff??
# #assert rails_views_path ==ActionController::Base.template_root
#end
if debug_TRACE_HACK
STDOUT.puts "...rails_app?=#{rails_app? ? 'yes' : 'nope'}"
STDOUT.puts "...on_rails?=#{on_rails? ? 'yes' : 'nope'}"
end
if debug_TRACE_HACK and rails_app?
STDOUT.puts "...rails_root_path=#{self.rails_root_path}"
STDOUT.puts "...rails_views_dir_path=#{rails_views_dir_path}"
end
# general options - establish the roots to anchor MV in an operating context
if rails_app?
self.root_path = self.rails_root_path
elsif app_root_path
self.root_path = File.expand_path( app_root_path )
else
#ISSUE: add hook here to check for MASTERVIEW_ROOT_PATH or ENV[xxx]???
self.root_path = program_root_path
end
# For a rails app, we have a point of view on where to find config files.
# A standalone client needs to proactively tell us where to find their settings.
self.config_dir_path = rails_app? ? "config/masterview" : nil
self.environment = on_rails? ? ::RAILS_ENV : env
self.directive_load_path = DirectiveLoadPath::Path.new
add_directive_path builtin_directives_path, { :use_masterview_namespace => true, }
discover_standard_directive_path_additions()
#TODO: if rails_app? && File.exist?( "#{rails_root_path}/app/masterview/directives" ) THEN append it as well
self.rebuild_backups_tmp_dir_path = rails_app? ? File.join( rails_root_path, 'tmp/masterview/rebuild/backups') : nil
# use Log4r by default if available, otherwise Logger from standard ruby library
# find out if Kernel#require will succeed if we try to load Log4R
log4r_dir = $:.detect { |dir| File.exists?("#{dir}/log4r.rb") } #? path_contains? predicate somewhere??
self.logger = log4r_dir ? 'log4r' : 'logger'
#self.log_level = nil # use the default level of the logger
self.log_level = (self.environment == 'development') ? 'INFO' : 'WARN'
if debug_TRACE_HACK
STDOUT.puts "...root_path=#{root_path}"
STDOUT.puts "...config_dir_path=#{config_dir_path || 'nil'}"
STDOUT.puts "...environment=#{environment || 'nil'}"
STDOUT.puts "...directive_load_path=[ #{directive_load_path.directory_paths.join(', ')} ]"
end
# template source options
self.template_src_dir_path = rails_app? ? 'app/views' : 'masterview/templates' # bolts down abs ref
self.template_filename_pattern = '*.html'
self.template_asset_base_ref_pattern = {
:images => /public\/images\/(.*)/,
:stylesheets => /public\/stylesheets\/(.*)/,
:javascripts => /public\/javascripts\/(.*)/,
}
STDOUT.puts "...template_src_dir_path=#{template_src_dir_path || 'nil'}" if debug_TRACE_HACK
# template generation options
self.template_dst_dir_path = rails_app? ? 'app/views' : 'masterview/output' # bolts down abs ref
self.output_filename_extension = '.rhtml'
self.generated_file_default_extension = '.rhtml'
self.include_generated_file_comment = true
self.generated_file_comment = <<-END
# WARNING - This is a generated file created by MasterView.
# Do not edit - changes will be lost when this file is re-generated.
#
# To make changes, edit the MasterView source file located at:
END
self.generated_file_comment << '# #{template_path}' # carefully avoid premature subst eval
STDOUT.puts "...template_dst_dir_path=#{template_dst_dir_path || 'nil'}" if debug_TRACE_HACK
# template parsing options
self.handle_parse_exceptions = true
self.default_parser_options = OriginalDefaultParserOptions.clone #we'll merge in whatever changes they make over the original set in const above
# default locations where tidy likely to be found; assume on user's PATH if on Windows
self.tidy_path = RUBY_PLATFORM =~ /mswin32/ ? 'c:/tidy/lib/tidy.dll' : '/usr/lib/libtidy.so'
self.namespace_prefix = 'mv:'
self.namespace_prefix_extensions = 'mvx:'
self.inline_erb_start = '{{{'
self.inline_erb_end = '}}}'
self.inline_erb_substitution_regex = /\{\{\{(([^}]|\}[^}]|\}\}[^}])*)\}\}\}/
# Rails application options
self.parse_masterview_templates_at_startup = true
self.reparse_changed_masterview_templates = on_rails? ? (not ActionController::Base.perform_caching) : false
self.admin_auth_mixin = nil # if defined then this module will be included in MasterView controller
self.enable_admin_pages = is_development
self.enable_view_rhtml = is_development
self.generate_rhtml_files = false
STDOUT.puts "...mv config initialized with default settings\n" if debug_TRACE_HACK
self.initialization_messages = [] # for installer validation checking and problem reporting
end
def decide_if_running_rails #:nodoc:
#old way using program name except that this needs to be maintained
@running_rails = has_rails_context && ($PROGRAM_NAME =~ rails_runner_scripts_pattern) != nil
# TODO could try checking if things are defined instead but what would we check for?? Something related to Dispatcher?
# Dispatcher doesn't seemed to be defined when we come through here
# @running_rails = has_rails_context && (defined?(::Dispatcher.dispatch)) != nil
end
# see if this app has the std file structure that indicates a rails application
def looks_like_rails_app? #:nodoc:
std_rails_directories = [ 'app', 'config', 'public' ]
std_rails_directories.each { | dir_path |
return if ! File.directory?(dir_path)
}
true
end
# automatically append directives in std app dir to mv builtins, if available
def discover_standard_directive_path_additions() #:nodoc:
return if ! rails_app? #?or can we take a point of view of std loc? e.g., 'masterview/directives'
app_directives_path = rails_app? ? "app/masterview/directives" : nil #??"masterview/directives"?
app_directives_path = File.join( root_path, app_directives_path )
if File.directory?(app_directives_path) #?and not empty?
#{ :use_masterview_namespace => false, }
add_directive_path app_directives_path #root_path already expanded
end
end
# The path to the application's config settings file.
# By default the file is at config/masterview/settings.rb.
def app_settings_path
config_dir_path ? "#{config_dir_path}/settings.rb" : nil
end
# The path to the current environment's settings file (development.rb, etc.).
# By default the file is at config/masterview/environments/{environment}.rb.
def environment_settings_path
(config_dir_path && environment) ? "#{config_dir_path}/environments/#{environment}.rb" : nil
end
# Add masterview directive implementations from plugins
# to the masterview directive load path
#
# EXPERIMENTAL PROTOTYPE - NOT YET PUBLICIZED [DJL 13-Feb-2007]
def add_plugin_directives(*base_paths) #:nodo:
# append plugin directives in alphabetical order, per Rails plugin loading
plugin_directives = find_plugin_directives(base_paths)
plugin_directives.each { | plugin_dir_path |
add_directive_path plugin_dir_path
}
plugin_directives
end
protected
# Return list of directives paths from plugins
def find_plugin_directives(*base_paths)
directive_paths = []
find_plugins(base_paths).each do | plugin_dir_path |
next if File.basename(plugin_dir_path) == 'masterview' #skip masterview's own built-in directives
directive_dir_path = File.join(plugin_dir_path, 'directives')
if File.directory?(directive_dir_path)
directive_paths << directive_dir_path
else
directive_dir_path = File.join(plugin_dir_path, 'lib', 'directives')
if File.directory?(directive_dir_path)
directive_paths << directive_dir_path
end
end
end
directive_paths
end
# cloned from Rails::Initializer {1.1.2); updated for Rails 1.2.2
# Return a list of plugin paths within base_path. A plugin path is
# a directory that contains either a lib directory or an init.rb file.
# This recurses into directories which are not plugin paths, so you
# may organize your plugins within the plugin path.
def find_plugins(*base_paths) #:nodoc:
base_paths.flatten.inject([]) do |plugins, base_path|
Dir.glob(File.join(base_path, '*')).each do |path|
if plugin_path?(path)
plugins << path if plugin_enabled?(path) #enabled check per Rails 1.2
elsif File.directory?(path)
plugins += find_plugins(path)
end
end
plugins
end
end
# cloned from Rails::Initializer {1.1.2)
def plugin_path?(path) #:nodoc:
File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb')))
end
# cloned from Rails::Initializer {1.2.2)
def plugin_enabled?(path) #:nodoc:
#ISSUE: how can our plugin init get at the rails config which is loading it?
#... don't want to duplicate notion of enabled plugins
###TODO: (rails) configuration.plugins.nil? || configuration.plugins.include?(File.basename(path))
true
end
end
# The Initializer is responsible for processing the MasterView configuration.
#
# In a Rails application using the MasterView facilities,
# the Initializer is run automatically during rails startup
# when the plugin is loaded. No special action is required
# by the application.
#
# To customize the MasterView configuration, provide config settings files
# in your application's config directory, setting standard settings
# in config/masterview/settings.rb and optionally setting
# environment-specific values in config/masterview/environments/{environment}.rb.
# The settings files are executed with the +config+ variable initialized to
# the current configuration. This is the standard technique for a Rails application.
#
# An application using the MasterView facilities outside the Rails context
# must run the MasterView::Initializer during its application startup.
#
# It can be run as a simple command that uses the default configuration:
#
# require 'masterview/initializer'
# MasterView::Initializer.run
#
# or more simply just load MasterView:
#
# require 'masterview'
#
# In order to customize the MasterView configuration outside the Rails context,
# you must explicitly provide a basic Configuration to the Initializer
# which specifies the root location of the application and the paths
# to the template directories if the default locations are not appropriate.
# Configuration settings can be set directly or provided through config
# settings file by specifying the +config_dir_path+ to the application's
# setttings files.
#
# require 'masterview/initializer'
# config = MasterView::Configuration( :app_root_path = '/path/to/my/app' )
# config.environment = 'production'
# config.config_dir_path = 'masterview/config'
# #... additional settings will be loaded from the config dir...
# Masterview::Initializer( :process, config )
#
class Initializer
# The Configuration instance used by this Initializer instance.
attr_reader :configuration
# Runs the initializer. By default, this will invoke the #process method,
# which simply executes all of the initialization routines. Alternately,
# you can specify explicitly which initialization routine you want:
#
# MasterView::Initializer.run(:initialize_configuration)
#
# This is useful if you only want the config settings initialized, without
# incurring the overhead of completely loading the entire component.
#
def self.run(command = :process, configuration = Configuration.new)
if block_given?
yield configuration
end
initializer = new(configuration)
initializer.send(command)
initializer
end
# Create a new Initializer instance that references the given Configuration
# instance.
def initialize(config) #:nodoc:
@configuration = config
end
# Load the MasterView configuration settings
# and initialize the template engine.
def process
initialize_configuration
load_plugin
complete_plugin_installation
end
# Load the Masterview configuration settings.
# Does *not* load and configure the template engine.
#
# Intended for use in testing.
def initialize_configuration
#?? return if MasterView.const_defined?(:ConfigSettings) ??
#ISSUE: support format_datetime option on the logger settings?? [DJL 30-Jul-2006]
configuration.initialization_messages << [ :info,
"Initializing MasterView configuration (#{DateTime.now.strftime('%Y-%m-%d %H:%M')})" ]
configuration.initialization_messages << [ :info,
"Program name = #{$PROGRAM_NAME}" ] # log the program that is starting the session
load_config_settings
ensure_valid_settings
install_config_settings
# make a final check for running_rails? (in case config settings changed scripts spec)
configuration.decide_if_running_rails
# keep a permananent record of how we got started
configuration.freeze
MasterView.const_set('ConfigSettings', configuration)
end
# Load configuration settings from {config.config_dir_path}/settings.rb
# and {config.config_dir_path}/environments/{config.environment}.rb.
def load_config_settings #:nodoc:
load_app_settings
load_environment_settings
end
# Loads application config settings.
def load_app_settings #:nodoc:
load_settings_file(configuration.app_settings_path)
end
# Loads config settings for the environment specified by
# Configuration#environment_path, which
# is typically one of development, testing, or production.
def load_environment_settings #:nodoc:
load_settings_file(configuration.environment_settings_path)
end
# Loads MasterView config settings from the specified config file.
def load_settings_file(config_file_path) #:nodoc:
return if not (config_file_path && File.file?(config_file_path))
config = configuration # define config var in the binding context of the settings eval
eval(IO.read(config_file_path), binding)
end
# ensure that the requested configuration settings are consistent and valid
# before we actually install anything. Normalize representations as needed.
def ensure_valid_settings #:nodoc:
config = configuration
#??config.root_path = File.expand_path(config.root_path) if config.root_path #?? ensure bolted down firmly?
# ensure that the directive load path entries are clean and available
if not config.directive_load_path.empty?
STDOUT.puts "###DEBUG: cleaning directive_load_path #{config.directive_load_path.inspect}" if DEBUG_DIRECTIVE_PATH
clean_path = DirectiveLoadPath.clean_path( config.directive_load_path, :dup_policy => :use_latest ) { | ex |
# record validation error from a path entry and press on
# just note the problem and let initializer report it later
config.initialization_messages << [ :error, ex.message ]
}
config.directive_load_path = clean_path
STDOUT.puts "###DEBUG: CLEANED directive_load_path #{config.directive_load_path.inspect}" if DEBUG_DIRECTIVE_PATH
end
# template source and generation options
if config.on_rails?
#TODO: ensure that the config.template_dst_dir_path is
# in the RAILS_ROOT/app/views directory
# (ActionController::Base.view_root)
# Otherwise all the fine rails view template stuff doesn't work
end
# Rails application options
if config.on_rails?
# ensure we don't activate runtime reparsing if we didn't autoparse at startup
# (?overzealous? But this what we're currently asserting as the intended behavior in the docs)
if not config.parse_masterview_templates_at_startup
config.reparse_changed_masterview_templates = false
end
#else
# self.reparse_changed_masterview_templates = false # not supported
end
end
# Install the configuration settings
def install_config_settings #:nodoc:
set_module_constants
DirectiveLoadPath.default_path_specs = configuration.directive_load_path # does clone before freezing config
end
def set_module_constants #:nodoc:
config = configuration
# create loaded feature map - this map will track exactly what was loaded taking into account failures, so it can differ
# from what is configured. key = feature symbol, value = true if enabled and loaded
MasterView.const_set('LoadedFeatures', {} )
# we don't record root_path or config_dir_path - their purpose is satisfied
# by the time we're done processing this installation configuration
# template source options
MasterView.const_set('TemplateFilenamePattern', config.template_filename_pattern)
# template generation options
MasterView.const_set('OutputExtension', config.output_filename_extension) ###??IS THIS NEEDED???
MasterView.const_set('OmitGeneratedComments', (not config.include_generated_file_comment))
MasterView.const_set('GeneratedCommentText', config.generated_file_comment)
# template parsing options
MasterView.const_set('RescueExceptions', config.handle_parse_exceptions)
MasterView.const_set('DefaultParserOptions', Configuration::OriginalDefaultParserOptions.merge(config.default_parser_options)) # merge in changes with original, so we can add more defaults later, users have to explicitly set an option to false to cancel them
MasterView.const_set('TidyPath', config.tidy_path)
MasterView.const_set('InlineErbStart', config.inline_erb_start)
MasterView.const_set('InlineErbEnd', config.inline_erb_end)
MasterView.const_set('InlineErbSubstitutionRegex', config.inline_erb_substitution_regex)
MasterView.const_set('UseOriginalRexmlSax2Parser', config.use_original_rexml_sax2parser)
# Rails application options
MasterView.const_set('ParseMasterViewTemplatesAtStartup', config.parse_masterview_templates_at_startup)
MasterView.const_set('ReparseChangedMasterViewTemplates', config.reparse_changed_masterview_templates)
MasterView.const_set('EnableMasterViewAdminPages', config.enable_admin_pages)
MasterView.const_set('EnableMasterViewAdminViewRHTML', config.enable_view_rhtml)
end
# Load the masterview code
def load_plugin #:nodoc:
require 'masterview' #:nodoc:
end
# Complete installation of masterview after its own code has been loaded
def complete_plugin_installation #:nodoc:
#?? return if MasterView.const_defined?(:Initialized) ??
MasterView::DirectiveRegistry.register_default_namespaces(
configuration.namespace_prefix,
configuration.namespace_prefix_extensions )
initialize_logger
initialize_mio
set_rexml_parser_class
initialize_auto_copy_files
#Back out experiment: causes load order problems
##load_directives # held off on this until logger is installed
install_in_rails
# mark the module as fully loaded and configured
MasterView.const_set('Initialized', true)
after_initialize # callback to client's afer_initialize block
end
# Initialize MasterView::Log with a logger which emits to std output, default DEBUG level
def initialize_logger #:nodoc:
#?return if defined?(Log)
require 'masterview/extras/init_logger'
# release any queued-up initialization messages yearning to be free
configuration.initialization_messages.each { | msg_level, msg |
Log.send msg_level, msg
}
end
# Initialize the MasterView I/O subsystem
def initialize_mio
config = configuration
MasterView.const_set('DefaultSerializer', TemplateProcessing::MIOSerializer)
# all root_path directory anchor points for I/O are expanded absolute paths
io_mgr = MIO::MIOTrees.new
template_extension = File.extname( config.template_filename_pattern )
io_mgr.template = MIO::FileMIOTree.new( config.template_src_dir_path, template_extension,
:escape_erb => DefaultParserOptions[:escape_erb], # use DefaultParserOptions since already has config merged
:tidy => DefaultParserOptions[:tidy],
:default_generate => DefaultParserOptions[:default_generate],
#TODO: expose the following in Configuration.default_parser_options and document
:caching => false,
:logging => true )
if config.generate_rhtml_files
io_mgr.erb = MIO::FileMIOTree.new( config.template_dst_dir_path, config.generated_file_default_extension, :logging => true)
else
io_mgr.erb = MIO::RailsErbCacheMIOTree.new( config.generated_file_default_extension, :logging => true)
end
io_mgr.backup = MIO::FileMIOTree.new( config.rebuild_backups_tmp_dir_path ) if config.rebuild_backups_tmp_dir_path
MasterView.const_set('IOMgr', io_mgr)
MasterView::LoadedFeatures[:tidy_template_read] = config.default_parser_options[:tidy]
end
# Initialize the auto_copy_files MasterView I/O and AutoCopy setup
def initialize_auto_copy_files
require 'masterview/extras/auto_copy'
auto_copy_file_entries = configuration.auto_copy_file_entries
if auto_copy_file_entries
auto_copy_file_entries.each do |entry|
if entry[:source] && entry[:destination]
sourceMIO = MasterView::MIO::FileMIOTree.new(entry[:source])
destMIO = MasterView::MIO::FileMIOTree.new(entry[:destination])
extensions = entry[:extensions] || []
filename_patterns = extensions.collect{ |ext| "*.#{ext.to_s}" }
filename_patterns = ['*'] if filename_patterns.empty?
filename_pattern_for_log = (filename_patterns.empty?) ? '*' : filename_patterns.join(',')
MasterView::AutoCopy.register(sourceMIO, destMIO, filename_patterns)
MasterView::Log.info{ "Auto copying files from #{entry[:source]} to #{entry[:destination]} with filter #{filename_patterns.join(',')}" }
else
MasterView::Log.error{ 'config.auto_copy_file_entries entry is missing required :source or :destination values, will be ignored. entry='+entry.inspect }
end
end
end
end
# set the REXML SAX2ParserClass, use patched parser or standard SAX2Parser
# checks version of REXML and value of :use_patched_rexml_sax2parser
def set_rexml_parser_class
if REXML::Version < '3.1.4' # below minimum version
MasterView::Log.error { 'Fatal error: MasterView requires REXML version 3.1.4 or greater' }
raise 'Fatal error: MasterView requires REXML version 3.1.4 or greater'
elsif MasterView::UseOriginalRexmlSax2Parser || !['3.1.4', '3.1.5', '3.1.6'].include?(REXML::Version)
MasterView::Log.info { 'MasterView using REXML Sax2Parser version = '+REXML::Version }
require 'rexml/parsers/sax2parser'
MasterView.const_set('REXMLSax2ParserClass', REXML::Parsers::SAX2Parser)
else
MasterView::Log.info { 'MasterView using REXML '+REXML::VERSION+' Sax2ParserWithDoctypeFix' }
require 'rexml/parsers/sax2parser_with_doctype_fix'
MasterView.const_set('REXMLSax2ParserClass', REXML::Parsers::SAX2ParserWithDoctypeFix)
end
end
#NOTE: not currently used - caused problems during startup, so reverted to original
# scheme where loading is triggered on demand by template parsing
# [SJL 20-Sep-2006]
def load_directives #:nodoc:
# get the directives loaded prior to firing up any template parsing
return if ! configuration.on_rails? #ISSUE: causes problem for test cases; is this ever a good idea??
MasterView::DirectiveRegistry.current.process_directives_load_path( configuration.directive_load_path )
end
def install_in_rails #:nodoc:
return if ! configuration.on_rails?
enable_mv_admin_pages
parse_templates_at_startup
auto_copy_at_startup
enable_reparse_changed_templates
enable_rails_erb_direct
MasterView::Log.info{ 'MasterView plugin initialized - Version '+MasterView::VERSION::STRING }
end
# install the MasterviewController to support masterview admin pages in the site
def enable_mv_admin_pages #:nodoc:
return if ! configuration.enable_admin_pages #MasterView::EnableMasterViewAdminPages
require 'masterview/extras/init_mv_admin_pages'
end
def parse_templates_at_startup #:nodoc:
if configuration.parse_masterview_templates_at_startup
require 'masterview/extras/watcher'
MasterView::Log.debug { 'Parsing MasterView templates...' }
MasterView::IOMgr.template.find(:pattern => MasterView::TemplateFilenamePattern) do |mio|
MasterView::Parser.parse_mio(mio, MasterView::IOMgr.erb)
end
MasterView::LoadedFeatures[:rails_parse_at_startup] = true
end
end
def auto_copy_at_startup #:nodoc:
MasterView::AutoCopy.copy_all_updated_files
end
#--
# DBC-style notation per DbC - rubydbc-0.1 (Andy Hunt's Design by Contract)
#pre( MasterView::ParseMasterViewTemplatesAtStartup )
#pre( MasterView::ReparseChangedMasterViewTemplates )
#pre( defined?(ActionController) && ActionController::Base.perform_caching )
#++
def enable_reparse_changed_templates #:nodoc:
if configuration.reparse_changed_masterview_templates #MasterView::ReparseChangedMasterViewTemplates
# if not caching then check for masterview updates on every request
# DBC-style notation per DbC - rubydbc-0.1 (Andy Hunt's Design by Contract)
#pre( MasterView::ParseMasterViewTemplatesAtStartup )
#pre( defined?(ActionController) && ActionController::Base.perform_caching )
require 'masterview/extras/watcher' #:nodoc:
MasterView::Log.info { 'Adding hook to allow MasterView to check for templates that have changed when processing a request' }
require 'masterview/rails_ext/action_controller_reparse_checking' #:nodoc:
MasterView::LoadedFeatures[:rails_reparse_checking] = true
end
end
def enable_rails_erb_direct #:nodoc:
unless configuration.generate_rhtml_files
# if not generating rhtml the read erb directly from masterview
MasterView::Log.info { 'Adding hooks to enable Rails to read erb directly from MasterView' }
require 'masterview/rails_ext/action_view_erb_direct' #:nodoc:
require 'masterview/rails_ext/action_controller_erb_direct' #:nodoc:
MasterView::LoadedFeatures[:rails_erb_mv_direct] = true
end
end
# Fires the user-supplied after_initialize block (Configuration#after_initialize)
def after_initialize #:nodoc:
configuration.after_initialize_block.call if configuration.after_initialize_block
end
end
end