├── .gitignore
├── content
├── about
│ ├── me.jpg
│ └── about.md
├── test
│ ├── hardware.jpg
│ ├── macrotest.md
│ ├── testpost.md
│ └── hilitetest.md
└── blog
│ └── hello-world.md
├── requirements.txt
├── static
├── feed
│ └── .htaccess
├── .htaccess
└── default.css
├── templates
├── 404.html
├── all.html
├── category.html
├── post_list.html
├── index-type.html
├── index.html
├── post_header.html
├── macros.html
├── feed.xml
├── base.html
└── post.html
├── now.py
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python bytecode
2 | __pycache__/
3 |
4 | # Generated HTML
5 | output/
6 |
--------------------------------------------------------------------------------
/content/about/me.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reedbeta/little-py-site/HEAD/content/about/me.jpg
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Jinja2 >= 2.8
2 | Markdown >= 2.6.6
3 | Pygments >= 2.1.3
4 | pyperclip >= 1.5.27
5 |
--------------------------------------------------------------------------------
/content/test/hardware.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reedbeta/little-py-site/HEAD/content/test/hardware.jpg
--------------------------------------------------------------------------------
/static/feed/.htaccess:
--------------------------------------------------------------------------------
1 | # Serve the RSS feed as the index of this directory
2 | AddType application/rss+xml .xml
3 | DirectoryIndex index.xml
4 |
--------------------------------------------------------------------------------
/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% set CanonicalURL = '/404.html' %}
3 | {% set PageTitle = '404 :(' %}
4 |
5 | {% block content %}
6 |
404 :(
7 | Whoops! That URL doesn't seem to be correct.
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/templates/all.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% set CanonicalURL = '/all/' %}
3 |
4 | {% import "post_list.html" as post_list %}
5 |
6 | {% block content %}
7 | All Posts
8 | There are {{ AllPosts|length }} posts in total!
9 | {{ post_list.render(AllPosts) }}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/now.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import os, time;
3 | timestr = time.strftime('Date: %Y-%m-%d %H:%M:%S %z')
4 | print(timestr)
5 |
6 | try:
7 | import pyperclip
8 | pyperclip.copy(timestr)
9 | print('(copied to clipboard)')
10 | except:
11 | print('(no clipboard support available)')
12 |
13 | if os.name == 'nt':
14 | os.system('pause')
15 |
--------------------------------------------------------------------------------
/templates/category.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% set CanonicalURL = '/blog/category/' + CategoryName|slugify %}
3 | {% set PageTitle = CategoryName %}
4 |
5 | {% import "post_list.html" as post_list %}
6 |
7 | {% block content %}
8 | Posts In “{{ CategoryName|e }}”
9 | {% if CategoryPosts|length > 1 %}
10 | There are {{ CategoryPosts|length }} posts about {{ CategoryName|lower|e }}.
11 | {% else %}
12 | There is 1 post about {{ CategoryName|lower|e }}.
13 | {% endif %}
14 | {{ post_list.render(CategoryPosts) }}
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/post_list.html:
--------------------------------------------------------------------------------
1 | {# Render a list of posts grouped by year. Used by all.html and category.html. #}
2 | {% macro render(posts) %}
3 | {% for year, yearPosts in posts|groupby('sortDate.year')|sort(reverse=True) %}
4 | {% set year = year if year != datetime.MAXYEAR else 'Undated' %}
5 | {{ year }}
6 |
7 | {% for p in yearPosts %}
8 | {{ (p.date.strftime('%b') + ' %d' % p.date.day) if p.date else '' }}
9 | {{ p.title }}
10 | {% endfor %}
11 |
12 | {% endfor %}
13 | {% endmacro %}
14 |
--------------------------------------------------------------------------------
/templates/index-type.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% set CanonicalURL = '/%s/' % PostType %}
3 |
4 | {% import "post_header.html" as post_header %}
5 |
6 | {% block content %}
7 |
10 | {% for p in Posts %}
11 |
12 | {{ post_header.render(p) }}
13 | {{ p.summary|absoluteUrls(p.url) }}
14 | {% if p.summary|length < p.html|length %}
15 | Read more...
16 | {% endif %}
17 |
18 | {% if not loop.last %} {% endif %}
19 | {% endfor %}
20 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/static/.htaccess:
--------------------------------------------------------------------------------
1 | AddCharset utf-8 .txt .html .css .js .xml .svg
2 | ErrorDocument 404 /404.html
3 |
4 |
5 |
6 | # Enable compression on text files
7 | AddOutputFilterByType DEFLATE text/plain text/html text/css application/x-javascript text/xml application/rss+xml image/svg+xml
8 |
9 |
10 |
11 |
12 | # Use rewrites to fix up differences between old Wordpress URI structure and new
13 | RewriteEngine on
14 | RewriteBase /
15 | RewriteRule ^blog/?$ / [L,R=301]
16 | RewriteRule ^blog/\d+/\d+/\d+/(.+)$ blog/$1 [L,R=301]
17 | RewriteRule ^blog/feed/?$ /feed/ [L,R=301]
18 | RewriteRule ^\d+/\d+/\d+/(.+)$ blog/$1 [L,R=301]
19 |
20 |
--------------------------------------------------------------------------------
/content/about/about.md:
--------------------------------------------------------------------------------
1 | Title: About Me
2 | Type: page
3 | Slug: about
4 | Files: me.jpg
5 | Hidden: yes
6 | Comments: no
7 |
8 | {:.float-right style="max-width:50%"}
9 | I'm a cat, currently freelancing in Seattle. I'm an expert in lap sitting, laser pointer chasing,
10 | walking on keyboards, and napping. I have an award-winning purr. In addition, I have a fair amount
11 | of familiarity with investigating closets, meowing at doors, and scratching on furniture.
12 |
13 | I've been interested in humans since about 2008 and have worked in various locations in the Seattle
14 | area, sometimes under _extremely_ adverse conditions, such as cohabiting with a dog. You can keep
15 | up to date with what I'm doing by following my current employer [on Twitter](http://twitter.com/@Reedbeta).
16 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% set CanonicalURL = '/' %}
3 |
4 | {% import "post_header.html" as post_header %}
5 |
6 | {% block content %}
7 |
10 | {# Display only the 10 most recent posts, to keep the page size reasonably small #}
11 | {% for p in AllPosts[:10] %}
12 |
13 | {{ post_header.render(p) }}
14 | {{ p.summary|absoluteUrls(p.url) }}
15 | {% if p.summary|length < p.html|length %}
16 | Read more...
17 | {% endif %}
18 |
19 | {% if not loop.last %} {% endif %}
20 | {% endfor %}
21 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/content/test/macrotest.md:
--------------------------------------------------------------------------------
1 | Title: Macro Test
2 | Date: 2016-09-14 20:34:33 -0700
3 | Categories: Test
4 | Files: hardware.jpg
5 |
6 | This is a test of using Jinja macros from inside Markdown.
7 |
8 |
9 |
10 | Image with a caption:
11 |
12 | {{ macros.imgcaption(
13 | src="hardware.jpg",
14 | caption="This image from the 1990 film [_Hardware_](http://www.imdb.com/title/tt0099740/) was tweeted by [Archillect](https://twitter.com/archillect).",
15 | alt="Creepy robot from Hardware (1990)")
16 | }}
17 |
18 | Here is an audio-embedding macro:
19 |
20 | {{ macros.audio("http://www.noiseaddicts.com/samples_1w72b820/2244.mp3") }}
21 |
22 | Here's a video-embedding one:
23 |
24 | {{ macros.video("https://media.giphy.com/media/mIZ9rPeMKefm0/giphy.mp4", style="max-height:20em") }}
25 |
26 | That's all for now!
27 |
--------------------------------------------------------------------------------
/templates/post_header.html:
--------------------------------------------------------------------------------
1 | {# Render the title and description line of a post. Used by index.html, index-type.html, and post.html. #}
2 | {% macro render(p) %}
3 |
4 |
5 | {% if p.date or p.categories or p.comments %}
6 |
7 | {%- if p.date -%}
8 | {{ p.date.strftime('%B') + ' %d, %d' % (p.date.day, p.date.year) }}
9 | {%- endif -%}
10 | {%- if p.categories -%}
11 | {%- for c in p.categories -%}
12 | {%- if p.date and loop.first %} · {% endif %}{{ c|e }} {% if not loop.last %}, {% endif -%}
13 | {%- endfor -%}
14 | {%- endif -%}
15 | {%- if p.comments -%}
16 | {%- if p.date or p.categories %} · {% endif -%}
17 |
18 | {%- endif -%}
19 |
20 | {% endif %}
21 |
22 | {% endmacro %}
23 |
--------------------------------------------------------------------------------
/templates/macros.html:
--------------------------------------------------------------------------------
1 | {# Helper macros for various things that can appear in posts. #}
2 |
3 | {% macro render_attributes(attrs) -%}
4 | {%- for k, v in attrs.items() %}{{k}}="{{v|e}}" {% endfor -%}
5 | {%- endmacro %}
6 |
7 | {% macro audio(src) -%}
8 |
9 | {%- endmacro %}
10 |
11 | {% macro video(src) -%}
12 |
13 | {%- endmacro %}
14 |
15 | {% macro imgcaption(src, caption, float=None) -%}
16 | {#- Copy alt text to title text (if it's not already there) so it appears as a tooltip. -#}
17 | {%- do kwargs.update({'title':kwargs['alt']}) if 'alt' in kwargs and 'title' not in kwargs else None -%}
18 | {%- if not float %}{% endif -%}
19 |
20 |
21 | {{ caption|md }}
22 |
23 | {%- if not float %}
{% endif -%}
24 | {%- endmacro %}
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Nathan Reed
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/templates/feed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ SiteTitle|e }}
5 | {{ SiteDomain }}/
6 | Latest posts on {{ SiteTitle|e }}
7 | en-us
8 | {{ time.strftime('%a, %d %b %Y %H:%M:%S %z') }}
9 |
10 | {# Display only the 10 most recent posts, to keep the feed size reasonably small #}
11 | {% for p in AllPosts[:10] %}
12 | -
13 |
{{ p.title|e }}
14 | {{ SiteDomain + p.url }}
15 | {{ SiteDomain + p.url }}
16 | {{ SiteAuthor }}
17 | {% if p.date %}{{ p.date.strftime('%a, %d %b %Y %H:%M:%S %z') }} {% endif %}
18 | {% if p.comments %}{{ SiteDomain + p.url }}#comments {% endif %}
19 | {% for c in p.categories %}
20 | {{ c|e }}
21 | {% endfor %}
22 | {{ p.html|absoluteUrls(SiteDomain + p.url)|e }}
23 |
24 | {% endfor %}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/content/blog/hello-world.md:
--------------------------------------------------------------------------------
1 | Title: Hello, World!
2 | Categories: Hello
3 | World
4 |
5 | [Lorem ipsum](http://www.lipsum.com/) dolor sit amet, consectetur adipiscing elit. Sed id dolor massa.
6 | Fusce quis quam porta, convallis risus nec, aliquam mi. Donec at ligula sed tellus facilisis elementum
7 | a in diam. Duis vitae _massa_ metus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut
8 | a nisl sit amet sapien interdum venenatis sed ut tortor. In hac habitasse platea dictumst. Maecenas
9 | eu `iaculis mauris`. Praesent porttitor et `mauris` quis tincidunt. Curabitur hendrerit sapien **quis
10 | fermentum elementum**. In pellentesque a felis quis mattis.
11 |
12 |
13 | Aliquam in libero ut orci semper pulvinar. Aenean tincidunt, lectus sit amet commodo pharetra, lacus
14 | magna ullamcorper velit, ac placerat lectus leo ut felis. Sed accumsan fermentum pharetra. Nulla
15 | posuere vitae nunc eu iaculis. Phasellus fermentum porta felis, quis auctor tortor tristique at.
16 | Morbi ultricies ullamcorper massa sit amet ornare. Sed sapien leo, lobortis vel posuere in, suscipit
17 | sed dolor.
18 |
19 | Suspendisse sollicitudin ex molestie quam placerat interdum. In ante lacus, venenatis et pretium quis,
20 | facilisis in felis. Praesent dictum cursus quam, eu vestibulum neque auctor ut. Pellentesque porttitor
21 | arcu orci, ut viverra lorem lacinia a. Ut egestas leo vel arcu interdum, eget porttitor ante semper.
22 | Vivamus in magna nec enim accumsan sodales quis ac arcu. Fusce aliquet arcu et mi tempor, quis
23 | scelerisque arcu efficitur. Mauris luctus eros sed turpis vulputate vestibulum.
24 |
--------------------------------------------------------------------------------
/content/test/testpost.md:
--------------------------------------------------------------------------------
1 | Title: Basic Formatting Test
2 | Date: 2016-08-23 12:00:00 -0700
3 | Categories: Test
4 |
5 | Hello, world! This is a markdown document. It _can_ have **formatting** and [links](http://example.com).
6 | Also, LaTeX equations both inline, like this: $e^{ix} = \cos x + i \sin x$, and display-style,
7 | like this:
8 | $$e^x = \sum_{k=0}^\infty \frac{x^k}{k!}$$
9 | should work (using [MathJax](https://www.mathjax.org/), of course).
10 |
11 | This paragraph is below the "fold", so to speak. Blah blah blabbity blah.
12 | Foo bar baz.
13 |
14 | Typographic tests: "double quotes", 'single quotes'. En dash: January--February. Em dash---it's a
15 | handy punctuation alternative, but don't overuse it. Unicode characters like the minus sign: −
16 | and the multiplication sign: × can be included directly in the source.
17 |
18 | Emphasis_doesn't_apply_within_one_long_word.
19 |
20 | Here are some more math tests with some tricky characters:
21 |
22 | * Less-than and greater-than: $x < y \Longleftrightarrow y > x$
23 | These symbols in the source text get auto-converted to `<` and `>` by Markdown.
24 | * Underscores are ok in simple cases: $a_1, a_2, \ldots a_n$.
25 |
26 | When the underscore is followed by a character like `\` or `{`...
27 | $$
28 | Y^m_\ell(\theta, \phi) \equiv
29 | (-1)^m \sqrt{\frac{(2\ell+1)}{4\pi} \frac{(\ell-m)!}{(\ell+m)!}} \,
30 | P^m_\ell(\cos\theta) \, e^{im\phi}
31 | \\
32 | L_{\text{reflected}}(\omega) = L_{\text{emitted}} +
33 | \int_\Omega L_{\text{incident}}(\omega') \, f_{\text{BRDF}}(\omega, \omega') \,
34 | (n \cdot \omega') \, d\omega'
35 | $$
36 | ...it should be handled correctly by the python-markdown-mathjax extension.
37 | This also works in inline equations: $f(\theta, \phi) = \sum_{\ell,m} a_{\ell,m} Y_\ell^m(\theta, \phi)$
38 |
39 | `Math code doesn't get interpreted inside backticks: $\|\cdot\|_1 \leq \|\cdot\|_2 \leq \|\cdot\|_\infty$`
40 |
--------------------------------------------------------------------------------
/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {% if PageTitle %}{{ PageTitle|e }} – {% endif %}{{ SiteTitle|e }}
17 |
18 |
19 |
54 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/templates/post.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% set CanonicalURL = Post.url %}
3 | {% set PageTitle = Post.title|striptags %}
4 |
5 | {% import "post_header.html" as post_header %}
6 |
7 | {% macro pagination_links() %}
8 | {% if PrevPost or NextPost %}
9 |
16 | {% endif %}
17 | {% endmacro %}
18 |
19 | {% block content %}
20 | {{ pagination_links() }}
21 |
22 | {{ post_header.render(Post) }}
23 | {{ Post.html }}
24 | {% if Post.comments or PrevPost or NextPost %}
25 |
26 | {% if Post.comments %}
27 |
28 |
29 |
30 |
31 |
32 | {% endif %}
33 | {{ pagination_links() }}
34 | {% if Post.comments %}
35 |
36 |
37 |
38 |
51 | Please enable JavaScript to view the comments powered by Disqus.
52 | {% endif %}
53 |
54 | {% endif %}
55 |
56 | {% if Post.comments %}
57 |
58 |
59 |
60 |
66 |
67 | {% endif %}
68 | {% endblock %}
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # little-py-site
2 |
3 | Little static site builder, made in Python 3.5 for easy customizability. You write posts in
4 | [Markdown](https://daringfireball.net/projects/markdown/) and the script renders them, using
5 | [Jinja templates](http://jinja.pocoo.org/) to produce the HTML output. [Disqus](https://disqus.com/)
6 | is integrated for comments.
7 |
8 | This is a fairly raw project without a lot of polish. I wrote it for [my personal site](http://reedbeta.com),
9 | and you're welcome to use it as a starting point for yours, but you'll need to be comfortable with
10 | editing Python to customize the script for your needs, and with editing HTML/CSS to customize the theme.
11 |
12 | ## Features
13 |
14 | * Basic responsive theme.
15 | * Syntax highlighting via [Pygments](http://pygments.org/).
16 | * [MathJax](https://www.mathjax.org/) built-in. Math code gets passed through Markdown,
17 | without needing to be escaped.
18 | * RSS feed generation.
19 | * Apache `.htaccess` file included to create HTTP 301 redirects, for compatibility with typical
20 | Wordpress URI structure. Useful for not breaking links if you're migrating from Wordpress.
21 | * Unicode-safe; all input and output text files are assumed to be UTF-8.
22 |
23 | ## Setup
24 |
25 | Requires Python 3.5 as well as some packages. You can install the packages with:
26 |
27 | sudo pip install -r requirements.txt
28 |
29 | Then open up `build_the_site.py` and edit the site domain, title, and Disqus code in the
30 | configuration section near the top. You'll also want to open up `templates/base.html` and edit the
31 | site author, description, and keywords in the meta tags near the top.
32 |
33 | For previewing your site locally, you'll need a web server. [XAMPP](https://www.apachefriends.org/index.html)
34 | is the easiest way I've found to get a local Apache instance up and running. (It comes with a bunch
35 | of extra stuff like MySQL and PHP, but at least you can turn most of that off in the installer.)
36 |
37 | ## Usage
38 |
39 | Run `build_the_site.py` to render everything to the `output/` directory. By default it will run in
40 | "dev" mode, which can have a different domain, title, etc. to the production site. Use command-line
41 | option `--prod` to build the production site, and `--clean` to destroy the output directory first
42 | and recreate it from scratch.
43 |
44 | ## Directory Structure
45 |
46 | * `content/` – All your posts go in here, as `.md` (Markdown) files. Each `.md` file has some
47 | metadata at the top to set its title, date, and how it gets rendered. See below for more.
48 |
49 | The script searches this directory recursively, so you can organize things in subdirectories as you like.
50 | The first subdirectory under `content/` is used to specify the "type" of the post (analogous to Hugo
51 | "sections"). So, for instance, anything under `content/blog/` will be of type `blog`. The type can
52 | be overridden through a post's metadata, too.
53 |
54 | You can also put other files, such as images, under the `content/` tree. You can reference such
55 | files in a post's metadata and they'll get copied to the output alongside the post.
56 |
57 | * `output/` – The output directory, where the generated HTML will be stored by the script.
58 |
59 | * `static/` – Static files such as CSS and images. They'll get copied to the output.
60 |
61 | * `templates/` – Jinja template files, called by the script to render the site's pages.
62 |
63 | ## Post Metadata
64 |
65 | Metadata uses the [Python-Markdown Meta-Data extension](http://pythonhosted.org/Markdown/extensions/meta_data.html)
66 | syntax. Supported fields are:
67 | * `Title` – the title of the post.
68 | * `Slug` – the URL slug, i.e. `my-cool-post` in `http://domain.com/blog/my-cool-post/`. Generated
69 | automatically from the title, but can be overridden here.
70 | * `Date` – the publication date/time and timezone of the post. Run `now.py` to print the current
71 | date/time in the format for this field.
72 | * `Categories` – list of categories for the post (one per line).
73 | * `Files` – list of files that should be copied alongside the post in the output (one per line).
74 | Wildcards work here, e.g. `*.png`.
75 | * `Hidden` – yes/no. Hidden posts get rendered, but don't show up on the index page or in any lists
76 | of posts, so the only way to get to them is by typing in or linking to their URL.
77 | * `Comments` – yes/no, whether comments are enabled on the post. Defaults to yes.
78 | * `Type` – overrides the post type inferred from the directory structure.
79 |
80 | There are two special types:
81 | * `test` posts appear only in the dev version of the site, but are left out in production.
82 | * `page` posts are standalone, top-level entries that don't appear under any "section". Their URL
83 | will be simply `http://domain.com/post-slug/` rather than `http://domain.com/type/post-slug/`.
84 |
85 | ## Macros
86 |
87 | Jinja macros defined in `templates/macros.html` can be called from Markdown source. This can be
88 | used for bits of HTML that aren't expressible in Markdown alone. For example, the `imgcaption` macro
89 | produces an image with a caption below it, which can optionally be floated left or right so that the
90 | article text flows around it.
91 |
92 | ## Future Work
93 |
94 | On my part, future development of this project is only going to consist of what I need/want for my
95 | personal site. However, bug reports and pull requests are welcome!
96 |
97 | Possible enhancements:
98 | * Markdown caching. Most of the execution time of the script is in Markdown parsing/rendering, so
99 | caching it to avoid re-rendering unchanged posts should improve performance.
100 | * Static LaTeX rendering to avoid the MathJax load time hit for math-heavy pages.
101 |
--------------------------------------------------------------------------------
/content/test/hilitetest.md:
--------------------------------------------------------------------------------
1 | Title: Syntax Highlighting Test
2 | Date: 2016-09-14 17:03:41 -0700
3 | Categories: Test
4 |
5 | This is a test of static syntax highlighting.
6 |
7 |
8 |
9 | Here is some unhighlighted preformatted code:
10 | ```text
11 | Hello, world
12 | This line is indented
13 | This one even more
14 | <> & ! _foo_ $bar$
15 | ```
16 |
17 | Here is some C++ code:
18 | ```cpp
19 | #pragma once
20 | #include "util-basics.h"
21 | #include "util-err.h"
22 | #include
23 | #include
24 |
25 | // Base array type: not really a container, just a pointer and size,
26 | // with the actual data stored elsewhere.
27 | template
28 | struct array
29 | {
30 | T * data;
31 | size_t size;
32 |
33 | // Subscript accessor
34 | T & operator[] (size_t i)
35 | {
36 | ASSERT_ERR(i < size);
37 | return data[i];
38 | }
39 |
40 | // Constructors
41 | array(): data(nullptr), size(0) {}
42 | array(T * data_, size_t size_): data(data_), size(size_) {}
43 | template array(std::initializer_list initList): data(&(*initList.begin())), size(initList.size()) {}
44 | template array(array a): data(a.data), size(a.size) {}
45 | template array(U(& a)[N]): data(a), size(N) {}
46 |
47 | // Create a "view" of a sub-range of the array
48 | array slice(size_t start, size_t sliceSize)
49 | {
50 | ASSERT_ERR(start < size);
51 | ASSERT_ERR(start + sliceSize < size);
52 | return { data + start, sliceSize };
53 | }
54 | };
55 | ```
56 |
57 | Here is some Python code:
58 | ```python
59 | # Parse a date string in ISO syntax (e.g. '2016-08-23')
60 | def parseDate(dateStr):
61 | return datetime.date(*(int(s) for s in dateStr.split('-')))
62 |
63 | # Convert a title to a URL slug, e.g. "My Awesome Post!!" -> 'my-awesome-post'
64 | def slugify(title):
65 | return '-'.join(re.sub(r'\W', '', s.lower()) for s in title.split())
66 |
67 | # Class for storing a rendered post and its metadata
68 | class Post:
69 | def __init__(self, sourcePath, html, meta):
70 | self.sourcePath = sourcePath
71 | self.html = html
72 |
73 | # Extract summary (for index page) by reading up to the marker in the HTML.
74 | # Note: if the marker isn't present this will return the entire HTML.
75 | self.summary = html.split('')[0].strip()
76 |
77 | # Allow post type to be specified by the name of the subdirectory under content/,
78 | # but this can be overridden by metadata.
79 | if 'type' in meta:
80 | self.type = meta['type'][0]
81 | else:
82 | subdir = os.path.dirname(os.path.relpath(sourcePath, contentDir))
83 | if subdir:
84 | self.type = pathlib.PurePath(subdir).parts[0]
85 | else:
86 | self.type = 'blog'
87 |
88 | # Normalize the other metadata parsed out of the source file.
89 | self.title = meta['title'][0] if 'title' in meta else 'Untitled Post'
90 | self.slug = meta['slug'][0] if 'slug' in meta else slugify(self.title)
91 | self.date = parseDate(meta['date'][0]) if 'date' in meta else datetime.date(datetime.date.today().year + 1, 1, 1)
92 | self.categories = meta.get('categories', [])
93 | self.files = meta.get('files', [])
94 | self.hidden = (meta['hidden'][0].lower() in ['true', 'yes']) if 'hidden' in meta else False
95 | ```
96 |
97 | String with escape codes:
98 | ```cpp
99 | printf("Hello, world!\t\tFoo!\n");
100 | ```
101 |
102 | Here's some Javascript with an inline regular expression:
103 | ```js
104 | var re = /(?:\d{3}|\(\d{3}\))([-\/\.])\d{3}\1\d{4}/;
105 | function testInfo(phoneInput){
106 | var OK = re.exec(phoneInput.value);
107 | if (!OK)
108 | window.alert(phoneInput.value + " isn't a phone number with area code!");
109 | else
110 | window.alert("Thanks, your phone number is " + OK[0]);
111 | }
112 | ```
113 |
114 | Here's some PHP with a heredoc string and variable interpolation:
115 | ```php
116 | foo = 'Foo';
126 | $this->bar = array('Bar1', 'Bar2', 'Bar3');
127 | }
128 | }
129 |
130 | $foo = new foo();
131 | $name = 'MyName';
132 |
133 | echo <<foo.
135 | Now, I am printing some {$foo->bar[1]}.
136 | This should print a capital 'A': \x41
137 | EOT;
138 | ?>
139 | ```
140 |
141 | Here's some Bash script with backticks and variable interpolation:
142 | ```bash
143 | sudo chown `id -u` /somedir
144 | for file in *.png
145 | do
146 | mv "$file" "${file/.png/_old.png}"
147 | done
148 | ```
149 |
150 | Here's some LaTeX code:
151 | ```latex
152 | \section{The Standard Model}
153 |
154 | It's an interesting exercise to try to count how many particle species there are in the Standard
155 | Model. The answer depends on what you decide to consider ``distinct'' species.
156 |
157 | First, let's limit ourselves to just the particles the SM considers fundamental (i.e.\ no hadrons).
158 | The usual accounting goes something like this:
159 | \begin{itemize}
160 | \item 6 leptons: $e, \mu, \tau, \nu_e, \nu_\mu, \nu_\tau$
161 | \item 6 quarks: $u, d, s, c, b, t$
162 | \item 4 ``force-carrier'' bosons: $\gamma, W, Z, g$
163 | \item The Higgs boson.
164 | \end{itemize}
165 | That adds up to 17 particle species.
166 | ```
167 |
168 | And here are some random snippets of HLSL I gathered:
169 |
170 | ```hlsl
171 | [numthreads(256, 1, 1)]
172 | void cs_main(uint3 threadId : SV_DispatchThreadID)
173 | {
174 | // Seed the PRNG using the thread ID
175 | rng_state = threadId.x;
176 |
177 | // Generate a few numbers...
178 | uint r0 = rand_xorshift();
179 | uint r1 = rand_xorshift();
180 | // Do some stuff with them...
181 |
182 | // Generate a random float in [0, 1)...
183 | float f0 = float(rand_xorshift()) * (1.0 / 4294967296.0);
184 |
185 | // ...etc.
186 | }
187 | ```
188 |
189 | ```hlsl
190 | // Constant buffer of parameters
191 | cbuffer IntegratorParams : register(b0)
192 | {
193 | float2 specPow; // Spec powers in XY directions (equal for isotropic BRDFs)
194 | float3 L; // Unit vector toward light
195 | int2 cThread; // Total threads launched in XY dimensions
196 | int2 xyOutput; // Where in the output buffer to store the result
197 | }
198 |
199 | static const float pi = 3.141592654;
200 |
201 | float AshikhminShirleyNDF(float3 H)
202 | {
203 | float normFactor = sqrt((specPow.x + 2.0f) * (specPow.y + 2.0)) * (0.5f / pi);
204 | float NdotH = H.z;
205 | float2 Hxy = normalize(H.xy);
206 | return normFactor * pow(NdotH, dot(specPow, Hxy * Hxy));
207 | }
208 |
209 | float BeckmannNDF(float3 H)
210 | {
211 | float glossFactor = specPow.x * 0.5f + 1.0f; // This is 1/m^2 in the usual Beckmann formula
212 | float normFactor = glossFactor * (1.0f / pi);
213 | float NdotHSq = H.z * H.z;
214 | return normFactor / (NdotHSq * NdotHSq) * exp(glossFactor * (1.0f - 1.0f / NdotHSq));
215 | }
216 | ```
217 |
--------------------------------------------------------------------------------
/static/default.css:
--------------------------------------------------------------------------------
1 | @charset 'utf-8';
2 |
3 | /* basic text formatting */
4 | * { box-sizing: border-box; }
5 | body {
6 | margin: 0;
7 | font-family: sans-serif;
8 | }
9 |
10 | /* main page layout - wide screen
11 | content and sidebar are side-by-side; everything's centered */
12 | @media (min-width: 52.1em) {
13 | body { text-align: center; }
14 | #content {
15 | display: inline-block;
16 | vertical-align: top;
17 | width: 45em;
18 | margin-right: 2em;
19 | text-align: left;
20 | }
21 | #sidebar {
22 | display: inline-block;
23 | vertical-align: top;
24 | width: 20em;
25 | margin-left: 2em;
26 | text-align: left;
27 | }
28 | #site-header-aligner, #site-footer-aligner {
29 | display: inline-block;
30 | width: 69em; /* nice */
31 | text-align: left;
32 | }
33 | }
34 | /* medium screen - sidebar goes below content */
35 | @media (min-width: 52.1em) and (max-width: 75em) {
36 | #content { margin-right: 0; }
37 | #sidebar {
38 | width: 45em;
39 | margin-left: 0;
40 | padding-right: 25em;
41 | }
42 | #site-header-aligner, #site-footer-aligner {
43 | width: 45em;
44 | }
45 | }
46 | /* narrow screen - everything's full-width with 3em side margins */
47 | @media (min-width: 40em) and (max-width: 52.1em) {
48 | #content, #sidebar, #site-header-aligner, #site-footer-aligner {
49 | margin: 0 3em;
50 | }
51 | }
52 | /* mobile - side margins reduce to 0.5em */
53 | @media (max-width: 40em) {
54 | #content, #sidebar, #site-header-aligner, #site-footer-aligner {
55 | margin: 0 .5em;
56 | }
57 | }
58 |
59 | /* site header */
60 | #site-header {
61 | background: #444;
62 | }
63 | #site-header-aligner {
64 | padding-top: .3em;
65 | }
66 | #site-header h1 a {
67 | color: #fff;
68 | }
69 |
70 | /* site footer */
71 | #site-footer {
72 | height: 18pt;
73 | background: #444;
74 | color: #fff;
75 | }
76 | #site-footer-aligner {
77 | height: 18pt;
78 | padding: .35em 0;
79 | }
80 | #site-footer p {
81 | margin: 0;
82 | font-size: 8pt;
83 | white-space: nowrap;
84 | }
85 | #site-footer a {
86 | color: #fff;
87 | }
88 | html, body {
89 | height: 100%;
90 | }
91 | #footer-pusher {
92 | min-height: 100%;
93 | margin-bottom: -18pt;
94 | }
95 | #footer-pusher::after {
96 | content: "";
97 | display: block;
98 | height: 18pt;
99 | }
100 |
101 | /* main nav bar */
102 | #site-header nav { white-space: nowrap; }
103 | #site-header nav a {
104 | display: inline-block;
105 | padding: .2em 0.4em;
106 | margin: .4em 0 0 0;
107 | color: #fff;
108 | font-size: 14pt;
109 | }
110 | @media (max-width: 40em) {
111 | #site-header nav a {
112 | padding: .1em .4em;
113 | margin-top: .3em;
114 | font-size: 12pt;
115 | }
116 | }
117 | #site-header nav a:first-of-type { margin-left: 0; }
118 | #site-header nav a:hover {
119 | background-color: #bbb;
120 | }
121 | #site-header nav a.active {
122 | background-color: #fff;
123 | color: #000;
124 | }
125 |
126 | /* pagination nav bar */
127 | .pagination {
128 | margin: 4pt 0;
129 | }
130 | .pagination::after {
131 | content: "";
132 | display: block;
133 | clear: both;
134 | }
135 | .pagination .page-left {
136 | float: left;
137 | text-align: left;
138 | }
139 | .pagination .page-right {
140 | float: right;
141 | text-align: right;
142 | }
143 |
144 | /* social media buttons */
145 | .twitter-share-button {
146 | vertical-align: bottom;
147 | }
148 | #___plusone_0 {
149 | width: 64px !important;
150 | }
151 |
152 | /* images and various ways of displaying them */
153 | img, video {
154 | display: block;
155 | margin: 0 auto;
156 | max-width: 100%;
157 | height: auto;
158 | }
159 | audio {
160 | vertical-align: middle;
161 | }
162 | .align-center { text-align: center; }
163 | figure {
164 | display: inline-block;
165 | }
166 | figure img { margin: 0; }
167 | .float-left {
168 | float: left;
169 | margin: 0 10pt 0 0;
170 | }
171 | .float-right {
172 | float: right;
173 | margin: 0 0 0 10pt;
174 | }
175 | @media (max-width: 40em) {
176 | figure { display: block; }
177 | figure img { margin: 0 auto; }
178 | .float-left, .float-right {
179 | float: none;
180 | margin: 0 auto;
181 | }
182 | }
183 |
184 | /* image captions */
185 | figcaption {
186 | max-width: 30em;
187 | margin: 0 auto;
188 | text-align: center;
189 | }
190 |
191 | /* post-list tables, as seen on all-posts and category pages */
192 | .post-list td {
193 | border: none;
194 | vertical-align: top;
195 | }
196 | .post-list td:first-child {
197 | min-width: 4.2em;
198 | white-space: nowrap;
199 | }
200 |
201 | /* embedded code */
202 | code, tt, pre { font-family: monospace; }
203 | code, tt {
204 | background-color: #ececec;
205 | padding: 0 0.2em;
206 | }
207 | .codehilite {
208 | display: inline-block;
209 | max-width: 100%;
210 | overflow-x: auto;
211 | background-color: #f8f8f8;
212 | border: 1px solid #ccc;
213 | }
214 | .codehilite pre {
215 | margin: 4pt;
216 | }
217 |
218 | /* pygments code highlighting styles:
219 | this is default.css from from https://github.com/richleland/pygments-css
220 | (go there to see plenty more highlighting themes too) */
221 | .codehilite .hll { background-color: #ffffcc }
222 | .codehilite { background: #f8f8f8; }
223 | .codehilite .c { color: #408080; font-style: italic } /* Comment */
224 | .codehilite .err { border: 1px solid #FF0000 } /* Error */
225 | .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
226 | .codehilite .o { color: #666666 } /* Operator */
227 | .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
228 | .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
229 | .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
230 | .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
231 | .codehilite .gd { color: #A00000 } /* Generic.Deleted */
232 | .codehilite .ge { font-style: italic } /* Generic.Emph */
233 | .codehilite .gr { color: #FF0000 } /* Generic.Error */
234 | .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
235 | .codehilite .gi { color: #00A000 } /* Generic.Inserted */
236 | .codehilite .go { color: #808080 } /* Generic.Output */
237 | .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
238 | .codehilite .gs { font-weight: bold } /* Generic.Strong */
239 | .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
240 | .codehilite .gt { color: #0040D0 } /* Generic.Traceback */
241 | .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
242 | .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
243 | .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
244 | .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
245 | .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
246 | .codehilite .kt { color: #B00040 } /* Keyword.Type */
247 | .codehilite .m { color: #666666 } /* Literal.Number */
248 | .codehilite .s { color: #BA2121 } /* Literal.String */
249 | .codehilite .na { color: #7D9029 } /* Name.Attribute */
250 | .codehilite .nb { color: #008000 } /* Name.Builtin */
251 | .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
252 | .codehilite .no { color: #880000 } /* Name.Constant */
253 | .codehilite .nd { color: #AA22FF } /* Name.Decorator */
254 | .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
255 | .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
256 | .codehilite .nf { color: #0000FF } /* Name.Function */
257 | .codehilite .nl { color: #A0A000 } /* Name.Label */
258 | .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
259 | .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
260 | .codehilite .nv { color: #19177C } /* Name.Variable */
261 | .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
262 | .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
263 | .codehilite .mf { color: #666666 } /* Literal.Number.Float */
264 | .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
265 | .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
266 | .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
267 | .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
268 | .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
269 | .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
270 | .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
271 | .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
272 | .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
273 | .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
274 | .codehilite .sx { color: #008000 } /* Literal.String.Other */
275 | .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
276 | .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
277 | .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
278 | .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
279 | .codehilite .vc { color: #19177C } /* Name.Variable.Class */
280 | .codehilite .vg { color: #19177C } /* Name.Variable.Global */
281 | .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
282 | .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
283 |
--------------------------------------------------------------------------------