2009年08月01日

日記スクリプト作成

tDiaryのバージョンが上がっていたのでアップデートしようとしたら、ものの見事に失敗しました。かなりスクリプトのあちこちを弄ってしまっていたので、時間をかければ動くとは思うのですが、今後も毎回バージョンアップのたびに弄り回すのはキツイので、日記用のスクリプトを自作することにしました。

毎日会社から帰ってきてから少しずつ書いていたので、こんなコードを書くのに1週間もかかってしまいました。徹夜で遊べた学生時代が懐かしいなぁ。

個人的に日記を動的に生成する必要性は全くないので、日記データをただひたすらパースするだけのスクリプトになります。特に設計もせずに書いたので、少しコードを整理出来たらドキュメントをマトモに書いて公開しようかなぁ。

スクリプト本体

#!/usr/local/bin/ruby

require "erb"
require "time"
require "date"
require "fileutils"

class Entries
  def initialize(diary_file_list)
    @diary_file_list = diary_file_list
  end

  #すべての日記データからエントリーを作成する。
  #すべて処理が終わると、すべてのエントリーリストを返す。
  def each
    html = "<ul>\n"
    @diary_file_list.each_with_index{|file, count|
      data = parse_html(file)
      entry = Hash.new
      entry[:file_path] = file
      entry[:count] = count + 1
      entry[:date] = parse_date(file)
      entry[:title] = data[0]
      entry[:tags] = data[1]
      entry[:text] = data[2]
      entry[:next_link] = make_link_next_entry(file)
      entry[:prev_link] = make_link_prev_entry(file)
      entry[:permalink] = make_link_permalink(file)
      entry[:tag_link] = make_link_tags(data[1])
      yield(entry)

      html << "<li>" + entry[:date] + "<br><a href=\"" + File.basename(entry[:file_path], ".txt") + ".html\">" + entry[:title] + "</a></li>\n"
    }
    html << "</ul>\n"
    return html
  end

  private
  def make_link_tags(tags)
    html = "<a href=\"" + ERB::Util.u(Diary::TAG_ALL_LIST) + ".html\">" + Diary::TAG_ALL_LIST + "</a>"
    tag_arr = tags.split(" ")
    tag_arr.each{|tag|
      html << ", <a href=\"" + ERB::Util.u(tag) + ".html\">" + tag + "</a>"
    }
    return html
  end

  def make_link_permalink(diary_file_path)
      file_name = File.basename(diary_file_path, ".txt")
      return "<a href=\"" + file_name + ".html\">" + Diary::LINK_PERMALINK + "</a>"
  end

  def make_link_next_entry(diary_file_path)
    index = @diary_file_list.index(diary_file_path)
    if(index+1 < @diary_file_list.length)
      file_name = File.basename(@diary_file_list[index+1], ".txt")
      return "<a href=\"" + file_name + ".html\">" + Diary::LINK_NEXT_ENTRY + "</a>"
    else
      return Diary::LINK_NEXT_ENTRY_EMPTY
    end
  end

  def make_link_prev_entry(diary_file_path)
    index = @diary_file_list.index(diary_file_path)
    if(index-1 >= 0)
      file_name = File.basename(@diary_file_list[index-1], ".txt")
      return "<a href=\"" + file_name + ".html\">" + Diary::LINK_PREV_ENTRY + "</a>"
    else
      return Diary::LINK_PREV_ENTRY_EMPTY
    end
  end

  def parse_date(diary_path)
    date = File.basename(diary_path, ".txt")
    date = date[0,8]
    date.insert(8, Diary::DATE_DAY)
    date.insert(6, Diary::DATE_MONTH)
    date.insert(4, Diary::DATE_YEAR)
    return date
  end
  
  def parse_html(diary_path)
    parse_data = Array.new(3)
    data = File.open(diary_path){|f| f.readlines}
    data << "\n"
    #日記タイトルを取得
    parse_data[0] = data[0].chomp
    data.delete_at(0)

    #タグ情報を取得
    parse_data[1] = data[0].chomp
    data.delete_at(0)

    #日記データの構文解析
    html = Array.new
    flag_p = false
    flag_html = false
    flag_pre = false
    data.each{|line|
      line = line.chomp
      #空行時の処理
      #通常モードとHTMLモード(非整形モード)を解除する
      if(line == "")
        if(flag_p)
          html << "</p>"
        elsif(flag_pre)
          html << ERB::Util.h(line)
        end
        flag_p = false
        flag_html = false
      ##空行でないときの処理
      else
        #フラグが立っている場合の処理
        if(flag_html)
          html << line
        elsif(flag_pre)
          #preモードの終わりを示す"?"があれば、モードを終了し</pre>タグを挿入する
          if(line[0,1] == "?")
            flag_pre = false
            html[html.length-1] = html.last + "</pre>"
          else
            html << ERB::Util.h(line)
          end
        #特殊文字"<", "?", "!"の処理
        elsif(line[0,1] == "<" and line[1,1] != "%")
          flag_html = true
          html << line
        elsif(line[0,1] == "?")
          flag_pre = true
          line = line[1,line.length]
          html << "<pre>" + ERB::Util.h(line)
        elsif(line[0,1] == '!')
          if(flag_p)
            html << "</p>"
            flag_p = false
          end
          if(line[1,1] == "!")
            if(line[2,1] == "!")
              if(line[3,1] == "!")
                if(line[4,1] == "!")
                  html << "<h6>" + line[4,line.length] + "</h6>"
                end
              else
                html << "<h5>" + line[3,line.length] + "</h5>"
              end
            else
              html << "<h4>" + line[2,line.length] + "</h4>"
            end
          else
            html << "<h3>" + line[1,line.length] + "</h3>"
          end
        #文頭が特殊文字でない場合の通常処理
        else
          #特殊文字を無効化する際に使う行頭スペースのの処理
          if(line[0] == " ")
            line = line[1,line.length]
          end
          #すでにパラグラフ内にいれば"<br>"を付け、そうでなければ"<p>"を付けパラグラフ内であるフラグを立てる。
          if(flag_p)
            html << "<br>"
            html << line
          else
            html << "<p>"
            html << line
            flag_p = true
          end
        end
      end
    }
    html_string = ""
    html.each{|line|
      html_string << line
      html_string << "\n"
    }
    parse_data[2] = html_string
    return parse_data
  end
