【翻译】编写一个小型静态网站生成器

2019/10/08 Python 转载

【翻译】编写一个小型静态网站生成器

大概有一百多种用 Python 编写的静态站点生成器(甚至还有其他语言编写的静态站点生成器)。

所以我决定写我自己的。为什么?好吧,我只是想。我希望将自己的博客从Ghost移开,并且希望保持真正的简约性。我决定使用 GitHub Pages 托管,因为他们最近宣布支持 自定义域的SSL

渲染内容

每个静态网站生成器都需要采用某种源格式(例如 Markdown 或 ReStructuredText)并将其转换为 HTML。自从我离开 Ghost 以来,我决定坚持 Markdown。

自从我最近将 Github风格的Markdown渲染 集成到 Warehouse 中以来,我决定使用 cmarkgfm 。使用以下方式将 Markdown 渲染为 HTML:

import cmarkgfm

def render_markdown(content: str) -> str:
    content = cmarkgfm.markdown_to_html_with_extensions(
        content,
        extensions=['table', 'autolink', 'strikethrough'])
    return content

cmarkgfm 确实有一个名为 github_flavored_markdown_to_html 的便捷方法,但是它使用 GitHub 的 tagfilter 扩展名,当我要将脚本和内容嵌入到帖子中时,这是不希望的。因此,我只是选择了我想使用的扩展。

收集资源(所有文档)

好的,我们有一种渲染 Markdown 的方法,但是我们还需要一种收集所有源文件的方法。我决定将所有来源存储在 ./src。我们可以pathlib用来收集它们:

import pathlib
from typing import Iterator


def get_sources() -> Iterator[pathlib.Path]:
    return pathlib.Path('.').glob('srcs/*.md')

头部元数据

许多静态网站生成器都有 “前题” 的概念-一种为每个源文件设置元数据等的方法。我想支持 frontmatter,让我为每个帖子设置日期和标题。看起来像这样:

---
title: Post time
date: 2018-05-11
---

# Markdown content here.

对于 frontmatter 有一个非常好的和简单的现有库,称为 python-frontmatter。我可以用它来提取前题和原始内容:

import frontmatter


def parse_source(source: pathlib.Path) -> frontmatter.Post:
    post = frontmatter.load(str(source))
    return post

返回的 post 对象的 .content 属性具有实际内容,而 .keys 属性则具有前题的值,可以通过 post['title'] 等方式调用。

渲染帖子

现在我们有了帖子的内容和要点,我们可以渲染它们。我决定使用 jinja2cmarkgfm 渲染后的 Markdown 和 frontmatter 放置到一个简单的HTML模板中。

这里是模板:

<!doctype html>
<html>
<head><title></title></head>
<body>
  <h1></h1>
  <em>Posted on </em>
  <article>
    <section class="collection-head small geopattern" data-pattern-id="timeit模块的使用">
<div class="container">
  <div class="columns">
    <div class="column three-fourths">
      <div class="collection-title">
        <h1 class="collection-header">timeit模块的使用</h1>
        <div class="collection-info">
          
          <span class="meta-info">
            <span class="octicon octicon-calendar"></span> 2019/10/06
          </span>
          
          
          <span class="meta-info">
            <span class="octicon octicon-file-directory"></span>
            <a href="https://halysl.github.io/categories/#Python" title="Python">Python</a>
          </span>
          
          <span class="meta-info">
            <span class="octicon octicon-file-directory"></span>
            <a href="https://halysl.github.io/categories/#Python模块" title="Python模块">Python模块</a>
          </span>
          
        </div>
      </div>
    </div>
  </div>
</div>
</section>
<!-- / .banner -->
<section class="container content">
<div class="columns">
  <div class="column three-fourths" >
    <article class="article-content markdown-body">
    <h1 id="timeit模块的使用">timeit模块的使用</h1>

<p>timeit 主要是为了测试代码运行速度。</p>

<p>它主要有两个方法,即 timeit 和 repeat。</p>

<p>测试一段代码的运行时间,在 python 里面有个很简单的方法,就是使用 timeit 模块,使用起来超级方便</p>

<p>下面简单介绍一个 timeit 模块中的函数。</p>

<p>主要就是这两个函数:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">timeit(stmt='pass', setup='pass', timer=&lt;defaulttimer&gt;, number=1000000)</code></li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    返回: 返回执行stmt这段代码number遍所用的时间,单位为秒,float型
    参数:
         stmt:要执行的那段代码
         setup:执行代码的准备工作,不计入时间,一般是import之类的
         timer:这个在win32下是time.clock(),linux下是time.time(),默认的,不用管
         number:要执行stmt多少遍
</code></pre></div></div>

<ol>
  <li><code class="language-plaintext highlighter-rouge">repeat(stmt='pass', setup='pass', timer=&lt;defaulttimer&gt;, repeat=3, number=1000000)</code></li>
</ol>

<p>这个函数比 timeit 函数多了一个 repeat 参数而已,表示重复执行 timeit 这个过程多少遍,返回一个列表,表示执行每遍的时间。</p>

