faceがnilのときだけsmartchr

ストリングのリストを引数にとって割り当てられたキーを連続して押すと順番に入力するコマンド関数を返す関数 - IMAKADO::BLOG
が便利そうだったので今日から導入してみました。
コメントとか、文字列の中では起動しないほうが自分は嬉しかったので、
faceがnilのときだけ起動するように、条件判定をかますようにしてみました。

;; http://d.hatena.ne.jp/IMAKADO/20080913/1221328814
;; 関数名だけsmartchrに変更させていただきました。
(eval-when-compile (require 'cl))
(defun smartchr (list-of-string)
  (lexical-let ((los list-of-string)
                (last-word "")
                (count 0))
    (lambda ()
      (interactive)
      (if (eq this-command real-last-command)
          (incf count)
        (setq count 0))
      (when (>= count (length los))
        (setq count 0))
      (let ((word (nth count los)))
        (when (eq this-command real-last-command)
          (delete-backward-char (length last-word)))
        (setq last-word word)
        (insert word)))))

(defun smartchr-if (list-of-string condition)
  (lexical-let ((f (smartchr list-of-string))
                (c condition))
    (lambda (arg)
      (interactive "p")
      (if (and (= arg 1) (funcall c))
          (funcall f)
        (self-insert-command arg)))))

(defmacro smartchr-if-face (list-of-string list-of-face)
  `(smartchr-if ,list-of-string
                (lambda ()
                  (memq (get-text-property (point) 'face)
                        ,list-of-face))))

(define-key php-mode-map (kbd "=") (smartchr-if-face '("=" " = "  " == " " === ") '(nil)))

追記 2009-05-29

  • C-u前置引数があればsmartchrは起動しないようにした。(無意識に結構使ってたみたいで不便だった)

Exuberant ctagsでphpのconstも拾うようにする

こちらのexuberant ctags 日本語対応版そのままではconstを拾ってくれなかったので、
見よう見まねでpatchを書いてみたのですが、その後に気がつきました。
実はこんな感じで--regex-PHPを引数で渡すだけでよかった。。

 $ ctags -R -e --langmap=PHP:.php --php-types=c+f+d \
 --regex-PHP='/^[ \t]*const[ \t]+([[:alnum:]_]+)/\1/d/'

ちなみにEmacsからの利用でしか試してません。

[Emacs][php] リージョン内の行をハッシュのkey形式に変換する

テーブルのカラム名とか、何かのドキュメントに書かれてたりするものとか、
既にどこかで定義されたものをキーとした、何かのハッシュを作りたいということがよくあるので、少しだけ楽にできるようにしました。

(defun php-convert-to-hash-key-region (beg end)
  "Convert selected region as hash key syntax for PHP"
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char beg)
      (while (re-search-forward "^\\s-*\\(.+?\\)\\s-*$" nil t)
        (replace-match "'\\1' => ," nil nil))))
  (indent-region beg end))

例として、MySQLのdbテーブルの各カラム名をキーにした配列を作りたいような時は、
M-x sql-mysql で、sql-interactive-modeで接続して

root@localhost[mysql]> desc db;
+-----------------------+---------------+------+-----+---------+-------+
| Field                 | Type          | Null | Key | Default | Extra |
+-----------------------+---------------+------+-----+---------+-------+
| Host                  | char(60)      | NO   | PRI |         |       |
| Db                    | char(64)      | NO   | PRI |         |       |
| User                  | char(16)      | NO   | PRI |         |       |
...略
| Alter_routine_priv    | enum('N','Y') | NO   |     | N       |       |
| Execute_priv          | enum('N','Y') | NO   |     | N       |       |
+-----------------------+---------------+------+-----+---------+-------+

FieldのところをC-x r r r (copy-rectangle-to-register)で矩形をレジスタに登録して、

<?php
$h = array(
    // 予め、充分空行をあけて置く
    // ...
);
?>

上記のようなコードのarray()の中でC-x r i r (insert-register)すると*1

<?php
$h = array(
Host
Db
User
Select_priv
Insert_priv
Update_priv
Delete_priv
Create_priv
Drop_priv
Grant_priv
References_priv
Index_priv
Alter_priv
Create_tmp_table_priv
Lock_tables_priv
Create_view_priv
Show_view_priv
Create_routine_priv
Alter_routine_priv
Execute_priv
);
?>

のようになり、上記だとHostのHの部分にカーソルがあります。
その後、M-x php-convert-to-hash-key-region とすると

<?php
$h = array(
    'Host' => ,
    'Db' => ,
    'User' => ,
    'Select_priv' => ,
    'Insert_priv' => ,
    'Update_priv' => ,
    'Delete_priv' => ,
    'Create_priv' => ,
    'Drop_priv' => ,
    'Grant_priv' => ,
    'References_priv' => ,
    'Index_priv' => ,
    'Alter_priv' => ,
    'Create_tmp_table_priv' => ,
    'Lock_tables_priv' => ,
    'Create_view_priv' => ,
    'Show_view_priv' => ,
    'Create_routine_priv' => ,
    'Alter_routine_priv' => ,
    'Execute_priv' => ,
);
?>

みたいになって、M-x align-current すると

<?php
$h = array(
    'Host'                  => ,
    'Db'                    => ,
    'User'                  => ,
    'Select_priv'           => ,
    'Insert_priv'           => ,
    'Update_priv'           => ,
    'Delete_priv'           => ,
    'Create_priv'           => ,
    'Drop_priv'             => ,
    'Grant_priv'            => ,
    'References_priv'       => ,
    'Index_priv'            => ,
    'Alter_priv'            => ,
    'Create_tmp_table_priv' => ,
    'Lock_tables_priv'      => ,
    'Create_view_priv'      => ,
    'Show_view_priv'        => ,
    'Create_routine_priv'   => ,
    'Alter_routine_priv'    => ,
    'Execute_priv'          => ,
);
?>

こんな感じになります。※要align設定
少し編集が楽になりました。

*1:矩形を貼り付ける前にその領域の行数分、空行を入れる方法ないかな。

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の関数名補完と連携してみたいなあ。

リージョン内のコードを実行

(defun php-eval (beg end)
  "Run selected region as PHP code"
  (interactive "r")
  (let ((code (concat "<?php " (buffer-substring beg end))))
    (with-temp-buffer
      (insert code)
      (shell-command-on-region (point-min) (point-max) "php")
      )))

emacs lisp勉強ちゅう。
開始タグを付けて実行する方法に悩んだ。

ファイル末尾の改行を削除

巷で話題の非常に便利なyasnippet。すばらしい。
で、仕事用のmysnippetをいろいろ書いてから気づいたのですが、
これって、ファイル末尾の改行も展開時に挿入するんですね。
yasnippetを使っていて、展開後末尾の改行は個人的に必要なかったので、全部除去することに。

取りあえず、指定したディレクトリ以下のファイルの末尾の改行を一括削除。

$ find ./mysnippet-dir -type f -print0 | xargs -0 perl -pi -e 's/\n$// if eof'

あ。でも、svnのファイルとか混じってたら危険なので、

$ find ./mysnippet-dir -type f | grep -v '/\.svn/' | xargs perl -pi -e 's/\n$// if eof'

それかやっぱり、ディレクトリ下って一つづつ、

$ perl -pi -e 's/\n$// if eof' *

でもいいかも。

末尾が改行じゃないファイルが欲しいことなんて殆ど無かったけど、こんなところに需要が。

追記 (02:05)

$ find ./mysnippet-dir -type f ! -path '*/.svn/*' -print0 | xargs -0 perl -pi -e 's/\n$// if eof'

2つ目のよりはこっち?findの勉強になってきた。