1import {
2  appendContextPath,
3  blockParams,
4  createFrame,
5  isArray,
6  isFunction
7} from '../utils';
8import Exception from '../exception';
9
10export default function(instance) {
11  instance.registerHelper('each', function(context, options) {
12    if (!options) {
13      throw new Exception('Must pass iterator to #each');
14    }
15
16    let fn = options.fn,
17      inverse = options.inverse,
18      i = 0,
19      ret = '',
20      data,
21      contextPath;
22
23    if (options.data && options.ids) {
24      contextPath =
25        appendContextPath(options.data.contextPath, options.ids[0]) + '.';
26    }
27
28    if (isFunction(context)) {
29      context = context.call(this);
30    }
31
32    if (options.data) {
33      data = createFrame(options.data);
34    }
35
36    function execIteration(field, index, last) {
37      if (data) {
38        data.key = field;
39        data.index = index;
40        data.first = index === 0;
41        data.last = !!last;
42
43        if (contextPath) {
44          data.contextPath = contextPath + field;
45        }
46      }
47
48      ret =
49        ret +
50        fn(context[field], {
51          data: data,
52          blockParams: blockParams(
53            [context[field], field],
54            [contextPath + field, null]
55          )
56        });
57    }
58
59    if (context && typeof context === 'object') {
60      if (isArray(context)) {
61        for (let j = context.length; i < j; i++) {
62          if (i in context) {
63            execIteration(i, i, i === context.length - 1);
64          }
65        }
66      } else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
67        const newContext = [];
68        const iterator = context[Symbol.iterator]();
69        for (let it = iterator.next(); !it.done; it = iterator.next()) {
70          newContext.push(it.value);
71        }
72        context = newContext;
73        for (let j = context.length; i < j; i++) {
74          execIteration(i, i, i === context.length - 1);
75        }
76      } else {
77        let priorKey;
78
79        Object.keys(context).forEach(key => {
80          // We're running the iterations one step out of sync so we can detect
81          // the last iteration without have to scan the object twice and create
82          // an itermediate keys array.
83          if (priorKey !== undefined) {
84            execIteration(priorKey, i - 1);
85          }
86          priorKey = key;
87          i++;
88        });
89        if (priorKey !== undefined) {
90          execIteration(priorKey, i - 1, true);
91        }
92      }
93    }
94
95    if (i === 0) {
96      ret = inverse(this);
97    }
98
99    return ret;
100  });
101}
102