Created
November 17, 2011 20:00
-
-
Save argent-smith/1374322 to your computer and use it in GitHub Desktop.
a conflict when tried to merge...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Tilt | |
TOPOBJECT = defined?(BasicObject) ? BasicObject : Object | |
# Base class for template implementations. Subclasses must implement | |
# the #prepare method and one of the #evaluate or #precompiled_template | |
# methods. | |
class Template | |
# Template source; loaded from a file or given directly. | |
attr_reader :data | |
# The name of the file where the template data was loaded from. | |
attr_reader :file | |
# The line number in #file where template data was loaded from. | |
attr_reader :line | |
# A Hash of template engine specific options. This is passed directly | |
# to the underlying engine and is not used by the generic template | |
# interface. | |
attr_reader :options | |
# Used to determine if this class's initialize_engine method has | |
# been called yet. | |
@engine_initialized = false | |
class << self | |
attr_accessor :engine_initialized | |
alias engine_initialized? engine_initialized | |
attr_accessor :default_mime_type | |
end | |
# Create a new template with the file, line, and options specified. By | |
# default, template data is read from file and assumed to be in the | |
# system default external encoding (Encoding.default_external). When a | |
# block is given, it should read template data and return a String with | |
# a best guess encoding. | |
# | |
# The :default_encoding option is supported by most template engines. When | |
# set, data read from disk will be assumed to be in this encoding instead | |
# of Encoding.default_external. The option has no effect when a custom | |
# reader block is given. | |
# | |
# All arguments are optional but a file or block must be specified. | |
def initialize(file=nil, line=1, options={}, &block) | |
@file, @line, @options = nil, 1, {} | |
[options, line, file].compact.each do |arg| | |
case | |
when arg.respond_to?(:to_str) ; @file = arg.to_str | |
when arg.respond_to?(:to_int) ; @line = arg.to_int | |
when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup | |
else raise TypeError | |
end | |
end | |
raise ArgumentError, "file or block required" if (@file || block).nil? | |
# call the initialize_engine method if this is the very first time | |
# an instance of this class has been created. | |
if !self.class.engine_initialized? | |
initialize_engine | |
self.class.engine_initialized = true | |
end | |
# used to hold compiled template methods | |
@compiled_method = {} | |
# Overrides Encoding.default_external when reading from filesystem | |
@default_encoding = @options.delete :default_encoding | |
# load template data and prepare (uses binread to avoid encoding issues) | |
@reader = block || lambda { |t| read_template_file } | |
@data = @reader.call(self) | |
prepare | |
end | |
# Render the template in the given scope with the locals specified. If a | |
# block is given, it is typically available within the template via | |
# +yield+. | |
def render(scope=Object.new, locals={}, &block) | |
evaluate scope, locals || {}, &block | |
end | |
# The basename of the template file. | |
def basename(suffix='') | |
File.basename(file, suffix) if file | |
end | |
# The template file's basename with all extensions chomped off. | |
def name | |
basename.split('.', 2).first if basename | |
end | |
# The filename used in backtraces to describe the template. | |
def eval_file | |
file || '(__TEMPLATE__)' | |
end | |
protected | |
# Called once and only once for each template subclass the first time | |
# the template class is initialized. This should be used to require the | |
# underlying template library and perform any initial setup. | |
def initialize_engine | |
end | |
# Read template data from file, possibly overriding the encoding based on | |
# the default_encoding option. This is used when the object is created with | |
# a file and no reader block. | |
# | |
# Unlike File.read, this method does not transcode into the system | |
# Encoding.default_internal encoding. The best guess encoding is set and | |
# available from data.encoding. | |
# | |
# Subclasses may override this method if they have specific knowledge about | |
# the file's encoding and can provide better default encoding support. | |
# | |
# Raise exception when file doesn't exist. | |
# Does not raise an exception when the file's data is invalid in the best | |
# guess encoding. | |
def read_template_file | |
data = File.open(file, 'rb') { |io| io.read } | |
if data.respond_to?(:force_encoding) | |
encoding = @default_encoding || Encoding.default_external | |
data.force_encoding(encoding) | |
end | |
data | |
end | |
# Like Kernel#require but issues a warning urging a manual require when | |
# running under a threaded environment. | |
def require_template_library(name) | |
if Thread.list.size > 1 | |
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " + | |
"explicit require '#{name}' suggested." | |
end | |
require name | |
end | |
# Do whatever preparation is necessary to setup the underlying template | |
# engine. Called immediately after template data is loaded. Instance | |
# variables set in this method are available when #evaluate is called. | |
# | |
# Subclasses must provide an implementation of this method. | |
# | |
# The data attribute holds the template source string marked with the best | |
# guess encoding. When the template was read from the filesystem this will | |
# be either the :default_encoding provided when the template was created or | |
# the system default Encoding.default_external encoding. When the template | |
# data was provided via reader block, it will be in whatever encoding was | |
# set on the string originally. Subclasses are responsible for detecting | |
# template specific magic syntax encodings embedded in the template data. | |
def prepare | |
if respond_to?(:compile!) | |
# backward compat with tilt < 0.6; just in case | |
warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.' | |
compile! | |
else | |
raise NotImplementedError | |
end | |
end | |
# Execute the compiled template and return the result string. Template | |
# evaluation is guaranteed to be performed in the scope object with the | |
# locals specified and with support for yielding to the block. | |
# | |
# This method is only used by source generating templates. Subclasses that | |
# override render() may not support all features. | |
def evaluate(scope, locals, &block) | |
method = compiled_method(locals.keys) | |
method.bind(scope).call(locals, &block) | |
end | |
# Generates all template source by combining the preamble, template, and | |
# postamble and returns a two-tuple of the form: [source, offset], where | |
# source is the string containing (Ruby) source code for the template and | |
# offset is the integer line offset where line reporting should begin. | |
# | |
# Template subclasses may override this method when they need complete | |
# control over source generation or want to adjust the default line | |
# offset. In most cases, overriding the #precompiled_template method is | |
# easier and more appropriate. | |
def precompiled(locals) | |
preamble = precompiled_preamble(locals) | |
template = precompiled_template(locals) | |
source = '' | |
if source.respond_to?(:force_encoding) | |
source.force_encoding template.encoding | |
end | |
source << preamble | |
source << "\n" | |
source << template | |
source << "\n" | |
source << precompiled_postamble(locals) | |
[source, preamble.count("\n") + 1] | |
end | |
# A string containing the (Ruby) source code for the template. The | |
# default Template#evaluate implementation requires either this method | |
# or the #precompiled method be overridden. When defined, the base | |
# Template guarantees correct file/line handling, locals support, custom | |
# scopes, and support for template compilation when the scope object | |
# allows it. | |
def precompiled_template(locals) | |
raise NotImplementedError | |
end | |
# Generates preamble code for initializing template state, and performing | |
# locals assignment. The default implementation performs locals | |
# assignment only. Lines included in the preamble are subtracted from the | |
# source line offset, so adding code to the preamble does not effect line | |
# reporting in Kernel::caller and backtraces. | |
def precompiled_preamble(locals) | |
locals.map { |k,v| "#{k} = locals[#{k.inspect}]" }.join("\n") | |
end | |
# Generates postamble code for the precompiled template source. The | |
# string returned from this method is appended to the precompiled | |
# template source. | |
def precompiled_postamble(locals) | |
'' | |
end | |
# The compiled method for the locals keys provided. | |
def compiled_method(locals_keys) | |
@compiled_method[locals_keys] ||= | |
compile_template_method(locals_keys) | |
end | |
private | |
def compile_template_method(locals) | |
source, offset = precompiled(locals) | |
method_name = "__tilt_#{Thread.current.object_id.abs}" | |
<<<<<<< HEAD | |
method_source = <<-RUBY | |
#{extract_magic_comment source} | |
======= | |
method_source = "" | |
if method_source.respond_to?(:force_encoding) | |
method_source.force_encoding source.encoding | |
end | |
method_source << <<-RUBY | |
>>>>>>> upstream/encodings | |
TOPOBJECT.class_eval do | |
def #{method_name}(locals) | |
Thread.current[:tilt_vars] = [self, locals] | |
class << self | |
this, locals = Thread.current[:tilt_vars] | |
this.instance_eval do | |
<<<<<<< HEAD | |
RUBY | |
offset += method_source.count("\n") | |
method_source << source | |
method_source << "\nend;end;end;end" | |
======= | |
RUBY | |
method_source << source | |
method_source << <<-RUBY | |
end | |
end | |
end | |
end | |
RUBY | |
>>>>>>> upstream/encodings | |
Object.class_eval method_source, eval_file, line - offset | |
unbind_compiled_method(method_name) | |
end | |
def unbind_compiled_method(method_name) | |
method = TOPOBJECT.instance_method(method_name) | |
TOPOBJECT.class_eval { remove_method(method_name) } | |
method | |
end | |
# Regexp used to find and remove magic comment lines from Ruby source. | |
MAGIC = /\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*?\n/mn | |
# Checks for a Ruby 1.9 encoding comment on the first line of source. | |
# | |
# source - string to check for magic comment line | |
# remove - set true to remove the line from the string in place | |
# | |
# Returns the encoding name string or nil when no comment was present. | |
def extract_source_encoding(source, remove=false) | |
binary source do | |
slice = remove ? :slice! : :slice | |
$1 if source.__send__(slice, MAGIC) | |
end | |
end | |
# Extract encoding comment from source and mark the string's encoding. The | |
# string is modified in place. When no encoding is found, the encoding | |
# passed in the default argument is used. The remove argument can be set | |
# true to remove the magic comment line from the source string in place. | |
# | |
# This method is a no-op under Ruby < 1.9 | |
if ''.respond_to?(:force_encoding) | |
def assign_source_encoding(source, default=nil, remove=false) | |
if encoding = extract_source_encoding(source, remove) | |
source.force_encoding(encoding) | |
elsif default | |
source.force_encoding(default) | |
else | |
source | |
end | |
end | |
else | |
def assign_source_encoding(source, *args) | |
source | |
end | |
end | |
# Mark the string as BINARY/ASCII-8BIT for the duration of the block. The | |
# string is reset to its original encoding before this method returns. This | |
# combined with //n flagged regular expressions is one way to avoid encoding | |
# compatibility errors while a string's encoding is still in best guess mode. | |
if ''.respond_to?(:force_encoding) | |
def binary(string) | |
original_encoding = string.encoding | |
string.force_encoding 'BINARY' | |
yield | |
ensure | |
string.force_encoding original_encoding | |
end | |
else | |
def binary(string) | |
yield string | |
end | |
end | |
# Special case Ruby 1.9.1's broken yield. | |
# | |
# http://github.com/rtomayko/tilt/commit/20c01a5 | |
# http://redmine.ruby-lang.org/issues/show/3601 | |
# | |
# Remove when 1.9.2 dominates 1.9.1 installs in the wild. | |
if RUBY_VERSION =~ /^1.9.1/ | |
undef compile_template_method | |
def compile_template_method(locals) | |
source, offset = precompiled(locals) | |
offset += 1 | |
method_name = "__tilt_#{Thread.current.object_id}" | |
Object.class_eval <<-RUBY, eval_file, line - offset | |
TOPOBJECT.class_eval do | |
def #{method_name}(locals) | |
#{source} | |
end | |
end | |
RUBY | |
unbind_compiled_method(method_name) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment