sal 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []