2011-08-23
■[Emacs][CoffeeScript] CoffeeScript + Guard::CoffeeScript + Emacs + Flymakeで「Errno::ENOENT: No such file or directory」が出ないように
最近、GuardでTitanium+CoffeeScriptの開発を快適に | ひげろぐを参考にGuard::CoffeeScriptを入れ、coffeescriptでflymakeする - antipopのFlymakeの設定を入れてCoffeeScriptでTitaniumでのアプリ作成を試してます。
Guard::CoffeeScriptでファイル変更の監視・コンパイルをしてると
ERROR: Guard::CoffeeScript failed to achieve its <run_on_change>, exception was: Errno::ENOENT: No such file or directory - coffee/app_flymake.coffee
というエラーが発生して頻繁にGuardが落ちる。
Flymake用の一時ファイルをGuardが検知し、コンパイルしようとするが、すぐに削除されるのでエラーになっているようだ。
そのまま参考になるruby-modeのflymakeでguardが誤動作しないようにする - むしゃくしゃしてやったを参考に、 flymake-create-temp-inplace を flymake-create-temp-with-folder-structure に変更し、同一ディレクトリ内でなく、tempディレクトリにflymake用のファイルを作成するようにした。
以下、diff
(setq flymake-coffeescript-err-line-patterns
'(("\\(Error: In \\([^,]+\\), .+ on line \\([0-9]+\\).*\\)" 2 3 nil 1)))
(defconst flymake-allowed-coffeescript-file-name-masks
'(("\\.coffee$" flymake-coffeescript-init)))
(defun flymake-coffeescript-init ()
(let* ((temp-file (flymake-init-create-temp-buffer-copy
+ 'flymake-create-temp-with-folder-structure))
- 'flymake-create-temp-inplace))
(local-file (file-relative-name
temp-file
(file-name-directory buffer-file-name))))
(list "coffee" (list local-file))))
(defun flymake-coffeescript-load ()
(interactive)
(defadvice flymake-post-syntax-check (before flymake-force-check-was-interrupted)
(setq flymake-check-was-interrupted t))
(ad-activate 'flymake-post-syntax-check)
(setq flymake-allowed-file-name-masks
(append flymake-allowed-file-name-masks
flymake-allowed-coffeescript-file-name-masks))
(setq flymake-err-line-patterns flymake-coffeescript-err-line-patterns)
(flymake-mode t))
(add-hook 'coffee-mode-hook 'flymake-coffeescript-load)
メモ
例えば/Users/foo/local/app/titanium/example/coffee/app.coffeeでエラーがある場合、エラー表示は以下のようになる。
[3] Error: In ../../../../../../../private/var/folders/OQ/OQ*************************/-Tmp-/Users/foo/local/app/titanium/example/coffee/app.coffee, Parse error on line 3: Unexpected 'TERMINATOR'
この/private/var/folders/..というのは何だろう?と見てみると、自分の環境では環境変数TMPDIR, TMP, TEMPを見ているようでmacではこうなってるみたい。
;; flymake.el -> flymake-create-temp-with-folder-structure (defun flymake-create-temp-with-folder-structure (file-name prefix) (unless (stringp file-name) (error "Invalid file-name")) (let* ((dir (file-name-directory file-name)) ;; Not sure what this slash-pos is all about, but I guess it's just ;; trying to remove the leading / of absolute file names. (slash-pos (string-match "/" dir)) (temp-dir (expand-file-name (substring dir (1+ slash-pos)) (flymake-get-temp-dir)))) (file-truename (expand-file-name (file-name-nondirectory file-name) temp-dir)))) ;; flymake.el -> flymake-get-temp-dir (defalias 'flymake-get-temp-dir (if (fboundp 'temp-directory) 'temp-directory (lambda () temporary-file-directory))) ;; files.el -> temporary-file-directory (defcustom temporary-file-directory (file-name-as-directory (cond ((memq system-type '(ms-dos windows-nt)) (or (getenv "TEMP") (getenv "TMPDIR") (getenv "TMP") "c:/temp")) (t (or (getenv "TMPDIR") (getenv "TMP") (getenv "TEMP") "/tmp")))) "The directory for writing temporary files." :group 'files :initialize 'custom-initialize-delay :type 'directory)
参考
2011-08-14
■[Emacs][php] Emacsのphp-modeでalignする その2
4年越しですが Emacsのphp-modeでalignする - てつじんにっき で書いていたものの挙動が気になったので修正し、requireして使うよう改良してphp-align.elという名称でgithubにあげてみました。
tetsujin/emacs-php-align ? GitHub
話が変わりますが最近php-modeは https://github.com/rradonic/php-mode を使っています。オリジナルに比べて色々と機能追加やバグ修正されているようです。*1
と、していたら https://github.com/ejmr/php-mode の方がEmacs同梱になるというのをブコメで知りました。
PHP5.4 対応版。php-mode-version-number は 1.5.1 になってる。と思ったら、あっという間に 1.6 へ。そして Emacs 同梱へ http://comments.gmane.org/gmane.emacs.devel/142507 2011/07/26
こちらも5.4対応など色々と機能追加されているようです。
乗り換えようかな。
2011-06-10
■[Emacs][Symfony2] anything-project.elでSymfony2
anything-project.elにSymfony2用のプロジェクトを定義してみた。
(ap:add-project :name 'symfony2 :look-for 'ap:symfony2-root-detector :grep-extensions '("\\.php" "\\.twig" "\\.yml") :exclude-regexp '("/cache/" "/logs/")) (defun ap:symfony2-root-detector (files) (ap:all-files-exist '("app" "bin" "src" "vendor" "web") files))
使ってみて、M-x anything-project-grepした時にcacheディレクトリなどが対象になって少し困ったので、ap:add-project-exという関数の追加とap:build-grep-commandを上書きして:grep-ignore-dirsというキーワード引数で除外するディレクトリを定義できるようにした。*1
最終的に以下のような感じ。
(require 'anything-project) (defun* ap:add-project-ex (&key name look-for (include-regexp ".*") (exclude-regexp nil) (exclude-directory-regexp nil) (grep-extensions nil) (grep-ignore-dirs nil)) (ap:add-project :name name :look-for look-for :include-regexp include-regexp :exclude-regexp exclude-regexp :exclude-directory-regexp exclude-directory-regexp :grep-extensions grep-extensions) (nconc (cdr (assq name ap:projects)) (list (cons :grep-ignore-dirs grep-ignore-dirs)))) (defun ap:get-grep-extra-options (key) (let ((grep-ignore-dirs (ap:get-project-data key :grep-ignore-dirs))) (mapconcat 'identity (mapcar (lambda (dir) (concat "--ignore-dir=" dir)) grep-ignore-dirs) " "))) (defun ap:build-grep-command (key) (let ((grep-extensions (ap:get-grep-extensions key)) (ack-command (ap:get-ack-command)) (grep-extra-options (ap:get-grep-extra-options key)) (xargs-command (ap:get-xargs-command)) (egrep-command (ap:get-egrep-command))) (concat ack-command " -afG " grep-extensions " " grep-extra-options " | " xargs-command " " egrep-command " -Hin " "%s"))) ;; PHP Symfony2 (ap:add-project-ex :name 'symfony2 :look-for 'ap:symfony2-root-detector :grep-extensions '("\\.php" "\\.twig" "\\.yml") :grep-ignore-dirs '("cache" "logs") ; これを新たに追加 :exclude-regexp '("/cache/" "/logs/")) (defun ap:symfony2-root-detector (files) (ap:all-files-exist '("app" "bin" "src" "vendor" "web") files))
そしてsymfony2.elが欲しくなってきた。
*1:emacs lisp知識不足でdefunしたけどdefadviceで出来るものなのかな?
2011-06-05
■[Symfony2][php][メモ] PHPer.jpでSymfony2のblogチュートリアルを動かすためにやったこと
PHPer.jp | PHP クラウドプラットフォームサービスでblogチュートリアル | Symfony2 Blog チュートリアルを動してみたので、雑なまとめですが、やった設定の備忘録。
最終的に下記のような構成に。
/path/to/approot/ - .phper/ # phper.jp用設定ファイル群 - Symfony/ # blogチュートリアルアプリケーション - phpMyAdmin/ # 管理用phpMyAdmin
1. phper.jpに登録
http://phper.jp/quickstartを見てユーザ登録〜デプロイまで進める。
ドキュメントルートは Symfony/web にしておく。
2. phperコマンドのインストール
Ruby製のphperというCLIツールが用意されているので、PHPerのためのかんたんRubyインストール | PHPer.jp開発ブログを参考にRuby環境整えて
$ gem install phper
で、phperコマンドをインストール。
3. blogチュートリアルの作成
ローカルでblogチュートリアルを読み進めながら、/path/to/approot/Symfony下に作成。
4. phper.jp用に設定の修正
■ Symfony/web/app_dev.php、Symfony/web/config.phpのアクセス制限変更
PHPer.jpのシステム構成 | PHPer.jp開発ブログの構成を見るとフロントにproxyが居るので、
$_SERVER['REMOTE_ADDR']だけじゃなくて、$_SERVER['HTTP_X_FORWARDED_FOR']も見てやるようにする。
# /path/to/approot/Symfony/web/app_dev.php # /path/to/approot/Symfony/web/config.php <?php ... $remoteIpAddresses = array_merge( (array)@$_SERVER['REMOTE_ADDR'], array_map('trim', explode(',', @$_SERVER['HTTP_X_FORWARDED_FOR'])) ); $allowIpAddresses = array( '127.0.0.1', '::1', 'your-global-ip-address', // 自分の接続元グローバルIP ); if (count(array_intersect($remoteIpAddresses, $allowIpAddresses)) === 0) { header('HTTP/1.0 403 Forbidden'); die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.'); } ...
■ .gitignore
下記をgitの管理対象外にする。
# /path/to/approot/Symfony/.gitignore */logs/* */cache/* app/config/parameters.ini
parameters.iniはローカル、phper.jp上で異なるのでgit管理対象外として、下記のようにした。
# /path/to/approot/Symfony/app/config/parameters.phper.ini
[parameters]
database_driver = pdo_mysql
database_host = db.phper.jp
database_name = yourname_appname
database_user = yourname
database_password = password
mysqlの接続情報などは、phper infoで確認できる。
$ phper info yourname-appname --> gitosis@git.phper.jp:yourname/appname.git --> mysql://yourname:password@db.phper.jp/yourname_appname
5. phpMyAdminを用意する
MySQLのコマンドラインクライアントが使えないのでphpMyAdminを入れる。
http://www.phpmyadmin.net/home_page/downloads.phpからDownloadして/path/to/approot/phpMyAdminに設置する。
また、できればphpMyAdminにはhttpsでアクセスしたい。
PHPer.jpのシステム構成 | PHPer.jp開発ブログを見ると、phper.jpではエッジレイヤのNginxがSSLアクセラレーションするようなので、
SSL-Proxy下でphpMyAdminをSSLで動かす設定も入れておいた。
config.sample.inc.phpをコピーしてconfig.inc.phpを作成。下記を設定。
# /path/to/approot/phpMyAdmin/config.inc.php <?php .. // 書き換え $cfg['Servers'][$i]['host'] = 'db.phper.jp'; // SSL-Proxy下でSSLで動かす設定。 http://wiki.phpmyadmin.net/pma/Config#PmaAbsoluteUri $cfg['PmaAbsoluteUri'] = 'https://yourappname.phper.jp/pma/';
6. phper設定
プロジェクト直下の.phper/ディレクトリにいろいろとスクリプトや設定を記述できる。
詳しくはマニュアル参照。http://phper.jp/manual
■ deployの設定: /path/to/approot/.phper/deploy
#!/bin/sh ROOT_DIR=$(cd $(dirname $0)/..; pwd) # キャッシュの削除 rm -rf $ROOT_DIR/Symfony/app/cache/* # parameters.iniのシンボリックリンク作成 ln -sf $ROOT_DIR/Symfony/app/config/parameters.phper.ini $ROOT_DIR/Symfony/app/config/parameters.ini
■ rsync除外設定: /path/to/approot/.phper/rsync_exclude.txt
Symfony/app/cache/* Symfony/app/logs/*
■ アプリケーションサーバのApacheの設定: /path/to/approot/.phper/httpd.conf
/pmaでphpMyAdminにアクセス。後、Digest認証も一応かけておく。
Alias /pma "/var/www/sites/yourname-appname/phpMyAdmin" <Location pma> AuthType Digest AuthName "Secret Zone" AuthDigestDomain pma AuthDigestProvider file AuthUserFile "/var/www/sites/yourname-appname/.phper/.htdigest" Require valid-user </Location>
$ htdigest -c /path/to/approot/.phper/.htdigest "Secret Zone" user New password: **** Re-type new password: ****
7. デプロイする
$ git push
8. phper.jp上のデータベースにschema定義を流しこむ
blogチュートリアルのschema定義をdumpして、phpMyAdminから流しこむ。
$ app/console doctrine:schema:create --dump-sql ATTENTION: This operation should not be executed in an production enviroment. CREATE TABLE Post (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, body LONGTEXT NOT NULL, createdAt DATETIME NOT NULL, updatedAt DATETIME NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB
後はブラウザから動作確認して動くか確認。
実際はこんなにすんなり行かずに、トライアンドエラーを繰り返しました。PaaS楽しい!!
2011-05-23
■[php][symfony][doctrine] symfony1.4+Doctrineでbuild-formsで生成されるコードに独自WidgetやValidatorをセットする
開発が進むに連れてフォームで
- 表示するエラーメッセージを全体的に変更したい
- 全角英数を半角英数に変換したい
- その他色々と機能を拡張したい
などと、全体的な挙動を変えたいことが多くて、
$ symfony doctrine:build-forms
で生成される、lib/form/doctrine/base/Base***Form.class.php 内のWidget, Validatorを独自拡張したものに変更できないかなと思う事が多く、調べてみました。*1
$ symfony doctrine:build-forms --generator-class=myDoctrineFormGenerator
と、build-formsの--generator-classにクラス名を渡してGeneratorクラスでいろいろとやれば良いのですが、
オプション付けるのを忘れたり、他の人も触ることを考えるとデフォルト値を置き換えられないかなーと思って調べました。
既存taskを継承すれば良いかなと思い継承しましたが下記のようなエラーが。
$ cat lib/task/myDoctrineBuildFormsTask.class.php <?php class myDoctrineBuildFormsTask extends sfDoctrineBuildFormsTask { } $ symfony doctrine:build-forms PHP Fatal error: Uncaught exception 'sfCommandException' with message 'The task named "doctrine:build-forms" in "myDoctrineBuildFormsTask" task is already registered by the "sfDoctrineBuildFormsTask" task.' in /usr/local/apps/example/lib/vendor/symfony/lib/command/sfCommandApplication.class.php:142
...と、taskの正しい拡張方法がわかりませんでした。
前置きが長くなりましたが無理やりオプション書き換えてみました。BKっぽいですがメモ。
config/ProjectConfiguration.class.php
command.filter_optionsイベントを捕捉して、generator-classを置き換えます。
<?php require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); class ProjectConfiguration extends sfProjectConfiguration { public function setup() { $this->dispatcher->connect( 'command.filter_options', array($this, 'commandFilterOptionsEventHook') ); } public function commandFilterOptionsEventHook(sfEvent $event, $options) { $task = $event->getSubject(); $parameters = $event->getParameters(); $commandManager = $parameters['command_manager']; if ($task->getFullName() === 'doctrine:build-forms') { $commandManager->getOptionSet() ->getOption('generator-class') ->setDefault('myDoctrineFormGenerator'); } return $options; } }
lib/generator/myDoctrineFormGenerator.class.php
- $extendedWidgetFormClasses
- $extendedValidatorClasses
のkey=>valueに置き換えたいクラスの定義を書きます。
<?php class myDoctrineFormGenerator extends sfDoctrineFormGenerator { protected $extendedWidgetFormClasses = array( 'sfWidgetFormDate' => 'myWidgetFormTextDate', // 日付のinput text入力 ); protected $extendedValidatorClasses = array( 'sfValidatorInteger' => 'myValidatorInteger', // 全角英数->半角英数 'sfValidatorDate' => 'myValidatorDate', // 全角英数->半角英数 ); public function getWidgetClassForColumn($column) { return $this->getExtendedClass( parent::getWidgetClassForColumn($column), $this->extendedWidgetFormClasses ); } public function getValidatorClassForColumn($column) { return $this->getExtendedClass( parent::getValidatorClassForColumn($column), $this->extendedValidatorClasses ); } protected function getExtendedClass($class, $extendedClasses) { return isset($extendedClasses[$class]) ? $extendedClasses[$class] : $class; } }
として、やるとsymfony doctrine:build-formsしてやると、生成されるソース内のWidget,Validatorが置き換わります。
Person: columns: name: string(255) date_of_birth: date allowance: integer(4)
public function setup()
{
$this->setWidgets(array(
'id' => new sfWidgetFormInputHidden(),
'name' => new sfWidgetFormInputText(),
'date_of_birth' => new myWidgetFormTextDate(),
'allowance' => new sfWidgetFormInputText(),
));
$this->setValidators(array(
'id' => new sfValidatorChoice(array('choices' => array($this->getObject()->get('id')), 'empty_value' => $this->getObject()->get('id'), 'required' => false)),
'name' => new sfValidatorString(array('max_length' => 255, 'required' => false)),
'date_of_birth' => new myValidatorDate(array('required' => false)),
'allowance' => new myValidatorInteger(array('required' => false)),
));
...
ちなみに今回、例として置き換えてみたWidgetとValidatorは以下です。参考までに。
lib/widget/myWidgetFormTextDate.class.php
<?php class myWidgetFormTextDate extends sfWidgetFormDate { protected function renderDayWidget($name, $value, $options, $attributes) { $attributes['size'] = '4'; $attributes['maxlength'] = 2; return $this->renderWidgetFormInput($name, $value, $attributes); } protected function renderMonthWidget($name, $value, $options, $attributes) { $attributes['size'] = '4'; $attributes['maxlength'] = 2; return $this->renderWidgetFormInput($name, $value, $attributes); } protected function renderYearWidget($name, $value, $options, $attributes) { $attributes['size'] = '6'; $attributes['maxlength'] = 4; return $this->renderWidgetFormInput($name, $value, $attributes); } protected function renderWidgetFormInput($name, $value, $attributes) { $widget = new sfWidgetFormInput(array(), $attributes); return $widget->render($name, $value); } }
lib/validator/myValidatorDate.class.php
<?php class myValidatorDate extends sfValidatorDate { protected function doClean($value) { if (is_string($value)) { $value = $this->filter($value); } else if (is_array($value)) { $value = array_map(array($this, 'filter'), $value); } return parent::doClean($value); } protected function filter($value) { return mb_convert_kana($value, 'a'); } }
lib/validator/myValidatorInteger.class.php
<?php class myValidatorInteger extends sfValidatorInteger { protected function doClean($value) { if ($value !== null) { $value = mb_convert_kana($value, 'a'); } return parent::doClean($value); } }