开源项目 PlainSite:A Truly Hackable Static Site Generator!

jex · November 26, 2014 · Last by jex replied at November 26, 2014 · 5677 hits

PlainSite: A Simple but Truly Hackable Static Site Generator https://github.com/CJex/PlainSite

这个项目其实是去年做的,当时没时间推广。之前看到某 Python 静态站点生成器还特地强调不仅是生成博客而是 Site ,我就低调不下去了。 PlainSite 虽然功能简单,但却具有无限的扩展性,理论上可以生成分类任意复杂的静态站点(只要你会写 Ruby)。没人用太可惜。

下载运行也和 Jekyll 差不多,gem install PlainSiteplainsite initplainsite build。支持 Markdown,也用了 FrontMatter 格式。 我先列举下它的亮点:

  1. 配合 Git,可以只生成更新的文章对应的页面,而不用每次都重新生成整个站点。我记得以前谁说用 Octpress 要是本地 build 话,硬盘卡卡响。PlainSite 可以通过git status读取哪些文件被修改过,只生成修改过的 Post 相关的 HTML 页面,不会每次都重新生成整个站点。(plainsite build -a可以重新生成整个)
  2. 采用 LazyLoad,仅读取需要用到的数据文件。进一步减少硬盘文件读取。
  3. 可以使输出页面中只包含相对路径的 URL,这样站点可以不需要 Web 服务器直接用 File 协议浏览。执行plainsite build -l就生成一个 local 站点,直接在文件管理器中打开就能浏览。当然 PlainSite 也内置了一个实时预览用的 WebServer,修改文件不需要重启。
  4. 自动清除已被删除文章相应的页面。PlainSite 需要把所有不由它管理的静态文件放到_src/assets下面,这样它就可以自动删除孤立的文章页面。你删除了某文章,生成的页面也能自动清理。
  5. 分页实在太简单了,下面提到 API 时再说。另外分面列表页面的文件名也作了优化,当有新文章时只需重新生成首页。

好了,上面的亮点其实是次要的。最主要的是,PlainSite 是一个 Framework,提供了一套 API,而且这套 API 和 MVC 框架很相近。首先是数据管理,在站点_src/data目录下,相当一个文件数据库,一个目录表示一个 Category,类似于数据库中的 Table,一个 Markdown 或 HTML 文件表示一个 Post,相应于数据库中一条记录。 如何查询呢?Ruby DSL 的强大体现出来了。 读取 news 目录下的所有 Post,相当于 SQL:SELECT * FROM news

$site.data / :news / '*'  # 返回PlainSite::Data::PostList 对象

你要文章置顶功能?

($site.data / :news / '*' ).order_by &:top

对应在_src/data/news/目录下的 Post 文件中就写:

---
title: Today Top News-Wall down
top: true
---
这里是正文内容

你要分页?好,按每页 5 条分页

$site.data / :news / '*'  / 5  # 返回 [PlainSite::Data::PostListPage]

可这数据拿出来又怎么使用呢?对应于 MVC 中的 URL Router,在_src/data/routes.rb

$site.route(
  url_pattern: "/{date}-top-news-{title}.html", 
  data: $site.data / :news / '*' ,
  template: 'news-list.html'  
)

上面的代码其实就相当于以 PostList 中每篇 Post 为 Context 去 Render news-list.html这个模板,URL 模式中用 Post 的属性值去替换。

那接下来就是模板,PlainSite 使用 ERB,扩展支持 include 和 layout,例如:

---
layout: base.html   # 相对于当前模板文件的路径
---
<% content_for :page_title do %>
  <%=title%> - <%=site.name %>
<% end %>
<% content_for :page_content do %>
  <h1><%=title%></h1>
  <p>Date:<%=date%></p>
  <%=content%>
  <hr />

  Use site.url_for to get url,so it can be affected by 'plainsite build --local',results in relative url.
  <%=site.url_for 'essays/hello' %>

  Also support includes.
  <%=include 'footer.html' %>
<% end %>

base.html 文件内容:

<html>
<head>
  <title><%=yield :page_title%></title>
</head>
<body>
  <%=yield :page_content%>
</body>
</html>

好了,MVC 齐全了。但强大在什么地方呢?强大的不是 PlainSite 而是你会 Ruby!因为routes.rb就是一个 Ruby Script。所以:

# 给每个分类都生成一个列表页面
# $site.data.subs 是子目录,返回 Category[]
$site.data.subs.each do |category|
  $site.route(
    url_pattern: "#{category.name}/{slug}.html",
    # category.posts/5 means category.posts.paginate(page_size:5)
    # return PostListPage[]
    data: category.posts/5, # category.posts is same as category / '*'  .
    template: 'list.html'
  )
end

# 要RSS?
$site.route(
  url_pattern: 'rss.xml',
  data: { posts: $site.data/'**' }, # RubyChina代码高亮补丁:/
  template: 'rss.erb'  # rss.erb在PlainSite中已经内置,`plainsite init`会自动生成这个文件
)