end

class Tag
  def initialize
    @tag_list = Hash.new
  end

  def add(tags, diary_path, date, title)
    tag_arr = tags.split(" ")
    tag_arr.each{|tag|
      path_arr= @tag_list[:"#{tag}"]
      if(path_arr == nil)
        @tag_list[:"#{tag}"] = Array.new
        @tag_list[:"#{tag}"] << [diary_path, date, title]
      else
        @tag_list[:"#{tag}"] << [diary_path, date, title]
      end
    }
  end

  def each
    yield({:name => Diary::TAG_ALL_LIST, :list => make_all_list, :num => @tag_list.length})
    @tag_list.each_key{|key|
      yield({:name => key.to_s, :list => make_list(key), :num => @tag_list[key].length})
    }
  end

  private
  def make_list(tag)
    html = "<ul>\n"
    @tag_list[tag].each{|items|
      html << "<li>" + items[1] + " <a href=\"" + File.basename(items[0], ".txt") + ".html\">" + items[2] + "</a></li>\n"
    }
    html << "</ul>\n"
    return html
  end

  def make_all_list
    html = "<ul>\n"
    @tag_list.each_key.sort{|a, b| a.to_s <=> b.to_s}.each{|key|
      html << "<li><a href=\"" + ERB::Util.u(key.to_s) + ".html\">" + key.to_s + "</a> (" + @tag_list[key].length.to_s + ") </li>"
    }
    html << "</ul>\n"
    return html
  end
end