<p>当然,为了方便,python 还用了一个 Timer 类,Timer 类里面的函数跟上面介绍的两个函数是一样一样的</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class timeit.Timer(stmt='pass', setup='pass',timer=&lt;timer function&gt;)
Timer.timeit(number=1000000)
Timer.repeat(repeat=3,number=1000000)
</code></pre></div></div>

<p>看懂了吧,一样的,使用的时候哪种方便用哪种。</p>

<p>就相当于</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">timeit</span><span class="p">(</span><span class="n">stmt</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">timer</span><span class="o">=&lt;</span><span class="n">defaulttimer</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000000</span><span class="p">)</span>
<span class="o">=</span>
<span class="n">Timer</span><span class="p">(</span><span class="n">stmt</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">timer</span><span class="o">=&lt;</span><span class="n">timerfunction</span><span class="o">&gt;</span><span class="p">).</span><span class="n">timeit</span><span class="p">(</span><span class="n">number</span><span class="o">=</span><span class="mi">1000000</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">repeat</span><span class="p">(</span><span class="n">stmt</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">timer</span><span class="o">=&lt;</span><span class="n">defaulttimer</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">repeat</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000000</span><span class="p">)</span>
<span class="o">=</span>
<span class="n">Timer</span><span class="p">(</span><span class="n">stmt</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s">'pass'</span><span class="p">,</span> <span class="n">timer</span><span class="o">=&lt;</span><span class="n">timerfunction</span><span class="o">&gt;</span><span class="p">).</span><span class="n">repeat</span><span class="p">(</span><span class="n">repeat</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000000</span><span class="p">)</span>
</code></pre></div></div>

    </article>
    <div class="share">
      <div class="share-component"></div>
    </div>
    <div class="comment">
      

  

  
      
    


    </div>
  </div>
  <div class="column one-fourth">
    
<h3>Search</h3>
<div id="site_search">
    <input type="text" id="search_box" placeholder="Search">
</div>

<ul id="search_results"></ul>

<link rel="stylesheet" type="text/css" href="https://halysl.github.io/assets/css/modules/sidebar-search.css">
<script src="https://halysl.github.io/assets/js/simple-jekyll-search.min.js"></script>
<script src="https://halysl.github.io/assets/js/search.js"></script>

<script type="text/javascript">
SimpleJekyllSearch({
    searchInput: document.getElementById('search_box'),
    resultsContainer: document.getElementById('search_results'),
    json: 'https://halysl.github.io/assets/search_data.json',
    searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
    noResultsText: 'No results found',
    limit: 10,
    fuzzy: false,
    exclude: ['Welcome']
})
</script>

    

    
<h3 class="post-directory-title mobile-hidden">Table of Contents</h3>
<div id="post-directory-module" class="mobile-hidden">
  <section class="post-directory">
  <!-- Links that trigger the jumping -->
  <!-- Added by javascript below -->
  <dl></dl>
  </section>
</div>

<script src="https://halysl.github.io/assets/js/jquery.toc.js"></script>

  </div>
</div>
</section>
<!-- /section.content -->

  </article>
</body>
</html>

这里是 python 的渲染代码:

import jinja2

jinja_env = jinja2.Environment(
    loader=jinja2.FileSystemLoader('templates'),
)


def write_post(post: frontmatter.Post, content: str):
    path = pathlib.Path("./docs/{}.html".format(post['stem']))

    template = jinja_env.get_template('post.html')
    rendered = template.render(post=post, content=content)
    path.write_text(rendered)

请注意,我将渲染后的 HTML 文件存储在 ./docs 中。这是因为我将 GitHub Pages 配置为发布 doc 目录 中的内容。

现在我们可以渲染单个帖子,我们可以使用一开始创建的 get_sources 函数遍历所有帖子:

from typing import Sequence

def write_posts() -> Sequence[frontmatter.Post]:
    posts = []
    sources = get_sources()

    for source in sources:
        # Get the Markdown and frontmatter.
        post = parse_source(source)
        # Render the markdown to HTML.
        content = render_markdown(post.content)
        # Write the post content and metadata to the final HTML file.
        post['stem'] = source.stem
        write_post(post, content)

        posts.append(post)

    return posts

编写目录

现在,我们可以渲染帖子,但是我们也应该渲染 index.html 列出所有帖子。我们可以使用另一个 jinja2 模板以及所有帖子的列表来执行此操作write_posts。

这里是渲染模板:

<!doctype html>
<html>
<body>
  <h1>My blog posts</h1>
  <ol>
    
  </ol>
</body>
</html>

这里是 python 渲染代码:

def write_index(posts: Sequence[frontmatter.Post]):
    # Sort the posts from newest to oldest.
    posts = sorted(posts, key=lambda post: post['date'], reverse=True)
    path = pathlib.Path("./docs/index.html")
    template = jinja_env.get_template('index.html')
    rendered = template.render(posts=posts)
    path.write_text(rendered)

整理起来

现在剩下的就是使用一个 main 函数将其连接起来。

def main():
    posts = write_posts()
    write_index(posts)


if __name__ == '__main__':
    main()

在GitHub上查看

因此,您正在阅读的页面已使用此代码呈现!您可以在 theacodes / blog.thea.codes 上查看完整的源代码,包括语法高亮显示支持。

转载信息

Search

    Table of Contents