How to make multi-line pop-up in Sublime Text 3 plugin - python

I am making a plugin for Sublime Text 3. It contacts my server in Java and receives a response in the form of a list of strings. I want a pop-up window to appear when you press a key combination, in which you can view all the line options and copy the desired one. I found an example of how to do this for one line (Github), but I don’t understand how to modify this for several lines (and several “copy” buttons, of course). It should be like:
TEXT1 - Copy
TEXT2 - Copy
TEXT3 - Copy
...
Below is the code of plugin that shows scope name in pop-up:
import sublime
import sublime_plugin
def copy(view, text):
sublime.set_clipboard(text)
view.hide_popup()
sublime.status_message('Scope name copied to clipboard')
class ShowScopeNameCommand(sublime_plugin.TextCommand):
def run(self, edit):
scope = self.view.scope_name(self.view.sel()[-1].b)
html = """
<body id=show-scope>
<style>
p {
margin-top: 0;
}
a {
font-family: system;
font-size: 1.05rem;
}
</style>
<p>%s</p>
Copy
</body>
""" % (scope.replace(' ', '<br>'), scope.rstrip())
self.view.show_popup(html, max_width=512, on_navigate=lambda x: copy(self.view, x))

It's actually quite simple once you know what get's copied when you click on the link that says Copy.
As per the official api reference, we have :-
on_navigate is a callback that should accept a string contents of the href attribute on the link the user clicked.
So whatever is in the href attribute gets copied to clipboard for the show_scope_name command (or to put in more correct terms, the href contents is passed on as an argument to the on_navigate callback). Armed with this information, here is a simple plugin that fetches some todos from Jsonplaceholder (which is a fake REST API for demo purposes), displays it as a list with each one having it's own Copy for you to select what to copy. Instead of Jsonplaceholder, you'll have to send a request to your Java server to get the list of strings and modify the example accordingly.
import json
import sublime
import urllib.parse
import urllib.request
import sublime_plugin
def get_data(num_of_todos):
""" Fetches some todos from the Jsonplaceholder API (for the purposes of getting fake data).
Args:
num_of_todos (int) : The number of todos to be fetched.
Returns:
final_data (list) : The number of todos as a list.
"""
try:
url = "https://jsonplaceholder.typicode.com/todos"
req = urllib.request.Request(url)
req.add_header('User-agent', 'Mozilla/5.0')
with urllib.request.urlopen(req) as response:
fake_data = json.loads(response.read().decode("utf-8"))
final_data = []
for todo in fake_data:
final_data.append(todo["title"])
return final_data[:num_of_todos]
except urllib.error.HTTPError as error:
return json.loads(error.read().decode("utf-8"))
class MultilinePopUpCopyCommand(sublime_plugin.TextCommand):
""" Command for fetching some todos & displaying a Copy link for each one of them,
which upon being pressed copies the specified todo
"""
def run(self, edit):
""" This method is invoked when the command is run.
Args:
edit (sublime.Edit) : The edit object necessary for making buffer modifications
in the current view.
Returns:
None.
"""
# Construct an li tree to be injected later in the ul tag.
li_tree = ""
final_data = get_data(5)
for i in range(len(final_data)):
li_tree += "<li>%s <a href='%s'>Copy</a></li>\n" %(final_data[i], final_data[i])
# The html to be shown.
html = """
<body id=copy-multiline>
<style>
ul {
margin: 0;
}
a {
font-family: system;
font-size: 1.05rem;
}
</style>
<ul>
%s
</ul>
</body>
""" %(li_tree)
self.view.show_popup(html, max_width=512, on_navigate=lambda todo: self.copy_todo(todo))
def copy_todo(self, todo):
""" Copies the todo to the clipboard.
Args:
todo (str) : The selected todo.
Returns:
None.
"""
sublime.set_clipboard(todo)
self.view.hide_popup()
sublime.status_message('Todo copied to clipboard !')
Here is a demo of the plugin (Here I have bound the command to a key binding) :-
Hope this meets your requirements.

Related

Add a custom javascript to the FastAPI Swagger UI docs webpage in Python

