アペフチ

雑多にプログラミング、集中力の衰え

OgaのXPathの名前空間サポートの拡充

EPUB ParserというRubyGemを作っている。電子書籍用に使われるEPUBというファイルフォーマットを扱うライブラリーなのだけど、EPUBというのは雑に言って「XHTMLとCSSと画像とメタデータ用XMLファイルをZIPアーカイブした物」なので、内部でXMLを扱う必要がある。

XMLライブラリーは切り替え可能にしていて、今はRuby標準添付のREXMLの他にNokogiriをサポートしている。これに加えてOgaもサポートしたいなあと前々から思っていた。

最近、全然プログラミングを楽しむ時間が取れていなくてあまりにフラストレーションが溜まってしまったので、他にやることもあったのだけど、Ogaに取り組むことにしてしまった。具体的には、Ogaの方のImprove XPath namespace supportというイシューを解決しようというものだ。これができれば、EPUB Parserの方にOgaサポートを追加するのは簡単。

問題の特定や変更箇所は既にイシューページで議論されているので、後残っているのはOgaの構造を理解すること、それから、実際に手を動かしてパッチを書くことだ。ソースコードを眺めたり動かしてみたりしてYorick Peterseさんの提案するAPIを定義することはできたので、後はAPI経由で渡された名前空間エイリアスを使うところを実装すればいい、というところまで進んだ(未コミット)。

Fiddleでlibzip

EPUBというのは「XHTMLとCSSと画像とメタデータ用XMLファイルをZIPアーカイブした物」なので、EPUB ParserではZIPアーカイブを扱う必要がある。これも切り替え可能にしていて、ピュアRubyのArchive::ZipとCのlibzipのバインディングであるZip/Rubyをサポートしている。当然Zip/Rubyの方が速い。のだけど、長いこと開発が止まっているので、何とかしたいなあと思っていた。具体的には、自分でメンテナンスを引き継ぐか、別のlibzipバインディングを作るか、(Rustに興味あるので)RustのZIPクレートをRubyから使えるようにしてみるか(Rutieなどで)。

それとは別にRubyでFFIする時にRuby FFIばっかりが使われてFiddleが見向きもされないのが何だかなあと思っていたので、二番目の「別のlibzipバインディングを作る」をFiddleを使ってやってみようと思った。

とは言え、Cプログラミングが全くの初心者なので、ドキュメント読んだり何とかlibzipのAPIリファレンス見たりしてやってみてるのだけどうまくできてるのか自信がない。特にメモリー管理が適切なのか全然分からん。途中経過がこんな感じ。どうなんでしょう?

require "fiddle/import"

module Libzip
  CREATE = 1
  EXCL = 2
  CHECKCONS = 4
  TRUNCATE = 8
  RDONLY = 16

  module FL
    NOCASE = 1
    NODIR = 2
    COMPRESSED = 4
    UNCHANGED = 8
    RECOMPRESS = 16
    ENCRYPTED = 32
    ENC_GUESS = 0
    ENC_RAW = 64
    ENC_STRICT = 128
    LOCAL = 256
    CENTRAL = 512

    ENC_UTF_8 = 2048
    ENC_CP437 = 4096
    OVERWRITE = 8192
  end

  extend Fiddle::Importer
  dlload "libzip.so", "libzip.so.4"

  typealias "zip_flags_t", "uint64_t"
  typealias "zip_uint16_t", "uint64_t"
  typealias "zip_uint32_t", "uint64_t"
  typealias "zip_uint64_t", "uint64_t"
  typealias "zip_int64_t", "zip_uint64_t"

  extern "zip_t * zip_open(const char *path, int flags, int *errorp)"
  extern "int zip_close(zip_t *archive)"
  extern "zip_int64_t zip_get_num_entries(zip_t *archive, zip_flags_t flags)"
  extern "zip_file_t * zip_fopen_index(zip_t *archive, zip_uint64_t index, zip_flags_t flags)"
  extern "int zip_fclose(zip_file_t *file)"
  extern "void zip_stat_init(zip_stat_t *sb)"
  extern "int zip_stat_index(zip_t *archive, zip_uint64_t index, zip_flags_t flags, zip_stat_t *sb)"

  Stat = struct([
                  "zip_uint64_t valid",
                  "const char *name",
                  "zip_uint64_t index",
                  "zip_uint64_t size",
                  "zip_uint64_t comp_size",
                  # "time_t mtime",
                  "zip_uint32_t crc",
                  "zip_uint16_t comp_method",
                  "zip_uint16_t encryption_method",
                  "zip_uint32_t flags",
                ])
  class Stat
    NAME = 0x0001
    INDEX = 0x0002
    SIZE = 0x0004
    COMP_SIZE = 0x0008
    MTIME = 0x0010
    CRC = 0x0020
    COMP_METHOD = 0x0040
    ENCRYPTION_METHOD = 0x0080
    FLAGS = 0x0100
  end
end

errorp = 0
archive = Libzip.zip_open("book.zip", Libzip::RDONLY, errorp)
pp archive
pp num_entries = Libzip.zip_get_num_entries(archive, Libzip::FL::UNCHANGED)
0.upto num_entries - 1 do |index|
  file = Libzip.zip_fopen_index(archive, index, 0)
  stat = Libzip::Stat.malloc
  Libzip.zip_stat_init stat
  p Libzip.zip_stat_index(archive, index, Libzip::Stat::NAME|Libzip::Stat::SIZE, stat)
  p [index, file, stat.size, stat.name.to_s]
  p Libzip.zip_fclose(file)
end
pp Libzip.zip_close(archive)
pp archive
# pp Libzip.zip_close(archive) # => SEGV

FiddleはRuby標準添付のFFIライブラリーなので、追加gemをインストールする必要がない。おまけにWindowsでも動く。ので基本はこちらがいいんではないかなあ。内部ではFiddleを使いつつ、APIだけRuby FFIと同じにするためのFiddleyというgemもあるので、興味ある人はこれを使って移行を始めてもいいだろう。

Public Outbox

自分のトゥートとかブログポストとかのActivityPubのアクティビティを一か所に集めて、「他人がここを見れば自分のアクティビティが分かる」というようにできたらどうだろう、と考えたりしてた。ActivityPubのoutboxのうちパブリックな物のリスト、とも考えられるので、Public Outboxという名前はどうか。というようなことを考えたりしていた。何で作るのがいいんだろう。Rails?

因みに名前はpublic-inboxのもじり。

集中力の衰え

以前だったら、OgaのXPath名前空間拡充は、ここで手を止めずにもっと進めていたと思うのだけど、今はそれができなくなってしまっている。歳なのかなんなのか、集中力が衰えてしまっているなあと感じる。まずいまずい。