Hatena::ブログ(Diary)

おんがえしの日記

2012-01-30

自分専用のメモを作って簡単に検索出来るようにする

f:id:tuto0621:20120130232818j:image

自分専用のメモを持つ

TwitterFacebookアカウントを誰もが持つようになり個人の日記がインターネット上で簡単に読めるようになりました。その反面、ちょっとした一言をきっかけに個人情報が流出したり、自身の犯罪行為を意図せず暴露してしまい社会問題になっています。何故このようなことが起きるのでしょうか?

最近の若者は・・・と言うのは簡単ですが、私の意見はもっとシンプルで「一番簡単に始めることが出来るのが誰でも検索出来る場所だから」なのだと思います。 自身の思いや感情をアウトプットしたくなるのは人としてごく自然な衝動です。文房具屋に行って日記帳を買うよりもスマートフォンからTwitterのサイトに行きアカウントを作成する方が早いし簡単なのです(お金もかからないしね)。

気をつけなければいけないのはTwitterFacebookは検索出来る場所だということです。自分以外の誰かが検索ワードを頼りにあなたのつぶやきを見つけることが出来ます。それがたくさんの人に読まれたりまとめサイトに載ってほしくないことでも。思ったことをそのまま書けば不利になるのは書いた人なのです。

ですが、思ったことは書かずに口につぐみなさいとか、大人になるまでTwitterを使うのをやめなさいとか言いたいのではありません。言いたかったのは、自分の思いをインターネットにダイレクトにぶつけず、人が決して検索出来ない場所に一回ぶつけてみるといいよ、ということです。

例えば個人のメモ帳のような場所に。

メモの作り方

プレーンなテキストファイルでもいいですし、ChangeLogのような専用の形式でもいいです。私はリンクが貼れてブラウザから簡単に使えるTiddlyWikiを愛用しています。※自分用に改造したTiddlyWikiこちら

おすすめは1年ごとに1ファイルを作ることです。100歳まで生きたとしても100ファイルを超えることはありません。私の個人メモの場合は2010年が1.2MB、2011年が1.8MBでした。 これならUSBメモリクラウドにも置けるデータサイズになりそうです。

最初は余りフォーマットにこだわらずに思いついたことをつらつらと書いてみましょう。どうせ自分以外は誰も見ないのですから言葉使いに気をつける必要もありません。一人きりで自分の感情と向きあってみましょう。少し時間を空けてメモを見直せば客観的な気持ちで向かい合えるはずです。寝かせたメモからはきっと今よりもいいつぶやきが書けますよ。

メモを検索出来るようにする

TwitterやFacebokを使っていて特に便利なのは検索機能です。せっかくなので個人メモも検索出来るようにしてみましょう。

プログラマにとって一番簡単なのはgrepを使うことでしょう。

$ grep 個人メモ /path/to/memo.txt
/path/to/memo.txt:5 個人メモを作った
/path/to/memo.txt:10 個人メモに追記

1ファイルだったらこれでも十分ですが以下のような場合は少し大変です。

  • 去年のメモを含めて検索したい
  • データが大きくなってgrepに時間がかかる
  • メモファイルがたくさんある

私は自作のMilkodeというソフトを使って検索しています。元々ソースコードの検索向けに作られたものですが、テキストであれば何でも登録することが出来ます。

$ milk add /path/to/memo
package    : memo
add_record : /path/to/memo/memo2010.txt
add_record : /path/to/memo/memo2011.txt
add_record : /path/to/memo/memo2012.txt
result     : 1 packages, 3 records, 3 add. (0.5sec)
*milkode*  : 86 packages, 61119 records in /Users/user/.milkode/db/milkode.db.

/path/to/memo'memo'というパッケージ名で登録されました。検索する時は、

$ gmilk -p memo 個人 メモ
memo2010.txt:16:個人メモに追記
memo2011.txt:7:メモを作成(個人的なもの)

