sdif updated

Side-by-side / ANSI color / word context

f:id:uta46:20140124233940p:plain

Unicode / Japanese

f:id:uta46:20140124234055p:plain

With --mecab morphology

f:id:uta46:20140124234120p:plain

NAME

sdif - side-by-side diff viewer for ANSI terminal

SYNOPSIS

sdif file_1 file_2

diff ... | sdif

--number, -n        print line number
--digit=n           set the line number digits (default 4)
--truncate, -t      truncate long line
--onword            fold line on word boundaries
--width, -w #       specify width of output (default is 80)
--diff=s            set diff command
--diffopts=s        set diff command options
--[no]color         use color or not
--colormap, --cm    specify color map
--mark=position     mark position (right, left, center, side) or no
--man               display manual page
-c, -C#             context diff
-u, -U#             unified diff

--cdif              use ``cdif'' as word context diff backend
--cdifopts          set cdif command options
--mecab             pass --mecab option to cdif

DESCRIPTION

sdif is inspired by System V sdiff(1) command. The basic feature of sdif is making a side-by-side listing of two different files. All contents of two files are listed on left and right sides. Center column is used to indicate how different those lines. No mark means no difference. Added, deleted and modified lines are marked with `<' and `>' character.

1 deleted  <
2 same          1 same
3 changed  <>   2 modified
4 same          3 same
            >   4 added

It also reads and formats the output from diff command from standard input. Besides normal diff output, context diff -c and unified diff -u output will be handled properly.

Each lines can be displayed in different colors. Read --colormap section in ths manual for detail.

While sdif doesn't care about the contents of each modified lines, it can read the output from cdif command which show the word context differences of each lines. Option --cdif set the appropriate options for cdif. Set --nocc, --nomc options at least when invoking cdif manually. Option --notc is pereferable because text color can be handled by sdif.

Environment valuable SDIFOPTS is used to set default options.

OPTIONS

  • --w=width, -w

    Use width as a width of output listing. Default width is 80. If the standard error is assinged to a terminal, the width is taken from it if possible.

  • --number, -n

    Print line number on each lines.

  • --digit=n

    Line number is diplayed in 4 digits by dafult. Use this option to change it.

  • -c, -Cn, -u, -Un

    Passed through to the back-end diff command. Sdif can interpret the output from normal, context (diff -c) and unified diff (diff -u).

  • --truncate, -t

    Truncate lines if they are longer than printing width.

  • --onword

    Fold longs line at word boundaries.

  • --cdif

    Use cdif command instead of normal diff command.

  • --cdifopts=option

    Specify options for backend cdif command.

  • --mecab

    Pass --mecab option to backend cdif command. Use --cdifopts to set other options.

  • --diff=command

    Any command can be specified as a diff command to be used. Piping output to sdif is easier unless you want to get whole text.

  • --diffopts=option

    Specify options for backend diff command.

  • --mark=position

    Specify the position for mark. Choose from left, right, center, side or no. Default is center.

  • --[no]color

    Use ANSI color escape sequence for output. Default is true.

  • --colormap=colormap, --cm=colormap

    Basic colormap format is :

      FIELD=COLOR
    

    where the FIELD is one from these :

      OLD       NEW       UNCHANGED
      --------- --------- ---------
      OCOMMAND  NCOMMAND           : Command line
      OFILE     NFILE              : File name
      OMARK     NMARK     UMARK    : Mark
      OLINE     NLINE     ULINE    : Line number
      OTEXT     NTEXT     UTEXT    : Text
    

    You can make multiple filelds same color joining them by = :

      FIELD1=FIELD2=...=COLOR
    

    Also wildcard can be used for field name :

      *CHANGE=BDw
    

    Multiple fields can be specified by repeating options

      --cm FILED1=COLOR1 --cm FIELD2=COLOR2 ...
    

    or combined with comma (,) :

      --cm FILED1=COLOR1,FIELD2=COLOR2, ...
    

    COLOR is combination of single character representing uppercase foreground color :

      R  Red
      G  Green
      B  Blue
      C  Cyan
      M  Magenta
      Y  Yellow
      K  Black
      W  White
    

    and corresponding lowercase background color :

      r, g, b, c, m, y, k, w
    

    and other effects :

      S  Standout (reverse video)
      U  Underline
      D  Double-struck (boldface)
      F  Flash (blink)
      E  Expand
    

    E is effective for command, file and text line. That line will be expanded to window width filling up by space characters. Left column is expanded always. You may want to use this to set background color for right column.

    Defaults are :

      OCOMMAND => "CSE"
      NCOMMAND => "MSE"
      OFILE    => "CDE"
      NFILE    => "MDE"
      OMARK    => "Cw"
      NMARK    => "Mw"
      UMARK    => "w"
      OLINE    => "Y"
      NLINE    => "Y"
      ULINE    => "Y"
      OTEXT    => "C"
      NTEXT    => "M"
      UTEXT    => ""
    

    This is equivalent to :

      sdif --cm 'OCOMMAND=CSE,NCOMMAND=MSE,OFILE=CDE,NFILE=MDE' \
           --cm 'OMARK=Cw,NMARK=Mw,UMARK=w' \
           --cm '*LINE=Y,OTEXT=C,NTEXT=M,UTEXT='
    

    Try next setting if you want to make modified section more visible.

      sdif -n --cdif --cm '[ON]TEXT=wE,UMARK=,OMARK=C,NMARK=M'
    