I want to load my custom javascript file or code to the FastAPI Swagger UI webpage, to add some dynamic interaction when I create a FastAPI object.
For example, in Swagger UI on docs webpage I would like to
<script src="custom_script.js"></script>
or
<script> alert('worked!') </script>
I tried:
api = FastAPI(docs_url=None)
api.mount("/static", StaticFiles(directory="static"), name="static")
#api.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=api.openapi_url,
title=api.title + " - Swagger UI",
oauth2_redirect_url=api.swagger_ui_oauth2_redirect_url,
swagger_js_url="/static/sample.js",
swagger_css_url="/static/sample.css",
)
but it is not working. Is there a way just to insert my custom javascript code on docs webpage of FastAPI Swagger UI with Python ?
Finally I made it working. This is what I did:
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles
api = FastAPI(docs_url=None)
path_to_static = os.path.join(os.path.dirname(__file__), 'static')
logger.info(f"path_to_static: {path_to_static}")
api.mount("/static", StaticFiles(directory=path_to_static), name="static")
#api.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=api.openapi_url,
title="My API",
oauth2_redirect_url=api.swagger_ui_oauth2_redirect_url,
swagger_js_url="/static/custom_script.js",
# swagger_css_url="/static/swagger-ui.css",
# swagger_favicon_url="/static/favicon-32x32.png",
)
Important notes:
Make sure the static path is correct and all your files are in the static folder, by default the static folder should be in the same folder with the script that created the FastAPI object.
For example:
-parent_folder
Build_FastAPI.py
-static_folder
custom_script.js
custom_css.css
Find the swagger-ui-bundle.js on internet and copy-paste all its content to custom_script.js, then add your custom javascript code at the beginning or at the end of custom_script.js.
For example:
setTimeout(function(){alert('My custom script is working!')}, 5000);
...
.....
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}
...
.....
Save and refresh your browser, you are all way up!
IF SOMEBODY KNOWS A BETTER ANSWER YOUR ARE WELCOME, THE BEST ONE WILL BE ACCEPTED!
If you take a look at the get_swagger_ui_html function that is imported from fastapi.openapi.docs, you will see that the HTML for the docs page is constructed manually via string interpolation/concatenation. It would be trivial to modify this function to include an additional script element, as shown below:
# custom_swagger.py
import json
from typing import Any, Dict, Optional
from fastapi.encoders import jsonable_encoder
from fastapi.openapi.docs import swagger_ui_default_parameters
from starlette.responses import HTMLResponse
def get_swagger_ui_html(
*,
openapi_url: str,
title: str,
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist#4/swagger-ui-bundle.js",
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist#4/swagger-ui.css",
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
oauth2_redirect_url: Optional[str] = None,
init_oauth: Optional[Dict[str, Any]] = None,
swagger_ui_parameters: Optional[Dict[str, Any]] = None,
custom_js_url: Optional[str] = None,
) -> HTMLResponse:
current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
if swagger_ui_parameters:
current_swagger_ui_parameters.update(swagger_ui_parameters)
html = f"""
<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet" href="{swagger_css_url}">
<link rel="shortcut icon" href="{swagger_favicon_url}">
<title>{title}</title>
</head>
<body>
<div id="swagger-ui">
</div>
"""
if custom_js_url:
html += f"""
<script src="{custom_js_url}"></script>
"""
html += f"""
<script src="{swagger_js_url}"></script>
<!-- `SwaggerUIBundle` is now available on the page -->
<script>
const ui = SwaggerUIBundle({{
url: '{openapi_url}',
"""
for key, value in current_swagger_ui_parameters.items():
html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
if oauth2_redirect_url:
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
html += """
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
})"""
if init_oauth:
html += f"""
ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
"""
html += """
</script>
</body>
</html>
"""
return HTMLResponse(html)
A new, optional parameter named custom_js_url is added:
custom_js_url: Optional[str] = None,
If a value is provided for this parameter, a script element is inserted into the DOM directly before the script element for swagger_js_url (this is an arbitrary choice, you can change the location of the custom script element based on your needs).
if custom_js_url:
html += f"""
<script src="{custom_js_url}"></script>
"""
If no value is provided, the HTML produced is the same as the original function.
Remember to update your import statements for get_swagger_ui_html and update your function for the /docs endpoint as shown below:
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles
from custom_swagger import get_swagger_ui_html
api = FastAPI(docs_url=None)
path_to_static = os.path.join(os.path.dirname(__file__), 'static')
logger.info(f"path_to_static: {path_to_static}")
api.mount("/static", StaticFiles(directory=path_to_static), name="static")
#api.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=api.openapi_url,
title="My API",
oauth2_redirect_url=api.swagger_ui_oauth2_redirect_url,
swagger_js_url="/static/swagger-ui-bundle.js",
swagger_css_url="/static/swagger-ui.css",
# swagger_favicon_url="/static/favicon-32x32.png",
custom_js_url="/static/custom_script.js",
)
This is still a pretty hacky solution, but I think it is much cleaner and more maintainable than putting a bunch of custom javascript inside the swagger-ui-bundle.js file.