#你要给每个分类单独生成一个RSS?
$site.data.subs.each do |category|
  $site.route(
    url_pattern: "#{category.name}/{slug}-rss.xml",
    data:category,
    template: 'category-rss.html'
  )
end

而且模板是 ERB,你想怎么搞就怎么搞:

# routes.rb 中写
$site.route(
   url_pattern:'jobs',
   data: {jobs:($site.data / :jobs / '*').order_by &:pay_money },  #按付的钱排序
   template: 'jobs.html'
)

然后jobs.html:

<%jobs.each do |job|%>
   <li><%=job.title%></li>
<%end%>

好了,明白了吧,意思就是只要你会写代码,那它扩展性是无限的。 Who choosed PlainSite?好吧,其实只有我自己一个人在用:https://jex.im/

另外我告诉你们一个网站:https://staticsitegenerators.net/ ,想用哪个随便挑

如果gem install plain_site不成功的话请告知我(Windows 需要安装 DevKit),因为我好不容易跨墙 gem push 上去,然后就没法再安装下来了,总是 timeout,这边墙太高了。(可以换 https://ruby.taobao.org/ 试试)

还有好像 gem 不解决 ruby headers 的问题,你需要安装好 ruby-dev(或 ruby-devel 什么的)。


如果是自己 build,我不知道 bundle 和 bundler 有什么区别,自从我敲错了哪个 bundle(r) build bundle(r) install 就有一个幽灵 plainsite 命令被安装进去却运行不起来也删不掉

本想集成到 githis.com。。。

#2 楼 @moliliang 这个跑在托管服务器上还真比较麻烦,因为允许执行任意代码

在 win 下安装成功

感觉很熟悉,原来半年前就关注了~

#4 楼 @ywjno 出乎意料。是不是以前已经装过 yaml、pygments 了?之前我看 windows 下装起来好麻烦

#6 楼 @jex 那些是什么东西完全不认识,就配置了一个 DevKit 就行了

#5 楼 @small_fish__ 我是因为不想 hack Jekyll 的代码所以自己写了,估计别人也跟我一样不喜欢去 hack 已有的东西都纷纷自己造轮子

#8 楼 @jex 不是,我是偶然看到你博客,然后发现的,所以。。。

#9 楼 @small_fish__ 想用哪个随你挑:https://staticsitegenerators.net/

总共三百多个静态站点生成器!

#11 楼 @xfstart07 我看了感觉它的 Dynamic Pages 思路跟我的有些相似,但我用 routes.rb 一个 raw 文件解决了 Dynamic Pages、sitemap、local data、Pretty URLs 等多个问题,而且我发现它好像没有类似 PlainSite 提供的直接用类似 SQL 的 DSL 读取数据的接口。

#10 楼 @jex 看了你的代码,与 jekyll 比起来,清爽许多。貌似没有看到 tag 的功能,只有 category 吗?

#13 楼 @small_fish__

要 tag?其实 DIY 起来很简单!在 post 中 front matter header 中写

---
tags: [Ruby,Rails,PlainSite]
---

然后给每种 tag 都生成一个列表页面?routes.rb

tagMap=Hash.new
($site.data['**'] ).each do |post|
    post.tags.each do |tag|
        tagMap[tag]=tagMap[tag] || []
        tagMap[tag].push post
    end
end

tagMap.each do |tag,posts|
   # 来个分页吧
   posts=PlainSite::Data::PostList.new posts,$site
   $site.route(
      url_patten: '/tag/#{tag}',
      data: posts / 10 ,  # 高亮补丁/
      template: 'your-tag-list-tpl.html'
   )
end

然后your-tag-list-tpl.html可以照抄list.html

只要会 Ruby,DIY 就是这么简单!(好坑爹,不过也正是因为我偷懒,所以我才把它设计的尽量灵活啊 ^O^)

PS:@lgn1st 下面的语法高亮不对,不知道有没有办法 FIX:

$site.data / '**'  
# 这里被当成正则表达式了可能

#14 楼 @jex 是的,灵活点好,代码也够简单,只是你的代码有些貌似需要格式化一下。

另外貌似 博客标题如果有 !就会报错(mac 环境)

~/my/plain_test:$ plainsite newpost post-slug "Hello,wolrd!This is the title"
-bash: !This: event not found

#15 楼 @small_fish__

哪的代码需要格式化?rb 代码我记得都重新格式化过了,可能 erb 代码我忘了重新格式化了。

那个错误不是 PlainSite 的,那是因为在 bash 中就这样

echo "Hello,wolrd!This is the title"

你要换成单引号才 OK:

echo 'Hello,wolrd!This is the title'

#16 楼 @jex 是这样,那你的 readme,就要修改写了,我一开始直接 copy 的。 https://github.com/JexCheng/plain_site#getting-started

You need to Sign in before reply, if you don't have an account, please Sign up first.