cellery 1.1.1 → 1.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.
package/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  const Cellery = require('./lib/cellery')
2
2
  const cells = require('./lib/cells')
3
+ const template = require('./lib/template')
3
4
  const decoration = require('./lib/decorations')
4
5
 
5
6
  module.exports = {
6
7
  Cellery,
7
8
  ...cells,
8
- ...decoration
9
+ ...decoration,
10
+ ...template
9
11
  }
@@ -0,0 +1,166 @@
1
+ const { Cell, Container, App, Text, Paragraph, Input } = require('./cells')
2
+
3
+ const SENTINEL = '\x00'
4
+
5
+ const registry = { Cell, Container, App, Text, Paragraph, Input }
6
+
7
+ function register(cells) {
8
+ Object.assign(registry, cells)
9
+ }
10
+
11
+ function cellery(strings, ...values) {
12
+ let raw = ''
13
+ const slots = []
14
+ for (let i = 0; i < strings.length; i++) {
15
+ raw += strings[i]
16
+ if (i < values.length) {
17
+ const id = slots.length
18
+ slots.push(values[i])
19
+ raw += SENTINEL + id + SENTINEL
20
+ }
21
+ }
22
+
23
+ const ast = parse(raw, slots)
24
+ if (!ast) throw new Error('Empty template')
25
+ return build(ast, slots)
26
+ }
27
+
28
+ // ---- parser ----
29
+
30
+ function parse(input, slots) {
31
+ let pos = 0
32
+
33
+ function peek() {
34
+ return input[pos]
35
+ }
36
+ function advance() {
37
+ return input[pos++]
38
+ }
39
+ function eof() {
40
+ return pos >= input.length
41
+ }
42
+
43
+ function skip() {
44
+ while (!eof() && /\s/.test(peek())) advance()
45
+ }
46
+
47
+ function resolveValue(str) {
48
+ str = str.trim()
49
+ const m = str.match(new RegExp('^' + SENTINEL + '(\\d+)' + SENTINEL + '$'))
50
+ if (m) return slots[parseInt(m[1])]
51
+ if (str === 'true') return true
52
+ if (str === 'false') return false
53
+ if (str !== '' && !isNaN(str)) return Number(str)
54
+ if (
55
+ (str[0] === '"' && str[str.length - 1] === '"') ||
56
+ (str[0] === "'" && str[str.length - 1] === "'")
57
+ ) {
58
+ return str.slice(1, -1)
59
+ }
60
+ return str
61
+ }
62
+
63
+ function parseNode() {
64
+ skip()
65
+ if (eof()) return null
66
+ if (peek() === '<') {
67
+ if (input[pos + 1] === '/') return null
68
+ return parseElement()
69
+ }
70
+ return parseText()
71
+ }
72
+
73
+ function parseText() {
74
+ let buf = ''
75
+ while (!eof() && peek() !== '<') buf += advance()
76
+ buf = buf.trim()
77
+ if (!buf) return null
78
+ return { type: 'text', value: resolveValue(buf) }
79
+ }
80
+
81
+ function parseElement() {
82
+ advance() // <
83
+
84
+ let name = ''
85
+ while (!eof() && /[a-zA-Z0-9_]/.test(peek())) name += advance()
86
+
87
+ const attrs = parseAttrs()
88
+
89
+ let selfClose = false
90
+ if (peek() === '/') {
91
+ selfClose = true
92
+ advance()
93
+ }
94
+ advance() // >
95
+
96
+ if (selfClose) return { type: 'element', tag: name, attrs, children: [] }
97
+
98
+ const children = []
99
+ while (!eof()) {
100
+ skip()
101
+ if (eof()) break
102
+ if (peek() === '<' && input[pos + 1] === '/') break
103
+ const child = parseNode()
104
+ if (child) children.push(child)
105
+ else break
106
+ }
107
+
108
+ if (!eof() && peek() === '<' && input[pos + 1] === '/') {
109
+ advance()
110
+ advance()
111
+ while (!eof() && peek() !== '>') advance()
112
+ if (!eof()) advance()
113
+ }
114
+
115
+ return { type: 'element', tag: name, attrs, children }
116
+ }
117
+
118
+ function parseAttrs() {
119
+ const attrs = {}
120
+
121
+ while (!eof()) {
122
+ skip()
123
+ if (peek() === '>' || peek() === '/') break
124
+
125
+ let name = ''
126
+ while (!eof() && /[a-zA-Z0-9_\-]/.test(peek())) name += advance()
127
+ if (!name) break
128
+
129
+ if (peek() === '=') {
130
+ advance()
131
+ let val = ''
132
+
133
+ if (peek() === '"' || peek() === "'") {
134
+ const q = advance()
135
+ while (!eof() && peek() !== q) val += advance()
136
+ if (!eof()) advance()
137
+ } else {
138
+ while (!eof() && !/[\s>\/]/.test(peek())) val += advance()
139
+ }
140
+
141
+ attrs[name] = resolveValue(val)
142
+ } else {
143
+ attrs[name] = true
144
+ }
145
+ }
146
+
147
+ return attrs
148
+ }
149
+
150
+ return parseNode()
151
+ }
152
+
153
+ // ---- builder ----
154
+
155
+ function build(node, slots) {
156
+ if (node.type === 'text') return node.value
157
+
158
+ const Ctor = registry[node.tag]
159
+ if (!Ctor) throw new Error('Unknown cell: ' + node.tag)
160
+
161
+ const children = node.children.map((c) => build(c, slots)).filter((c) => c != null)
162
+
163
+ return new Ctor({ ...node.attrs, children })
164
+ }
165
+
166
+ module.exports = { cellery, register }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cellery",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "cellery",
5
5
  "exports": {
6
6
  "./package": "./package.json",