slim 0.7.0.beta.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,41 @@
1
+ ! doctype html
2
+ html
3
+ head
4
+ title Slim Test
5
+ meta name="keywords" content="slim, syntax"
6
+
7
+ javascript:
8
+ $(function() {
9
+ alert('Hello World');
10
+ });
11
+
12
+ body
13
+ / comment block
14
+ with multiple lines
15
+
16
+ h1 = @page_title
17
+ p#notice.message
18
+ | Welcome to the the syntax test.
19
+ This file is to excercise the various markup.
20
+
21
+ - unless @users.empty?
22
+ table
23
+ - for user in users do
24
+ tr
25
+ td.user id=user.name = user.name
26
+ - else
27
+ p There are no users.
28
+
29
+ #content Hello #{@user.name}! Welcome to the test page!
30
+ Try out Slim!
31
+
32
+ = function_with_many_parameters(:a, \
33
+ variable, :option => 1)
34
+
35
+ p.text
36
+ ' Another text block
37
+ with multiple lines
38
+
39
+ .text#footer
40
+ ` Footer text block
41
+ with multiple lines
@@ -11,6 +11,7 @@ require 'slim/embedded_engine'
11
11
  require 'slim/compiler'
12
12
  require 'slim/engine'
13
13
  require 'slim/template'
14
+ require 'slim/version'
14
15
 
15
16
  begin
16
17
  require 'escape_utils'
@@ -19,6 +20,6 @@ end
19
20
 
20
21
  module Slim
21
22
  def self.version
22
- Slim::VERSION
23
+ VERSION
23
24
  end
24
25
  end
@@ -0,0 +1,80 @@
1
+ require 'slim'
2
+ require 'optparse'
3
+
4
+ module Slim
5
+ # Slim commandline interface
6
+ # @api private
7
+ class Command
8
+ def initialize(args)
9
+ @args = args
10
+ @options = {}
11
+ end
12
+
13
+ # Run command
14
+ def run
15
+ @opts = OptionParser.new(&method(:set_opts))
16
+ @opts.parse!(@args)
17
+ process
18
+ exit 0
19
+ rescue Exception => ex
20
+ raise ex if @options[:trace] || SystemExit === ex
21
+ $stderr.print "#{ex.class}: " if ex.class != RuntimeError
22
+ $stderr.puts ex.message
23
+ $stderr.puts ' Use --trace for backtrace.'
24
+ exit 1
25
+ end
26
+
27
+ private
28
+
29
+ # Configure OptionParser
30
+ def set_opts(opts)
31
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
32
+ @options[:input] = $stdin
33
+ end
34
+
35
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
36
+ @options[:trace] = true
37
+ end
38
+
39
+ opts.on('-c', '--compile', :NONE, 'Compile only but do not run') do
40
+ @options[:compile] = true
41
+ end
42
+
43
+ opts.on_tail('-h', '--help', 'Show this message') do
44
+ puts opts
45
+ exit
46
+ end
47
+
48
+ opts.on_tail('-v', '--version', 'Print version') do
49
+ puts "Slim #{Slim.version}"
50
+ exit
51
+ end
52
+ end
53
+
54
+ # Process command
55
+ def process
56
+ args = @args.dup
57
+ unless @options[:input]
58
+ file = args.shift
59
+ if file
60
+ @options[:file] = file
61
+ @options[:input] = File.open(file, 'r')
62
+ else
63
+ @options[:file] = 'STDIN'
64
+ @options[:input] = $stdin
65
+ end
66
+ end
67
+
68
+ unless @options[:output]
69
+ file = args.shift
70
+ @options[:output] = file ? File.open(file, 'w') : $stdout
71
+ end
72
+
73
+ if @options[:compile]
74
+ @options[:output].puts(Slim::Engine.new(:file => @options[:file]).compile(@options[:input].read))
75
+ else
76
+ @options[:output].puts(Slim::Template.new(@options[:file]) { @options[:input].read }.render)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,33 +1,64 @@
1
1
  module Slim
2
2
  # Compiles Slim expressions into Temple::HTML expressions.
3
+ # @api private
3
4
  class Compiler < Filter
5
+ # Handle text expression `[:slim, :text, string]`
6
+ #
7
+ # @param [String] string Static text
8
+ # @return [Array] Compiled temple expression
4
9
  def on_text(string)
5
- if string.include?('#{')
6
- [:dynamic, escape_interpolation(string)]
7
- else
8
- [:static, string]
10
+ # Interpolate variables in text (#{variable}).
11
+ # Split the text into multiple dynamic and static parts.
12
+ block = [:multi]
13
+ until string.empty?
14
+ case string
15
+ when /^\\(\#\{[^\}]*\})/
16
+ # Escaped interpolation
17
+ block << [:static, $1]
18
+ when /^\#\{([^\}]*)\}/
19
+ # Interpolation
20
+ block << [:dynamic, escape_code($1)]
21
+ when /^([^\#]+|\#)/
22
+ # Static text
23
+ block << [:static, $&]
24
+ end
25
+ string = $'
9
26
  end
27
+ block
10
28
  end
11
29
 
30
+ # Handle control expression `[:slim, :control, code, content]`
31
+ #
32
+ # @param [String] ruby code
33
+ # @param [Array] content Temple expression
34
+ # @return [Array] Compiled temple expression
12
35
  def on_control(code, content)
13
36
  [:multi,
14
37
  [:block, code],
15
38
  compile(content)]
16
39
  end
17
40
 
18
- def on_embedded(engine, *body)
19
- EmbeddedEngine[engine].compile(body)
20
- end
21
-
22
- # why is escaping not handled by temple?
41
+ # Handle output expression `[:slim, :output, escape, code, content]`
42
+ #
43
+ # @param [Boolean] escape Escape html
44
+ # @param [String] code Ruby code
45
+ # @param [Array] content Temple expression
46
+ # @return [Array] Compiled temple expression
23
47
  def on_output(escape, code, content)
24
48
  if empty_exp?(content)
25
- [:dynamic, escape ? escape_code(code) : code]
49
+ [:multi, [:dynamic, escape ? escape_code(code) : code], content]
26
50
  else
27
51
  on_output_block(escape, code, content)
28
52
  end
29
53
  end
30
54
 
55
+ # Handle output expression `[:slim, :output, escape, code, content]`
56
+ # if content is not empty.
57
+ #
58
+ # @param [Boolean] escape Escape html
59
+ # @param [String] code Ruby code
60
+ # @param [Array] content Temple expression
61
+ # @return [Array] Compiled temple expression
31
62
  def on_output_block(escape, code, content)
32
63
  tmp1, tmp2 = tmp_var, tmp_var
33
64
 
@@ -47,28 +78,36 @@ module Slim
47
78
  [:block, tmp2],
48
79
 
49
80
  # Close the block.
50
- [:block, "end"],
81
+ [:block, 'end'],
51
82
 
52
83
  # Output the content.
53
84
  on_output(escape, tmp1, [:multi])]
54
85
  end
55
86
 
87
+ # Handle directive expression `[:slim, :directive, type]`
88
+ #
89
+ # @param [String] type Directive type
90
+ # @return [Array] Compiled temple expression
56
91
  def on_directive(type)
57
- case type
58
- when /^doctype/
92
+ if type =~ /^doctype/
59
93
  [:html, :doctype, $'.strip]
60
- else
61
94
  end
62
95
  end
63
96
 
97
+ # Handle tag expression `[:slim, :tag, name, attrs, content]`
98
+ #
99
+ # @param [String] name Tag name
100
+ # @param [Array] attrs Attributes
101
+ # @param [Array] content Temple expression
102
+ # @return [Array] Compiled temple expression
64
103
  def on_tag(name, attrs, content)
65
- attrs = attrs.inject([:html, :attrs]) do |m, (key, value)|
66
- if value.include?('#{')
67
- value = [:dynamic, escape_interpolation(value)]
68
- else
69
- value = [:static, value]
70
- end
71
- m << [:html, :basicattr, [:static, key], value]
104
+ attrs = attrs.inject([:html, :attrs]) do |m, (key, dynamic, value)|
105
+ value = if dynamic
106
+ [:dynamic, escape_code(value)]
107
+ else
108
+ on_text(value)
109
+ end
110
+ m << [:html, :basicattr, [:static, key.to_s], value]
72
111
  end
73
112
 
74
113
  [:html, :tag, name, attrs, compile(content)]
@@ -76,17 +115,17 @@ module Slim
76
115
 
77
116
  private
78
117
 
79
- def escape_interpolation(string)
80
- string.gsub!(/(.?)\#\{(.*?)\}/) do
81
- $1 == '\\' ? $& : "#{$1}#\{#{escape_code($2)}}"
82
- end
83
- '"%s"' % string
84
- end
85
-
118
+ # Generate code to escape html
119
+ #
120
+ # @param [String] param Ruby code
121
+ # @return [String] Ruby code
86
122
  def escape_code(param)
87
123
  "Slim::Helpers.escape_html#{@options[:use_html_safe] ? '_safe' : ''}((#{param}))"
88
124
  end
89
125
 
126
+ # Generate unique temporary variable name
127
+ #
128
+ # @return [String] Variable name
90
129
  def tmp_var
91
130
  @tmp_var ||= 0
92
131
  "_slimtmp#{@tmp_var += 1}"
@@ -1,11 +1,11 @@
1
1
  module Slim
2
- class EmbeddedEngine
2
+ # Temple filter which processes embedded engines
3
+ # @api private
4
+ class EmbeddedEngine < Filter
3
5
  @engines = {}
4
6
 
5
- attr_reader :options
6
-
7
- def initialize(options)
8
- @options = options
7
+ def self.register(name, klass, options = {})
8
+ @engines[name.to_s] = klass.new(options)
9
9
  end
10
10
 
11
11
  def self.[](name)
@@ -14,9 +14,8 @@ module Slim
14
14
  engine.dup
15
15
  end
16
16
 
17
- def self.register(name, klass, options = {})
18
- options.merge!(:name => name.to_s)
19
- @engines[name.to_s] = klass.new(options)
17
+ def on_embedded(engine, *body)
18
+ EmbeddedEngine[engine].on_embedded(engine, *body)
20
19
  end
21
20
 
22
21
  def collect_text(body)
@@ -34,20 +33,20 @@ module Slim
34
33
  # Code to collect local variables
35
34
  COLLECT_LOCALS = %q{eval('{' + local_variables.select {|v| v[0] != ?_ }.map {|v| ":#{v}=>#{v}" }.join(',') + '}')}
36
35
 
37
- def compile(body)
36
+ def on_embedded(engine, *body)
38
37
  text = collect_text(body)
39
- engine = Tilt[options[:name]]
40
- if options[:precompiled]
38
+ engine = Tilt[engine]
39
+ if @options[:precompiled]
41
40
  # Wrap precompiled code in proc, local variables from out the proc are accessible
42
41
  # WARNING: This is a bit of a hack. Tilt::Engine#precompiled is protected
43
42
  precompiled = engine.new { text }.send(:precompiled, {}).first
44
43
  [:dynamic, "proc { #{precompiled} }.call"]
45
- elsif options[:dynamic]
44
+ elsif @options[:dynamic]
46
45
  # Fully dynamic evaluation of the template during runtime (Slow and uncached)
47
46
  [:dynamic, "#{engine.name}.new { #{text.inspect} }.render(self, #{COLLECT_LOCALS})"]
48
- elsif options[:interpolate]
47
+ elsif @options[:interpolate]
49
48
  # Static template with interpolated ruby code
50
- [:dynamic, '"%s"' % engine.new { text }.render]
49
+ [:slim, :text, engine.new { text }.render]
51
50
  else
52
51
  # Static template
53
52
  [:static, engine.new { text }.render]
@@ -56,23 +55,14 @@ module Slim
56
55
  end
57
56
 
58
57
  class TagEngine < EmbeddedEngine
59
- def compile_text(body)
60
- body.inject([:multi]) do |block, exp|
61
- block << (exp[0] == :slim && exp[1] == :text ? [:static, exp[2]] : exp)
62
- end
63
- end
64
-
65
- def compile(body)
66
- attrs = [:html, :attrs]
67
- options[:attributes].each do |key, value|
68
- attrs << [:html, :basicattr, [:static, key.to_s], [:static, value.to_s]]
69
- end
70
- [:html, :tag, options[:tag], attrs, compile_text(body)]
58
+ def on_embedded(engine, *body)
59
+ content = @options[:engine] ? @options[:engine].new.on_embedded(engine, *body) : [:multi, *body]
60
+ [:slim, :tag, @options[:tag], @options[:attributes].map {|k, v| [k, false, v ] }, content]
71
61
  end
72
62
  end
73
63
 
74
64
  class RubyEngine < EmbeddedEngine
75
- def compile(body)
65
+ def on_embedded(engine, *body)
76
66
  [:block, collect_text(body)]
77
67
  end
78
68
  end
@@ -83,9 +73,10 @@ module Slim
83
73
  register :rdoc, TiltEngine, :interpolate => true
84
74
 
85
75
  # These engines are executed at compile time
86
- register :sass, TiltEngine
87
- register :less, TiltEngine
88
- register :coffee, TiltEngine
76
+ register :coffee, TagEngine, :tag => 'script', :attributes => { :type => 'text/javascript' }, :engine => TiltEngine
77
+ register :sass, TagEngine, :tag => 'style', :attributes => { :type => 'text/css' }, :engine => TiltEngine
78
+ register :scss, TagEngine, :tag => 'style', :attributes => { :type => 'text/css' }, :engine => TiltEngine
79
+ register :less, TagEngine, :tag => 'style', :attributes => { :type => 'text/css' }, :engine => TiltEngine
89
80
 
90
81
  # These engines are precompiled, code is embedded
91
82
  register :erb, TiltEngine, :precompiled => true
@@ -7,10 +7,13 @@ module Slim
7
7
  # However, the parser is not smart enough (and that's a good thing) to
8
8
  # automatically insert end's where they are needed. Luckily, this filter
9
9
  # does *exactly* that (and it does it well!)
10
+ #
11
+ # @api private
10
12
  class EndInserter < Filter
11
13
  ELSE_REGEX = /^(else|elsif|when|end)\b/
12
14
  END_REGEX = /^end\b/
13
15
 
16
+ # Handle multi expression `[:multi, *exps]`
14
17
  def on_multi(*exps)
15
18
  result = [:multi]
16
19
  # This variable is true if the previous line was
@@ -1,7 +1,10 @@
1
1
  module Slim
2
+ # Slim engine which transforms slim code to executable ruby code
3
+ # @api public
2
4
  class Engine < Temple::Engine
3
- use Slim::Parser
5
+ use Slim::Parser, :file
4
6
  use Slim::EndInserter
7
+ use Slim::EmbeddedEngine
5
8
  use Slim::Compiler, :use_html_safe
6
9
  #use Slim::Debugger
7
10
  use Temple::HTML::Fast, :format, :attr_wrapper => '"', :format => :html5
@@ -9,13 +12,5 @@ module Slim
9
12
  filter :StaticMerger
10
13
  filter :DynamicInliner
11
14
  generator :ArrayBuffer
12
-
13
- def self.new(*args)
14
- if args.first.respond_to?(:each_line)
15
- Template.new(Hash === args.last ? args.last : {}) { args.first }
16
- else
17
- super
18
- end
19
- end
20
15
  end
21
16
  end
@@ -1,4 +1,6 @@
1
1
  module Slim
2
+ # Base class for Temple filters used in Slim
3
+ # @api private
2
4
  class Filter
3
5
  include Temple::Utils
4
6
 
@@ -35,6 +37,8 @@ module Slim
35
37
  end
36
38
  end
37
39
 
40
+ # Simple filter which prints Temple expression
41
+ # @api private
38
42
  class Debugger < Filter
39
43
  def compile(exp)
40
44
  puts exp.inspect
@@ -1,32 +1,76 @@
1
1
  module Slim
2
+ # Slim helper functions
3
+ #
4
+ # @api public
2
5
  module Helpers
6
+ # Iterate over `Enumerable` object
7
+ # yielding each element to a Slim block
8
+ # and putting the result into `<li>` elements.
9
+ # For example:
10
+ #
11
+ # = list_of([1,2]) do |i|
12
+ # = i
13
+ #
14
+ # Produces:
15
+ #
16
+ # <li>1</li>
17
+ # <li>2</li>
18
+ #
19
+ # @param enum [Enumerable] The enumerable objects to iterate over
20
+ # @yield [item] A block which contains Slim code that goes within list items
21
+ # @yieldparam item An element of `enum`
22
+ # @api public
3
23
  def list_of(enum, &block)
4
- enum.map do |i|
24
+ list = enum.map do |i|
5
25
  "<li>#{yield(i)}</li>"
6
26
  end.join("\n")
27
+ list.respond_to?(:html_safe) ? list.html_safe : list
7
28
  end
8
29
 
30
+ # Returns an escaped copy of `html`.
31
+ # Strings which are declared as html_safe are not escaped.
32
+ #
33
+ # @param html [String] The string to escape
34
+ # @return [String] The escaped string
35
+ # @api public
9
36
  def escape_html_safe(html)
10
37
  html.html_safe? ? html : escape_html(html)
11
38
  end
12
39
 
13
40
  if defined?(EscapeUtils)
41
+ # Returns an escaped copy of `html`.
42
+ #
43
+ # @param html [String] The string to escape
44
+ # @return [String] The escaped string
45
+ # @api public
14
46
  def escape_html(html)
15
47
  EscapeUtils.escape_html(html.to_s)
16
48
  end
17
49
  elsif RUBY_VERSION > '1.9'
50
+ # Used by escape_html
51
+ # @api private
18
52
  ESCAPE_HTML = {
19
53
  '&' => '&amp;',
20
54
  '"' => '&quot;',
21
55
  '<' => '&lt;',
22
56
  '>' => '&gt;',
23
57
  '/' => '&#47;',
24
- }
58
+ }.freeze
25
59
 
60
+ # Returns an escaped copy of `html`.
61
+ #
62
+ # @param html [String] The string to escape
63
+ # @return [String] The escaped string
64
+ # @api public
26
65
  def escape_html(html)
27
66
  html.to_s.gsub(/[&\"<>\/]/, ESCAPE_HTML)
28
67
  end
29
68
  else
69
+ # Returns an escaped copy of `html`.
70
+ #
71
+ # @param html [String] The string to escape
72
+ # @return [String] The escaped string
73
+ # @api public
30
74
  def escape_html(html)
31
75
  html.to_s.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;').gsub(/\//, '&#47;')
32
76
  end