UserScriptにはまってるって話

Greasemonkeyの名前をよく聞いたころからすると、周回遅れ感がすごいけども。

  • Tampermonkey便利
    • おもむろにGistに貼って拡張子を.user.jsにするとRawクリックするだけでインストールできて超便利
  • 匿名関数のコメントを使ったヒアドキュメント便利。
  • @requireとcdnjs便利
    • 「このサイトjQuery使ってないんだけど」「@require jquery.min.js」
    • 「テンプレエンジン使いたい」「@require mustache.min.js」
    • 「便利なprototype拡張ほしい」「@require sugar.min.js」
  • Mustache便利
    • シンプルでいいと思います

何書いたの

最近オンゲやったりしてたのでWikiをごにょごにょするのとか、スタック・オーバーフローの翻訳に使うTransifexがらみとか。

Gist見るよろし。 https://gist.github.com/unarist

課題

  • GistにUserScriptが散らばるのお手軽だけど微妙
    • タグ付けとかないしなー
    • 増えてくるとまとめてインストールとかしたくなりそう?
  • インラインでCSS書くの微妙
    • jsfiddleな感じにUserScript書きたい
  • 思いつきで書くのでコードが汚い

メールの件名が一部文字化けする話をRFC2047からひも解く

「こういう長い件名を書くと途中で文字が2=$1$k」みたいに件名の途中から文字化けする問題に引っかかったので、じゃあどうエンコードするのが正解なの?というのを調べた。

なおencoded-wordとencoded-textはRFCに書かれてるやつです。

 encoded-word = "=?" charset "?" encoding "?" encoded-text "?="

BASE64における4文字のかたまり

3バイトを4文字に置き換えるBASE64みたいなやつで、その4文字のかたまりを分割してもいいかどうか。

A.それぞれのencoded-textは独立していなければならず、他のencoded-textから継続してはならない。

The 'encoded-text' in an 'encoded-word' must be self-contained; 'encoded-text' MUST NOT be continued from one 'encoded-word' to another. This implies that the 'encoded-text' portion of a "B" 'encoded-word' will be a multiple of 4 characters long; for a "Q" 'encoded-word', any "=" character that appears in the 'encoded-text' portion will be followed by two hexadecimal characters.

http://tools.ietf.org/html/rfc2047#section-5

PHPでmb系使うならBASE64エンコードと行分割は同時に行われるので普通ありえない。

ISO-2022-JPにおけるESC$Bみたいなモード切替

ISO-2022-JPなんかだとESC$Bとかで文字セットを切り替えるわけですが、切り替えたまま次のencoded-wordに行けるか、その状態が次のencoded-wordでも引き継がれるか。

A.encoded-word内で非ASCIIモードに切り替える場合、encoded-wordの末尾でASCIIモードに戻っているようにコントロールコードを含まなければならない。

Some character sets use code-switching techniques to switch between "ASCII mode" and other modes. If unencoded text in an 'encoded-word' contains a sequence which causes the charset interpreter to switch out of ASCII mode, it MUST contain additional control codes such that ASCII mode is again selected at the end of the 'encoded-word'. (This rule applies separately to each 'encoded-word', including adjacent 'encoded-word's within a single header field.)

http://tools.ietf.org/html/rfc2047#section-3

エンコード済みテキストを表示する際はASCIIモードであることが黙示され、encoded-wordを表示した後は確実にASCIIモードに戻っているようにしなければならない。

If the character set being used employs code-switching techniques, display of the encoded text implicitly begins in "ASCII mode". In addition, the mail reader must ensure that the output device is once again in "ASCII mode" after the 'encoded-word' is displayed.

http://tools.ietf.org/html/rfc2047#section-6.2

PHPでいうと、MIMEエンコードとは別に先にISO-2022-JPに変換しちゃって、mb_internal_encodingもISO-2022-JPでない、という時に起きうる。

<?php
mb_internal_encoding('utf-8');

// 先にエンコードしよう!
$input = 'なんか長い文字列(40バイトぐらい)';
$encoded = mb_convert_encoding($input, 'iso-2022-jp');
$mime_encoded = mb_encode_mimeheader($encoded, 'iso-2022-jp');

// 先にエンコードしよう!
$input = 'なんか長い件名(40バイトぐらい)';
$encoded = mb_convert_encoding($input, 'iso-2022-jp');
mb_send_mail('aaa@example.com', $encoded, 'msg');

ここで$encodedの文字コードとmb_internal_encodingが食い違っているので全体が化けそうなものだけど、ISO-2022-JPは大半がASCII文字と被ってるのでそのまま通ってしまう*1・・・。

MBCS

A.複数オクテットで構成される文字が複数のencoded-wordに分割されてはならない。

Each 'encoded-word' MUST represent an integral number of characters. A multi-octet character may not be split across adjacent 'encoded-word's.

