sal 0.1.0 → 0.2.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.
data/README.md CHANGED
@@ -1,7 +1,125 @@
1
- # sal.rb
2
1
 
3
- sal.rb is a simple attribute language for Ruby.
2
+ # Warning
3
+ If you hate writing HTML, this project is not for you. Try [Slim](https://github.com/stonean/slim) or one of the many other template languages. Each has its strengths, weaknesses and place in this world.
4
+
5
+ ## About
6
+ Simple Attribute Language (sal.rb) is logic-less with valid HTML.
7
+
8
+ ## Why
9
+ With all the other options, why sal.rb?
10
+
11
+ Having spent many years in `<% %>` land with JSP and ERB templates and seeing the issues that arise with this approach, I wanted to eliminate the ability to freely code in views. My first attempt implementing this idea was [RuHL](https://github.com/stonean/ruhl).
12
+
13
+ RuHL had issues though. First and least important from a proof a concept angle was the name. RuHL rules! Bleh.
14
+
15
+ More to the point: RuHL wasn't compilable and therefore didn't fit easily into the world of Ruby templates. After a short time, the frameworks I had support for (Rails and Sinatra) progressed past RuHL's ability to hack its way into the framework.
16
+
17
+ Earlier this year I started work on [sal.js](https://github.com/stonean/sal.js) to tackle another view related issue. It just so happens that the idea behind RuHL was the way to make sal.js a reality. While working on sal.js, I refined the approach RuHL took to make it smarter.
18
+
19
+ As it happens, request were made to bring RuHL up-to-date and make it work with the latest and greatest. Also as it happens, this was turning out to be more work that I wanted to put into RuHL knowing the architecture simply wouldn't work in the long term.
20
+
21
+ After weeks of distractions and no real clue how to make a compilable RuHL , the solution presented itself and here we are.
22
+
23
+ ## Syntax
24
+ sal.rb works by parsing your HTML looking for a data-sal attribute and executing the value as a method call against the rendering scope. How sal acts depends on the type of response.
25
+
26
+ #### String
27
+ The simplest of return values is a string. This tells sal to make this value the contents of the tag.
28
+
29
+ Lets say there's a method called title:
30
+
31
+ def title
32
+ 'This is different'
33
+ end
34
+
35
+ Then you have some markup in your template like so:
36
+
37
+ <h1 data-sal="title">This will be replaced</h1>
38
+
39
+ When you render the template you'll get:
40
+
41
+ <h1>This is different</h1>
42
+
43
+ As you've no doubt noticed, the data-sal attribute is gone and the contents have been replaced with the value of the `title` method call.
44
+
45
+
46
+ #### Hash
47
+ The next return value sal knows about is a hash. With the exception of an :html key, sal simply treats each the keys as attributes to be added to the tag. The value of the html key becomes the value of the tag.
48
+
49
+ Lets say there's a method called login_link:
50
+
51
+ def login_link
52
+ if logged_in?
53
+ { :href => '/logout', :html => 'Log out' }
54
+ else
55
+ { :href => '/login', :html => 'Log in' }
56
+ end
57
+ end
58
+
59
+ Then you have some markup in your template like so:
60
+
61
+ <a data-sal="title">Login link</a>
62
+
63
+ If logged_in is false you'll get:
64
+
65
+ <a href='/login'>Log in</a>
66
+
67
+
68
+ #### Array
69
+ If the return value is an array, sal repeats the markup for each item in the array applying the contents accordingly.
70
+
71
+
72
+ Lets say there's a method called days_of_week:
73
+
74
+ def days_of_week
75
+ %w{Monday Tuesday Wednesday Thursday Friday Saturday Sunday}
76
+ end
77
+
78
+ Then you have some markup in your template like so:
79
+
80
+ <select name="day">
81
+ <option>Please choose</option>
82
+ <option data-sal='days_of_week'>Monday</option>
83
+ </select>
84
+
85
+ When you render the template you'll get:
86
+
87
+ <select name="day">
88
+ <option>Please choose</option>
89
+ <option>Monday</option>
90
+ <option>Tuesday</option>
91
+ <option>Wednesday</option>
92
+ <option>Thursday</option>
93
+ <option>Friday</option>
94
+ <option>Saturday</option>
95
+ <option>Sunday</option>
96
+ </select>
97
+
98
+ But wait, you want the value to be different...
99
+
100
+ def days_of_week
101
+ [ {:value => 0, :html => 'Monday'},
102
+ {:value => 1, :html => 'Tuesday'},
103
+ {:value => 2, :html => 'Wednesday'},
104
+ {:value => 3, :html => 'Thursday'},
105
+ {:value => 4, :html => 'Friday'},
106
+ {:value => 5, :html => 'Saturday'},
107
+ {:value => 6, :html => 'Sunday'} ]
108
+ end
109
+
110
+ Same markup as above, but when you render you'll get:
111
+
112
+ <select name="day">
113
+ <option>Please choose</option>
114
+ <option value='0'>Monday</option>
115
+ <option value='1'>Tuesday</option>
116
+ <option value='2'>Wednesday</option>
117
+ <option value='3'>Thursday</option>
118
+ <option value='4'>Friday</option>
119
+ <option value='5'>Saturday</option>
120
+ <option value='6'>Sunday</option>
121
+ </select>
122
+
123
+ That's the basics for now...more documenation to come.
4
124
 
5
- ## What?
6
125
 
7
- I haven't gotten to the docs for this project yet, but fortunately [sal.js](http://github.com/stonean/sal.js) does and sal.rb will act accordingly.
data/lib/sal.rb CHANGED
@@ -1,26 +1,11 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'temple'
4
+ require 'sal/u'
5
+ require 'sal/wrapper'
4
6
  require 'sal/parser'
5
7
  require 'sal/filter'
6
8
  require 'sal/compiler'
7
9
  require 'sal/engine'
8
10
  require 'sal/template'
9
11
  require 'sal/version'
10
-
11
- module Sal
12
- class << self
13
- def parse_for_attributes(result = nil)
14
- return unless result.kind_of?(Hash)
15
- " #{result.collect{ |k,v| "#{k}='#{v}'" unless k.to_s == 'html' }.compact.join(' ')}"
16
- end
17
-
18
- def parse_for_html(result = nil)
19
- if result.kind_of?(String)
20
- result
21
- elsif result.kind_of?(Hash)
22
- result.delete(:html)
23
- end
24
- end
25
- end
26
- end
@@ -3,33 +3,52 @@ module Sal
3
3
  # @api private
4
4
  class Compiler < Filter
5
5
 
6
- def on_sal_tag(ele, attrs, closed, content)
7
- [:html, :tag, ele, attrs, closed, compile(content)]
6
+ def on_sal_tag(tag, attrs, content)
7
+ [:html, :tag, tag, format_attrs(attrs), false, compile(content)]
8
+ end
9
+
10
+ def on_sal_text(incode, text)
11
+ if incode
12
+ [:dynamic, "Sal::U.parse_for_html(_saldict, '#{text}')"]
13
+ else
14
+ [:static, text]
15
+ end
16
+ end
17
+
18
+ def on_sal_nested(tag, attrs, content)
19
+ [:html, :tag, tag, ada(attrs, '_saldict'), false, compile(content)]
8
20
  end
9
21
 
10
22
  def on_sal_code(code, tag, attrs, content)
11
23
  tmp1, tmp2 = tmp_var(:sal), tmp_var(:sal)
12
24
 
13
25
  [:multi,
14
- [:block, "#{tmp1} = #{code}"],
15
- [:block, "if #{tmp1}.kind_of?(String)"],
16
- [:html, :tag, tag, attrs, false, [:dynamic, tmp1]],
17
- [:block, "elsif #{tmp1}.kind_of?(Array)"],
18
- [:block, "#{tmp1}.each do |#{tmp2}|"],
19
- [:html, :tag, tag, ada(attrs, tmp2), false,
20
- [:dynamic, "Sal.parse_for_html(#{tmp2})"]],
26
+ [:block, "if (#{tmp1} = _saldict['#{code}'])"],
27
+ [:block, "case (#{tmp1})"],
28
+ [:block, "when Array"],
29
+ [:block, "#{tmp2} = _saldict"],
30
+ [:block, "#{tmp1}.each do |_saldict|"],
31
+ compile([:sal, :nested, tag, attrs, content]),
32
+ [:block, 'end'],
33
+ [:block, "_saldict = #{tmp2}"],
34
+ [:block, "else"],
35
+ [:html, :tag, tag, ada(attrs, tmp1), false,
36
+ [:dynamic, "Sal::U.parse_for_html(#{tmp1})"]],
21
37
  [:block, 'end'],
22
- [:block, "else"],
23
- [:html, :tag, tag, ada(attrs, tmp1), false,
24
- [:dynamic, "Sal.parse_for_html(#{tmp1})"]],
25
- [:block, 'end'],
38
+ [:block, 'end'],
26
39
  ]
27
40
  end
28
41
 
29
42
  private
30
43
 
44
+ def format_attrs(attrs)
45
+ a = []
46
+ attrs.each{ |k,v| a << [k, [:static, v]] }
47
+ [:multi, [:html, :staticattrs] + a]
48
+ end
49
+
31
50
  def ada(attrs, tmpvar)
32
- attrs.dup << [:dynamic, "Sal.parse_for_attributes(#{tmpvar})"]
51
+ [:dynamic, "Sal::U.parse_for_attributes(#{tmpvar}, #{attrs.inspect})"]
33
52
  end
34
53
  end
35
54
  end
@@ -1,13 +1,5 @@
1
1
  module Sal
2
2
  class Filter < Temple::Filter
3
3
  temple_dispatch :sal
4
-
5
- def on_sal_code(code, tag, attrs, content)
6
- [:sal, :code, code, tag, attrs, content]
7
- end
8
-
9
- def on_sal_tag(tag, attrs, content)
10
- [:sal, :tag, tag, attrs, content]
11
- end
12
4
  end
13
5
  end
@@ -41,7 +41,8 @@ module Sal
41
41
  str.force_encoding(old_enc) unless str.valid_encoding?
42
42
  end
43
43
 
44
- result = [:multi]
44
+ result = [:multi,
45
+ [:block, "_saldict = Sal::Wrapper.new(self)"]]
45
46
 
46
47
  doc = Nokogiri::XML(str)
47
48
 
@@ -52,7 +53,7 @@ module Sal
52
53
 
53
54
  private
54
55
 
55
- def parse_nodeset(stacks, nodes)
56
+ def parse_nodeset(stacks, nodes, incode = false)
56
57
  nodes.children.each do |node|
57
58
  tag = node.name
58
59
 
@@ -61,19 +62,21 @@ module Sal
61
62
  content = [:multi]
62
63
  attrs, code = parse_attrs(node)
63
64
  if code
65
+ incode = true
64
66
  stacks.last << [:sal, :code, code, tag, attrs, content]
65
67
  else
66
- stacks.last << [:sal, :tag, tag, attrs, false, content]
68
+ incode = false
69
+ stacks.last << [:sal, :tag, tag, attrs, content]
67
70
  end
68
71
  stacks << content
69
- parse_nodeset(stacks, node)
72
+ parse_nodeset(stacks, node, incode)
70
73
  stacks.pop
71
74
  when Nokogiri::XML::Node::TEXT_NODE
72
75
  str = node.text
73
76
  if str.strip.empty?
74
77
  stacks.last << [:newline]
75
78
  else
76
- stacks.last << [:static, str]
79
+ stacks.last << [:sal, :text, incode, str]
77
80
  end
78
81
  when Nokogiri::XML::Node::DTD_NODE
79
82
  stacks.last << [:static, node.to_s]
@@ -87,17 +90,17 @@ module Sal
87
90
 
88
91
  def parse_attrs(node)
89
92
  code = nil
90
- attrs = []
93
+ attrs = {}
91
94
 
92
95
  node.attribute_nodes.each do |an|
93
96
  if an.name == 'data-sal'
94
97
  code = an.value
95
98
  else
96
- attrs << [an.name, [:static, an.value]]
99
+ attrs[an.name.to_s] = an.value
97
100
  end
98
101
  end
99
102
 
100
- return [:multi, [:html, :staticattrs] + attrs], code
103
+ return attrs, code
101
104
  end
102
105
  end
103
106
  end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module Sal
4
+ class U
5
+ class << self
6
+ def parse_for_attributes(result = nil, attrs = {})
7
+ result = result.value if result.kind_of?(Sal::Wrapper)
8
+
9
+ return unless result.kind_of?(Hash)
10
+
11
+ result = stringify_keys(result)
12
+
13
+ # concat values on class attribute
14
+ if attrs['class'] && result['class']
15
+ result['class'] = attrs['class'] + ' ' + result['class']
16
+ end
17
+
18
+ " #{result.collect{ |k,v| "#{k}='#{v}'" unless k.to_s == 'html' }.compact.join(' ')}"
19
+ end
20
+
21
+ def parse_for_html(result, text = '')
22
+ result = result.value if result.kind_of?(Sal::Wrapper)
23
+ if result.kind_of?(String)
24
+ result
25
+ elsif result.kind_of?(Hash)
26
+ result.delete(:html)
27
+ else
28
+ text unless text.strip.empty?
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def stringify_keys(result)
35
+ new = {}
36
+ result.each{ |k,v| new[k.to_s] = v }
37
+ new
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,3 +1,3 @@
1
1
  module Sal
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,56 @@
1
+ module Sal
2
+ # For logic-less mode, objects can be encased in the Wrapper class.
3
+ class Wrapper
4
+ attr_reader :value, :parent
5
+
6
+ def initialize(value, parent = nil)
7
+ @value, @parent = value, parent
8
+ end
9
+
10
+ # To find the reference, first check for standard method
11
+ # access by using respond_to?.
12
+ #
13
+ # If not found, check to see if the value is a hash and if the
14
+ # the name is a key on the hash.
15
+ #
16
+ # Not a hash, or not a key on the hash, then check to see if there
17
+ # is an instance variable with the name.
18
+ #
19
+ # If the instance variable doesn't exist and there is a parent object,
20
+ # go through the same steps on the parent object. This is useful when
21
+ # you are iterating over objects.
22
+ def [](name)
23
+ return wrap(value.send(name)) if value.respond_to?(name)
24
+ if value.respond_to?(:has_key?)
25
+ return wrap(value[name.to_sym]) if value.has_key?(name.to_sym)
26
+ return wrap(value[name.to_s]) if value.has_key?(name.to_s)
27
+ end
28
+ return wrap(value.instance_variable_get("@#{name}")) if value.instance_variable_defined?("@#{name}")
29
+ parent[name] if parent
30
+ end
31
+
32
+ # Empty objects must appear empty for inverted sections
33
+ def empty?
34
+ value.respond_to?(:empty) && value.empty?
35
+ end
36
+
37
+ # Used for output
38
+ def to_s
39
+ value.to_s
40
+ end
41
+
42
+ private
43
+
44
+ def wrap(response)
45
+ # Primitives are not wrapped
46
+ if [String, Numeric, TrueClass, FalseClass, NilClass].any? {|primitive| primitive === response }
47
+ response
48
+ # Enumerables are mapped with wrapped values (except Hash-like objects)
49
+ elsif !response.respond_to?(:has_key?) && response.respond_to?(:map)
50
+ response.map {|v| wrap(v) }
51
+ else
52
+ Wrapper.new(response, self)
53
+ end
54
+ end
55
+ end
56
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: sal
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.0
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Andrew Stone
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-19 00:00:00 -04:00
13
+ date: 2011-04-26 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -63,7 +63,9 @@ files:
63
63
  - lib/sal/filter.rb
64
64
  - lib/sal/parser.rb
65
65
  - lib/sal/template.rb
66
+ - lib/sal/u.rb
66
67
  - lib/sal/version.rb
68
+ - lib/sal/wrapper.rb
67
69
  has_rdoc: true
68
70
  homepage: http://github.com/stonean/sal.rb
69
71
  licenses: []