ありダラ

とゆーわけで。
ほんとはHDWriter::Application#postの中身もちょっとメソッドでくくりたかった
んだけど。疲れててしなくて、確かそのまま。

…何が問題って、そりゃ一番の問題は私がほとんどWWW::Mechanizeのことを
しらないことです。


□あからさまな変更点

  • HDWriterモジュール
    • HDWriter#Config
    • HDWriter#Option
    • HDWriter#Application
  • 設定ファイルの書式
  • 例外をファイルにダンプするようになった
  • 日記テキストを検索するディレクトリを指定するようになった
  • touchはYAMLで書き出す

[Ruby]application.rb

アプリケーションのライブラリ。

  • 2007-11-11:Hash#updateの挙動を勘違いしていたので修正。
# WWW::Mechanize版はてダラ(はてなダイアリーライター)
# 
# from: http://d.hatena.ne.jp/spider-man/20070818
# 
# mofdified by arikui.
# distributes under the same term as Ruby.
# 
# Version -- 0.3.1

require 'rubygems'
require 'mechanize'
require 'optparse'
require 'yaml'


module HDWriter
  class LoginError < RuntimeError ; end
  
  
  CONFIG_PATH     = './config.rb'
  TOUCH_SAVE_PATH = './_touch.dat'
  ERROR_DUMP_PATH = './_error.dump'
  
  
  class Config
    def self.empty
      new()
    end
    
    def initialize(h={})
      @configs = h
    end
    
    [
      :diary_directory,
      :hatena_id,
      :password,
      :proxy_address,
      :proxy_port
    ].each do |name|
      define_method(name){ @configs[name] }
    end
  end
  
  
  class Option
    DEFAULT = {
      :trivial => false
    }
    
    def self.default
      new()
    end
    
    def initialize(h={})
      @options = DEFAULT.dup.update(h)
    end
    
    DEFAULT.keys.each do |name|
      define_method(name){ @options[name] }
      define_method("#{name}="){|v| @options[name] = v }
    end
  end
  
  
  class Application
    VERSION = '0.3.0'
    
    DIARY_FILE_PATH_FILTER = /\d\d\d\d-\d\d-\d\d\.txt/
    
    def initialize(configs=Config.empty, options=Option.default)
      @on_login = false
      @echo     = $stdout
      @agent    = WWW::Mechanize.new
      @configs  = configs
      @options  = options
      @opp      = new_optionparser()
    end
    
    attr_reader :echo
    attr_reader :agent
    attr_reader :configs
    attr_reader :options
    
    def new_optionparser
      opp = OptionParser.new
      opp.on('-t', '--trivial', 'trivial update'){
        options.trivial = true
        echo.puts 'trivial update on'
      }
      opp.on('-h', '--help', 'show this help'){ print_help }
      opp.on('-v', '--version', 'show version'){ print_version }
      opp
    end
    private :new_optionparser
    
    def option_parse!(argv)
      @opp.parse!(argv)
    end
    
    def print_help
      $stderr.puts @opp.help
      exit
    end
    private :print_help
    
    def print_version
      $stderr.puts "hdwriter -- version #{VERSION}"
      exit
    end
    private :print_version
    
    def setup_agent
      return unless configs.proxy_address
      echo.puts "set proxy -> #{configs.proxy_address}:#{configs.proxy_port}"
      agent.set_proxy(configs.proxy_address , configs.proxy_port)
    end
    
    def login
      form  = agent.get('https://www.hatena.ne.jp/login').forms.first
      set_login_form(form)
      res = form.submit
      if /.*error-message"\>.*\<p\>(.*?)\<\/p\>.*\<\/div\>/m =~ res.body
        raise LoginError, "login error to #{configs.hatena_id}"
      else
        echo.puts "login to #{configs.hatena_id}"
        @on_login = true
      end
    end
    
    def set_login_form(form)
      form["name"]       = configs.hatena_id
      form["password"]   = configs.password
      form["persistent"] = "true"
      form
    end
    private :set_login_form
    
    def post
      touched = touch_time()  #optimize by cache value.
      Dir.foreach(diary_dir) do |path|
        next unless DIARY_FILE_PATH_FILTER === path
        path = File.join(diary_dir(), path)
        next unless File.mtime(path) > touched
        login unless @on_login
        echo.print "post #{path} ... "
        date = File.basename(path, '.txt').split('-')
        uri = hatena_dirary_edit_uri(date)
        form = agent.get(uri).forms.first
        body = File.open(path){|f| f.read.split("\n", 2) }
        set_write_form(form, date, body)
        form.submit
        echo.puts "end"
      end
    end
    
    def diary_dir
      configs.diary_directory || '.'
    end
    private :diary_dir
    
    def hatena_dirary_edit_uri(date)
      [
        "http://d.hatena.ne.jp",
        configs.hatena_id,
        "edit?date=#{date.join}"
      ].join('/')
    end
    private :hatena_dirary_edit_uri
    
    def set_write_form(form, date, body)
      form["year"]  = date[0]
      form["month"] = date[1]
      form["day"]   = date[2]
      form["title"] = body[0]
      form["body"]  = body[1]
      form["trivial"] = "true" if options.trivial
      form
    end
    private :set_write_form
    
    def touch_time
      if File.exist?(TOUCH_SAVE_PATH)
        YAML.load_file(TOUCH_SAVE_PATH)
      else
        Time.now
      end
    end
    private :touch_time
    
    def update_touch_time
      File.open(TOUCH_SAVE_PATH, 'w') do |f|
        YAML.dump(Time.now, f)
      end
    end
    
  end
end

[Ruby]hdwriter.rb

エントリポイント。

require 'application.rb'

def exceptions_handling
  begin
    yield
  rescue Exception => ex
    err = HDWriter::ERROR_DUMP_PATH #optimize
    File.open(err, 'w') do |f|
      f.puts Time.now, ex.class, ex.message, ex.backtrace
    end
    $stderr.puts "Error! -- #{ex.message}"
    $stderr.puts "(error details was dumped at `#{err}')"
    exit
  end
end

def run_application(application)
  exceptions_handling do
    application.option_parse!(ARGV)
    application.setup_agent
    application.post
    application.update_touch_time
  end
end

load HDWriter::CONFIG_PATH
configs = hdwriter_context()
run_application HDWriter::Application.new(configs)

[Ruby]config.rb(例)

def hatena_diary_context
  {
    :hatena_id      => "arikui1911",
    :password       => "xxxx"
  }
end

def hdwriter_context
  HDWriter::Config.new(
    hatena_diary_context.dup.update(
      :diary_directory => './diary'
    )
  )
end