ローカルでのマニュアル検索

Emacsのphp-modeでは、php-search-documentation(C-c C-f)でPHPのマニュアルを検索可能。

バッファ上の関数名にカーソルを合わせて"C-c C-f"でマニュアルを表示出来るのは便利。問題は、作業環境でネットワークがつながらなかったり、サーバ側に問題があった時にWWW相手ではどうしようもないということ。

そこで、オフラインハックのすすめにのって『マニュアルくらいは手元に置く』事を心がけてみます。基本的にCHMファイルを手元に置いていけば済む話ですが、折角なので『Emacsからの検索』である程度対応したいと思います。

php-search-documentationでは、php-search-urlに指定したURLに問い合わせを実行するので、これを自前のURLに置き換えます。

(add-hook 'php-mode-user-hook
  '(lambda ()
             ; (略)
             (setq php-search-url "http://man.local/php/")
             ; (略)
     ))

これで、例えば"str_replace"の上にカーソルを置いて"C-c C-f"とすれば、"http://man.local/php/str_replace"にリクエストが飛びます。ここで、"/php/"へのリクエストを全て検索スクリプトにリライトするなどして検索結果を得ます。

以下のようなリライトルールを指定しました。

 
        RewriteEngine On
        # PHP function search
        RewriteRule ^php/([\d\w]*)$ php/search.php?q=$1 [L]

以下は"/php/search.php"とした検索スクリプトです。

<?php
 
/**
 * Search someone from local PHP documentations
 *
 *   - 1) from PHP functions
 *   - 2) from PEAR packages
 *   - 3) grep PHP manuals
 *   - 4) grep PEAR manuals
 *   - 5) Document Not Found
 *
 *   Directories:
 *     - /path/to/DocumentRoot
 *       - /php
 *         - /search.php <self>
 *         - /html
 *           - /php <PHP Manual; html>
 *           - /pear <PEAR Manual; html>
 *
 *   Rewrite use(Apache):
 *     - ex)
 *        RewriteEngine On
 *        RewriteRule ^php/([\d\w]*)$ php/search.php?q=$1 [L]
 *
 * @author koshigoe <KoshigoeBushou@gmail.com>
 * @see    PHP: Download documentation <http://jp.php.net/download-docs.php>
 * @see    PEAR :: Documentation <http://pear.php.net/manual/>
 *
 */
 
define("SEARCH_PATH", realpath(dirname(__FILE__) . "/html/"));
 
if (isset($_GET["q"])) {
    $q = "";
}
$q = preg_replace("|[^\d\w]|", "", $_GET["q"]);
 
$search = new PHP_Manual_Search(SEARCH_PATH);
 
if (($content = $search->searchPhpFunction($q)) != null) {
    echo $content;
} else if (($content = $search->searchPearPackage($q)) != null) {
    echo $content;
} else if (preg_match("/[A-Z]/", $q) == 0 and
           ($content = $search->grepPhpManual($q)) != null) {
    echo $content;
} else if (preg_match("/[A-Z]/", $q) > 0 and
           ($content = $search->grepPearManual($q)) != null) {
    echo $content;
} else {
    header("HTTP/1.x 404 Not Found");
    echo '<h1>404 Error Document Not Found: ' . $q . '</h1>';
}
exit;
 
 
class PHP_Manual_Search
{
 
    public function __construct($baseDir)
    {
        if (!file_exists($baseDir) or !is_dir($baseDir)) {
            throw new Exception('base directory not faound.');
        }
        $this->baseDir = $baseDir;
    }
 
    public function searchPhpFunction($search)
    {
        $fileName = "function." . str_replace("_", "-", $search) . ".html";
        $path     = $this->baseDir . "/php/" . $fileName;
        if (is_readable($path)) {
            return file_get_contents($path);
        } else {
            return null;
        }
    }
 
    public function searchPearPackage($search)
    {
        if (strtolower($search) == "pear" and
            is_readable($this->baseDir . "/pear/package.pear.html")) {
            return file_get_contents($this->baseDir . "/pear/package.pear.html");
        }
        $subject = '.*/package\.[^\.]*\.' . str_replace("_", "-", strtolower($search)) . '\.html';
        $where   = $this->baseDir . "/pear/";
        $results = array();
        exec("find $where -regex \"$subject\"", $results);
        if (sizeof($results) > 0) {
            sort($results);
            if (is_readable($results[0])) {
                return file_get_contents($results[0]);
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
 
    public function grepManual($search, $dir, $title = "Search results")
    {
        $results = array();
        $content = "<html>
<head>
  <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />
  <title>$title</title>
</head>
<body>";
        exec("grep -A3 -B3 -r $q " . $dir, $results);
        if (sizeof($results) > 0) {
            $content .= "<h1>Search results</h1>\n";
            $content .= "<dl>\n";
            $data = array();
            foreach ($results as $result) {
                if (preg_match("|^([\/\.\d\w\-]+)\:\s*(.+)$|", $result, $match)) {
                    $link = basename($match[1]);
                    if (!isset($data[$link])) {
                        $data[$link] = "";
                    }
                    $data[$link] .= str_replace(array("<", ">"), "", strip_tags($match[2]));
                }
            }
            foreach ($data as $link => $desc) {
                $content .= '<dt><a href=html/"' . $link . '">' . $link . '</a></dt>' . "\n";
                $content .= '<dd>' . $desc . '</dd>' . "\n";
            }
            $content .= "</dl>\n</body>\n</html>";
        } else {
            return null;
        }
    }
 
    public function grepPhpManual($search)
    {
        $dir = realpath($this->baseDir . "/php/");
        return $this->grepManual($search, $dir, "PHP manual");
    }
 
    public function grepPearManual($search)
    {
        $dir = realpath($this->baseDir . "/pear/");
        return $this->grepManual($search, $dir, "PEAR manual");
    }
 
}
 
?>

『Emacsからの検索』にこだわっての結果が上記スクリプトですが、二兎を追うものなんとやらになりそうな気もします。あまりこだわらず、オンラインやCHMなどを利用しつつ快適なスタイルを身につけていきたいと思います。


IDEを試してみるべきでしょうか?Emacsにこだわりすぎているのかも。

プロフィール

このブログ記事について

このページは、koshigoeが2007年1月25日 00:04に書いたブログ記事です。

ひとつ前のブログ記事は「PHPでURIをパーセントエンコード」です。

次のブログ記事は「PEAR::Text_CAPTCHAを試す」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。