です。データの置き場所を気にせず高速に検索出来てAND検索も可能です。

メモを更新した時は、

$ cd /path/to/memo
$ milk update 

もしくは検索と同時にインデックスを更新するなら

$ gmilk -u -p memo test

です(後者がおすすめです)。

TiddlyWikiを検索する

TiddlyWiki本体に簡単な検索機能は含まれていますが、複数のTiddlyWikiから検索したり行単位の検索が出来ないためMilkodeから検索することを考えてみます。

TiddlyWikiは1つのhtmlの中に複数の仮想ファイル(Tiddlerといいます)が含まれており、単純にファイルを登録しただけでは余り価値のある情報を引き出すことが出来ません。そこで1Tiddlerを1テキストファイルに分割してMilkodeに登録するという作戦を考えてみます。

TiddlyWikiをテキストファイルに分割するために'tiddler2texts'というスクリプトを作りました。tidtoolsというgemに含まれています。

$ gem install tidtools

/path/to/memo/2010/memo.html/path/to/export-memo/2010 に出力するには以下のようにします。

$ tiddler2texts /path/to/memo/2010/memo.html /path/to/export-memo/2010

複数のメモをテキスト分割→Milkodeに追加 を自動化するために簡単なスクリプトを書いてみます。

/path/to/export-memo/output.rb

require 'rubygems'
require 'tidtools/tiddler2texts'

if __FILE__ == $0
  Tidtools::Tiddler2texts.output('/path/to/memo/2010/memo.html', '2010')
  Tidtools::Tiddler2texts.output('/path/to/memo/2011/memo.html', '2011')
end

メモの登録は最初の一回だけです。

$ cd /path/to/export-memo/
$ ruby output.rb
$ milk add /path/to/export-memo

update_memoコマンドを.bashrcに追記します。

# .bashrc
.
.
# メモの更新
alias update_memo='(cd /path/to/export-memo && ruby output.rb && milk update export-memo)'

.bashrcを更新します。

$ source ~/.bashrc

これで全ての準備は整いました!以下のコマンドを実行するたびに、TiddlyWikiに追加されたTiddlerがテキストファイルに追加され、合わせてMilkodeにも登録されます。

$ update_memo

終わりに

個人のメモを帳を付け始めて3年程経ちますが色々な所で役立つようになりました。何かに迷ったり混乱した時は事実を客観的にメモに書き出していきます。リストを見返せば要点が整理されていい案が浮かんでくるでしょう。瞬間的に感情的になった時はまずはメモに思うままに書きこんでひとまず落ち着きます。後で見返してみるとなんでこんなことで怒っていたのだろう?ということがよくあります。

新しいことをする時は過去のメモを読み返して昔似たようなことをしていなかったか調べます。他人が最善と思うことよりも自分が過去に成功した方法の方が安心して出来ることは多いはずです。クリエイティブであるべきは作るものであり、やり方全てが斬新である必要はまったくないのです。

自身を記録し、振り返り、未来を決めるためのメモ帳を持ちましょう。

参考文献

2012-01-21

Milkode 0.3、行に含まれないがファイル名や内容に含まれる検索ワードを指定することが出来るように

Milkode0.3をリリースしました。gmilkに絞り込みキーワードを指定出来るようにしたり、検索時の大文字小文字の扱いを改善しました。

キーワードによる絞り込み

その行には含まれていないけどファイル名やファイル内に含まれている検索ワードを指定することが出来るようになりました。

小文字で入力時、大文字/小文字を区別しない

インストール

$ gem install milkode

詳しくはこちらをどうぞ。 - ダウンロード - Milkode

リリースノート

  • gmilk
    • -k, -lオプションの追加、絞り込み検索が簡単に出来るように
    • 全て小文字で入力したら大文字小文字は無視、大文字が一つでも入れば厳密に検索
  • milk
    • milk info コマンドを追加
    • milk list コマンドの際、パッケージのファイル数を表示するように
  • その他
    • cdv, cdview を削除

