Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I am working in a branch #1

Closed
gvanrossum opened this issue May 1, 2022 · 7 comments
Closed

I am working in a branch #1

gvanrossum opened this issue May 1, 2022 · 7 comments

Comments

@gvanrossum
Copy link
Collaborator

gvanrossum commented May 1, 2022

https://github.com/gvanrossum/cpython/tree/tag-strings

(Or in draft PR form: python/cpython#103766)

@gvanrossum
Copy link
Collaborator Author

Progress! I now have something working: foo "bar" is translated into foo("bar"). Anything more complicated reports a SyntaxError. Example session:

>>> repr "foo"
"'foo'"
>>> repr "foo" "bar"
"'foobar'"
>>> repr b"foo"
  File "<stdin>", line 1
SyntaxError: More complicated tag-string not yet supported
>>> repr "foo{bar}"
"'foo{bar}'"
>>> 

Note that the final example is incorrect -- it should call repr("foo", bar). Getting into the f-string interpolations will require a lot of work, and we need to define the Thunk type and lambdafy the interpolations.

@gvanrossum
Copy link
Collaborator Author

gvanrossum commented May 3, 2022

More progress! (I am using #5 for now, and the 'x + 1' is calculated using the unparse functionality.)

>>> def tag(*args):
...   return args
... 
>>> tag"foo {x} bar"
('foo ', (<function <lambda> at 0x10f8252e0>, 'x', None, None), ' bar')
>>> tag"foo {x + 1!s} bar"
('foo ', (<function <lambda> at 0x10f825390>, 'x + 1', 's', None), ' bar')
>>> tag"foo {x + 1!s:10s} bar"
('foo ', (<function <lambda> at 0x10f8252e0>, 'x + 1', 's', '10s'), ' bar')
>>> x = 42
>>> tag"foo {x + 1!s:10s} bar" [1][0]()
43
>>> 

@gvanrossum
Copy link
Collaborator Author

gvanrossum commented May 3, 2022

I have a working demo that implements an html tag. The tag parses a string containing HTML and returns an HTMLNode instance. That's a simple dataclass that overrides __str__ to render the HTML as a string. The parser is built on top of the stdlib's html.parser.HTMLParser.

>>> a = html"<div>Hello world</div>"
>>> print(a)
<div>Hello world</div>

You can interpolate variables or expressions; and there isn't a sys.getframe call in sight:

>>> user = "Jim"
>>> print(html"<div>Hello {user}</div>")
<div>Hello Jim</div>

Special characters in interpolations are properly escaped:

>>> user = "Bobby<table>s</table>"
>>> print(html"<div>Hello {user}</div>")
<div>Hello Bobby&lt;table&gt;s&lt;/table&gt;</div>

You can also interpolate other HTMLNode instances; these aren't escaped:

>>> user = "Jim"
>>> greeting = html"<div>Hello {user}</div>"
>>> print(html"<body>{greeting}</body>")
<body><div>Hello Jim</div></body>

If you somehow wanted to escape an HTMLNode instance, you could use !s in the interpolation:

>>> print(html"<div>Greeting text: {greeting!s}</div>")
<div>Greeting text: &lt;div&gt;Hello Jim&lt;/div&gt;</div>

You can also interpolate attribute values:

>>> cls = "yo.yoyo"
>>> print(html"<div class={cls}>tata</div>")
<div class="yo.yoyo">tata</div>

Although best practice here is to put quotes in the HTML:

>>> cls = "yo\" other=yoyo"
>>> print(html"<div class={cls}></div>"). # THIS IS WRONG!
<div class="yo&quot;" other="yoyo"></div>
>>> print(html'<div class="{cls}"></div>'). # OK
<div class="yo&quot; other=yoyo"></div>

NEW! You can also interpolate lists of HTMLNodes, e.g.

>>> items = [html"<ul>item {i}</ul>" for i in range(3)]
>>> print(html"<li>{items}</li>")
<li><ul>item 0</ul><ul>item 1</ul><ul>item 2</ul></li>

Here's the text of the demo: https://gist.github.com/gvanrossum/a465d31d9402bae2c79e89b2f344c10c

@jimbaker
Copy link
Owner

jimbaker commented May 4, 2022

I tried the demo, and it works! In particular, as expected, by using lexical scope, this solves the scoping issue that Paul Everitt mentions in jviide/htm.py#11 (which comes from the need to use dynamic scoping with sys._getframe, via inspect.stack). So putting the code in demo in a htmltag file:

from htmltag import html

def Todo(prefix, label):
    return html'<li>{prefix}{label}</li>'

def TodoList(prefix, todos):
    return html'<ul>{[Todo(prefix, label) for label in todos]}</ul>'

b = html"""
<html>
    <body attr=blah" yo={1}>
        {[html"<div class=c{i}>haha{i}</div> " for i in range(3)]}
        {TodoList('High: ', ['Get milk', 'Change tires'])}
    </body>
</html>
"""

print(b)

which results in

<html>
    <body attr="blah&quot;" yo="1">
        <div class="c0">haha0</div><div class="c1">haha1</div><div class="c2">haha2</div>
        <ul><li>High: Get milk</li><li>High: Change tires</li></ul>
    </body>
</html>

@pauleveritt
Copy link
Collaborator

I'll need to give that a try and compare the performance change.

@gvanrossum
Copy link
Collaborator Author

Another thing to compare is pyxl https://github.com/awable/pyxl .

@gvanrossum
Copy link
Collaborator Author

Closing in favor of #22.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants