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 +122 -4
- data/lib/sal.rb +2 -17
- data/lib/sal/compiler.rb +33 -14
- data/lib/sal/filter.rb +0 -8
- data/lib/sal/parser.rb +11 -8
- data/lib/sal/u.rb +41 -0
- data/lib/sal/version.rb +1 -1
- data/lib/sal/wrapper.rb +56 -0
- metadata +4 -2
data/README.md
CHANGED
@@ -1,7 +1,125 @@
|
|
1
|
-
# sal.rb
|
2
1
|
|
3
|
-
|
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
|
data/lib/sal/compiler.rb
CHANGED
@@ -3,33 +3,52 @@ module Sal
|
|
3
3
|
# @api private
|
4
4
|
class Compiler < Filter
|
5
5
|
|
6
|
-
def on_sal_tag(
|
7
|
-
[:html, :tag,
|
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,
|
15
|
-
[:block,
|
16
|
-
|
17
|
-
[:block,
|
18
|
-
[:block,
|
19
|
-
|
20
|
-
|
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,
|
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
|
-
|
51
|
+
[:dynamic, "Sal::U.parse_for_attributes(#{tmpvar}, #{attrs.inspect})"]
|
33
52
|
end
|
34
53
|
end
|
35
54
|
end
|
data/lib/sal/filter.rb
CHANGED
@@ -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
|
data/lib/sal/parser.rb
CHANGED
@@ -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
|
-
|
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 << [:
|
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
|
99
|
+
attrs[an.name.to_s] = an.value
|
97
100
|
end
|
98
101
|
end
|
99
102
|
|
100
|
-
return
|
103
|
+
return attrs, code
|
101
104
|
end
|
102
105
|
end
|
103
106
|
end
|
data/lib/sal/u.rb
ADDED
@@ -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
|
data/lib/sal/version.rb
CHANGED
data/lib/sal/wrapper.rb
ADDED
@@ -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.
|
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-
|
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: []
|