PHP標準関数のsnippetを作る

寝れないのでご無沙汰エントリ。

http://hg.apgwoz.com/yasnippet-php-mode/
ここのを使ってたりしたんですが、どうもカスタマイズしたくなってきて
こんな感じで、自分用オレオレPHP標準関数snippetを生成しました。(作ったのは結構前なんですが)

オレオレなところは、

  • yasnippetはanything-c-yasnippetからしか使ってないので、それに特化した感じで。
  • anythingの候補選択時に、関数の簡単な説明も表示したい。(関数名だけじゃわかんない)
  • 引数毎に、TABで移動するのは面倒なのでまとめて一つのフィールドに。(アドバイス程度に表示するくらいに)

get_defined_functions()を使って、実行環境に定義されている関数を取得し、
PHPのドキュメントにぶつけてsnippetを生成します。

anything-c-yasnippet等の導入方法などは、作者のid:IMAKADOさんのエントリを参照。
http://d.hatena.ne.jp/IMAKADO/20080401/1206715770

1. PHPのドキュメントを落としてくる

http://jp.php.net/download-docs.php
から php_manual_ja.tar.gz(英語のでもたぶん大丈夫です)をwgetしてきます。解凍したらhtmlというディレクトリ以下にファイルが展開されます。

2. snippet生成

下記の、やっつけ生成スクリプトをコピペして実行します。
snippet_gen.php

#!/usr/bin/env php
<?php
define('DOC_DIR'    , './html');     // PHPドキュメントディレクトリ
define('SNIPPET_DIR', './snippets'); // snippet作成先ディレクトリ

// snippetファイルフォーマット #name : 関数名 : 説明文
$SNIPPET_FORMAT = <<<EOF
#name : %-30s : %s
# --
%s
EOF;

if (!is_dir(SNIPPET_DIR)) {
    mkdir(SNIPPET_DIR) or exit("can't make snippet directory: " .  SNIPPET_DIR . PHP_EOL);
}

// aliases.htmlを無理やり解析してから、エイリアス関数名 => エイリアス先関数名のハッシュを作る
$alias_file = sprintf('%s/aliases.html', DOC_DIR);
if (!is_file($alias_file)) {
    exit("alias doc file does not exist" . PHP_EOL);
}
$body = file_get_contents($alias_file);
$alias_pattern = <<<EOF
<tr[ ]valign="middle">
\s+
<td[ ]align="left">(.+?)</td>
\s+
<td[ ]align="left"><[^>]+>(.+?)\(\)</[^>]+></td>
.+?
</tr>
EOF;
preg_match_all("|$alias_pattern|xms", $body, $matches, PREG_SET_ORDER);
// エイリアス名 => エイリアス先関数名
$alias_map = array();
foreach ($matches as $matche) {
    $alias_map[$matche[1]] = $matche[2];
}

// 定義済み関数を1件ずつ処理
foreach (array_shift(get_defined_functions()) as $function) {
    $name = isset($alias_map[$function])
        ? $alias_map[$function]
        : $function;

    list($description, $arguments) = get_func_information($name);

    // エイリアスの場合は、説明文にエイリアスと追記
    if ($name !== $function) {
        $description = "[Alias $name] $description";
    }

    // snippetファイル作成
    $snippet_file = sprintf('%s/%s', SNIPPET_DIR, $function);
    $snippet = $arguments
        ? sprintf('%s(${1:%s})', $function, $arguments)
        : sprintf('%s($1)'     , $function);

    if (!file_put_contents(
            $snippet_file,
            sprintf($SNIPPET_FORMAT, $function, $description, $snippet))) {
        echo "can't wirte snippet file: $function" . PHP_EOL;
    }
}

function get_func_information($function)
{
    $name     = strtr(strtolower($function), array('_' => '-'));
    $doc_file = sprintf('%s/function.%s.html', DOC_DIR, $name);

    $description = '';
    $arguments   = '';

    if (is_file($doc_file)) {
        $body = file_get_contents($doc_file);

        // 説明文
        $r = preg_match('|<title>(.+?)</title>|ms', $body, $matches);
        $description = $r ? filter($matches[1]) : '';

        // 引数を無理やり解析
        $r = preg_match('|<div class="methodsynopsis dc-description">.+?\((.+?)\).+?</div>|ms', $body, $matches);
        $arguments =  $r ? filter($matches[1]) : '';
        if ($arguments === 'void') {
            $arguments = '';
        }
    }
    else {
        echo "doc file does not exist: $function" . PHP_EOL;
    }

    return array($description, $arguments);
}

// 不要な文字列を除去する
function filter($str)
{
    $str = strip_tags($str);
    $str = html_entity_decode($str);
    $str = preg_replace(array("|\n|", '|\s{2,}|'),
                        array(''    , ''        ),
                        $str);
    return trim($str);
}

実行したらsnippets以下にファイルが作成されるはずなので、後はこんな感じで自分のsnippet配下に登録して..

$ ./snippet_gen.php 
$ cp snippets/* ~/.emacs.d/snippets/text-mode/php-mode/

M-x yas/reload-all でyasnippet再読み込み。
動かなかったらゴメンナサイ。

php-completionの関数名補完と連携してみたいなあ。