l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

handlebars.js (6490B)


      1 /**
      2  * @param {string} value
      3  * @returns {RegExp}
      4  * */
      5 
      6 /**
      7  * @param {RegExp | string } re
      8  * @returns {string}
      9  */
     10 function source(re) {
     11   if (!re) return null;
     12   if (typeof re === "string") return re;
     13 
     14   return re.source;
     15 }
     16 
     17 /**
     18  * @param {...(RegExp | string) } args
     19  * @returns {string}
     20  */
     21 function concat(...args) {
     22   const joined = args.map((x) => source(x)).join("");
     23   return joined;
     24 }
     25 
     26 /*
     27 Language: Handlebars
     28 Requires: xml.js
     29 Author: Robin Ward <robin.ward@gmail.com>
     30 Description: Matcher for Handlebars as well as EmberJS additions.
     31 Website: https://handlebarsjs.com
     32 Category: template
     33 */
     34 
     35 function handlebars(hljs) {
     36   const BUILT_INS = {
     37     'builtin-name': [
     38       'action',
     39       'bindattr',
     40       'collection',
     41       'component',
     42       'concat',
     43       'debugger',
     44       'each',
     45       'each-in',
     46       'get',
     47       'hash',
     48       'if',
     49       'in',
     50       'input',
     51       'link-to',
     52       'loc',
     53       'log',
     54       'lookup',
     55       'mut',
     56       'outlet',
     57       'partial',
     58       'query-params',
     59       'render',
     60       'template',
     61       'textarea',
     62       'unbound',
     63       'unless',
     64       'view',
     65       'with',
     66       'yield'
     67     ].join(" ")
     68   };
     69 
     70   const LITERALS = {
     71     literal: [
     72       'true',
     73       'false',
     74       'undefined',
     75       'null'
     76     ].join(" ")
     77   };
     78 
     79   // as defined in https://handlebarsjs.com/guide/expressions.html#literal-segments
     80   // this regex matches literal segments like ' abc ' or [ abc ] as well as helpers and paths
     81   // like a/b, ./abc/cde, and abc.bcd
     82 
     83   const DOUBLE_QUOTED_ID_REGEX=/".*?"/;
     84   const SINGLE_QUOTED_ID_REGEX=/'.*?'/;
     85   const BRACKET_QUOTED_ID_REGEX=/\[.*?\]/;
     86   const PLAIN_ID_REGEX=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/;
     87   const PATH_DELIMITER_REGEX=/\.|\//;
     88 
     89   const IDENTIFIER_REGEX = concat(
     90     '(',
     91     SINGLE_QUOTED_ID_REGEX, '|',
     92     DOUBLE_QUOTED_ID_REGEX, '|',
     93     BRACKET_QUOTED_ID_REGEX, '|',
     94     PLAIN_ID_REGEX, '|',
     95     PATH_DELIMITER_REGEX,
     96     ')+'
     97   );
     98 
     99   // identifier followed by a equal-sign (without the equal sign)
    100   const HASH_PARAM_REGEX = concat(
    101     '(',
    102     BRACKET_QUOTED_ID_REGEX, '|',
    103     PLAIN_ID_REGEX,
    104     ')(?==)'
    105   );
    106 
    107   const HELPER_NAME_OR_PATH_EXPRESSION = {
    108     begin: IDENTIFIER_REGEX,
    109     lexemes: /[\w.\/]+/
    110   };
    111 
    112   const HELPER_PARAMETER = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
    113     keywords: LITERALS
    114   });
    115 
    116   const SUB_EXPRESSION = {
    117     begin: /\(/,
    118     end: /\)/
    119     // the "contains" is added below when all necessary sub-modes are defined
    120   };
    121 
    122   const HASH = {
    123     // fka "attribute-assignment", parameters of the form 'key=value'
    124     className: 'attr',
    125     begin: HASH_PARAM_REGEX,
    126     relevance: 0,
    127     starts: {
    128       begin: /=/,
    129       end: /=/,
    130       starts: {
    131         contains: [
    132           hljs.NUMBER_MODE,
    133           hljs.QUOTE_STRING_MODE,
    134           hljs.APOS_STRING_MODE,
    135           HELPER_PARAMETER,
    136           SUB_EXPRESSION
    137         ]
    138       }
    139     }
    140   };
    141 
    142   const BLOCK_PARAMS = {
    143     // parameters of the form '{{#with x as | y |}}...{{/with}}'
    144     begin: /as\s+\|/,
    145     keywords: { keyword: 'as' },
    146     end: /\|/,
    147     contains: [
    148       {
    149         // define sub-mode in order to prevent highlighting of block-parameter named "as"
    150         begin: /\w+/
    151       }
    152     ]
    153   };
    154 
    155   const HELPER_PARAMETERS = {
    156     contains: [
    157       hljs.NUMBER_MODE,
    158       hljs.QUOTE_STRING_MODE,
    159       hljs.APOS_STRING_MODE,
    160       BLOCK_PARAMS,
    161       HASH,
    162       HELPER_PARAMETER,
    163       SUB_EXPRESSION
    164     ],
    165     returnEnd: true
    166     // the property "end" is defined through inheritance when the mode is used. If depends
    167     // on the surrounding mode, but "endsWithParent" does not work here (i.e. it includes the
    168     // end-token of the surrounding mode)
    169   };
    170 
    171   const SUB_EXPRESSION_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
    172     className: 'name',
    173     keywords: BUILT_INS,
    174     starts: hljs.inherit(HELPER_PARAMETERS, {
    175       end: /\)/,
    176     })
    177   });
    178 
    179   SUB_EXPRESSION.contains = [
    180     SUB_EXPRESSION_CONTENTS
    181   ];
    182 
    183   const OPENING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
    184     keywords: BUILT_INS,
    185     className: 'name',
    186     starts: hljs.inherit(HELPER_PARAMETERS, {
    187       end: /}}/,
    188     })
    189   });
    190 
    191   const CLOSING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
    192     keywords: BUILT_INS,
    193     className: 'name'
    194   });
    195 
    196   const BASIC_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
    197     className: 'name',
    198     keywords: BUILT_INS,
    199     starts: hljs.inherit(HELPER_PARAMETERS, {
    200       end: /}}/,
    201     })
    202   });
    203 
    204   const ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH = {begin: /\\\{\{/, skip: true};
    205   const PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH = {begin: /\\\\(?=\{\{)/, skip: true};
    206 
    207   return {
    208     name: 'Handlebars',
    209     aliases: ['hbs', 'html.hbs', 'html.handlebars', 'htmlbars'],
    210     case_insensitive: true,
    211     subLanguage: 'xml',
    212     contains: [
    213       ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH,
    214       PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH,
    215       hljs.COMMENT(/\{\{!--/, /--\}\}/),
    216       hljs.COMMENT(/\{\{!/, /\}\}/),
    217       {
    218         // open raw block "{{{{raw}}}} content not evaluated {{{{/raw}}}}"
    219         className: 'template-tag',
    220         begin: /\{\{\{\{(?!\/)/,
    221         end: /\}\}\}\}/,
    222         contains: [OPENING_BLOCK_MUSTACHE_CONTENTS],
    223         starts: {end: /\{\{\{\{\//, returnEnd: true, subLanguage: 'xml'}
    224       },
    225       {
    226         // close raw block
    227         className: 'template-tag',
    228         begin: /\{\{\{\{\//,
    229         end: /\}\}\}\}/,
    230         contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS]
    231       },
    232       {
    233         // open block statement
    234         className: 'template-tag',
    235         begin: /\{\{#/,
    236         end: /\}\}/,
    237         contains: [OPENING_BLOCK_MUSTACHE_CONTENTS],
    238       },
    239       {
    240         className: 'template-tag',
    241         begin: /\{\{(?=else\}\})/,
    242         end: /\}\}/,
    243         keywords: 'else'
    244       },
    245       {
    246         // closing block statement
    247         className: 'template-tag',
    248         begin: /\{\{\//,
    249         end: /\}\}/,
    250         contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS],
    251       },
    252       {
    253         // template variable or helper-call that is NOT html-escaped
    254         className: 'template-variable',
    255         begin: /\{\{\{/,
    256         end: /\}\}\}/,
    257         contains: [BASIC_MUSTACHE_CONTENTS]
    258       },
    259       {
    260         // template variable or helper-call that is html-escaped
    261         className: 'template-variable',
    262         begin: /\{\{/,
    263         end: /\}\}/,
    264         contains: [BASIC_MUSTACHE_CONTENTS]
    265       }
    266     ]
    267   };
    268 }
    269 
    270 module.exports = handlebars;