http://tools.ietf.org/html/rfc2047#section-5

同上。

おまけ:一部のMUAがTo/Fromとかの引用符の中をエンコードしちゃう件

RFC822(古い?)から関連する構文を抜き出すとこんな感じ。

  ; from §4.1
authentic   =   "From"       ":"   mailbox  ; Single author
            / ( "Sender"     ":"   mailbox  ; Actual submittor
                "From"       ":" 1#mailbox) ; Multiple authors or not sender
destination =  "To"          ":" 1#address  ; Primary
            /  "Resent-To"   ":" 1#address
            /  "cc"          ":" 1#address  ; Secondary
            /  "Resent-cc"   ":" 1#address
            /  "bcc"         ":"  #address  ; Blind carbon
            /  "Resent-bcc"  ":"  #address

  ; from §6.1
address     =  mailbox                      ; one addressee
            /  group                        ; named list
group       =  phrase ":" [#mailbox] ";"
mailbox     =  addr-spec                    ; simple address
            /  phrase route-addr            ; name & addr-spec

  ; from §3.3
atom        =  1*<any CHAR except specials, SPACE and CTLs>
quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or quoted chars.
phrase      =  1*word                       ; Sequence of words
word        =  atom / quoted-string

要するに、あの名前の部分はphrase=atom/quoted-stringの羅列。で、RFC2047に戻ると、確かにquoted-stringの中には使えないと書かれている。

These are the ONLY locations where an 'encoded-word' may appear. In particular:
(略)
+ An 'encoded-word' MUST NOT appear within a 'quoted-string'.

http://tools.ietf.org/html/rfc2047#section-5

じゃあencoded-wordは名前として使えないのかと言うと、encoded-wordはphraseに含まれるwordの代わりとして使える、とある。

(3) As a replacement for a 'word' entity within a 'phrase', for example,
one that precedes an address in a From, To, or Cc header. The ABNF
definition for 'phrase' from RFC 822 thus becomes:

phrase = 1*( encoded-word / word )

http://tools.ietf.org/html/rfc2047#section-5

つまり、引用符で囲まなければいいんだろうか。あるいは引用符ごとエンコードすればいいんだろうか。

RFC2822でも同じような定義だった(§3.4)ので、そのうち書き直そう。

おまけ:一行の文字数

An 'encoded-word' may not be more than 75 characters long, including 'charset', 'encoding', 'encoded-text', and delimiters. If it is desirable to encode more text than will fit in an 'encoded-word' of 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may be used.
While there is no limit to the length of a multiple-line header field, each line of a header field that contains one or more 'encoded-word's is limited to 76 characters.

http://tools.ietf.org/html/rfc2047#section-2

encoded-word自体の制限としては75文字?だとしてもメール側の標準で文字数制限あるんでしょ?と思ったら、RFC822では65〜72文字だと困らないよねぐらいのふわっとした書き方で、RFC2822になって明言されていた。

There are two limits that this standard places on the number of characters in a line. Each line of characters MUST be no more than 998 characters, and SHOULD be no more than 78 characters, excluding the CRLF.

http://tools.ietf.org/html/rfc2822#section-2.1.1

というわけで、SHOULDに従うなら「Subject: 」みたいな部分の長さも考慮する必要がありますね。mb_encode_mimeheaderの$indentとか。

PHP向けのまとめ

mb_encode_mimeheaderが第二引数に指定した文字コードに変換してくれる、とだけ考えればよほど失敗しないはず。
ちなみに$charsetを省略するとmb_languageの設定に基づいて$charsetと$transfer_encodingが決まる。
http://ideone.com/KtgtZD
https://github.com/php/php-src/blob/0e30c543ec8e6c371e0aef6e125e7b90f4b1b790/ext/mbstring/mbstring.c#L3382

あたりがハマりポイントなのかなあと。

WindowsのShellAPIでサムネ取得

http://ja.stackoverflow.com/a/6237/8000について調べた結果を供養。

IDLIST

SHGetDesktopFolderで取得したIShellFolderでフルパスをParseDisplayNameさせると、IDLIST_RELATIVEが返ってくる。中身を軽く覗いた感じだと、「ドライブ名」「子名」「孫名」「ひ孫のno」みたいな。末端だけ数値という謎。
でこれをILFindLastIDしたり、デスクトップの代わりに親ディレクトリで取得したりすると末端ノードだけが手に入る。これがITEMID_CHILD。
http://pg-torch-ic.jugem.jp/?eid=31
アイテムIDリストとは?

GetUIObjectOf

UIObjectってあたりで薄々そんな気はしていたのだけど、どうもあるノードに関するインターフェイスは親ディレクトリが提供する物のようだ。ところでUIObjectに渡すのはIDLISTだと思っていたので、じゃあcidlはIDLISTの深さでも指定するんだろうと踏んでいた。IDLISTの一部だけ読めるようにとか。
改めてシグニチャを見ると、PCUITEMID_CHILD_ARRAYとなっている。

The address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies a file object or subfolder relative to the parent folder. Each item identifier list must contain exactly one SHITEMID structure followed by a terminating zero.

IShellFolder::GetUIObjectOf (shobjidl_core.h) | Microsoft Docs

つまり、ITEMID_CHILDの配列だったわけだ。その要素数を指定するためのcidl。
複数要素のUIObjectってなんだろう?というのも書いてあって、IContextMenuとかIDataObjectとか。なるほど。

ここまでをまとめると、なんとかして親ディレクトリのIShellFolderとITEMID_CHILDを手に入れて、そこからIExtractImageやらIExtractIconやらIThumbnailProviderを取り出し、サムネを生成することになる。ってのがIExtractIconのページに書いてあるし。
IExtractIconA (shlobj_core.h) | Microsoft Docs

サムネ生成色々

IExtractImage
2000で登場。GetLocationでパラメータを指定して、Extractを呼ぶという構成。GetLocationはパスも返すらしいが、特に使わないようで謎。
IExtractIcon
XPで登場。IExtractImage同様にGetLocation+Extractという構成だが、GetLocationの結果をExtractに指定することになっている。
IThumbnailProvider
Vistaで登場。IExtractImageを置き換えるものらしい。ただクライアントアプリでは後述するIShellItemImageFactoryを使えとある。

ちなみにIExtractImage2というインターフェイスもある。これはサムネの更新日時を取得するメソッドだけが入っていて、キャッシュに活用するんだそうな。一般市民が使うものじゃないよって書いてあるけども。
IExtractImage::GetLocationにはIEIFLAG_ASYNCというフラグがあり、これを使うと非同期にサムネを生成させられるらしい。が、この辺の情報がこれまた出てこないのなんの。

IThumbnailProvider

IThumbnailProviderはInitializeWith**というインターフェイスもいくつか実装していて、「何の」サムネを生成するかというのをこれで指定する。のだけれど、実際にはクライアントアプリでその初期化を行う必要はないらしい。
http://eternalwindows.jp/shell/shellname/shellname05.html
IThumbnailProviderを差し込みやすくなるとか、Preview周りと共通化を図れるとか、なんかそういう感じですかねえ。流行りの疎結合
実際には同じくVistaで増えたIThumbnailCache、さらにはIShellItemImageFactoryを使うとより簡単便利。

IShellItem

SHCreateItem系で得られるIShellItemとはなんなのか。
SHCreateShellItem function (shlobj_core.h) | Microsoft Docs
IShellItem (shobjidl_core.h) | Microsoft Docs
この辺を見た感じだと、IShellFolderとPIDLIST_CHILDを組み合わせた高レベルAPIに見える。
それからIShellItemImageFactoryも。
IShellItemImageFactory (shobjidl_core.h) | Microsoft Docs
今のところIShellItemImageFactory=(IThumbailCache(IThumbnailProvicer|IExtractImage)|IExtractIcon)ってところかな。

iPod touchの掃除をした

パソコンの調子が悪い時は再インストールすれば直ると思ってます。そんな感じ。
まあiOS7すら入らない第四世代なのでそろそろきついとは思ってる。

バックアップ

  1. App本体をバックアップするために、一度iTunesからAppを全削除して「購入した項目を○○から転送」。バックアップ時の質問にはいと答えればそれでよかったのかもしれない。
  2. バックアップの邪魔なのでついでにボイスメモと写真を全部PCに移動、削除
  3. バックアップを作成

初期化

  1. iTunesから「iPodを復元」(6.1.6だそうな)
  2. iPod側にプログレスバーが出るのでしばらく待つ
  3. iTunesリカバリモードのiPodを見つけました。iTunesでご利用になる前に、このiPodを復元する必要があります。」!?
  4. iPod側にプログレスバーが出るのでしばらく待つ
  5. 「新しいiPodへようこそ」ほっ

復元

  1. バックアップから復元を実行
  2. パスワードが違うと怒られるので必死に思い出す
  3. 数分待つ。短いなーと思いつつも、iTunes全く使えないので暇。
  4. 勝手にiPodが再起動する
  5. せっかちさんがホーム画面を見るとまっさらで、復元できてないじゃん!と思い込む
  6. 勝手に同期が行われる。バックアップし忘れたAppがあったらしく、インストールできなかったよと怒られる。
  7. 待つ。普通の同期なのでiTunesは使えるけど超長い。ひょっとしてAppStoreから直接入れたほうが早いのでは・・・とか考えるのはよそう。
  8. 20GB分を1hかけて同期完了

結果

手さぐりでやるもんだからやたら時間かかった。
空き領域は大幅に増えたものの、多分写真消したのが大きい。
スペックは変わらないので当然処理速度が大幅に上がる、なんてことはないのだけれど、少しでもアプリが落ちにくくなっているといいなあ。

F#で暗黙の型変換とか Member Constraints とか

「暗黙の型変換を明示的に呼び出す」ってなんかアレですね。

C#で書かれたライブラリを使っていると、暗黙の型変換しか用意されてないケースがあったりします。SharpDX.DrawingSizeとか。

ところがアップキャストすら暗黙には行わない世界ですから、これもやってくれません。演算子も用意されていません。

でこの記事。

Is there an equivalent to creating a C# implicit operator in F#? - Stack Overflow

let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b) x)

