listingsの言語を追加する

めっちゃブログサボってます。

TeXlistingsは便利ですよね。 TeX文書内にソースコードシンタックスハイライト付きで よしなに表示するためのパッケージです。 情報系の学生御用達のパッケージですよね。

ですが、わりと標準で対応してる言語が少ないようにも思います。 以前、Schemeを使って関数プログラミングの思想を学ぶ感じの講義で レポートを書くときに、対応してないじゃん、ってなった記憶があります。

もちろん、そういうことにはちゃんと対応していて、 言語設定を追加することができます。 上記の講義レポートに関してはググッて出てきた設定を使うだけで十分だったのですが、 ちょっと自分でそういう設定を書かなきゃいけなかったので、 備忘録代わりに書いておきます。

……まあ、マニュアル読めば分かるんですけど、 毎回英語ドキュメント読むのもかったるいし、 日本語文献少ない気がするのでこういうこと書いていてもいいかなあという感じで 雑に書きます。

概形

lstdefinelanguageというものを使って定義します。

\lstdefinelanguage{言語名}{
  キー名=値,
  キー名=値,
  キー名=値,
  ...
}

最初の引数に言語名(後でソースコード貼り付けるときに使う)を設定して、 次の引数に色々と設定を書いていきます。 なんか言語名の前にオプションを設定できるみたいなんですけど、 よくわからないので飛ばします。

キーワードの追加

morekeywordsというキーに値を設定していきます。 例えばJavaScriptを例にするとこんな感じ。*1

\lstdefinelanguage{javascript}{
  morekeywords = [1]{ %keywords
    await, break, case, catch, class, const, continue, debugger, default, delete, 
    do, else, enum, export, extends, finally, for, function, function*, if, implements, import, in, 
    instanceof, interface, let, new, package, private, protected, public, return, static, super,
    switch, this, throw, try, typeof, var, void, while, with, yield, yield*
  },
  morekeywords = [2]{ %literal
    false, Infinity, NaN, null, true, undefined
  },
  morekeywords = [3] { %Classes
    Array, ArrayBuffer, Boolean, DataView, Date, Error, EvalError, Float32Array, Float64Array,
    Function, Generator, GeneratorFunction, Int16Array, Int32Array, Int8Array, InternalError,
    JSON, Map, Math, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect,
    RegExp, Set, String, Symbol, SyntaxError, TypeError, URIError, Uint16Array, Uint32Array,
    Uint8Array, Uint8ClampedArray, WeakMap, WeakSet
  },
  sensitive = true
}

{}の中にカンマ区切りでキーワードを追加していきます。 [1]などのオプションは後でシンタックスハイライトなどの設定をするときに スタイルを別に付けることを可能にするためにつけておくと嬉しいかもです。

本当はkeywordsというキーにキーワードを設定して、 morekeywordsでキーワードを追加、deletekeywordsでキーワードを削除 とやるようなんですが、別の言語設定をベースに言語設定を定義する際にkeywordsを使われていると ベース言語のkeywordsの設定がぶっ飛ばされるのかなんかでこういう使われ方している方が多いみたいです。

sensitiveっていうのをtrueにしておくと、 大文字小文字を区別してくれます。

使える文字について

listingsの中ではアルファベットはletter、数字はdigitというように文字がクラス分けされていて、 通常ではletterとdigitの組み合わせで成り立つキーワードしか追加できません(リストに入れてても無視される)。 CSSfont-heightのように記号を含むようなキーワードを追加したいときは

  alsoletter = {-},

のようにalso<class>というキーに記号を追加すれば大丈夫っぽいです。

文字列・コメントの設定

