#56 Conditionals in Markdown

Open
opened 1 month ago by edemaine · 4 comments
edemaine commented 1 month ago

It’d be really nice to be able to cleanly make parts of pages conditional, for example, accessibly only to staff (if is_staff). You already have @{...} for inline expansion, like most templating languages, and such languages also typically have some conditionals (and loops). For example, Spacebars has {{#if}} and Jade has if; here’s a big table. Probably the hardest question is what the right syntax is. Some options I see:

  • JSX-style jumping between Python and HTML/Markdown. This approach may not be that compatible with Markdown or Python, though. You could imagine @{{...}} for embedding multiline Python statements (instead of just expressions). And print('''...''') to get back out? (Seems a bit ugly…)
  • <#if> or similar HTML-looking tag, given that Markdown supports HTML which you could later postprocess (including running Markdown again on the contents, if necessary).
  • <div cs-hide-if="@{condition}"> or <div cs-show-if="@{condition}"> or other HTML attributes that trigger Catsoop action (deleting element if/unless Python condition holds). Perhaps the simplest approach? Again relying on HTML-in-Markdown, which can (in appropriate circumstances, with enough blank lines as I recall) support Markdown inside HTML blocks.
It'd be really nice to be able to cleanly make parts of pages conditional, for example, accessibly only to staff (`if is_staff`). You already have `@{...}` for inline expansion, like most templating languages, and such languages also typically have some conditionals (and loops). For example, [Spacebars has `{{#if}}`](http://blazejs.org/api/spacebars.html#If-Unless) and [Jade has `if`](http://jade-lang.com/); [here's a big table](https://en.wikipedia.org/wiki/Comparison_of_web_template_engines). Probably the hardest question is what the right syntax is. Some options I see: * JSX-style jumping between Python and HTML/Markdown. This approach may not be that compatible with Markdown or Python, though. You could imagine `@{{...}}` for embedding multiline Python statements (instead of just expressions). And `print('''...''')` to get back out? (Seems a bit ugly...) * `<#if>` or similar HTML-looking tag, given that Markdown supports HTML which you could later postprocess (including running Markdown again on the contents, if necessary). * `<div cs-hide-if="@{condition}">` or `<div cs-show-if="@{condition}">` or other HTML attributes that trigger Catsoop action (deleting element if/unless Python condition holds). Perhaps the simplest approach? Again relying on HTML-in-Markdown, which can (in appropriate circumstances, with enough blank lines as I recall) support Markdown inside HTML blocks.
hz commented 1 month ago
Owner

I definitely agree that it would be a really nice addition to have an easy way to conditionally include content (something people want to do all the time, I think). I’ll quickly note a few ways that this behavior is currently possible first:

CURRENTLY AVAILABLE

Some of the currently-available options (all of which are ugly, each in its own way) are:

Using Python directly, and using print to generate the output:

<python>
if is_staff:
    print(r'''
SECRET
''')
</python>

Something similar, but using @{...} to conditionally print something stored elsewhere:

<python>
staff_only_stuff = "SECRET"
</python>

@{staff_only_stuff if is_staff else ""}

Or, we can define a ‘custom tag’ in preload.py, something like:

def cs_course_handle_custom_tags(text):
    def handle_staffonly(m):
        if cs_user_info.get('role', None) in {'UTA', 'TA', 'Instructor', 'Admin'}:
            return m.groupdict()['body']
        else:
            return ''
    todo_regex = re.compile(r"<staffonly>(?P<body>.*?)</staffonly>", re.MULTILINE|re.DOTALL)
    text = re.sub(staffonly_regex, handle_staffonly, text)

Then any text in <staffonly>...</staffonly> would only be rendered for staff.

NEW SYNTAX

Of your suggestions, I think I like your last suggestion the best, in part because implementation seems like it would be pretty straightforward, given BeautifulSoup. A couple of issues, though, that we’ve had with similar stuff in the past, that might show up:

  • Part of the Markdown spec is that Markdown isn’t parsed inside of block-level HTML tags, so wrapping in a div is probably not super nice. There’s an extension to Python-Markdown that would enable that, I think, but we may still have the next issue:

  • Having a blank line inside of an HTML tag can confuse the combination of Markdown and BeautifulSoup (I think because Markdown adds </p> or something like that, which then causes BeautifulSoup to freak out and close the enclosing HTML tag). But it might just be worth figuring out how to appease those libraries, rather than doing something else to avoid the issue.

Definitely happy to continue the conversation; I think this would be a great thing to have, and it’s worth making sure we choose a sensible syntax.

I definitely agree that it would be a really nice addition to have an easy way to conditionally include content (something people want to do all the time, I think). I'll quickly note a few ways that this behavior is currently possible first: **CURRENTLY AVAILABLE** Some of the currently-available options (all of which are ugly, each in its own way) are: Using Python directly, and using `print` to generate the output: ```nohighlight <python> if is_staff: print(r''' SECRET ''') </python> ``` Something similar, but using `@{...}` to conditionally print something stored elsewhere: ```nohighlight <python> staff_only_stuff = "SECRET" </python> @{staff_only_stuff if is_staff else ""} ``` Or, we can define a 'custom tag' in `preload.py`, something like: ```py def cs_course_handle_custom_tags(text): def handle_staffonly(m): if cs_user_info.get('role', None) in {'UTA', 'TA', 'Instructor', 'Admin'}: return m.groupdict()['body'] else: return '' todo_regex = re.compile(r"<staffonly>(?P<body>.*?)</staffonly>", re.MULTILINE|re.DOTALL) text = re.sub(staffonly_regex, handle_staffonly, text) ``` Then any text in `<staffonly>...</staffonly>` would only be rendered for staff. **NEW SYNTAX** Of your suggestions, I think I like your last suggestion the best, in part because implementation seems like it would be pretty straightforward, given BeautifulSoup. A couple of issues, though, that we've had with similar stuff in the past, that might show up: * [Part of the Markdown spec](https://daringfireball.net/projects/markdown/syntax#html) is that Markdown isn't parsed inside of block-level HTML tags, so wrapping in a `div` is probably not super nice. There's an extension to Python-Markdown that would enable that, I think, but we may still have the next issue: * Having a blank line inside of an HTML tag can confuse the combination of Markdown and BeautifulSoup (I think because Markdown adds `</p>` or something like that, which then causes BeautifulSoup to freak out and close the enclosing HTML tag). But it might just be worth figuring out how to appease those libraries, rather than doing something else to avoid the issue. Definitely happy to continue the conversation; I think this would be a great thing to have, and it's worth making sure we choose a sensible syntax.
hz added the
enhancement
label 1 month ago
edemaine commented 1 month ago
Poster

Right, I’ve mainly seen the <python>if...print '''...'''</python> solution. I’m essentially hoping for a cleaner version of that notation.

I didn’t realize/forgot that there are custom tags (or maybe fairer to say “custom raw text processing” :smiley:). Perhaps an <if> or <#if> tag, and/or a show-if/hide-if attribute, can actually be implemented at the user level then? (Still might be nice/interesting to do at the Catsoop level, of course.)

I haven’t looked at what you’re using to parse Markdown, but the more modern spec is actually CommonMark, which says that a blank line is enough to stop the HTML passthrough. Assuming this is the case with your parser, we could do things like this:

<div cs-show-if="@{role == 'Instructor'}">

*Greetings*, Professor!

</div>

You’re right that you get a <p> tag out of that, though it shouldn’t be bad in this setting. (There are many examples in the CommonMark spec link.) The above example (without the attribute) produces this output via markdown-it (even without the second blank line):

<div>
<p><em>Greetings</em>, Professor!</p>
</div>
Right, I've mainly seen the `<python>if...print '''...'''</python>` solution. I'm essentially hoping for a cleaner version of that notation. I didn't realize/forgot that there are custom tags (or maybe fairer to say "custom raw text processing" :smiley:). Perhaps an `<if>` or `<#if>` tag, and/or a `show-if`/`hide-if` attribute, can actually be implemented at the user level then? (Still might be nice/interesting to do at the Catsoop level, of course.) I haven't looked at what you're using to parse Markdown, but the more modern spec is actually [CommonMark](https://commonmark.org/), which [says](https://spec.commonmark.org/0.29/#html-blocks) that a blank line is enough to stop the HTML passthrough. Assuming this is the case with your parser, we could do things like this: ```markdown <div cs-show-if="@{role == 'Instructor'}"> *Greetings*, Professor! </div> ``` You're right that you get a `<p>` tag out of that, though it shouldn't be bad in this setting. (There are many examples in the [CommonMark spec link](https://spec.commonmark.org/0.29/#html-blocks).) The above example (without the attribute) produces this output via `markdown-it` (even without the second blank line): ```html <div> <p><em>Greetings</em>, Professor!</p> </div> ```
edemaine commented 1 month ago
Poster

Actually, looking how <python> tags are handled (via a regex), we could do the same with an <if> tag, and we wouldn’t have to worry about blank lines and such. It would actually be harder to do it with an attribute, because then you’d actually have to emit a tag, whereas with <if> we could just spit out the contents before Markdown processing happens.

Not sure of the details of <if> notation… Some options off the top of my head:

  • <if>condition<then>...</then><else>...</else></if> -- maybe <then> and <else> are self-closing, so it’s enough to say <if>condition<then>...<else>...</if>.
  • <if condition>...<else>...</if> -- not very HTML-like, but <if condition="condition">...<else>...</if> seems tedious.
Actually, looking how `<python>` tags are handled (via a regex), we could do the same with an `<if>` tag, and we wouldn't have to worry about blank lines and such. It would actually be harder to do it with an attribute, because then you'd actually have to emit a tag, whereas with `<if>` we could just spit out the contents before Markdown processing happens. Not sure of the details of `<if>` notation... Some options off the top of my head: * `<if>condition<then>...</then><else>...</else></if>` -- maybe `<then>` and `<else>` are self-closing, so it's enough to say `<if>condition<then>...<else>...</if>`. * `<if condition>...<else>...</if>` -- not very HTML-like, but `<if condition="condition">...<else>...</if>` seems tedious.
hz commented 1 month ago
Owner

I’m essentially hoping for a cleaner version of that notation.

Agree 100%.

…but the more modern spec is actually CommonMark

I’ll look into that. Maybe div with an attribute is feasible after all…

It would actually be harder to do it with an attribute…

That might be true, particularly if it requires effort to make Markdown/CommonMark do what an author wants it to do by putting blank lines in just the right places…

That said, though, handling it elsewhere does have its own downsides; in particular, it requires doing more parsing of our own (and likely hacky parsing at that).

…we could do the same with an <if> tag…

True. As another alternative (in the same vein as <python> and @{...}), we could do something like the following. Not sure if it’s prettier or uglier than an HTML-like option…

!{if ARBITRARY_EXPRESSION}
...
!{elif ANOTHER_EXPRESSION}
...
!{else}
...
!{endif}

or maybe @{{if ...}} or something like that…

> I'm essentially hoping for a cleaner version of that notation. Agree 100%. > ...but the more modern spec is actually CommonMark I'll look into that. Maybe `div` with an attribute is feasible after all... > It would actually be harder to do it with an attribute... That _might_ be true, particularly if it requires effort to make Markdown/CommonMark do what an author wants it to do by putting blank lines in _just_ the right places... That said, though, handling it elsewhere does have its own downsides; in particular, it requires doing more parsing of our own (and likely hacky parsing at that). > ...we could do the same with an `<if>` tag... True. As another alternative (in the same vein as `<python>` and `@{...}`), we could do something like the following. Not sure if it's prettier or uglier than an HTML-like option... ```nohighlight !{if ARBITRARY_EXPRESSION} ... !{elif ANOTHER_EXPRESSION} ... !{else} ... !{endif} ``` or maybe `@{{if ...}}` or something like that...
Sign in to join this conversation.
No Milestone
No Assignees
2 Participants
Due Date

No due date set.

Dependencies

This issue currently doesn't have any dependencies.

Loading…
Cancel
Save
There is no content yet.