文字化けを防ぐには
したがって、そのファイルのプログラミング言語を予め指定し、シンタックスハイライトを有効化できれば、文字化けを回避できる可能性が高いということになります。UTF-8以外のエンコーディングを使って開発を行う場合で、かつ、プログラミング言語の判定ができない可能性のある拡張子を持ったファイルを扱う場合は、この方法を試してみてください。GitHubに対してプログラミング言語の指定をする方法は2つあるので、それぞれ紹介します。
.gitattributesを使ってファイルの言語を指定する
.gitattributesとは、Gitの属性を細かくコントロールするためのファイルです。.gitattributesというファイルに
*.src linguist-language=JavaScript
と記載することで、全ての.srcという拡張子を持つファイルをJavaScriptファイルとして認識させた上で、日本語に変更を加えたコミット387fd5fでは、文字化けは起きていません。
modelineを使ってファイルの言語を指定する
もう一つの方法は、ファイル内にVimもしくはEmacs形式のmodelineを記入する方法です。各ファイルの最後にmodelineを追記し、同じくファイルをJavaScriptとして認識させた上で、日本語に変更を加えたコミット62c5379でも、やはり文字化けは起きていません。
どちらの方法でも、6つ全てのファイルでシンタックスハイライトが有効になっていることがわかります。
また、.gitattributesでは、ファイルごと、ディレクトリ以下のすべてのファイル、特定の拡張子をもったすべてのファイル、など細かい指定が可能です。詳細はLinguistのREADMEのOverridesセクションを参照してください。
Linguistの仕組み
最後にこのプログラミング言語判定ライブラリLinguistの仕組みを簡単に解説します。
前述したように、LinguistはGitHubの言語バーを実装するのに使われているライブラリです。GitHubはこのライブラリをオープンソースプロジェクトとして公開しているので、誰でもコードを読むことができます。
言語の判定には、その名の通りdetectというメソッドが起点になります。detectメソッドは入力(Blob)に対して複数のストラテジーを順に評価しながら候補を絞り込んでいき、最も可能性が高いものを返すような仕組みになっています。
ストラテジーは下記の6つです(linguist.rb#L60-L67)。
STRATEGIES = [
Linguist::Strategy::Modeline,
Linguist::Strategy::Filename,
Linguist::Shebang,
Linguist::Strategy::Extension,
Linguist::Heuristics,
Linguist::Classifier
]
ご覧の通り、上記で説明したmodelineの追加は、Modelineストラテジーによって一番最初に評価されます。ここでマッチすれば、その後のストラテジーは評価されません。その次にファイル名、ファイルのシェバン、拡張子をそれぞれ評価するストラテジーが実行されます。
ファイル名や拡張子の一覧はlanguages.ymlというファイルに記載されています。今回の例で使用した.srcという拡張子はこのファイルに存在しません。つまり、Extensionストラテジーにはマッチしないということです。
Heuristicsストラテジーは経験則的な判定方法です。例えば.sqlというSQLファイルはさまざまな種類のデータベースで利用されています。そのようなケースでは、最初の4つのストラテジーだけでは、そのファイルがPostgreSQLのSQL文なのか、IBM Db2のものか、またはOracleのものなのか、正確に判定できません。そのようなケースで、データベースごとのSQLファイルの特徴を元に判断を行うのがこのストラテジーです。.sqlの場合は以下のように記述されています(heuristics.rb#L453-L467)。
disambiguate ".sql" do |data| if /^\\i\b|AS \$\$|LANGUAGE '?plpgsql'?/i.match(data) || /SECURITY (DEFINER|INVOKER)/i.match(data) || /BEGIN( WORK| TRANSACTION)?;/i.match(data) #Postgres Language["PLpgSQL"] elsif /(alter module)|(language sql)|(begin( NOT)+ atomic)/i.match(data) || /signal SQLSTATE '[0-9]+'/i.match(data) #IBM db2 Language["SQLPL"] elsif /\$\$PLSQL_|XMLTYPE|sysdate|systimestamp|\.nextval|connect by|AUTHID (DEFINER|CURRENT_USER)/i.match(data) || /constructor\W+function/i.match(data) #Oracle Language["PLSQL"] elsif ! /begin|boolean|package|exception/i.match(data) #Generic SQL Language["SQL"] end end
Classifierは、それでも言語が判定できなかった場合に使われる最後のストラテジーで、単純ベイズ分類器の手法を使ってテキストファイルの言語を推測します。各言語の訓練データはsamplesディレクトリに保存されています。
もちろん、これら6つのストラテジーを用いても言語が判定できない場合もあります。今回のように、内容がJavaScriptで拡張子が.srcというファイルは、ここまでの6つのストラテジーを使ってもJavaScriptと判定されることはありませんでした。
Linguistはローカルで実行して確認できるので、試してみましょう。lowply/garbled-diff-testリポジトリをクローンし、下記のコマンドを実行してください。
$ bundle install # (...Install RubyGems...) $ bundle exec linguist example-for-linguist.src
結果は下記のようになるはずです。
example-for-linguist.src: 13 lines (11 sloc) type: Text mime type: application/x-wais-source language:
languageの値が空になっていて、example-for-linguist.srcの言語が判定できなかったことがわかります。ただし、Classifierストラテジーはファイルの内容を元に判定を行うので、同じように「内容はJavaScriptだが拡張子は.src」というファイルであっても、内容次第ではJavaScriptと判定される可能性もあります。
まとめ
本稿では、GitHubが日本語などのマルチバイト文字をどのように扱っているのかというトピックを、文字化けというキーワードを軸に、GitHubのサポートエンジニアが解説しました。なお、本稿に記載の内容は2018年8月時点の仕様を元にしています。GitHubは常にアップデートされており、今後エンコーディングにまつわる仕様も変わる可能性があります。
それでは、次回は以降の連載もお楽しみに!
