1'use strict';
2
3var dirname = require('path').dirname;
4var constantinople = require('constantinople');
5var walk = require('pug-walk');
6var error = require('pug-error');
7var runFilter = require('./run-filter');
8
9module.exports = handleFilters;
10function handleFilters(ast, filters, options, filterAliases) {
11  options = options || {};
12  walk(ast, function (node) {
13    var dir = node.filename ? dirname(node.filename) : null;
14    if (node.type === 'Filter') {
15      handleNestedFilters(node, filters, options, filterAliases);
16      var text = getBodyAsText(node);
17      var attrs = getAttributes(node, options);
18      attrs.filename = node.filename;
19      node.type = 'Text';
20      node.val = filterWithFallback(node, text, attrs);
21    } else if (node.type === 'RawInclude' && node.filters.length) {
22      var firstFilter = node.filters.pop();
23      var attrs = getAttributes(firstFilter, options);
24      var filename = attrs.filename = node.file.fullPath;
25      var str = node.file.str;
26      node.type = 'Text';
27      node.val = filterFileWithFallback(firstFilter, filename, str, attrs);
28      node.filters.slice().reverse().forEach(function (filter) {
29        var attrs = getAttributes(filter, options);
30        attrs.filename = filename;
31        node.val = filterWithFallback(filter, node.val, attrs);
32      });
33      node.filters = undefined;
34      node.file = undefined;
35    }
36
37    function filterWithFallback(filter, text, attrs, funcName) {
38      try {
39        var filterName = getFilterName(filter);
40        if (filters && filters[filterName]) {
41          return filters[filterName](text, attrs);
42        } else {
43          return runFilter(filterName, text, attrs, dir, funcName);
44        }
45      } catch (ex) {
46        if (ex.code === 'UNKNOWN_FILTER') {
47          throw error(ex.code, ex.message, filter);
48        }
49        throw ex;
50      }
51    }
52
53    function filterFileWithFallback(filter, filename, text, attrs) {
54      var filterName = getFilterName(filter);
55      if (filters && filters[filterName]) {
56        return filters[filterName](text, attrs);
57      } else {
58        return filterWithFallback(filter, filename, attrs, 'renderFile');
59      }
60    }
61  }, {includeDependencies: true});
62  function getFilterName(filter) {
63    var filterName = filter.name;
64    if (filterAliases && filterAliases[filterName]) {
65      filterName = filterAliases[filterName];
66      if (filterAliases[filterName]) {
67        throw error(
68          'FILTER_ALISE_CHAIN',
69          'The filter "' + filter.name + '" is an alias for "' + filterName +
70          '", which is an alias for "' + filterAliases[filterName] +
71          '".  Pug does not support chains of filter aliases.',
72          filter
73        );
74      }
75    }
76    return filterName;
77  }
78  return ast;
79};
80
81function handleNestedFilters(node, filters, options, filterAliases) {
82  if (node.block.nodes[0] && node.block.nodes[0].type === 'Filter') {
83    node.block.nodes[0] = handleFilters(node.block, filters, options, filterAliases).nodes[0];
84  }
85}
86
87function getBodyAsText(node) {
88  return node.block.nodes.map(
89    function(node){ return node.val; }
90  ).join('');
91}
92
93function getAttributes(node, options) {
94  var attrs = {};
95  node.attrs.forEach(function (attr) {
96    try {
97      attrs[attr.name] = attr.val === true ? true : constantinople.toConstant(attr.val);
98    } catch (ex) {
99      if (/not constant/.test(ex.message)) {
100        throw error('FILTER_OPTION_NOT_CONSTANT', ex.message + ' All filters are rendered compile-time so filter options must be constants.', node);
101      }
102      throw ex;
103    }
104  });
105  var opts = options[node.name] || {};
106  Object.keys(opts).forEach(function (opt) {
107    if (!attrs.hasOwnProperty(opt)) {
108      attrs[opt] = opts[opt];
109    }
110  });
111  return attrs;
112}
113