AUTHOR

Kazumasa Utashiro

https://github.com/kaz-utashiro/

SEE ALSO

perl(1), diff(1), cdif(1)

cdif, sdif

中学生と Perl プログラムを作ってみた

プログラミングなんかやったことない中学生の息子に、ゲームプログラムはどうやって作るんだと訊かれたので、くどくど説明するよりやってみた方が早いだろうと一緒に作ってみた。

最初は乱数を発生して上か下かを選ぶプログラムだったんけど、リアリティがないようだったので対戦形式にしてみた。

売ってるゲームも、これを複雑にしたようなもんだと説明したが、簡略化しすぎだろうかw?

(https://gist.github.com/8420481)

設問

  • 実はこのゲームには必勝法がある。必ず全問正解するにはどうすればいいか?
  • その必勝法を無効にするようにプログラムを修正してくれ。
  • 対戦成績を表示するようにしてみよう。
  • 相手が同じモンスターだったら、半分の確率で勝つようにしてみよう。

実は greple で PDF ファイルを検索できる

マニュアルに書いていませんが、実は greple で PDF ファイルを検索すると、自動的pdftotext コマンドが実行されて、テキストに変換した結果を検索します。

f:id:uta46:20140110145608p:plain

画像は、Ultegra Di2 のマニュアルから「フロントディレイラー」「動作」「バッテリー」のすべてを含む段落を探した例。 たまには役に立つこともあった。

探したかったのはこの文章。

バッテリー残量が少ない時、まずフロントディレイラーが動作しなくなり、続いてリアディレイラーが動作しなくなります。

--if オプションを使えば、自分で好きなフィルタを設定することもできます。

最後に空白行が出てるのはバグですねえ。 前にいじったときにエンバグしたらしい。 誰かテストスイート書いてくれないかなあ…

関連

cdif v2.6 --mecab オプション

Unicode 対応した cdif では、漢字・平仮名・片仮名の連続を1つのトークンとして処理し、その diff をとっています。 このトークン化に形態素解析ツールの mecab を使えるようにしてみました。

cdif --mecab

のように使います。

今のところ、外部コマンドとして mecab を起動しているので、効率的にはあまりよろしくはありません。

以下は、sdif と組み合わせて使っている例。 違う人の文章だから、違いが多すぎてあんまり参考にはならないか。

f:id:uta46:20140207155012j:plain

変更点

  • --mecab オプションの追加
    • Unicode 対応の mecab コマンドがインストールされている必要があります
    • mecab コマンドの仕様により、空白だけの行が無視されます
    • 日本語以外の部分のトークン化が影響を受けます
      • 具体的には、連続する記号が1つのトークンにされることがあります
  • --textcolor をデフォルトにした
    • diff -u の出力を見るのにはやはり未修整部分にも色が付いていた方がいい感じです

cdif v2.5

昨年末に更新した cdif を修正しました。

未修正部分の着色に対応

自分では必要ないと思っているのですが、未修整部分の文字にも色を付けられるようにしました。 また --colormap というオプションで、それぞれのフィールドの色を指定できるようになっています。 デフォルトの設定はこう。

cdif --cm 'COMMAND=SE,OMARK=CS,NMARK=MS' \
     --cm '*TEXT=,*CHANGE=BDw,DELETE=APPEND=RDw'

--commandcolor, --markcolor, --textcolor オプションで、フィールドに色を付けるかどうかを指示することもできます。 --cc, --mc, --tc とも省略可。 デフォルトでは --notc になっています。

f:id:uta46:20140111102309j:plain

あ、DELETE がなかった…

diff -t, -T に対応

その他、若干のバグ修正

ところで

今回、--colormap オプションをテーブルに反映する部分のコードはこんな風になっています。

if (@opt_colormap) {
    map {
        my $c = pop @$_;
        map { $colormap{$_} = $c }
        map { match_glob $_, keys %colormap }
        @$_;
    }
    map { [ split /=/, $_, -1 ] }
    map { split /,/ }
    @opt_colormap;
}

自分で書いといて、なんだか Perl っぽくないコードだなあと時代を感じたりするわけですが、3行目だけ古風な感じなので List::Util の reduce を使って書き直そうかと思うと、これがどうもうまくいかないのですよ。 次のプログラムを実行すると、$hash{k1}, $hash{k2} に v が入ってほしいのだけど、2番目は undef になってしまいます。

my %hash;
reduce {
    $hash{$b} = $a;
    $a;
} qw(v k1 k2);
print Dumper \%hash;

$a は一度代入すると値を失うようで、$a; だけの行を削除すると動くし(この行は本来不必要)、代入する部分を "$a" にしても大丈夫。 どうも perl5.12 に含まれる reduce のバグではないかと。 List::Util::PP を使えば動作するし。

なんとか使う方法はわかり、map と reduce だけのそれなりに趣のあるコードになりますが、動いたところで変な warning は出るし、無駄に可読性を下げるだけなので不採用でした。 参考のために一応見せるとこんなです。

use List::Util qw(reduce);
no warnings;

if (@opt_colormap) {
    map {
        reduce {
            map { $colormap{$_} = "$a" } match_glob $b, keys %colormap;
        } reverse @$_;
    }
    map { [ split /=/, $_, -1 ] }
    map { split /,/ }
    @opt_colormap;
}

cdif アップデートしました

久しぶりに cdif コマンドを使おうとしたら $* なんか使ったらいかんよと動いてくれなかった。 なにしろ、作ったのは1992年で、最後に更新したのが2003年という骨董品のコマンドなので、perl4 の仕様で書いてある。

翻訳作業などでは、文章の変更点を確認したいことがよくある。 diff を使えば、行単位での違いを表示してくれるのだが、1行が長くてその一部だけが変更されているような場合には、変更点を探すのが大変だ。 cdif を使うと、行の中で異なる部分をハイライトしてくれる。 フィルタとして動作するので、diff の出力をパイプで流し込めばよくて、通常の形式以外に -c-u 形式にも対応している。 元はと言えば、FrameMaker だったか何かで書かれた英文マニュアルの更新作業のためにやっつけで作ったものだ。 ちょうど、UCB の CSRG と 4.4BSD の開発をしている時で、Berkeley から Cupertino に帰るフリーウェイを走りながら実装方法を考えて、帰ってから深夜に作った記憶がある。

滅多に使うわけではないがないと困るので、まずは、今時、他にもっといいツールがあるんじゃないかと思って調べてみた。

wdiff

  • https://www.gnu.org/software/wdiff/
  • C
  • 独自形式
  • Unicode 対応してないっぽい
  • ワードの区切りはスペースだけで、変更もできなさそう
  • 所感
    • スペース区切りだけでは日本語の文章に使うのは無理
    • 出力が見にくい

jwdiff (追加)

colordiff

  • http://www.colordiff.org/
  • Perl
  • diff の出力に行単位で色を付けるだけ
  • wdiff の出力も受け付けるが、やはりただ色を付けるだけ
  • 特に Unicode 対応はしていないようだが、行指向なので普通に動作する
  • 所感

diffc

  • https://code.google.com/p/diffc/
  • Python
  • Unicode 対応なし
  • 単語ではなく文字単位で処理するので出力が見にくい
  • デフォルトの色使いは非常に見にくい
  • 遅い
  • 所感
    • 労作ではあるのだが、どうも実用のために作ったのではない気がする
    • Python だし、改造するつもりがあれば、わりと筋がいいかもしれない
      • でも、ソースがチョー長くて読む気にならない
    • 動作が遅いので、大量のデータを処理するのには向いていないかも

git-diff

  • --color で色分けしてくれるが、これは行単位
  • --word-diff で、wdiff 形式で出力
  • --word-diff-color で変更部分を色分けして出力
  • Unicode は扱えるが、単語の区切りは空白
  • そのため日本語は全部1ワードとして扱われてしまう
    • --word-diff-regex でワードを正規表現で指定できるが使い方がよくわからない
    • とりあえず文字列を指定しただけではうまく動作しない
    • \p{Han} のような Unicode スタイルの指定は効かない
    • POSIX スタイルでの Unicode Property の指定の仕方が不明
  • 機能的には総じてよくできているが、通常の diff 形式には対応していないし、フィルタとしても動作しないような気がする
    • 多少手を入れればコマンド化は難しくないと思う
    • wdiff 形式を diff 形式に変換することは簡単だが、それでは diff 形式の出力を処理することはできない

diff-highlight.pl

  • https://github.com/git/git/tree/master/contrib/diff-highlight
  • 2011年
  • Perl
  • git で使うために作られたもの
    • フィルタとして機能する
    • git diff --color の出力も処理できる
  • コマンド自体は Unicode 未対応だが、perl -CSDA で実行すれば問題なく機能する
  • コードは極めて短く、処理も単純
  • 問題点
    • diff -u 形式にしか対応していない
    • 変更前後の行数が同じものだけを処理する
      • プログラムの変更点を見るにはいいが、文章の変更部分をチェックするのには向いていない
    • 文字単位で処理するため、単語の変更がわかりにくい (diffc と同様)
    • アルゴリズムが単純なので、パターンによってはうまく処理できず、変更部分の抽出に失敗する
  • 所感
    • ちょっとしたコードの変更をわかりやすくすることができ、実装も簡潔かつ高速で優れた実装だが、文章の校正のような用途には向いていない。
    • パラグラフが1文になっているワープロ原稿のようなもので、修正点が微細な場合には使えると思う。

Python 版 diff-highlight

diff-highlight の機能が意外にしょぼいと思った人は他にもいるようで、拡張してくれていた。

ediff.el

  • emacs で動作する
  • 日本語には対応している。
  • ワードは句読点で区切られる感じなので、荒すぎる。
  • 所感
    • 昔見た時にはよくできていると関心したのだが、実用的に使ったことはなかった
    • 改めて使ってみると、そんなには関心しない
      • 単語の区切りが荒すぎるせいもあるのだろうが、どちらかというとウインドウのインタフェースが使いにくいのだと思う。何がいけないかを分析するためには時間が必要だが、その労力に見合うか不明。

cdif

というわけで、どうも自分の目的にちょうどいいコマンドが見当たらないので、cdif を更新することにしました。 作ったのはモノクロ時代で、アンダーラインと反転で表示するしかなかったが、近代的にカラー化することに。 基本的な機能は20年前と変わっていなくて、モダンなプログラミングスタイルに書き換えて Unicode 対応した以外は、主にコスメティックな修正です。

  • https://github.com/kaz-utashiro/cdif
  • 1992年
  • Perl
  • Unicode 対応
  • ワードは alphanumeric、漢字、平仮名、片仮名の連続
    • それ以外の文字は、同じ文字が連続する場合はワードとして処理
    • ついでにハングルも入れてある
    • ヨーロッパ系はよくわからないので未対応
      • 教えてくれたら入れます
    • 中国語は漢字の連続をワードで処理すると困るかも
  • 文字単位で比較したい場合は -B オプションを使う
  • 色使いは OS X のターミナルでチューニングしてある
    • 一応 --colormap というオプションで変更可能
    • 仕様が固まっていないのでマニュアルには書いていません
    • 変えたい人は、ソースを書き換えちゃった方が早いです
  • そこそこ高速に動作
  • テキスト全体に色を付けるのは、見た目は派手だけど実用的に必要かというとそうでもない気がしてやっていない
    • ハイライトから抜ける時の処理が面倒なだけでもあったりはする
  • 実装は手抜きで、バックエンドで diff コマンドを使っているので、多分 Unix 系 OS のみ対応

f:id:uta46:20140110150042g:plain

greple -Mdiff

さて、ついでというわけではないが、単に diff の出力に色を付けるだけなら greple のマクロだけでできちゃうぜということで、diff 用のモジュールを書いてみた。 いかが?

Greple command module for colorise diff output.

(2018-3-27 更新)

greple --word-diff-color 相当

git diff で、--word-diff-color を付けると wdiff のシンボルを削除して色づけして表示してくれるので、使い方によっては見やすくなる。 ちょっとした関数を定義すると greple でも同じことができる。 コードは短いがやっていることは、少々複雑である。 パラメータとして連想配列の要素が渡され、matched というキーを持つ値にマッチした範囲が入っている。 テキスト全体は $_ に入っているので、これを書き換えれば出力を修正することができるのだが、位置が変わってしまうと、それに合わせて範囲の指定も修正してあげないといけない。 先頭から書き換えると後ろが狂ってきちゃうので、配列の最後からテキストと範囲の両方を修正してあげればいいという寸法だ。 ちなみに matched の要素は開始位置、終了位置以外に、パターンのインデックスも付いているので注意。 出力は通常の機能で行うので --print print_wdiff--continue オプションも付けて使います。

見た目を気にしなければ wdiff -t を使えばいいので、機能自体はご愛嬌だが、print 関数の例としてはなかなかよいのではないかと。

print function to remove wdiff control sequence fo ...

Greple モジュール対応

関連

モジュール

Greple は、~/.greplerc にオプションやプログラムを書くことができて、これはこれで便利なのだが、量が多くなってくるとごちゃごちゃしてわかりにくくなるのが問題だった。 また、使わない機能を毎回読み込むのも無駄なので、以前からモジュールを定義するようにしたいと思っていた。 やっとそれが実装できたのでリリースすることにする。

以下に Perlスクリプトを検索するためのモジュールを付ける。 デモ用なので、あまり凝ったことはしていなくて、コメント、POD、それ以外というセクションを指定してパターンを探すことができる。 組み合わせることも可能で、--allsection というオプションを使うと、すべてのセクションが指定される。 それではすべてのパターンが表示されてしまって意味がないようだが、マルチカラー環境だとそれぞれが違う色で表示されるのだ。

この程度の機能はわざわざプログラムを作らなくてもオプションだけで実装できる。 オプションの指定は、モジュールの __DATA__ 以降に書くことができる。 形式は .greplerc と同じ。 参考のために、プログラム版とオプション版の両方を記述してあるが、オプションのみで実装するなら package 宣言だけあれば十分だ。

Greple module to handle perl script.

使う時には、次のように -M オプションでモジュールを指定する。 このオプションは特別で、必ず先頭で指定しなければならない。 複数指定することもできる。

greple -Mperl --code default greple
greple -Mperl --colorful --allsection default greple

実行結果はこんな感じ。

f:id:uta46:20140111102649p:plain

マルチカラーは結構便利で、~/.greplerc に次のような行を入れておくと常に有効になる。 ただ、OS X のターミナルは比較的目に優しい色で表示してくれるのでいいのだが、原色で表示されるエミュレータなどを使っている場合は、何か方法を考えた方がいいだろう。

option default --colorful

デフォルトオプションは、もちろんモジュールでも定義することができる。 --chdir--glob と組み合わせると、ファイルを指定しないで使用することもできる。 昔は、RFC のインデックスを検索するのにこの機能を使っていたが、最近は読まなくなっちゃったし、自分しか持っていないデータでなければ検索エンジンを使った方が早い。

--inside, --outside, --include, --exclude

--inside/outside オプションの仕様が変更されている。 上の説明からもわかると思うが、複数の領域を指定した場合、いずれかの領域に含まれるマッチがすべて選択される。

以前はすべての条件を満たすマッチが選択されていたが、これは --include/exclude オプションで実装するようにした。 単独で使うのであれば、どちらでも同じである。

--inside/outside オプションで必要なマッチを選択し、--include/exclude オプションで不必要な部分を削除するという使い方を想定している。

ところで

--include/exclude は、grep との互換性のために使わずにおいたものだが、わかりやすいので使ってしまった。 他にもっといい考えがあれば、早めに教えて頂ければ今からでも変えちゃうかもしれない。 ちなみに mg再帰的検索をサポートしていたが、greple では廃止してしまった。 実際に役立ったことはほとんどなかったからだ。 また greple は、テキスト検索に特化するように変更したので、いろいろなファイルが混在した環境では Encode モジュールが文句を言ったりしてイマイチ使いにくい。 どうしてもやりたいという方は、find の出力をパイプで渡して --readlist で読み込んでください。