cutaneous 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +0 -0
  3. data/README.md +0 -0
  4. data/Rakefile +150 -0
  5. data/cutaneous.gemspec +103 -0
  6. data/lib/cutaneous/compiler/expression.rb +85 -0
  7. data/lib/cutaneous/compiler.rb +223 -0
  8. data/lib/cutaneous/context.rb +70 -0
  9. data/lib/cutaneous/engine.rb +66 -0
  10. data/lib/cutaneous/lexer.rb +92 -0
  11. data/lib/cutaneous/loader.rb +147 -0
  12. data/lib/cutaneous/syntax.rb +39 -0
  13. data/lib/cutaneous/template.rb +40 -0
  14. data/lib/cutaneous.rb +23 -0
  15. data/test/fixtures/a.html.cut +13 -0
  16. data/test/fixtures/b.html.cut +8 -0
  17. data/test/fixtures/c.html.cut +8 -0
  18. data/test/fixtures/comments.html.cut +1 -0
  19. data/test/fixtures/d.html.cut +8 -0
  20. data/test/fixtures/e.html.cut +4 -0
  21. data/test/fixtures/error.html.cut +30 -0
  22. data/test/fixtures/expressions.html.cut +1 -0
  23. data/test/fixtures/include.html.cut +3 -0
  24. data/test/fixtures/include.rss.cut +3 -0
  25. data/test/fixtures/included_error.html.cut +1 -0
  26. data/test/fixtures/instance.html.cut +2 -0
  27. data/test/fixtures/instance_include.html.cut +1 -0
  28. data/test/fixtures/missing.html.cut +1 -0
  29. data/test/fixtures/other/different.html.cut +1 -0
  30. data/test/fixtures/other/error.html.cut +5 -0
  31. data/test/fixtures/partial.html.cut +1 -0
  32. data/test/fixtures/partial.rss.cut +1 -0
  33. data/test/fixtures/render.html.cut +6 -0
  34. data/test/fixtures/statements.html.cut +3 -0
  35. data/test/fixtures/target.html.cut +1 -0
  36. data/test/fixtures/whitespace.html.cut +6 -0
  37. data/test/helper.rb +18 -0
  38. data/test/test_blocks.rb +19 -0
  39. data/test/test_cache.rb +104 -0
  40. data/test/test_core.rb +168 -0
  41. metadata +90 -0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
