# All files / src/lib/nunjucks-extensions set-external.ts

91.11% Statements 41/45
77.77% Branches 7/9
100% Functions 9/9
90.69% Lines 39/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    916x
    6x
    6x

    6x

    6x

    6x

    6x

    6x
    83x

    83x

    3x

    3x

    3x
    3x

    3x
    5x
    2x

    3x

    3x
    3x
    3x
    3x
    3x
    3x
    6x
    3x

    3x
    3x

    3x
    3x

    3x

    2x

    3x
    3x

    3x

    3x
    3x
    3x

    3x

     import fs from 'fs-extra';
    import path from 'path';
    import csvParse from 'csv-parse/lib/sync';
    import { Environment, Extension } from 'nunjucks';
    // Type definitions are undefined / documented and in flux for these. See the source.
    // @ts-ignore
    import { Parser } from 'nunjucks/src/parser';
    // @ts-ignore
    import { lex } from 'nunjucks/src/lexer';

    import * as logger from '../../utils/logger';

    const acceptedFileTypes = ['json', 'csv'];

    /**
     * Nunjucks extension for sourcing in variables from external sources.
     * Supports only .json and .csv sources for now.
     */
    export class SetExternalExtension implements Extension {
      tags = ['ext'];

      constructor(private rootPath: string, private env: Environment) {}

      emitLoad(fullPath: string) {
        // Emit the nunjucks load event for the listener in {@link VariableRenderer}. No type defs.
        // @ts-ignore
        this.env.emit('load', '', { path: fullPath });
      }

      parse(parser: any, nodes: any, lexer: any) {
        // get the tag token
        const setExtTagToken = parser.nextToken();

        // parse the args and move after the block end. passing true
        // as the second arg is required if there are no parentheses
        const args = parser.parseSignature(null, true);
        parser.advanceAfterBlockEnd(setExtTagToken.value);

        const options = args.children
          .filter((child: any) => !(child instanceof nodes.KeywordArgs))
          .map((child: any) => child.value as string);

        // last child contains the kvp containing the path to the external variable source
        const lastChild = args.children[args.children.length - 1];

        const buffer: string[] = [];
        if (lastChild instanceof nodes.KeywordArgs) {
          lastChild.children.forEach((pair: any) => {
            const variableName = pair.key.value;
            const resourcePath = pair.value.value;
            acceptedFileTypes.forEach((fileType) => {
              if (!resourcePath.endsWith(fileType)) {
                return;
              }

              const fullResourcePath = path.resolve(this.rootPath, resourcePath);
              Iif (fileType === 'json') {
                const resourceRaw = fs.readFileSync(fullResourcePath);
                buffer.push(`{% set ${variableName} = ${resourceRaw} %}`);
              } else if (fileType === 'csv') {
                const hasNoHeader = options.includes('noHeader');

                const csvResourceRaw = csvParse(
                  fs.readFileSync(fullResourcePath), {
                    bom: true, // strip the byte order mark (BOM) from the input string or buffer.
                    columns: (
                      hasNoHeader
                        ? false // if noHeader is present, first row is not header row
                        : header => header.map((col: string) => col)
                    ),
                  });
                const resourceRaw = JSON.stringify(csvResourceRaw);
                buffer.push(`{% set ${variableName} = ${resourceRaw} %}`);
              }
              this.emitLoad(fullResourcePath);
            });
          });
        } else E{
          logger.error(`Invalid {% ext %} tag at line ${setExtTagToken.lineno}.`);
          return new nodes.NodeList(setExtTagToken.lineno, setExtTagToken.colno, []);
        }

        const newParser = new Parser(lex(buffer.join('\n'), lexer.opts));
        if (parser.extensions !== undefined) {
          newParser.extensions = parser.extensions;
        }

        return newParser.parse();
      }
    }