├── .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 | 9 | 10 | {% endfor %} 11 |
{{ (p.date.strftime('%b') + ' %d' % p.date.day) if p.date else '' }}{{ p.title }}
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 | ![it me](me.jpg){:.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 |

{{ p.title }}

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 | Comments 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 | 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 | --------------------------------------------------------------------------------