Projekt

Obecné

Profil

Stáhnout (26.2 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
var typeOf = require('kind-of');
4
var utils = module.exports;
5

    
6
/**
7
 * Returns true if the given value is a node.
8
 *
9
 * ```js
10
 * var Node = require('snapdragon-node');
11
 * var node = new Node({type: 'foo'});
12
 * console.log(utils.isNode(node)); //=> true
13
 * console.log(utils.isNode({})); //=> false
14
 * ```
15
 * @param {Object} `node` Instance of [snapdragon-node][]
16
 * @returns {Boolean}
17
 * @api public
18
 */
19

    
20
utils.isNode = function(node) {
21
  return typeOf(node) === 'object' && node.isNode === true;
22
};
23

    
24
/**
25
 * Emit an empty string for the given `node`.
26
 *
27
 * ```js
28
 * // do nothing for beginning-of-string
29
 * snapdragon.compiler.set('bos', utils.noop);
30
 * ```
31
 * @param {Object} `node` Instance of [snapdragon-node][]
32
 * @returns {undefined}
33
 * @api public
34
 */
35

    
36
utils.noop = function(node) {
37
  append(this, '', node);
38
};
39

    
40
/**
41
 * Appdend `node.val` to `compiler.output`, exactly as it was created
42
 * by the parser.
43
 *
44
 * ```js
45
 * snapdragon.compiler.set('text', utils.identity);
46
 * ```
47
 * @param {Object} `node` Instance of [snapdragon-node][]
48
 * @returns {undefined}
49
 * @api public
50
 */
51

    
52
utils.identity = function(node) {
53
  append(this, node.val, node);
54
};
55

    
56
/**
57
 * Previously named `.emit`, this method appends the given `val`
58
 * to `compiler.output` for the given node. Useful when you know
59
 * what value should be appended advance, regardless of the actual
60
 * value of `node.val`.
61
 *
62
 * ```js
63
 * snapdragon.compiler
64
 *   .set('i', function(node) {
65
 *     this.mapVisit(node);
66
 *   })
67
 *   .set('i.open', utils.append('<i>'))
68
 *   .set('i.close', utils.append('</i>'))
69
 * ```
70
 * @param {Object} `node` Instance of [snapdragon-node][]
71
 * @returns {Function} Returns a compiler middleware function.
72
 * @api public
73
 */
74

    
75
utils.append = function(val) {
76
  return function(node) {
77
    append(this, val, node);
78
  };
79
};
80

    
81
/**
82
 * Used in compiler middleware, this onverts an AST node into
83
 * an empty `text` node and deletes `node.nodes` if it exists.
84
 * The advantage of this method is that, as opposed to completely
85
 * removing the node, indices will not need to be re-calculated
86
 * in sibling nodes, and nothing is appended to the output.
87
 *
88
 * ```js
89
 * utils.toNoop(node);
90
 * // convert `node.nodes` to the given value instead of deleting it
91
 * utils.toNoop(node, []);
92
 * ```
93
 * @param {Object} `node` Instance of [snapdragon-node][]
94
 * @param {Array} `nodes` Optionally pass a new `nodes` value, to replace the existing `node.nodes` array.
95
 * @api public
96
 */
97

    
98
utils.toNoop = function(node, nodes) {
99
  if (nodes) {
100
    node.nodes = nodes;
101
  } else {
102
    delete node.nodes;
103
    node.type = 'text';
104
    node.val = '';
105
  }
106
};
107

    
108
/**
109
 * Visit `node` with the given `fn`. The built-in `.visit` method in snapdragon
110
 * automatically calls registered compilers, this allows you to pass a visitor
111
 * function.
112
 *
113
 * ```js
114
 * snapdragon.compiler.set('i', function(node) {
115
 *   utils.visit(node, function(childNode) {
116
 *     // do stuff with "childNode"
117
 *     return childNode;
118
 *   });
119
 * });
120
 * ```
121
 * @param {Object} `node` Instance of [snapdragon-node][]
122
 * @param {Function} `fn`
123
 * @return {Object} returns the node after recursively visiting all child nodes.
124
 * @api public
125
 */
126

    
127
utils.visit = function(node, fn) {
128
  assert(utils.isNode(node), 'expected node to be an instance of Node');
129
  assert(isFunction(fn), 'expected a visitor function');
130
  fn(node);
131
  return node.nodes ? utils.mapVisit(node, fn) : node;
132
};
133

    
134
/**
135
 * Map [visit](#visit) the given `fn` over `node.nodes`. This is called by
136
 * [visit](#visit), use this method if you do not want `fn` to be called on
137
 * the first node.
138
 *
139
 * ```js
140
 * snapdragon.compiler.set('i', function(node) {
141
 *   utils.mapVisit(node, function(childNode) {
142
 *     // do stuff with "childNode"
143
 *     return childNode;
144
 *   });
145
 * });
146
 * ```
147
 * @param {Object} `node` Instance of [snapdragon-node][]
148
 * @param {Object} `options`
149
 * @param {Function} `fn`
150
 * @return {Object} returns the node
151
 * @api public
152
 */
153

    
154
utils.mapVisit = function(node, fn) {
155
  assert(utils.isNode(node), 'expected node to be an instance of Node');
156
  assert(isArray(node.nodes), 'expected node.nodes to be an array');
157
  assert(isFunction(fn), 'expected a visitor function');
158

    
159
  for (var i = 0; i < node.nodes.length; i++) {
160
    utils.visit(node.nodes[i], fn);
161
  }
162
  return node;
163
};
164

    
165
/**
166
 * Unshift an `*.open` node onto `node.nodes`.
167
 *
168
 * ```js
169
 * var Node = require('snapdragon-node');
170
 * snapdragon.parser.set('brace', function(node) {
171
 *   var match = this.match(/^{/);
172
 *   if (match) {
173
 *     var parent = new Node({type: 'brace'});
174
 *     utils.addOpen(parent, Node);
175
 *     console.log(parent.nodes[0]):
176
 *     // { type: 'brace.open', val: '' };
177
 *
178
 *     // push the parent "brace" node onto the stack
179
 *     this.push(parent);
180
 *
181
 *     // return the parent node, so it's also added to the AST
182
 *     return brace;
183
 *   }
184
 * });
185
 * ```
186
 * @param {Object} `node` Instance of [snapdragon-node][]
187
 * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][].
188
 * @param {Function} `filter` Optionaly specify a filter function to exclude the node.
189
 * @return {Object} Returns the created opening node.
190
 * @api public
191
 */
192

    
193
utils.addOpen = function(node, Node, val, filter) {
194
  assert(utils.isNode(node), 'expected node to be an instance of Node');
195
  assert(isFunction(Node), 'expected Node to be a constructor function');
196

    
197
  if (typeof val === 'function') {
198
    filter = val;
199
    val = '';
200
  }
201

    
202
  if (typeof filter === 'function' && !filter(node)) return;
203
  var open = new Node({ type: node.type + '.open', val: val});
204
  var unshift = node.unshift || node.unshiftNode;
205
  if (typeof unshift === 'function') {
206
    unshift.call(node, open);
207
  } else {
208
    utils.unshiftNode(node, open);
209
  }
210
  return open;
211
};
212

    
213
/**
214
 * Push a `*.close` node onto `node.nodes`.
215
 *
216
 * ```js
217
 * var Node = require('snapdragon-node');
218
 * snapdragon.parser.set('brace', function(node) {
219
 *   var match = this.match(/^}/);
220
 *   if (match) {
221
 *     var parent = this.parent();
222
 *     if (parent.type !== 'brace') {
223
 *       throw new Error('missing opening: ' + '}');
224
 *     }
225
 *
226
 *     utils.addClose(parent, Node);
227
 *     console.log(parent.nodes[parent.nodes.length - 1]):
228
 *     // { type: 'brace.close', val: '' };
229
 *
230
 *     // no need to return a node, since the parent
231
 *     // was already added to the AST
232
 *     return;
233
 *   }
234
 * });
235
 * ```
236
 * @param {Object} `node` Instance of [snapdragon-node][]
237
 * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][].
238
 * @param {Function} `filter` Optionaly specify a filter function to exclude the node.
239
 * @return {Object} Returns the created closing node.
240
 * @api public
241
 */
242

    
243
utils.addClose = function(node, Node, val, filter) {
244
  assert(utils.isNode(node), 'expected node to be an instance of Node');
245
  assert(isFunction(Node), 'expected Node to be a constructor function');
246

    
247
  if (typeof val === 'function') {
248
    filter = val;
249
    val = '';
250
  }
251

    
252
  if (typeof filter === 'function' && !filter(node)) return;
253
  var close = new Node({ type: node.type + '.close', val: val});
254
  var push = node.push || node.pushNode;
255
  if (typeof push === 'function') {
256
    push.call(node, close);
257
  } else {
258
    utils.pushNode(node, close);
259
  }
260
  return close;
261
};
262

    
263
/**
264
 * Wraps the given `node` with `*.open` and `*.close` nodes.
265
 *
266
 * @param {Object} `node` Instance of [snapdragon-node][]
267
 * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][].
268
 * @param {Function} `filter` Optionaly specify a filter function to exclude the node.
269
 * @return {Object} Returns the node
270
 * @api public
271
 */
272

    
273
utils.wrapNodes = function(node, Node, filter) {
274
  assert(utils.isNode(node), 'expected node to be an instance of Node');
275
  assert(isFunction(Node), 'expected Node to be a constructor function');
276

    
277
  utils.addOpen(node, Node, filter);
278
  utils.addClose(node, Node, filter);
279
  return node;
280
};
281

    
282
/**
283
 * Push the given `node` onto `parent.nodes`, and set `parent` as `node.parent.
284
 *
285
 * ```js
286
 * var parent = new Node({type: 'foo'});
287
 * var node = new Node({type: 'bar'});
288
 * utils.pushNode(parent, node);
289
 * console.log(parent.nodes[0].type) // 'bar'
290
 * console.log(node.parent.type) // 'foo'
291
 * ```
292
 * @param {Object} `parent`
293
 * @param {Object} `node` Instance of [snapdragon-node][]
294
 * @return {Object} Returns the child node
295
 * @api public
296
 */
297

    
298
utils.pushNode = function(parent, node) {
299
  assert(utils.isNode(parent), 'expected parent node to be an instance of Node');
300
  assert(utils.isNode(node), 'expected node to be an instance of Node');
301

    
302
  node.define('parent', parent);
303
  parent.nodes = parent.nodes || [];
304
  parent.nodes.push(node);
305
  return node;
306
};
307

    
308
/**
309
 * Unshift `node` onto `parent.nodes`, and set `parent` as `node.parent.
310
 *
311
 * ```js
312
 * var parent = new Node({type: 'foo'});
313
 * var node = new Node({type: 'bar'});
314
 * utils.unshiftNode(parent, node);
315
 * console.log(parent.nodes[0].type) // 'bar'
316
 * console.log(node.parent.type) // 'foo'
317
 * ```
318
 * @param {Object} `parent`
319
 * @param {Object} `node` Instance of [snapdragon-node][]
320
 * @return {undefined}
321
 * @api public
322
 */
323

    
324
utils.unshiftNode = function(parent, node) {
325
  assert(utils.isNode(parent), 'expected parent node to be an instance of Node');
326
  assert(utils.isNode(node), 'expected node to be an instance of Node');
327

    
328
  node.define('parent', parent);
329
  parent.nodes = parent.nodes || [];
330
  parent.nodes.unshift(node);
331
};
332

    
333
/**
334
 * Pop the last `node` off of `parent.nodes`. The advantage of
335
 * using this method is that it checks for `node.nodes` and works
336
 * with any version of `snapdragon-node`.
337
 *
338
 * ```js
339
 * var parent = new Node({type: 'foo'});
340
 * utils.pushNode(parent, new Node({type: 'foo'}));
341
 * utils.pushNode(parent, new Node({type: 'bar'}));
342
 * utils.pushNode(parent, new Node({type: 'baz'}));
343
 * console.log(parent.nodes.length); //=> 3
344
 * utils.popNode(parent);
345
 * console.log(parent.nodes.length); //=> 2
346
 * ```
347
 * @param {Object} `parent`
348
 * @param {Object} `node` Instance of [snapdragon-node][]
349
 * @return {Number|Undefined} Returns the length of `node.nodes` or undefined.
350
 * @api public
351
 */
352

    
353
utils.popNode = function(node) {
354
  assert(utils.isNode(node), 'expected node to be an instance of Node');
355
  if (typeof node.pop === 'function') {
356
    return node.pop();
357
  }
358
  return node.nodes && node.nodes.pop();
359
};
360

    
361
/**
362
 * Shift the first `node` off of `parent.nodes`. The advantage of
363
 * using this method is that it checks for `node.nodes` and works
364
 * with any version of `snapdragon-node`.
365
 *
366
 * ```js
367
 * var parent = new Node({type: 'foo'});
368
 * utils.pushNode(parent, new Node({type: 'foo'}));
369
 * utils.pushNode(parent, new Node({type: 'bar'}));
370
 * utils.pushNode(parent, new Node({type: 'baz'}));
371
 * console.log(parent.nodes.length); //=> 3
372
 * utils.shiftNode(parent);
373
 * console.log(parent.nodes.length); //=> 2
374
 * ```
375
 * @param {Object} `parent`
376
 * @param {Object} `node` Instance of [snapdragon-node][]
377
 * @return {Number|Undefined} Returns the length of `node.nodes` or undefined.
378
 * @api public
379
 */
380

    
381
utils.shiftNode = function(node) {
382
  assert(utils.isNode(node), 'expected node to be an instance of Node');
383
  if (typeof node.shift === 'function') {
384
    return node.shift();
385
  }
386
  return node.nodes && node.nodes.shift();
387
};
388

    
389
/**
390
 * Remove the specified `node` from `parent.nodes`.
391
 *
392
 * ```js
393
 * var parent = new Node({type: 'abc'});
394
 * var foo = new Node({type: 'foo'});
395
 * utils.pushNode(parent, foo);
396
 * utils.pushNode(parent, new Node({type: 'bar'}));
397
 * utils.pushNode(parent, new Node({type: 'baz'}));
398
 * console.log(parent.nodes.length); //=> 3
399
 * utils.removeNode(parent, foo);
400
 * console.log(parent.nodes.length); //=> 2
401
 * ```
402
 * @param {Object} `parent`
403
 * @param {Object} `node` Instance of [snapdragon-node][]
404
 * @return {Object|undefined} Returns the removed node, if successful, or undefined if it does not exist on `parent.nodes`.
405
 * @api public
406
 */
407

    
408
utils.removeNode = function(parent, node) {
409
  assert(utils.isNode(parent), 'expected parent.node to be an instance of Node');
410
  assert(utils.isNode(node), 'expected node to be an instance of Node');
411

    
412
  if (!parent.nodes) {
413
    return null;
414
  }
415

    
416
  if (typeof parent.remove === 'function') {
417
    return parent.remove(node);
418
  }
419

    
420
  var idx = parent.nodes.indexOf(node);
421
  if (idx !== -1) {
422
    return parent.nodes.splice(idx, 1);
423
  }
424
};
425

    
426
/**
427
 * Returns true if `node.type` matches the given `type`. Throws a
428
 * `TypeError` if `node` is not an instance of `Node`.
429
 *
430
 * ```js
431
 * var Node = require('snapdragon-node');
432
 * var node = new Node({type: 'foo'});
433
 * console.log(utils.isType(node, 'foo')); // false
434
 * console.log(utils.isType(node, 'bar')); // true
435
 * ```
436
 * @param {Object} `node` Instance of [snapdragon-node][]
437
 * @param {String} `type`
438
 * @return {Boolean}
439
 * @api public
440
 */
441

    
442
utils.isType = function(node, type) {
443
  assert(utils.isNode(node), 'expected node to be an instance of Node');
444
  switch (typeOf(type)) {
445
    case 'array':
446
      var types = type.slice();
447
      for (var i = 0; i < types.length; i++) {
448
        if (utils.isType(node, types[i])) {
449
          return true;
450
        }
451
      }
452
      return false;
453
    case 'string':
454
      return node.type === type;
455
    case 'regexp':
456
      return type.test(node.type);
457
    default: {
458
      throw new TypeError('expected "type" to be an array, string or regexp');
459
    }
460
  }
461
};
462

    
463
/**
464
 * Returns true if the given `node` has the given `type` in `node.nodes`.
465
 * Throws a `TypeError` if `node` is not an instance of `Node`.
466
 *
467
 * ```js
468
 * var Node = require('snapdragon-node');
469
 * var node = new Node({
470
 *   type: 'foo',
471
 *   nodes: [
472
 *     new Node({type: 'bar'}),
473
 *     new Node({type: 'baz'})
474
 *   ]
475
 * });
476
 * console.log(utils.hasType(node, 'xyz')); // false
477
 * console.log(utils.hasType(node, 'baz')); // true
478
 * ```
479
 * @param {Object} `node` Instance of [snapdragon-node][]
480
 * @param {String} `type`
481
 * @return {Boolean}
482
 * @api public
483
 */
484

    
485
utils.hasType = function(node, type) {
486
  assert(utils.isNode(node), 'expected node to be an instance of Node');
487
  if (!Array.isArray(node.nodes)) return false;
488
  for (var i = 0; i < node.nodes.length; i++) {
489
    if (utils.isType(node.nodes[i], type)) {
490
      return true;
491
    }
492
  }
493
  return false;
494
};
495

    
496
/**
497
 * Returns the first node from `node.nodes` of the given `type`
498
 *
499
 * ```js
500
 * var node = new Node({
501
 *   type: 'foo',
502
 *   nodes: [
503
 *     new Node({type: 'text', val: 'abc'}),
504
 *     new Node({type: 'text', val: 'xyz'})
505
 *   ]
506
 * });
507
 *
508
 * var textNode = utils.firstOfType(node.nodes, 'text');
509
 * console.log(textNode.val);
510
 * //=> 'abc'
511
 * ```
512
 * @param {Array} `nodes`
513
 * @param {String} `type`
514
 * @return {Object|undefined} Returns the first matching node or undefined.
515
 * @api public
516
 */
517

    
518
utils.firstOfType = function(nodes, type) {
519
  for (var i = 0; i < nodes.length; i++) {
520
    var node = nodes[i];
521
    if (utils.isType(node, type)) {
522
      return node;
523
    }
524
  }
525
};
526

    
527
/**
528
 * Returns the node at the specified index, or the first node of the
529
 * given `type` from `node.nodes`.
530
 *
531
 * ```js
532
 * var node = new Node({
533
 *   type: 'foo',
534
 *   nodes: [
535
 *     new Node({type: 'text', val: 'abc'}),
536
 *     new Node({type: 'text', val: 'xyz'})
537
 *   ]
538
 * });
539
 *
540
 * var nodeOne = utils.findNode(node.nodes, 'text');
541
 * console.log(nodeOne.val);
542
 * //=> 'abc'
543
 *
544
 * var nodeTwo = utils.findNode(node.nodes, 1);
545
 * console.log(nodeTwo.val);
546
 * //=> 'xyz'
547
 * ```
548
 *
549
 * @param {Array} `nodes`
550
 * @param {String|Number} `type` Node type or index.
551
 * @return {Object} Returns a node or undefined.
552
 * @api public
553
 */
554

    
555
utils.findNode = function(nodes, type) {
556
  if (!Array.isArray(nodes)) {
557
    return null;
558
  }
559
  if (typeof type === 'number') {
560
    return nodes[type];
561
  }
562
  return utils.firstOfType(nodes, type);
563
};
564

    
565
/**
566
 * Returns true if the given node is an "*.open" node.
567
 *
568
 * ```js
569
 * var Node = require('snapdragon-node');
570
 * var brace = new Node({type: 'brace'});
571
 * var open = new Node({type: 'brace.open'});
572
 * var close = new Node({type: 'brace.close'});
573
 *
574
 * console.log(utils.isOpen(brace)); // false
575
 * console.log(utils.isOpen(open)); // true
576
 * console.log(utils.isOpen(close)); // false
577
 * ```
578
 * @param {Object} `node` Instance of [snapdragon-node][]
579
 * @return {Boolean}
580
 * @api public
581
 */
582

    
583
utils.isOpen = function(node) {
584
  assert(utils.isNode(node), 'expected node to be an instance of Node');
585
  return node.type.slice(-5) === '.open';
586
};
587

    
588
/**
589
 * Returns true if the given node is a "*.close" node.
590
 *
591
 * ```js
592
 * var Node = require('snapdragon-node');
593
 * var brace = new Node({type: 'brace'});
594
 * var open = new Node({type: 'brace.open'});
595
 * var close = new Node({type: 'brace.close'});
596
 *
597
 * console.log(utils.isClose(brace)); // false
598
 * console.log(utils.isClose(open)); // false
599
 * console.log(utils.isClose(close)); // true
600
 * ```
601
 * @param {Object} `node` Instance of [snapdragon-node][]
602
 * @return {Boolean}
603
 * @api public
604
 */
605

    
606
utils.isClose = function(node) {
607
  assert(utils.isNode(node), 'expected node to be an instance of Node');
608
  return node.type.slice(-6) === '.close';
609
};
610

    
611
/**
612
 * Returns true if `node.nodes` **has** an `.open` node
613
 *
614
 * ```js
615
 * var Node = require('snapdragon-node');
616
 * var brace = new Node({
617
 *   type: 'brace',
618
 *   nodes: []
619
 * });
620
 *
621
 * var open = new Node({type: 'brace.open'});
622
 * console.log(utils.hasOpen(brace)); // false
623
 *
624
 * brace.pushNode(open);
625
 * console.log(utils.hasOpen(brace)); // true
626
 * ```
627
 * @param {Object} `node` Instance of [snapdragon-node][]
628
 * @return {Boolean}
629
 * @api public
630
 */
631

    
632
utils.hasOpen = function(node) {
633
  assert(utils.isNode(node), 'expected node to be an instance of Node');
634
  var first = node.first || node.nodes ? node.nodes[0] : null;
635
  if (utils.isNode(first)) {
636
    return first.type === node.type + '.open';
637
  }
638
  return false;
639
};
640

    
641
/**
642
 * Returns true if `node.nodes` **has** a `.close` node
643
 *
644
 * ```js
645
 * var Node = require('snapdragon-node');
646
 * var brace = new Node({
647
 *   type: 'brace',
648
 *   nodes: []
649
 * });
650
 *
651
 * var close = new Node({type: 'brace.close'});
652
 * console.log(utils.hasClose(brace)); // false
653
 *
654
 * brace.pushNode(close);
655
 * console.log(utils.hasClose(brace)); // true
656
 * ```
657
 * @param {Object} `node` Instance of [snapdragon-node][]
658
 * @return {Boolean}
659
 * @api public
660
 */
661

    
662
utils.hasClose = function(node) {
663
  assert(utils.isNode(node), 'expected node to be an instance of Node');
664
  var last = node.last || node.nodes ? node.nodes[node.nodes.length - 1] : null;
665
  if (utils.isNode(last)) {
666
    return last.type === node.type + '.close';
667
  }
668
  return false;
669
};
670

    
671
/**
672
 * Returns true if `node.nodes` has both `.open` and `.close` nodes
673
 *
674
 * ```js
675
 * var Node = require('snapdragon-node');
676
 * var brace = new Node({
677
 *   type: 'brace',
678
 *   nodes: []
679
 * });
680
 *
681
 * var open = new Node({type: 'brace.open'});
682
 * var close = new Node({type: 'brace.close'});
683
 * console.log(utils.hasOpen(brace)); // false
684
 * console.log(utils.hasClose(brace)); // false
685
 *
686
 * brace.pushNode(open);
687
 * brace.pushNode(close);
688
 * console.log(utils.hasOpen(brace)); // true
689
 * console.log(utils.hasClose(brace)); // true
690
 * ```
691
 * @param {Object} `node` Instance of [snapdragon-node][]
692
 * @return {Boolean}
693
 * @api public
694
 */
695

    
696
utils.hasOpenAndClose = function(node) {
697
  return utils.hasOpen(node) && utils.hasClose(node);
698
};
699

    
700
/**
701
 * Push the given `node` onto the `state.inside` array for the
702
 * given type. This array is used as a specialized "stack" for
703
 * only the given `node.type`.
704
 *
705
 * ```js
706
 * var state = { inside: {}};
707
 * var node = new Node({type: 'brace'});
708
 * utils.addType(state, node);
709
 * console.log(state.inside);
710
 * //=> { brace: [{type: 'brace'}] }
711
 * ```
712
 * @param {Object} `state` The `compiler.state` object or custom state object.
713
 * @param {Object} `node` Instance of [snapdragon-node][]
714
 * @return {Array} Returns the `state.inside` stack for the given type.
715
 * @api public
716
 */
717

    
718
utils.addType = function(state, node) {
719
  assert(utils.isNode(node), 'expected node to be an instance of Node');
720
  assert(isObject(state), 'expected state to be an object');
721

    
722
  var type = node.parent
723
    ? node.parent.type
724
    : node.type.replace(/\.open$/, '');
725

    
726
  if (!state.hasOwnProperty('inside')) {
727
    state.inside = {};
728
  }
729
  if (!state.inside.hasOwnProperty(type)) {
730
    state.inside[type] = [];
731
  }
732

    
733
  var arr = state.inside[type];
734
  arr.push(node);
735
  return arr;
736
};
737

    
738
/**
739
 * Remove the given `node` from the `state.inside` array for the
740
 * given type. This array is used as a specialized "stack" for
741
 * only the given `node.type`.
742
 *
743
 * ```js
744
 * var state = { inside: {}};
745
 * var node = new Node({type: 'brace'});
746
 * utils.addType(state, node);
747
 * console.log(state.inside);
748
 * //=> { brace: [{type: 'brace'}] }
749
 * utils.removeType(state, node);
750
 * //=> { brace: [] }
751
 * ```
752
 * @param {Object} `state` The `compiler.state` object or custom state object.
753
 * @param {Object} `node` Instance of [snapdragon-node][]
754
 * @return {Array} Returns the `state.inside` stack for the given type.
755
 * @api public
756
 */
757

    
758
utils.removeType = function(state, node) {
759
  assert(utils.isNode(node), 'expected node to be an instance of Node');
760
  assert(isObject(state), 'expected state to be an object');
761

    
762
  var type = node.parent
763
    ? node.parent.type
764
    : node.type.replace(/\.close$/, '');
765

    
766
  if (state.inside.hasOwnProperty(type)) {
767
    return state.inside[type].pop();
768
  }
769
};
770

    
771
/**
772
 * Returns true if `node.val` is an empty string, or `node.nodes` does
773
 * not contain any non-empty text nodes.
774
 *
775
 * ```js
776
 * var node = new Node({type: 'text'});
777
 * utils.isEmpty(node); //=> true
778
 * node.val = 'foo';
779
 * utils.isEmpty(node); //=> false
780
 * ```
781
 * @param {Object} `node` Instance of [snapdragon-node][]
782
 * @param {Function} `fn`
783
 * @return {Boolean}
784
 * @api public
785
 */
786

    
787
utils.isEmpty = function(node, fn) {
788
  assert(utils.isNode(node), 'expected node to be an instance of Node');
789

    
790
  if (!Array.isArray(node.nodes)) {
791
    if (node.type !== 'text') {
792
      return true;
793
    }
794
    if (typeof fn === 'function') {
795
      return fn(node, node.parent);
796
    }
797
    return !utils.trim(node.val);
798
  }
799

    
800
  for (var i = 0; i < node.nodes.length; i++) {
801
    var child = node.nodes[i];
802
    if (utils.isOpen(child) || utils.isClose(child)) {
803
      continue;
804
    }
805
    if (!utils.isEmpty(child, fn)) {
806
      return false;
807
    }
808
  }
809

    
810
  return true;
811
};
812

    
813
/**
814
 * Returns true if the `state.inside` stack for the given type exists
815
 * and has one or more nodes on it.
816
 *
817
 * ```js
818
 * var state = { inside: {}};
819
 * var node = new Node({type: 'brace'});
820
 * console.log(utils.isInsideType(state, 'brace')); //=> false
821
 * utils.addType(state, node);
822
 * console.log(utils.isInsideType(state, 'brace')); //=> true
823
 * utils.removeType(state, node);
824
 * console.log(utils.isInsideType(state, 'brace')); //=> false
825
 * ```
826
 * @param {Object} `state`
827
 * @param {String} `type`
828
 * @return {Boolean}
829
 * @api public
830
 */
831

    
832
utils.isInsideType = function(state, type) {
833
  assert(isObject(state), 'expected state to be an object');
834
  assert(isString(type), 'expected type to be a string');
835

    
836
  if (!state.hasOwnProperty('inside')) {
837
    return false;
838
  }
839

    
840
  if (!state.inside.hasOwnProperty(type)) {
841
    return false;
842
  }
843

    
844
  return state.inside[type].length > 0;
845
};
846

    
847
/**
848
 * Returns true if `node` is either a child or grand-child of the given `type`,
849
 * or `state.inside[type]` is a non-empty array.
850
 *
851
 * ```js
852
 * var state = { inside: {}};
853
 * var node = new Node({type: 'brace'});
854
 * var open = new Node({type: 'brace.open'});
855
 * console.log(utils.isInside(state, open, 'brace')); //=> false
856
 * utils.pushNode(node, open);
857
 * console.log(utils.isInside(state, open, 'brace')); //=> true
858
 * ```
859
 * @param {Object} `state` Either the `compiler.state` object, if it exists, or a user-supplied state object.
860
 * @param {Object} `node` Instance of [snapdragon-node][]
861
 * @param {String} `type` The `node.type` to check for.
862
 * @return {Boolean}
863
 * @api public
864
 */
865

    
866
utils.isInside = function(state, node, type) {
867
  assert(utils.isNode(node), 'expected node to be an instance of Node');
868
  assert(isObject(state), 'expected state to be an object');
869

    
870
  if (Array.isArray(type)) {
871
    for (var i = 0; i < type.length; i++) {
872
      if (utils.isInside(state, node, type[i])) {
873
        return true;
874
      }
875
    }
876
    return false;
877
  }
878

    
879
  var parent = node.parent;
880
  if (typeof type === 'string') {
881
    return (parent && parent.type === type) || utils.isInsideType(state, type);
882
  }
883

    
884
  if (typeOf(type) === 'regexp') {
885
    if (parent && parent.type && type.test(parent.type)) {
886
      return true;
887
    }
888

    
889
    var keys = Object.keys(state.inside);
890
    var len = keys.length;
891
    var idx = -1;
892
    while (++idx < len) {
893
      var key = keys[idx];
894
      var val = state.inside[key];
895

    
896
      if (Array.isArray(val) && val.length !== 0 && type.test(key)) {
897
        return true;
898
      }
899
    }
900
  }
901
  return false;
902
};
903

    
904
/**
905
 * Get the last `n` element from the given `array`. Used for getting
906
 * a node from `node.nodes.`
907
 *
908
 * @param {Array} `array`
909
 * @param {Number} `n`
910
 * @return {undefined}
911
 * @api public
912
 */
913

    
914
utils.last = function(arr, n) {
915
  return arr[arr.length - (n || 1)];
916
};
917

    
918
/**
919
 * Cast the given `val` to an array.
920
 *
921
 * ```js
922
 * console.log(utils.arrayify(''));
923
 * //=> []
924
 * console.log(utils.arrayify('foo'));
925
 * //=> ['foo']
926
 * console.log(utils.arrayify(['foo']));
927
 * //=> ['foo']
928
 * ```
929
 * @param {any} `val`
930
 * @return {Array}
931
 * @api public
932
 */
933

    
934
utils.arrayify = function(val) {
935
  if (typeof val === 'string' && val !== '') {
936
    return [val];
937
  }
938
  if (!Array.isArray(val)) {
939
    return [];
940
  }
941
  return val;
942
};
943

    
944
/**
945
 * Convert the given `val` to a string by joining with `,`. Useful
946
 * for creating a cheerio/CSS/DOM-style selector from a list of strings.
947
 *
948
 * @param {any} `val`
949
 * @return {Array}
950
 * @api public
951
 */
952

    
953
utils.stringify = function(val) {
954
  return utils.arrayify(val).join(',');
955
};
956

    
957
/**
958
 * Ensure that the given value is a string and call `.trim()` on it,
959
 * or return an empty string.
960
 *
961
 * @param {String} `str`
962
 * @return {String}
963
 * @api public
964
 */
965

    
966
utils.trim = function(str) {
967
  return typeof str === 'string' ? str.trim() : '';
968
};
969

    
970
/**
971
 * Return true if val is an object
972
 */
973

    
974
function isObject(val) {
975
  return typeOf(val) === 'object';
976
}
977

    
978
/**
979
 * Return true if val is a string
980
 */
981

    
982
function isString(val) {
983
  return typeof val === 'string';
984
}
985

    
986
/**
987
 * Return true if val is a function
988
 */
989

    
990
function isFunction(val) {
991
  return typeof val === 'function';
992
}
993

    
994
/**
995
 * Return true if val is an array
996
 */
997

    
998
function isArray(val) {
999
  return Array.isArray(val);
1000
}
1001

    
1002
/**
1003
 * Shim to ensure the `.append` methods work with any version of snapdragon
1004
 */
1005

    
1006
function append(compiler, val, node) {
1007
  if (typeof compiler.append !== 'function') {
1008
    return compiler.emit(val, node);
1009
  }
1010
  return compiler.append(val, node);
1011
}
1012

    
1013
/**
1014
 * Simplified assertion. Throws an error is `val` is falsey.
1015
 */
1016

    
1017
function assert(val, message) {
1018
  if (!val) throw new Error(message);
1019
}
(3-3/4)