ぼろぼろ平原

困った

Rubyで大きいファイルを読み込む時に進捗を表示

f:id:tatzyr:20160713144219g:plain

進捗を表示するためには、ファイル全体の行数かファイルサイズが必要になる。

ファイルの行数を取得するにはファイルを一度全部読み込む必要があり、大きいファイルだと時間がかかるのでダメ。

一方、ファイルのサイズはFileTest.sizeを使えば一瞬で取得できる。 また、IO#posを使うとファイルポインタの現在の位置が取得できるので、現在の進捗が計算できる。

プログレスバーの表示には ruby-progressbar というgemを使う。

ruby-progressbarのインストール

$ gem install ruby-progressbar

進捗を表示するプログラム

require "ruby-progressbar"

# 大きいファイルのファイル名
filename = "sugoku_dekai_file.txt"

# ファイルのサイズを取得しProgressBarを作成
progressbar = ProgressBar.create(total: FileTest.size(filename))

# ファイルを開いて行ごとに処理
open(filename) do |f|
  f.each_line do |line|
    # lineに対してなにか処理
    foo(line)

    # 進捗を更新
    progressbar.progress = f.pos
  end
end

# 進捗を100%にする
progressbar.finish

RubyでTimeオブジェクトを"正しい"JSONに出力する

TL;DR: JSONは日時の表現方法を定めていないので好きにして良い。ただし、JavaScriptYAMLは定めているので迷ったらそれに従う。

JSONの日時の表記

JSONでは以下のデータ型しか扱えないので、日時は表現できない。

  • string
  • number
  • object
  • array
  • true
  • false
  • null

名前が紛らわしいが、「object」というのはRubyでいうHashに相当するもの(keyとvalueのペア)なので、RubyのObjectとは別物。

RubyJSONライブラリでの日時型の扱い

Rubyには複数のJSONライブラリがある。

それらで日時t = Time.parse("2016-02-21T06:11:12.110028+09:00")JSONに変換した時の結果は以下のようになる。 *1

参考までに、YAML.dumpTime#iso8601などの結果も載せている。

表記 結果
JSON.dump(t) "\"2016-02-21 06:11:12 +0900\""
Oj.dump(t) "{\"^t\":1456002672.110028000e32400}"
Oj.dump(t, mode: :compat) "1456002672.110028000e32400"
Oj.dump(t, mode: :compat, time_format: :unix) "1456002672.110028000"
Oj.dump(t, mode: :compat, time_format: :xmlschema) "\"2016-02-21T06:11:12.110028000+09:00\""
Oj.dump(t, mode: :compat, time_format: :ruby) "\"2016-02-21 06:11:12 +0900\""
t.to_s "2016-02-21 06:11:12 +0900"
t.iso8601 "2016-02-21T06:11:12+09:00"
t.iso8601(6) "2016-02-21T06:11:12.110028+09:00"

ライブラリやオプションの違いによって結果がバラバラになる。

JSONを復号した時に正しくTimeクラスになるのはOj.dump(t)の時だけで、JSON.dump(t)は復号するとStringクラス、Oj.dump(t, mode: :compat)BigDecimalクラスになる。

RubyYAMLライブラリでの日時の扱い

JSONとは異なり、YAMLでは日時の表記を定めている *2

基本的にはISO8601のサブセットで、スペースの有無、大文字小文字、小数点以下の桁数などは任意。

日時t = Time.parse("2016-02-21T06:11:12.110028+09:00")YAMLに変換した時の結果を示す。

表記 結果
YAML.dump(t) "2016-02-21 06:11:12.110028000 +09:00"

日時部分だけ抜粋した。

仕様にはスペース無し、Tは大文字、タイムゾーンUTCが正式(canonical)だと記載されているが、RubyYAMLライブラリは違う形式を出力する。

JavaScriptのtoJSON()での日時の扱い

JavaScriptでは、Date.prototype.toJSON()Date.prototype.toISOString()を使って日時をJSONに変換する。

toISOString()は常に24文字長のYYYY-MM-DDTHH:mm:ss.sssZという形式になる。

タイムゾーンは、接尾辞Zで表記されているように、常にUTCオフセットになる。

日時var d = new Date("2016-02-21T06:11:12.110028+09:00")JSONに変換した時の結果を示す。

