-
The modern way
Pip it
pip install handcrank
Old-fashioned
Easy install it
easy_install handcrank
Or directly from GitHub
Latest and greatest code is available at
Pip it
pip install handcrank
Easy install it
easy_install handcrank
Latest and greatest code is available at
This is a no-frills, get it done, kind of static content generator. It generates a one-page site. It has simple templating yet uses HTML5 and CSS3 and can be extended easily if you are willing to write some Python code.
You need to install handcrank first, use pip install handcrank if you haven't done so.
Handcrank can create everything you need to get you started.
handcrank --sitedir ~/mysite startsite
This creates a new directory in your home directory called mysite.
You'll notice some things if you look in this directory
- config.cfg
- This configure your site, we'll cover this in detail in the API reference.
- docs
- These are where your reStructuredText files go. We'll go over this in API reference.
- template
- Is the HTML, CSS, and Javascript used to make up site. Again, check the API reference.
Now we'll generate the site
handcrank --sitedir ~/mysite generate
The output will look something like this
__ .--------. `. ______| |\ | / ```` | | | | | ,---| |/ |_____| | `--------' | ```` | |~~~~~~~/ ___.' Handcrank version 0.1 -_-_ erkkk erkkk Source rST files .......... (1 files) /Users/johncleese/mysite/docs Used the template ......... /Users/johncleese/mysite/template Created ................... /Users/johncleese/mysite/output
A good static site generator let's you view the site without a server.
If you are on a Mac, you can type this command and view what you've created.
open ~/mysite/output/index.html
Or File, Open in the browser of your choice will get you there as well.
You should see something like this
Create ~/mysite/docs/what_next.rst.
Remember that Handcrank uses reStructuredText. Check the Quick Reference if you want to go all rogue and put your own content in this new file.
Copy paste this into it
============================================ Adding a new document with Handcrank is easy ============================================ Handcrank automatically finds it, and because it's an *.rst* extension, puts it into your generated site.
Save the file and exit your text editor.
Re-generate the site with the command you used earlier:
handcrank --sitedir ~/mysite generate
It should now tell you that it generated 2 files. Go back to your browser and refresh. You should see this:
It's annoying to keep going back and forth between the handbrake command, your editor, and the browser. We can take one of these annoyances away.
To keep Handbrake up as a daemon, run the following command.
handcrank --keepgoing --sitedir ~/mysite generate
You'll see this as the last line of the output, and notice how the command did not immediately exit.
Running in daemon mode, hit <CTRL-C> to exit
You can edit, add new docs, or change something in the template directory and Handbrake will detect this and automatically regenerate the site for you.
You can continue to add pages to the site or you can dig a little deeper into extending what you already have.
View the API reference to find out how.
Here are some of the things that you can accomplish:
API reference table of content
There are three main directories that you need to worry about when working with a Handcrank site.
The directory locations and names can be changed using a configuration file.
This directory contains only files with an rst extension. You should organize these files into a flat hierarchy, meaning there should be no sub-directories under this.
Corresponds to [source] in the config file
Here is an example directory listing
total 64 drwxr-xr-x 7 robmadole staff 238 Sep 21 22:05 . drwxr-xr-x 8 robmadole staff 272 Sep 21 22:05 .. -rw-r--r-- 1 robmadole staff 322 Sep 14 22:23 installation.rst -rw-r--r-- 1 robmadole staff 6628 Sep 21 22:05 reference.rst -rw-r--r-- 1 robmadole staff 6437 Sep 11 16:36 sample_styles.rst -rw-r--r-- 1 robmadole staff 211 Sep 8 12:16 tech_used.rst -rw-r--r-- 1 robmadole staff 4178 Sep 21 12:01 welcome.rst
Each rST file should begin with a root title like this
========================= Welcome Holy Grail Seeker =========================
This title will become the identifier for this document.
This directory should contain one index.html file that represents your one-page site. See the templating section for information on how this Jinja2 template should be formatted and what data is available when the site is generated.
Corresponds to [template] in the config file
This directory contains your generated site. If your template has a index.html, your output directory will also. Any files in the template directory will be first copied to output before the site is generated.
Corresponds to [output] in the config file
Each site is required to have a configuration file named config.cfg.
Here is a basic example:
[source]
directory = ./docs
delegate = Delegate
[template]
directory = ./template
name = index.html
[output]
directory = ./output
name = index.html
The paths in the config file are relative to the directory the config file is in. This means that for this example, a docs directory should be beside the config.cfg file. A directory listing would look like this
-rw-r--r-- 1 robmadole staff 174 Sep 11 16:34 config.cfg -rw-r--r-- 1 robmadole staff 468 Sep 15 13:08 delegate.py drwxr-xr-x 7 robmadole staff 238 Sep 21 22:55 docs drwxr-xr-x 7 robmadole staff 238 Sep 21 22:55 template drwxr-xr-x 6 robmadole staff 204 Sep 20 21:58 output
Changing the name for the [output] or [template] sections specifies a different file for the generator to look for or create.
The delegate value in the [source] section is the class name of the object the generator will delegate certain behavior to. See the delegate section for more information. The generator will create an instance of this class when the time is appropriate.
The important thing to note about the delegate is that a delegate.py file or delegate module putting it in Python terms will be imported. If the generator cannot find a delegate module it will fall back to a default that only alphabetizes the source list.
To highlight source code, you can use the code block directive. This is copied from the Sphinx project and follows the same syntax.
To highlight Python code
.. code-block:: python from os.path import dirname # Where are we? here = dirname(__file__)
Produces this
from os.path import dirname
# Where are we?
here = dirname(__file__)
You can highlight other code as well, check the Pygments websites for a complete list.
Handcrank uses a flat directory to store your rST documents. For example if you had the following directory listing in your docs directory.
- index.rst
- the_quest.rst
- the_grail.rst
You can link between the documents with the :doc: role.
I would like to learn more about :doc:`The grail <the_grail.rst`
This produces the following HTML
<a href="the_grail.rst" rel="doc-link">The grail</a>
You'll notice that unless you use Javascript to do something with this, the link is broken, the_grail.rst only exists in the source directory.
The default site created for you when you used handcrank startsite provides the Javascript that expands the appropriate tab when a link is clicked. See the source code for folders.js
Sites are rendered through the OnePageGenerator class. While it would certainly be possible to alter this, or better yet create a subclass, there is another alternative.
Using a Delegate pattern, we can choose certain key points within the machinery to perform special calls to a delegate.
Here is an example
from datetime import datetime
from handcrank.generator import DelegateBase
class Delegate(DelegateBase):
def sort_source_list(self, source_list):
"""
Put installation.rst and welcome.rst first in the list
"""
return self.items_place_first(
self.items_sort_alpha(source_list),
'installation.rst', 'welcome.rst')
def provide_extra_context(self):
"""
Adds a generation time that can be used in the template like:
{{ time_generated }}
"""
return {'time_generated': datetime.now().strftime('%b %d, %Y')}
A delegate alters the behavior of the generator in some specific way.
It's probably been a while since reading an introduction to Python, but recall that dict data type keys are not ordered. Because of this, we have to take special care if utilizing them in these delegate methods. If you find yourself reaching for a dict, consider using an OrderedDict instead.
If you examine a source_list object coming in from the generator you'll notice that the structure is a bit unexpected.
>>> source_list
<handcrank.generator.SourceList instance at 0x1023ed830>
Using a list comprehension, lets look at the first 25 characters of the source.
>>> [(i,j[:25],) for i,j in source_list]
[('installation.rst', 'Installation\n============'), ('reference.rst',
'API Reference\n==========='), ('sample_styles.rst', 'Sample
styling\n=========='), ('tech_used.rst', 'Projects and technology u'),
('welcome.rst', 'Getting started\n=========')]
Notice that iterating through the source list results in a tuple each time the loop moves to the next item. The tuple contains:
('basename', 'source',)
Your delegate methods do not have to return a SourceList object, but they do need to adhere to the way the iterator works. Meaning each pass through the loop needs to provide the tuple.
To save some time, the following helper methods provide these features:
You can see both of these helper methods used in this example:
def sort_source_list(self, source_list):
"""
Put installation.rst and welcome.rst first in the list
"""
return self.items_place_first(
self.items_sort_alpha(source_list),
'installation.rst', 'welcome.rst')
Jinja2 is used for templating. Check out the documentation.
Our main object containing the generated content is the gathering object.
Here is a very simple example index.html Jinja2 template that uses this variable.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Handcrank Site Generator</title>
</head>
<body>
<header>Documentation</header>
{% for title in gathering %}
<article class="folder">
<h1>{{ title }}</h1>
{{ gathering[title]['fragment'] }}
</article>
{% endfor %}
</body>
</html>
Once within the for loop, the following values are available.
The generator uses docutils, specifically the publish_parts function and the html4css1 writer, to generate the content it then renders, so you have access to more than just the information above.
A full list of variables here:
The example above used provide_extra_context to allow a formatted date to be added to the available data when the template renders.
Taking another look at that:
def provide_extra_context(self):
"""
Adds a generation time that can be used in the template like:
{{ time_generated }}
"""
return {'time_generated': datetime.now().strftime('%b %d, %Y')}
A dictionary is returned containing one key, time_generated.
To use this within the template is fairly straight-forward.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Handcrank Site Generator</title>
</head>
<body>
<header>Documentation</header>
{% for title in gathering %}
<article class="folder">
<h1>{{ title }}</h1>
{{ gathering[title]['fragment'] }}
</article>
{% endfor %}
<footer>Generated on {{ time_generated }}</footer>
</body>
</html>
reStructuredText supports a lot of markup. This page tries to display a large combination of all of them. While developing handcrank, this page was used to make sure everything looks awesome.
You can view the quick-start guide for reStructureText here. From there you can get to the rest of the documentation for it.
This is a paragraph with a lot of text in it. Part of the text can have emphasis while other parts of it can have strong emphasis. We can also have interpreted text that has domain-specific context. An inline literal is great for putting code snippets in.
It has multiple paragraphs.
Authors: | Tony J. (Tibs) Ibbs, David Goodger (and sundry other good-natured folks) |
---|---|
Version: | 1.0 of 2001/08/08 |
Dedication: | To my father. |
-a | command-line option "a" |
-b file | options can have arguments and long descriptions |
--long | options can be long also |
--input=file | long options can also have arguments |
/V | DOS/VMS-style options too |
A paragraph containing only two colons indicates that the following indented or quoted text is a literal block.
Whitespace, newlines, blank lines, and all kinds of markup (like *this* or \this) is preserved by literal blocks. The paragraph containing only '::' will be omitted from the result.
The :: may be tacked onto the very end of any paragraph. The :: will be omitted if it is preceded by whitespace. The :: will be converted to a single colon if preceded by text, like this:
It's very convenient to use this form.
Literal blocks end when text returns to the preceding paragraph's indentation. This means that something like this is possible:
We start here and continue here and end here.
Per-line quoting can also be used on unindented literal blocks:
> Useful for quotes from email and > for Haskell literate programming.
class HtmlCodeBlock(Directive):
"""
Borrowed from the Sphinx project
"""
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'linenos': directives.flag,
}
def run(self):
code = u'\n'.join(self.content)
linenos = 'linenos' in self.options
lexer = get_lexer_by_name(self.arguments[0])
formatter = HtmlFormatter(linenos=linenos)
hi_code = highlight(code, lexer, formatter)
raw = nodes.raw('', hi_code, **{'format': 'html', 'source': None})
return [raw]
directives.register_directive('code-block', HtmlCodeBlock)
Block quotes are just:
Indented paragraphs,
and they may nest.
Header 1 | Header 2 | Header 3 |
---|---|---|
body row 1 | column 2 | column 3 |
body row 2 | Cells may span columns. | |
body row 3 | Cells may span rows. |
|
body row 4 |
A transition marker is a horizontal line of 4 or more repeated punctuation characters.
A transition should not begin or end a section or document, nor should two transitions be immediately adjacent.
Footnote references, like [5]. Note that footnotes may get rearranged, e.g., to the bottom of the "page".
[5] | A numerical footnote. Note there's no colon after the ]. |
Autonumbered footnotes are possible, like using [1] and [2].
[1] | This is the first one. |
[2] | This is the second one. |
They may be assigned 'autonumber labels' - for instance, [4] and [3].
[3] | a.k.a. third |
[4] | a.k.a. fourth |
Citation references, like [CIT2002]. Note that citations may get rearranged, e.g., to the bottom of the "page".
[CIT2002] | A citation (as often used in journals). |
Citation labels contain alphanumerics, underlines, hyphens and fullstops. Case is not significant.
Given a citation like [this], one can also refer to it like this.
[this] | here. |
Here are the numerous "shoulders of giants" that I stood upon while cobbling this together. Thanks to all these projects for saving me so much time.
For development and testing: