From 9c71dc8a2327002390a5df4011e91bbdfb13cab2 Mon Sep 17 00:00:00 2001 From: j3lte Date: Fri, 24 Nov 2023 20:36:10 +0100 Subject: [PATCH] First setup --- .github/ISSUE_TEMPLATE/bug_report.md | 33 ++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++ .github/workflows/npm.yml | 35 ++ .github/workflows/release.yml | 34 ++ .github/workflows/test.yml | 42 +++ .github/workflows/version.yml | 35 ++ .gitignore | 9 + LICENSE | 21 ++ README.md | 18 ++ deno.json | 50 +++ deno.lock | 204 ++++++++++++ dev_deps.ts | 18 ++ mod.ts | 6 + scripts/build-npm.ts | 71 ++++ scripts/check-license.ts | 60 ++++ scripts/update-version.ts | 29 ++ scripts/watch-test.ts | 67 ++++ src/lib/Context.ts | 130 ++++++++ src/lib/Mustache.ts | 67 ++++ src/lib/Scanner.ts | 80 +++++ src/lib/Writer.ts | 281 ++++++++++++++++ src/lib/parse.ts | 268 +++++++++++++++ src/lib/util.ts | 80 +++++ src/mod.ts | 8 + test/Context.test.ts | 76 +++++ test/Scanner.test.ts | 90 ++++++ test/helpers/_files/ampersand_escape.js | 3 + test/helpers/_files/ampersand_escape.mustache | 1 + test/helpers/_files/ampersand_escape.txt | 1 + test/helpers/_files/apostrophe.js | 4 + test/helpers/_files/apostrophe.mustache | 1 + test/helpers/_files/apostrophe.txt | 1 + test/helpers/_files/array_of_strings.js | 3 + test/helpers/_files/array_of_strings.mustache | 1 + test/helpers/_files/array_of_strings.txt | 1 + .../avoids_obj_prototype_in_view_cache.js | 4 + ...voids_obj_prototype_in_view_cache.mustache | 1 + .../avoids_obj_prototype_in_view_cache.txt | 1 + test/helpers/_files/backslashes.js | 3 + test/helpers/_files/backslashes.mustache | 7 + test/helpers/_files/backslashes.txt | 7 + .../_files/bug_11_eating_whitespace.js | 3 + .../_files/bug_11_eating_whitespace.mustache | 1 + .../_files/bug_11_eating_whitespace.txt | 1 + test/helpers/_files/bug_length_property.js | 3 + .../_files/bug_length_property.mustache | 1 + test/helpers/_files/bug_length_property.txt | 1 + test/helpers/_files/changing_delimiters.js | 4 + .../_files/changing_delimiters.mustache | 1 + test/helpers/_files/changing_delimiters.txt | 1 + test/helpers/_files/check_falsy.js | 7 + test/helpers/_files/check_falsy.mustache | 1 + test/helpers/_files/check_falsy.txt | 1 + test/helpers/_files/comments.js | 5 + test/helpers/_files/comments.mustache | 1 + test/helpers/_files/comments.txt | 1 + test/helpers/_files/complex.js | 19 ++ test/helpers/_files/complex.mustache | 16 + test/helpers/_files/complex.txt | 6 + test/helpers/_files/context_lookup.js | 8 + test/helpers/_files/context_lookup.mustache | 1 + test/helpers/_files/context_lookup.txt | 1 + test/helpers/_files/delimiters.js | 6 + test/helpers/_files/delimiters.mustache | 7 + test/helpers/_files/delimiters.txt | 5 + .../helpers/_files/disappearing_whitespace.js | 4 + .../_files/disappearing_whitespace.mustache | 1 + .../_files/disappearing_whitespace.txt | 1 + test/helpers/_files/dot_notation.js | 24 ++ test/helpers/_files/dot_notation.mustache | 12 + test/helpers/_files/dot_notation.txt | 12 + test/helpers/_files/double_render.js | 5 + test/helpers/_files/double_render.mustache | 1 + test/helpers/_files/double_render.txt | 1 + test/helpers/_files/empty_list.js | 3 + test/helpers/_files/empty_list.mustache | 4 + test/helpers/_files/empty_list.txt | 1 + test/helpers/_files/empty_sections.js | 1 + test/helpers/_files/empty_sections.mustache | 1 + test/helpers/_files/empty_sections.txt | 1 + test/helpers/_files/empty_string.js | 6 + test/helpers/_files/empty_string.mustache | 1 + test/helpers/_files/empty_string.txt | 1 + test/helpers/_files/empty_template.js | 1 + test/helpers/_files/empty_template.mustache | 1 + test/helpers/_files/empty_template.txt | 1 + test/helpers/_files/error_not_found.js | 3 + test/helpers/_files/error_not_found.mustache | 1 + test/helpers/_files/error_not_found.txt | 0 test/helpers/_files/escaped.js | 7 + test/helpers/_files/escaped.mustache | 2 + test/helpers/_files/escaped.txt | 2 + test/helpers/_files/falsy.js | 8 + test/helpers/_files/falsy.mustache | 12 + test/helpers/_files/falsy.txt | 12 + test/helpers/_files/falsy_array.js | 10 + test/helpers/_files/falsy_array.mustache | 3 + test/helpers/_files/falsy_array.txt | 6 + test/helpers/_files/grandparent_context.js | 19 ++ .../_files/grandparent_context.mustache | 10 + test/helpers/_files/grandparent_context.txt | 17 + test/helpers/_files/higher_order_sections.js | 9 + .../_files/higher_order_sections.mustache | 1 + test/helpers/_files/higher_order_sections.txt | 1 + test/helpers/_files/implicit_iterator.js | 8 + .../helpers/_files/implicit_iterator.mustache | 7 + test/helpers/_files/implicit_iterator.txt | 3 + test/helpers/_files/included_tag.js | 3 + test/helpers/_files/included_tag.mustache | 1 + test/helpers/_files/included_tag.txt | 1 + test/helpers/_files/inverted_section.js | 3 + test/helpers/_files/inverted_section.mustache | 3 + test/helpers/_files/inverted_section.txt | 3 + .../helpers/_files/keys_with_questionmarks.js | 5 + .../_files/keys_with_questionmarks.mustache | 3 + .../_files/keys_with_questionmarks.txt | 1 + test/helpers/_files/malicious_template.js | 1 + .../_files/malicious_template.mustache | 5 + test/helpers/_files/malicious_template.txt | 2 + test/helpers/_files/multiline_comment.js | 1 + .../helpers/_files/multiline_comment.mustache | 6 + test/helpers/_files/multiline_comment.txt | 1 + test/helpers/_files/nested_dot.js | 1 + test/helpers/_files/nested_dot.mustache | 1 + test/helpers/_files/nested_dot.txt | 1 + .../_files/nested_higher_order_sections.js | 8 + .../nested_higher_order_sections.mustache | 1 + .../_files/nested_higher_order_sections.txt | 1 + test/helpers/_files/nested_iterating.js | 8 + test/helpers/_files/nested_iterating.mustache | 1 + test/helpers/_files/nested_iterating.txt | 1 + test/helpers/_files/nesting.js | 7 + test/helpers/_files/nesting.mustache | 5 + test/helpers/_files/nesting.txt | 3 + test/helpers/_files/nesting_same_name.js | 8 + .../helpers/_files/nesting_same_name.mustache | 1 + test/helpers/_files/nesting_same_name.txt | 1 + test/helpers/_files/null_lookup_array.js | 9 + .../helpers/_files/null_lookup_array.mustache | 3 + test/helpers/_files/null_lookup_array.txt | 3 + test/helpers/_files/null_lookup_object.js | 31 ++ .../_files/null_lookup_object.mustache | 9 + test/helpers/_files/null_lookup_object.txt | 7 + test/helpers/_files/null_string.js | 10 + test/helpers/_files/null_string.mustache | 6 + test/helpers/_files/null_string.txt | 6 + test/helpers/_files/null_view.js | 4 + test/helpers/_files/null_view.mustache | 1 + test/helpers/_files/null_view.txt | 1 + test/helpers/_files/partial_array.js | 3 + test/helpers/_files/partial_array.mustache | 1 + test/helpers/_files/partial_array.partial | 4 + test/helpers/_files/partial_array.txt | 5 + .../_files/partial_array_of_partials.js | 8 + .../_files/partial_array_of_partials.mustache | 4 + .../_files/partial_array_of_partials.partial | 1 + .../_files/partial_array_of_partials.txt | 5 + .../partial_array_of_partials_implicit.js | 3 + ...artial_array_of_partials_implicit.mustache | 4 + ...partial_array_of_partials_implicit.partial | 1 + .../partial_array_of_partials_implicit.txt | 5 + test/helpers/_files/partial_empty.js | 3 + test/helpers/_files/partial_empty.mustache | 2 + test/helpers/_files/partial_empty.partial | 0 test/helpers/_files/partial_empty.txt | 1 + test/helpers/_files/partial_template.js | 6 + test/helpers/_files/partial_template.mustache | 2 + test/helpers/_files/partial_template.partial | 1 + test/helpers/_files/partial_template.txt | 2 + test/helpers/_files/partial_view.js | 14 + test/helpers/_files/partial_view.mustache | 3 + test/helpers/_files/partial_view.partial | 5 + test/helpers/_files/partial_view.txt | 5 + test/helpers/_files/partial_whitespace.js | 14 + .../_files/partial_whitespace.mustache | 3 + .../helpers/_files/partial_whitespace.partial | 5 + test/helpers/_files/partial_whitespace.txt | 5 + .../_files/recursion_with_same_names.js | 8 + .../_files/recursion_with_same_names.mustache | 7 + .../_files/recursion_with_same_names.txt | 7 + test/helpers/_files/reuse_of_enumerables.js | 6 + .../_files/reuse_of_enumerables.mustache | 8 + test/helpers/_files/reuse_of_enumerables.txt | 8 + test/helpers/_files/section_as_context.js | 10 + .../_files/section_as_context.mustache | 9 + test/helpers/_files/section_as_context.txt | 6 + .../_files/section_functions_in_partials.js | 7 + .../section_functions_in_partials.mustache | 3 + .../section_functions_in_partials.partial | 1 + .../_files/section_functions_in_partials.txt | 3 + test/helpers/_files/simple.js | 8 + test/helpers/_files/simple.mustache | 5 + test/helpers/_files/simple.txt | 3 + test/helpers/_files/string_as_context.js | 4 + .../helpers/_files/string_as_context.mustache | 5 + test/helpers/_files/string_as_context.txt | 5 + test/helpers/_files/two_in_a_row.js | 4 + test/helpers/_files/two_in_a_row.mustache | 1 + test/helpers/_files/two_in_a_row.txt | 1 + test/helpers/_files/two_sections.js | 1 + test/helpers/_files/two_sections.mustache | 4 + test/helpers/_files/two_sections.txt | 0 test/helpers/_files/unescaped.js | 6 + test/helpers/_files/unescaped.mustache | 1 + test/helpers/_files/unescaped.txt | 1 + .../_files/uses_props_from_view_prototype.js | 30 ++ .../uses_props_from_view_prototype.mustache | 1 + .../_files/uses_props_from_view_prototype.txt | 1 + test/helpers/_files/whitespace.js | 4 + test/helpers/_files/whitespace.mustache | 4 + test/helpers/_files/whitespace.txt | 4 + test/helpers/_files/zero_view.js | 1 + test/helpers/_files/zero_view.mustache | 1 + test/helpers/_files/zero_view.txt | 1 + test/helpers/render-helper.ts | 61 ++++ test/parse.test.ts | 188 +++++++++++ test/partial.test.ts | 175 ++++++++++ test/render.test.ts | 305 ++++++++++++++++++ 218 files changed, 3396 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/npm.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .github/workflows/version.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 dev_deps.ts create mode 100644 mod.ts create mode 100644 scripts/build-npm.ts create mode 100644 scripts/check-license.ts create mode 100644 scripts/update-version.ts create mode 100644 scripts/watch-test.ts create mode 100644 src/lib/Context.ts create mode 100644 src/lib/Mustache.ts create mode 100644 src/lib/Scanner.ts create mode 100644 src/lib/Writer.ts create mode 100644 src/lib/parse.ts create mode 100644 src/lib/util.ts create mode 100644 src/mod.ts create mode 100644 test/Context.test.ts create mode 100644 test/Scanner.test.ts create mode 100644 test/helpers/_files/ampersand_escape.js create mode 100644 test/helpers/_files/ampersand_escape.mustache create mode 100644 test/helpers/_files/ampersand_escape.txt create mode 100644 test/helpers/_files/apostrophe.js create mode 100644 test/helpers/_files/apostrophe.mustache create mode 100644 test/helpers/_files/apostrophe.txt create mode 100644 test/helpers/_files/array_of_strings.js create mode 100644 test/helpers/_files/array_of_strings.mustache create mode 100644 test/helpers/_files/array_of_strings.txt create mode 100644 test/helpers/_files/avoids_obj_prototype_in_view_cache.js create mode 100644 test/helpers/_files/avoids_obj_prototype_in_view_cache.mustache create mode 100644 test/helpers/_files/avoids_obj_prototype_in_view_cache.txt create mode 100644 test/helpers/_files/backslashes.js create mode 100644 test/helpers/_files/backslashes.mustache create mode 100644 test/helpers/_files/backslashes.txt create mode 100644 test/helpers/_files/bug_11_eating_whitespace.js create mode 100644 test/helpers/_files/bug_11_eating_whitespace.mustache create mode 100644 test/helpers/_files/bug_11_eating_whitespace.txt create mode 100644 test/helpers/_files/bug_length_property.js create mode 100644 test/helpers/_files/bug_length_property.mustache create mode 100644 test/helpers/_files/bug_length_property.txt create mode 100644 test/helpers/_files/changing_delimiters.js create mode 100644 test/helpers/_files/changing_delimiters.mustache create mode 100644 test/helpers/_files/changing_delimiters.txt create mode 100644 test/helpers/_files/check_falsy.js create mode 100644 test/helpers/_files/check_falsy.mustache create mode 100644 test/helpers/_files/check_falsy.txt create mode 100644 test/helpers/_files/comments.js create mode 100644 test/helpers/_files/comments.mustache create mode 100644 test/helpers/_files/comments.txt create mode 100644 test/helpers/_files/complex.js create mode 100644 test/helpers/_files/complex.mustache create mode 100644 test/helpers/_files/complex.txt create mode 100644 test/helpers/_files/context_lookup.js create mode 100644 test/helpers/_files/context_lookup.mustache create mode 100644 test/helpers/_files/context_lookup.txt create mode 100644 test/helpers/_files/delimiters.js create mode 100644 test/helpers/_files/delimiters.mustache create mode 100644 test/helpers/_files/delimiters.txt create mode 100644 test/helpers/_files/disappearing_whitespace.js create mode 100644 test/helpers/_files/disappearing_whitespace.mustache create mode 100644 test/helpers/_files/disappearing_whitespace.txt create mode 100644 test/helpers/_files/dot_notation.js create mode 100644 test/helpers/_files/dot_notation.mustache create mode 100644 test/helpers/_files/dot_notation.txt create mode 100644 test/helpers/_files/double_render.js create mode 100644 test/helpers/_files/double_render.mustache create mode 100644 test/helpers/_files/double_render.txt create mode 100644 test/helpers/_files/empty_list.js create mode 100644 test/helpers/_files/empty_list.mustache create mode 100644 test/helpers/_files/empty_list.txt create mode 100644 test/helpers/_files/empty_sections.js create mode 100644 test/helpers/_files/empty_sections.mustache create mode 100644 test/helpers/_files/empty_sections.txt create mode 100644 test/helpers/_files/empty_string.js create mode 100644 test/helpers/_files/empty_string.mustache create mode 100644 test/helpers/_files/empty_string.txt create mode 100644 test/helpers/_files/empty_template.js create mode 100644 test/helpers/_files/empty_template.mustache create mode 100644 test/helpers/_files/empty_template.txt create mode 100644 test/helpers/_files/error_not_found.js create mode 100644 test/helpers/_files/error_not_found.mustache create mode 100644 test/helpers/_files/error_not_found.txt create mode 100644 test/helpers/_files/escaped.js create mode 100644 test/helpers/_files/escaped.mustache create mode 100644 test/helpers/_files/escaped.txt create mode 100644 test/helpers/_files/falsy.js create mode 100644 test/helpers/_files/falsy.mustache create mode 100644 test/helpers/_files/falsy.txt create mode 100644 test/helpers/_files/falsy_array.js create mode 100644 test/helpers/_files/falsy_array.mustache create mode 100644 test/helpers/_files/falsy_array.txt create mode 100644 test/helpers/_files/grandparent_context.js create mode 100644 test/helpers/_files/grandparent_context.mustache create mode 100644 test/helpers/_files/grandparent_context.txt create mode 100644 test/helpers/_files/higher_order_sections.js create mode 100644 test/helpers/_files/higher_order_sections.mustache create mode 100644 test/helpers/_files/higher_order_sections.txt create mode 100644 test/helpers/_files/implicit_iterator.js create mode 100644 test/helpers/_files/implicit_iterator.mustache create mode 100644 test/helpers/_files/implicit_iterator.txt create mode 100644 test/helpers/_files/included_tag.js create mode 100644 test/helpers/_files/included_tag.mustache create mode 100644 test/helpers/_files/included_tag.txt create mode 100644 test/helpers/_files/inverted_section.js create mode 100644 test/helpers/_files/inverted_section.mustache create mode 100644 test/helpers/_files/inverted_section.txt create mode 100644 test/helpers/_files/keys_with_questionmarks.js create mode 100644 test/helpers/_files/keys_with_questionmarks.mustache create mode 100644 test/helpers/_files/keys_with_questionmarks.txt create mode 100644 test/helpers/_files/malicious_template.js create mode 100644 test/helpers/_files/malicious_template.mustache create mode 100644 test/helpers/_files/malicious_template.txt create mode 100644 test/helpers/_files/multiline_comment.js create mode 100644 test/helpers/_files/multiline_comment.mustache create mode 100644 test/helpers/_files/multiline_comment.txt create mode 100644 test/helpers/_files/nested_dot.js create mode 100644 test/helpers/_files/nested_dot.mustache create mode 100644 test/helpers/_files/nested_dot.txt create mode 100644 test/helpers/_files/nested_higher_order_sections.js create mode 100644 test/helpers/_files/nested_higher_order_sections.mustache create mode 100644 test/helpers/_files/nested_higher_order_sections.txt create mode 100644 test/helpers/_files/nested_iterating.js create mode 100644 test/helpers/_files/nested_iterating.mustache create mode 100644 test/helpers/_files/nested_iterating.txt create mode 100644 test/helpers/_files/nesting.js create mode 100644 test/helpers/_files/nesting.mustache create mode 100644 test/helpers/_files/nesting.txt create mode 100644 test/helpers/_files/nesting_same_name.js create mode 100644 test/helpers/_files/nesting_same_name.mustache create mode 100644 test/helpers/_files/nesting_same_name.txt create mode 100644 test/helpers/_files/null_lookup_array.js create mode 100644 test/helpers/_files/null_lookup_array.mustache create mode 100644 test/helpers/_files/null_lookup_array.txt create mode 100644 test/helpers/_files/null_lookup_object.js create mode 100644 test/helpers/_files/null_lookup_object.mustache create mode 100644 test/helpers/_files/null_lookup_object.txt create mode 100644 test/helpers/_files/null_string.js create mode 100644 test/helpers/_files/null_string.mustache create mode 100644 test/helpers/_files/null_string.txt create mode 100644 test/helpers/_files/null_view.js create mode 100644 test/helpers/_files/null_view.mustache create mode 100644 test/helpers/_files/null_view.txt create mode 100644 test/helpers/_files/partial_array.js create mode 100644 test/helpers/_files/partial_array.mustache create mode 100644 test/helpers/_files/partial_array.partial create mode 100644 test/helpers/_files/partial_array.txt create mode 100644 test/helpers/_files/partial_array_of_partials.js create mode 100644 test/helpers/_files/partial_array_of_partials.mustache create mode 100644 test/helpers/_files/partial_array_of_partials.partial create mode 100644 test/helpers/_files/partial_array_of_partials.txt create mode 100644 test/helpers/_files/partial_array_of_partials_implicit.js create mode 100644 test/helpers/_files/partial_array_of_partials_implicit.mustache create mode 100644 test/helpers/_files/partial_array_of_partials_implicit.partial create mode 100644 test/helpers/_files/partial_array_of_partials_implicit.txt create mode 100644 test/helpers/_files/partial_empty.js create mode 100644 test/helpers/_files/partial_empty.mustache create mode 100644 test/helpers/_files/partial_empty.partial create mode 100644 test/helpers/_files/partial_empty.txt create mode 100644 test/helpers/_files/partial_template.js create mode 100644 test/helpers/_files/partial_template.mustache create mode 100644 test/helpers/_files/partial_template.partial create mode 100644 test/helpers/_files/partial_template.txt create mode 100644 test/helpers/_files/partial_view.js create mode 100644 test/helpers/_files/partial_view.mustache create mode 100644 test/helpers/_files/partial_view.partial create mode 100644 test/helpers/_files/partial_view.txt create mode 100644 test/helpers/_files/partial_whitespace.js create mode 100644 test/helpers/_files/partial_whitespace.mustache create mode 100644 test/helpers/_files/partial_whitespace.partial create mode 100644 test/helpers/_files/partial_whitespace.txt create mode 100644 test/helpers/_files/recursion_with_same_names.js create mode 100644 test/helpers/_files/recursion_with_same_names.mustache create mode 100644 test/helpers/_files/recursion_with_same_names.txt create mode 100644 test/helpers/_files/reuse_of_enumerables.js create mode 100644 test/helpers/_files/reuse_of_enumerables.mustache create mode 100644 test/helpers/_files/reuse_of_enumerables.txt create mode 100644 test/helpers/_files/section_as_context.js create mode 100644 test/helpers/_files/section_as_context.mustache create mode 100644 test/helpers/_files/section_as_context.txt create mode 100644 test/helpers/_files/section_functions_in_partials.js create mode 100644 test/helpers/_files/section_functions_in_partials.mustache create mode 100644 test/helpers/_files/section_functions_in_partials.partial create mode 100644 test/helpers/_files/section_functions_in_partials.txt create mode 100644 test/helpers/_files/simple.js create mode 100644 test/helpers/_files/simple.mustache create mode 100644 test/helpers/_files/simple.txt create mode 100644 test/helpers/_files/string_as_context.js create mode 100644 test/helpers/_files/string_as_context.mustache create mode 100644 test/helpers/_files/string_as_context.txt create mode 100644 test/helpers/_files/two_in_a_row.js create mode 100644 test/helpers/_files/two_in_a_row.mustache create mode 100644 test/helpers/_files/two_in_a_row.txt create mode 100644 test/helpers/_files/two_sections.js create mode 100644 test/helpers/_files/two_sections.mustache create mode 100644 test/helpers/_files/two_sections.txt create mode 100644 test/helpers/_files/unescaped.js create mode 100644 test/helpers/_files/unescaped.mustache create mode 100644 test/helpers/_files/unescaped.txt create mode 100644 test/helpers/_files/uses_props_from_view_prototype.js create mode 100644 test/helpers/_files/uses_props_from_view_prototype.mustache create mode 100644 test/helpers/_files/uses_props_from_view_prototype.txt create mode 100644 test/helpers/_files/whitespace.js create mode 100644 test/helpers/_files/whitespace.mustache create mode 100644 test/helpers/_files/whitespace.txt create mode 100644 test/helpers/_files/zero_view.js create mode 100644 test/helpers/_files/zero_view.mustache create mode 100644 test/helpers/_files/zero_view.txt create mode 100644 test/helpers/render-helper.ts create mode 100644 test/parse.test.ts create mode 100644 test/partial.test.ts create mode 100644 test/render.test.ts diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..28661dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: j3lte + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Runtime info:** +- [ ] Deno +- [ ] Node + - OS: [e.g. iOS] + - Version [e.g. 22] + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml new file mode 100644 index 0000000..1dc6d37 --- /dev/null +++ b/.github/workflows/npm.yml @@ -0,0 +1,35 @@ +name: Build NPM package (On Demand) +on: + workflow_dispatch: + inputs: + version: + description: "Release version (x.y.z):" + required: true + +jobs: + release: + name: Build NPM package (On Demand) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.x + registry-url: 'https://registry.npmjs.org' + + - name: Run Deno dnt + run: deno task npm ${{ github.event.inputs.version }} + + - name: Check Version + run: cat ./npm/package.json | jq .version + + # - name: Publish to NPM + # run: cd ./npm && yarn publish --verbose --access public --new-version ${{ github.event.inputs.version }} + # env: + # NODE_AUTH_TOKEN: ${{ secrets.NPMJS_ACCESS_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8627fbf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Build NPM package (On Release) + +on: + release: + types: + - published + +jobs: + release: + name: Build NPM package (On Demand) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.x + registry-url: 'https://registry.npmjs.org' + + - name: Run Deno dnt + run: deno task npm ${{ github.ref_name }} + + - name: Check Version + run: cat ./npm/package.json | jq .version + + # - name: Publish to NPM + # run: cd ./npm && yarn publish --verbose --access public --new-version ${{ github.ref_name }} + # env: + # NODE_AUTH_TOKEN: ${{ secrets.NPMJS_ACCESS_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0a1dfd0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: Deno CI (test) + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + + - name: Check format + run: deno fmt --check + + - name: Check linting + run: deno lint + + - name: Check license + run: deno task check:license + + - name: Tests + run: deno task test + + - name: Coverage + run: deno task coverage + + # - name: Upload coverage reports to Codecov + # uses: codecov/codecov-action@v3 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # file: ./.coverage/coverage.lcov + # flags: unittests + # name: codecov-umbrella + # fail_ci_if_error: true diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..43761a0 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,35 @@ +name: Update version (On Demand) +on: + workflow_dispatch: + inputs: + version: + description: "Version (x.y.z):" + required: true +permissions: + contents: write + +jobs: + release: + name: Update version (On Demand) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.38.2 + + - name: Run Update Version + run: deno task update:version ${{ github.event.inputs.version }} + + - name: Commit changes + id: auto-commit-action + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Update Version (On Demand) + commit_options: '--no-verify' + # tagging_message: ${{ github.event.inputs.version }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb139ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.env* +*.json +!deno.json +.DS_Store +node_modules +npm/ +_local_testing.ts +.coverage/ +coverage.lcov diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..73eb6ca --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © J.W. Lagendijk 2023. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d25644 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# mustachio + +[![Build Status](https://travis-ci.org/j3lte/mustachio.svg?branch=master)](https://travis-ci.org/j3lte/mustachio) +[![Coverage Status](https://coveralls.io/repos/github/j3lte/mustachio/badge.svg?branch=master)](https://coveralls.io/github/j3lte/mustachio?branch=master) + +> ------------------------------------- +> +> Work in progress +> +> ------------------------------------- + +## License + +[MIT](LICENSE) + +--- + +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/j3lte) diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..ffa8f28 --- /dev/null +++ b/deno.json @@ -0,0 +1,50 @@ +{ + "tasks": { + "check:license": "deno run -A ./scripts/check-license.ts --check", + "format": "deno fmt ./src/ ./test/*.ts ./test/helpers/*.ts", + "lint": "deno lint ./src/ ./test/*.ts ./test/helpers/*.ts", + "test": "deno test --allow-read --coverage=.coverage", + "coverage": "deno coverage .coverage --lcov --exclude=/src/node/ --exclude=/test/ --exclude=/scripts/ > ./.coverage/coverage.lcov", + "test:watch": "deno test --watch", + "update:version": "deno run --allow-read --allow-write ./scripts/update-version.ts", + "update:deno_deps": "deno run -A https://deno.land/x/udd/main.ts dev_deps.ts ./scripts/build-npm.ts ./src/lib/Pastebin.ts ./src/lib/Scraper.ts", + "update:deps": "deno task update:deno_deps", + "localTest": "deno run --allow-read --allow-write --allow-run ./scripts/watch-test.ts", + "clean": "rm -r ./coverage", + "npm": "deno run -A ./scripts/build-npm.ts" + }, + "fmt": { + "indentWidth": 2, + "lineWidth": 100, + "singleQuote": false, + "useTabs": false, + "proseWrap": "preserve", + "exclude": [ + ".coverage/", + "npm/", + "./test/helpers/_files/", + ".github/", + "README.md" + ] + }, + "lint": { + "rules": { + "include": [ + "ban-untagged-todo", + "explicit-function-return-type" + ] + }, + "exclude": [ + "./_local_testing.ts", + "./test/helpers/_files/", + "npm/" + ] + }, + "test": { + "exclude": [ + "npm/", + "src/node/", + ".coverage/" + ] + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..c4e70ea --- /dev/null +++ b/deno.lock @@ -0,0 +1,204 @@ +{ + "version": "3", + "remote": { + "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", + "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", + "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", + "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", + "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", + "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", + "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", + "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", + "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", + "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", + "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", + "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", + "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", + "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", + "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", + "https://deno.land/std@0.181.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", + "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", + "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", + "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", + "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", + "https://deno.land/std@0.208.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.208.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", + "https://deno.land/std@0.208.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.208.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.208.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.208.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.208.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", + "https://deno.land/std@0.208.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.208.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", + "https://deno.land/std@0.208.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", + "https://deno.land/std@0.208.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", + "https://deno.land/std@0.208.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", + "https://deno.land/std@0.208.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", + "https://deno.land/std@0.208.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", + "https://deno.land/std@0.208.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", + "https://deno.land/std@0.208.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.208.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.208.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.208.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.208.0/assert/assert_not_strict_equals.ts": "4cdef83df17488df555c8aac1f7f5ec2b84ad161b6d0645ccdbcc17654e80c99", + "https://deno.land/std@0.208.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", + "https://deno.land/std@0.208.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.208.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", + "https://deno.land/std@0.208.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.208.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.208.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.208.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.208.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.208.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", + "https://deno.land/std@0.208.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.208.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.208.0/fmt/colors.ts": "34b3f77432925eb72cf0bfb351616949746768620b8e5ead66da532f93d10ba2", + "https://deno.land/std@0.208.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", + "https://deno.land/std@0.208.0/fs/copy.ts": "ca19e4837965914471df38fbd61e16f9e8adfe89f9cffb0c83615c83ea3fc2bf", + "https://deno.land/std@0.208.0/fs/empty_dir.ts": "7fba29ef2d03f3503cd512616efc0535984cf1bbe7ca9d098e8b4d0d88910120", + "https://deno.land/std@0.208.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.208.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", + "https://deno.land/std@0.208.0/fs/ensure_link.ts": "c15e69c48556d78aae31b83e0c0ece04b7b8bc0951412f5b759aceb6fde7f0ac", + "https://deno.land/std@0.208.0/fs/ensure_symlink.ts": "b389c8568f0656d145ac7ece472afe710815cccbb2ebfd19da7978379ae143fe", + "https://deno.land/std@0.208.0/fs/eol.ts": "8565e1e076c5baced170236617150a7833668658e000205d896fc54084309ce1", + "https://deno.land/std@0.208.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", + "https://deno.land/std@0.208.0/fs/expand_glob.ts": "4f98c508fc9e40d6311d2f7fd88aaad05235cc506388c22dda315e095305811d", + "https://deno.land/std@0.208.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", + "https://deno.land/std@0.208.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", + "https://deno.land/std@0.208.0/fs/walk.ts": "c1e6b43f72a46e89b630140308bd51a4795d416a416b4cfb7cd4bd1e25946723", + "https://deno.land/std@0.208.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", + "https://deno.land/std@0.208.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", + "https://deno.land/std@0.208.0/path/_common/common.ts": "9e4233b2eeb50f8b2ae10ecc2108f58583aea6fd3e8907827020282dc2b76143", + "https://deno.land/std@0.208.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.208.0/path/_common/dirname.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", + "https://deno.land/std@0.208.0/path/_common/format.ts": "11aa62e316dfbf22c126917f5e03ea5fe2ee707386555a8f513d27ad5756cf96", + "https://deno.land/std@0.208.0/path/_common/from_file_url.ts": "ef1bf3197d2efbf0297a2bdbf3a61d804b18f2bcce45548ae112313ec5be3c22", + "https://deno.land/std@0.208.0/path/_common/glob_to_reg_exp.ts": "5c3c2b79fc2294ec803d102bd9855c451c150021f452046312819fbb6d4dc156", + "https://deno.land/std@0.208.0/path/_common/normalize.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", + "https://deno.land/std@0.208.0/path/_common/normalize_string.ts": "88c472f28ae49525f9fe82de8c8816d93442d46a30d6bb5063b07ff8a89ff589", + "https://deno.land/std@0.208.0/path/_common/relative.ts": "1af19d787a2a84b8c534cc487424fe101f614982ae4851382c978ab2216186b4", + "https://deno.land/std@0.208.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", + "https://deno.land/std@0.208.0/path/_common/to_file_url.ts": "a8cdd1633bc9175b7eebd3613266d7c0b6ae0fb0cff24120b6092ac31662f9ae", + "https://deno.land/std@0.208.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.208.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", + "https://deno.land/std@0.208.0/path/basename.ts": "04bb5ef3e86bba8a35603b8f3b69537112cdd19ce64b77f2522006da2977a5f3", + "https://deno.land/std@0.208.0/path/common.ts": "f4d061c7d0b95a65c2a1a52439edec393e906b40f1caf4604c389fae7caa80f5", + "https://deno.land/std@0.208.0/path/dirname.ts": "88a0a71c21debafc4da7a4cd44fd32e899462df458fbca152390887d41c40361", + "https://deno.land/std@0.208.0/path/extname.ts": "2da4e2490f3b48b7121d19fb4c91681a5e11bd6bd99df4f6f47d7a71bb6ecdf2", + "https://deno.land/std@0.208.0/path/format.ts": "3457530cc85d1b4bab175f9ae73998b34fd456c830d01883169af0681b8894fb", + "https://deno.land/std@0.208.0/path/from_file_url.ts": "e7fa233ea1dff9641e8d566153a24d95010110185a6f418dd2e32320926043f8", + "https://deno.land/std@0.208.0/path/glob.ts": "a00a81a55c02bbe074ab21a50b6495c6f7795f54cd718c824adaa92c6c9b7419", + "https://deno.land/std@0.208.0/path/glob_to_regexp.ts": "74d7448c471e293d03f05ccb968df4365fed6aaa508506b6325a8efdc01d8271", + "https://deno.land/std@0.208.0/path/is_absolute.ts": "67232b41b860571c5b7537f4954c88d86ae2ba45e883ee37d3dec27b74909d13", + "https://deno.land/std@0.208.0/path/is_glob.ts": "567dce5c6656bdedfc6b3ee6c0833e1e4db2b8dff6e62148e94a917f289c06ad", + "https://deno.land/std@0.208.0/path/join.ts": "98d3d76c819af4a11a81d5ba2dbb319f1ce9d63fc2b615597d4bcfddd4a89a09", + "https://deno.land/std@0.208.0/path/join_globs.ts": "9b84d5103b63d3dbed4b2cf8b12477b2ad415c7d343f1488505162dc0e5f4db8", + "https://deno.land/std@0.208.0/path/mod.ts": "3defabebc98279e62b392fee7a6937adc932a8f4dcd2471441e36c15b97b00e0", + "https://deno.land/std@0.208.0/path/normalize.ts": "aa95be9a92c7bd4f9dc0ba51e942a1973e2b93d266cd74f5ca751c136d520b66", + "https://deno.land/std@0.208.0/path/normalize_glob.ts": "674baa82e1c00b6cb153bbca36e06f8e0337cb8062db6d905ab5de16076ca46b", + "https://deno.land/std@0.208.0/path/parse.ts": "d87ff0deef3fb495bc0d862278ff96da5a06acf0625ca27769fc52ac0d3d6ece", + "https://deno.land/std@0.208.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", + "https://deno.land/std@0.208.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", + "https://deno.land/std@0.208.0/path/posix/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", + "https://deno.land/std@0.208.0/path/posix/dirname.ts": "f48c9c42cc670803b505478b7ef162c7cfa9d8e751b59d278b2ec59470531472", + "https://deno.land/std@0.208.0/path/posix/extname.ts": "ee7f6571a9c0a37f9218fbf510c440d1685a7c13082c348d701396cc795e0be0", + "https://deno.land/std@0.208.0/path/posix/format.ts": "b94876f77e61bfe1f147d5ccb46a920636cd3cef8be43df330f0052b03875968", + "https://deno.land/std@0.208.0/path/posix/from_file_url.ts": "b97287a83e6407ac27bdf3ab621db3fccbf1c27df0a1b1f20e1e1b5acf38a379", + "https://deno.land/std@0.208.0/path/posix/glob_to_regexp.ts": "6ed00c71fbfe0ccc35977c35444f94e82200b721905a60bd1278b1b768d68b1a", + "https://deno.land/std@0.208.0/path/posix/is_absolute.ts": "159900a3422d11069d48395568217eb7fc105ceda2683d03d9b7c0f0769e01b8", + "https://deno.land/std@0.208.0/path/posix/is_glob.ts": "ec4fbc604b9db8487f7b56ab0e759b24a971ab6a45f7b0b698bc39b8b9f9680f", + "https://deno.land/std@0.208.0/path/posix/join.ts": "0c0d84bdc344876930126640011ec1b888e6facf74153ffad9ef26813aa2a076", + "https://deno.land/std@0.208.0/path/posix/join_globs.ts": "f4838d54b1f60a34a40625a3293f6e583135348be1b2974341ac04743cb26121", + "https://deno.land/std@0.208.0/path/posix/mod.ts": "f1b08a7f64294b7de87fc37190d63b6ce5b02889af9290c9703afe01951360ae", + "https://deno.land/std@0.208.0/path/posix/normalize.ts": "11de90a94ab7148cc46e5a288f7d732aade1d616bc8c862f5560fa18ff987b4b", + "https://deno.land/std@0.208.0/path/posix/normalize_glob.ts": "10a1840c628ebbab679254d5fa1c20e59106102354fb648a1765aed72eb9f3f9", + "https://deno.land/std@0.208.0/path/posix/parse.ts": "199208f373dd93a792e9c585352bfc73a6293411bed6da6d3bc4f4ef90b04c8e", + "https://deno.land/std@0.208.0/path/posix/relative.ts": "e2f230608b0f083e6deaa06e063943e5accb3320c28aef8d87528fbb7fe6504c", + "https://deno.land/std@0.208.0/path/posix/resolve.ts": "51579d83159d5c719518c9ae50812a63959bbcb7561d79acbdb2c3682236e285", + "https://deno.land/std@0.208.0/path/posix/separator.ts": "0b6573b5f3269a3164d8edc9cefc33a02dd51003731c561008c8bb60220ebac1", + "https://deno.land/std@0.208.0/path/posix/to_file_url.ts": "08d43ea839ee75e9b8b1538376cfe95911070a655cd312bc9a00f88ef14967b6", + "https://deno.land/std@0.208.0/path/posix/to_namespaced_path.ts": "c9228a0e74fd37e76622cd7b142b8416663a9b87db643302fa0926b5a5c83bdc", + "https://deno.land/std@0.208.0/path/relative.ts": "23d45ede8b7ac464a8299663a43488aad6b561414e7cbbe4790775590db6349c", + "https://deno.land/std@0.208.0/path/resolve.ts": "5b184efc87155a0af9fa305ff68a109e28de9aee81fc3e77cd01380f19daf867", + "https://deno.land/std@0.208.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", + "https://deno.land/std@0.208.0/path/to_file_url.ts": "edaafa089e0bce386e1b2d47afe7c72e379ff93b28a5829a5885e4b6c626d864", + "https://deno.land/std@0.208.0/path/to_namespaced_path.ts": "cf8734848aac3c7527d1689d2adf82132b1618eff3cc523a775068847416b22a", + "https://deno.land/std@0.208.0/path/windows/_util.ts": "f32b9444554c8863b9b4814025c700492a2b57ff2369d015360970a1b1099d54", + "https://deno.land/std@0.208.0/path/windows/basename.ts": "8a9dbf7353d50afbc5b221af36c02a72c2d1b2b5b9f7c65bf6a5a2a0baf88ad3", + "https://deno.land/std@0.208.0/path/windows/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", + "https://deno.land/std@0.208.0/path/windows/dirname.ts": "5c2aa541384bf0bd9aca821275d2a8690e8238fa846198ef5c7515ce31a01a94", + "https://deno.land/std@0.208.0/path/windows/extname.ts": "07f4fa1b40d06a827446b3e3bcc8d619c5546b079b8ed0c77040bbef716c7614", + "https://deno.land/std@0.208.0/path/windows/format.ts": "343019130d78f172a5c49fdc7e64686a7faf41553268961e7b6c92a6d6548edf", + "https://deno.land/std@0.208.0/path/windows/from_file_url.ts": "d53335c12b0725893d768be3ac6bf0112cc5b639d2deb0171b35988493b46199", + "https://deno.land/std@0.208.0/path/windows/glob_to_regexp.ts": "290755e18ec6c1a4f4d711c3390537358e8e3179581e66261a0cf348b1a13395", + "https://deno.land/std@0.208.0/path/windows/is_absolute.ts": "245b56b5f355ede8664bd7f080c910a97e2169972d23075554ae14d73722c53c", + "https://deno.land/std@0.208.0/path/windows/is_glob.ts": "ec4fbc604b9db8487f7b56ab0e759b24a971ab6a45f7b0b698bc39b8b9f9680f", + "https://deno.land/std@0.208.0/path/windows/join.ts": "e6600bf88edeeef4e2276e155b8de1d5dec0435fd526ba2dc4d37986b2882f16", + "https://deno.land/std@0.208.0/path/windows/join_globs.ts": "f4838d54b1f60a34a40625a3293f6e583135348be1b2974341ac04743cb26121", + "https://deno.land/std@0.208.0/path/windows/mod.ts": "d7040f461465c2c21c1c68fc988ef0bdddd499912138cde3abf6ad60c7fb3814", + "https://deno.land/std@0.208.0/path/windows/normalize.ts": "9deebbf40c81ef540b7b945d4ccd7a6a2c5a5992f791e6d3377043031e164e69", + "https://deno.land/std@0.208.0/path/windows/normalize_glob.ts": "344ff5ed45430495b9a3d695567291e50e00b1b3b04ea56712a2acf07ab5c128", + "https://deno.land/std@0.208.0/path/windows/parse.ts": "120faf778fe1f22056f33ded069b68e12447668fcfa19540c0129561428d3ae5", + "https://deno.land/std@0.208.0/path/windows/relative.ts": "026855cd2c36c8f28f1df3c6fbd8f2449a2aa21f48797a74700c5d872b86d649", + "https://deno.land/std@0.208.0/path/windows/resolve.ts": "5ff441ab18a2346abadf778121128ee71bda4d0898513d4639a6ca04edca366b", + "https://deno.land/std@0.208.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", + "https://deno.land/std@0.208.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", + "https://deno.land/std@0.208.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", + "https://deno.land/std@0.208.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", + "https://deno.land/std@0.208.0/testing/bdd.ts": "c41f019786c4a9112aadb7e5a7bbcc711f58429ac5904b3855fa248ba5fa0ba6", + "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", + "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", + "https://deno.land/x/deno_cache@0.6.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", + "https://deno.land/x/deno_cache@0.6.2/cache.ts": "58b53c128b742757efcad10af9a3871f23b4e200674cb5b0ddf61164fb9b2fe7", + "https://deno.land/x/deno_cache@0.6.2/deno_dir.ts": "1ea355b8ba11c630d076b222b197cfc937dd81e5a4a260938997da99e8ff93a0", + "https://deno.land/x/deno_cache@0.6.2/deps.ts": "12cca94516cf2d3ed42fccd4b721ecd8060679253f077d83057511045b0081aa", + "https://deno.land/x/deno_cache@0.6.2/dirs.ts": "009c6f54e0b610914d6ce9f72f6f6ccfffd2d47a79a19061e0a9eb4253836069", + "https://deno.land/x/deno_cache@0.6.2/disk_cache.ts": "66a1e604a8d564b6dd0500326cac33d08b561d331036bf7272def80f2f7952aa", + "https://deno.land/x/deno_cache@0.6.2/file_fetcher.ts": "4f3e4a2c78a5ca1e4812099e5083f815a8525ab20d389b560b3517f6b1161dd6", + "https://deno.land/x/deno_cache@0.6.2/http_cache.ts": "407135eaf2802809ed373c230d57da7ef8dff923c4abf205410b9b99886491fd", + "https://deno.land/x/deno_cache@0.6.2/lib/deno_cache_dir.generated.js": "59f8defac32e8ebf2a30f7bc77e9d88f0e60098463fb1b75e00b9791a4bbd733", + "https://deno.land/x/deno_cache@0.6.2/lib/snippets/deno_cache_dir-a2aecaa9536c9402/fs.js": "cbe3a976ed63c72c7cb34ef845c27013033a3b11f9d8d3e2c4aa5dda2c0c7af6", + "https://deno.land/x/deno_cache@0.6.2/mod.ts": "b4004287e1c6123d7f07fe9b5b3e94ce6d990c4102949a89c527c68b19627867", + "https://deno.land/x/deno_cache@0.6.2/util.ts": "f3f5a0cfc60051f09162942fb0ee87a0e27b11a12aec4c22076e3006be4cc1e2", + "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", + "https://deno.land/x/dnt@0.39.0/lib/compiler.ts": "7f4447531581896348b8a379ab94730856b42ae50d99043f2468328360293cb1", + "https://deno.land/x/dnt@0.39.0/lib/compiler_transforms.ts": "f21aba052f5dcf0b0595c734450842855c7f572e96165d3d34f8fed2fc1f7ba1", + "https://deno.land/x/dnt@0.39.0/lib/mod.deps.ts": "8d6123c8e1162037e58aa8126686a03d1e2cffb250a8757bf715f80242097597", + "https://deno.land/x/dnt@0.39.0/lib/npm_ignore.ts": "57fbb7e7b935417d225eec586c6aa240288905eb095847d3f6a88e290209df4e", + "https://deno.land/x/dnt@0.39.0/lib/package_json.ts": "607b0a4f44acad071a4c8533b312a27d6671eac8e6a23625c8350ce29eadb2ba", + "https://deno.land/x/dnt@0.39.0/lib/pkg/dnt_wasm.generated.js": "4f9c59b3ca6c875adabb10df256e273fff1129fca3a1557eb8936bddd7da7b18", + "https://deno.land/x/dnt@0.39.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "aba69a019a6da6f084898a6c7b903b8b583bc0dbd82bfb338449cf0b5bce58fd", + "https://deno.land/x/dnt@0.39.0/lib/shims.ts": "60fd285ad433c6944544595e7b885eab3eab09253252891380654f4cd3addaaa", + "https://deno.land/x/dnt@0.39.0/lib/test_runner/get_test_runner_code.ts": "4dc7a73a13b027341c0688df2b29a4ef102f287c126f134c33f69f0339b46968", + "https://deno.land/x/dnt@0.39.0/lib/test_runner/test_runner.ts": "4d0da0500ec427d5f390d9a8d42fb882fbeccc92c92d66b6f2e758606dbd40e6", + "https://deno.land/x/dnt@0.39.0/lib/transform.deps.ts": "2e159661e1c5c650de9a573babe0e319349fe493105157307ec2ad2f6a52c94e", + "https://deno.land/x/dnt@0.39.0/lib/types.ts": "b8e228b2fac44c2ae902fbb73b1689f6ab889915bd66486c8a85c0c24255f5fb", + "https://deno.land/x/dnt@0.39.0/lib/utils.ts": "224f15f33e7226a2fd991e438d0291d7ed8c7889807efa2e1ecb67d2d1db6720", + "https://deno.land/x/dnt@0.39.0/mod.ts": "9df36a862161d9eb376472b699f6cb08ba0ad1704e0826fbe13be766bd3c01da", + "https://deno.land/x/dnt@0.39.0/transform.ts": "f68743a14cf9bf53bfc9c81073871d69d447a7f9e3453e0447ca2fb78926bb1d", + "https://deno.land/x/ts_morph@20.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", + "https://deno.land/x/ts_morph@20.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", + "https://deno.land/x/ts_morph@20.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", + "https://deno.land/x/ts_morph@20.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", + "https://deno.land/x/ts_morph@20.0.0/common/ts_morph_common.js": "2325f94f61dc5f3f98a1dab366dc93048d11b1433d718b10cfc6ee5a1cfebe8f", + "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", + "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", + "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63" + } +} diff --git a/dev_deps.ts b/dev_deps.ts new file mode 100644 index 0000000..1639c7a --- /dev/null +++ b/dev_deps.ts @@ -0,0 +1,18 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +export { emptyDir } from "https://deno.land/std@0.208.0/fs/mod.ts"; +export { walk } from "https://deno.land/std@0.208.0/fs/walk.ts"; +export { + assert, + assertEquals, + assertNotEquals, + assertStrictEquals, + assertThrows, +} from "https://deno.land/std@0.208.0/assert/mod.ts"; +export { + afterEach, + beforeAll, + beforeEach, + describe, + it, +} from "https://deno.land/std@0.208.0/testing/bdd.ts"; diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..10e2ff3 --- /dev/null +++ b/mod.ts @@ -0,0 +1,6 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import mustache from "./src/mod.ts"; +export type { Config, Context, Mustache } from "./src/mod.ts"; + +export default mustache; diff --git a/scripts/build-npm.ts b/scripts/build-npm.ts new file mode 100644 index 0000000..97df0bc --- /dev/null +++ b/scripts/build-npm.ts @@ -0,0 +1,71 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { build, emptyDir } from "https://deno.land/x/dnt@0.39.0/mod.ts"; + +const cleanupTypes = async (dir: string) => { + for await (const dirEntry of Deno.readDir(dir)) { + const entryPath = `${dir}/${dirEntry.name}`; + if (dirEntry.isDirectory) { + await cleanupTypes(entryPath); + } else { + const file = await Deno.readTextFile(entryPath); + const newFile = file.replaceAll('.js"', '"'); + await Deno.writeTextFile(entryPath, newFile); + } + } +}; + +await emptyDir("./npm"); + +await build({ + entryPoints: ["./src/mod.ts"], + outDir: "./npm", + mappings: {}, + declaration: "separate", + skipSourceOutput: true, + // scriptModule: false, + shims: { + // deno: true, + }, + test: false, + typeCheck: false, + compilerOptions: { + importHelpers: true, + target: "ES2021", + lib: ["ESNext"], + }, + package: { + // package.json properties + name: "mustachio", + version: Deno.args[0] || "1.0.0", + description: "Mustache template engine for Deno/Node", + license: "MIT", + publishConfiig: { + access: "public", + }, + keywords: [ + "mustachio", + "mustache", + "template", + "templates", + "ejs", + ], + author: { + name: "J.W. Lagendijk", + email: "jwlagendijk@gmail.com", + }, + repository: { + type: "git", + url: "git+https://github.com/j3lte/mustachio.git", + }, + bugs: { + url: "https://github.com/j3lte/mustachio/issues", + }, + }, + async postBuild(): Promise { + // steps to run after building and before running the tests + await Deno.copyFile("./LICENSE", "npm/LICENSE"); + await Deno.copyFile("./README.md", "npm/README.md"); + await cleanupTypes("./npm/types"); + }, +}); diff --git a/scripts/check-license.ts b/scripts/check-license.ts new file mode 100644 index 0000000..2e966c0 --- /dev/null +++ b/scripts/check-license.ts @@ -0,0 +1,60 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { walk } from "../dev_deps.ts"; + +const EXTENSIONS = [".ts"]; + +const ROOT = new URL("../", import.meta.url); +const CHECK = Deno.args.includes("--check"); +const CURRENT_YEAR = new Date().getFullYear(); +const RX_COPYRIGHT = new RegExp( + `// Copyright ([0-9]{4}) J.W. Lagendijk\\. All rights reserved\\. MIT license\\.\n`, +); +const COPYRIGHT = `// Copyright ${CURRENT_YEAR} J.W. Lagendijk. All rights reserved. MIT license.`; + +let failed = false; + +for await ( + const { path } of walk(ROOT, { + exts: EXTENSIONS, + skip: [ + /\.coverage/, + /node_modules/, + /npm/, + /_local_testing\.ts/, + ], + includeDirs: false, + }) +) { + console.log("Checking " + path); + const content = await Deno.readTextFile(path); + const match = content.match(RX_COPYRIGHT); + + if (!match) { + if (CHECK) { + console.error(`Missing copyright header: ${path}`); + failed = true; + } else { + const contentWithCopyright = COPYRIGHT + "\n" + content; + await Deno.writeTextFile(path, contentWithCopyright); + console.log("Copyright header automatically added to " + path); + } + } else if (parseInt(match[1]) !== CURRENT_YEAR) { + if (CHECK) { + console.error(`Incorrect copyright year: ${path}`); + failed = true; + } else { + const index = match.index ?? 0; + const contentWithoutCopyright = content.replace(match[0], ""); + const contentWithCopyright = contentWithoutCopyright.substring(0, index) + + COPYRIGHT + "\n" + contentWithoutCopyright.substring(index); + await Deno.writeTextFile(path, contentWithCopyright); + console.log("Copyright header automatically updated in " + path); + } + } +} + +if (failed) { + console.info(`Copyright header should be "${COPYRIGHT}"`); + Deno.exit(1); +} diff --git a/scripts/update-version.ts b/scripts/update-version.ts new file mode 100644 index 0000000..868724e --- /dev/null +++ b/scripts/update-version.ts @@ -0,0 +1,29 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +async function update(args: string[]): Promise { + const version = args[0]; + if (!version) { + console.error("No version provided."); + Deno.exit(1); + } + + const filePath = new URL(import.meta.url).pathname; + const dirPath = filePath.split("/").slice(0, -1).join("/"); + + const paths = [ + `${dirPath}/../src/lib/Mustache.ts`, + ]; + + for (const path of paths) { + const file = await Deno.readTextFile(path); + const updatedFile = file.replace( + /static version = ".*";/, + `static version = "${version}";`, + ); + await Deno.writeTextFile(path, updatedFile); + } + + console.log(`Updated version to ${version}.`); +} + +update(Deno.args); diff --git a/scripts/watch-test.ts b/scripts/watch-test.ts new file mode 100644 index 0000000..0b8562a --- /dev/null +++ b/scripts/watch-test.ts @@ -0,0 +1,67 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { emptyDir } from "../dev_deps.ts"; + +const watcher = Deno.watchFs([ + "./src/", + "./test/", +], { recursive: true }); + +const runCmd = async (args: string[], deno = false) => { + let cmd: Deno.Command; + if (deno) { + cmd = new Deno.Command(Deno.execPath(), { + args, + }); + } else { + cmd = new Deno.Command(args[0], { + args: args.slice(1), + }); + } + const { code, stdout, stderr } = await cmd.output(); + return { + output: new TextDecoder().decode(stdout), + error: new TextDecoder().decode(stderr), + code, + }; +}; + +// Debounce runner +let timeout: number | undefined; + +const runTest = (path: string) => { + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(async () => { + console.log(">>>>> runTest", path); + await emptyDir("./.coverage/"); + const test = await runCmd(["test", "--allow-read", "--coverage=./.coverage", "./test/"], true); + console.log(test.output); + + if (test.code !== 0) { + console.log(test.error); + return; + } + const cov = await runCmd([ + "coverage", + "./.coverage/", + "--lcov", + "--exclude=/test|scripts/", + ], true); + await Deno.writeTextFile("./.coverage/coverageFile.lcov", cov.output); + + await runCmd(["genhtml", "-o", "./.coverage/", "./.coverage/coverageFile.lcov"]); + }, 100); +}; + +for await (const event of watcher) { + // console.log(">>>>> event", event); + const { kind, paths } = event; + if (["modify", "create", "delete"].includes(kind) && paths[0]) { + const [path] = paths; + if (path.endsWith(".ts")) { + runTest(path); + } + } +} diff --git a/src/lib/Context.ts b/src/lib/Context.ts new file mode 100644 index 0000000..2c4e9d1 --- /dev/null +++ b/src/lib/Context.ts @@ -0,0 +1,130 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { hasProperty, isFunction, primitiveHasOwnProperty } from "./util.ts"; + +/** + * Represents a rendering context by wrapping a view object and + * maintaining a reference to the parent context. + */ +export class Context { + #view: Record; + private cache: Record; + #parent: Context | undefined; + + constructor(view: Record, parentContext?: Context) { + this.#view = view; + this.cache = { ".": this.#view }; + this.#parent = parentContext; + } + + /** + * Creates a new context using the given view with this context + * as the parent. + */ + push(view: Record): Context { + return new Context(view, this); + } + + /** + * Returns the value of the given name in this context, traversing + * up the context hierarchy if the value is absent in this context's view. + */ + lookup(name: string): unknown { + const cache = this.cache; + + let value; + + if (Object.prototype.hasOwnProperty.call(cache, name)) { + value = cache[name]; + } else { + // deno-lint-ignore no-this-alias + let context: Context | undefined = this; + let intermediateValue: object | Record; + let names: string[]; + let index: number; + let lookupHit = false; + + while (context) { + if (name.indexOf(".") > 0) { + intermediateValue = context.view; + names = name.split("."); + index = 0; + + /** + * Using the dot notion path in `name`, we descend through the + * nested objects. + * + * To be certain that the lookup has been successful, we have to + * check if the last object in the path actually has the property + * we are looking for. We store the result in `lookupHit`. + * + * This is specially necessary for when the value has been set to + * `undefined` and we want to avoid looking up parent contexts. + * + * In the case where dot notation is used, we consider the lookup + * to be successful even if the last "object" in the path is + * not actually an object but a primitive (e.g., a string, or an + * integer), because it is sometimes useful to access a property + * of an autoboxed primitive, such as the length of a string. + */ + while (intermediateValue != null && index < names.length) { + if (index === names.length - 1) { + lookupHit = hasProperty(intermediateValue, names[index]) || + primitiveHasOwnProperty(intermediateValue, names[index]); + } + + // @ts-ignore Need to investigate this error + intermediateValue = intermediateValue[names[index++]]; + } + } else { + // @ts-ignore Need to investigate this error + intermediateValue = context.view[name]; + + /** + * Only checking against `hasProperty`, which always returns `false` if + * `context.view` is not an object. Deliberately omitting the check + * against `primitiveHasOwnProperty` if dot notation is not used. + * + * Consider this example: + * ``` + * Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"}) + * ``` + * + * If we were to check also against `primitiveHasOwnProperty`, as we do + * in the dot notation case, then render call would return: + * + * "The length of a football field is 9." + * + * rather than the expected: + * + * "The length of a football field is 100 yards." + */ + lookupHit = hasProperty(context.view, name); + } + + if (lookupHit) { + value = intermediateValue; + break; + } + + context = context.parent; + } + + cache[name] = value; + } + + if (isFunction(value)) { + value = value.call(this.#view); + } + + return value; + } + + get view(): Record { + return this.#view; + } + + get parent(): Context | undefined { + return this.#parent; + } +} diff --git a/src/lib/Mustache.ts b/src/lib/Mustache.ts new file mode 100644 index 0000000..dce7408 --- /dev/null +++ b/src/lib/Mustache.ts @@ -0,0 +1,67 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { Context } from "./Context.ts"; +import { Config, Writer } from "./Writer.ts"; +import { Token } from "./parse.ts"; +import { escapeHtml, typeStr } from "./util.ts"; + +export class Mustache { + static name = "mustache.deno"; + static version = "0.1.0"; + tags = ["{{", "}}"]; + escape = escapeHtml; + + writer: Writer; + + /** + * The name of the module. + * `name = "mustache.deno"` + */ + readonly name = Mustache.name; + /** + * The version of the module. + * `version = "1.0.0"` + */ + readonly version = Mustache.version; + + constructor() { + this.writer = new Writer(); + } + + set templateCache(cache: Map | undefined) { + this.writer.templateCache = cache; + } + + clearCache(): void { + return this.writer.clearCache(); + } + + parse(template: string, tags: string[] = this.tags): Token[] { + return this.writer.parse(template, tags); + } + + render( + template: string, + view: Context | unknown, + partials: Record = {}, + config: Partial | string[] = { + tags: this.tags, + escape: escapeHtml, + }, + ): string { + if (typeof template !== "string") { + throw new TypeError( + 'Invalid template! Template should be a "string" ' + + 'but "' + + typeStr(template) + + '" was given as the first ' + + "argument for mustache#render(template, view, partials)", + ); + } + return this.writer.render(template, view, partials, config); + } +} + +const mustache = new Mustache(); + +export default mustache; diff --git a/src/lib/Scanner.ts b/src/lib/Scanner.ts new file mode 100644 index 0000000..db174d7 --- /dev/null +++ b/src/lib/Scanner.ts @@ -0,0 +1,80 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +/** + * A simple string scanner that is used by the template parser to find + * tokens in template strings. + */ +export class Scanner { + #string; + #tail; + #pos = 0; + + constructor(string: string) { + this.#string = string; + this.#tail = string; + } + + /** + * Returns `true` if the tail is empty (end of string). + */ + eos(): boolean { + return this.#tail === ""; + } + + /** + * Tries to match the given regular expression at the current position. + * Returns the matched text if it can match, the empty string otherwise. + */ + scan(re: RegExp): string { + const match = this.#tail.match(re); + + if (!match || match.index !== 0) { + return ""; + } + + const string = match[0]; + + this.#tail = this.#tail.substring(string.length); + this.#pos += string.length; + + return string; + } + + /** + * Skips all text until the given regular expression can be matched. Returns + * the skipped string, which is the entire tail if no match can be made. + */ + scanUntil(re: RegExp): string { + const index = this.#tail.search(re); + let match; + + switch (index) { + case -1: + match = this.#tail; + this.#tail = ""; + break; + case 0: + match = ""; + break; + default: + match = this.#tail.substring(0, index); + this.#tail = this.#tail.substring(index); + } + + this.#pos += match.length; + + return match; + } + + get pos(): number { + return this.#pos; + } + + get string(): string { + return this.#string; + } + + get tail(): string { + return this.#tail; + } +} diff --git a/src/lib/Writer.ts b/src/lib/Writer.ts new file mode 100644 index 0000000..5f1c654 --- /dev/null +++ b/src/lib/Writer.ts @@ -0,0 +1,281 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { Context } from "./Context.ts"; +import { parseTemplate, Token } from "./parse.ts"; +import { escapeHtml, isArray, isFunction } from "./util.ts"; + +const defaultTags = ["{{", "}}"]; + +export type Config = { + tags: string[]; + escape: (value: string) => string; +}; + +type Partials = (name: string) => string | Record; + +export class Writer { + templateCache: Map | undefined = new Map(); + + clearCache(): void { + if (typeof this.templateCache !== "undefined") { + this.templateCache.clear(); + } + } + + /** + * Parses and caches the given `template` according to the given `tags` or + * `mustache.tags` if `tags` is omitted, and returns the array of tokens + * that is generated from the parse. + */ + parse(template: string, tags: string[] = defaultTags): Token[] { + const cache = this.templateCache; + const cacheKey = template + ":" + tags.join(":"); + const isCacheEnabled = typeof cache !== "undefined"; + let tokens = isCacheEnabled ? cache.get(cacheKey) : undefined; + + if (typeof tokens === "undefined") { + tokens = parseTemplate(template, tags); + isCacheEnabled && cache.set(cacheKey, tokens); + } + return tokens; + } + + /** + * High-level method that is used to render the given `template` with + * the given `view`. + * + * The optional `partials` argument may be an object that contains the + * names and templates of partials that are used in the template. It may + * also be a function that is used to load partial templates on the fly + * that takes a single argument: the name of the partial. + * + * If the optional `config` argument is given here, then it should be an + * object with a `tags` attribute or an `escape` attribute or both. + * If an array is passed, then it will be interpreted the same way as + * a `tags` attribute on a `config` object. + * + * The `tags` attribute of a `config` object must be an array with two + * string values: the opening and closing tags used in the template (e.g. + * [ "<%", "%>" ]). The default is to mustache.tags. + * + * The `escape` attribute of a `config` object must be a function which + * accepts a string as input and outputs a safely escaped string. + * If an `escape` function is not provided, then an HTML-safe string + * escaping function is used as the default. + */ + // render(template, view, partials, config) { + render( + template: string, + view: Context | unknown, + partials: Partials | Record, + config: Partial | string[], + ): string { + const tags = this.getConfigTags(config); + const tokens = this.parse(template, tags); + const context = (view instanceof Context) ? view : new Context(view as Record); + return this.renderTokens(tokens, context, partials, template, config); + } + + /** + * Low-level method that renders the given array of `tokens` using + * the given `context` and `partials`. + * + * Note: The `originalTemplate` is only ever used to extract the portion + * of the original template that was contained in a higher-order section. + * If the template doesn't use higher-order sections, this argument may + * be omitted. + */ + // renderTokens(tokens, context, partials, originalTemplate, config) { + renderTokens( + tokens: Token[], + context: Context, + partials: Partials | Record, + originalTemplate: string, + config: Partial | string[], + ): string { + let buffer = ""; + + // var token, symbol, value; + let token: Token; + let symbol: string; + let value: unknown; + + for (let i = 0, numTokens = tokens.length; i < numTokens; ++i) { + value = undefined; + token = tokens[i]; + symbol = token[0]; + + if (symbol === "#") { + value = this.renderSection(token, context, partials, originalTemplate, config); + } else if (symbol === "^") { + value = this.renderInverted(token, context, partials, originalTemplate, config); + } else if (symbol === ">") value = this.renderPartial(token, context, partials, config); + else if (symbol === "&") value = this.unescapedValue(token, context); + else if (symbol === "name") value = this.escapedValue(token, context, config); + else if (symbol === "text") value = this.rawValue(token); + + if (value !== undefined) { + buffer += value; + } + } + + return buffer; + } + + // renderSection(token, context, partials, originalTemplate, config) { + renderSection( + token: Token, + context: Context, + partials: Partials | Record, + originalTemplate: string, + config: Partial | string[], + ): string | undefined { + let buffer = ""; + let value = context.lookup(token[1]); + + // This function is used to render an arbitrary template + // in the current context by higher-order sections. + const subRender = (template: string): string => { + return this.render(template, context, partials, config); + }; + + if (!value) return; + + if (isArray(value)) { + for (let j = 0; j < value.length; ++j) { + buffer += this.renderTokens( + token[4] as Token[], + context.push(value[j]), + partials, + originalTemplate, + config, + ); + } + } else if ( + typeof value === "object" || typeof value === "string" || typeof value === "number" + ) { + buffer += this.renderTokens( + token[4] as Token[], + context.push(value as Record), + partials, + originalTemplate, + config, + ); + } else if (isFunction(value)) { + if (typeof originalTemplate !== "string") { + throw new Error("Cannot use higher-order sections without the original template"); + } + + // Extract the portion of the original template that the section contains. + value = value.call( + context.view, + originalTemplate.slice(token[3], token[5]), + subRender, + ) as string; + + if (value != null) { + buffer += value; + } + } else { + buffer += this.renderTokens(token[4] as Token[], context, partials, originalTemplate, config); + } + return buffer; + } + + renderInverted( + token: Token, + context: Context, + partials: Partials | Record, + originalTemplate: string, + config: Partial | string[], + ): string | undefined { + const value = context.lookup(token[1]); + + // Use JavaScript's definition of falsy. Include empty arrays. + // See https://github.com/janl/mustache.js/issues/186 + if (!value || (isArray(value) && value.length === 0)) { + return this.renderTokens(token[4] as Token[], context, partials, originalTemplate, config); + } + } + + indentPartial(partial: string, indentation: string, lineHasNonSpace: boolean): string { + const filteredIndentation = indentation.replace(/[^ \t]/g, ""); + const partialByNl = partial.split("\n"); + for (let i = 0; i < partialByNl.length; i++) { + if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) { + partialByNl[i] = filteredIndentation + partialByNl[i]; + } + } + return partialByNl.join("\n"); + } + + renderPartial( + token: Token, + context: Context, + partials: Partials | Record, + config: Partial | string[], + ): string | undefined { + if (!partials) return; + const tags = this.getConfigTags(config); + + const value = + (isFunction(partials) + ? partials(token[1]) + : partials[token[1]] || partials[token[1] + (tags as string[])[1]]) as string; + + if (value != null) { + const lineHasNonSpace = token[6] as boolean; + const tagIndex = token[5]; + const indentation = token[4] as string; + let indentedValue = value; + if (tagIndex == 0 && indentation) { + indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); + } + const tokens = this.parse(indentedValue, tags); + return this.renderTokens(tokens, context, partials, indentedValue, config); + } + } + + unescapedValue(token: Token, context: Context): string | undefined { + const value = context.lookup(token[1]); + if (value != null) { + return value as string; + } + } + + escapedValue( + token: Token, + context: Context, + config: Partial | string[], + ): string | undefined { + const escape = this.getConfigEscape(config) || escapeHtml; + const value = context.lookup(token[1]); + if (value != null) { + return (typeof value === "number" && escape === escapeHtml) + ? String(value) + : escape(value as string); + } + } + + rawValue(token: Token): string { + return token[1]; + } + + getConfigTags(config: string[] | Partial): string[] | undefined { + if (isArray(config)) { + return config; + } + if (config && typeof config === "object") { + return config.tags; + } + return; + } + + getConfigEscape(config: string[] | Partial): ((value: string) => string) | undefined { + if (config && typeof config === "object" && !isArray(config)) { + return config.escape; + } else { + return undefined; + } + } +} diff --git a/src/lib/parse.ts b/src/lib/parse.ts new file mode 100644 index 0000000..e78421a --- /dev/null +++ b/src/lib/parse.ts @@ -0,0 +1,268 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { Scanner } from "./Scanner.ts"; +import { escapeRegExp, isArray, isWhitespace } from "./util.ts"; + +const whiteRe = /\s*/; +const spaceRe = /\s+/; +const equalsRe = /\s*=/; +const curlyRe = /\s*\}/; +const tagRe = /#|\^|\/|>|\{|&|=|!/; + +export type Token = [string, string, number, number, (string | Token[])?, number?, boolean?]; + +/** + * Forms the given array of `tokens` into a nested tree structure where + * tokens that represent a section have two additional items: 1) an array of + * all tokens that appear in that section and 2) the index in the original + * template that represents the end of that section. + */ +function nestTokens(tokens: Token[]): Token[] { + const nestedTokens: Token[] = []; + let collector: Token[] = nestedTokens; + const sections: Token[] = []; + + // var token, section; + let token: Token; + let section: Token; + + for (let i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + + switch (token[0]) { + case "#": + case "^": + collector.push(token); + sections.push(token); + collector = token[4] = []; + break; + case "/": + section = sections.pop() as Token; + section[5] = token[2]; + collector = + (sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens) as Token[]; + break; + default: + collector.push(token); + } + } + + return nestedTokens; +} + +/** + * Combines the values of consecutive text tokens in the given `tokens` array + * to a single token. + */ +function squashTokens(tokens: Token[]): Token[] { + const squashedTokens: Token[] = []; + + // var token, lastToken; + let token: Token; + let lastToken: Token | undefined; + + for (let i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + + if (token) { + if (token[0] === "text" && lastToken && lastToken[0] === "text") { + lastToken[1] += token[1]; + lastToken[3] = token[3]; + } else { + squashedTokens.push(token); + lastToken = token; + } + } + } + + return squashedTokens; +} + +function compileTags(tagsToCompile: string | string[]): { + openingTagRe: RegExp; + closingTagRe: RegExp; + closingCurlyRe: RegExp; +} { + if (typeof tagsToCompile === "string") { + tagsToCompile = tagsToCompile.split(spaceRe, 2); + } + + if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) { + throw new Error("Invalid tags: " + tagsToCompile); + } + + const openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "\\s*"); + const closingTagRe = new RegExp("\\s*" + escapeRegExp(tagsToCompile[1])); + const closingCurlyRe = new RegExp("\\s*" + escapeRegExp("}" + tagsToCompile[1])); + + return { openingTagRe, closingTagRe, closingCurlyRe }; +} + +/** + * Breaks up the given `template` string into a tree of tokens. If the `tags` + * argument is given here it must be an array with two string values: the + * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of + * course, the default is to use mustaches (i.e. mustache.tags). + * + * A token is an array with at least 4 elements. The first element is the + * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag + * did not contain a symbol (i.e. {{myValue}}) this element is "name". For + * all text that appears outside a symbol this element is "text". + * + * The second element of a token is its "value". For mustache tags this is + * whatever else was inside the tag besides the opening symbol. For text tokens + * this is the text itself. + * + * The third and fourth elements of the token are the start and end indices, + * respectively, of the token in the original template. + * + * Tokens that are the root node of a subtree contain two more elements: 1) an + * array of tokens in the subtree and 2) the index in the original template at + * which the closing tag for that section begins. + * + * Tokens for partials also contain two more elements: 1) a string value of + * indendation prior to that tag and 2) the index of that tag on that line - + * eg a value of 2 indicates the partial is the third tag on this line. + */ + +export function parseTemplate(template: string, tags: string[]): Token[] { + if (!template) { + return []; + } + let lineHasNonSpace = false; + const sections = []; // Stack to hold section tokens + const tokens: Array = []; // Buffer to hold the tokens + + let spaces: number[] = []; // Indices of whitespace tokens on the current line + let hasTag = false; // Is there a {{tag}} on the current line? + let nonSpace = false; // Is there a non-space char on the current line? + let indentation = ""; // Tracks indentation for tags that use it + let tagIndex = 0; // Stores a count of number of tags encountered on a line + + // Strips all whitespace tokens array for the current line + // if there was a {{#tag}} on it and otherwise only space. + function stripSpace(): void { + if (hasTag && !nonSpace) { + while (spaces.length) { + delete tokens[spaces.pop() as number]; + } + } else { + spaces = []; + } + + hasTag = false; + nonSpace = false; + } + + let { openingTagRe, closingTagRe, closingCurlyRe } = compileTags(tags || ["{{", "}}"]); + + const scanner = new Scanner(template); + + let start: number; + let type: string; + let value: string; + let chr: string; + let token: Token; + let openSection; + + while (!scanner.eos()) { + start = scanner.pos; + + // Match any text between tags. + value = scanner.scanUntil(openingTagRe); + + if (value) { + for (let i = 0, valueLength = value.length; i < valueLength; ++i) { + chr = value.charAt(i); + + if (isWhitespace(chr)) { + spaces.push(tokens.length); + indentation += chr; + } else { + nonSpace = true; + lineHasNonSpace = true; + indentation += " "; + } + + tokens.push(["text", chr, start, start + 1]); + start += 1; + + // Check for whitespace on the current line. + if (chr === "\n") { + stripSpace(); + indentation = ""; + tagIndex = 0; + lineHasNonSpace = false; + } + } + } + + // Match the opening tag. + if (!scanner.scan(openingTagRe)) { + break; + } + + hasTag = true; + + // Get the tag type. + type = scanner.scan(tagRe) || "name"; + scanner.scan(whiteRe); + + // Get the tag value. + if (type === "=") { + value = scanner.scanUntil(equalsRe); + scanner.scan(equalsRe); + scanner.scanUntil(closingTagRe); + } else if (type === "{") { + value = scanner.scanUntil(closingCurlyRe); + scanner.scan(curlyRe); + scanner.scanUntil(closingTagRe); + type = "&"; + } else { + value = scanner.scanUntil(closingTagRe); + } + + // Match the closing tag. + if (!scanner.scan(closingTagRe)) { + throw new Error("Unclosed tag at " + scanner.pos); + } + + if (type == ">") { + token = [type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace]; + } else { + token = [type, value, start, scanner.pos]; + } + tagIndex++; + tokens.push(token); + + if (type === "#" || type === "^") { + sections.push(token); + } else if (type === "/") { + // Check section nesting. + openSection = sections.pop(); + + if (!openSection) { + throw new Error('Unopened section "' + value + '" at ' + start); + } + + if (openSection[1] !== value) { + throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); + } + } else if (type === "name" || type === "{" || type === "&") { + nonSpace = true; + } else if (type === "=") { + ({ openingTagRe, closingTagRe, closingCurlyRe } = compileTags(value)); + } + } + + stripSpace(); + + // Make sure there are no open sections when we're done. + openSection = sections.pop(); + + if (openSection) { + throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); + } + + return nestTokens(squashTokens(tokens)); +} diff --git a/src/lib/util.ts b/src/lib/util.ts new file mode 100644 index 0000000..8978569 --- /dev/null +++ b/src/lib/util.ts @@ -0,0 +1,80 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +// deno-lint-ignore-file ban-types no-explicit-any + +export function isFunction(obj: any): obj is Function { + return typeof obj === "function"; +} + +export function objectToString(o: any): string { + return Object.prototype.toString.call(o); +} + +export function isArray(a: any): a is Array { + return Array.isArray(a) || objectToString(a) === "[object Array]"; +} + +export function typeStr(obj: any): string { + return isArray(obj) ? "array" : typeof obj; +} + +export function escapeRegExp(string: string): string { + return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); +} + +/** + * Null safe way of checking whether or not an object, + * including its prototype, has a given property + * + * @param obj The object to check + * @param propName The property name to check for + * @returns true if the object has the property, false otherwise + */ +export function hasProperty(obj: any, propName: string): boolean { + return obj != null && typeof obj === "object" && (propName in obj); +} + +/** + * Safe way of detecting whether or not the given thing is a primitive and + * whether it has the given property + * + * @param primitive The thing to check + * @param propName The property name to check for + * @returns true if the thing is a primitive and has the property, false otherwise + */ +export function primitiveHasOwnProperty(primitive: any, propName: string): boolean { + return ( + primitive != null && + typeof primitive !== "object" && + primitive.hasOwnProperty && + // deno-lint-ignore no-prototype-builtins + primitive.hasOwnProperty(propName) + ); +} + +const regExpTest = RegExp.prototype.test; +export function testRegExp(re: RegExp, string: string): boolean { + return regExpTest.call(re, string); +} + +const nonSpaceRe = /\S/; +export function isWhitespace(string: string): boolean { + return !testRegExp(nonSpaceRe, string); +} + +const entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "/": "/", + "`": "`", + "=": "=", +}; + +export function escapeHtml(string: string): string { + return String(string).replace(/[&<>"'`=\/]/g, (s) => { + return entityMap[s as keyof typeof entityMap] as string; + }); +} diff --git a/src/mod.ts b/src/mod.ts new file mode 100644 index 0000000..5845c99 --- /dev/null +++ b/src/mod.ts @@ -0,0 +1,8 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import mustache, { Mustache } from "./lib/Mustache.ts"; +export type { Mustache }; +export type { Config } from "./lib/Writer.ts"; +export type { Context } from "./lib/Context.ts"; + +export default mustache; diff --git a/test/Context.test.ts b/test/Context.test.ts new file mode 100644 index 0000000..a579d10 --- /dev/null +++ b/test/Context.test.ts @@ -0,0 +1,76 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { assertEquals, beforeEach, describe, it } from "../dev_deps.ts"; + +import { Context } from "../src/lib/Context.ts"; + +describe("A new Mustache.Context", () => { + let context: Context; + + beforeEach(() => { + context = new Context({ name: "parent", message: "hi", a: { b: "b" } }); + }); + + it("is able to lookup properties of its own view", () => { + assertEquals(context.lookup("name"), "parent"); + }); + + it("is able to lookup nested properties of its own view", () => { + assertEquals(context.lookup("a.b"), "b"); + }); + + describe("when pushed", () => { + beforeEach(() => { + context = context.push({ name: "child", c: { d: "d" } }); + }); + + describe("returns the child context", () => { + it("has child name", () => { + assertEquals(context.view.name, "child"); + }); + + it("has parent name", () => { + assertEquals(context.parent?.view.name, "parent"); + }); + + it("is able to lookup properties of its own view", () => { + assertEquals(context.lookup("name"), "child"); + }); + + it("is able to lookup properties of the parent context's view", () => { + assertEquals(context.lookup("message"), "hi"); + }); + + it("is able to lookup nested properties of its own view", () => { + assertEquals(context.lookup("c.d"), "d"); + }); + }); + + it("is able to lookup nested properties of its parent view", () => { + assertEquals(context.lookup("a.b"), "b"); + }); + }); +}); + +describe("A Mustache.Context", () => { + let context: Context; + + describe("with an empty string in the lookup chain", () => { + let view: Record>; + + beforeEach(() => { + view = { + a: { + b: "b", + "": "empty", + }, + }; + (view.a as Record).b = "value"; + context = new Context(view); + }); + + it("is able to lookup a nested property", () => { + assertEquals(context.lookup("a.b"), (view.a as Record).b); + }); + }); +}); diff --git a/test/Scanner.test.ts b/test/Scanner.test.ts new file mode 100644 index 0000000..73653ab --- /dev/null +++ b/test/Scanner.test.ts @@ -0,0 +1,90 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { assert, assertEquals, beforeEach, describe, it } from "../dev_deps.ts"; + +import { Scanner } from "../src/lib/Scanner.ts"; + +describe("A new Mustache.Scanner", () => { + describe("for an empty string", () => { + it("is at the end", () => { + const scanner = new Scanner(""); + assert(scanner.eos()); + }); + }); + + describe("for a non-empty string", () => { + let scanner: Scanner; + beforeEach(() => { + scanner = new Scanner("a b c"); + }); + + describe("scan", () => { + describe("has a string property", () => { + it("returns the string", () => { + assertEquals(scanner.string, "a b c"); + }); + it("returns the tail", () => { + assertEquals(scanner.tail, "a b c"); + }); + }); + + describe("when the RegExp matches the entire string", () => { + it("returns the entire string", () => { + const match = scanner.scan(/a b c/); + assertEquals(match, scanner.string); + assert(scanner.eos()); + }); + }); + + describe("when the RegExp matches at index 0", () => { + it("returns the portion of the string that matched", () => { + const match = scanner.scan(/a/); + assertEquals(match, "a"); + assertEquals(scanner.pos, 1); + }); + }); + + describe("when the RegExp matches at some index other than 0", () => { + it("returns the empty string", () => { + const match = scanner.scan(/b/); + assertEquals(match, ""); + assertEquals(scanner.pos, 0); + }); + }); + + describe("when the RegExp does not match", () => { + it("returns the empty string", () => { + const match = scanner.scan(/z/); + assertEquals(match, ""); + assertEquals(scanner.pos, 0); + }); + }); + }); // scan + + describe("scanUntil", () => { + describe("when the RegExp matches at index 0", () => { + it("returns the empty string", () => { + const match = scanner.scanUntil(/a/); + assertEquals(match, ""); + assertEquals(scanner.pos, 0); + }); + }); + + describe("when the RegExp matches at some index other than 0", () => { + it("returns the string up to that index", () => { + const match = scanner.scanUntil(/b/); + assertEquals(match, "a "); + assertEquals(scanner.pos, 2); + }); + }); + + describe("when the RegExp does not match", () => { + it("returns the entire string", () => { + const match = scanner.scanUntil(/z/); + assertEquals(match, scanner.string); + assert(scanner.eos()); + }); + }); + }); // scanUntil + }); // for a non-empty string +}); diff --git a/test/helpers/_files/ampersand_escape.js b/test/helpers/_files/ampersand_escape.js new file mode 100644 index 0000000..07da0c7 --- /dev/null +++ b/test/helpers/_files/ampersand_escape.js @@ -0,0 +1,3 @@ +({ + message: 'Some ' +}); diff --git a/test/helpers/_files/ampersand_escape.mustache b/test/helpers/_files/ampersand_escape.mustache new file mode 100644 index 0000000..6501a48 --- /dev/null +++ b/test/helpers/_files/ampersand_escape.mustache @@ -0,0 +1 @@ +{{&message}} diff --git a/test/helpers/_files/ampersand_escape.txt b/test/helpers/_files/ampersand_escape.txt new file mode 100644 index 0000000..2ed3fd3 --- /dev/null +++ b/test/helpers/_files/ampersand_escape.txt @@ -0,0 +1 @@ +Some diff --git a/test/helpers/_files/apostrophe.js b/test/helpers/_files/apostrophe.js new file mode 100644 index 0000000..7acfce0 --- /dev/null +++ b/test/helpers/_files/apostrophe.js @@ -0,0 +1,4 @@ +({ + 'apos': "'", + 'control': 'X' +}); diff --git a/test/helpers/_files/apostrophe.mustache b/test/helpers/_files/apostrophe.mustache new file mode 100644 index 0000000..e8687aa --- /dev/null +++ b/test/helpers/_files/apostrophe.mustache @@ -0,0 +1 @@ +{{apos}}{{control}} diff --git a/test/helpers/_files/apostrophe.txt b/test/helpers/_files/apostrophe.txt new file mode 100644 index 0000000..4427c30 --- /dev/null +++ b/test/helpers/_files/apostrophe.txt @@ -0,0 +1 @@ +'X diff --git a/test/helpers/_files/array_of_strings.js b/test/helpers/_files/array_of_strings.js new file mode 100644 index 0000000..6eb9e63 --- /dev/null +++ b/test/helpers/_files/array_of_strings.js @@ -0,0 +1,3 @@ +({ + array_of_strings: ['hello', 'world'] +}); diff --git a/test/helpers/_files/array_of_strings.mustache b/test/helpers/_files/array_of_strings.mustache new file mode 100644 index 0000000..4d65738 --- /dev/null +++ b/test/helpers/_files/array_of_strings.mustache @@ -0,0 +1 @@ +{{#array_of_strings}}{{.}} {{/array_of_strings}} diff --git a/test/helpers/_files/array_of_strings.txt b/test/helpers/_files/array_of_strings.txt new file mode 100644 index 0000000..4a1f475 --- /dev/null +++ b/test/helpers/_files/array_of_strings.txt @@ -0,0 +1 @@ +hello world diff --git a/test/helpers/_files/avoids_obj_prototype_in_view_cache.js b/test/helpers/_files/avoids_obj_prototype_in_view_cache.js new file mode 100644 index 0000000..ae88d2b --- /dev/null +++ b/test/helpers/_files/avoids_obj_prototype_in_view_cache.js @@ -0,0 +1,4 @@ +({ + valueOf: 'Avoids methods', + watch: 'in Object.prototype' +}); diff --git a/test/helpers/_files/avoids_obj_prototype_in_view_cache.mustache b/test/helpers/_files/avoids_obj_prototype_in_view_cache.mustache new file mode 100644 index 0000000..f6e469b --- /dev/null +++ b/test/helpers/_files/avoids_obj_prototype_in_view_cache.mustache @@ -0,0 +1 @@ +{{valueOf}} {{watch}} \ No newline at end of file diff --git a/test/helpers/_files/avoids_obj_prototype_in_view_cache.txt b/test/helpers/_files/avoids_obj_prototype_in_view_cache.txt new file mode 100644 index 0000000..2621d99 --- /dev/null +++ b/test/helpers/_files/avoids_obj_prototype_in_view_cache.txt @@ -0,0 +1 @@ +Avoids methods in Object.prototype \ No newline at end of file diff --git a/test/helpers/_files/backslashes.js b/test/helpers/_files/backslashes.js new file mode 100644 index 0000000..0a5d845 --- /dev/null +++ b/test/helpers/_files/backslashes.js @@ -0,0 +1,3 @@ +({ + value: '\\abc' +}); diff --git a/test/helpers/_files/backslashes.mustache b/test/helpers/_files/backslashes.mustache new file mode 100644 index 0000000..fe7745b --- /dev/null +++ b/test/helpers/_files/backslashes.mustache @@ -0,0 +1,7 @@ +* {{value}} +* {{{value}}} +* {{&value}} + diff --git a/test/helpers/_files/backslashes.txt b/test/helpers/_files/backslashes.txt new file mode 100644 index 0000000..038dd37 --- /dev/null +++ b/test/helpers/_files/backslashes.txt @@ -0,0 +1,7 @@ +* \abc +* \abc +* \abc + diff --git a/test/helpers/_files/bug_11_eating_whitespace.js b/test/helpers/_files/bug_11_eating_whitespace.js new file mode 100644 index 0000000..3a91f96 --- /dev/null +++ b/test/helpers/_files/bug_11_eating_whitespace.js @@ -0,0 +1,3 @@ +({ + tag: 'yo' +}); diff --git a/test/helpers/_files/bug_11_eating_whitespace.mustache b/test/helpers/_files/bug_11_eating_whitespace.mustache new file mode 100644 index 0000000..8d5cd92 --- /dev/null +++ b/test/helpers/_files/bug_11_eating_whitespace.mustache @@ -0,0 +1 @@ +{{tag}} foo diff --git a/test/helpers/_files/bug_11_eating_whitespace.txt b/test/helpers/_files/bug_11_eating_whitespace.txt new file mode 100644 index 0000000..f5bbc85 --- /dev/null +++ b/test/helpers/_files/bug_11_eating_whitespace.txt @@ -0,0 +1 @@ +yo foo diff --git a/test/helpers/_files/bug_length_property.js b/test/helpers/_files/bug_length_property.js new file mode 100644 index 0000000..c63899e --- /dev/null +++ b/test/helpers/_files/bug_length_property.js @@ -0,0 +1,3 @@ +({ + length: 'hello' +}); diff --git a/test/helpers/_files/bug_length_property.mustache b/test/helpers/_files/bug_length_property.mustache new file mode 100644 index 0000000..b000887 --- /dev/null +++ b/test/helpers/_files/bug_length_property.mustache @@ -0,0 +1 @@ +{{#length}}The length variable is: {{length}}{{/length}} diff --git a/test/helpers/_files/bug_length_property.txt b/test/helpers/_files/bug_length_property.txt new file mode 100644 index 0000000..f5355d3 --- /dev/null +++ b/test/helpers/_files/bug_length_property.txt @@ -0,0 +1 @@ +The length variable is: hello diff --git a/test/helpers/_files/changing_delimiters.js b/test/helpers/_files/changing_delimiters.js new file mode 100644 index 0000000..0bb3b13 --- /dev/null +++ b/test/helpers/_files/changing_delimiters.js @@ -0,0 +1,4 @@ +({ + 'foo': 'foooooooooooooo', + 'bar': 'bar!' +}); diff --git a/test/helpers/_files/changing_delimiters.mustache b/test/helpers/_files/changing_delimiters.mustache new file mode 100644 index 0000000..0cd044c --- /dev/null +++ b/test/helpers/_files/changing_delimiters.mustache @@ -0,0 +1 @@ +{{=<% %>=}}<% foo %> {{foo}} <%{bar}%> {{{bar}}} diff --git a/test/helpers/_files/changing_delimiters.txt b/test/helpers/_files/changing_delimiters.txt new file mode 100644 index 0000000..1b1510d --- /dev/null +++ b/test/helpers/_files/changing_delimiters.txt @@ -0,0 +1 @@ +foooooooooooooo {{foo}} bar! {{{bar}}} diff --git a/test/helpers/_files/check_falsy.js b/test/helpers/_files/check_falsy.js new file mode 100644 index 0000000..aeb7422 --- /dev/null +++ b/test/helpers/_files/check_falsy.js @@ -0,0 +1,7 @@ +({ + number: function (text, render) { + return function (text, render) { + return +render(text); + }; + } +}); diff --git a/test/helpers/_files/check_falsy.mustache b/test/helpers/_files/check_falsy.mustache new file mode 100644 index 0000000..30e2547 --- /dev/null +++ b/test/helpers/_files/check_falsy.mustache @@ -0,0 +1 @@ +

