mirror of https://github.com/zulip/zulip.git
linters: Rewrite check-templates.
I rewrote most of tools/lib/pretty-printer.py, which was fairly easy due to being able to crib some important details from the previous implementation. The main motivation for the rewrite was that we weren't handling else/elif blocks correctly, and it was difficult to modify the previous code. The else/elif shortcomings were somewhat historical in nature--the original parser didn't recognize them (since they weren't in any Zulip templates at the time), and then the pretty printer was mostly able to hack around that due to the "nudge" strategy. Eventually the nudge strategy became too brittle. The "nudge" strategy was that we would mostly trust the existing templates, and we would just nudge over some lines in cases of obviously faulty indentation. Now we are bit more opinionated and rigorous, and we basically set the indentation explicitly for any line that is not in a code/script block. This leads to this diff touching several templates for mostly minor fix-ups. We aren't completely opinionated, as we respect the author's line wrapping decisions in many cases, and we also allow authors not to indent blocks within the template language's block constructs.
This commit is contained in:
parent
4792af5682
commit
fdd63546b2
|
@ -36,7 +36,13 @@
|
|||
|
||||
{{#unless status_message}}
|
||||
{{#unless is_hidden}}
|
||||
<div class="message_content rendered_markdown">{{#if use_match_properties}}{{rendered_markdown msg/match_content}}{{else}}{{rendered_markdown msg/content}}{{/if}}</div>
|
||||
<div class="message_content rendered_markdown">
|
||||
{{#if use_match_properties}}
|
||||
{{rendered_markdown msg/match_content}}
|
||||
{{else}}
|
||||
{{rendered_markdown msg/content}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{> message_hidden_dialog}}
|
||||
{{/unless}}
|
||||
|
|
|
@ -72,8 +72,10 @@
|
|||
{{#if has_been_editable}}
|
||||
<div class="message-edit-timer-control-group">
|
||||
<span class="message_edit_countdown_timer"></span>
|
||||
<span><i id="message_edit_tooltip" class="tippy-zulip-tooltip message_edit_tooltip fa fa-question-circle" aria-hidden="true"
|
||||
{{#if is_widget_message}} data-tippy-content="{{#tr}}Widgets cannot be edited.{{/tr}}" {{else}} data-tippy-content="{{#tr}}This organization is configured to restrict editing of message content to {minutes_to_edit} minutes after it is sent.{{/tr}}" {{/if}}></i>
|
||||
<span>
|
||||
<i id="message_edit_tooltip" class="tippy-zulip-tooltip message_edit_tooltip fa fa-question-circle" aria-hidden="true"
|
||||
{{#if is_widget_message}} data-tippy-content="{{#tr}}Widgets cannot be edited.{{/tr}}" {{else}} data-tippy-content="{{#tr}}This organization is configured to restrict editing of message content to {minutes_to_edit} minutes after it is sent.{{/tr}}" {{/if}}>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
<div contenteditable="true" class="input search-query input-block-level" id="search_query" type="text" placeholder="{{t 'Search' }}"
|
||||
autocomplete="off" aria-label="{{t 'Search' }}" title="{{t 'Search' }} (/)">
|
||||
</div>
|
||||
<button class="btn search_button" type="button" id="search_exit" aria-label="{{t 'Exit search' }}"><i class="fa fa-remove" aria-hidden="true"></i></button>
|
||||
<button class="btn search_button" type="button" id="search_exit" aria-label="{{t 'Exit search' }}">
|
||||
<i class="fa fa-remove" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,12 @@
|
|||
<div class="button-group">
|
||||
<div class="sub_unsub_button_wrapper inline-block">
|
||||
<button class="button small rounded subscribe-button sub_unsub_button {{#unless subscribed }}unsubscribed{{/unless}}" type="button" name="button" {{#if should_display_subscription_button}}title="{{t 'Toggle subscription'}} (S)" {{else}}disabled="disabled"{{/if}}>
|
||||
{{#if subscribed }}{{#tr}}Unsubscribe{{/tr}}{{else}}{{#tr}}Subscribe{{/tr}}{{/if}}</button>
|
||||
{{#if subscribed }}
|
||||
{{#tr}}Unsubscribe{{/tr}}
|
||||
{{else}}
|
||||
{{#tr}}Subscribe{{/tr}}
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
<a href="{{preview_url}}" class="button small rounded" id="preview-stream-button" role="button" title="{{t 'View stream'}} (V)" {{#unless should_display_preview_button }}style="display: none"{{/unless}}><i class="fa fa-eye"></i></a>
|
||||
{{#if is_realm_admin}}
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
{% else %}
|
||||
<h1 class="lead">Page not found (404)</h1>
|
||||
{% endif %}
|
||||
<p>If this error is unexpected, you can <a href="mailto:{{ support_email }}">contact
|
||||
support</a>.</p>
|
||||
<p>
|
||||
If this error is unexpected, you can
|
||||
<a href="mailto:{{ support_email }}">contact support</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -157,11 +157,14 @@
|
|||
suffice.
|
||||
</li>
|
||||
<li>Share your expertise (both newly-acquired and longstanding) with the
|
||||
rest of the team, through short- and long-form written communication.</li>
|
||||
rest of the team, through short- and long-form written communication.
|
||||
</li>
|
||||
<li>Write primarily JavaScript using React Native, with some Java, Swift,
|
||||
and backend Python, and learn whichever of those are new to you.</li>
|
||||
and backend Python, and learn whichever of those are new to you.
|
||||
</li>
|
||||
<li>Work from our office in San Francisco, or from anywhere in the United
|
||||
States.</li>
|
||||
States.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Extra credit for any of the following:</h3>
|
||||
|
|
|
@ -35,13 +35,21 @@
|
|||
<h2>If you want to automatically transfer your existing Zephyr subscriptions</h2>
|
||||
|
||||
<ol>
|
||||
<li><p>Get your Zulip API key from the Zulip "Settings" panel and put it in a file in your
|
||||
Athena home directory called <code>~/Private/.zulip-api-key</code>.</p></li>
|
||||
<li>
|
||||
<p>
|
||||
Get your Zulip API key from the Zulip "Settings" panel and put it in a file in your
|
||||
Athena home directory called <code>~/Private/.zulip-api-key</code>.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li><p>Run the following command to copy over all of your subscriptions:<br />
|
||||
<code>/mit/tabbott/zulip/zephyr_mirror.py --sync-subscriptions</code></p>
|
||||
<li>
|
||||
<p>
|
||||
Run the following command to copy over all of your subscriptions:<br />
|
||||
<code>/mit/tabbott/zulip/zephyr_mirror.py --sync-subscriptions</code>
|
||||
</p>
|
||||
|
||||
<p> <strong>NOTE</strong>: Zulip supports several ways to control what messages you want to read
|
||||
<p>
|
||||
<strong>NOTE</strong>: 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
|
||||
|
|
|
@ -98,8 +98,8 @@
|
|||
<div class="alert-box">
|
||||
<div class="alert alert_sidebar alert-error home-error-bar" id="connection-error">
|
||||
<div class="exit"></div>
|
||||
{% trans %}<strong class="message">Unable to connect to
|
||||
Zulip.</strong> Updates may be delayed.{% endtrans %} {{ _('Retrying soon...') }} <a class="restart_get_events_button">{{ _('Try now.') }}</a>
|
||||
{% trans %}<strong class="message">Unable to connect to Zulip.</strong>
|
||||
Updates may be delayed.{% endtrans %} {{ _('Retrying soon...') }} <a class="restart_get_events_button">{{ _('Try now.') }}</a>
|
||||
</div>
|
||||
<div class="alert alert_sidebar alert-error home-error-bar" id="zephyr-mirror-error">
|
||||
<div class="exit"></div>
|
||||
|
@ -115,8 +115,9 @@
|
|||
Zephyr mirror script yourself</a> in a screen
|
||||
session.
|
||||
</span>
|
||||
<span id="desktop-zephyr-mirror-error-text" class="notdisplayed">To fix
|
||||
this, you'll need to use the web interface.</span>
|
||||
<span id="desktop-zephyr-mirror-error-text" class="notdisplayed">
|
||||
To fix this, you'll need to use the web interface.
|
||||
</span>
|
||||
</div>
|
||||
<div class="alert alert_sidebar alert-error home-error-bar" id="home-error"></div>
|
||||
<div class="alert alert_sidebar alert-error home-error-bar" id="reloading-application"></div>
|
||||
|
|
|
@ -77,9 +77,9 @@
|
|||
</p>
|
||||
{% if development_environment %}
|
||||
<p>
|
||||
See also
|
||||
the <a href="https://zulip.readthedocs.io/en/latest/development/authentication.html#saml">SAML
|
||||
guide</a> for the development environment.
|
||||
See also the
|
||||
<a href="https://zulip.readthedocs.io/en/latest/development/authentication.html#saml">SAML guide</a>
|
||||
for the development environment.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -98,10 +98,14 @@
|
|||
</p>
|
||||
<h2>Connecting to the local PostgreSQL database</h2>
|
||||
<ul>
|
||||
<li><code>./manage.py dbshell</code>: Connect to
|
||||
PostgreSQL database via your terminal.</li>
|
||||
<li><code>provision</code> creates a <code>~/.pgpass</code> file,
|
||||
so <code>psql -U zulip -h localhost</code> works too.</li>
|
||||
<li>
|
||||
<code>./manage.py dbshell</code>: Connect to
|
||||
PostgreSQL database via your terminal.
|
||||
</li>
|
||||
<li>
|
||||
<code>provision</code> creates a <code>~/.pgpass</code> file,
|
||||
so <code>psql -U zulip -h localhost</code> works too.
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
To connect using a graphical PostgreSQL client
|
||||
|
|
|
@ -292,10 +292,18 @@
|
|||
Powerful formatting
|
||||
</h1>
|
||||
<ul>
|
||||
<li><div class="list-content"><a href="/help/code-blocks">Zulip
|
||||
code blocks</a> come with syntax highlighting for over 250 languages, and integrated <a href="/help/code-blocks#code-playgrounds">code playgrounds.</a></div></li>
|
||||
<li><div class="list-content"><a href="/help/format-your-message-using-markdown#latex">Type LaTeX</a> directly into your Zulip message, and see it beautifully rendered.</div></li>
|
||||
<li><div class="list-content">Enjoy inline image, video and Tweet previews.</div></li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
<a href="/help/code-blocks">Zulip code blocks</a>
|
||||
come with syntax highlighting for over 250 languages, and integrated <a href="/help/code-blocks#code-playgrounds">code playgrounds.</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content"><a href="/help/format-your-message-using-markdown#latex">Type LaTeX</a> directly into your Zulip message, and see it beautifully rendered.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Enjoy inline image, video and Tweet previews.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
If you made a mistake, no worries! You
|
||||
|
|
|
@ -218,12 +218,21 @@
|
|||
With <a href="/apps">apps for every platform</a>, you can check Zulip at your computer or on your phone.
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="list-content">Zulip alerts you
|
||||
about timely messages with <a href="/help/stream-notifications">fully customizable</a> mobile, email and desktop notifications.</div></li>
|
||||
<li><div class="list-content">Mention <a href="/help/mention-a-user-or-group">users</a>, <a href="/help/mention-a-user-or-group#mention-a-user-or-group">groups of users</a> or <a href="/help/pm-mention-alert-notifications#wildcard-mentions">everyone</a> when you need their attention.</div>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
Zulip alerts you about timely messages with
|
||||
<a href="/help/stream-notifications">fully customizable</a> mobile, email and desktop notifications.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Mention <a href="/help/mention-a-user-or-group">users</a>, <a href="/help/mention-a-user-or-group#mention-a-user-or-group">groups of users</a> or <a href="/help/pm-mention-alert-notifications#wildcard-mentions">everyone</a> when you need their attention.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Use Zulip in your language of choice, with translations into <a href="https://www.transifex.com/zulip/zulip/">17 languages</a>.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Zulip works reliably for organizations with thousands of users online at once.</div>
|
||||
</li>
|
||||
<li><div class="list-content">Use Zulip in your language of choice, with translations into <a href="https://www.transifex.com/zulip/zulip/">17 languages</a>.</div></li>
|
||||
<li><div class="list-content">Zulip works reliably for organizations with thousands of users online at once.</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -113,12 +113,24 @@
|
|||
Your communication hub
|
||||
</h1>
|
||||
<ul>
|
||||
<li><div class="list-content">Share the agenda and presentations with
|
||||
<a href="/help/share-and-upload-files">drag-and-drop file uploads</a>.</div></li>
|
||||
<li><div class="list-content">Use <a href="/help/emoji-reactions">emoji reactions</a> for lightweight interactions. Have fun with <a href="/help/custom-emoji">custom emoji</a>, or get feedback with a <a href="/help/create-a-poll">poll</a>.</div></li>
|
||||
<li><div class="list-content">Announce the schedule without worrying about time zones using <a href="/help/format-your-message-using-markdown#global-times">global times</a>.</div></li>
|
||||
<li><div class="list-content">Make a <a href="/help/start-a-call">video call</a> with the click of a button.</div></li>
|
||||
<li><div class="list-content">Use Zulip in your language of choice, with translations into <a href="https://www.transifex.com/zulip/zulip/">17 languages</a>.</div></li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
Share the agenda and presentations with
|
||||
<a href="/help/share-and-upload-files">drag-and-drop file uploads</a>.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Use <a href="/help/emoji-reactions">emoji reactions</a> for lightweight interactions. Have fun with <a href="/help/custom-emoji">custom emoji</a>, or get feedback with a <a href="/help/create-a-poll">poll</a>.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Announce the schedule without worrying about time zones using <a href="/help/format-your-message-using-markdown#global-times">global times</a>.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Make a <a href="/help/start-a-call">video call</a> with the click of a button.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">Use Zulip in your language of choice, with translations into <a href="https://www.transifex.com/zulip/zulip/">17 languages</a>.</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="quote">
|
||||
|
@ -239,8 +251,12 @@
|
|||
</li>
|
||||
<li><div class="list-content">Zulip alerts participants about timely messages with <a href="/help/stream-notifications">fully customizable</a> mobile, email and desktop notifications.</div></li>
|
||||
<li><div class="list-content">Mention <a href="/help/mention-a-user-or-group">users</a>, <a href="/help/mention-a-user-or-group#mention-a-user-or-group">groups of users</a> or <a href="/help/pm-mention-alert-notifications#wildcard-mentions">everyone</a> when you need their attention.</div></li>
|
||||
<li><div class="list-content">Zulip works reliably for organizations
|
||||
with thousands of users online at once.</div></li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
Zulip works reliably for organizations
|
||||
with thousands of users online at once.
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -243,17 +243,17 @@
|
|||
Zulip is <a href="https://github.com/zulip">100%
|
||||
open-source software</a>, with no "open core"
|
||||
catch. We work hard to make it <a href="https://zulip.readthedocs.io/en/latest/production/install.html">easy to set up</a>,
|
||||
<a href="https://zulip.readthedocs.io/en/stable/production/export-and-import.html#backups">backup
|
||||
</a>, and <a href="https://zulip.readthedocs.io/en/stable/production/upgrade-or-modify.html">maintain
|
||||
</a> a self-hosted Zulip installation, where you
|
||||
<a href="https://zulip.readthedocs.io/en/stable/production/export-and-import.html#backups">backup</a>,
|
||||
and <a href="https://zulip.readthedocs.io/en/stable/production/upgrade-or-modify.html">maintain</a>
|
||||
a self-hosted Zulip installation, where you
|
||||
have full control of your data.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
Our high quality <a href="/help/export-your-organization">export
|
||||
</a> and <a href="https://zulip.readthedocs.io/en/latest/production/export-and-import.html">import
|
||||
</a> tools ensure that you can always move from
|
||||
Our high quality <a href="/help/export-your-organization">export</a>
|
||||
and <a href="https://zulip.readthedocs.io/en/latest/production/export-and-import.html">import</a>
|
||||
tools ensure that you can always move from
|
||||
<a href="/plans/">Zulip Cloud</a> hosting to your
|
||||
own servers. There is no lock-in.
|
||||
</div>
|
||||
|
|
|
@ -61,8 +61,12 @@
|
|||
discussion.
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="list-content">Find active conversations, or see what
|
||||
happened while you were away, with the <a href="/help/reading-strategies#recent-topics">Recent Topics</a> view.</div></li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
Find active conversations, or see what happened while you were away,
|
||||
with the <a href="/help/reading-strategies#recent-topics">Recent Topics</a> view.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
Keep discussions orderly
|
||||
|
@ -154,9 +158,19 @@
|
|||
Powerful formatting
|
||||
</h1>
|
||||
<ul>
|
||||
<li><div class="list-content"><a href="/help/format-your-message-using-markdown#latex">Type LaTeX</a> directly into your Zulip message, and see it beautifully rendered.</div></li>
|
||||
<li><div class="list-content"><a href="/help/code-blocks">Zulip
|
||||
code blocks</a> come with syntax highlighting for over 250 languages, and integrated <a href="/help/code-blocks#code-playgrounds">code playgrounds.</a></div></li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
<a href="/help/format-your-message-using-markdown#latex">Type LaTeX</a>
|
||||
directly into your Zulip message, and see it beautifully rendered.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
<a href="/help/code-blocks">Zulip code blocks</a>
|
||||
come with syntax highlighting for over 250 languages, and integrated
|
||||
<a href="/help/code-blocks#code-playgrounds">code playgrounds.</a>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="list-content">Structure your points with bulleted and numbered <a href="/help/format-your-message-using-markdown#lists">lists</a>.</div></li>
|
||||
<li>
|
||||
<div class="list-content">
|
||||
|
|
|
@ -371,8 +371,8 @@
|
|||
and <a href="https://github.com/matrix-org/synapse/graphs/contributors">matrix.org</a>.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://zulip.readthedocs.io/en/stable/production/install.html"
|
||||
class="button">Install Zulip {{ latest_release_version }}</a> or <a href="{{ latest_release_announcement }}">read the Zulip {{ latest_major_version }} release announcement</a>.
|
||||
<a href="https://zulip.readthedocs.io/en/stable/production/install.html" class="button">Install Zulip {{ latest_release_version }}</a>
|
||||
or <a href="{{ latest_release_announcement }}">read the Zulip {{ latest_major_version }} release announcement</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -117,8 +117,7 @@
|
|||
As of October 2018, the Zulip server project had
|
||||
merged <a href="https://github.com/zulip/zulip/pulls">
|
||||
6500 pull requests</a> written by over
|
||||
<a href="https://github.com/zulip/zulip/graphs/contributors">
|
||||
400 developers</a>.
|
||||
<a href="https://github.com/zulip/zulip/graphs/contributors">400 developers</a>.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
<h1 class="center">Case study: <br/>Lean theorem prover community</h1>
|
||||
</div>
|
||||
<div class="hero-text">
|
||||
Learn more about using Zulip for <a
|
||||
href="/for/research">research</a><br/> and <a
|
||||
href="/for/open-source">open source</a> communities.
|
||||
Learn more about using Zulip for <a href="/for/research">research</a><br/>
|
||||
and <a href="/for/open-source">open source</a> communities.
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
|
|
|
@ -48,11 +48,15 @@ page can be easily identified in it's respective JavaScript file. -->
|
|||
{% endif %}
|
||||
{% if no_auth_enabled %}
|
||||
<div class="alert">
|
||||
<p>No authentication backends are enabled on this
|
||||
server yet, so it is impossible to log in!</p>
|
||||
<p>
|
||||
No authentication backends are enabled on this
|
||||
server yet, so it is impossible to log in!
|
||||
</p>
|
||||
|
||||
<p>See the <a href="https://zulip.readthedocs.io/en/latest/production/install.html#step-3-configure-zulip">Zulip
|
||||
authentication documentation</a> to learn how to configure authentication backends.</p>
|
||||
<p>
|
||||
See the <a href="https://zulip.readthedocs.io/en/latest/production/install.html#step-3-configure-zulip">
|
||||
Zulip authentication documentation</a> to learn how to configure authentication backends.
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if password_auth_enabled %}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
<h1>{% trans %}Unknown email unsubscribe request{% endtrans %}</h1>
|
||||
|
||||
<p>{% trans %}Hi there! It looks like you tried to unsubscribe from something, but we don't
|
||||
recognize the URL.{% endtrans %}</p>
|
||||
<p>
|
||||
{% trans %}Hi there! It looks like you tried to unsubscribe from something,
|
||||
but we don't recognize the URL.{% endtrans %}
|
||||
</p>
|
||||
|
||||
<p>{% trans %}Please double-check that you have the full URL and try again, or <a href="mailto:{{ support_email }}?Subject=Unsubscribe%20me%2C%20please!&Body=Hi%20there!%0A%0AI%20clicked%20this%20unsubscribe%20link%20in%20a%20Zulip%20e-mail%2C%20but%20it%20took%20me%20to%20an%20error%20page%3A%0A%0A_____________%0A%0APlease%20unsubscribe%20me.%0A%0AThanks%2C%0A_____________%0A">email us</a> and we'll get this squared away!{% endtrans %}</p>
|
||||
|
||||
|
|
|
@ -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.
|
||||
tokens = tokenize(html)
|
||||
lines = html.split("\n")
|
||||
def requires_indent(line: str) -> bool:
|
||||
line = line.lstrip()
|
||||
return line.startswith("<")
|
||||
|
||||
# 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",
|
||||
def open_token(token: Token) -> bool:
|
||||
if token.kind in (
|
||||
"handlebars_start",
|
||||
"handlebars_singleton",
|
||||
"html_singleton",
|
||||
"django_start",
|
||||
"jinja2_whitespace_stripped_type2_start",
|
||||
"jinja2_whitespace_stripped_start",
|
||||
)
|
||||
and stack[-1]["tag"] != "pre"
|
||||
"html_start",
|
||||
):
|
||||
# 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"]
|
||||
return True
|
||||
|
||||
if is_block:
|
||||
if (
|
||||
(
|
||||
token.kind == "handlebars_start"
|
||||
and stack[-1]["token_kind"] == "handlebars_start"
|
||||
)
|
||||
or (
|
||||
token.kind
|
||||
in {
|
||||
if 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",
|
||||
):
|
||||
return is_django_block_tag(token.tag)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def close_token(token: Token) -> bool:
|
||||
return token.kind in (
|
||||
"django_end",
|
||||
"handlebars_singleton_end",
|
||||
"handlebars_end",
|
||||
"html_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.
|
||||
formatted_lines = []
|
||||
for i, line in enumerate(html.split("\n")):
|
||||
row = i + 1
|
||||
offset = offsets.get(row, 0)
|
||||
pretty_line = line
|
||||
if line.strip() == "":
|
||||
pretty_line = ""
|
||||
|
||||
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:
|
||||
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)
|
||||
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")
|
||||
|
||||
open_offsets: List[str] = []
|
||||
formatted_lines = []
|
||||
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() == "":
|
||||
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)
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
@ -324,6 +343,8 @@ def validate(
|
|||
line {end_line}, col {end_col}
|
||||
"""
|
||||
)
|
||||
|
||||
if not is_else_tag:
|
||||
state.matcher = old_matcher
|
||||
state.foreign = old_foreign
|
||||
state.depth -= 1
|
||||
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue