nm 0.5.4 → 0.6.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.
- checksums.yaml +7 -7
- data/.gitignore +1 -0
- data/.l.yml +9 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/.t.yml +6 -0
- data/Gemfile +6 -2
- data/README.md +53 -51
- data/lib/nm.rb +12 -2
- data/lib/nm/context.rb +55 -0
- data/lib/nm/ext.rb +11 -18
- data/lib/nm/render.rb +12 -0
- data/lib/nm/source.rb +60 -47
- data/lib/nm/template_behaviors.rb +96 -0
- data/lib/nm/version.rb +3 -1
- data/nm.gemspec +12 -6
- data/test/helper.rb +6 -13
- data/test/support/factory.rb +3 -2
- data/test/support/templates/_list.nm +2 -0
- data/test/support/templates/_locals.nm +4 -2
- data/test/support/templates/_obj.nm +5 -3
- data/test/support/templates/aliases.nm +8 -3
- data/test/support/templates/list.nm +2 -0
- data/test/support/templates/locals.nm +4 -2
- data/test/support/templates/locals_alt.data.inem +2 -2
- data/test/support/templates/obj.nm +6 -4
- data/test/system/.keep +0 -0
- data/test/unit/context_tests.rb +51 -0
- data/test/unit/ext_tests.rb +27 -42
- data/test/unit/nm_tests.rb +20 -0
- data/test/unit/render_tests.rb +28 -0
- data/test/unit/source_tests.rb +89 -106
- data/test/unit/template_behaviors_tests.rb +259 -0
- metadata +82 -43
- data/lib/nm/template.rb +0 -103
- data/test/unit/template_tests.rb +0 -357
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dc0aecffbcfc3c14162b53a334c10172f41946f3e3d9a524e98b223dcb5a64bb
|
4
|
+
data.tar.gz: 25559fce124e9cfb71d46e9eda6d79ab39585f94fff0bdfee7b591df540616f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 862701c2457f04f1479c124a0ba2d1d5c464b819baf035ba0779eb21f8253e2b0c133b5700268a20416b26a7e3629fc08b2187887f23f55c2b8cca78188b1c5d
|
7
|
+
data.tar.gz: acadad3b3ee918cf36e02975718cb0d7770d5b9f7b190451172c009ca90ad0b276efe970a697ecc3190d6f89bc1c91bc62a08049a09f6c40aaa59b3e36e248ae
|
data/.gitignore
CHANGED
data/.l.yml
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.3
|
data/.t.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Nm
|
2
2
|
|
3
|
-
|
3
|
+
Node-Map: a data templating DSL. Named for its two main markup methods: "node" and "map". Designed to template data objects for JSON/BSON/whatever/etc serialization.
|
4
4
|
|
5
5
|
## Usage
|
6
6
|
|
@@ -9,15 +9,15 @@ Template:
|
|
9
9
|
```ruby
|
10
10
|
# in /path/to/views/slideshow.json.nm
|
11
11
|
|
12
|
-
node
|
13
|
-
node
|
14
|
-
node
|
12
|
+
node "slideshow" do
|
13
|
+
node "start_slide", start_slide
|
14
|
+
node "slides" do
|
15
15
|
map slides do |slide|
|
16
|
-
node
|
17
|
-
node
|
18
|
-
node
|
19
|
-
node
|
20
|
-
node
|
16
|
+
node "id", slide.id
|
17
|
+
node "title", slide.title
|
18
|
+
node "image", slide.image_url
|
19
|
+
node "thumb", slide.thumb_url
|
20
|
+
node "url", slide.url
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -26,12 +26,15 @@ end
|
|
26
26
|
Render:
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
require
|
30
|
-
source = Nm::Source.new(
|
31
|
-
source.render(
|
32
|
-
|
33
|
-
:
|
34
|
-
|
29
|
+
require "nm"
|
30
|
+
source = Nm::Source.new("/path/to/views")
|
31
|
+
source.render(
|
32
|
+
"slideshow.json",
|
33
|
+
locals: {
|
34
|
+
start_slide: 1,
|
35
|
+
slides: [ ... ] #=> list of slide objects 1, 2 and 3
|
36
|
+
}
|
37
|
+
)
|
35
38
|
```
|
36
39
|
|
37
40
|
Output:
|
@@ -67,10 +70,10 @@ Output:
|
|
67
70
|
|
68
71
|
### Cache Templates
|
69
72
|
|
70
|
-
By default the source doesn't cache template files.
|
73
|
+
By default the source doesn't cache template files. You can configure it to cache templates using the `:cache` option:
|
71
74
|
|
72
75
|
```ruby
|
73
|
-
source = Nm::Source.new(
|
76
|
+
source = Nm::Source.new("/path/to/views", cache: true)
|
74
77
|
```
|
75
78
|
|
76
79
|
### Default locals
|
@@ -78,14 +81,13 @@ source = Nm::Source.new('/path/to/views', :cache => true)
|
|
78
81
|
You can specify a set of default locals to use on all renders for a source using the `:locals` option:
|
79
82
|
|
80
83
|
```ruby
|
81
|
-
source =
|
82
|
-
|
83
|
-
})
|
84
|
+
source =
|
85
|
+
Nm::Source.new("/path/to/views", locals: { "something" => "value" })
|
84
86
|
```
|
85
87
|
|
86
88
|
### Render Format
|
87
89
|
|
88
|
-
Rendering templates returns a data object (`::Hash` or `::Array`).
|
90
|
+
Rendering templates returns a data object (`::Hash` or `::Array`). To serialize, bring in your favorite JSON/BSON/whatever serializer and pass the rendered object to it.
|
89
91
|
|
90
92
|
### Markup Methods
|
91
93
|
|
@@ -96,18 +98,18 @@ There are two main markup methods:
|
|
96
98
|
|
97
99
|
### Default render value
|
98
100
|
|
99
|
-
Nm templates render an empty object (ie `::Hash.new`) if no source is given or no markup methods are called in the template source.
|
101
|
+
Nm templates render an empty object (ie `::Hash.new`) if no source is given or no markup methods are called in the template source. The idea is that the templates should always return *something* and avoid `nil` values as much as possible.
|
100
102
|
|
101
103
|
This is also more consistent with rendering mapped lists vs reduced objects. Say your are mapping a list of objects in your template (using the `map` markup method):
|
102
104
|
|
103
105
|
```ruby
|
104
106
|
map incoming_list do |item|
|
105
|
-
node
|
106
|
-
node
|
107
|
+
node "name", item.name
|
108
|
+
node "value", item.value
|
107
109
|
end
|
108
110
|
```
|
109
111
|
|
110
|
-
If there are no items in the incoming list, the template render produces an empty list.
|
112
|
+
If there are no items in the incoming list, the template render produces an empty list. Now say you are reducing an incoming list to a single object:
|
111
113
|
|
112
114
|
```ruby
|
113
115
|
incoming_list.each do |item|
|
@@ -126,47 +128,47 @@ If there are no items in the incoming list, no markup methods are called, but th
|
|
126
128
|
```ruby
|
127
129
|
# in /path/to/views/slideshow.json.nm
|
128
130
|
|
129
|
-
node
|
130
|
-
node
|
131
|
-
node
|
131
|
+
node "slideshow" do
|
132
|
+
node "start_slide", start_slide
|
133
|
+
node "slides" do
|
132
134
|
map slides do |slide|
|
133
|
-
partial
|
135
|
+
partial "_slide.json", slide: slide
|
134
136
|
end
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
138
140
|
# in /path/to/views/_slide.json.nm
|
139
141
|
|
140
|
-
node
|
141
|
-
node
|
142
|
-
node
|
143
|
-
node
|
144
|
-
node
|
142
|
+
node "id", slide.id
|
143
|
+
node "title", slide.title
|
144
|
+
node "image", slide.image_url
|
145
|
+
node "thumb", slide.thumb_url
|
146
|
+
node "url", slide.url
|
145
147
|
```
|
146
148
|
|
147
149
|
This will render the same output as above.
|
148
150
|
|
149
151
|
### Markup Aliases
|
150
152
|
|
151
|
-
If you find you need to use a local named `node` or `map`, the markup methods are aliased as `n`, `_node`, `m`, and `_map` respectively.
|
153
|
+
If you find you need to use a local named `node` or `map`, the markup methods are aliased as `n`, `_node`, `m`, and `_map` respectively. Any combination of aliases is valid:
|
152
154
|
|
153
155
|
```ruby
|
154
|
-
node
|
155
|
-
n
|
156
|
-
_node
|
156
|
+
node "slideshow" do
|
157
|
+
n "start_slide", start_slide
|
158
|
+
_node "slides" do
|
157
159
|
_map slides do |slide|
|
158
|
-
_node
|
159
|
-
node
|
160
|
-
_node
|
161
|
-
node
|
162
|
-
_node
|
160
|
+
_node "id", slide.id
|
161
|
+
node "title", slide.title
|
162
|
+
_node "image", slide.image_url
|
163
|
+
node "thumb", slide.thumb_url
|
164
|
+
_node "url", slide.url
|
163
165
|
end
|
164
166
|
m other_slides do |slide|
|
165
|
-
node
|
166
|
-
_node
|
167
|
-
node
|
168
|
-
_node
|
169
|
-
node
|
167
|
+
node "id", slide.id
|
168
|
+
_node "title", slide.title
|
169
|
+
node "image", slide.image_url
|
170
|
+
_node "thumb", slide.thumb_url
|
171
|
+
node "url", slide.url
|
170
172
|
end
|
171
173
|
end
|
172
174
|
end
|
@@ -174,9 +176,9 @@ end
|
|
174
176
|
|
175
177
|
## Installation
|
176
178
|
|
177
|
-
Add this line to your application
|
179
|
+
Add this line to your application"s Gemfile:
|
178
180
|
|
179
|
-
gem
|
181
|
+
gem "nm"
|
180
182
|
|
181
183
|
And then execute:
|
182
184
|
|
@@ -190,6 +192,6 @@ Or install it yourself as:
|
|
190
192
|
|
191
193
|
1. Fork it
|
192
194
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
193
|
-
3. Commit your changes (`git commit -am
|
195
|
+
3. Commit your changes (`git commit -am "Added some feature"`)
|
194
196
|
4. Push to the branch (`git push origin my-new-feature`)
|
195
197
|
5. Create new Pull Request
|
data/lib/nm.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "nm/version"
|
2
|
-
require
|
4
|
+
require "nm/source"
|
5
|
+
|
6
|
+
module Nm
|
7
|
+
def self.default_context_class
|
8
|
+
@default_context_class ||= Class.new
|
9
|
+
end
|
3
10
|
|
4
|
-
|
11
|
+
def self.default_context
|
12
|
+
default_context_class.new
|
13
|
+
end
|
14
|
+
end
|
data/lib/nm/context.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nm/template_behaviors"
|
4
|
+
|
5
|
+
module Nm; end
|
6
|
+
|
7
|
+
class Nm::Context
|
8
|
+
def initialize(context, source:, locals:)
|
9
|
+
@context = context
|
10
|
+
@source = source
|
11
|
+
|
12
|
+
# apply template behaviors to the meta-context
|
13
|
+
metacontext = class << context; self; end
|
14
|
+
metacontext.class_eval do
|
15
|
+
include Nm::TemplateBehaviors
|
16
|
+
|
17
|
+
locals.each do |key, value|
|
18
|
+
define_method(key){ value }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@context.__nm_context__ = self
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(template_name, locals = {})
|
25
|
+
source_file_path = @source.file_path!(template_name)
|
26
|
+
render_content(
|
27
|
+
@source.data(source_file_path),
|
28
|
+
locals: locals,
|
29
|
+
file_path: source_file_path,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :partial, :render
|
34
|
+
|
35
|
+
def render_content(content, locals: {}, file_path: nil)
|
36
|
+
@context.__nm_push_render__(locals.to_h)
|
37
|
+
@context.instance_eval(
|
38
|
+
"#{locals_code_for(locals)};#{content}",
|
39
|
+
file_path,
|
40
|
+
1,
|
41
|
+
)
|
42
|
+
@context.__nm_data__.tap{ |_data| @context.__nm_pop_render__ }
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def locals_code_for(locals)
|
48
|
+
# Assign for the same variable is to suppress unused variable warning.
|
49
|
+
locals.reduce(+"") do |code, (key, _value)|
|
50
|
+
code <<
|
51
|
+
"#{key} = @__nm_locals__[:#{key}] || @__nm_locals__[\"#{key}\"]; "\
|
52
|
+
"#{key} = #{key};"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/nm/ext.rb
CHANGED
@@ -1,31 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class InvalidError < RuntimeError; end
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
3
|
+
module Nm
|
4
|
+
class InvalidError < RuntimeError
|
5
|
+
end
|
5
6
|
end
|
6
7
|
|
7
8
|
class ::Hash
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
raise Nm::InvalidError, "invalid `#{call_name}` call"
|
12
|
-
end
|
13
|
-
self.merge(data || {})
|
9
|
+
def __nm_add_partial_data__(data)
|
10
|
+
raise Nm::InvalidError, "invalid partial call" if data.is_a?(::Array)
|
11
|
+
merge(data || {})
|
14
12
|
end
|
15
|
-
|
16
13
|
end
|
17
14
|
|
18
15
|
class ::Array
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
raise Nm::InvalidError, "invalid `#{call_name}` call"
|
23
|
-
end
|
24
|
-
self.concat(data || [])
|
16
|
+
def __nm_add_partial_data__(data)
|
17
|
+
raise Nm::InvalidError, "invalid partial call" if data.is_a?(::Hash)
|
18
|
+
concat(data || [])
|
25
19
|
end
|
26
|
-
|
27
20
|
end
|
28
21
|
|
29
|
-
def nil.
|
22
|
+
def nil.__nm_add_partial_data__(data)
|
30
23
|
data
|
31
24
|
end
|
data/lib/nm/render.rb
ADDED
data/lib/nm/source.rb
CHANGED
@@ -1,70 +1,83 @@
|
|
1
|
-
|
2
|
-
require 'nm/template'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require "pathname"
|
4
|
+
require "nm/context"
|
5
5
|
|
6
|
-
|
6
|
+
module Nm; end
|
7
7
|
|
8
|
-
|
8
|
+
class Nm::Source
|
9
|
+
attr_reader :root, :extension, :cache, :locals
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@template_class = Class.new(Template) do
|
17
|
-
(opts[:locals] || {}).each{ |key, value| define_method(key){ value } }
|
18
|
-
end
|
19
|
-
end
|
11
|
+
def initialize(root, extension: nil, cache: false, locals: {})
|
12
|
+
@root = Pathname.new(root.to_s)
|
13
|
+
@extension = extension ? ".#{extension}" : nil
|
14
|
+
@cache = cache ? {} : NullCache.new
|
15
|
+
@locals = locals
|
16
|
+
end
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
def inspect
|
19
|
+
"#<#{self.class}:#{"0x0%x" % (object_id << 1)} @root=#{@root.inspect}>"
|
20
|
+
end
|
24
21
|
|
25
|
-
|
26
|
-
|
22
|
+
def data(file_path)
|
23
|
+
@cache[file_path] ||=
|
24
|
+
begin
|
27
25
|
File.send(File.respond_to?(:binread) ? :binread : :read, file_path)
|
28
26
|
end
|
29
|
-
|
27
|
+
end
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
def render(template_name, context: Nm.default_context, locals: {})
|
30
|
+
Nm::Context
|
31
|
+
.new(context, source: self, locals: @locals)
|
32
|
+
.render(template_name, locals)
|
33
|
+
end
|
34
|
+
|
35
|
+
def file_path!(template_name)
|
36
|
+
if (path = file_path(template_name)).nil?
|
37
|
+
message = "a template file named #{template_name.inspect}"
|
38
|
+
message += " that ends in #{@extension.inspect}" unless @extension.nil?
|
39
|
+
message += " does not exist"
|
40
|
+
raise ArgumentError, message
|
40
41
|
end
|
42
|
+
path
|
43
|
+
end
|
41
44
|
|
42
|
-
|
45
|
+
def ==(other)
|
46
|
+
return super unless other.is_a?(self.class)
|
43
47
|
|
44
|
-
|
48
|
+
root == other.root &&
|
49
|
+
extension == other.extension &&
|
50
|
+
cache == other.cache &&
|
51
|
+
locals == other.locals
|
52
|
+
end
|
45
53
|
|
46
|
-
|
47
|
-
Dir.glob(self.root.join(source_file_glob_string(name))).first
|
48
|
-
end
|
54
|
+
private
|
49
55
|
|
50
|
-
|
51
|
-
|
52
|
-
|
56
|
+
def file_path(template_name)
|
57
|
+
Dir.glob(root.join(file_glob_string(template_name))).first
|
58
|
+
end
|
53
59
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
def file_glob_string(template_name)
|
61
|
+
if !@extension.nil? && template_name.end_with?(@extension)
|
62
|
+
template_name
|
63
|
+
else
|
64
|
+
"#{template_name}*#{@extension}"
|
58
65
|
end
|
59
|
-
|
60
66
|
end
|
61
67
|
|
62
|
-
class
|
68
|
+
class NullCache
|
69
|
+
def [](template_name)
|
70
|
+
end
|
63
71
|
|
64
|
-
def
|
65
|
-
super('/')
|
72
|
+
def []=(template_name, value)
|
66
73
|
end
|
67
74
|
|
68
|
-
|
75
|
+
def keys
|
76
|
+
[]
|
77
|
+
end
|
69
78
|
|
79
|
+
def ==(other)
|
80
|
+
other.is_a?(self.class)
|
81
|
+
end
|
82
|
+
end
|
70
83
|
end
|