Python yield clear output [duplicate]

I have a view that generates data and streams it in real time. I can't figure out how to send this data to a variable that I can use in my HTML template. My current solution just outputs the data to a blank page as it arrives, which works, but I want to include it in a larger page with formatting. How do I update, format, and display the data as it is streamed to the page?
import flask
import time, math
app = flask.Flask(__name__)
#app.route('/')
def index():
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return flask.Response(inner(), mimetype='text/html')
app.run(debug=True)
You can stream data in a response, but you can't dynamically update a template the way you describe. The template is rendered once on the server side, then sent to the client.
One solution is to use JavaScript to read the streamed response and output the data on the client side. Use XMLHttpRequest to make a request to the endpoint that will stream the data. Then periodically read from the stream until it's done.
This introduces complexity, but allows updating the page directly and gives complete control over what the output looks like. The following example demonstrates that by displaying both the current value and the log of all values.
This example assumes a very simple message format: a single line of data, followed by a newline. This can be as complex as needed, as long as there's a way to identify each message. For example, each loop could return a JSON object which the client decodes.
from math import sqrt
from time import sleep
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.route("/stream")
def stream():
def generate():
for i in range(500):
yield "{}\n".format(sqrt(i))
sleep(1)
return app.response_class(generate(), mimetype="text/plain")
<p>This is the latest output: <span id="latest"></span></p>
<p>This is all the output:</p>
<ul id="output"></ul>
<script>
var latest = document.getElementById('latest');
var output = document.getElementById('output');
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('stream') }}');
xhr.send();
var position = 0;
function handleNewData() {
// the response text include the entire response so far
// split the messages, then take the messages that haven't been handled yet
// position tracks how many messages have been handled
// messages end with a newline, so split will always show one extra empty message at the end
var messages = xhr.responseText.split('\n');
messages.slice(position, -1).forEach(function(value) {
latest.textContent = value; // update the latest value in place
// build and append a new item to a list to log all output
var item = document.createElement('li');
item.textContent = value;
output.appendChild(item);
});
position = messages.length - 1;
}
var timer;
timer = setInterval(function() {
// check the response for new data
handleNewData();
// stop checking once the response has ended
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
latest.textContent = 'Done';
}
}, 1000);
</script>
An <iframe> can be used to display streamed HTML output, but it has some downsides. The frame is a separate document, which increases resource usage. Since it's only displaying the streamed data, it might not be easy to style it like the rest of the page. It can only append data, so long output will render below the visible scroll area. It can't modify other parts of the page in response to each event.
index.html renders the page with a frame pointed at the stream endpoint. The frame has fairly small default dimensions, so you may want to to style it further. Use render_template_string, which knows to escape variables, to render the HTML for each item (or use render_template with a more complex template file). An initial line can be yielded to load CSS in the frame first.
from flask import render_template_string, stream_with_context
#app.route("/stream")
def stream():
#stream_with_context
def generate():
yield render_template_string('<link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}">')
for i in range(500):
yield render_template_string("<p>{{ i }}: {{ s }}</p>\n", i=i, s=sqrt(i))
sleep(1)
return app.response_class(generate())
<p>This is all the output:</p>
<iframe src="{{ url_for("stream") }}"></iframe>
5 years late, but this actually can be done the way you were initially trying to do it, javascript is totally unnecessary (Edit: the author of the accepted answer added the iframe section after I wrote this). You just have to include embed the output as an <iframe>:
from flask import Flask, render_template, Response
import time, math
app = Flask(__name__)
#app.route('/content')
def content():
"""
Render the content a url different from index
"""
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return Response(inner(), mimetype='text/html')
#app.route('/')
def index():
"""
Render a template at the index. The content will be embedded in this template
"""
return render_template('index.html.jinja')
app.run(debug=True)
Then the 'index.html.jinja' file will include an <iframe> with the content url as the src, which would something like:
<!doctype html>
<head>
<title>Title</title>
</head>
<body>
<div>
<iframe frameborder="0"
onresize="noresize"
style='background: transparent; width: 100%; height:100%;'
src="{{ url_for('content')}}">
</iframe>
</div>
</body>
When rendering user-provided data render_template_string() should be used to render the content to avoid injection attacks. However, I left this out of the example because it adds additional complexity, is outside the scope of the question, isn't relevant to the OP since he isn't streaming user-provided data, and won't be relevant for the vast majority of people seeing this post since streaming user-provided data is a far edge case that few if any people will ever have to do.
Originally I had a similar problem to the one posted here where a model is being trained and the update should be stationary and formatted in Html. The following answer is for future reference or people trying to solve the same problem and need inspiration.
A good solution to achieve this is to use an EventSource in Javascript, as described here. This listener can be started using a context variable, such as from a form or other source. The listener is stopped by sending a stop command. A sleep command is used for visualization without doing any real work in this example. Lastly, Html formatting can be achieved using Javascript DOM-Manipulation.
Flask Application
import flask
import time
app = flask.Flask(__name__)
#app.route('/learn')
def learn():
def update():
yield 'data: Prepare for learning\n\n'
# Preapre model
time.sleep(1.0)
for i in range(1, 101):
# Perform update
time.sleep(0.1)
yield f'data: {i}%\n\n'
yield 'data: close\n\n'
return flask.Response(update(), mimetype='text/event-stream')
#app.route('/', methods=['GET', 'POST'])
def index():
train_model = False
if flask.request.method == 'POST':
if 'train_model' in list(flask.request.form):
train_model = True
return flask.render_template('index.html', train_model=train_model)
app.run(threaded=True)
HTML Template
<form action="/" method="post">
<input name="train_model" type="submit" value="Train Model" />
</form>
<p id="learn_output"></p>
{% if train_model %}
<script>
var target_output = document.getElementById("learn_output");
var learn_update = new EventSource("/learn");
learn_update.onmessage = function (e) {
if (e.data == "close") {
learn_update.close();
} else {
target_output.innerHTML = "Status: " + e.data;
}
};
</script>
{% endif %}