File without changes
data/README.md ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,150 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/test_*.rb'
52
+ test.verbose = false
53
+ end
54
+
55
+ desc "Generate RCov test coverage and open in your browser"
56
+ task :coverage do
57
+ require 'rcov'
58
+ sh "rm -fr coverage"
59
+ sh "rcov test/test_*.rb"
60
+ sh "open coverage/index.html"
61
+ end
62
+
63
+ require 'rdoc/task'
64
+ Rake::RDocTask.new do |rdoc|
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "#{name} #{version}"
67
+ rdoc.rdoc_files.include('README*')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
70
+
71
+ desc "Open an irb session preloaded with this library"
72
+ task :console do
73
+ sh "irb -rubygems -r ./lib/#{name}.rb"
74
+ end
75
+
76
+ #############################################################################
77
+ #
78
+ # Custom tasks (add your own tasks here)
79
+ #
80
+ #############################################################################
81
+
82
+
83
+
84
+ #############################################################################
85
+ #
86
+ # Packaging tasks
87
+ #
88
+ #############################################################################
89
+
90
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
91
+ task :release => :build do
92
+ unless `git branch` =~ /^\* master$/
93
+ puts "You must be on the master branch to release!"
94
+ exit!
95
+ end
96
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
97
+ sh "git tag v#{version}"
98
+ sh "git push origin master"
99
+ sh "git push origin v#{version}"
100
+ sh "gem push pkg/#{name}-#{version}.gem"
101
+ end
102
+
103
+ desc "Build #{gem_file} into the pkg directory"
104
+ task :build => :gemspec do
105
+ sh "mkdir -p pkg"
106
+ sh "gem build #{gemspec_file}"
107
+ sh "mv #{gem_file} pkg"
108
+ end
109
+
110
+ desc "Generate #{gemspec_file}"
111
+ task :gemspec => :validate do
112
+ # read spec file and split out manifest section
113
+ spec = File.read(gemspec_file)
114
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
115
+
116
+ # replace name version and date
117
+ replace_header(head, :name)
118
+ replace_header(head, :version)
119
+ replace_header(head, :date)
120
+ #comment this out if your rubyforge_project has a different name
121
+ replace_header(head, :rubyforge_project)
122
+
123
+ # determine file list from git ls-files
124
+ files = `git ls-files`.
125
+ split("\n").
126
+ sort.
127
+ reject { |file| file =~ /^\./ }.
128
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
129
+ map { |file| " #{file}" }.
130
+ join("\n")
131
+
132
+ # piece file back together and write
133
+ manifest = " s.files = %w[\n#{files}\n ]\n"
134
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
135
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
136
+ puts "Updated #{gemspec_file}"
137
+ end
138
+
139
+ desc "Validate #{gemspec_file}"
140
+ task :validate do
141
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
142
+ unless libfiles.empty?
143
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
144
+ exit!
145
+ end
146
+ unless Dir['VERSION*'].empty?
147
+ puts "A `VERSION` file at root level violates Gem best practices."
148
+ exit!
149
+ end
150
+ end
data/cutaneous.gemspec ADDED
@@ -0,0 +1,103 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'cutaneous'
16
+ s.version = '0.1.0'
17
+ s.date = '2012-07-23'
18
+ s.rubyforge_project = 'cutaneous'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = "Short description used in Gem listings."
23
+ s.description = "Long description. Maybe copied from the README."
24
+
25
+ ## List the primary authors. If there are a bunch of authors, it's probably
26
+ ## better to set the email to an email list or something. If you don't have
27
+ ## a custom homepage, consider using your GitHub URL or the like.
28
+ s.authors = ["Garry Hill"]
29
+ s.email = 'garry@spontaneous.io'
30
+ s.homepage = 'https://github.com/SpontaneousCMS/cutaneous'
31
+
32
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
33
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
34
+ s.require_paths = %w[lib]
35
+
36
+ ## If your gem includes any executables, list them here.
37
+ # s.executables = ["name"]
38
+
39
+ ## Specify any RDoc options here. You'll want to add your README and
40
+ ## LICENSE files to the extra_rdoc_files list.
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.extra_rdoc_files = %w[README.md LICENSE]
43
+
44
+ ## List your runtime dependencies here. Runtime dependencies are those
45
+ ## that are needed for an end user to actually USE your code.
46
+ # s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
47
+
48
+ ## List your development dependencies here. Development dependencies are
49
+ ## those that are only needed during development
50
+ # s.add_development_dependency('DEVDEPNAME', [">= 1.1.0", "< 2.0.0"])
51
+
52
+ ## Leave this section as-is. It will be automatically generated from the
53
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
54
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
55
+ # = MANIFEST =
56
+ s.files = %w[
57
+ Gemfile
58
+ LICENSE
59
+ README.md
60
+ Rakefile
61
+ cutaneous.gemspec
62
+ lib/cutaneous.rb
63
+ lib/cutaneous/compiler.rb
64
+ lib/cutaneous/compiler/expression.rb
65
+ lib/cutaneous/context.rb
66
+ lib/cutaneous/engine.rb
67
+ lib/cutaneous/lexer.rb
68
+ lib/cutaneous/loader.rb
69
+ lib/cutaneous/syntax.rb
70
+ lib/cutaneous/template.rb
71
+ test/fixtures/a.html.cut
72
+ test/fixtures/b.html.cut
73
+ test/fixtures/c.html.cut
74
+ test/fixtures/comments.html.cut
75
+ test/fixtures/d.html.cut
76
+ test/fixtures/e.html.cut
77
+ test/fixtures/error.html.cut
78
+ test/fixtures/expressions.html.cut
79
+ test/fixtures/include.html.cut
80
+ test/fixtures/include.rss.cut
81
+ test/fixtures/included_error.html.cut
82
+ test/fixtures/instance.html.cut
83
+ test/fixtures/instance_include.html.cut
84
+ test/fixtures/missing.html.cut
85
+ test/fixtures/other/different.html.cut
86
+ test/fixtures/other/error.html.cut
87
+ test/fixtures/partial.html.cut
88
+ test/fixtures/partial.rss.cut
89
+ test/fixtures/render.html.cut
90
+ test/fixtures/statements.html.cut
91
+ test/fixtures/target.html.cut
92
+ test/fixtures/whitespace.html.cut
93
+ test/helper.rb
94
+ test/test_blocks.rb
95
+ test/test_cache.rb
96
+ test/test_core.rb
97
+ ]
98
+ # = MANIFEST =
99
+
100
+ ## Test files will be grabbed from the file list. Make sure the path glob
101
+ ## matches what you actually use.
102
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
103
+ end
@@ -0,0 +1,85 @@
1
+ module Cutaneous
2
+ class Compiler
3
+ class Expression
4
+ def initialize(expression)
5
+ @expression = expression
6
+ end
7
+
8
+ def to_script
9
+ %{__buf << __decode_params((} << @expression << %{)) ; }
10
+ end
11
+
12
+ def affect(builder)
13
+ builder.push(self)
14
+ end
15
+ end
16
+
17
+ class EscapedExpression < Expression
18
+ def to_script
19
+ %{__buf << escape(__decode_params((} << @expression << %{))) ; }
20
+ end
21
+ end
22
+
23
+ class Statement < Expression
24
+ def to_script
25
+ "" << @expression << " ; "
26
+ end
27
+ end
28
+
29
+ class Text < Expression
30
+ BEGINNING_WHITESPACE ||= /\A(\s*?[\r\n]+)/
31
+
32
+ def initialize(expression, strip_whitespace = false)
33
+ @whitespace = ""
34
+ if strip_whitespace && expression =~ BEGINNING_WHITESPACE
35
+ @whitespace = $1
36
+ expression.slice!(0, @whitespace.length)
37
+ end
38
+ super(expression)
39
+ end
40
+
41
+ def to_script
42
+ %(__buf << %Q`) << @expression << %(` ; ) << @whitespace << ";"
43
+ end
44
+ end
45
+
46
+ class Comment < Expression
47
+ # Need to make sure that the line positions are the same
48
+ def to_script
49
+ $/ * (@expression.count($/))
50
+ end
51
+ end
52
+
53
+ class Extends
54
+ def initialize(template_name)
55
+ @template_name = template_name
56
+ end
57
+
58
+ def affect(builder)
59
+ builder.extends(@template_name)
60
+ end
61
+ end
62
+
63
+ class BlockStart
64
+ def initialize(block_name)
65
+ @block_name = block_name.to_sym
66
+ end
67
+
68
+ def affect(builder)
69
+ builder.block_start(@block_name)
70
+ end
71
+ end
72
+
73
+ class BlockEnd
74
+ def affect(builder)
75
+ builder.block_end
76
+ end
77
+ end
78
+
79
+ class BlockSuper
80
+ def affect(builder)
81
+ builder.block_super
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,223 @@
1
+ require 'cutaneous/compiler/expression'
2
+
3
+ module Cutaneous
4
+ class Compiler
5
+ # A single named block of template expressions
6
+ class Block
7
+ attr_reader :name
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ @expressions = []
12
+ end
13
+
14
+ def push(expression)
15
+ @expressions << expression
16
+ end
17
+
18
+ alias_method :<<, :push
19
+
20
+ def to_script
21
+ script = ""
22
+ @expressions.each do |expression|
23
+ script << expression.to_script
24
+ end
25
+ script
26
+ end
27
+ end
28
+
29
+ # Represents the block structure of a top-level master template,
30
+ # i.e. one with no `extends` call.
31
+ class BlockSet
32
+ attr_reader :current_block
33
+ attr_accessor :loader
34
+
35
+ def initialize
36
+ @block_order = []
37
+ @block_store = {}
38
+ block_start
39
+ end
40
+
41
+ def block_start(name = Object.new)
42
+ @current_block = Block.new(name)
43
+ @block_order << name
44
+ @block_store[name] = @current_block
45
+ end
46
+
47
+ def block_end
48
+ block_start
49
+ end
50
+
51
+ def push(tag)
52
+ @current_block << tag
53
+ end
54
+
55
+ def block_order
56
+ @block_order
57
+ end
58
+
59
+ def super_block
60
+ raise CompilationError.new("Invalid 'blocksuper' call from top-level template")
61
+ end
62
+
63
+ def block(name)
64
+ @block_store[name]
65
+ end
66
+
67
+ def each_block
68
+ block_order.each do |block_name|
69
+ yield block(block_name)
70
+ end
71
+ end
72
+
73
+ def to_script
74
+ script = ""
75
+ each_block do |block|
76
+ script << block.to_script
77
+ end
78
+ script
79
+ end
80
+ end
81
+
82
+ # Represents the structure of a sub-template that inherits its
83
+ # block structure from some parent template defined by an `extends`
84
+ # tag.
85
+ class ExtendedBlockSet < BlockSet
86
+ def initialize(template_name)
87
+ @super_template_name = template_name
88
+ super()
89
+ end
90
+
91
+ def super_template
92
+ @super_template ||= @loader.template(@super_template_name)
93
+ end
94
+
95
+ def super_block
96
+ super_template.block(current_block.name)
97
+ end
98
+
99
+ def block(name)
100
+ return @block_store[name] if @block_store.key?(name)
101
+ super_template.block(name)
102
+ end
103
+
104
+ def block_order
105
+ super_template.block_order
106
+ end
107
+ end
108
+
109
+ # Converts a list of expressions into either a master or child block
110
+ # set.
111
+ class BlockBuilder
112
+ def initialize(loader)
113
+ @loader = loader
114
+ assign_block_set(BlockSet.new)
115
+ end
116
+
117
+ def build(expressions)
118
+ expressions.each do |expression|
119
+ expression.affect(self)
120
+ end
121
+ @block_set
122
+ end
123
+
124
+ def extends(parent)
125
+ assign_block_set(ExtendedBlockSet.new(parent))
126
+ end
127
+
128
+ def assign_block_set(block_set)
129
+ @block_set = block_set
130
+ @block_set.loader = @loader
131
+ end
132
+
133
+ def current_block
134
+ @block_set.current_block
135
+ end
136
+
137
+ def block_start(block_name)
138
+ @block_set.block_start(block_name)
139
+ end
140
+
141
+ def block_end
142
+ @block_set.block_end
143
+ end
144
+
145
+ def block_super
146
+ push(@block_set.super_block)
147
+ end
148
+
149
+ def push(tag)
150
+ @block_set.push(tag)
151
+ end
152
+ end
153
+
154
+ def initialize(lexer, loader)
155
+ @lexer, @loader = lexer, loader
156
+ end
157
+
158
+
159
+ def blocks
160
+ @blocks ||= build_hierarchy
161
+ end
162
+
163
+ def build_hierarchy
164
+ builder = BlockBuilder.new(@loader)
165
+ builder.build(expressions)
166
+ end
167
+
168
+ def expressions
169
+ expressions = []
170
+ strip = nil
171
+ @lexer.tokens.each do |type, expression, strip_whitespace|
172
+ case type
173
+ when :text
174
+ expressions << Text.new(expression, strip)
175
+ when :expression
176
+ expressions << Expression.new(expression)
177
+ when :escaped_expression
178
+ expressions << EscapedExpression.new(expression)
179
+ when :statement
180
+ expressions << parse_statement(expression)
181
+ when :comment
182
+ expressions << Comment.new(expression)
183
+ end
184
+ strip = strip_whitespace
185
+ end
186
+ # We don't need this any more so release it
187
+ @lexer = nil
188
+ expressions
189
+ end
190
+
191
+ EXTENDS = /\A\s*extends\s+["']([^"']+)["']\s*\z/o
192
+ BLOCK_START = /\A\s*block\s+:?([a-zA-Z_][a-zA-Z0-9_]*)\s*\z/o
193
+ BLOCK_END = /\A\s*endblock(?:\s+:?[a-zA-Z_][a-zA-Z0-9_]*)?\s*\z/o
194
+ BLOCK_SUPER = /\A\s*block_?super\s*\z/o
195
+
196
+ def parse_statement(statement)
197
+ case statement
198
+ when EXTENDS
199
+ Extends.new($1)
200
+ when BLOCK_START
201
+ BlockStart.new($1)
202
+ when BLOCK_END
203
+ BlockEnd.new
204
+ when BLOCK_SUPER
205
+ BlockSuper.new
206
+ else
207
+ Statement.new(statement)
208
+ end
209
+ end
210
+
211
+ def script
212
+ blocks.to_script
213
+ end
214
+
215
+ def block_order
216
+ blocks.block_order
217
+ end
218
+
219
+ def block(name)
220
+ blocks.block(name)
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,70 @@
1
+ require 'delegate'
2
+
3
+ module Cutaneous
4
+ class Context < Delegator
5
+ attr_accessor :__buf, :__loader, :__target, :__locals
6
+
7
+ def initialize(target, locals_or_context = {})
8
+ super(target)
9
+ @__target, @__locals = target, {}
10
+ __update_context(locals_or_context)
11
+ end
12
+
13
+ def __decode_params(params)
14
+ params.to_s
15
+ end
16
+
17
+ def escape(value)
18
+ value
19
+ end
20
+
21
+ def include(template_name, locals = {})
22
+ context = self.dup.__update_with_locals(locals)
23
+ self.__buf << __loader.template(template_name).render(context)
24
+ end
25
+
26
+ def respond_to?(name)
27
+ return true if @__locals.key?(name.to_s) || @__locals.key?(name.to_sym)
28
+ super
29
+ end
30
+
31
+ def method_missing(name, *args)
32
+ return @__locals[name.to_s] if @__locals.key?(name.to_s)
33
+ return @__locals[name.to_sym] if @__locals.key?(name.to_sym)
34
+ super
35
+ rescue NameError => e
36
+ __handle_error(e)
37
+ end
38
+
39
+ def __handle_error(e)
40
+ # Default behaviour is to silently discard errors
41
+ end
42
+
43
+ def __update_context(parent)
44
+ case parent
45
+ when Hash
46
+ __update_with_locals(parent)
47
+ when Cutaneous::Context
48
+ parent.instance_variables.reject { |var| /^@__/o === var.to_s }.each do |variable|
49
+ instance_variable_set(variable, parent.instance_variable_get(variable))
50
+ end
51
+ __update_with_locals(parent.__locals) if parent.respond_to?(:__locals)
52
+ end
53
+ end
54
+
55
+ def __update_with_locals(locals)
56
+ @__locals.update(locals)
57
+ self
58
+ end
59
+
60
+ # Required by the Delegator class
61
+ def __setobj__(obj)
62
+ @__target = obj
63
+ end
64
+
65
+ # Required by the Delegator class
66
+ def __getobj__
67
+ @__target
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,66 @@
1
+
2
+ module Cutaneous
3
+ # Manages a set of Loaders that render templates
4
+ class Engine
5
+ attr_reader :roots
6
+ attr_accessor :loader_class, :default_format
7
+
8
+ def initialize(template_roots, syntax, default_format = "html")
9
+ @roots = Array(template_roots)
10
+ @syntax = syntax
11
+ @loader_class = FileLoader
12
+ @default_format = default_format
13
+ end
14
+
15
+ def render_file(path, context, format = default_format)
16
+ file_loader(format).render(path, context)
17
+ end
18
+
19
+ alias_method :render, :render_file
20
+
21
+ def render_string(template_string, context, format = default_format)
22
+ string_loader(format).render(template_string, context)
23
+ end
24
+
25
+ # Create and cache a file loader on a per-format basis
26
+ def file_loader(format)
27
+ file_loader_instance(format.to_s).tap do |loader|
28
+ loader.syntax = @syntax
29
+ end
30
+ end
31
+
32
+ # Not worth caching string templates as they are most likely to be one-off
33
+ # instances & not repeated in the lifetime of the engine.
34
+ def string_loader(format)
35
+ StringLoader.new(file_loader(format))
36
+ end
37
+
38
+ def template_exists?(root, relative_path, format)
39
+ file_loader(format).exists?(root, relative_path)
40
+ end
41
+
42
+ protected
43
+
44
+ def file_loader_instance(format)
45
+ loader_class.new(@roots, format)
46
+ end
47
+ end
48
+
49
+ # A caching version of the default Engine implementation
50
+ class CachingEngine < Engine
51
+ attr_writer :write_compiled_scripts
52
+
53
+ def initialize(template_roots, syntax, default_format = "html")
54
+ super
55
+ @loader_class = CachedFileLoader
56
+ @loaders = {}
57
+ @write_compiled_scripts = true
58
+ end
59
+
60
+ def file_loader_instance(format)
61
+ @loaders[format] ||= super.tap do |loader|
62
+ loader.write_compiled_scripts = @write_compiled_scripts if loader.respond_to?(:write_compiled_scripts=)
63
+ end
64
+ end
65
+ end
66
+ end