slim 0.7.0.beta.2 → 0.7.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.
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