snap
source code
snap
#!/usr/bin/env ruby
# encoding: utf-8
require 'yaml'
require 'optparse'
rebuild = false
opt = OptionParser.new
opt.on('--rebuild') {|v| rebuild = true }
opt.on('--help') do |v|
puts "Usage: snap [--rebuild] [--help]"
exit
end
opt.parse!(ARGV)
CONFIG = YAML.load_file('config.yaml')
BASE = CONFIG["base"]
VERBOSE = CONFIG["verbose"]
COMMON_OPTS = [
"--latexmathml",
"--template=template/default.html",
"-f markdown",
"-t html5",
]
META_OPTS = []
CONFIG.each_pair do |key, value|
META_OPTS << "-M #{key}=\"#{value}\""
end
puts "Metas: #{META_OPTS.join(' ')}" if VERBOSE > 1
def summarize(t)
summary = ''
t.split(/\n/).each do |line|
line.gsub!(/([-\\~*`#$])/, ' ')
line.gsub!(/&/, '&')
line.gsub!(/</, '<')
line.gsub!(/>/, '>')
line.gsub!(/"/, '"')
line.gsub!(/\s+/, ' ')
summary += line
if summary.length > 200
break
end
end
summary
end
def read_info
info = []
Dir.glob("source/*.md").each do |filename|
filename.match(/source\/(.*)\.md/)
id = $1
open(filename) do |file|
fm = "filename: #{filename}\n"
if file.read.match(/---\n(.*\n)---\n(.*)/m)
fm += $1
fm += "summary: \"#{summarize($2)}\"\n"
end
fm = YAML.load(fm)
fm["id"] = id
info << fm
end
end
info.sort {|a,b| b["id"] <=> a["id"]}
end
def collect_tags(info) # { 'vim' => [ {}, {}, {} ], ... }
tags = Hash.new {|h,k| h[k] = [] }
info.each do |i|
if i["tags"]
i["tags"].each do |tag|
tags[tag] << i
end
end
end
tags
end
def create_list(info, type, permalink, dir, listtitle)
mdfilename = ".list-#{listtitle}.md"
open(mdfilename, 'w') do |file|
file.puts "---"
file.puts "pages:"
info.each do |i|
id = i["id"]
title = id
date = i["date"]
count = 0
file.puts " - id: #{i["id"]}"
file.puts " title: #{i["title"]}"
file.puts " date: #{i["date"]}"
file.puts " summary: \"#{i["summary"]}\""
file.puts " tags: #{i["tags"]}" if i["tags"]
end
file.puts "---"
end
system("mkdir -p #{dir} 2> /dev/null")
puts "Processing \"#{mdfilename}\"" if VERBOSE > 2
system([
"pandoc",
COMMON_OPTS.join(' '),
META_OPTS.join(' '),
"-M listtitle=\"#{listtitle}\"",
"-M permalink=#{permalink}",
"-M #{type}=true",
"-o #{dir}/index.html",
"\"#{mdfilename}\"",
].join(' '))
# system("rm -f \"#{mdfilename}\"")
end
def create_posts(info)
updated = 0
info.each do |i|
id = i["id"]
src = "source/#{id}.md"
dst = "public/#{id}/index.html"
if not File.exists?(dst) or File.ctime(src) > File.ctime(dst)
title = i["title"]
permalink = "#{BASE}/#{id}/"
system("mkdir public/#{id} 2> /dev/null")
template_opt = if i["template"]
"--template=template/#{i["template"]}.html"
else
""
end
command = [
"pandoc",
COMMON_OPTS.join(' '),
META_OPTS.join(' '),
"-M permalink=#{permalink}",
"-o public/#{id}/index.html",
template_opt,
"source/#{id}.md",
].join(' ')
puts "command line: #{command}" if VERBOSE > 2
system(command)
puts "Updated: #{id} #{title}" if VERBOSE > 0
updated += 1
else
puts "Not updated: #{id}" if VERBOSE > 1
end
end
puts "Updated #{updated} file(s)." if VERBOSE > 0
end
puts "Current dir is #{Dir.getwd}" if VERBOSE > 1
if rebuild
puts "Rebuild public dir." if VERBOSE > 0
system("rm -rf public/*")
system("mkdir public 2> /dev/null")
end
system("rsync -auqz --delete static/ public/static")
all_info = read_info
all_tags = collect_tags(all_info)
create_posts(all_info)
create_list(all_info, "LIST", "#{BASE}/all", "public/all", "All Posts")
create_list(all_info.take(20), "TOP", "#{BASE}/", "public", CONFIG["sitename"])
all_tags.each_pair do |tag, taginfo|
create_list(taginfo, "LIST", "#{BASE}/#{tag}", "public/#{tag}", tag)
end
# vim: set filetype=ruby:
config.yaml
base: https://snap.textfile.org
author: 結城浩 / Hiroshi Yuki
verbose: 1
sitename: snap.textfile.org
twitter: hyuki
logo: https://snap.textfile.org/static/images/snap.png
logo_facebook: https://snap.textfile.org/static/images/1200x630-facebook.png
description: "結城浩がちょっとしたことをメモしておくところ。"
favicon: "https://www.hyuki.com/favicon.ico"
profile: "<blockquote class=\"twitter-tweet\" lang=\"ja\"><p lang=\"ja\" dir=\"ltr\">『数学ガール』作者。毎週火曜日は結城メルマガ。毎週金曜日はWeb連載「数学ガールの秘密ノート」。文章書きとプログラミングが好きなクリスチャン。</p>— 結城浩 (@hyuki) <a href=\"https://twitter.com/hyuki/status/599907803461857281\">2015年5月17日</a></blockquote>"
template/default.hml
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="$description$">
<meta name="author" content="$author$">
<base href="$base$">
<title>$title$</title>
<link rel="icon" href="$favicon$">
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/readable/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link href="static/css/github-markdown.css" rel="stylesheet">
<link href="static/css/snap.css" rel="stylesheet">
<link href="static/css/code.css" rel="stylesheet">
<meta property="og:title" content="$title$">
<meta property="og:type" content="website">
<meta property="og:image" content="$logo_facebook$">
<meta property="og:site_name" content="$sitename$">
<meta property="og:description" content="$description$">
<meta property="og:url" content="$base$">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@$twitter$">
<meta name="twitter:creator" content="@$twitter$">
<meta name="twitter:title" content="$title$">
<meta name="twitter:description" content="$description$">
<meta name="twitter:image:src" content="$logo$">
<script type='text/x-mathjax-config'>
MathJax.Hub.Config({tex2jax: {inlineMath: [["\x24","\x24"]]}});
</script>
<script type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML%2CSafe.js&ver=3.8'></script>
</head>
<body>
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a style="display:block;color:black; border: 0; padding-bottom:0; margin-bottom:0;" class="navbar-brand" href="$base$"><img style="display:inline" width="32" height="32" src="static/images/hyuki-ghost-bold.svg"> $sitename$</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a style="color:black;" href="$base$/all">All Posts</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="markdown-body">
$if(TOP)$ <!-- TOP -->
<!--
<h1 style="border: 0; padding-bottom:0; margin-bottom:0;"><span class="fa fa-file-text-o"></span> $sitename$</h1>
-->
<h1 style="border: 0; padding-bottom:0; margin-bottom:0;"><img width="48" height="48" src="static/images/hyuki-ghost-bold.svg"></span> $sitename$</h1>
<p class="text-muted"><small>$description$</small></p>
<div class="list-group">
$for(pages)$
<div class="list-group-item">
<h2 style="border: 0" class="list-group-item-heading"><a style="color: black;" href="$pages.id$">$pages.title$ <span class="fa fa-chevron-right"></span></a></h2>
<p style="text-align: right;" class="text-muted">
$for(pages.tags)$
<a href="$pages.tags$" class="btn btn-info btn-xs">$pages.tags$</a>
$endfor$
<small>$pages.date$</small>
</p>
<!--
<p class="list-group-item-text">$pages.summary$<br><br>
<a href="$pages.id$" class="btn btn-primary">Read</a>
</p>
-->
</div>
$endfor$
<a href="$base$/all" class="list-group-item">
<h2 style="border: 0" class="list-group-item-heading">Show All Posts <span class="fa fa-chevron-down"></span></h2>
</a>
</div>
$else$<!-- not TOP -->
$if(LIST)$ <!-- list -->
<h1>$listtitle$</h1>
<div class="list-group">
$for(pages)$
<div class="list-group-item">
<h2 style="border: 0" class="list-group-item-heading"><a style="color: black;"href="$pages.id$">$pages.title$ <span class="fa fa-chevron-right"></span></a></h2>
<p style="text-align: right;" class="text-muted">
$for(pages.tags)$
<a href="$pages.tags$" class="btn btn-info btn-xs">$pages.tags$</a>
$endfor$
<small>$pages.date$</small>
</p>
<!--
<p class="list-group-item-text">$pages.summary$<br><br>
<a href="$pages.id$" class="btn btn-primary">Read</a>
</p>
-->
</div><!-- ./list-group-item -->
$endfor$
</div>
$else$ <!-- post -->
<h1>
$title$
<span style="text-align:right">
$for(tags)$
<a href="$tags$" class="btn btn-info btn-xs">$tags$</a>
$endfor$
</span>
</h1>$body$<p style="text-align:right">
<a href="$permalink$">$date$</a>
by
<a href="https://twitter.com/$twitter$">$twitter$</a><br>
$for(tags)$
<a href="$tags$" class="btn btn-info btn-xs">$tags$</a>
$endfor$
</p>
$endif$ <!-- /post -->
$endif$
</div>
</div><!-- /.container -->
<footer>
<div class="container">
<a href="https://twitter.com/share" class="twitter-share-button" data-url="$permalink$" data-text="$title$" data-via="$twitter$">Tweet</a><script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
$profile$
</div><!-- /.container -->
</footer>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</body>
</html>