2012-01-16

TiddlyWiki備忘録(2012年版)を作りました。

去年に引き続き、TiddlyWiki備忘録(2012年版)を作りました。

f:id:tuto0621:20120116003506p:image

TiddlyWikiが備忘録に向いている5の理由

  1. ブラウザさえあればどこでも使える、世界にあるほとんどのパソコンで開くことが出来る
  2. たった1つのhtmlファイルだけで構成されているので、持ち運びが楽
  3. 見出し、リスト、表組、リンク等、単なるテキスト以上の機能を内包する
  4. 豊富なプラグインが世界中で開発されている、アップデートも簡単
  5. JavaScriptで作られているのでブラウザの進化に合わせて表現力が上がる。

ダウンロード

zipアーカイブ
my_tiddlywiki_20120102.zip
gitレポジトリ
ongaeshi/my_tiddlywiki - GitHub

zipアーカイブを展開します。

2012年版の新機能

  • ヘッダーの色を紫に
  • 最新のTiddlyWikiに更新 2.6.1 → 2.6.5 に
  • TiddlerListMacroプラグインを追加
  • Startupページを調整、''よく使う'', ''ドキュメント''の二項目に絞る。

本家TiddlyWikiからの改造部分

終わりに

要望や不具合、使ってみた報告など頂けたら、2013年版を作る時の励みになります。

2011-12-25

Milkode0.2.9リリース、大量のテキストをgrepの10倍以上の速度で検索出来るgmilkコマンドを追加

f:id:tuto0621:20111226002329p:image

前回のバージョンアップから大分間が空いてしまいましたが、Milkode 0.2.9をリリースしました。

0.2.9の特徴

  1. gmilkコマンドの追加
  2. mcdコマンドの追加

要望の多かったコマンドラインツールを追加することが出来ました。

mcdとgmilkコマンドを使いはじめて一週間程経ちますが、かなり便利です。個人的にはgrepをほとんど使わなくなりつつあります。

ホームページ

今回のバージョンアップに合わせて大幅に更新しました。

リリースノート

  • gmilk
  • mcd
  • milk
  • milk web
    • Ruby1.8の時はhpricotを(文字化けが直る)、Ruby1.9の時はnokogiriを使うように(hpricotだと日本語検索が上手くいかない)。
    • ファイルリストをフォルダ、名前で並べ替えるように
  • MilkodeTestWorkクラスを追加、テスト向け環境構築を容易に

使っている人の声

tsurushuuさんがSublime Textのプラグインを作って下さいました。

終わりに

  • ツールが動かない
  • ドキュメントの内容がおかしい、こういう説明が欲しい
  • その他、質問

ありましたら、ブログコメントやTwitter(@ongaeshi)にて教えて頂けると助かります。

2011-12-14

RubyGemsはrequireの裏で何をやっているのか?

f:id:tuto0621:20111213005815p:image

ライブラリやツールをコマンド一発でインストール出来るRubyGemsはとっても便利です。自作ソフトをRubyGems.orgに登録すれば世界中でインストールして使ってもらえます。便利なRubyGemsですが内部ではどのような仕組みで動いているのでしょうか?

インストールしたgemはどこへいくのか?

試しに適当なgemインストールしてみましょう。

$ gem install rubywho
Successfully installed rubywho-0.4.0
1 gem installed
Installing ri documentation for rubywho-0.4.0...
Installing RDoc documentation for rubywho-0.4.0...

OSXMacPorts経由でインストールした場合は、以下にインストールされます。

/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/

古いバージョンもインストールしてみます。

$ gem install rubywho -v 0.3.0
Fetching: rubywho-0.3.0.gem (100%)
Successfully installed rubywho-0.3.0
1 gem installed
Installing ri documentation for rubywho-0.3.0...
Installing RDoc documentation for rubywho-0.3.0...