表記 結果
d.toJSON() "2016-02-20T21:11:12.110Z"

結論

1. 復号した時に元のTimeクラスに戻ってほしいとき

require "oj"

Oj.dump(t) # => "{\"^t\":1456002672.110028000e32400}"
Oj.load(Oj.dump(t)).class # => Time

2. JavaScriptJSONとの互換性が必要な時

秒の小数第4位以下とタイムゾーンの情報は失われる。

require "oj"

class Time
  def to_json
    '"' + utc.iso8601(3) + '"'
  end
end

Oj.dump(t, mode: :compat) #=> "\"2016-02-20T21:11:12.110Z\""

3. YAMLとの互換性が必要な時

require "oj"

class Time
  def to_json
    '"' +  iso8601(9) "'"
  end
end

Oj.dump(t, mode: :compat) #=> "2016-02-21T06:11:12.110028000+09:00"

参考文献

*1:JSONライブラリをrequrieするとTime#to_jsonが上書きされるのでOj.dumpの結果がこの表と変わる

*2:working draft

ぼっちWikiに最適なGollumについてのメモ

自分だけが使うぼっちWikiGollumで作成したときのメモ。

GollumはデータベースにGitを使い、Markdownで書けるWiki

Gollumの特徴は以下の通り。

  • シンタックスハイライト数式に対応している
  • 様々な記法で書ける
  • gemコマンドで簡単にインストールできる
  • データベースとしてGitを使うのでMySQLなどが不要
  • RubySinatraで実装されている
  • Rubyでマクロを書いて拡張できる

欠点は、日本語の扱いに気をつける必要があること(後述)。

Gollumのインストール

ICUという文字コード変換ライブラリに依存しているのでまずはそれをインストール。

# OS Xの場合
$ brew install icu4c

# Debian/Ubuntuの場合
$ sudo apt-get install libicu-dev

続いて、Gollum本体をインストール。

$ gem install github-markdown gollum

日本語の扱いについて

ページのタイトルに漢字を使うと、URLがピンイン(中国語のローマ字のようなもの)になってしまう。 タイトルは適当に英語で入力し、真の日本語タイトルは本文中でMarkdownの#を使って書けば問題ない。

認証の設定

ぼっちWikiなので、他人に内容を見られてしまっては困る。

Gollumは普通のSinatraアプリと同じように、ユーザー認証をつけることができる。

こんな感じのauth.rbを作成。

module Precious
  class App < Sinatra::Base
    use Rack::Auth::Basic, "Restricted Area" do |username, password|
      [username, password] == ["username", "password"]
    end
  end
end

Basic認証&ファイルにパスワード直書きなので、盗聴やパスワード漏洩の可能性がある。あくまでも気休め。

Gollumの起動

$ git init # 初回だけ
$ gollum --mathjax --config auth.rb --allow-uploads

他人の全ツイートをJSONで一括保存するコマンドを作った

Twitterには、自分の全ツイートアーカイブをダウンロードできる機能があるが、他人のツイートはダウンロードできない。

support.twitter.com

そこで、他人のツイートをダウンロードするalltweetsというコマンドを作った(もちろん自分のツイートもダウンロードできる)。

許可されていれば、非公開アカウントのツイートもダウンロードできる。

インストール

gemコマンドでインストールできる。Macの場合はsudoが必要。

$ gem install alltweets

使い方

結果は標準出力に表示される。

$ alltweets tatzyr    # tatzyrのツイートをJSON形式でダウンロード

$ alltweets -y tatzyr # tatzyrのツイートをYAML形式でダウンロード

$ alltweets -r tatzyr # tatzyrのツイートをリツイートも含めてJSON形式でダウンロード

$ alltweets -r -y tatzyr # tatzyrのツイートをリツイートも含めてYAML形式でダウンロード

github.com

RubyGemsとGitHubに公開したgemの名前を変更する手順

前提

  • gemをGitHubRubyGemsに公開したけど、あとから名前を変えたくなった
  • bundle gemコマンドでgemを作成した
  • oldnameという名前のgemをnewnameという名前にしたい

GitHubリポジトリ名を変更する

oldnameのリポジトリ → Settings → Repository name にnewnameと入力してRename

oldname gem使用者向けに警告を表示する

oldnameというブランチをmasterから作り、そこで作業する。

