2015-07-16   snap   ruby 

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!(/&/, '&amp;')
    line.gsub!(/</, '&lt;')
    line.gsub!(/>/, '&gt;')
    line.gsub!(/"/, '&quot;')
    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}\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>&mdash; 結城浩 (@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&#038;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">&nbsp;$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>&nbsp;$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>&nbsp;$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$&nbsp;<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&nbsp;<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$&nbsp;<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>
 2015-07-16   snap   ruby