//その上で例えば
let a : System.Nullable<int> = (!> 1)

この呪文、「^aもしくは^bにあるop_Implicit(ry)をxに適用する」というだけです。なので

let a : System.Nullable<int> =
  System.Nullable<int>.op_Implicit 1

とかでもいいわけです。

深堀り

上の呪文は規格で言うところの「Member Constraint Invocation Expressions (§6.4.8)」というやつで、次のような構文を持つ式です。

(static-typars : (member-sig) expr)

よく似た構文に、「Member Constraints (§5.2.3)」があります。

static-typars : (member-sig)

似てるというかなんというか。

規格書から引用するとこういうコード。F#のソースから探したら、/src/fsharp/FSharp.Core/prim-types.fsiにありました。

val inline (+) : ^a -> ^b -> ^c
 when (^a or ^b) : (static member (+) : ^a * ^b -> ^c)

でそんな制約が書けるなら、§6.4.8のサンプルを改造して、こんなことができるんじゃないかと。

let inline speak (a: ^a when ^a : (member Speak: unit -> string)) =
 let x = a.Speak()
 printfn "It said: %s" x
type Duck() =
 member x.Speak() = "I'm a duck"
type Dog() =
 member x.Speak() = "I'm a dog"
let x = new Duck()
let y = new Dog()
speak x
speak y

