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 +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: []
|