bottle › template with rebase and/or include

Need some advice to use rebase and/or include.
For building a flexible concept with a variable menu system I need to insert a 'menuY.tpl' template into different 'mainX.tpl' pages.
Sounds easy but not only the pages need
thisTemplate = template('mainX', keys)
but also the menus need changing menukeys
thisMenu = template('menuY', menukeys)
How to define the different instructions?
python
#app.route('/doit')
def week():
...some code here to load keys etc ...
thisTemplate = template('mainX', keys)
return thisTemplate
mainX.tpl with
<body>
% insert ('menuY', rv)
<section class="container">
<p>{{param1}}</p>
some html code for the main page
</section>
</body>
menuY.tpl with just html code for the menu code like this
<div id="hambgMenu">
Home - {{titleY}}
{{titleZ}}
</div>
This will not work, at the mainX.tpl line with % insert python says:
NameError: name 'insert' is not defined
Also how are the variables (titleY,titleZ) passed to that 'menuY'? There is no reference for 'rv' with the coding above.
The solution was described here How to pass html directly to template ... very easy, just to add ! to the template reference.
I did some further steps just to have on Python:
#app.route('/doit')
def doit():
page = insertTPL('mainX', keys, 'menuY', menukeys, 'menuTag')
return page
.. with menuTag as declared as follows:
So mainX.tpl becomes
<body>
{{!menuTag}}
<section class="container">
<p>{{param1}}</p>
some html code for the main page
</section>
</body>
The mentioned insertTPL python function has:
def insertTPL(tpl, keys, menu, menukeys, menuTag):
subtpl = template(menu, menukeys)
rv = combineKeys(keys, {menuTag:subtpl}) # combine the menu code with other keys!
return template(tpl, rv)
def combineKeys(rv1, rv2):
try:
keys = {key: value for (key, value) in (rv1.items() + rv2.items())}
except:
keys = rv1
return keys

Python webapp to set all query vars to a js object

