Yawrap Documentation¶
Yawrap is a powerful, lightweight, pythonic pseudo-static HTML builder that works with:
python2.7
,python3.4
-python.3.6
pypy
.
The name comes from something like Yet Another Wrapper (of HTML code).
Repo: | https://github.com/kamichal/yarap |
OldRepo: | https://bitbucket.org/gandowin/yarap (goes obsolete) |
Docs: | http://yawrap.readthedocs.io |
Pypi: | https://pypi.python.org/pypi/yawrap |
Author: | Michał Kaczmarczyk from Poland |
Email: | michal.skaczmarczy.k at gmail.com |
Features¶
Very nice syntax
No more headache caused by closing and indentation of HTML elements! Just write python code.
Yawrap reflects python scopes in HTML perfectly - with no mistakes and indents it natively for free.
- CSS & JS support
Handle it how you like. JS and CSS can be sourced either:
- from local file
- from url
- from python string
And it can be placed either:
- as internal content
- as external file
- as linked resource.
From single “All in one” HTML file to multi-page documents sharing CSS&JS resources. Yawrap takes care for handling them properly.
- SVG support
Don’t care about defining SVG structure, just write its main contents. Yawrap will take care about the whole rest. Also typical SVG attributes which are problematic from python keyword-arguments point of view have it’s convenience feature.
- Linking local files
You can reference local files by passing its absolute path on python side and it will appear under links relative to the current document. And you don’t have to calculate the paths.
- Defining page style and scripts on python class level
Page styles can be defined by deriving Yawrap classes. This makes possibility to get the styles shared / inherited / override in well known pythonic way.
- Multi-page structure
Define multiple pages (even in complex directory structure) and don’t care about the paths. Not existing directories will be automatically created, you just define the path of target file.
- Automatic navigation
That’s ultra-nice. Probably the cutest yawrap’s feature. Create multiple pages and see how yawrap joins them in navigation panel created for each generated page. Everything happens behind the curtains.
The only one thing you need to do is to care for the navigation’s
CSS
style (if you don’t like the default navigation style provided by Yawrap).
- Bookmarking
Create intra-page bookmarks with just one command during document definition and see how they are inserted in correct subsection of the page navigation.
Usage Examples¶
Because a good example is worth more than thousand words.
Basic Usage¶
Passing target file location to Yawrap constructor is mandatory, although the file
will be not generated until Yawrap.render()
is called. Title is optional.
from yawrap import Yawrap
def hello_yawrap():
doc = Yawrap("not/revelant/path.html", "The super title")
with doc.tag("div", id="content", klass="main"):
with doc.tag('h2'):
doc.text('Hello yawrap!')
with doc.tag('p'):
doc.text('Could it be simpler?')
doc.comment("that's it")
result = doc.getvalue()
assert result == """\
<!doctype html><html lang='en-US'>
<head>
<meta charset='UTF-8' />
<title>The super title</title>
</head>
<body>
<div class='main'>
<h2>Hello yawrap!</h2>
<p>Could it be simpler?</p>
</div>
<!-- that's it -->
</body>
</html>"""
Will create a page that looks like this:
Basic JS usage¶
It’s an adaptation of following example:
https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_fadetoggle
from yawrap import Yawrap, ExternalJs, EmbedCss, EmbedJs, BODY_END
class MyPageTemplate(Yawrap):
resources = [
ExternalJs("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"),
EmbedCss("""\
body {
padding: 12px;
font-family: helvetica, sans-serif;
font-size: 14px;
}
.box {
display: inline-block;
height: 80px;
margin: 8px;
padding: 8px;
width: 80px;
}"""),
EmbedJs("""\
$("button").click(function(){
$("#red-box").fadeToggle();
$("#green-box").fadeToggle("slow");
$("#blue-box").fadeToggle(3000);
});""", placement=BODY_END),
]
def _create_box(self, name, color):
style = "background-color:{};".format(color)
with self.tag('div', id=name, klass="box", style=style):
self.text(name)
def fill_with_content(self):
with self.tag("h2"):
self.text("Demonstrate a simple JavaScript.")
with self.tag('p'):
self.text("fadeToggle() operating with different speed parameters.")
with self.tag('button'):
self.text("Click to fade in/out boxes")
with self.tag("div"):
self._create_box('red-box', "red")
self._create_box('green-box', "#0f0")
self._create_box('blue-box', "#0a1cf0")
def create_page(output_file_path):
doc = MyPageTemplate(output_file_path, "Name of the page")
doc.fill_with_content()
doc.render()
# that's it
Will create a page as below. Note that the placement=BODY_END
in EmbedJs
call caused the script to
be placed at the end. Withouth that argument it would get into head
section.
That looks like this:
Ways to feed it with CSS / JS resources¶
There are several possibilities to place a CSS and JS in html page. Each has some advantages. Yawrap helps handling probably all of them.
from yawrap import Yawrap
class PlainPage(Yawrap):
resources = []
doc = PlainPage("/tmp/test.html")
doc.render()
Will create a page that has no style sheet (and no content) defined. It’s just to show the Tabula rasa case, when the resources list is empty.
OK, but of course you would like to use the styles and scripts.
External resources¶
This is how to reference resources hosted on external server.
from yawrap import ExternalCss, ExternalJs
class PageWithExternalCss(Yawrap):
resources = [
ExternalCss.from_url("https://www.w3schools.com/w3css/4/w3.css"),
ExternalJs.from_url("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"),
]
That gives:
Disadvantage of such approach is of course - dependency on other web resource, …but Yawrap doesn’t care it’s your business.
Auto-Linked resources¶
Yawrap can download the sheet or script given by URL (while page build), save it as a local file and manage its relative path calculation.
Just replace ExternalCss
with LinkCss
and ExternalJs
with LinkJs
like this:
from yawrap import LinkCss, LinkJs
class LinkedCssPage(Yawrap):
resources = [
LinkCss.from_url("https://www.w3schools.com/w3css/4/w3.css"),
LinkJs.from_url("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"),
]
With such a result:
See how these link point to local files in resources
directory?
It’s a default location, next to output html file. Yawrap created it while build.
Let’s say it once again Yawrap downloads and saves the files with each run.
If you allways want to get a fresh copy from these urls, it’s ok. But it’s probably eaiser to download it manually, save it close to your yawrap generator code. While each run, yawrap will source its contents from local files, instead of the web. Rest (i.e. saving to target resources dir) is the same as before.
Such a code will source the linked code from given files:
class LocalSources(Yawrap):
resources = [
LinkCss.from_file("path/to/source_of/w3.css"),
LinkJs.from_file("path/to/source_of/jquery.min.js"),
]
Changing default resources path¶
The default resources
output directory can be changed like this:
class LinkCssInMyStyle(LinkCss):
resource_subdir = "the_super_directory"
# for nested structure, do:
# # resource_subdir = os.path.join("the", "super", "directory")
class LinkJsInMyStyle(LinkJs):
resource_subdir = ""
# empty string will same dir, as parent html
class MyStyleOfLinking(Yawrap):
resources = [
LinkCssInMyStyle.from_url("https://www.w3schools.com/w3css/4/w3.css"),
LinkJsInMyStyle.from_url("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"),
]
This code will generate exactly the same HTML, just paths differ.
Note that JS and CSS will go to separate dirs.
Note
Relative path management makes sense when several pages from different paths share same CSS/JS file.
E.g. when NavedYawrap
is in use. Then the resources
directory path is threated as relative to
the top level of output directory structure (relative to root, the first page).
Defining CSS / JS in python code¶
Sometimes you would like to keep definition of the CSS or JS inside a python module. Especially if you use python for generating it (e.g. calculate CSS colors, margin sizes, etc..)
Then there are
If you are interested with getting single-file page (all-in-one, no linked CSS nor JS), where everything is embedded in single html, then use such an pattern:
Replacing a resource sourcer LinkCss
with EmbedCss
as here:
LinkCss.from_url("https://www.w3schools.com/w3css/4/w3.css") # with
EmbedCss.from_url("https://www.w3schools.com/w3css/4/w3.css")
will cause downloading the w3 style sheet and placing it directly to the /html/head/style
section.
The download happens with each call of Yawrap.render()
.
It also embeds additional style for body defined as a python string.
The most useful in my opinion is to
create own class derived from Yawrap
(or NavedYawrap
or whatever from the family).
Contents¶
CSS Support¶
Yawrap has it’s basic support of cascade style sheets. During creation of html page you can
append styles definition as strings or dictionaries. It will appear in /html/head/style
section
of current yawrap document. Also linking CSS as external file is possible.
Note
The class
keyword, widely used in html/css
will cause python’s syntax error if used as keyword argument,
so you can define it by typing klass
instead. Yattag will convert it to class
automatically.
Internal CSS as str¶
from yawrap import Yawrap, EmbedCss
out_file = '/tmp/css_1.html'
jawrap = Yawrap(out_file)
with jawrap.tag('div', klass='content'):
with jawrap.tag('span'):
jawrap.text('CSS in yawrap.')
jawrap.add(EmbedCss('''
.content { margin: 2px; }
.content:hover {
background-color: #DAF;
}'''))
jawrap.render()
print(open(out_file, 'rt').read())
That outputs:
<!doctype html><html lang='en-US'>
<head>
<meta charset='UTF-8' />
<style>
.content { margin: 2px; }
.content:hover {
background-color: #DAF;
}</style>
</head>
<body><div class='content'><span>CSS in yawrap.</span></div></body>
</html>
The method add()
called with action EmbedCss()
appends CSS definitions to page head section.
It can be called several times, output CSS will be sorted and nicely joined.
The passed CSS can be a string without any formatting. It will be reformatted during page generation.
Internal CSS as python’s dict¶
The argument passed to add_css()
can be a regular python dictionary definins css.
css_dict = {
'.content': {
'color': '#321',
'padding': '1px 16px'
},
'span': {
'border': '1px solid black'
}
}
# reusing jawrap instance from subsection above.
jawrap.add(EmbedCss(css_dict))
jawrap.render()
print(open(out_file, 'rt').read())
Will give:
<!doctype html><html lang='en-US'>
<head>
<meta charset='UTF-8' />
<style>
.content { margin: 2px; }
.content:hover {
background-color: #DAF;
}</style>
<style>
.content {
color: #321;
padding: 1px 16px;
}
span {
border: 1px solid black;
}
</style>
</head>
<body><div class='content'><span>CSS in yawrap.</span></div></body>
</html>
Note the previous .content
selector’s definition is overwritten with new one.
External CSS from local file¶
It’s also possible to link style sheet from local file. It’s source can be placed anywhere as long as
it’s accessible for build process. Yawrap will copy it and place in default resources
directory, next to
target file (or next to root document):
from yawrap import Yawrap, LinkCss
out_file = '/tmp/css_2.html'
jawrap = Yawrap(out_file)
jawrap.text('CSS from local file.')
jawrap.add(LinkCss.from_file('/tmp/files/my.css'))
jawrap.render()
External CSS from web¶
Using global CSS from some resources can be obtained by calling add()
with ExternalCss
object.
from yawrap import Yawrap, ExternalCss
out_file = '/tmp/css_3.html'
jawrap = Yawrap(out_file)
jawrap.text('CSS from web.')
jawrap.add(ExternalCss("https://www.w3schools.com/w3css/4/w3.css"))
html = jawrap.getvalue()
print(html)
<!doctype html><html lang='en-US'>
<head>
<meta charset='UTF-8' />
<link type='text/css' href='https://www.w3schools.com/w3css/4/w3.css' rel='stylesheet' />
</head>
<body>CSS from web.</body>
</html>
CSS defined on class level¶
You can derive own class from Yawrap
or Navrap
class and define its CSS that will be inherited
in its subclasses. You have to define css class attribute either as a string or a dictionary.
from yawrap import Yawrap, EmbedCss
out_file = '/tmp/css_4.html'
class MyStyledPage(Yawrap):
resources = [EmbedCss('''\
body {
margin: 0px;
padding: 13px 14px;
}
.content {
color: #BAC;
margin: 2px;
}''')
]
myStyled = MyStyledPage(out_file)
with myStyled.tag('div', klass='content'):
myStyled.text('Deriving CSS.')
myStyled.render()
print(open(out_file, 'rt').read())
Should give:
<!doctype html><html lang='en-US'>
<head>
<meta charset='UTF-8' />
<style>
body {
margin: 0px;
padding: 13px 14px;
}
.content {
color: #BAC;
margin: 2px;
}
</style>
</head>
<body><div class='content'>Deriving CSS.</div></body>
</html>
Adding CSS is still possible, but to instance of the derived class (to myStyled
above), not
to the class definition (here MyStyledPage
), so the appended CSS will not be inherited.
JavaScript support¶
Yattag does not modify nor even analyze added js
sources. You provide them as strings and these are rendered in
/html/head/script
section. Each addition places the content in separate <script>
section.
Of course there is also possibility to link external js
source file.
Internal JS¶
Appending js to Yawrap
or Navrap
instances can be done appending string-code to Class
Note
Yawrap has a class-level atribute being a list named js
. It is supposed to contain JavaScript code as strings.
Similary as with CSS, you can derive from Yawrap
class and also define the class-level JS that will be
inherited by its subclasses unless you override it.
Using jQuery¶
It’s an adaptation of following example: https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_fadetoggle
from yawrap import Yawrap, ExternalJs, EmbedCss, EmbedJs, BODY_END
class MyPageTemplate(Yawrap):
resources = [
ExternalJs("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"),
EmbedCss("""\
body {
padding: 12px;
font-family: helvetica, sans-serif;
font-size: 14px;
}
.box {
display: inline-block;
height: 80px;
margin: 8px;
padding: 8px;
width: 80px;
}"""),
EmbedJs("""\
$("button").click(function(){
$("#red-box").fadeToggle();
$("#green-box").fadeToggle("slow");
$("#blue-box").fadeToggle(3000);
});""", placement=BODY_END),
]
def _create_box(self, name, color):
style = "background-color:{};".format(color)
with self.tag('div', id=name, klass="box", style=style):
self.text(name)
def fill_with_content(self):
with self.tag("h2"):
self.text("Demonstrate a simple JavaScript.")
with self.tag('p'):
self.text("fadeToggle() operating with different speed parameters.")
with self.tag('button'):
self.text("Click to fade in/out boxes")
with self.tag("div"):
self._create_box('red-box', "red")
self._create_box('green-box', "#0f0")
self._create_box('blue-box', "#0a1cf0")
def create_page(output_file_path):
doc = MyPageTemplate(output_file_path, "Name of the page")
doc.fill_with_content()
doc.render()
# that's it
Will create a page as below. Note that the placement=BODY_END
in EmbedJs
call caused the script to
be placed at the end. Withouth that argument it would get into head
section.
That looks like this:
Sharing scripts and styles across multiple pages¶
Similar effect as above but with reusable java scripts and CSS can be obtained by defining them as class level attributes like this:
from yawrap import LinkCss, LinkJs
class MyPage(MyPageTemplate):
resources = [
ExternalJs("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"),
LinkCss("""\
body {
padding: 12px;
font-family: helvetica, sans-serif;
font-size: 14px;
}
.box {
display: inline-block;
height: 80px;
margin: 8px;
padding: 8px;
width: 80px;
}""", file_name="common_linked.css"),
LinkJs("""\
$("button").click(function(){
$("#red-box").fadeToggle();
$("#green-box").fadeToggle("slow");
$("#blue-box").fadeToggle(3000);
});
""", placement=BODY_END, file_name="common_linked.js"),
]
# uses methods inherited from MyPageTemplate defined above
def create_page_with_linked_resources(output_file_path):
doc = MyPage(output_file_path)
doc.fill_with_content()
doc.render()
Which produces the page:
..and the script resources/common_linked.js
:
..and the style resources/common_linked.css
:
Examples¶
This is a tiny example. Such a code is sufficient to generate an html page:
>>> from yawrap import Yawrap
>>> jawrap = Yawrap('/tmp/example_1.html', 'the title')
>>> with jawrap.tag('div'):
... with jawrap.tag('p'):
... jawrap.text('Nothing much here.')
After executing that - calling render() function will store the page in a target file specified in Yawrap constructor:
>>> jawrap.render()
>>> print(open('/tmp/example_1.html', 'rt').read())
<!doctype html><html lang='en-US'>
<head>
<meta charset='UTF-8' />
<title>the title</title>
</head>
<body><div><p>Nothing much here.</p></div></body>
</html>
yattag’s heritage¶
Till Yawrap
version 0.3.2 - its core was yattag.
It used it as a library. But it’s been decided to reimplement the XML-tag engine in own way.
Yawrap requred more features and yattag was not able to provide it.
Since version 0.4. Yawrap has no external dependencies at all.
Note
Yawrap is backward compatible with yattag. Vast functionality of yattag.SimpleDoc
is supported.
You can use yawrap in the same way as yattag or even just replace import statement in your existing code.
Unfortunatelly I cannot guarantee 100% match, but I’m pretty sure it will work.