You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
8.1 KiB
352 lines
8.1 KiB
/** |
|
* @param {string} value |
|
* @returns {RegExp} |
|
* */ |
|
|
|
/** |
|
* @param {RegExp | string } re |
|
* @returns {string} |
|
*/ |
|
function source(re) { |
|
if (!re) return null; |
|
if (typeof re === "string") return re; |
|
|
|
return re.source; |
|
} |
|
|
|
/** |
|
* @param {RegExp | string } re |
|
* @returns {string} |
|
*/ |
|
function anyNumberOfTimes(re) { |
|
return concat('(', re, ')*'); |
|
} |
|
|
|
/** |
|
* @param {RegExp | string } re |
|
* @returns {string} |
|
*/ |
|
function optional(re) { |
|
return concat('(', re, ')?'); |
|
} |
|
|
|
/** |
|
* @param {...(RegExp | string) } args |
|
* @returns {string} |
|
*/ |
|
function concat(...args) { |
|
const joined = args.map((x) => source(x)).join(""); |
|
return joined; |
|
} |
|
|
|
/** |
|
* Any of the passed expresssions may match |
|
* |
|
* Creates a huge this | this | that | that match |
|
* @param {(RegExp | string)[] } args |
|
* @returns {string} |
|
*/ |
|
function either(...args) { |
|
const joined = '(' + args.map((x) => source(x)).join("|") + ")"; |
|
return joined; |
|
} |
|
|
|
/* |
|
Language: Handlebars |
|
Requires: xml.js |
|
Author: Robin Ward <robin.ward@gmail.com> |
|
Description: Matcher for Handlebars as well as EmberJS additions. |
|
Website: https://handlebarsjs.com |
|
Category: template |
|
*/ |
|
|
|
function handlebars(hljs) { |
|
const BUILT_INS = { |
|
'builtin-name': [ |
|
'action', |
|
'bindattr', |
|
'collection', |
|
'component', |
|
'concat', |
|
'debugger', |
|
'each', |
|
'each-in', |
|
'get', |
|
'hash', |
|
'if', |
|
'in', |
|
'input', |
|
'link-to', |
|
'loc', |
|
'log', |
|
'lookup', |
|
'mut', |
|
'outlet', |
|
'partial', |
|
'query-params', |
|
'render', |
|
'template', |
|
'textarea', |
|
'unbound', |
|
'unless', |
|
'view', |
|
'with', |
|
'yield' |
|
] |
|
}; |
|
|
|
const LITERALS = { |
|
literal: [ |
|
'true', |
|
'false', |
|
'undefined', |
|
'null' |
|
] |
|
}; |
|
|
|
// as defined in https://handlebarsjs.com/guide/expressions.html#literal-segments |
|
// this regex matches literal segments like ' abc ' or [ abc ] as well as helpers and paths |
|
// like a/b, ./abc/cde, and abc.bcd |
|
|
|
const DOUBLE_QUOTED_ID_REGEX = /""|"[^"]+"/; |
|
const SINGLE_QUOTED_ID_REGEX = /''|'[^']+'/; |
|
const BRACKET_QUOTED_ID_REGEX = /\[\]|\[[^\]]+\]/; |
|
const PLAIN_ID_REGEX = /[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/; |
|
const PATH_DELIMITER_REGEX = /(\.|\/)/; |
|
const ANY_ID = either( |
|
DOUBLE_QUOTED_ID_REGEX, |
|
SINGLE_QUOTED_ID_REGEX, |
|
BRACKET_QUOTED_ID_REGEX, |
|
PLAIN_ID_REGEX |
|
); |
|
|
|
const IDENTIFIER_REGEX = concat( |
|
optional(/\.|\.\/|\//), // relative or absolute path |
|
ANY_ID, |
|
anyNumberOfTimes(concat( |
|
PATH_DELIMITER_REGEX, |
|
ANY_ID |
|
)) |
|
); |
|
|
|
// identifier followed by a equal-sign (without the equal sign) |
|
const HASH_PARAM_REGEX = concat( |
|
'(', |
|
BRACKET_QUOTED_ID_REGEX, '|', |
|
PLAIN_ID_REGEX, |
|
')(?==)' |
|
); |
|
|
|
const HELPER_NAME_OR_PATH_EXPRESSION = { |
|
begin: IDENTIFIER_REGEX, |
|
lexemes: /[\w.\/]+/ |
|
}; |
|
|
|
const HELPER_PARAMETER = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, { |
|
keywords: LITERALS |
|
}); |
|
|
|
const SUB_EXPRESSION = { |
|
begin: /\(/, |
|
end: /\)/ |
|
// the "contains" is added below when all necessary sub-modes are defined |
|
}; |
|
|
|
const HASH = { |
|
// fka "attribute-assignment", parameters of the form 'key=value' |
|
className: 'attr', |
|
begin: HASH_PARAM_REGEX, |
|
relevance: 0, |
|
starts: { |
|
begin: /=/, |
|
end: /=/, |
|
starts: { |
|
contains: [ |
|
hljs.NUMBER_MODE, |
|
hljs.QUOTE_STRING_MODE, |
|
hljs.APOS_STRING_MODE, |
|
HELPER_PARAMETER, |
|
SUB_EXPRESSION |
|
] |
|
} |
|
} |
|
}; |
|
|
|
const BLOCK_PARAMS = { |
|
// parameters of the form '{{#with x as | y |}}...{{/with}}' |
|
begin: /as\s+\|/, |
|
keywords: { |
|
keyword: 'as' |
|
}, |
|
end: /\|/, |
|
contains: [ |
|
{ |
|
// define sub-mode in order to prevent highlighting of block-parameter named "as" |
|
begin: /\w+/ |
|
} |
|
] |
|
}; |
|
|
|
const HELPER_PARAMETERS = { |
|
contains: [ |
|
hljs.NUMBER_MODE, |
|
hljs.QUOTE_STRING_MODE, |
|
hljs.APOS_STRING_MODE, |
|
BLOCK_PARAMS, |
|
HASH, |
|
HELPER_PARAMETER, |
|
SUB_EXPRESSION |
|
], |
|
returnEnd: true |
|
// the property "end" is defined through inheritance when the mode is used. If depends |
|
// on the surrounding mode, but "endsWithParent" does not work here (i.e. it includes the |
|
// end-token of the surrounding mode) |
|
}; |
|
|
|
const SUB_EXPRESSION_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, { |
|
className: 'name', |
|
keywords: BUILT_INS, |
|
starts: hljs.inherit(HELPER_PARAMETERS, { |
|
end: /\)/ |
|
}) |
|
}); |
|
|
|
SUB_EXPRESSION.contains = [SUB_EXPRESSION_CONTENTS]; |
|
|
|
const OPENING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, { |
|
keywords: BUILT_INS, |
|
className: 'name', |
|
starts: hljs.inherit(HELPER_PARAMETERS, { |
|
end: /\}\}/ |
|
}) |
|
}); |
|
|
|
const CLOSING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, { |
|
keywords: BUILT_INS, |
|
className: 'name' |
|
}); |
|
|
|
const BASIC_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, { |
|
className: 'name', |
|
keywords: BUILT_INS, |
|
starts: hljs.inherit(HELPER_PARAMETERS, { |
|
end: /\}\}/ |
|
}) |
|
}); |
|
|
|
const ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH = { |
|
begin: /\\\{\{/, |
|
skip: true |
|
}; |
|
const PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH = { |
|
begin: /\\\\(?=\{\{)/, |
|
skip: true |
|
}; |
|
|
|
return { |
|
name: 'Handlebars', |
|
aliases: [ |
|
'hbs', |
|
'html.hbs', |
|
'html.handlebars', |
|
'htmlbars' |
|
], |
|
case_insensitive: true, |
|
subLanguage: 'xml', |
|
contains: [ |
|
ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH, |
|
PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH, |
|
hljs.COMMENT(/\{\{!--/, /--\}\}/), |
|
hljs.COMMENT(/\{\{!/, /\}\}/), |
|
{ |
|
// open raw block "{{{{raw}}}} content not evaluated {{{{/raw}}}}" |
|
className: 'template-tag', |
|
begin: /\{\{\{\{(?!\/)/, |
|
end: /\}\}\}\}/, |
|
contains: [OPENING_BLOCK_MUSTACHE_CONTENTS], |
|
starts: { |
|
end: /\{\{\{\{\//, |
|
returnEnd: true, |
|
subLanguage: 'xml' |
|
} |
|
}, |
|
{ |
|
// close raw block |
|
className: 'template-tag', |
|
begin: /\{\{\{\{\//, |
|
end: /\}\}\}\}/, |
|
contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS] |
|
}, |
|
{ |
|
// open block statement |
|
className: 'template-tag', |
|
begin: /\{\{#/, |
|
end: /\}\}/, |
|
contains: [OPENING_BLOCK_MUSTACHE_CONTENTS] |
|
}, |
|
{ |
|
className: 'template-tag', |
|
begin: /\{\{(?=else\}\})/, |
|
end: /\}\}/, |
|
keywords: 'else' |
|
}, |
|
{ |
|
className: 'template-tag', |
|
begin: /\{\{(?=else if)/, |
|
end: /\}\}/, |
|
keywords: 'else if' |
|
}, |
|
{ |
|
// closing block statement |
|
className: 'template-tag', |
|
begin: /\{\{\//, |
|
end: /\}\}/, |
|
contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS] |
|
}, |
|
{ |
|
// template variable or helper-call that is NOT html-escaped |
|
className: 'template-variable', |
|
begin: /\{\{\{/, |
|
end: /\}\}\}/, |
|
contains: [BASIC_MUSTACHE_CONTENTS] |
|
}, |
|
{ |
|
// template variable or helper-call that is html-escaped |
|
className: 'template-variable', |
|
begin: /\{\{/, |
|
end: /\}\}/, |
|
contains: [BASIC_MUSTACHE_CONTENTS] |
|
} |
|
] |
|
}; |
|
} |
|
|
|
/* |
|
Language: HTMLBars (legacy) |
|
Requires: xml.js |
|
Description: Matcher for Handlebars as well as EmberJS additions. |
|
Website: https://github.com/tildeio/htmlbars |
|
Category: template |
|
*/ |
|
|
|
function htmlbars(hljs) { |
|
const definition = handlebars(hljs); |
|
|
|
definition.name = "HTMLbars"; |
|
|
|
// HACK: This lets handlebars do the auto-detection if it's been loaded (by |
|
// default the build script will load in alphabetical order) and if not (perhaps |
|
// an install is only using `htmlbars`, not `handlebars`) then this will still |
|
// allow HTMLBars to participate in the auto-detection |
|
|
|
// worse case someone will have HTMLbars and handlebars competing for the same |
|
// content and will need to change their setup to only require handlebars, but |
|
// I don't consider this a breaking change |
|
if (hljs.getLanguage("handlebars")) { |
|
definition.disableAutodetect = true; |
|
} |
|
|
|
return definition; |
|
} |
|
|
|
module.exports = htmlbars;
|
|
|