I am building a simple web app with Python and web.py. My current challenge is to take all query vars (web.input()) and set a JavaScript object in the HTML template. However, I don't know how to ensure this gets rendered OK instead of a long string with encoding.
Thanks a million!
I have the following code in app.py, template HTML and content HTML:
app.py:
import web
urls = (
'/hello', 'hello',
'/bye/', 'bye'
)
app = web.application(urls, globals(), autoreload=True)
#test = "testwaarde"
test = web.input()
render = web.template.render('templates/', base='layout')
class hello:
def GET(self):
return render.index("Templates demo",test, "Hello", "A long time ago...")
class bye:
def GET(self):
return render.hell_form("Templates demo",test, "Bye", "14", "8", "25", "42", "19")
if __name__ == "__main__":
app.run()
Template:
$def with (page)
<html>
<head>
<title>$page.title</title>
<!-- Digital Data Layer -->
<script>
var test = "{{$page.path}}"
var digitalData = {
'page':{
'path': '$page.path'
}
};
</script>
</head>
<body>
<p>You are visiting page <b>$page.name</b>.</p>
$:page
</body>
</html>
index HTML:
$def with (title, **path,name, content)
$var title:$title
$var name:$name
$var path:$path
<p>$content</p>
You're close:
Move your call to test = web.input() to within your GET() methods, not at the top of the file. That's just a bug. (I know that's just your sample code, but it doesn't work.)
Within index.html, use
$var path = path
The using var path: $path sets the template variable to a string representation of path. You want to set it to the dict. That's one difference between the colon and the equal sign. Second, because it's '=', the right hand side is interpreted as python, so no leading '$'.
Within your template layout.html, change your javascript to something similar to:
var test = "$:page.path";
var digitalData = {
'page': {
'path': $:page.path
}
};
You'll want to escape the data using $: rather than $. That's why you're seeing the encoded values. Also, you don't want to surround the final $:page.path with quotes to set page.path to an object, rather than just a string.

Generating HTML documents in python