思ったんですが無理でしたね。「このプログラムの場所の前方にある情報に基づく不確定の型のオブジェクトに対する参照です。」と怒られました。

StackOverflowで探してみると、メンバ制約呼び出し式を使えば呼べるけど、それよりinterfaceにしろよ、といった回答が。

f# - Generic type constraints and duck typing - Stack Overflow

まあMSDN Libraryにも"which is used to implement operator overloading for arithmetic operators"って書かれているので、黒魔術と思っておいた方がいいのかもしれません。

余談

素のMarkdownの貧弱さも大概ですが、はてな記法もつらいところがあります。F#のシンタックスハイライトできないからOcamlって誤魔化したり。GithubもQiitaもMarkdownだしなー。

そろそろはてなブログに移行したい。

neocompleteでインテリセンスみたいなのをやりたかった

矢印キーの話。Vimなんだから矢印キーでカーソル移動するなよ、そんなにやりたきゃE○acs使えよって怒られそうですが。

初期状態だと、neocompleteは次のような場面で補完を開始しようとします。

  • インサートモードに移行したとき
  • インサートモードでカーソルが移動したとき(文字入力に由来するものも含む)

このため、インサートモード中にカーソルを移動すると、既存のコードを通り過ぎる時にも補完を開始してしまいます。おまけに上下キーは候補選択にも使うので、そこでカーソル移動できなくなってしまいます。結構うっとうしい。

で公式にもいくつかの方法が提示されています。

矢印キーをinoremapして、ポップアップ消したうえでカーソル移動するようにする
カーソル移動ができない件は解決しますが、補完自体は発生しますし、候補を上下キーで選択できなくなります。
カーソルが一定時間動かないときに発生するCursorHoldIイベントを使う
一度カーソルを止めてしまうと同じ状態になりますし、後述するバックスペース問題があります。文字入力による補完開始にもラグが発生しますが、これは好みですね。
文字入力時に発生するInsertCharPreイベントを使う
カーソル移動で補完が始まらない、候補選択も上下キーでできる、となかなかいいことづくめですが、バックスペース問題があります。

バックスペース問題と言ったのは、バックスペースを押した時に補完続行できないこと。どうやらInsertCharPreではバックスペースを取得できず、カーソルの後退を検知しているみたいで。CursorHoldIは・・・なんでだろ。

https://github.com/Shougo/neocomplete.vim/issues/106

に「文字消して再度補完」ってのを割り当てられないかなーと探ったものの、自動補完と同じ挙動を再現するのが難しいのと、neocompleteで補完してる時以外のことを考えるのが面倒になり断念。上のIssueの最後に書いてある例でも時々上下移動で補完起動することがあって、ぐぬぬ

neocompleteの補完が消えた後に行補完モードになることがあるのも気になるなあ。。
https://github.com/Shougo/neocomplete.vim/issues/333 なるほど。