{{#number}}0{{/number}}

diff --git a/test/helpers/_files/check_falsy.txt b/test/helpers/_files/check_falsy.txt new file mode 100644 index 0000000..3bb2f51 --- /dev/null +++ b/test/helpers/_files/check_falsy.txt @@ -0,0 +1 @@ +

0

diff --git a/test/helpers/_files/comments.js b/test/helpers/_files/comments.js new file mode 100644 index 0000000..af291cb --- /dev/null +++ b/test/helpers/_files/comments.js @@ -0,0 +1,5 @@ +({ + title: function () { + return 'A Comedy of Errors'; + } +}); diff --git a/test/helpers/_files/comments.mustache b/test/helpers/_files/comments.mustache new file mode 100644 index 0000000..5036801 --- /dev/null +++ b/test/helpers/_files/comments.mustache @@ -0,0 +1 @@ +

{{title}}{{! just something interesting... or not... }}

diff --git a/test/helpers/_files/comments.txt b/test/helpers/_files/comments.txt new file mode 100644 index 0000000..0133517 --- /dev/null +++ b/test/helpers/_files/comments.txt @@ -0,0 +1 @@ +

A Comedy of Errors

diff --git a/test/helpers/_files/complex.js b/test/helpers/_files/complex.js new file mode 100644 index 0000000..d6c3ae2 --- /dev/null +++ b/test/helpers/_files/complex.js @@ -0,0 +1,19 @@ +({ + header: function () { + return 'Colors'; + }, + item: [ + {name: 'red', current: true, url: '#Red'}, + {name: 'green', current: false, url: '#Green'}, + {name: 'blue', current: false, url: '#Blue'} + ], + link: function () { + return this['current'] !== true; + }, + list: function () { + return this.item.length !== 0; + }, + empty: function () { + return this.item.length === 0; + } +}); diff --git a/test/helpers/_files/complex.mustache b/test/helpers/_files/complex.mustache new file mode 100644 index 0000000..869a4f0 --- /dev/null +++ b/test/helpers/_files/complex.mustache @@ -0,0 +1,16 @@ +

{{header}}

+{{#list}} +
    + {{#item}} + {{#current}} +
  • {{name}}
  • + {{/current}} + {{#link}} +
  • {{name}}
  • + {{/link}} + {{/item}} +
+{{/list}} +{{#empty}} +

The list is empty.

+{{/empty}} diff --git a/test/helpers/_files/complex.txt b/test/helpers/_files/complex.txt new file mode 100644 index 0000000..596d3f6 --- /dev/null +++ b/test/helpers/_files/complex.txt @@ -0,0 +1,6 @@ +

Colors

+ diff --git a/test/helpers/_files/context_lookup.js b/test/helpers/_files/context_lookup.js new file mode 100644 index 0000000..b9f891c --- /dev/null +++ b/test/helpers/_files/context_lookup.js @@ -0,0 +1,8 @@ +({ + 'outer': { + 'id': 1, + 'second': { + 'nothing': 2 + } + } +}); diff --git a/test/helpers/_files/context_lookup.mustache b/test/helpers/_files/context_lookup.mustache new file mode 100644 index 0000000..3c7b767 --- /dev/null +++ b/test/helpers/_files/context_lookup.mustache @@ -0,0 +1 @@ +{{#outer}}{{#second}}{{id}}{{/second}}{{/outer}} diff --git a/test/helpers/_files/context_lookup.txt b/test/helpers/_files/context_lookup.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/test/helpers/_files/context_lookup.txt @@ -0,0 +1 @@ +1 diff --git a/test/helpers/_files/delimiters.js b/test/helpers/_files/delimiters.js new file mode 100644 index 0000000..b4c9f50 --- /dev/null +++ b/test/helpers/_files/delimiters.js @@ -0,0 +1,6 @@ +({ + first: 'It worked the first time.', + second: 'And it worked the second time.', + third: 'Then, surprisingly, it worked the third time.', + fourth: 'Fourth time also fine!.' +}); diff --git a/test/helpers/_files/delimiters.mustache b/test/helpers/_files/delimiters.mustache new file mode 100644 index 0000000..7fac846 --- /dev/null +++ b/test/helpers/_files/delimiters.mustache @@ -0,0 +1,7 @@ +{{=<% %>=}}* +<% first %> +* <% second %> +<%=| |=%> +* | third | +|={{ }}=| +* {{ fourth }} diff --git a/test/helpers/_files/delimiters.txt b/test/helpers/_files/delimiters.txt new file mode 100644 index 0000000..698a6bb --- /dev/null +++ b/test/helpers/_files/delimiters.txt @@ -0,0 +1,5 @@ +* +It worked the first time. +* And it worked the second time. +* Then, surprisingly, it worked the third time. +* Fourth time also fine!. diff --git a/test/helpers/_files/disappearing_whitespace.js b/test/helpers/_files/disappearing_whitespace.js new file mode 100644 index 0000000..1d2f392 --- /dev/null +++ b/test/helpers/_files/disappearing_whitespace.js @@ -0,0 +1,4 @@ +({ + bedrooms: true, + total: 1 +}); diff --git a/test/helpers/_files/disappearing_whitespace.mustache b/test/helpers/_files/disappearing_whitespace.mustache new file mode 100644 index 0000000..16c16e0 --- /dev/null +++ b/test/helpers/_files/disappearing_whitespace.mustache @@ -0,0 +1 @@ +{{#bedrooms}}{{total}}{{/bedrooms}} BED diff --git a/test/helpers/_files/disappearing_whitespace.txt b/test/helpers/_files/disappearing_whitespace.txt new file mode 100644 index 0000000..66e98ef --- /dev/null +++ b/test/helpers/_files/disappearing_whitespace.txt @@ -0,0 +1 @@ +1 BED diff --git a/test/helpers/_files/dot_notation.js b/test/helpers/_files/dot_notation.js new file mode 100644 index 0000000..8dbef43 --- /dev/null +++ b/test/helpers/_files/dot_notation.js @@ -0,0 +1,24 @@ +({ + name: 'A Book', + authors: ['John Power', 'Jamie Walsh'], + price: { + value: 200, + vat: function () { + return this.value * 0.2; + }, + currency: { + symbol: '$', + name: 'USD' + } + }, + availability: { + status: true, + text: 'In Stock' + }, + // And now, some truthy false values + truthy: { + zero: 0, + notTrue: false + }, + singletonList: [{singletonItem: 'singleton item'}] +}); diff --git a/test/helpers/_files/dot_notation.mustache b/test/helpers/_files/dot_notation.mustache new file mode 100644 index 0000000..1ef2209 --- /dev/null +++ b/test/helpers/_files/dot_notation.mustache @@ -0,0 +1,12 @@ + +

{{name}}

+

Authors:

    {{#authors}}
  • {{.}}
  • {{/authors}}

+

Price: {{{price.currency.symbol}}}{{price.value}} {{#price.currency}}{{name}} {{availability.text}}{{/price.currency}}

+

VAT: {{{price.currency.symbol}}}{{#price}}{{vat}}{{/price}}

+ +

Test truthy false values:

+

Zero: {{truthy.zero}}

+

False: {{truthy.notTrue}}

+

length of string should be rendered: {{price.currency.name.length}}

+

length of string in a list should be rendered: {{#singletonList}}{{singletonItem.length}}{{/singletonList}}

+

length of an array should be rendered: {{authors.length}}

diff --git a/test/helpers/_files/dot_notation.txt b/test/helpers/_files/dot_notation.txt new file mode 100644 index 0000000..c71ab27 --- /dev/null +++ b/test/helpers/_files/dot_notation.txt @@ -0,0 +1,12 @@ + +

A Book

+

Authors:

  • John Power
  • Jamie Walsh

+

Price: $200 USD In Stock

+

VAT: $40

+ +

Test truthy false values:

+

Zero: 0

+

False: false

+

length of string should be rendered: 3

+

length of string in a list should be rendered: 14

+

length of an array should be rendered: 2

diff --git a/test/helpers/_files/double_render.js b/test/helpers/_files/double_render.js new file mode 100644 index 0000000..d8ade03 --- /dev/null +++ b/test/helpers/_files/double_render.js @@ -0,0 +1,5 @@ +({ + foo: true, + bar: '{{win}}', + win: 'FAIL' +}); diff --git a/test/helpers/_files/double_render.mustache b/test/helpers/_files/double_render.mustache new file mode 100644 index 0000000..4500fd7 --- /dev/null +++ b/test/helpers/_files/double_render.mustache @@ -0,0 +1 @@ +{{#foo}}{{bar}}{{/foo}} diff --git a/test/helpers/_files/double_render.txt b/test/helpers/_files/double_render.txt new file mode 100644 index 0000000..b6e652d --- /dev/null +++ b/test/helpers/_files/double_render.txt @@ -0,0 +1 @@ +{{win}} diff --git a/test/helpers/_files/empty_list.js b/test/helpers/_files/empty_list.js new file mode 100644 index 0000000..c40f6d8 --- /dev/null +++ b/test/helpers/_files/empty_list.js @@ -0,0 +1,3 @@ +({ + jobs: [] +}); diff --git a/test/helpers/_files/empty_list.mustache b/test/helpers/_files/empty_list.mustache new file mode 100644 index 0000000..4fdf13d --- /dev/null +++ b/test/helpers/_files/empty_list.mustache @@ -0,0 +1,4 @@ +These are the jobs: +{{#jobs}} +{{.}} +{{/jobs}} diff --git a/test/helpers/_files/empty_list.txt b/test/helpers/_files/empty_list.txt new file mode 100644 index 0000000..d9b4a67 --- /dev/null +++ b/test/helpers/_files/empty_list.txt @@ -0,0 +1 @@ +These are the jobs: diff --git a/test/helpers/_files/empty_sections.js b/test/helpers/_files/empty_sections.js new file mode 100644 index 0000000..b3bf1dd --- /dev/null +++ b/test/helpers/_files/empty_sections.js @@ -0,0 +1 @@ +({}); diff --git a/test/helpers/_files/empty_sections.mustache b/test/helpers/_files/empty_sections.mustache new file mode 100644 index 0000000..b6065db --- /dev/null +++ b/test/helpers/_files/empty_sections.mustache @@ -0,0 +1 @@ +{{#foo}}{{/foo}}foo{{#bar}}{{/bar}} diff --git a/test/helpers/_files/empty_sections.txt b/test/helpers/_files/empty_sections.txt new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/test/helpers/_files/empty_sections.txt @@ -0,0 +1 @@ +foo diff --git a/test/helpers/_files/empty_string.js b/test/helpers/_files/empty_string.js new file mode 100644 index 0000000..59de15d --- /dev/null +++ b/test/helpers/_files/empty_string.js @@ -0,0 +1,6 @@ +({ + description: 'That is all!', + child: { + description: '' + } +}); diff --git a/test/helpers/_files/empty_string.mustache b/test/helpers/_files/empty_string.mustache new file mode 100644 index 0000000..f568441 --- /dev/null +++ b/test/helpers/_files/empty_string.mustache @@ -0,0 +1 @@ +{{description}}{{#child}}{{description}}{{/child}} diff --git a/test/helpers/_files/empty_string.txt b/test/helpers/_files/empty_string.txt new file mode 100644 index 0000000..22e2a6e --- /dev/null +++ b/test/helpers/_files/empty_string.txt @@ -0,0 +1 @@ +That is all! diff --git a/test/helpers/_files/empty_template.js b/test/helpers/_files/empty_template.js new file mode 100644 index 0000000..b3bf1dd --- /dev/null +++ b/test/helpers/_files/empty_template.js @@ -0,0 +1 @@ +({}); diff --git a/test/helpers/_files/empty_template.mustache b/test/helpers/_files/empty_template.mustache new file mode 100644 index 0000000..bb2367a --- /dev/null +++ b/test/helpers/_files/empty_template.mustache @@ -0,0 +1 @@ +

Test

\ No newline at end of file diff --git a/test/helpers/_files/empty_template.txt b/test/helpers/_files/empty_template.txt new file mode 100644 index 0000000..bb2367a --- /dev/null +++ b/test/helpers/_files/empty_template.txt @@ -0,0 +1 @@ +

Test

\ No newline at end of file diff --git a/test/helpers/_files/error_not_found.js b/test/helpers/_files/error_not_found.js new file mode 100644 index 0000000..6dcb14f --- /dev/null +++ b/test/helpers/_files/error_not_found.js @@ -0,0 +1,3 @@ +({ + bar: 2 +}); diff --git a/test/helpers/_files/error_not_found.mustache b/test/helpers/_files/error_not_found.mustache new file mode 100644 index 0000000..24369f7 --- /dev/null +++ b/test/helpers/_files/error_not_found.mustache @@ -0,0 +1 @@ +{{foo}} \ No newline at end of file diff --git a/test/helpers/_files/error_not_found.txt b/test/helpers/_files/error_not_found.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/_files/escaped.js b/test/helpers/_files/escaped.js new file mode 100644 index 0000000..8345ecb --- /dev/null +++ b/test/helpers/_files/escaped.js @@ -0,0 +1,7 @@ +({ + title: function () { + return 'Bear > Shark'; + }, + symbol: null, + entities: "" \"'<>`=/" +}); diff --git a/test/helpers/_files/escaped.mustache b/test/helpers/_files/escaped.mustache new file mode 100644 index 0000000..5c44268 --- /dev/null +++ b/test/helpers/_files/escaped.mustache @@ -0,0 +1,2 @@ +

{{title}}{{symbol}}

+And even {{entities}}, but not {{{entities}}}. diff --git a/test/helpers/_files/escaped.txt b/test/helpers/_files/escaped.txt new file mode 100644 index 0000000..182f99e --- /dev/null +++ b/test/helpers/_files/escaped.txt @@ -0,0 +1,2 @@ +

Bear > Shark

+And even &quot; "'<>`=/, but not " "'<>`=/. diff --git a/test/helpers/_files/falsy.js b/test/helpers/_files/falsy.js new file mode 100644 index 0000000..e3fd422 --- /dev/null +++ b/test/helpers/_files/falsy.js @@ -0,0 +1,8 @@ +({ + 'emptyString': '', + 'emptyArray': [], + 'zero': 0, + 'null': null, + 'undefined': undefined, + 'NaN': 0/0 +}); \ No newline at end of file diff --git a/test/helpers/_files/falsy.mustache b/test/helpers/_files/falsy.mustache new file mode 100644 index 0000000..f3698da --- /dev/null +++ b/test/helpers/_files/falsy.mustache @@ -0,0 +1,12 @@ +{{#emptyString}}empty string{{/emptyString}} +{{^emptyString}}inverted empty string{{/emptyString}} +{{#emptyArray}}empty array{{/emptyArray}} +{{^emptyArray}}inverted empty array{{/emptyArray}} +{{#zero}}zero{{/zero}} +{{^zero}}inverted zero{{/zero}} +{{#null}}null{{/null}} +{{^null}}inverted null{{/null}} +{{#undefined}}undefined{{/undefined}} +{{^undefined}}inverted undefined{{/undefined}} +{{#NaN}}NaN{{/NaN}} +{{^NaN}}inverted NaN{{/NaN}} diff --git a/test/helpers/_files/falsy.txt b/test/helpers/_files/falsy.txt new file mode 100644 index 0000000..9b7cde3 --- /dev/null +++ b/test/helpers/_files/falsy.txt @@ -0,0 +1,12 @@ + +inverted empty string + +inverted empty array + +inverted zero + +inverted null + +inverted undefined + +inverted NaN diff --git a/test/helpers/_files/falsy_array.js b/test/helpers/_files/falsy_array.js new file mode 100644 index 0000000..209c589 --- /dev/null +++ b/test/helpers/_files/falsy_array.js @@ -0,0 +1,10 @@ +({ + 'list': [ + ['', 'emptyString'], + [[], 'emptyArray'], + [0, 'zero'], + [null, 'null'], + [undefined, 'undefined'], + [0/0, 'NaN'] + ] +}); \ No newline at end of file diff --git a/test/helpers/_files/falsy_array.mustache b/test/helpers/_files/falsy_array.mustache new file mode 100644 index 0000000..2be7b37 --- /dev/null +++ b/test/helpers/_files/falsy_array.mustache @@ -0,0 +1,3 @@ +{{#list}} +{{#.}}{{#.}}{{.}}{{/.}}{{^.}}inverted {{/.}}{{/.}} +{{/list}} \ No newline at end of file diff --git a/test/helpers/_files/falsy_array.txt b/test/helpers/_files/falsy_array.txt new file mode 100644 index 0000000..6f24529 --- /dev/null +++ b/test/helpers/_files/falsy_array.txt @@ -0,0 +1,6 @@ +inverted emptyString +inverted emptyArray +inverted zero +inverted null +inverted undefined +inverted NaN diff --git a/test/helpers/_files/grandparent_context.js b/test/helpers/_files/grandparent_context.js new file mode 100644 index 0000000..4d30400 --- /dev/null +++ b/test/helpers/_files/grandparent_context.js @@ -0,0 +1,19 @@ +({ + grand_parent_id: 'grand_parent1', + parent_contexts: [ + { + parent_id: 'parent1', + child_contexts: [ + { child_id: 'parent1-child1' }, + { child_id: 'parent1-child2' } + ] + }, + { + parent_id: 'parent2', + child_contexts: [ + { child_id: 'parent2-child1' }, + { child_id: 'parent2-child2' } + ] + } + ] +}); diff --git a/test/helpers/_files/grandparent_context.mustache b/test/helpers/_files/grandparent_context.mustache new file mode 100644 index 0000000..e6c07a2 --- /dev/null +++ b/test/helpers/_files/grandparent_context.mustache @@ -0,0 +1,10 @@ +{{grand_parent_id}} +{{#parent_contexts}} +{{grand_parent_id}} +{{parent_id}} +{{#child_contexts}} +{{grand_parent_id}} +{{parent_id}} +{{child_id}} +{{/child_contexts}} +{{/parent_contexts}} diff --git a/test/helpers/_files/grandparent_context.txt b/test/helpers/_files/grandparent_context.txt new file mode 100644 index 0000000..64996ad --- /dev/null +++ b/test/helpers/_files/grandparent_context.txt @@ -0,0 +1,17 @@ +grand_parent1 +grand_parent1 +parent1 +grand_parent1 +parent1 +parent1-child1 +grand_parent1 +parent1 +parent1-child2 +grand_parent1 +parent2 +grand_parent1 +parent2 +parent2-child1 +grand_parent1 +parent2 +parent2-child2 diff --git a/test/helpers/_files/higher_order_sections.js b/test/helpers/_files/higher_order_sections.js new file mode 100644 index 0000000..6a4a310 --- /dev/null +++ b/test/helpers/_files/higher_order_sections.js @@ -0,0 +1,9 @@ +({ + name: 'Tater', + helper: 'To tinker?', + bolder: function () { + return function (text, render) { + return text + ' => ' + render(text) + ' ' + this.helper; + }; + } +}); diff --git a/test/helpers/_files/higher_order_sections.mustache b/test/helpers/_files/higher_order_sections.mustache new file mode 100644 index 0000000..04f5318 --- /dev/null +++ b/test/helpers/_files/higher_order_sections.mustache @@ -0,0 +1 @@ +{{#bolder}}Hi {{name}}.{{/bolder}} diff --git a/test/helpers/_files/higher_order_sections.txt b/test/helpers/_files/higher_order_sections.txt new file mode 100644 index 0000000..be50ad7 --- /dev/null +++ b/test/helpers/_files/higher_order_sections.txt @@ -0,0 +1 @@ +Hi {{name}}. => Hi Tater. To tinker? diff --git a/test/helpers/_files/implicit_iterator.js b/test/helpers/_files/implicit_iterator.js new file mode 100644 index 0000000..47a6b68 --- /dev/null +++ b/test/helpers/_files/implicit_iterator.js @@ -0,0 +1,8 @@ +({ + data: { + author: { + twitter_id: 819606, + name: 'janl' + } + } +}); diff --git a/test/helpers/_files/implicit_iterator.mustache b/test/helpers/_files/implicit_iterator.mustache new file mode 100644 index 0000000..ae31f34 --- /dev/null +++ b/test/helpers/_files/implicit_iterator.mustache @@ -0,0 +1,7 @@ +{{# data.author.twitter_id }} + +{{/ data.author.twitter_id }} + +{{# data.author.name }} + +{{/ data.author.name }} diff --git a/test/helpers/_files/implicit_iterator.txt b/test/helpers/_files/implicit_iterator.txt new file mode 100644 index 0000000..0fccefd --- /dev/null +++ b/test/helpers/_files/implicit_iterator.txt @@ -0,0 +1,3 @@ + + + diff --git a/test/helpers/_files/included_tag.js b/test/helpers/_files/included_tag.js new file mode 100644 index 0000000..2dfc0c3 --- /dev/null +++ b/test/helpers/_files/included_tag.js @@ -0,0 +1,3 @@ +({ + html: 'I like {{mustache}}' +}); diff --git a/test/helpers/_files/included_tag.mustache b/test/helpers/_files/included_tag.mustache new file mode 100644 index 0000000..70631c2 --- /dev/null +++ b/test/helpers/_files/included_tag.mustache @@ -0,0 +1 @@ +You said "{{{html}}}" today diff --git a/test/helpers/_files/included_tag.txt b/test/helpers/_files/included_tag.txt new file mode 100644 index 0000000..1af4556 --- /dev/null +++ b/test/helpers/_files/included_tag.txt @@ -0,0 +1 @@ +You said "I like {{mustache}}" today diff --git a/test/helpers/_files/inverted_section.js b/test/helpers/_files/inverted_section.js new file mode 100644 index 0000000..40fcba8 --- /dev/null +++ b/test/helpers/_files/inverted_section.js @@ -0,0 +1,3 @@ +({ + 'repos': [] +}); diff --git a/test/helpers/_files/inverted_section.mustache b/test/helpers/_files/inverted_section.mustache new file mode 100644 index 0000000..b0a183b --- /dev/null +++ b/test/helpers/_files/inverted_section.mustache @@ -0,0 +1,3 @@ +{{#repos}}{{name}}{{/repos}} +{{^repos}}No repos :({{/repos}} +{{^nothin}}Hello!{{/nothin}} diff --git a/test/helpers/_files/inverted_section.txt b/test/helpers/_files/inverted_section.txt new file mode 100644 index 0000000..b421582 --- /dev/null +++ b/test/helpers/_files/inverted_section.txt @@ -0,0 +1,3 @@ + +No repos :( +Hello! diff --git a/test/helpers/_files/keys_with_questionmarks.js b/test/helpers/_files/keys_with_questionmarks.js new file mode 100644 index 0000000..345e97f --- /dev/null +++ b/test/helpers/_files/keys_with_questionmarks.js @@ -0,0 +1,5 @@ +({ + 'person?': { + name: 'Jon' + } +}); diff --git a/test/helpers/_files/keys_with_questionmarks.mustache b/test/helpers/_files/keys_with_questionmarks.mustache new file mode 100644 index 0000000..417f17f --- /dev/null +++ b/test/helpers/_files/keys_with_questionmarks.mustache @@ -0,0 +1,3 @@ +{{#person?}} + Hi {{name}}! +{{/person?}} diff --git a/test/helpers/_files/keys_with_questionmarks.txt b/test/helpers/_files/keys_with_questionmarks.txt new file mode 100644 index 0000000..0f69b94 --- /dev/null +++ b/test/helpers/_files/keys_with_questionmarks.txt @@ -0,0 +1 @@ + Hi Jon! diff --git a/test/helpers/_files/malicious_template.js b/test/helpers/_files/malicious_template.js new file mode 100644 index 0000000..b3bf1dd --- /dev/null +++ b/test/helpers/_files/malicious_template.js @@ -0,0 +1 @@ +({}); diff --git a/test/helpers/_files/malicious_template.mustache b/test/helpers/_files/malicious_template.mustache new file mode 100644 index 0000000..b956867 --- /dev/null +++ b/test/helpers/_files/malicious_template.mustache @@ -0,0 +1,5 @@ +{{"+(function () {throw "evil"})()+"}} +{{{"+(function () {throw "evil"})()+"}}} +{{> "+(function () {throw "evil"})()+"}} +{{# "+(function () {throw "evil"})()+"}} +{{/ "+(function () {throw "evil"})()+"}} diff --git a/test/helpers/_files/malicious_template.txt b/test/helpers/_files/malicious_template.txt new file mode 100644 index 0000000..139597f --- /dev/null +++ b/test/helpers/_files/malicious_template.txt @@ -0,0 +1,2 @@ + + diff --git a/test/helpers/_files/multiline_comment.js b/test/helpers/_files/multiline_comment.js new file mode 100644 index 0000000..b3bf1dd --- /dev/null +++ b/test/helpers/_files/multiline_comment.js @@ -0,0 +1 @@ +({}); diff --git a/test/helpers/_files/multiline_comment.mustache b/test/helpers/_files/multiline_comment.mustache new file mode 100644 index 0000000..dff0893 --- /dev/null +++ b/test/helpers/_files/multiline_comment.mustache @@ -0,0 +1,6 @@ +{{! + +This is a multi-line comment. + +}} +Hello world! diff --git a/test/helpers/_files/multiline_comment.txt b/test/helpers/_files/multiline_comment.txt new file mode 100644 index 0000000..cd08755 --- /dev/null +++ b/test/helpers/_files/multiline_comment.txt @@ -0,0 +1 @@ +Hello world! diff --git a/test/helpers/_files/nested_dot.js b/test/helpers/_files/nested_dot.js new file mode 100644 index 0000000..72493f6 --- /dev/null +++ b/test/helpers/_files/nested_dot.js @@ -0,0 +1 @@ +({ name: 'Bruno' }); diff --git a/test/helpers/_files/nested_dot.mustache b/test/helpers/_files/nested_dot.mustache new file mode 100644 index 0000000..12b0728 --- /dev/null +++ b/test/helpers/_files/nested_dot.mustache @@ -0,0 +1 @@ +{{#name}}Hello {{.}}{{/name}} \ No newline at end of file diff --git a/test/helpers/_files/nested_dot.txt b/test/helpers/_files/nested_dot.txt new file mode 100644 index 0000000..58df047 --- /dev/null +++ b/test/helpers/_files/nested_dot.txt @@ -0,0 +1 @@ +Hello Bruno \ No newline at end of file diff --git a/test/helpers/_files/nested_higher_order_sections.js b/test/helpers/_files/nested_higher_order_sections.js new file mode 100644 index 0000000..3ccf4d3 --- /dev/null +++ b/test/helpers/_files/nested_higher_order_sections.js @@ -0,0 +1,8 @@ +({ + bold: function () { + return function (text, render) { + return '' + render(text) + ''; + }; + }, + person: { name: 'Jonas' } +}); diff --git a/test/helpers/_files/nested_higher_order_sections.mustache b/test/helpers/_files/nested_higher_order_sections.mustache new file mode 100644 index 0000000..e312fe7 --- /dev/null +++ b/test/helpers/_files/nested_higher_order_sections.mustache @@ -0,0 +1 @@ +{{#bold}}{{#person}}My name is {{name}}!{{/person}}{{/bold}} diff --git a/test/helpers/_files/nested_higher_order_sections.txt b/test/helpers/_files/nested_higher_order_sections.txt new file mode 100644 index 0000000..0ee6a40 --- /dev/null +++ b/test/helpers/_files/nested_higher_order_sections.txt @@ -0,0 +1 @@ +My name is Jonas! diff --git a/test/helpers/_files/nested_iterating.js b/test/helpers/_files/nested_iterating.js new file mode 100644 index 0000000..333d925 --- /dev/null +++ b/test/helpers/_files/nested_iterating.js @@ -0,0 +1,8 @@ +({ + inner: [{ + foo: 'foo', + inner: [{ + bar: 'bar' + }] + }] +}); diff --git a/test/helpers/_files/nested_iterating.mustache b/test/helpers/_files/nested_iterating.mustache new file mode 100644 index 0000000..1a3bb1a --- /dev/null +++ b/test/helpers/_files/nested_iterating.mustache @@ -0,0 +1 @@ +{{#inner}}{{foo}}{{#inner}}{{bar}}{{/inner}}{{/inner}} diff --git a/test/helpers/_files/nested_iterating.txt b/test/helpers/_files/nested_iterating.txt new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/test/helpers/_files/nested_iterating.txt @@ -0,0 +1 @@ +foobar diff --git a/test/helpers/_files/nesting.js b/test/helpers/_files/nesting.js new file mode 100644 index 0000000..d0eab3a --- /dev/null +++ b/test/helpers/_files/nesting.js @@ -0,0 +1,7 @@ +({ + foo: [ + {a: {b: 1}}, + {a: {b: 2}}, + {a: {b: 3}} + ] +}); diff --git a/test/helpers/_files/nesting.mustache b/test/helpers/_files/nesting.mustache new file mode 100644 index 0000000..551366d --- /dev/null +++ b/test/helpers/_files/nesting.mustache @@ -0,0 +1,5 @@ +{{#foo}} + {{#a}} + {{b}} + {{/a}} +{{/foo}} diff --git a/test/helpers/_files/nesting.txt b/test/helpers/_files/nesting.txt new file mode 100644 index 0000000..7db34b1 --- /dev/null +++ b/test/helpers/_files/nesting.txt @@ -0,0 +1,3 @@ + 1 + 2 + 3 diff --git a/test/helpers/_files/nesting_same_name.js b/test/helpers/_files/nesting_same_name.js new file mode 100644 index 0000000..84bc5c0 --- /dev/null +++ b/test/helpers/_files/nesting_same_name.js @@ -0,0 +1,8 @@ +({ + items: [ + { + name: 'name', + items: [1, 2, 3, 4] + } + ] +}); diff --git a/test/helpers/_files/nesting_same_name.mustache b/test/helpers/_files/nesting_same_name.mustache new file mode 100644 index 0000000..777dbd6 --- /dev/null +++ b/test/helpers/_files/nesting_same_name.mustache @@ -0,0 +1 @@ +{{#items}}{{name}}{{#items}}{{.}}{{/items}}{{/items}} diff --git a/test/helpers/_files/nesting_same_name.txt b/test/helpers/_files/nesting_same_name.txt new file mode 100644 index 0000000..34fcfd3 --- /dev/null +++ b/test/helpers/_files/nesting_same_name.txt @@ -0,0 +1 @@ +name1234 diff --git a/test/helpers/_files/null_lookup_array.js b/test/helpers/_files/null_lookup_array.js new file mode 100644 index 0000000..40efeea --- /dev/null +++ b/test/helpers/_files/null_lookup_array.js @@ -0,0 +1,9 @@ +({ + 'name': 'David', + 'twitter': '@dasilvacontin', + 'farray': [ + ['Flor', '@florrts'], + ['Miquel', null], + ['Chris', undefined] + ] +}); diff --git a/test/helpers/_files/null_lookup_array.mustache b/test/helpers/_files/null_lookup_array.mustache new file mode 100644 index 0000000..0543895 --- /dev/null +++ b/test/helpers/_files/null_lookup_array.mustache @@ -0,0 +1,3 @@ +{{#farray}} +{{#.}}{{#.}}{{.}} {{/.}}{{^.}}no twitter{{/.}}{{/.}} +{{/farray}} diff --git a/test/helpers/_files/null_lookup_array.txt b/test/helpers/_files/null_lookup_array.txt new file mode 100644 index 0000000..94edf99 --- /dev/null +++ b/test/helpers/_files/null_lookup_array.txt @@ -0,0 +1,3 @@ +Flor @florrts +Miquel no twitter +Chris no twitter diff --git a/test/helpers/_files/null_lookup_object.js b/test/helpers/_files/null_lookup_object.js new file mode 100644 index 0000000..6f6b0e5 --- /dev/null +++ b/test/helpers/_files/null_lookup_object.js @@ -0,0 +1,31 @@ +({ + 'name': 'David', + 'twitter': '@dasilvacontin', + 'fobject': [ + { + 'name': 'Flor', + 'twitter': '@florrts' + }, + { + 'name': 'Miquel', + 'twitter': null + }, + { + 'name': 'Chris', + 'twitter': undefined + } + ], + 'favorites': { + 'color': 'blue', + 'president': 'Bush', + 'show': 'Futurama' + }, + 'mascot': { + 'name': 'Squid', + 'favorites': { + 'color': 'orange', + 'president': undefined, + 'show': null + } + } +}); diff --git a/test/helpers/_files/null_lookup_object.mustache b/test/helpers/_files/null_lookup_object.mustache new file mode 100644 index 0000000..243e218 --- /dev/null +++ b/test/helpers/_files/null_lookup_object.mustache @@ -0,0 +1,9 @@ +{{#fobject}} +{{name}}'s twitter: {{#twitter}}{{.}}{{/twitter}}{{^twitter}}unknown{{/twitter}}. +{{/fobject}} + +{{#mascot}} +{{name}}'s favorite color: {{#favorites.color}}{{.}}{{/favorites.color}}{{^favorites.color}}no one{{/favorites.color}}. +{{name}}'s favorite president: {{#favorites.president}}{{.}}{{/favorites.president}}{{^favorites.president}}no one{{/favorites.president}}. +{{name}}'s favorite show: {{#favorites.show}}{{.}}{{/favorites.show}}{{^favorites.show}}none{{/favorites.show}}. +{{/mascot}} diff --git a/test/helpers/_files/null_lookup_object.txt b/test/helpers/_files/null_lookup_object.txt new file mode 100644 index 0000000..0f71bdb --- /dev/null +++ b/test/helpers/_files/null_lookup_object.txt @@ -0,0 +1,7 @@ +Flor's twitter: @florrts. +Miquel's twitter: unknown. +Chris's twitter: unknown. + +Squid's favorite color: orange. +Squid's favorite president: no one. +Squid's favorite show: none. diff --git a/test/helpers/_files/null_string.js b/test/helpers/_files/null_string.js new file mode 100644 index 0000000..4fc1033 --- /dev/null +++ b/test/helpers/_files/null_string.js @@ -0,0 +1,10 @@ +({ + name: 'Elise', + glytch: true, + binary: false, + value: null, + undef: undefined, + numeric: function () { + return NaN; + } +}); diff --git a/test/helpers/_files/null_string.mustache b/test/helpers/_files/null_string.mustache new file mode 100644 index 0000000..a6f3300 --- /dev/null +++ b/test/helpers/_files/null_string.mustache @@ -0,0 +1,6 @@ +Hello {{name}} +glytch {{glytch}} +binary {{binary}} +value {{value}} +undef {{undef}} +numeric {{numeric}} diff --git a/test/helpers/_files/null_string.txt b/test/helpers/_files/null_string.txt new file mode 100644 index 0000000..bcabe0a --- /dev/null +++ b/test/helpers/_files/null_string.txt @@ -0,0 +1,6 @@ +Hello Elise +glytch true +binary false +value +undef +numeric NaN diff --git a/test/helpers/_files/null_view.js b/test/helpers/_files/null_view.js new file mode 100644 index 0000000..7f82792 --- /dev/null +++ b/test/helpers/_files/null_view.js @@ -0,0 +1,4 @@ +({ + name: 'Joe', + friends: null +}); diff --git a/test/helpers/_files/null_view.mustache b/test/helpers/_files/null_view.mustache new file mode 100644 index 0000000..115b376 --- /dev/null +++ b/test/helpers/_files/null_view.mustache @@ -0,0 +1 @@ +{{name}}'s friends: {{#friends}}{{name}}, {{/friends}} \ No newline at end of file diff --git a/test/helpers/_files/null_view.txt b/test/helpers/_files/null_view.txt new file mode 100644 index 0000000..15ed2ab --- /dev/null +++ b/test/helpers/_files/null_view.txt @@ -0,0 +1 @@ +Joe's friends: \ No newline at end of file diff --git a/test/helpers/_files/partial_array.js b/test/helpers/_files/partial_array.js new file mode 100644 index 0000000..30b3c77 --- /dev/null +++ b/test/helpers/_files/partial_array.js @@ -0,0 +1,3 @@ +({ + array: ['1', '2', '3', '4'] +}); diff --git a/test/helpers/_files/partial_array.mustache b/test/helpers/_files/partial_array.mustache new file mode 100644 index 0000000..7a336fe --- /dev/null +++ b/test/helpers/_files/partial_array.mustache @@ -0,0 +1 @@ +{{>partial}} \ No newline at end of file diff --git a/test/helpers/_files/partial_array.partial b/test/helpers/_files/partial_array.partial new file mode 100644 index 0000000..0ba652c --- /dev/null +++ b/test/helpers/_files/partial_array.partial @@ -0,0 +1,4 @@ +Here's a non-sense array of values +{{#array}} + {{.}} +{{/array}} diff --git a/test/helpers/_files/partial_array.txt b/test/helpers/_files/partial_array.txt new file mode 100644 index 0000000..892837c --- /dev/null +++ b/test/helpers/_files/partial_array.txt @@ -0,0 +1,5 @@ +Here's a non-sense array of values + 1 + 2 + 3 + 4 diff --git a/test/helpers/_files/partial_array_of_partials.js b/test/helpers/_files/partial_array_of_partials.js new file mode 100644 index 0000000..4f21b75 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials.js @@ -0,0 +1,8 @@ +({ + numbers: [ + {i: '1'}, + {i: '2'}, + {i: '3'}, + {i: '4'} + ] +}); diff --git a/test/helpers/_files/partial_array_of_partials.mustache b/test/helpers/_files/partial_array_of_partials.mustache new file mode 100644 index 0000000..1af6d68 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials.mustache @@ -0,0 +1,4 @@ +Here is some stuff! +{{#numbers}} +{{>partial}} +{{/numbers}} diff --git a/test/helpers/_files/partial_array_of_partials.partial b/test/helpers/_files/partial_array_of_partials.partial new file mode 100644 index 0000000..bdde77d --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials.partial @@ -0,0 +1 @@ +{{i}} diff --git a/test/helpers/_files/partial_array_of_partials.txt b/test/helpers/_files/partial_array_of_partials.txt new file mode 100644 index 0000000..f622375 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials.txt @@ -0,0 +1,5 @@ +Here is some stuff! +1 +2 +3 +4 diff --git a/test/helpers/_files/partial_array_of_partials_implicit.js b/test/helpers/_files/partial_array_of_partials_implicit.js new file mode 100644 index 0000000..350ad17 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials_implicit.js @@ -0,0 +1,3 @@ +({ + numbers: ['1', '2', '3', '4'] +}); diff --git a/test/helpers/_files/partial_array_of_partials_implicit.mustache b/test/helpers/_files/partial_array_of_partials_implicit.mustache new file mode 100644 index 0000000..1af6d68 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials_implicit.mustache @@ -0,0 +1,4 @@ +Here is some stuff! +{{#numbers}} +{{>partial}} +{{/numbers}} diff --git a/test/helpers/_files/partial_array_of_partials_implicit.partial b/test/helpers/_files/partial_array_of_partials_implicit.partial new file mode 100644 index 0000000..12f7159 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials_implicit.partial @@ -0,0 +1 @@ +{{.}} diff --git a/test/helpers/_files/partial_array_of_partials_implicit.txt b/test/helpers/_files/partial_array_of_partials_implicit.txt new file mode 100644 index 0000000..f622375 --- /dev/null +++ b/test/helpers/_files/partial_array_of_partials_implicit.txt @@ -0,0 +1,5 @@ +Here is some stuff! +1 +2 +3 +4 diff --git a/test/helpers/_files/partial_empty.js b/test/helpers/_files/partial_empty.js new file mode 100644 index 0000000..03db6fd --- /dev/null +++ b/test/helpers/_files/partial_empty.js @@ -0,0 +1,3 @@ +({ + foo: 1 +}); diff --git a/test/helpers/_files/partial_empty.mustache b/test/helpers/_files/partial_empty.mustache new file mode 100644 index 0000000..a710047 --- /dev/null +++ b/test/helpers/_files/partial_empty.mustache @@ -0,0 +1,2 @@ +hey {{foo}} +{{>partial}} diff --git a/test/helpers/_files/partial_empty.partial b/test/helpers/_files/partial_empty.partial new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/_files/partial_empty.txt b/test/helpers/_files/partial_empty.txt new file mode 100644 index 0000000..1a67907 --- /dev/null +++ b/test/helpers/_files/partial_empty.txt @@ -0,0 +1 @@ +hey 1 diff --git a/test/helpers/_files/partial_template.js b/test/helpers/_files/partial_template.js new file mode 100644 index 0000000..a527657 --- /dev/null +++ b/test/helpers/_files/partial_template.js @@ -0,0 +1,6 @@ +({ + title: function () { + return 'Welcome'; + }, + again: 'Goodbye' +}); diff --git a/test/helpers/_files/partial_template.mustache b/test/helpers/_files/partial_template.mustache new file mode 100644 index 0000000..6a7492e --- /dev/null +++ b/test/helpers/_files/partial_template.mustache @@ -0,0 +1,2 @@ +

{{title}}

+{{>partial}} diff --git a/test/helpers/_files/partial_template.partial b/test/helpers/_files/partial_template.partial new file mode 100644 index 0000000..a404529 --- /dev/null +++ b/test/helpers/_files/partial_template.partial @@ -0,0 +1 @@ +Again, {{again}}! diff --git a/test/helpers/_files/partial_template.txt b/test/helpers/_files/partial_template.txt new file mode 100644 index 0000000..692698f --- /dev/null +++ b/test/helpers/_files/partial_template.txt @@ -0,0 +1,2 @@ +

Welcome

+Again, Goodbye! diff --git a/test/helpers/_files/partial_view.js b/test/helpers/_files/partial_view.js new file mode 100644 index 0000000..6a83f54 --- /dev/null +++ b/test/helpers/_files/partial_view.js @@ -0,0 +1,14 @@ +({ + greeting: function () { + return 'Welcome'; + }, + farewell: function () { + return 'Fair enough, right?'; + }, + name: 'Chris', + value: 10000, + taxed_value: function () { + return this.value - (this.value * 0.4); + }, + in_ca: true +}); diff --git a/test/helpers/_files/partial_view.mustache b/test/helpers/_files/partial_view.mustache new file mode 100644 index 0000000..f8f6a5b --- /dev/null +++ b/test/helpers/_files/partial_view.mustache @@ -0,0 +1,3 @@ +

{{greeting}}

+{{>partial}} +

{{farewell}}

diff --git a/test/helpers/_files/partial_view.partial b/test/helpers/_files/partial_view.partial new file mode 100644 index 0000000..03df206 --- /dev/null +++ b/test/helpers/_files/partial_view.partial @@ -0,0 +1,5 @@ +Hello {{name}} +You have just won ${{value}}! +{{#in_ca}} +Well, ${{ taxed_value }}, after taxes. +{{/in_ca}} \ No newline at end of file diff --git a/test/helpers/_files/partial_view.txt b/test/helpers/_files/partial_view.txt new file mode 100644 index 0000000..c09147c --- /dev/null +++ b/test/helpers/_files/partial_view.txt @@ -0,0 +1,5 @@ +

Welcome

+Hello Chris +You have just won $10000! +Well, $6000, after taxes. +

Fair enough, right?

diff --git a/test/helpers/_files/partial_whitespace.js b/test/helpers/_files/partial_whitespace.js new file mode 100644 index 0000000..6a83f54 --- /dev/null +++ b/test/helpers/_files/partial_whitespace.js @@ -0,0 +1,14 @@ +({ + greeting: function () { + return 'Welcome'; + }, + farewell: function () { + return 'Fair enough, right?'; + }, + name: 'Chris', + value: 10000, + taxed_value: function () { + return this.value - (this.value * 0.4); + }, + in_ca: true +}); diff --git a/test/helpers/_files/partial_whitespace.mustache b/test/helpers/_files/partial_whitespace.mustache new file mode 100644 index 0000000..48bd1ff --- /dev/null +++ b/test/helpers/_files/partial_whitespace.mustache @@ -0,0 +1,3 @@ +

{{ greeting }}

+{{> partial }} +

{{ farewell }}

diff --git a/test/helpers/_files/partial_whitespace.partial b/test/helpers/_files/partial_whitespace.partial new file mode 100644 index 0000000..30de8f6 --- /dev/null +++ b/test/helpers/_files/partial_whitespace.partial @@ -0,0 +1,5 @@ +Hello {{ name}} +You have just won ${{value }}! +{{# in_ca }} +Well, ${{ taxed_value }}, after taxes. +{{/ in_ca }} \ No newline at end of file diff --git a/test/helpers/_files/partial_whitespace.txt b/test/helpers/_files/partial_whitespace.txt new file mode 100644 index 0000000..c09147c --- /dev/null +++ b/test/helpers/_files/partial_whitespace.txt @@ -0,0 +1,5 @@ +

Welcome

+Hello Chris +You have just won $10000! +Well, $6000, after taxes. +

Fair enough, right?

diff --git a/test/helpers/_files/recursion_with_same_names.js b/test/helpers/_files/recursion_with_same_names.js new file mode 100644 index 0000000..fd0f7d0 --- /dev/null +++ b/test/helpers/_files/recursion_with_same_names.js @@ -0,0 +1,8 @@ +({ + name: 'name', + description: 'desc', + terms: [ + {name: 't1', index: 0}, + {name: 't2', index: 1} + ] +}); diff --git a/test/helpers/_files/recursion_with_same_names.mustache b/test/helpers/_files/recursion_with_same_names.mustache new file mode 100644 index 0000000..c331d04 --- /dev/null +++ b/test/helpers/_files/recursion_with_same_names.mustache @@ -0,0 +1,7 @@ +{{ name }} +{{ description }} + +{{#terms}} + {{name}} + {{index}} +{{/terms}} diff --git a/test/helpers/_files/recursion_with_same_names.txt b/test/helpers/_files/recursion_with_same_names.txt new file mode 100644 index 0000000..cb15d75 --- /dev/null +++ b/test/helpers/_files/recursion_with_same_names.txt @@ -0,0 +1,7 @@ +name +desc + + t1 + 0 + t2 + 1 diff --git a/test/helpers/_files/reuse_of_enumerables.js b/test/helpers/_files/reuse_of_enumerables.js new file mode 100644 index 0000000..b59b4a8 --- /dev/null +++ b/test/helpers/_files/reuse_of_enumerables.js @@ -0,0 +1,6 @@ +({ + terms: [ + {name: 't1', index: 0}, + {name: 't2', index: 1} + ] +}); diff --git a/test/helpers/_files/reuse_of_enumerables.mustache b/test/helpers/_files/reuse_of_enumerables.mustache new file mode 100644 index 0000000..cc0cb7a --- /dev/null +++ b/test/helpers/_files/reuse_of_enumerables.mustache @@ -0,0 +1,8 @@ +{{#terms}} + {{name}} + {{index}} +{{/terms}} +{{#terms}} + {{name}} + {{index}} +{{/terms}} diff --git a/test/helpers/_files/reuse_of_enumerables.txt b/test/helpers/_files/reuse_of_enumerables.txt new file mode 100644 index 0000000..6d05d96 --- /dev/null +++ b/test/helpers/_files/reuse_of_enumerables.txt @@ -0,0 +1,8 @@ + t1 + 0 + t2 + 1 + t1 + 0 + t2 + 1 diff --git a/test/helpers/_files/section_as_context.js b/test/helpers/_files/section_as_context.js new file mode 100644 index 0000000..aeb7b1f --- /dev/null +++ b/test/helpers/_files/section_as_context.js @@ -0,0 +1,10 @@ +({ + a_object: { + title: 'this is an object', + description: 'one of its attributes is a list', + a_list: [ + {label: 'listitem1'}, + {label: 'listitem2'} + ] + } +}); diff --git a/test/helpers/_files/section_as_context.mustache b/test/helpers/_files/section_as_context.mustache new file mode 100644 index 0000000..59990f6 --- /dev/null +++ b/test/helpers/_files/section_as_context.mustache @@ -0,0 +1,9 @@ +{{#a_object}} +

{{title}}

+

{{description}}

+
    + {{#a_list}} +
  • {{label}}
  • + {{/a_list}} +
+{{/a_object}} diff --git a/test/helpers/_files/section_as_context.txt b/test/helpers/_files/section_as_context.txt new file mode 100644 index 0000000..d834e80 --- /dev/null +++ b/test/helpers/_files/section_as_context.txt @@ -0,0 +1,6 @@ +

this is an object

+

one of its attributes is a list

+
    +
  • listitem1
  • +
  • listitem2
  • +
diff --git a/test/helpers/_files/section_functions_in_partials.js b/test/helpers/_files/section_functions_in_partials.js new file mode 100644 index 0000000..ece5fc9 --- /dev/null +++ b/test/helpers/_files/section_functions_in_partials.js @@ -0,0 +1,7 @@ +({ + bold: function (){ + return function (text, render) { + return '' + render(text) + ''; + }; + } +}); diff --git a/test/helpers/_files/section_functions_in_partials.mustache b/test/helpers/_files/section_functions_in_partials.mustache new file mode 100644 index 0000000..8164932 --- /dev/null +++ b/test/helpers/_files/section_functions_in_partials.mustache @@ -0,0 +1,3 @@ +{{> partial}} + +

some more text

diff --git a/test/helpers/_files/section_functions_in_partials.partial b/test/helpers/_files/section_functions_in_partials.partial new file mode 100644 index 0000000..3e90b00 --- /dev/null +++ b/test/helpers/_files/section_functions_in_partials.partial @@ -0,0 +1 @@ +{{#bold}}Hello There{{/bold}} diff --git a/test/helpers/_files/section_functions_in_partials.txt b/test/helpers/_files/section_functions_in_partials.txt new file mode 100644 index 0000000..2f5955c --- /dev/null +++ b/test/helpers/_files/section_functions_in_partials.txt @@ -0,0 +1,3 @@ +Hello There + +

some more text

diff --git a/test/helpers/_files/simple.js b/test/helpers/_files/simple.js new file mode 100644 index 0000000..6c6b1f0 --- /dev/null +++ b/test/helpers/_files/simple.js @@ -0,0 +1,8 @@ +({ + name: 'Chris', + value: 10000, + taxed_value: function () { + return this.value - (this.value * 0.4); + }, + in_ca: true +}); diff --git a/test/helpers/_files/simple.mustache b/test/helpers/_files/simple.mustache new file mode 100644 index 0000000..2fea632 --- /dev/null +++ b/test/helpers/_files/simple.mustache @@ -0,0 +1,5 @@ +Hello {{name}} +You have just won ${{value}}! +{{#in_ca}} +Well, ${{ taxed_value }}, after taxes. +{{/in_ca}} diff --git a/test/helpers/_files/simple.txt b/test/helpers/_files/simple.txt new file mode 100644 index 0000000..5d75d65 --- /dev/null +++ b/test/helpers/_files/simple.txt @@ -0,0 +1,3 @@ +Hello Chris +You have just won $10000! +Well, $6000, after taxes. diff --git a/test/helpers/_files/string_as_context.js b/test/helpers/_files/string_as_context.js new file mode 100644 index 0000000..118e2b5 --- /dev/null +++ b/test/helpers/_files/string_as_context.js @@ -0,0 +1,4 @@ +({ + a_string: 'aa', + a_list: ['a','b','c'] +}); diff --git a/test/helpers/_files/string_as_context.mustache b/test/helpers/_files/string_as_context.mustache new file mode 100644 index 0000000..00f7181 --- /dev/null +++ b/test/helpers/_files/string_as_context.mustache @@ -0,0 +1,5 @@ +
    +{{#a_list}} +
  • {{a_string}}/{{.}}
  • +{{/a_list}} +
\ No newline at end of file diff --git a/test/helpers/_files/string_as_context.txt b/test/helpers/_files/string_as_context.txt new file mode 100644 index 0000000..8bd87ff --- /dev/null +++ b/test/helpers/_files/string_as_context.txt @@ -0,0 +1,5 @@ +
    +
  • aa/a
  • +
  • aa/b
  • +
  • aa/c
  • +
\ No newline at end of file diff --git a/test/helpers/_files/two_in_a_row.js b/test/helpers/_files/two_in_a_row.js new file mode 100644 index 0000000..272f860 --- /dev/null +++ b/test/helpers/_files/two_in_a_row.js @@ -0,0 +1,4 @@ +({ + name: 'Joe', + greeting: 'Welcome' +}); diff --git a/test/helpers/_files/two_in_a_row.mustache b/test/helpers/_files/two_in_a_row.mustache new file mode 100644 index 0000000..b23f29e --- /dev/null +++ b/test/helpers/_files/two_in_a_row.mustache @@ -0,0 +1 @@ +{{greeting}}, {{name}}! diff --git a/test/helpers/_files/two_in_a_row.txt b/test/helpers/_files/two_in_a_row.txt new file mode 100644 index 0000000..c6d6a9b --- /dev/null +++ b/test/helpers/_files/two_in_a_row.txt @@ -0,0 +1 @@ +Welcome, Joe! diff --git a/test/helpers/_files/two_sections.js b/test/helpers/_files/two_sections.js new file mode 100644 index 0000000..b3bf1dd --- /dev/null +++ b/test/helpers/_files/two_sections.js @@ -0,0 +1 @@ +({}); diff --git a/test/helpers/_files/two_sections.mustache b/test/helpers/_files/two_sections.mustache new file mode 100644 index 0000000..a4b9f2a --- /dev/null +++ b/test/helpers/_files/two_sections.mustache @@ -0,0 +1,4 @@ +{{#foo}} +{{/foo}} +{{#bar}} +{{/bar}} diff --git a/test/helpers/_files/two_sections.txt b/test/helpers/_files/two_sections.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/_files/unescaped.js b/test/helpers/_files/unescaped.js new file mode 100644 index 0000000..3b4d3cb --- /dev/null +++ b/test/helpers/_files/unescaped.js @@ -0,0 +1,6 @@ +({ + title: function () { + return 'Bear > Shark'; + }, + symbol: null +}); diff --git a/test/helpers/_files/unescaped.mustache b/test/helpers/_files/unescaped.mustache new file mode 100644 index 0000000..1a27f58 --- /dev/null +++ b/test/helpers/_files/unescaped.mustache @@ -0,0 +1 @@ +

{{{title}}}{{{symbol}}}

diff --git a/test/helpers/_files/unescaped.txt b/test/helpers/_files/unescaped.txt new file mode 100644 index 0000000..089ad79 --- /dev/null +++ b/test/helpers/_files/unescaped.txt @@ -0,0 +1 @@ +

Bear > Shark

diff --git a/test/helpers/_files/uses_props_from_view_prototype.js b/test/helpers/_files/uses_props_from_view_prototype.js new file mode 100644 index 0000000..6754348 --- /dev/null +++ b/test/helpers/_files/uses_props_from_view_prototype.js @@ -0,0 +1,30 @@ +var Aaa = (function () { + function Aaa (x, y) { + this.x = x; + this._y = y; + } + Object.defineProperty(Aaa.prototype, 'y', { + get: function () { + return this._y; + }, + set: function (value) { + this._y = value; + }, + enumerable: true, + configurable: true + }); + return Aaa; +})(); +var Bbb = (function () { + function Bbb () { + } + return Bbb; +})(); + +var b = new Bbb(); +b.item = new Aaa('0', '00'); +b.items = []; +b.items.push({ a: new Aaa('1', '2') }); +b.items.push({ a: new Aaa('3', '4') }); + +(b); diff --git a/test/helpers/_files/uses_props_from_view_prototype.mustache b/test/helpers/_files/uses_props_from_view_prototype.mustache new file mode 100644 index 0000000..874f0c9 --- /dev/null +++ b/test/helpers/_files/uses_props_from_view_prototype.mustache @@ -0,0 +1 @@ +[{{ item.x }};{{ item.y }}]||{{#items}}[{{ a.x }};{{ a.y }} {{#a}}{{y}}{{/a}}]{{/items}} \ No newline at end of file diff --git a/test/helpers/_files/uses_props_from_view_prototype.txt b/test/helpers/_files/uses_props_from_view_prototype.txt new file mode 100644 index 0000000..f416840 --- /dev/null +++ b/test/helpers/_files/uses_props_from_view_prototype.txt @@ -0,0 +1 @@ +[0;00]||[1;2 2][3;4 4] \ No newline at end of file diff --git a/test/helpers/_files/whitespace.js b/test/helpers/_files/whitespace.js new file mode 100644 index 0000000..dc12fe2 --- /dev/null +++ b/test/helpers/_files/whitespace.js @@ -0,0 +1,4 @@ +({ + tag1: 'Hello', + tag2: 'World' +}); diff --git a/test/helpers/_files/whitespace.mustache b/test/helpers/_files/whitespace.mustache new file mode 100644 index 0000000..aa76e08 --- /dev/null +++ b/test/helpers/_files/whitespace.mustache @@ -0,0 +1,4 @@ +{{tag1}} + + +{{tag2}}. diff --git a/test/helpers/_files/whitespace.txt b/test/helpers/_files/whitespace.txt new file mode 100644 index 0000000..851fa74 --- /dev/null +++ b/test/helpers/_files/whitespace.txt @@ -0,0 +1,4 @@ +Hello + + +World. diff --git a/test/helpers/_files/zero_view.js b/test/helpers/_files/zero_view.js new file mode 100644 index 0000000..d584042 --- /dev/null +++ b/test/helpers/_files/zero_view.js @@ -0,0 +1 @@ +({ nums: [0, 1, 2] }); diff --git a/test/helpers/_files/zero_view.mustache b/test/helpers/_files/zero_view.mustache new file mode 100644 index 0000000..1cdc190 --- /dev/null +++ b/test/helpers/_files/zero_view.mustache @@ -0,0 +1 @@ +{{#nums}}{{.}},{{/nums}} \ No newline at end of file diff --git a/test/helpers/_files/zero_view.txt b/test/helpers/_files/zero_view.txt new file mode 100644 index 0000000..2aee585 --- /dev/null +++ b/test/helpers/_files/zero_view.txt @@ -0,0 +1 @@ +0,1,2, \ No newline at end of file diff --git a/test/helpers/render-helper.ts b/test/helpers/render-helper.ts new file mode 100644 index 0000000..5dc668a --- /dev/null +++ b/test/helpers/render-helper.ts @@ -0,0 +1,61 @@ +const getDirPath = (): string => { + const filePath = new URL(import.meta.url).pathname; + const dirPath = filePath.split("/").slice(0, -1).join("/"); + return dirPath; +}; + +const filesPath = `${getDirPath()}/_files`; + +const getContens = async (testName: string, ext: string): Promise => { + try { + const contents = await Deno.readTextFile(`${filesPath}/${testName}.${ext}`); + return contents; + } catch (_ex) { + return null; + } +}; + +const getView = async (testName: string): Promise => { + let view = await getContens(testName, "js"); + if (!view) view = await getContens(testName, "cjs"); + if (!view) throw new Error(`Cannot find view for test "${testName}"`); + return view; +}; + +const getPartial = async (testName: string): Promise => { + try { + const partial = await getContens(testName, "partial"); + return partial; + } catch (_ex) { + return null; + } +}; + +export interface Test { + name: string; + view: string; + template: string | null; + partial: string | null; + expect: string | null; +} + +const getTest = async (testName: string): Promise => { + const view = await getView(testName); + const template = await getContens(testName, "mustache"); + const partial = await getPartial(testName); + const expect = await getContens(testName, "txt"); + return { name: testName, view, template, partial, expect }; +}; + +export const getTests = async (): Promise => { + const testNames = Deno.readDir(filesPath); + const tests: Test[] = []; + + for await (const file of testNames) { + if (!file.name.endsWith(".js") && !file.name.endsWith(".cjs")) continue; + const testName = file.name.split(".")[0]; + const test = await getTest(testName); + tests.push(test); + } + return tests; +}; diff --git a/test/parse.test.ts b/test/parse.test.ts new file mode 100644 index 0000000..9ceef73 --- /dev/null +++ b/test/parse.test.ts @@ -0,0 +1,188 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { + assertEquals, + assertNotEquals, + assertThrows, + beforeAll, + beforeEach, + describe, + it, +} from "../dev_deps.ts"; + +import mustache from "../src/lib/Mustache.ts"; + +import { Token } from "../src/lib/parse.ts"; + +// A map of templates to their expected token output. Tokens are in the format: +// [type, value, startIndex, endIndex, subTokens]. + +// @deno-fmt-ignore +const expectations = { + '' : [], + '{{hi}}' : [ [ 'name', 'hi', 0, 6 ] ], + '{{hi.world}}' : [ [ 'name', 'hi.world', 0, 12 ] ], + '{{hi . world}}' : [ [ 'name', 'hi . world', 0, 14 ] ], + '{{ hi}}' : [ [ 'name', 'hi', 0, 7 ] ], + '{{hi }}' : [ [ 'name', 'hi', 0, 7 ] ], + '{{ hi }}' : [ [ 'name', 'hi', 0, 8 ] ], + '{{{hi}}}' : [ [ '&', 'hi', 0, 8 ] ], + '{{!hi}}' : [ [ '!', 'hi', 0, 7 ] ], + '{{! hi}}' : [ [ '!', 'hi', 0, 8 ] ], + '{{! hi }}' : [ [ '!', 'hi', 0, 9 ] ], + '{{ !hi}}' : [ [ '!', 'hi', 0, 8 ] ], + '{{ ! hi}}' : [ [ '!', 'hi', 0, 9 ] ], + '{{ ! hi }}' : [ [ '!', 'hi', 0, 10 ] ], + 'a\n b' : [ [ 'text', 'a\n b', 0, 4 ] ], + 'a{{hi}}' : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ] ], + 'a {{hi}}' : [ [ 'text', 'a ', 0, 2 ], [ 'name', 'hi', 2, 8 ] ], + ' a{{hi}}' : [ [ 'text', ' a', 0, 2 ], [ 'name', 'hi', 2, 8 ] ], + ' a {{hi}}' : [ [ 'text', ' a ', 0, 3 ], [ 'name', 'hi', 3, 9 ] ], + 'a{{hi}}b' : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ], [ 'text', 'b', 7, 8 ] ], + 'a{{hi}} b' : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ], [ 'text', ' b', 7, 9 ] ], + 'a{{hi}}b ' : [ [ 'text', 'a', 0, 1 ], [ 'name', 'hi', 1, 7 ], [ 'text', 'b ', 7, 9 ] ], + 'a\n{{hi}} b \n' : [ [ 'text', 'a\n', 0, 2 ], [ 'name', 'hi', 2, 8 ], [ 'text', ' b \n', 8, 12 ] ], + 'a\n {{hi}} \nb' : [ [ 'text', 'a\n ', 0, 3 ], [ 'name', 'hi', 3, 9 ], [ 'text', ' \nb', 9, 12 ] ], + 'a\n {{!hi}} \nb' : [ [ 'text', 'a\n', 0, 2 ], [ '!', 'hi', 3, 10 ], [ 'text', 'b', 12, 13 ] ], + 'a\n{{#a}}{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [], 8 ], [ 'text', 'b', 15, 16 ] ], + 'a\n {{#a}}{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [], 9 ], [ 'text', 'b', 16, 17 ] ], + 'a\n {{#a}}{{/a}} \nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [], 9 ], [ 'text', 'b', 17, 18 ] ], + 'a\n{{#a}}\n{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [], 9 ], [ 'text', 'b', 16, 17 ] ], + 'a\n {{#a}}\n{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [], 10 ], [ 'text', 'b', 17, 18 ] ], + 'a\n {{#a}}\n{{/a}} \nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [], 10 ], [ 'text', 'b', 18, 19 ] ], + 'a\n{{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [], 9 ], [ '#', 'b', 16, 22, [], 23 ], [ 'text', 'b', 30, 31 ] ], + 'a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [], 10 ], [ '#', 'b', 17, 23, [], 24 ], [ 'text', 'b', 31, 32 ] ], + 'a\n {{#a}}\n{{/a}}\n{{#b}}\n{{/b}} \nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [], 10 ], [ '#', 'b', 17, 23, [], 24 ], [ 'text', 'b', 32, 33 ] ], + 'a\n{{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 2, 8, [ [ '#', 'b', 9, 15, [], 16 ] ], 23 ], [ 'text', 'b', 30, 31 ] ], + 'a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}}\nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [ [ '#', 'b', 10, 16, [], 17 ] ], 24 ], [ 'text', 'b', 31, 32 ] ], + 'a\n {{#a}}\n{{#b}}\n{{/b}}\n{{/a}} \nb' : [ [ 'text', 'a\n', 0, 2 ], [ '#', 'a', 3, 9, [ [ '#', 'b', 10, 16, [], 17 ] ], 24 ], [ 'text', 'b', 32, 33 ] ], + '{{>abc}}' : [ [ '>', 'abc', 0, 8, '', 0, false ] ], + '{{> abc }}' : [ [ '>', 'abc', 0, 10, '', 0, false ] ], + '{{ > abc }}' : [ [ '>', 'abc', 0, 11, '', 0, false ] ], + ' {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ] ], + ' {{> abc }} {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ], [ '>', 'abc', 13, 23, ' ', 1, false ] ], + '{{=<% %>=}}' : [ [ '=', '<% %>', 0, 11 ] ], + '{{= <% %> =}}' : [ [ '=', '<% %>', 0, 13 ] ], + '{{=<% %>=}}<%={{ }}=%>' : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ], + '{{=<% %>=}}<%hi%>' : [ [ '=', '<% %>', 0, 11 ], [ 'name', 'hi', 11, 17 ] ], + '{{#a}}{{/a}}hi{{#b}}{{/b}}\n' : [ [ '#', 'a', 0, 6, [], 6 ], [ 'text', 'hi', 12, 14 ], [ '#', 'b', 14, 20, [], 20 ], [ 'text', '\n', 26, 27 ] ], + '{{a}}\n{{b}}\n\n{{#c}}\n{{/c}}\n' : [ [ 'name', 'a', 0, 5 ], [ 'text', '\n', 5, 6 ], [ 'name', 'b', 6, 11 ], [ 'text', '\n\n', 11, 13 ], [ '#', 'c', 13, 19, [], 20 ] ], + '{{#foo}}\n {{#a}}\n {{b}}\n {{/a}}\n{{/foo}}\n' + : [ [ '#', 'foo', 0, 8, [ [ '#', 'a', 11, 17, [ [ 'text', ' ', 18, 22 ], [ 'name', 'b', 22, 27 ], [ 'text', '\n', 27, 28 ] ], 30 ] ], 37 ] ] +}; + +let originalTemplateCache: Map; + +describe("mustache.parse", () => { + beforeAll(() => { + originalTemplateCache = mustache.templateCache as Map; + }); + + beforeEach(() => { + mustache.clearCache(); + mustache.templateCache = originalTemplateCache; + }); + + for (const template in expectations) { + if (Object.prototype.hasOwnProperty.call(expectations, template)) { + ((template, tokens) => { + it("knows how to parse " + JSON.stringify(template), () => { + assertEquals(mustache.parse(template), tokens as Token[]); + }); + })(template, expectations[template as keyof typeof expectations]); + } + } + + describe("when there is an unclosed tag", () => { + it("throws an error", () => { + assertThrows(() => { + mustache.parse("My name is {{name"); + }); + }); + }); + + describe("when there is an unclosed section", () => { + it("throws an error", () => { + assertThrows(() => { + mustache.parse("A list: {{#people}}{{name}}"); + }); + }); + }); + + describe("when there is an unopened section", () => { + it("throws an error", () => { + assertThrows(() => { + mustache.parse("The end of the list! {{/people}}"); + }); + }); + }); + + describe("when invalid tags are given as an argument", () => { + it("throws an error", () => { + assertThrows(() => { + mustache.parse("A template <% name %>", ["<%"]); + }); + }); + }); + + describe("when the template contains invalid tags", () => { + it("throws an error", () => { + assertThrows(() => { + mustache.parse("A template {{=<%=}}"); + }); + }); + }); + + describe("when parsing a template without tags specified followed by the same template with tags specified", () => { + it("returns different tokens for the latter parse", () => { + const template = "{{foo}}[bar]"; + const parsedWithBraces = mustache.parse(template); + const parsedWithBrackets = mustache.parse(template, ["[", "]"]); + assertNotEquals(parsedWithBrackets, parsedWithBraces); + }); + }); + + describe("when parsing a template with tags specified followed by the same template with different tags specified", () => { + it("returns different tokens for the latter parse", () => { + const template = "(foo)[bar]"; + const parsedWithParens = mustache.parse(template, ["(", ")"]); + const parsedWithBrackets = mustache.parse(template, ["[", "]"]); + assertNotEquals(parsedWithBrackets, parsedWithParens); + }); + }); + + describe("when parsing a template after already having parsed that template with a different mustache.tags", () => { + it("returns different tokens for the latter parse", () => { + const template = "{{foo}}[bar]"; + const parsedWithBraces = mustache.parse(template); + + const oldTags = mustache.tags; + mustache.tags = ["[", "]"]; + const parsedWithBrackets = mustache.parse(template); + mustache.tags = oldTags; + + assertNotEquals(parsedWithBrackets, parsedWithBraces); + }); + }); + + describe("when parsing a template with the same tags second time, return the cached tokens", () => { + it("returns the same tokens for the latter parse", () => { + const template = "{{foo}}[bar]"; + const parsedResult1 = mustache.parse(template); + const parsedResult2 = mustache.parse(template); + + assertEquals(parsedResult1, parsedResult2); + }); + }); + + describe("when parsing a template with caching disabled and the same tags second time, do not return the cached tokens", () => { + it("returns different tokens for the latter parse", () => { + mustache.templateCache = undefined; + const template = "{{foo}}[bar]"; + const parsedResult1 = mustache.parse(template); + const parsedResult2 = mustache.parse(template); + + assertEquals(parsedResult1, parsedResult2); + }); + }); +}); diff --git a/test/partial.test.ts b/test/partial.test.ts new file mode 100644 index 0000000..ec658a8 --- /dev/null +++ b/test/partial.test.ts @@ -0,0 +1,175 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { assertEquals, beforeEach, describe, it } from "../dev_deps.ts"; + +import mustache from "../src/lib/Mustache.ts"; + +describe("Partials spec", () => { + beforeEach(() => { + mustache.clearCache(); + }); + + it("The greater-than operator should expand to the named partial.", () => { + const template = '"{{>text}}"'; + const data = {}; + const partials = { "text": "from partial" }; + const expected = '"from partial"'; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("The empty string should be used when the named partial is not found.", () => { + const template = '"{{>text}}"'; + const data = {}; + const partials = {}; + const expected = '""'; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("The greater-than operator should operate within the current context.", () => { + const template = '"{{>partial}}"'; + const data = { "text": "content" }; + const partials = { "partial": "*{{text}}*" }; + const expected = '"*content*"'; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("Inline partials should not be indented", () => { + const template = "
{{> partial}}
"; + const data = {}; + const partials = { "partial": "This is a partial." }; + const expected = "
This is a partial.
"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("Inline partials should not be indented (multiline)", () => { + const template = "
{{> partial}}
"; + const data = {}; + const partials = { "partial": "This is a\npartial." }; + const expected = "
This is a\n partial.
"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("The greater-than operator should properly recurse.", () => { + const template = "{{>node}}"; + const data = { "content": "X", "nodes": [{ "content": "Y", "nodes": [] }] }; + const partials = { "node": "{{content}}<{{#nodes}}{{>node}}{{/nodes}}>" }; + const expected = "X>"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("The greater-than operator should not alter surrounding whitespace.", () => { + const template = "| {{>partial}} |"; + const data = {}; + const partials = { "partial": "\t|\t" }; + const expected = "| \t|\t |"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it('"\r\n" should be considered a newline for standalone tags.', () => { + const template = "|\r\n{{>partial}}\r\n|"; + const data = {}; + const partials = { "partial": ">" }; + const expected = "|\r\n>|"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("Standalone tags should not require a newline to precede them.", () => { + const template = " {{>partial}}\n>"; + const data = {}; + const partials = { "partial": ">\n>" }; + const expected = " >\n >>"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("Superfluous in-tag whitespace should be ignored.", () => { + const template = "|{{> partial }}|"; + const data = { "boolean": true }; + const partials = { "partial": "[]" }; + const expected = "|[]|"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + it("Each line of the partial should be indented before rendering.", () => { + const template = "\\\n {{>partial}}\n/\n"; + const data = { + "content": "<\n->", + }; + const partials = { + "partial": "|\n{{{content}}}\n|\n", + }; + const expected = "\\\n |\n <\n->\n |\n/\n"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("Standalone tags should not require a newline to follow them.", () => { + const template = ">\n {{>partial}}"; + const data = {}; + const partials = { + "partial": ">\n>", + }; + const expected = ">\n >\n >"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("Whitespace should be left untouched.", () => { + const template = " {{data}} {{> partial}}\n"; + const data = { + "data": "|", + }; + const partials = { + "partial": ">\n>", + }; + const expected = " | >\n>\n"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("Partial without indentation should inherit functions.", () => { + const template = "{{> partial }}"; + const data = { + toUpperCase: () => { + return function (label: string): string { + return label.toUpperCase(); + }; + }, + }; + const partials = { partial: "aA-{{ #toUpperCase }}Input{{ /toUpperCase }}-Aa" }; + const expected = "aA-INPUT-Aa"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("Partial with indentation should inherit functions.", () => { + const template = " {{> partial }}"; + const data = { + toUpperCase: () => { + return function (label: string): string { + return label.toUpperCase(); + }; + }, + }; + const partials = { partial: "aA-{{ #toUpperCase }}Input{{ /toUpperCase }}-Aa" }; + const expected = " aA-INPUT-Aa"; + const renderResult = mustache.render(template, data, partials); + assertEquals(renderResult, expected); + }); + + it("Nested partials should support custom delimiters.", () => { + const tags = ["[[", "]]"]; + const template = "[[> level1 ]]"; + const partials = { + level1: "partial 1\n[[> level2]]", + level2: "partial 2\n[[> level3]]", + level3: "partial 3\n[[> level4]]", + level4: "partial 4\n[[> level5]]", + level5: "partial 5", + }; + const expected = "partial 1\npartial 2\npartial 3\npartial 4\npartial 5"; + const renderResult = mustache.render(template, {}, partials, tags); + assertEquals(renderResult, expected); + }); +}); diff --git a/test/render.test.ts b/test/render.test.ts new file mode 100644 index 0000000..7d6b527 --- /dev/null +++ b/test/render.test.ts @@ -0,0 +1,305 @@ +// Copyright 2023 J.W. Lagendijk. All rights reserved. MIT license. + +import { assertEquals, assertThrows, beforeEach, describe, it } from "../dev_deps.ts"; +import { getTests } from "./helpers/render-helper.ts"; + +import mustache from "../src/lib/Mustache.ts"; + +const tests = await getTests(); + +describe("mustache.render", () => { + beforeEach(() => { + mustache.clearCache(); + }); + + it("requires template to be a string", () => { + assertThrows( + () => { + // @ts-ignore This should be caught by the type system, but for JS users we need to check at runtime + mustache.render(["dummy template"], ["foo", "bar"]); + }, + TypeError, + 'Invalid template! Template should be a "string" but ' + + '"array" was given as the first argument ' + + "for mustache#render(template, view, partials)", + ); + }); + + describe("custom tags", () => { + it("uses tags argument instead of mustache.tags when given", () => { + const template = "<>bar{{placeholder}}"; + + mustache.tags = ["{{", "}}"]; + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, ["<<", ">>"]), + "foobar{{placeholder}}", + ); + }); + + it("uses config.tags argument instead of mustache.tags when given", () => { + const template = "<>bar{{placeholder}}"; + + mustache.tags = ["{{", "}}"]; + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, { tags: ["<<", ">>"] }), + "foobar{{placeholder}}", + ); + }); + + it("uses tags argument instead of mustache.tags when given, even when it previously rendered the template using mustache.tags", () => { + const template = "((placeholder))bar{{placeholder}}"; + + mustache.tags = ["{{", "}}"]; + mustache.render(template, { placeholder: "foo" }); + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, ["((", "))"]), + "foobar{{placeholder}}", + ); + }); + + it("uses config.tags argument instead of mustache.tags when given, even when it previously rendered the template using mustache.tags", () => { + const template = "((placeholder))bar{{placeholder}}"; + + mustache.tags = ["{{", "}}"]; + mustache.render(template, { placeholder: "foo" }); + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, { tags: ["((", "))"] }), + "foobar{{placeholder}}", + ); + }); + + it("uses tags argument instead of mustache.tags when given, even when it previously rendered the template using different tags", () => { + const template = "[[placeholder]]bar<>"; + + mustache.render(template, { placeholder: "foo" }, {}, ["<<", ">>"]); + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, ["[[", "]]"]), + "foobar<>", + ); + }); + + it("uses config.tags argument instead of mustache.tags when given, even when it previously rendered the template using different tags", () => { + const template = "[[placeholder]]bar<>"; + + mustache.render(template, { placeholder: "foo" }, {}, ["<<", ">>"]); + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, { tags: ["[[", "]]"] }), + "foobar<>", + ); + }); + + it("does not mutate mustache.tags when given tags argument", () => { + const correctMustacheTags = ["{{", "}}"]; + mustache.tags = correctMustacheTags; + + mustache.render("((placeholder))", { placeholder: "foo" }, {}, ["((", "))"]); + + assertEquals(mustache.tags, correctMustacheTags); + assertEquals(mustache.tags, ["{{", "}}"]); + }); + + it("does not mutate mustache.tags when given config.tags argument", () => { + const correctMustacheTags = ["{{", "}}"]; + mustache.tags = correctMustacheTags; + + mustache.render( + "((placeholder))", + { placeholder: "foo" }, + {}, + { tags: ["((", "))"] }, + ); + + assertEquals(mustache.tags, correctMustacheTags); + assertEquals(mustache.tags, ["{{", "}}"]); + }); + + it("uses provided tags when rendering partials", () => { + const output = mustache.render("<%> partial %>", { name: "Santa Claus" }, { + partial: "<% name %>", + }, ["<%", "%>"]); + + assertEquals(output, "Santa Claus"); + }); + + it("uses provided config.tags when rendering partials", () => { + const output = mustache.render("<%> partial %>", { name: "Santa Claus" }, { + partial: "<% name %>", + }, { tags: ["<%", "%>"] }); + + assertEquals(output, "Santa Claus"); + }); + + it("uses config.escape argument instead of mustache.escape when given", () => { + const template = "Hello, {{placeholder}}"; + + function escapeBang(text: string): string { + return text + "!"; + } + assertEquals( + mustache.render(template, { placeholder: "world" }, {}, { escape: escapeBang }), + "Hello, world!", + ); + }); + + it("uses config.escape argument instead of mustache.escape when given, even when it previously rendered the template using mustache.escape", () => { + const template = "Hello, {{placeholder}}"; + + function escapeQuestion(text: string): string { + return text + "?"; + } + mustache.render(template, { placeholder: "world" }); + assertEquals( + mustache.render( + template, + { placeholder: "world" }, + {}, + { escape: escapeQuestion }, + ), + "Hello, world?", + ); + }); + + it("uses config.escape argument instead of mustache.escape when given, even when it previously rendered the template using a different escape function", () => { + const template = "Hello, {{placeholder}}"; + + function escapeQuestion(text: string): string { + return text + "?"; + } + function escapeBang(text: string): string { + return text + "!"; + } + mustache.render(template, { placeholder: "foo" }, {}, { escape: escapeQuestion }); + assertEquals( + mustache.render(template, { placeholder: "foo" }, {}, { escape: escapeBang }), + "Hello, foo!", + ); + }); + + it("does not mutate mustache.escape when given config.escape argument", () => { + const correctMustacheEscape = mustache.escape; + + function escapeNone(text: string): string { + return text; + } + mustache.render( + "((placeholder))", + { placeholder: "foo" }, + {}, + { escape: escapeNone }, + ); + + assertEquals(mustache.escape, correctMustacheEscape); + assertEquals(mustache.escape(">&"), ">&"); + }); + + it("uses provided config.escape when rendering partials", () => { + function escapeDoubleAmpersand(text: string): string { + return text.replace("&", "&&"); + } + const output = mustache.render("{{> partial }}", { name: "Ampersand &" }, { + partial: "{{ name }}", + }, { escape: escapeDoubleAmpersand }); + + assertEquals(output, "Ampersand &&"); + }); + + it("uses config.tags and config.escape arguments instead of mustache.tags and mustache.escape when given", () => { + const template = "Hello, {{placeholder}} [[placeholder]]"; + + function escapeTwoBangs(text: string): string { + return text + "!!"; + } + const config = { + tags: ["[[", "]]"], + escape: escapeTwoBangs, + }; + assertEquals( + mustache.render(template, { placeholder: "world" }, {}, config), + "Hello, {{placeholder}} world!!", + ); + }); + + it("uses provided config.tags and config.escape when rendering partials", () => { + function escapeDoubleAmpersand(text: string): string { + return text.replace("&", "&&"); + } + const config = { + tags: ["[[", "]]"], + escape: escapeDoubleAmpersand, + }; + const output = mustache.render("[[> partial ]]", { name: "Ampersand &" }, { + partial: "[[ name ]]", + }, config); + + assertEquals(output, "Ampersand &&"); + }); + + it("uses provided config.tags and config.escape when rendering sections", () => { + const template = "<[[&value-raw]]: " + + "[[#test-1]][[value-1]][[/test-1]]" + + "[[^test-2]][[value-2]][[/test-2]], " + + "[[#test-lambda]][[value-lambda]][[/test-lambda]]" + + ">"; + + function escapeQuotes(text: string): string { + return '"' + text + '"'; + } + const config = { + tags: ["[[", "]]"], + escape: escapeQuotes, + }; + const viewTestTrue = { + "value-raw": "foo", + "test-1": true, + "value-1": "abc", + "test-2": true, + "value-2": "123", + "test-lambda": () => { + return function (text: string, render: (text: string) => string): string { + return "lambda: " + render(text); + }; + }, + "value-lambda": "bar", + }; + const viewTestFalse = { + "value-raw": "foo", + "test-1": false, + "value-1": "abc", + "test-2": false, + "value-2": "123", + "test-lambda": () => { + return function (text: string, render: (text: string) => string): string { + return "lambda: " + render(text); + }; + }, + "value-lambda": "bar", + }; + const outputTrue = mustache.render(template, viewTestTrue, {}, config); + const outputFalse = mustache.render(template, viewTestFalse, {}, config); + + assertEquals(outputTrue, ''); + assertEquals(outputFalse, ''); + }); + }); + + tests.forEach((test) => { + const view = eval(test.view); + + if (test.template === null) { + console.log("Skipping " + test.name + " because no template was found."); + return; + } + + it("knows how to render " + test.name, () => { + let output; + if (test.partial) { + output = mustache.render(test.template as string, view, { partial: test.partial }); + } else { + output = mustache.render(test.template as string, view); + } + + // output.should.equal(test.expect); + assertEquals(output, test.expect); + }); + }); +});