文字列・コメントの設定もキーワード同様、morestringmorecommentsを使って定義します。

  morecomment = [l]{//},
  morecomment = [s]{/*}{*/},
  morestring = [b]{"},
  morestring = [b]{'},

morecomments[l]{<pattern>}は行コメント、 morecomments[s]{<begin>}{<end>}複数行コメントです。 似たようなものにmorecomments[n]{<begin>}{<end>}というものもあって、 こちらはネスト可能な複数行コメントらしいです。

morestringの方は文字列として表現するために囲むときの記号を指定します。 オプションはその記号を文字列に含む際の方法を指定するもので、 [b]はバックスラッシュでエスケープ、[d]は2つ続けるとエスケープされます。

JavaScriptの完成形

以上の設定を組み合わせるとそこそこうまくいきそうなJavaScriptの設定になります。

\lstdefinelanguage{javascript}{
  morekeywords = [1]{ %keywords
    await, break, case, catch, class, const, continue, debugger, default, delete, 
    do, else, enum, export, extends, finally, for, function, function*, if, implements, import, in, 
    instanceof, interface, let, new, package, private, protected, public, return, static, super,
    switch, this, throw, try, typeof, var, void, while, with, yield, yield*
  },
  morekeywords = [2]{ %literal
    false, Infinity, NaN, null, true, undefined
  },
  morekeywords = [3] { %Classes
    Array, ArrayBuffer, Boolean, DataView, Date, Error, EvalError, Float32Array, Float64Array,
    Function, Generator, GeneratorFunction, Int16Array, Int32Array, Int8Array, InternalError,
    JSON, Map, Math, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect,
    RegExp, Set, String, Symbol, SyntaxError, TypeError, URIError, Uint16Array, Uint32Array,
    Uint8Array, Uint8ClampedArray, WeakMap, WeakSet
  },
  morecomment = [l]{//},
  morecomment = [s]{/*}{*/},
  morestring = [b]{"},
  morestring = [b]{'},
  alsodigit = {-},
  sensitive = true
}

使ってみる

オプション付きでキーワードを追加すると、色んなスタイルが適応できてイケです。

\documentclass[a4j]{jsarticle}
\usepackage[dvipdfmx]{color} %dvipdfmxを使わない人はよしなに変更すべし
\usepackage{listings}

% solarized
\definecolor{base}{gray}{0} %black
\definecolor{comment}{rgb}{0.52,0.60,0.00} %green
\definecolor{string}{rgb}{0.83,0.21,0.51} %magenta
\definecolor{keyword1}{rgb}{0.15,0.55,0.82} %blue
\definecolor{keyword2}{rgb}{0.80,0.29,0.09} %orange
\definecolor{keyword3}{rgb}{0.71,0.54,0.00} %yellow
\definecolor{keyword4}{rgb}{0.42,0.44,0.77} %violet[f:id:e8l:20151129232557p:plain][f:id:e8l:20151129232557p:plain][f:id:e8l:20151129232557p:plain]

% 上記コードのJavaScriptの言語設定がされてる

\lstset
{
    basicstyle={\ttfamily\color{base}\scriptsize},%コードの基本書式
    keywordstyle=[1]{\color{keyword1}\textbf},%キーワード1のスタイル
    keywordstyle=[2]{\color{keyword2}\textbf},%キーワード2のスタイル
    keywordstyle=[3]{\color{keyword3}\textbf},%キーワード3のスタイル
    keywordstyle=[4]{\color{keyword4}\textbf},%キーワード4のスタイル
    commentstyle={\gtfamily\scriptsize\color{comment}},%コメントのスタイル
    stringstyle={\gtfamily\scriptsize\color{string}},%文字列のスタイル
    numbers=left,%行番号は左
    stepnumber=1,%一行ずつ行番号をふる
    numberstyle={\sffamily\scriptsize},%行番号の書式
    xleftmargin=0zw, %左余白
    xrightmargin=0zw,%右余白
    tabsize=4,%タブの空白数
    frame=single,%フレームの書式
    frameround=tttt,%角を丸めるかどうか tで丸める
    breaklines=true,%長くなったら途中で改行
    captionpos=b,%タイトルの位置
    breakindent=10pt,%改行されたときの送り幅
    showstringspaces=false,%文字列中の半角スペースを表示させない
    lineskip=-1pt%通常の文章より行送りを狭くする
}

\begin{document}
\begin{figure}[h]
  \centering
  \begin{lstlisting}[language=javascript]
  // comment 1
  /* comment
       style 2
  */
  var hoge = 1;
  var x = undefined;
  var str = "string";
  var str2 = 'str';
  var cls = new Map();
  \end{lstlisting}
\end{figure}
\end{document}

f:id:e8l:20151129232557p:plain

もっとスゴイ設定とかできるならぜひとも教えて下さい。

*1:実はJavaScriptについて設定を作る必要があったので、そのとき作ったものをそのままサンプルに使っているのは内緒だぞ

Javaにやられた話

今、ボスが受け持っている実験のTAをやっています。 Javaでアレコレやる実験なのですが、 受講している学生から質問(ヘルプ)を受けたにも関わらず 解決に導くことができなかったので、 反省と後学のためにメモしておきます。

今日やられた内容は、String.valueOfというメソッドです。 受けた質問は、こいつにchar型の配列を投げ込んで作ったStringと 同じ内容の文字列リテラルを比較しても一致しない、というものでした。

で、何が原因だったかというと、「char型の配列の長さが作成する文字列よりも長かった」ということでした。

全く知らなかったんですけど、String.valueOfで文字列作るとき、多分newしてそのままの0が入っている部分もNULL文字が入っているものとして文字列作ってくれやがります。

実際に以下のコードを試してみると分かるかと思います。

String a = "hoge";

char b[] = new char[3];
b[0] = a.charAt(0);
b[1] = a.charAt(1);

String c = String.valueOf(b);

System.out.println(c);

for(byte d: c.getBytes()){
  System.out.printf("%x ", d);
}
System.out.println();

System.out.println("## equals ##");
System.out.println(c.equals("ho"));
System.out.println("## compareTo##");
System.out.println(c.compareTo("ho"));

文字列が一致しないときに、「見えない文字で文字数が異なる」という点に頭が働かなかったのが悔やまれますが、なんでこんなときだけNULL文字までしっかり見てやがるんだと思いました。

川遊びをした

後輩に誘われて、大学近くの野川で川遊びしました。 具体的には釣りをしました。

当初の目的としては、後輩がペットとしてザリガニを飼いたくて、 それを取りにいこうというものでした。

で、ザリガニはというと、死骸は見つかるものの生きているザリガニには出会うことができませんでした (具体的な種類は分からないのですが、モエビはいっぱいいます。網を使えば簡単に取れました)。

なので、一応持ってきた釣り竿で、魚釣りをすることにしました。 釣りと聞いて、どのようなイメージを想像されるかは分かりませんが、 ウキをつけてエサをつけて魚が食べるのを待つといったものです。 今日は天気が良かったので、他にも5組ほど釣りをしている方がいました。

このあたりの野川では、大体狙いは2通りだと思います。 一方は、~20cmほどの鯉または鮒を対象とする釣り。 他方は、50cm~の大型鯉を対象とする釣りです。 今回は前者の方を行いました。

野川は結構釣るのに手頃なサイズの鯉や鮒がいっぱいいるので、 釣りをするのは楽しい川です。 棒ウキという、ヘラブナ釣りのアレといえば伝わるようなウキを使い、 なるべく小さな「かえし」のない針に練り餌をつければ簡単に魚が釣れると思います。 今回は玉ウキというザ・釣りといったウキを使ったのですが、 あたりが見難いのであまりオススメしません。

なお、大きな鯉を釣りたい場合はタモとそれに耐えられる糸と竿を用意しましょう。 練り餌でも釣れるでしょうが、食パンなどをつけて浮かばせておいた方が 釣れるかも知れません。 今日も中高生と思われる人たちがそのような方法で釣りをしていました。 一度かかったのですが、残念ながら、糸が切れたのか釣り上げることはできませんでした。

大きい鯉と言えば、「のんのんびより りぴーと」で釣り回がありましたね。 あの回では登場人物たちが大きな鯉のパワーに四苦八苦していましたが、 あれ誇張表現ほとんどないですからね。 鯉めっちゃパワーありますし、特に川にいる鯉はめちゃくちゃ強いです。 嘘だと思うなら一度試して見てください。

……話が横にずれました。今日の釣果を記します。 後輩と代わりばんこで釣りをしたので、なかなか釣れなかったのですが、 なんとかそれぞれ1匹ずつ手頃な大きさの鯉を釣ることができたので満足でした。

主催者である後輩くんは、自分で釣った鯉をザリガニ代わりに飼うことにしてました。 ぜひとも「のんのんびより」よろしく、ひかりもの親方という名前をつけて欲しいです*1

*1:ところで、水槽・フィルター・砂利・カルキ抜き・エアレーション・エサ・蛍光灯などで10kくらい飛ぶことになると思うのですが、大丈夫ですかね

Windows版のRubyはANSIエスケープシーケンスをいい感じに処理してくれる?

夏コミ終わりましたね。 参加された方々はお疲れ様でした。

さて、最近ちょっと気付いたのですが、 Windows版のRubyANSIエスケープシーケンスをいい感じに処理してくれるっぽいのです。 ほとんどのRubyistの方々には常識なのかも知れませんが、 ちょっと感動したので記事として書かせていただきます。

ANSIエスケープシーケンスとは

以下のリンクの方を参照していただければ、よく分かると思います。 要は、コンソールで文字色を付けたりカーソルを動かしたりするための 特殊な文字列のことです。 こういった規格があるおかげで、Vimのカラースキームとかが可能になるわけですね。

Windows環境とANSIエスケープシーケンス

Windowsは標準ではANSIエスケープシーケンスに対応していないらしく、 以下のANSICONをインストールするとか、Cygwin(Mintty)やbash(Git/MSYS)を使うとか、 以前のエントリで書いたように ConEmuをはじめとするコンソールエミュレータを使うこととかでようやく色付きの文字が出せるようになります。

github.com

対応していない場合は意味不明な文字列、つまりANSIエスケープシーケンスそのものが表示されることになります。

Rubyだと何だというのか、

ANSIエスケープシーケンスをつけて、色付きの文字列を出力してみたいと思います。 PerlPythonRubyの3つの言語で、ごくごく単純なコードを作ってみました。

print("\033[38;5;64m hoge \033[0m\n");
print "\033[38;5;64m hoge \033[0m"
puts "\033[38;5;64m hoge \033[0m"

さて、これら3つのスクリプトを、ANSIエスケープシーケンスに対応している ConEmu上で実行してみると、以下のように緑色の文字列を出力することができます*1

f:id:e8l:20150816231236p:plain

では、今度はANSIエスケープシーケンスに対応していない生のコマンドプロンプトで 同じスクリプトを実行してみると、以下のようになります。

f:id:e8l:20150816231248p:plain

はい。 見て分かりますね。 RubyだけANSIエスケープシーケンスが表示されてないんです。

どういうふうに処理しているのか分からないのですが、 Rubyだけ、内部で特別に処理を行って、対応していない場合は出力しないようにしてくれるようです。 Rubyすごい。

補足

環境(バイナリ)依存かもしれないので、一応使っているRubyについて補足しておきます。 僕の環境ではRubyInstaller版のRuby 2.2.2(x64)を使用しています。

*1:拡張文字色を使っているので、Linuxなどでも256色対応のターミナルじゃないと表示できないことがあります。

コミケ1日目

でしたね。 参加したみなさまお疲れ様でした。

自分は生まれてから一度もコミケに行ったことありません。 ちょっと、あんな感じで人がわちゃわちゃいるようなところにプライベートで遊びに行くのが苦手なんです……。 いわゆる「人に疲れる」ってやつです。

あとは日頃からサークル参加するような方々を追っかけていないので、 「コミケ行って買うぞ!」っていうものがないのも大きいです。

まだ、明日明後日とコミケはあるので、 参加される方はお体にお気をつけて頑張ってください。

ところで、前回の投稿で色々苦戦していたものができたので ここに載せておきますです。

github.com

定期的にWebカメラで撮影した画像をTwitterに投稿するような Webカムサーバ(Twitterクライアント?)です。

慣れていないくせにPythonで色々頑張っていたので 不備が多々あるかと思いますが、良かったら使ってみて下さい。

rauthでTwitterに画像投稿しようと思ったら大変だった話、……は入手したバージョンが古いのが原因だった

2015/08/12: 内容を修正しました

今ちょっとPython3でTwitter API叩くもの(ライブラリとかではない)を作っています。 Twitter APIPythonで叩くにあたり、自分はrauthというOAuthライブラリを使っています。

github.com

理由としては以下のQiitaの記事を読んだことが大きいです。 他にいくつかOAuthライブラリを見つけたのですが、一番書いてて気持ちよさそうだったのでこれにしました。

qiita.com

さて、上記の記事にあるように、テキストだけの投稿は簡単にできました。

が、画像投稿に苦労してしまったので、 ここではどういう問題が発生したかと、その対処について書いていきたいと思います。

初めに結論

以下長ったらしく記述していきますが、 基本的にはGitHubからrauth最新版をインストールしているという条件のもと、次の記述で画像の投稿が可能になります。

2015/08/12: コードの内容を修正しました

# 変数reqに適切なOAuth1Session型のインスタンスが入っていると仮定します

media = {"media": open(filePath, "rb").read()}
res = req.post(
    "https://upload.twitter.com/1.1/media/upload.json",
    files=media)

media_ids = [res.json()["media_id"]]

req.post(
    "https://api.twitter.com/1.1/statuses/update.json",
    data={"status": "Hoge Fuga", "media_ids": media_ids})

APIをどう呼ぶか

まずさしあたっての問題は、どう画像投稿のためのAPIを呼び出すか、ということでした。

2015/08/11現在、media/uploadというAPIを使って1つずつ画像をアップロードするという方法が主流です。 このAPIは例えばstatuses/updateとは異なり、multipart/form-dataでデータを送り、かつOAuthのSignatureの作り方も異なります。

ググってみると、rauthとは違いますが、同様にRequestsを利用しているライブラリでの呼び出し例が見つかりました。

qiita.com

とりあえずはこのような方法を試してみることにしました。 実際、基本的にはこの方法でよいことが分かりました(multipart/form-dataでデータをPOSTする場合のSignatureの作り方はTwitter特別という訳ではなく決まっているもよう[要検証])。

Python3のopenの返り値が違ってた

Python2を使ってる人は無視して結構です。

実際に上の記事と同様な呼び出しを行うと、見事にエラーが発生しました(エラーメッセージ取り忘れました)。

問題の原因は、Requestsのドキュメントにも書かれている、multipart/form-dataでデータを送信するための記述でした。 Requestsでは

files = {"file": open(filepath, "rb")}

のようなオブジェクトを用意し、postメソッドを呼び出す際に

req.post(url, files=files)

とすることでmultipart/form-dataでファイルの内容をバイナリで送信してくれます。

……、rauthではこの呼び出しをラップし、Signature生成のための処理などを挟んだあとに本物の(Requestsの)メソッドを呼び出すのですが、 その際にkwargsの内容をdeepcopyしています(Session.py 172行目付近)。

Python2はopenの結果はfileなので問題はないみたいなのですが、 Python3では結果が_io.BufferedReaderのインスタンスに変わっています。 そのためか、deepcopyの部分でエラーが発生してしまいます。

Requestsのコードを少し読んでみると、別にopenの戻り値をそのまま渡さなければいけない訳ではなく、読み込んだ結果のBytes型のインスタンスでもいいようなので、代わりに

files = {"file": open(filepath, "rb").read()}

とすることで問題を解決することができました。

しかし、まだ問題は続きます。

multipart/form-dataにならない

上記の対応で修正完了、かと思いきや、まだエラーが発生します。 次に出てきたエラーの発生箇所を見てみると、どうやらmultipart/form-dataとして送信するものだとみなされておらず、Signatureの計算をしようとして 存在しないキーにアクセスしようとするために発生しているようでした。

Requestsは名前付き引数filesにデータを設定してくれるじゃん! と思いながらも、ソースを読んでみると、自分でContent-Typeを設定するようにすればいいようなのでそれに従います。

res = req.post(
    "https://upload.twitter.com/1.1/media/upload.json",
    files=media,
    headers={"Content-Type": "multipart/form-data"})

これで問題が解決……、かと思いきや、まだエラーが消えません。

2015/08/12: 追記

のは、ライブラリが古いことが原因でした。 Content-Typeの指定は関係なく、ちゃんと最新版では引数filesにデータが指定されているかどうかで 判断してくれるようになっていました。

ここで、Content-Typeを指定してしまうと、あとでRequestsの方でリクエストデータを作成する際、 正しいmultipart/form-dataが作れないため、APIの呼び出しに失敗します。 そうです、boundaryがここに書いてないし、Requests側で更新もしてくれないので不正なmultipart/form-dataになってしまうんです。

あとでそのことに気付き、「なんで前はこれで動いたんだ?」と思ったら、

    headers={"Content-Type", "multipart/form-data"})

と「:」と「,」をミスタイプしてSet型になってました。 なので、当人はDict型として書いていたつもりで、たまたま正しく動いてしまっていたようです。

ライブラリが古い?

ソースを読んで記述を変えたのに、なんで!? もう最悪の手段だけど手元のライブラリの実体に手を加えて調査しないとダメか……、 と思ってソースを開いてビックリ。GitHubのコードと内容が違うじゃありませんか。

元々pipを使ってインストールしていたので、

$ pip install -U rauth

と入力してみましたが、すでに最新版のライブラリが入っているとのこと。なんで? GitHubの最終更新が4ヶ月くらい前だったけど、まだ反映されてないの?

ということで、仕方なくGitHubから最新版のライブラリをインストールするようにします。

$ pip install -U -e git+https://github.com/litl/rauth.git#egg=rauth

これを試したら無事画像がアップされました。

まとめ

rauthは確かに楽です。 ちょっとだけTwitterAPIを叩きたいときには便利です。 しかし、画像のアップは大変でした。主に自分の不注意で。

2015/08/12 に改めて間違った内容を修正させていただきました。 rauthでも簡単にできることがちゃんと判明しました(関係者のみなさま、すみませんでした)。 ただ、できればpipで導入できるバージョンを最新のものに、 そしてこのような場合の例をドキュメントに載せてほしいかなと思いました