class Image
  attr_accessor :thumb

  def initialize(diary_file_list, html_path, thumb_size,image_size, flag_overwrite, jpg_quality)
    @thumb = Hash.new
    make_images(diary_file_list, html_path, thumb_size,image_size, flag_overwrite, jpg_quality)
  end

  def make_images(diary_file_list, html_path, thumb_size,image_size, flag_overwrite, jpg_quality)
    diary_file_list.each{|txt_file|
      dir = File.dirname(txt_file) + "/" + File.basename(txt_file, ".txt") + "/"
      next if(!File.exist?(dir)||File.ftype(dir)!="directory")
      @thumb[File.basename(txt_file, ".txt").to_sym] = Array.new
      index = 0
      Dir.entries(dir).sort.each{|img_file|
        next if("."==img_file||".."==img_file||!(File.ftype(dir+img_file)=="file"&&File.extname(img_file).downcase==".jpg"))
        thumb_file = File.basename(txt_file, ".txt") + "-thumb-" + index.to_s + ".jpg"
        if(flag_overwrite||!File.exist?(html_path + thumb_file))
          p thumb_file
          cmd = "convert -quality \"#{jpg_quality}\" -geometry \"#{thumb_size}\" \"#{dir+img_file}\" \"#{html_path + thumb_file}\""
          %x(#{cmd})
        end
        image_file = File.basename(txt_file, ".txt") + "-image-" + index.to_s + ".jpg"
        if(flag_overwrite||!File.exist?(html_path + image_file))
          p image_file
          cmd = "convert -quality \"#{jpg_quality}\" -geometry \"#{image_size}\" \"#{dir+img_file}\" \"#{html_path + image_file}\""
          %x(#{cmd})
        end
        index += 1
        @thumb[File.basename(txt_file, ".txt").to_sym] << "<span class=\"center\"><a href=\"" + image_file + "\"><img src=\"" + thumb_file + "\" alt=\"Thumbnail Image [" + img_file + "]\"></a></span>"
      }
    }
  end
end

class Diary
  def initialize
    #外部コンフィグファイルの読み込み
    eval( File.open("./diary.conf"){|f| f.read })

    #日記データファイル名の一覧を取得
    @diary_file_list = Array.new
    Dir.entries(@diary_path).sort{|a,b| (a<=>b)*-1}.each{|f|
      next if(("."==f)||(".."==f)||(File.ftype(@diary_path+f)!="file"))
      @diary_file_list << @diary_path + f
    }

    #タグ一覧を作るためのクラスの初期化
    @tags = Tag.new
    #画像を処理するクラスの初期化
    @img = Image.new(@diary_file_list, @html_path, @thumb_size, @image_size, @flag_overwrite, @jpg_quality)
  end

  def make_rss
    @rss_items = ""
    @rss_item_num.times{|index|
      break if index >= @entries.length

      @rss_items << "<item>"
      @rss_items << "<title>" + @entries[index][:title] + "</title>"
      @rss_items << "<link>" + @rss_base_url + File.basename(@entries[index][:file_path],".txt") + ".html</link>"
      @rss_items << "<pubDate>" + Time.parse(DateTime.strptime("#{@entries[index][:date].to_s}+9", "%Y#{DATE_YEAR}%m#{DATE_MONTH}%d#{DATE_DAY}%Z").to_s).rfc822.to_s + "</pubDate>"
      @rss_items << "</item>"
    }

    write_html(@rss_filename, @template_rss)
  end

  def make_top
    write_html(@top_page_filename, @template_top)
  end

  def make_tag_list
    @tags_list = Array.new
    @tags.each{|@tag|
      @tags_list << @tag
      #@tag_name = tag; @tag_list = list; @tag_num = num;
      write_html(@tag[:name] + ".html", @template_tag_list)
    }
  end

  def make_entries
    @entries = Array.new
    entries = Entries.new(@diary_file_list)
    @entries_title_list = entries.each{|@entry|
      @entries << @entry
      @tags.add(@entry[:tags], @entry[:file_path], @entry[:date], @entry[:title])
      html_filename= File.basename(@entry[:file_path], ".txt") + ".html"
      write_html(html_filename, @template_entry)
    }
  end

  def make_stylesheet
    write_html(File.basename(@template_stylesheet), @template_stylesheet)
  end

  def write_html(html_filename, template_path)
    p "html=" + html_filename + " template" + template_path
    html = template_match(File.open(template_path){|f| f.read})
    html = template_match(html)
    File.open(@html_path + html_filename, "w"){|f| f.write(html)}
  end

  def template_match(template)
    erb = ERB.new(template)
    return erb.result(binding)
  end

  def copy_files
    FileUtils.cp_r(@files_path, @html_path)
  end
end

diary = Diary.new
diary.make_entries
diary.make_tag_list
diary.make_top
diary.make_stylesheet
diary.make_rss
diary.copy_files

設定ファイル diary.conf

#!/usr/local/bin/ruby
#diaryの名前
@title="意伝子発信器"
#著者の名前
@author="gaso"
#日記のヘッダー
@header="<p>いずれ消え行く無駄な情報を、密やかに発信する装置。つまり日記。</p>"
#日記のフッター
@footer=\
"<p>このページ内で掲載している画像、文章等の転載・転用は自由に行ってください。</p>
<address>author : gaso<br>
address : gaso@gaso.jpn.org<br>
Copyright 2003-2009 gaso All rights reserved.
</address>
<p>Generated by SiRuDiar<br>
Powerd by Ruby ver <%= RUBY_VERSION %><br>
Output Date <%= Time.now.rfc822.to_s %>
</p>"


#日記データの場所(最後のスラッシュを忘れずに!)
@diary_path="./data/"
#htmlを書き出す場所(最後のスラッシュを忘れずに!)
@html_path="./html/"
#topページのファイル名
@top_page_filename = "index.html"
#rss2.0のファイル名
@rss_filename="index.rdf"
#webページの場所(最後のスラッシュを忘れずに!)
@rss_base_url="http://gaso.jpn.org/receptive-field/diary/html/"
#日記に使う素材や雑多なファイルの場所(最後のスラッシュを忘れずに!)
#ファイルは@html_path+"files"という名前のディレクトリにコピーされる
@files_path="./data/files/"


#作成するサムネイルのサイズ
@thumb_size = "320x240>"
#作成する画像ファイルのサイズ
@image_size = "1024x768>"
#常に新しく画像を生成するか
@flag_overwrite = false
#JPEGファイルの画質
@jpg_quality = "95"

##diaryのテンプレートの場所
#新しいエントリーをまとめて表示するページのテンプレート
@template_top="./template/top.rhtml"
#個々のエントリーのテンプレート
@template_entry="./template/entry.rhtml"
#エントリーのタイトル一覧を表示するページのテンプレート
@template_entirs_list="./template/entries_list.rhtml"
#タグ一覧を表示するページのテンプレート
@template_tag_list="./template/tag.rhtml"
#スタイルシートを保存してある場所
@template_stylesheet="./template/stylesheet.css"
#rss2.0のテンプレート
@template_rss="./template/rss2_0.rhtml"

#rssファイルに書くitemの数
@rss_item_num=10
#RSSの概要
@rss_description="日常的な記録情報を発信します"

LINK_NEXT_ENTRY="Next"
LINK_NEXT_ENTRY_EMPTY="None"
LINK_PREV_ENTRY="Prev"
LINK_PREV_ENTRY_EMPTY="None"
LINK_PERMALINK="Permalink"

DATE_YEAR="年"
DATE_MONTH="月"
DATE_DAY="日"

TAG_ALL_LIST = "カテゴリー"

topページのtemplate

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  <html lang="ja">
    <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <meta name="format-detection" content="telephone=no">
      <link rel="stylesheet" type="text/css" href="stylesheet.css">
      <title><%= @title %></title>
    </head>
    <body>
      <h1><a href="./index.html"><%= @title %></a></h1>
      <div class="header"><%= @header %></div>
      <hr>
      <div class="side">
        <div class="menu">
          <h2>タイトル一覧 <a href="./index.rdf"><img src="./files/rss_icon.png" alt="rss icon" title="rss icon"></a></h2>
          <%= @entries_title_list %>
        </div>
        <div class="menu">
          <h2><%= @tags_list[0][:name] %></h2>
          <%= @tags_list[0][:list] %>
        </div>
      </div>

      <div class="main">
        <% 10.times{|index| %>
        <div class="entry">
          <div class="date"><%= @entries[index][:date] %></div>
          <h2><%= @entries[index][:title] %></h2>
          <%= @entries[index][:text] %>
          <p class="footer"><%= @entries[index][:permalink] %> - <%= @entries[index][:tag_link] %></p>
        </div>
        <% } %>
        <p class="entry_link"><%= @entries[10][:next_link] %></p>
      </div>
      <hr>
      <div class="footer">
        <%= @footer %>
      </div>
    </body>
  </html>