In python, what is the most elegant way to generate HTML documents. I currently manually append all of the tags to a giant string, and write that to a file. Is there a more elegant way of doing this?
You can use yattag to do this in an elegant way. FYI I'm the author of the library.
from yattag import Doc
doc, tag, text = Doc().tagtext()
with tag('html'):
with tag('body'):
with tag('p', id = 'main'):
text('some text')
with tag('a', href='/my-url'):
text('some link')
result = doc.getvalue()
It reads like html, with the added benefit that you don't have to close tags.
I would suggest using one of the many template languages available for python, for example the one built into Django (you don't have to use the rest of Django to use its templating engine) - a google query should give you plenty of other alternative template implementations.
I find that learning a template library helps in so many ways - whenever you need to generate an e-mail, HTML page, text file or similar, you just write a template, load it with your template library, then let the template code create the finished product.
Here's some simple code to get you started:
#!/usr/bin/env python
from django.template import Template, Context
from django.conf import settings
settings.configure() # We have to do this to use django templates standalone - see
# http://stackoverflow.com/questions/98135/how-do-i-use-django-templates-without-the-rest-of-django
# Our template. Could just as easily be stored in a separate file
template = """
<html>
<head>
<title>Template {{ title }}</title>
</head>
<body>
Body with {{ mystring }}.
</body>
</html>
"""
t = Template(template)
c = Context({"title": "title from code",
"mystring":"string from code"})
print t.render(c)
It's even simpler if you have templates on disk - check out the render_to_string function for django 1.7 that can load templates from disk from a predefined list of search paths, fill with data from a dictory and render to a string - all in one function call. (removed from django 1.8 on, see Engine.from_string for comparable action)
If you're building HTML documents than I highly suggest using a template system (like jinja2) as others have suggested. If you're in need of some low level generation of html bits (perhaps as an input to one of your templates), then the xml.etree package is a standard python package and might fit the bill nicely.
import sys
from xml.etree import ElementTree as ET
html = ET.Element('html')
body = ET.Element('body')
html.append(body)
div = ET.Element('div', attrib={'class': 'foo'})
body.append(div)
span = ET.Element('span', attrib={'class': 'bar'})
div.append(span)
span.text = "Hello World"
if sys.version_info < (3, 0, 0):
# python 2
ET.ElementTree(html).write(sys.stdout, encoding='utf-8',
method='html')
else:
# python 3
ET.ElementTree(html).write(sys.stdout, encoding='unicode',
method='html')
Prints the following:
<html><body><div class="foo"><span class="bar">Hello World</span></div></body></html>
There is also a nice, modern alternative: airium: https://pypi.org/project/airium/
from airium import Airium
a = Airium()
a('<!DOCTYPE html>')
with a.html(lang="pl"):
with a.head():
a.meta(charset="utf-8")
a.title(_t="Airium example")
with a.body():
with a.h3(id="id23409231", klass='main_header'):
a("Hello World.")
html = str(a) # casting to string extracts the value
print(html)
Prints such a string:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<title>Airium example</title>
</head>
<body>
<h3 id="id23409231" class="main_header">
Hello World.
</h3>
</body>
</html>
The greatest advantage of airium is - it has also a reverse translator, that builds python code out of html string. If you wonder how to implement a given html snippet - the translator gives you the answer right away.
Its repository contains tests with example pages translated automatically with airium in: tests/documents. A good starting point (any existing tutorial) - is this one: tests/documents/w3_architects_example_original.html.py
I would recommend using xml.dom to do this.
http://docs.python.org/library/xml.dom.html
Read this manual page, it has methods for building up XML (and therefore XHTML). It makes all XML tasks far easier, including adding child nodes, document types, adding attributes, creating texts nodes. This should be able to assist you in the vast majority of things you will do to create HTML.
It is also very useful for analysing and processing existing xml documents.
Here is a tutorial that should help you with applying the syntax:
http://www.postneo.com/projects/pyxml/
I am using the code snippet known as throw_out_your_templates for some of my own projects:
https://github.com/tavisrudd/throw_out_your_templates
https://bitbucket.org/tavisrudd/throw-out-your-templates/src
Unfortunately, there is no pypi package for it and it's not part of any distribution as this is only meant as a proof-of-concept. I was also not able to find somebody who took the code and started maintaining it as an actual project. Nevertheless, I think it is worth a try even if it means that you have to ship your own copy of throw_out_your_templates.py with your code.
Similar to the suggestion to use yattag by John Smith Optional, this module does not require you to learn any templating language and also makes sure that you never forget to close tags or quote special characters. Everything stays written in Python. Here is an example of how to use it:
html(lang='en')[
head[title['An example'], meta(charset='UTF-8')],
body(onload='func_with_esc_args(1, "bar")')[
div['Escaped chars: ', '< ', u'>', '&'],
script(type='text/javascript')[
'var lt_not_escaped = (1 < 2);',
'\nvar escaped_cdata_close = "]]>";',
'\nvar unescaped_ampersand = "&";'
],
Comment('''
not escaped "< & >"
escaped: "-->"
'''),
div['some encoded bytes and the equivalent unicode:',
'你好', unicode('你好', 'utf-8')],
safe_unicode('<b>My surrounding b tags are not escaped</b>'),
]
]
I am attempting to make an easier solution called
PyperText
In Which you can do stuff like this:
from PyperText.html import Script
from PyperText.htmlButton import Button
#from PyperText.html{WIDGET} import WIDGET; ex from PyperText.htmlEntry import Entry; variations shared in file
myScript=Script("myfile.html")
myButton=Button()
myButton.setText("This is a button")
myScript.addWidget(myButton)
myScript.createAndWrite()
I wrote a simple wrapper for the lxml module (should work fine with xml as well) that makes tags for HTML/XML -esq documents.
Really, I liked the format of the answer by John Smith but I didn't want to install yet another module to accomplishing something that seemed so simple.
Example first, then the wrapper.
Example
from Tag import Tag
with Tag('html') as html:
with Tag('body'):
with Tag('div'):
with Tag('span', attrib={'id': 'foo'}) as span:
span.text = 'Hello, world!'
with Tag('span', attrib={'id': 'bar'}) as span:
span.text = 'This was an example!'
html.write('test_html.html')
Output:
<html><body><div><span id="foo">Hello, world!</span><span id="bar">This was an example!</span></div></body></html>
Output after some manual formatting:
<html>
<body>
<div>
<span id="foo">Hello, world!</span>
<span id="bar">This was an example!</span>
</div>
</body>
</html>
Wrapper
from dataclasses import dataclass, field
from lxml import etree
PARENT_TAG = None
#dataclass
class Tag:
tag: str
attrib: dict = field(default_factory=dict)
parent: object = None
_text: str = None
#property
def text(self):
return self._text
#text.setter
def text(self, value):
self._text = value
self.element.text = value
def __post_init__(self):
self._make_element()
self._append_to_parent()
def write(self, filename):
etree.ElementTree(self.element).write(filename)
def _make_element(self):
self.element = etree.Element(self.tag, attrib=self.attrib)
def _append_to_parent(self):
if self.parent is not None:
self.parent.element.append(self.element)
def __enter__(self):
global PARENT_TAG
if PARENT_TAG is not None:
self.parent = PARENT_TAG
self._append_to_parent()
PARENT_TAG = self
return self
def __exit__(self, typ, value, traceback):
global PARENT_TAG
if PARENT_TAG is self:
PARENT_TAG = self.parent

Categories