$ git checkout master
$ git checkout -b oldname

以下のコードをoldname gemのどこかに書く(colorizeライブラリなどで文字色を変更して目立たせると気づかれやすい)。

warn "[DEPRECATION] This gem has been renamed to `newname` and will no longer be supported. Please switch to `newname` as soon as possible."

次に、以下のコードをoldname.gemspecに追加する。するとoldname gemのアップデート時にこのメッセージが出る。

spec.post_install_message = <<-MESSAGE
 ! The 'oldname' gem has been deprecated and has been replaced by 'newname'.
 ! See: https://rubygems.org/gems/newname
 ! And: https://github.com/YourID/newname
MESSAGE

最後にtinyバージョンを1つ上げる。

lib/oldname/version.rb

module OldName
  VERSION = "0.1.1" # 0.0.1上げる
end

newname gemの設定

newnameというブランチをmasterから作り、そこで作業する。

$ git checkout master
$ git checkout -b newname

まずはコード中のoldnameOldNameをすべてnewnameNewNameに置換する。ファイルやディレクトリ名のoldnamenewnameに置換する。その時は、mvコマンドではなくgit mvコマンドを使う。

$ git mv oldname.gemspec newname.gemspec
$ git mv lib/oldname/ lib/newname/
(省略)

newname.gemspecdescriptionsummaryに以下の文を追加する(任意)。

Formerly known as 'oldname'.

gemのリリース

まずはoldnameをRubyGemsにリリースする。masterでないブランチでreleaseするので、set-upstream(-u)する必要がある。

$ git checkout oldname
$ git push -u origin oldname
$ bundle exec rake release

次にnewnameをRubyGemsにリリースする。

$ git checkout newname
$ git push -u origin newname
$ bundle exec rake release

masterブランチの変更

masterブランチをnewnameブランチと同じにする。

$ git checkout newname
$ git branch -d master
$ git branch master

最後にGitHubにmasterをpushして完了。

$ git checkout master
$ git push -u origin master -f

Twitterのふぁぼを寿司にするChrome拡張作った

f:id:tatzyr:20151104190122p:plain:w640

Twitterのふぁぼがハートアイコンの「いいね」に変わった。

Twitterにハートが登場しました | Twitter Blogs

そういう事情なのでふぁぼを?にするChrome拡張を作ったらやたら「いいね」された。

ソースコードGitHubで公開したが、こっちは0 Starなのが大変厳しい。

github.com

GitHubのStarも?に置き換えるChrome拡張を作ったらStarを付けてもらえるだろうか。

iPhoneのカレンダーをGoogle Calendarに移行する方法

f:id:tatzyr:20151030202735p:plain:w171

iPhoneiPadのカレンダーをGoogleカレンダーに移行する方法。同期ではなく移行

iPhoneからAndroidにカレンダーを引っ越したいときや、Googleカレンダーと連携するアプリをiPhoneで使いたいならこの方法で移行するのがおすすめ。

手順

  1. iCloudカレンダー にサインイン

  2. サイドバーのアイコン > パブリックカレンダー をクリックし、カレンダーを共有する

    f:id:tatzyr:20151029133200p:plain:w289 f:id:tatzyr:20151029133210p:plain:w430

  3. 共有したカレンダーの URL をコピーし、ブラウザのアドレスフィールドに貼り付ける(まだreturnキーは押さない)

  4. webcalhttpに変更してからreturnキーを押す。すると、ICS ファイルがダウンロードされる

  5. パブリックカレンダーのチェックを外し、カレンダーの一般共有を停止する

  6. Google Calendarにログイン

  7. マイカレンダー > ▼ > 新しいカレンダー をクリックし同名のカレンダーを作成する

    f:id:tatzyr:20151030200437p:plain:w437

  8. 右上の設定アイコン > 設定 >「 カレンダー」タブ > カレンダーをインポートをクリックする

    f:id:tatzyr:20151029144652p:plain:w183 f:id:tatzyr:20151029144449p:plain:w464

  9. 「ファイルを選択」ボタンでダウンロードしたICSファイルを選択する

  10. 「カレンダー」セレクトボックスで、先ほど作成した同名のカレンダーを選択する

    f:id:tatzyr:20151030195238p:plain:w539

  11. インポートをクリック

これでiCloudカレンダーのGoogle Calendar移行は完了。