gemは複数のバージョンを同時に管理することが出来ます。rubywho-0.3.0は0.4.0の横に置かれます。

  • /opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/
  • /opt/local/lib/ruby/gems/1.8/gems/rubywho-0.3.0/

rubywho-0.4.0/の下には、コードがテストを含めた状態で入っています。ongaeshi/rubywho - GitHubは完全なrubywhoのソースコード一式ですが、gemの中身とほとんど変わりません。

gemをフォルダごと適当な場所にコピーすればテストも実行出来ますし改造することも可能です(unix環境ではパーミッションを直す必要があるかもしれません)。

# コピーして・・・
$ cp -rpv /opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/ ~/tmp/rubywho
/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/ -> /Users/ongaeshi/tmp/rubywho
/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0//.document -> /Users/ongaeshi/tmp/rubywho/.document
.
.
# テストも実行出来る
$ cd ~/tmp/rubywho/
$ rake test
(in /Users/ongaeshi/tmp/rubywho)
/opt/local/bin/ruby -I"lib:lib:test" "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/test_rubywho.rb" 
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
......
Finished in 0.016145 seconds.

6 tests, 48 assertions, 0 failures, 0 errors

何かRubyで作りたいものがある時は、似たようなことを実現しているgemを見つけ、そのソースコードを読むとかなり役立ちます。

$LOAD_PATHって何者?

まずは以下のプログラムを実行してみましょう。

# requireやloadはLOAD_PATHに登録されているディレクトリの中を順番に探して最初に見つかったものを採用する
p $LOAD_PATH                    #=> ["/opt/local/lib/ruby/site_ruby/1.8", ...]
# p $:                            # $LOAD_PATHの省略形

# ファイルが見つからない場合は、LoadError例外
begin
  require 'test_script'
rescue LoadError
  puts "'test_script' not found."
end

# LOAD_PATHに適切なディレクトリを設定すると読み込めるようになる
$LOAD_PATH.unshift "test"
require 'test_script'
puts test_script()

# LOAD_PATHに含まれたディレクトリからの相対パスでもOK
require 'a/test_script2'
puts a_test_script()

実行結果

$ ruby ./load_path_test.rb
["/opt/local/lib/ruby/site_ruby/1.8", "/opt/local/lib/ruby/site_ruby/1.8/i686-darwin10", "/opt/local/lib/ruby/site_ruby", "/opt/local/lib/ruby/vendor_ruby/1.8", "/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin10", "/opt/local/lib/ruby/vendor_ruby", "/opt/local/lib/ruby/1.8", "/opt/local/lib/ruby/1.8/i686-darwin10", "."]
'test_script' not found.
test_script!!
a/test_script!!

Rubyライブラリ読み込みの仕組みはとても単純です。

  1. $LOAD_PATH($:)を順に辿る
  2. 目的のファイルが見つかったら終了
  3. 最後まで来たらLoadError例外

C言語C++に慣れている人は、gccのインクルードパス(-Iオプション)と同じようなもの、と言うと分かりやすいかもしれません。

Rubyでは$LOAD_PATHを実行時に変更することが可能なため、C言語と比べて柔軟な動作をすることが出来ます。

require 'rubygems'すると、$LOAD_PATHにどのような変化があるか?

以下のプログラムを実行してみましょう。

# gemをrequireするとLOAD_PATHが変化する
# require 'rubywho' すると、 gems/rubywho-0.4.0/bin や gems/rubywho-0.4.0 が追加される
require 'rubygems'
require 'rubywho'
p $LOAD_PATH                    #=> ["/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/bin", "/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/lib", ...]

実行結果

