diff --git a/static/templates/message_body.hbs b/static/templates/message_body.hbs index b15ed9f30f..dbba9f2036 100644 --- a/static/templates/message_body.hbs +++ b/static/templates/message_body.hbs @@ -36,7 +36,13 @@ {{#unless status_message}} {{#unless is_hidden}} -
{{#if use_match_properties}}{{rendered_markdown msg/match_content}}{{else}}{{rendered_markdown msg/content}}{{/if}}
+
+ {{#if use_match_properties}} + {{rendered_markdown msg/match_content}} + {{else}} + {{rendered_markdown msg/content}} + {{/if}} +
{{else}} {{> message_hidden_dialog}} {{/unless}} diff --git a/static/templates/message_edit_form.hbs b/static/templates/message_edit_form.hbs index 411b2d8a98..bb7c2d2fb5 100644 --- a/static/templates/message_edit_form.hbs +++ b/static/templates/message_edit_form.hbs @@ -72,8 +72,10 @@ {{#if has_been_editable}}
- + +
{{/if}} diff --git a/static/templates/navbar.hbs b/static/templates/navbar.hbs index 1a7eb12d0c..e9e2bdff85 100644 --- a/static/templates/navbar.hbs +++ b/static/templates/navbar.hbs @@ -23,7 +23,9 @@
- + diff --git a/static/templates/stream_settings/stream_settings.hbs b/static/templates/stream_settings/stream_settings.hbs index 2735e6a169..c06ea3f2ba 100644 --- a/static/templates/stream_settings/stream_settings.hbs +++ b/static/templates/stream_settings/stream_settings.hbs @@ -4,7 +4,12 @@
+ {{#if subscribed }} + {{#tr}}Unsubscribe{{/tr}} + {{else}} + {{#tr}}Subscribe{{/tr}} + {{/if}} +
{{#if is_realm_admin}} diff --git a/static/templates/typeahead_list_item.hbs b/static/templates/typeahead_list_item.hbs index 9d96be5122..fc7f55e39f 100644 --- a/static/templates/typeahead_list_item.hbs +++ b/static/templates/typeahead_list_item.hbs @@ -1,8 +1,8 @@ {{#if is_emoji}} {{#if has_image}} - + {{else}} - + {{/if}}    {{else}} diff --git a/static/templates/user_profile_modal.hbs b/static/templates/user_profile_modal.hbs index 4f6bb25a28..7e3c663e55 100644 --- a/static/templates/user_profile_modal.hbs +++ b/static/templates/user_profile_modal.hbs @@ -48,9 +48,9 @@
- {{else if this.is_link}} + {{else if this.is_link}} {{this.value}} - {{else if this.is_external_account}} + {{else if this.is_external_account}} {{this.value}} {{else}} {{#if this.rendered_value}} diff --git a/templates/404.html b/templates/404.html index b2b022ffb1..64f01bb855 100644 --- a/templates/404.html +++ b/templates/404.html @@ -14,8 +14,10 @@ {% else %}

Page not found (404)

{% endif %} -

If this error is unexpected, you can contact - support.

+

+ If this error is unexpected, you can + contact support. +

diff --git a/templates/corporate/jobs.html b/templates/corporate/jobs.html index 2324e5ec88..9f3534eee4 100644 --- a/templates/corporate/jobs.html +++ b/templates/corporate/jobs.html @@ -157,11 +157,14 @@ suffice.
  • Share your expertise (both newly-acquired and longstanding) with the - rest of the team, through short- and long-form written communication.
  • + rest of the team, through short- and long-form written communication. +
  • Write primarily JavaScript using React Native, with some Java, Swift, - and backend Python, and learn whichever of those are new to you.
  • + and backend Python, and learn whichever of those are new to you. +
  • Work from our office in San Francisco, or from anywhere in the United - States.
  • + States. +

    Extra credit for any of the following:

    diff --git a/templates/corporate/upgrade.html b/templates/corporate/upgrade.html index 4291e394c1..3910afd4d4 100644 --- a/templates/corporate/upgrade.html +++ b/templates/corporate/upgrade.html @@ -200,7 +200,7 @@

    Number of licenses (minimum {{ min_invoiced_licenses }})


    + id="invoiced_licenses" name="licenses" required/>
    diff --git a/templates/corporate/zephyr.html b/templates/corporate/zephyr.html index 46d4089434..aa9d2b5415 100644 --- a/templates/corporate/zephyr.html +++ b/templates/corporate/zephyr.html @@ -35,13 +35,21 @@

    If you want to automatically transfer your existing Zephyr subscriptions

      -
    1. Get your Zulip API key from the Zulip "Settings" panel and put it in a file in your - Athena home directory called ~/Private/.zulip-api-key.

    2. +
    3. +

      + Get your Zulip API key from the Zulip "Settings" panel and put it in a file in your + Athena home directory called ~/Private/.zulip-api-key. +

      +
    4. -
    5. Run the following command to copy over all of your subscriptions:
      - /mit/tabbott/zulip/zephyr_mirror.py --sync-subscriptions

      +
    6. +

      + Run the following command to copy over all of your subscriptions:
      + /mit/tabbott/zulip/zephyr_mirror.py --sync-subscriptions +

      -

      NOTE: Zulip supports several ways to control what messages you want to read +

      + NOTE: Zulip supports several ways to control what messages you want to read right now, but Zulip does not yet have a direct equivalent to BarnOwl filters. If you have more subscriptions than you generally read, we recommend that you use Zulip's "Mute" option to hide those subscriptions from your diff --git a/templates/zerver/app/index.html b/templates/zerver/app/index.html index c452bceb6a..5273174f0b 100644 --- a/templates/zerver/app/index.html +++ b/templates/zerver/app/index.html @@ -98,8 +98,8 @@

      - {% trans %}Unable to connect to - Zulip. Updates may be delayed.{% endtrans %} {{ _('Retrying soon...') }} {{ _('Try now.') }} + {% trans %}Unable to connect to Zulip. + Updates may be delayed.{% endtrans %} {{ _('Retrying soon...') }} {{ _('Try now.') }}
      @@ -115,8 +115,9 @@ Zephyr mirror script yourself in a screen session. - To fix - this, you'll need to use the web interface. + + To fix this, you'll need to use the web interface. +
      diff --git a/templates/zerver/config_error.html b/templates/zerver/config_error.html index 6136d00f66..3dac1bacce 100644 --- a/templates/zerver/config_error.html +++ b/templates/zerver/config_error.html @@ -77,9 +77,9 @@

      {% if development_environment %}

      - See also - the SAML - guide for the development environment. + See also the + SAML guide + for the development environment.

      {% endif %} {% endif %} diff --git a/templates/zerver/development/dev_tools.html b/templates/zerver/development/dev_tools.html index fc7ca766ba..8d5fbeb6df 100644 --- a/templates/zerver/development/dev_tools.html +++ b/templates/zerver/development/dev_tools.html @@ -98,10 +98,14 @@

      Connecting to the local PostgreSQL database

        -
      • ./manage.py dbshell: Connect to - PostgreSQL database via your terminal.
      • -
      • provision creates a ~/.pgpass file, - so psql -U zulip -h localhost works too.
      • +
      • + ./manage.py dbshell: Connect to + PostgreSQL database via your terminal. +
      • +
      • + provision creates a ~/.pgpass file, + so psql -U zulip -h localhost works too. +
      • To connect using a graphical PostgreSQL client diff --git a/templates/zerver/for-companies.html b/templates/zerver/for-companies.html index 99353372b7..22b35a8cbb 100644 --- a/templates/zerver/for-companies.html +++ b/templates/zerver/for-companies.html @@ -292,10 +292,18 @@ Powerful formatting

          -
        • Zulip - code blocks come with syntax highlighting for over 250 languages, and integrated code playgrounds.
        • -
        • Type LaTeX directly into your Zulip message, and see it beautifully rendered.
        • -
        • Enjoy inline image, video and Tweet previews.
        • +
        • +
          + Zulip code blocks + come with syntax highlighting for over 250 languages, and integrated code playgrounds. +
          +
        • +
        • +
          Type LaTeX directly into your Zulip message, and see it beautifully rendered.
          +
        • +
        • +
          Enjoy inline image, video and Tweet previews.
          +
        • If you made a mistake, no worries! You diff --git a/templates/zerver/for-education.html b/templates/zerver/for-education.html index c3b35de000..a5c59b23cd 100644 --- a/templates/zerver/for-education.html +++ b/templates/zerver/for-education.html @@ -218,12 +218,21 @@ With apps for every platform, you can check Zulip at your computer or on your phone.
        • -
        • Zulip alerts you - about timely messages with fully customizable mobile, email and desktop notifications.
        • -
        • Mention users, groups of users or everyone when you need their attention.
          +
        • +
          + Zulip alerts you about timely messages with + fully customizable mobile, email and desktop notifications. +
          +
        • +
        • +
          Mention users, groups of users or everyone when you need their attention.
          +
        • +
        • +
          Use Zulip in your language of choice, with translations into 17 languages.
          +
        • +
        • +
          Zulip works reliably for organizations with thousands of users online at once.
        • -
        • Use Zulip in your language of choice, with translations into 17 languages.
        • -
        • Zulip works reliably for organizations with thousands of users online at once.
      diff --git a/templates/zerver/for-events.html b/templates/zerver/for-events.html index baff95a463..05c56f9f4e 100644 --- a/templates/zerver/for-events.html +++ b/templates/zerver/for-events.html @@ -113,12 +113,24 @@ Your communication hub
      @@ -239,8 +251,12 @@
    7. Zulip alerts participants about timely messages with fully customizable mobile, email and desktop notifications.
    8. Mention users, groups of users or everyone when you need their attention.
    9. -
    10. Zulip works reliably for organizations - with thousands of users online at once.
    11. +
    12. +
      + Zulip works reliably for organizations + with thousands of users online at once. +
      +
    13. diff --git a/templates/zerver/for-open-source.html b/templates/zerver/for-open-source.html index 5432267684..8b40ccd319 100644 --- a/templates/zerver/for-open-source.html +++ b/templates/zerver/for-open-source.html @@ -243,17 +243,17 @@ Zulip is 100% open-source software, with no "open core" catch. We work hard to make it easy to set up, - backup - , and maintain - a self-hosted Zulip installation, where you + backup, + and maintain + a self-hosted Zulip installation, where you have full control of your data.
    14. - Our high quality export - and import - tools ensure that you can always move from + Our high quality export + and import + tools ensure that you can always move from Zulip Cloud hosting to your own servers. There is no lock-in.
      diff --git a/templates/zerver/for-research.html b/templates/zerver/for-research.html index 7d13b3b1c5..699c275a5f 100644 --- a/templates/zerver/for-research.html +++ b/templates/zerver/for-research.html @@ -61,8 +61,12 @@ discussion.
    15. -
    16. Find active conversations, or see what - happened while you were away, with the Recent Topics view.
    17. +
    18. +
      + Find active conversations, or see what happened while you were away, + with the Recent Topics view. +
      +
    19. Keep discussions orderly @@ -154,9 +158,19 @@ Powerful formatting
      diff --git a/templates/zerver/history.html b/templates/zerver/history.html index 97154a3134..edb03ddb5b 100644 --- a/templates/zerver/history.html +++ b/templates/zerver/history.html @@ -117,8 +117,7 @@ As of October 2018, the Zulip server project had merged 6500 pull requests written by over - - 400 developers. + 400 developers.
    20. diff --git a/templates/zerver/lean-case-study.html b/templates/zerver/lean-case-study.html index 41c405a4c7..248bf7fb03 100644 --- a/templates/zerver/lean-case-study.html +++ b/templates/zerver/lean-case-study.html @@ -20,9 +20,8 @@

      Case study:
      Lean theorem prover community

      - Learn more about using Zulip for research
      and open source communities. + Learn more about using Zulip for research
      + and open source communities.
      diff --git a/templates/zerver/login.html b/templates/zerver/login.html index 4a3840fab0..86de0c4a10 100644 --- a/templates/zerver/login.html +++ b/templates/zerver/login.html @@ -48,11 +48,15 @@ page can be easily identified in it's respective JavaScript file. --> {% endif %} {% if no_auth_enabled %}
      -

      No authentication backends are enabled on this - server yet, so it is impossible to log in!

      +

      + No authentication backends are enabled on this + server yet, so it is impossible to log in! +

      -

      See the Zulip - authentication documentation to learn how to configure authentication backends.

      +

      + See the + Zulip authentication documentation to learn how to configure authentication backends. +

      {% else %} {% if password_auth_enabled %} diff --git a/templates/zerver/portico.html b/templates/zerver/portico.html index 1ddcc8687e..0145459cee 100644 --- a/templates/zerver/portico.html +++ b/templates/zerver/portico.html @@ -3,8 +3,8 @@ {# A base template for stuff like login, register, etc. - Not inside the app itself, but covered by the same structure, - hence the name. +Not inside the app itself, but covered by the same structure, +hence the name. #} {% block content %} diff --git a/templates/zerver/unsubscribe_link_error.html b/templates/zerver/unsubscribe_link_error.html index 5978e41bca..8b2cf02232 100644 --- a/templates/zerver/unsubscribe_link_error.html +++ b/templates/zerver/unsubscribe_link_error.html @@ -4,8 +4,10 @@

      {% trans %}Unknown email unsubscribe request{% endtrans %}

      -

      {% trans %}Hi there! It looks like you tried to unsubscribe from something, but we don't -recognize the URL.{% endtrans %}

      +

      + {% trans %}Hi there! It looks like you tried to unsubscribe from something, + but we don't recognize the URL.{% endtrans %} +

      {% trans %}Please double-check that you have the full URL and try again, or email us and we'll get this squared away!{% endtrans %}

      diff --git a/tools/lib/pretty_print.py b/tools/lib/pretty_print.py index d1495acf81..ec4fb7c77c 100644 --- a/tools/lib/pretty_print.py +++ b/tools/lib/pretty_print.py @@ -1,223 +1,198 @@ import subprocess -from typing import Any, Dict, List +from typing import List, Optional, Set from zulint.printer import ENDC, GREEN -from .template_parser import is_django_block_tag, tokenize +from .template_parser import Token, is_django_block_tag, tokenize -def pretty_print_html(html: str, num_spaces: int = 4) -> str: - # We use 1-based indexing for both rows and columns. +def requires_indent(line: str) -> bool: + line = line.lstrip() + return line.startswith("<") + + +def open_token(token: Token) -> bool: + if token.kind in ( + "handlebars_start", + "html_start", + ): + return True + + if token.kind in ( + "django_start", + "jinja2_whitespace_stripped_start", + "jinja2_whitespace_stripped_type2_start", + ): + return is_django_block_tag(token.tag) + + return False + + +def close_token(token: Token) -> bool: + return token.kind in ( + "django_end", + "handlebars_end", + "html_end", + "jinja2_whitespace_stripped_end", + ) + + +def else_token(token: Token) -> bool: + return token.kind in ( + "django_else", + "handlebars_else", + ) + + +def pop_unused_tokens(tokens: List[Token], row: int) -> bool: + while tokens and tokens[-1].line <= row: + token = tokens.pop() + if close_token(token): + return True + return False + + +def indent_pref(row: int, tokens: List[Token], line: str) -> str: + opens = 0 + closes = 0 + is_else = False + + while tokens and tokens[-1].line == row: + token = tokens.pop() + if open_token(token): + opens += 1 + elif close_token(token): + closes += 1 + elif else_token(token): + is_else = True + + if is_else: + if opens and closes: + return "neutral" + return "else" + + i = opens - closes + if i == 0: + return "neutral" + elif i == 1: + return "open" + elif i == -1: + return "close" + else: + print(i, opens, closes) + raise Exception(f"too many tokens on row {row}") + + +def indent_level(s: str) -> int: + return len(s) - len(s.lstrip()) + + +def same_indent(s1: str, s2: str) -> bool: + return indent_level(s1) == indent_level(s2) + + +def next_non_blank_line(lines: List[str], i: int) -> str: + next_line = "" + for j in range(i + 1, len(lines)): + next_line = lines[j] + if next_line.strip() != "": + break + return next_line + + +def get_exempted_lines(tokens: List[Token]) -> Set[int]: + exempted = set() + for code_tag in ("code", "pre", "script"): + for token in tokens: + if token.kind == "html_start" and token.tag == code_tag: + start: Optional[int] = token.line + + if token.kind == "html_end" and token.tag == code_tag: + # The pretty printer expects well-formed HTML, even + # if it's strangely formatted, so we expect start + # to be None. + assert start is not None + + # We leave code blocks completely alone, including + # the start and end tags. + for i in range(start, token.line + 1): + exempted.add(i) + start = None + return exempted + + +def pretty_print_html(html: str) -> str: tokens = tokenize(html) + + exempted_lines = get_exempted_lines(tokens) + + tokens.reverse() lines = html.split("\n") - # We will keep a stack of "start" tags so that we know - # when HTML ranges end. Note that some start tags won't - # be blocks from an indentation standpoint. - stack: List[Dict[str, Any]] = [] - - # Seed our stack with a pseudo entry to make depth calculations - # easier. - info: Dict[str, Any] = dict( - block=False, - depth=-1, - line=-1, - token_kind="html_start", - tag="html", - extra_indent=0, - ignore_lines=[], - ) - stack.append(info) - - # Our main job is to figure out offsets that we use to nudge lines - # over by. - offsets: Dict[int, int] = {} - - # Loop through our start/end tokens, and calculate offsets. As - # we proceed, we will push/pop info dictionaries on/off a stack. - for token in tokens: - - if ( - token.kind - in ( - "html_start", - "handlebars_start", - "handlebars_singleton", - "html_singleton", - "django_start", - "jinja2_whitespace_stripped_type2_start", - "jinja2_whitespace_stripped_start", - ) - and stack[-1]["tag"] != "pre" - ): - # An HTML start tag should only cause a new indent if we - # are on a new line. - if token.tag not in ("extends", "include", "else", "elif") and ( - is_django_block_tag(token.tag) or token.kind != "django_start" - ): - is_block = token.line > stack[-1]["line"] - - if is_block: - if ( - ( - token.kind == "handlebars_start" - and stack[-1]["token_kind"] == "handlebars_start" - ) - or ( - token.kind - in { - "django_start", - "jinja2_whitespace_stripped_type2_start", - "jinja2_whitespace_stripped_start", - } - and stack[-1]["token_kind"] - in { - "django_start", - "jinja2_whitespace_stripped_type2_start", - "jinja2_whitespace_stripped_start", - } - ) - ) and not stack[-1]["indenting"]: - info = stack.pop() - info["depth"] = info["depth"] + 1 - info["indenting"] = True - info["adjust_offset_until"] = token.line - stack.append(info) - new_depth = stack[-1]["depth"] + 1 - extra_indent = stack[-1]["extra_indent"] - line = lines[token.line - 1] - adjustment = len(line) - len(line.lstrip()) + 1 - offset = (1 + extra_indent + new_depth * num_spaces) - adjustment - info = dict( - block=True, - depth=new_depth, - actual_depth=new_depth, - line=token.line, - tag=token.tag, - token_kind=token.kind, - line_span=token.line_span, - offset=offset, - extra_indent=token.col - adjustment + extra_indent, - extra_indent_prev=extra_indent, - adjustment=adjustment, - indenting=True, - adjust_offset_until=token.line, - ignore_lines=[], - ) - if token.kind in ("handlebars_start", "django_start"): - info.update(dict(depth=new_depth - 1, indenting=False)) - else: - info = dict( - block=False, - depth=stack[-1]["depth"], - actual_depth=stack[-1]["depth"], - line=token.line, - tag=token.tag, - token_kind=token.kind, - extra_indent=stack[-1]["extra_indent"], - ignore_lines=[], - ) - stack.append(info) - elif ( - token.kind - in ( - "html_end", - "handlebars_end", - "html_singleton_end", - "django_end", - "handlebars_singleton_end", - "jinja2_whitespace_stripped_end", - ) - and (stack[-1]["tag"] != "pre" or token.tag == "pre") - ): - info = stack.pop() - if info["block"]: - # We are at the end of an indentation block. We - # assume the whole block was formatted ok before, just - # possibly at an indentation that we don't like, so we - # nudge over all lines in the block by the same offset. - start_line = info["line"] - end_line = token.line - if token.tag == "pre": - offsets[start_line] = 0 - offsets[end_line] = 0 - stack[-1]["ignore_lines"].append(start_line) - stack[-1]["ignore_lines"].append(end_line) - else: - offsets[start_line] = info["offset"] - line = lines[token.line - 1] - adjustment = len(line) - len(line.lstrip()) + 1 - if adjustment == token.col and token.kind != "html_singleton_end": - offsets[end_line] = ( - info["offset"] - + info["adjustment"] - - adjustment - + info["extra_indent"] - - info["extra_indent_prev"] - ) - elif start_line + info["line_span"] - 1 == end_line and info["line_span"] > 1: - offsets[end_line] = ( - 1 + info["extra_indent"] + (info["depth"] + 1) * num_spaces - ) - adjustment - # We would like singleton tags and tags which spread over - # multiple lines to have 2 space indentation. - offsets[end_line] -= 2 - elif token.line != info["line"]: - offsets[end_line] = info["offset"] - if token.tag != "pre" and token.tag != "script": - for line_num in range(start_line + 1, end_line): - # Be careful not to override offsets that happened - # deeper in the HTML within our block. - if line_num not in offsets: - line = lines[line_num - 1] - new_depth = info["depth"] + 1 - if ( - line.lstrip().startswith("{{else}}") - or line.lstrip().startswith("{% else %}") - or line.lstrip().startswith("{% elif") - ): - new_depth = info["actual_depth"] - extra_indent = info["extra_indent"] - adjustment = len(line) - len(line.lstrip()) + 1 - offset = (1 + extra_indent + new_depth * num_spaces) - adjustment - if line_num <= start_line + info["line_span"] - 1: - # We would like singleton tags and tags which spread over - # multiple lines to have 2 space indentation. - offset -= 2 - offsets[line_num] = offset - elif ( - token.kind in ("handlebars_end", "django_end") - and info["indenting"] - and line_num < info["adjust_offset_until"] - and line_num not in info["ignore_lines"] - ): - offsets[line_num] += num_spaces - elif token.tag != "pre": - for line_num in range(start_line + 1, end_line): - if line_num not in offsets: - offsets[line_num] = info["offset"] - else: - for line_num in range(start_line + 1, end_line): - if line_num not in offsets: - offsets[line_num] = 0 - stack[-1]["ignore_lines"].append(line_num) - - # Now that we have all of our offsets calculated, we can just - # join all our lines together, fixing up offsets as needed. + open_offsets: List[str] = [] formatted_lines = [] - for i, line in enumerate(html.split("\n")): - row = i + 1 - offset = offsets.get(row, 0) - pretty_line = line + next_offset: str = "" + tag_end_row: Optional[int] = None + tag_continuation_offset = "" + + def line_offset(row: int, line: str, next_line: str) -> Optional[str]: + nonlocal next_offset + nonlocal tag_end_row + nonlocal tag_continuation_offset + + if tag_end_row and row < tag_end_row: + was_closed = pop_unused_tokens(tokens, row) + if was_closed: + next_offset = open_offsets.pop() + return tag_continuation_offset + + offset = next_offset + if tokens: + token = tokens[-1] + if token.line == row and token.line_span > 1: + if token.kind in ("django_comment", "handlebar_comment", "html_comment"): + tag_continuation_offset = offset + else: + tag_continuation_offset = offset + " " + tag_end_row = row + token.line_span + + pref = indent_pref(row, tokens, line) + if pref == "open": + if same_indent(line, next_line) and not requires_indent(line): + next_offset = offset + else: + next_offset = offset + " " * 4 + open_offsets.append(offset) + elif pref == "else": + offset = open_offsets[-1] + if same_indent(line, next_line): + next_offset = offset + else: + next_offset = offset + " " * 4 + elif pref == "close": + offset = open_offsets.pop() + next_offset = offset + return offset + + def adjusted_line(row: int, line: str, next_line: str) -> str: if line.strip() == "": - pretty_line = "" - else: - if offset > 0: - pretty_line = (" " * offset) + pretty_line - elif offset < 0: - pretty_line = pretty_line[-1 * offset :] - assert line.strip() == pretty_line.strip() - formatted_lines.append(pretty_line) + return "" + + offset = line_offset(row, line, next_line) + + if row in exempted_lines: + return line.rstrip() + + if offset is None: + return line.rstrip() + + return offset + line.strip() + + for i, line in enumerate(lines): + # We use 1-based indexing for both rows and columns. + next_line = next_non_blank_line(lines, i) + row = i + 1 + formatted_lines.append(adjusted_line(row, line, next_line)) return "\n".join(formatted_lines) diff --git a/tools/lib/template_parser.py b/tools/lib/template_parser.py index b433a99df8..c829f3ceb4 100644 --- a/tools/lib/template_parser.py +++ b/tools/lib/template_parser.py @@ -70,11 +70,17 @@ def tokenize(text: str) -> List[Token]: def looking_at_handlebars_start() -> bool: return looking_at("{{#") or looking_at("{{^") + def looking_at_handlebars_else() -> bool: + return looking_at("{{else") + def looking_at_handlebars_end() -> bool: return looking_at("{{/") def looking_at_django_start() -> bool: - return looking_at("{% ") and not looking_at("{% end") + return looking_at("{% ") + + def looking_at_django_else() -> bool: + return looking_at("{% else") or looking_at("{% elif") def looking_at_django_end() -> bool: return looking_at("{% end") @@ -130,6 +136,10 @@ def tokenize(text: str) -> List[Token]: s = get_html_tag(text, state.i) tag = s[2:-1] kind = "html_end" + elif looking_at_handlebars_else(): + s = get_handlebars_tag(text, state.i) + tag = "else" + kind = "handlebars_else" elif looking_at_handlebars_start(): s = get_handlebars_tag(text, state.i) tag = s[3:-2].split()[0] @@ -140,17 +150,22 @@ def tokenize(text: str) -> List[Token]: s = get_handlebars_tag(text, state.i) tag = s[3:-2] kind = "handlebars_end" + elif looking_at_django_else(): + s = get_django_tag(text, state.i) + tag = "else" + kind = "django_else" + elif looking_at_django_end(): + s = get_django_tag(text, state.i) + tag = s[6:-3] + kind = "django_end" elif looking_at_django_start(): + # must check this after end/else s = get_django_tag(text, state.i) tag = s[3:-2].split()[0] kind = "django_start" if s[-3] == "-": kind = "jinja2_whitespace_stripped_start" - elif looking_at_django_end(): - s = get_django_tag(text, state.i) - tag = s[6:-3] - kind = "django_end" elif looking_at_jinja2_end_whitespace_stripped(): s = get_django_tag(text, state.i) tag = s[7:-3] @@ -284,6 +299,7 @@ def validate( state.foreign = True def f(end_token: Token) -> None: + is_else_tag = end_token.tag == "else" end_tag = end_token.tag.strip("~") end_line = end_token.line @@ -297,9 +313,12 @@ def validate( problem = None if (start_tag == "code") and (end_line == start_line + 1): problem = "Code tag is split across two lines." - if start_tag != end_tag: + if is_else_tag: + pass + elif start_tag != end_tag: problem = "Mismatched tag." - elif check_indent and (end_line > start_line + max_lines): + + if not problem and check_indent and (end_line > start_line + max_lines): if end_col != start_col: problem = "Bad indentation." @@ -315,7 +334,7 @@ def validate( raise TemplateParserException( f""" fn: {fn} - {problem} + {problem} start: {start_token.s} line {start_line}, col {start_col} @@ -324,9 +343,11 @@ def validate( line {end_line}, col {end_col} """ ) - state.matcher = old_matcher - state.foreign = old_foreign - state.depth -= 1 + + if not is_else_tag: + state.matcher = old_matcher + state.foreign = old_foreign + state.depth -= 1 state.matcher = f @@ -350,17 +371,20 @@ def validate( elif kind == "handlebars_start": start_tag_matcher(token) + elif kind == "handlebars_else": + state.matcher(token) elif kind == "handlebars_end": state.matcher(token) elif kind in { "django_start", + "django_else", "jinja2_whitespace_stripped_start", "jinja2_whitespace_stripped_type2_start", }: if is_django_block_tag(tag): start_tag_matcher(token) - elif kind in {"django_end", "jinja2_whitespace_stripped_end"}: + elif kind in {"django_else", "django_end", "jinja2_whitespace_stripped_end"}: state.matcher(token) if state.depth != 0: diff --git a/tools/tests/test_pretty_print.py b/tools/tests/test_pretty_print.py index b1a4e13b54..419ccaa36e 100644 --- a/tools/tests/test_pretty_print.py +++ b/tools/tests/test_pretty_print.py @@ -192,12 +192,12 @@ BAD_HTML8 = """ GOOD_HTML8 = """ {{#each test}} {{#with this}} - {{#if foobar}} + {{#if foobar}}
      {{{test}}}
      - {{/if}} - {{#if foobar2}} - {{> teststuff}} - {{/if}} + {{/if}} + {{#if foobar2}} + {{> teststuff}} + {{/if}} {{/with}} {{/each}} """ @@ -325,9 +325,9 @@ GOOD_HTML13 = """
       :{{this.name}}:
      {{else}} {{#if this.is_realm_emoji}} - + {{else}} -
      +
      {{/if}} {{/if}}
      {{this.count}}
      @@ -354,12 +354,12 @@ GOOD_HTML14 = """ {{#if this.code}}
      Here goes some cool code.
      {{else}} -
      - content of first div
      - content of second div. + content of first div +
      + content of second div. +
      -
      {{/if}}
      """