$ ruby ./require_gem_test.rb
["/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/bin", "/opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/lib", "/opt/local/lib/ruby/site_ruby/1.8", "/opt/local/lib/ruby/site_ruby/1.8/i686-darwin10", "/opt/local/lib/ruby/site_ruby", "/opt/local/lib/ruby/vendor_ruby/1.8", "/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin10", "/opt/local/lib/ruby/vendor_ruby", "/opt/local/lib/ruby/1.8", "/opt/local/lib/ruby/1.8/i686-darwin10", "."]
  • /opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/bin
  • /opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/lib

の2つが$LOAD_PATHの先頭に追加されています。この状態で

require 'rubywho'

すると

  1. Ruby$LOAD_PATHを先頭から辿る
  2. /opt/local/lib/ruby/gems/1.8/gems/rubywho-0.4.0/lib/rubywho.rb が最初に見つかって読み込まれる。

つまりRubyGemsの仕事は「登録されているライブラリパスの中から適切なものを探してそれを$LOAD_PATHに追加する」ということのようです。

コードリーディングrubygems/lib/rubygems/custom_require.rb

RubyGemsでは Kernel#require を独自のものに置き換えることで上記の仕事をしているようです。

rubygems/lib/rubygems/custom_require.rb (コメントは日本語に訳してあります)

require 'rubygems'

module Kernel
  ##
  # RubyGemsがロードされる前のKernel#require

  alias gem_original_require require

  ##
  # RubyGemsがrequireされた時、Kernel#requireは、必要な時にgemを読み込む独自の実装に置き換えられます。
  #
  # <tt>require 'x'</tt> した時、以下のことが起きる:
  # * もし現在の$LOAD_PATHからファイルが読み込める時は読み込む
  # * 見つからなければ、インストールされたgemsからマッチするファイルがあるか探す。
  #   もし 'y' gem から見つかれば、そのgemをアクティベートする ($LOAD_PATHに追加する)
  #
  # 通常<tt>require</tt>の'ファイルがすでにロード済みの時にfalseを返す機能'は維持している。
  #
  def require(path) # :doc:
    gem_original_require path
  rescue LoadError => load_error
    if load_error.message =~ /#{Regexp.escape path}\z/ and
       spec = Gem.searcher.find(path) then
      Gem.activate(spec.name, "= #{spec.version}")
      gem_original_require path
    else
      raise load_error
    end
  end

  private :require
  private :gem_original_require

end
  1. gem_original_require で元々の require を呼べるようにする
  2. 最初に$LOAD_PATHを探して見つかればそのまま読み込む
  3. 見つからなければインストールされたgemsからマッチするファイルがあるか探して、見つかればアクティベートして$LOAD_PATHに追加する
  4. gem_original_require して読み込み完了

はじめに$LOAD_PATHから探すのが肝です。

あるgemから2つ以上のファイルをrequireする場合、2回目以降は$LOAD_PATHから読み込まれることになります(アクティベートされるのは最初の一回目だけです)。

通常requireの「ファイルがすでにロード済みの時にfalseを返す」機能も維持出来る、シンプルで素晴らしい実装ですね。

gem本体を書き換えずに特定のアプリ上でのみ挙動を変更するには?

自分のアプリケーションが特定のgemを利用している時、一部の挙動に問題があることがあります。もちろんgemに対してパッチを送るのがベストですが、そのパッチ棄却されたり取り込まれたバージョンがなかなかリリースされないこともあるはずです。そんな時はどのように対処すればいいのでしょうか?gem本体を書き換えずに特定のアプリ上でのみ挙動の一部を書き換えることは可能なのでしょうか?

もちろん$LOAD_PATHを利用すれば可能です!!

半年程前、Milkodeというアプリケーションを作っていた時にarchive-zipというgemzipファイルの展開に使うことにしました。gem自体は使いやすくて問題もなかったのですが、当時最新だった0.3ではRuby1.9で動かなかったことが問題でした(最新の0.4.0では修正済みです)。パッチを送ったもののすでに他のアプローチをとっていたため取り込まれず、Ruby1.9で動くバージョンが出るのも少し後になりそうでした。

そこで「1.9で動くパッチをmilkode内部でだけ当ててarchive-zipが対応したらそのパッチを外す」というアプローチをとることにしました。

まずは問題となったコードです。

lib/archive/support/io.rb at GitHub

require 'readbytes'

class IO
  # Returns +true+ if the seek method of this IO instance would succeed, +false+
  # otherwise.
  def seekable?
    begin
      pos
      true
    rescue SystemCallError
      false
    end
  end
end
require 'archive/support/io' 

というパスでrequireされます。

細かい説明は省略しますが'readbytes'というライブラリRuby1.9では無くなっているのが原因でした。色々と考えた結果、archive/support/io.rb が以下のようになればRuby1.8, 1.9 の両方で動くことが分かりました。

# -*- coding: utf-8 -*-
#
# @file
# @brief archive-zip/lib/archive/support/io.rb patch, removed in the future.
# @author ongaeshi
# @date 2011/08/04

begin
  require 'readbytes'
rescue LoadError
  # for Ruby 1.9.2
  class TruncatedDataError<IOError
    def initialize(mesg, data) # :nodoc:
      @data = data
      super(mesg)
    end

    # The read portion of an IO#readbytes attempt.
    attr_reader :data
  end
  
  class IO
    # Reads exactly +n+ bytes.
    #
    # If the data read is nil an EOFError is raised.
    #
    # If the data read is too short a TruncatedDataError is raised and the read
    # data is obtainable via its #data method.
    def readbytes(n)
      str = read(n)
      if str == nil
        raise EOFError, "End of file reached"
      end
      if str.size < n
        raise TruncatedDataError.new("data truncated", str)
      end
      str
    end
  end
end
  
class IO
  # Returns +true+ if the seek method of this IO instance would succeed, +false+
  # otherwise.
  def seekable?
    begin
      pos
      true
    rescue SystemCallError
      false
    end
  end
end

ここからはmilkode本体を書き換えていきます。

  1. milkode/vendor/archive/support/io.rb を追加します。
  2. milkode/lib/milkode/common/archive-zip.rb というファイルを作ります。
  3. require 'archive/zip' している箇所を require 'milkode/common/archive-zip' に置き換えます。

これで準備は終わりです!!

lib/common/archive-zip.rb

# -*- coding: utf-8 -*-
#
# @file
# @brief archive-zipがRuby1.9.2に対応するまでのパッチ
# @author ongaeshi
# @date 2011/08/04

$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../../../vendor')
require 'archive/zip'

File.join(File.dirname(__FILE__), 'path/to/dir')スクリプト位置からの相対パス絶対パスに変換するための定型文です。milkode/lib/milkode/common/../../../vendorなので、結果としてmilkode/vendorに変換されます。

milkode/vendor$LOAD_PATHの先頭に挿入されるため、

  1. milkode/vendor 以下にファイルが存在する場合は優先して読み込み
  2. 見つからなかった場合は通常の読み込み処理へ

となります(先ほどのRubyGemsの読み込みルールを思い出して下さい)。

今回の場合 milkode/vendor/archive/support/io.rb があるので、

require 'archive/support/io'

の時、vendor/以下のファイルが通常のgemコードよりも優先して読み込まれます。これで目的の動作です!!

gem本体のバージョンが上がりパッチを当てる必要がなくなったら$LOAD_PATH.unshiftの部分をコメントアウトしましょう。

Commit c727625e7ba114b72d5ae5b0290e610ae6bb5fd1 to ongaeshi/milkode - GitHub

# 0.4.0になったため、必要なくなった。
# $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../../../vendor')
require 'archive/zip'

Ruby Advent Calendar jp: 2011

この記事はRuby Advent Calendar jp: 2011の14日目の記事でした!!

よかったらこちらもどうぞ、面白い記事がたくさんあります。

参考文献