2noの日記

メモ用

【CentOS6】yum で最新の git を入れる

wing リポジトリを使う。
groonga+mroonga なども yum でインストール出来る様になるので便利。

epel リポジトリの導入

$ rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

wing リポジトリの導入

$ cd /etc/yum.repos.d/
$ wget http://wing-repo.net/wing/6/EL6.wing.repo

git のインストール

$ yum --enablerepo=wing install git

結果

==============================================================================================================================================================
 パッケージ                          アーキテクチャ                    バージョン                                       リポジトリー                     容量
==============================================================================================================================================================
更新:
 git                                 x86_64                            2.4.1-1.el6_34.wing                              wing                            8.2 M
依存性関連での更新をします。:
 perl-Git                            noarch                            2.4.1-1.el6_34.wing                              wing                             34 k

【gulp】タスク内で run-sequence を使うなら気をつけること

基本的にタスク内で run-sequence を使うなら、引数のコールバックを最後に指定するべき。

つまりこんな感じ。

gulp.task('hoge-fuga', function(callback) {
  runSequence('hoge', 'fuga', callback); // <- 引数 callback を最後に指定する
});

こうしないと同期されない。
次の場合は処理の順番が保証されず、期待通りに動かないことがあるので注意したい。

gulp.task('hoge-fuga', function() {
  runSequence('hoge', 'fuga');
});

gulp.task('task', function() {
  runSequence('hoge-fuga', 'piyo');
});

上記だと、task を実行した際、hoge-fuga 内のタスクが非同期で動き出し、後に続く piyo がこれらの終わりを待たずに実行される。
処理の重さによっては、一番最後の piyo が先に終わる場合もあり得る。

watch とか、後に続く処理が無ければコールバックは無くても良い。

gulp-devtools であまり黒い画面を触らずに済ます

使う機会ないけどメモ。

個人的にローカルインストールで済ませたいので、npm の scripts プロパティに設定しておく。

▼ gulp-devtools のインストール

$ npm install gulp-devtools --save-dev

▼ package.json

{
  "scripts": {
    "gulp-devtools": "gulp-devtools"
  }
}

▼ gulpfile.js

var gulp = require('gulp');

...

module.exports = gulp;

後は以下を実行するだけ。

$ npm run gulp-devtools

以降は上記のコマンドを打つ時だけ黒い画面に触ることになる。

CakePHP 2.x でバリデート前にまとめてデータを加工する

最近業務で CakePHP を使い始めたので備忘録的な。

FuelPHP などに慣れていると、バリデートのルールと一緒にフィルタ処理したい衝動に駆られる。
こういった機構は CodeIgniter や Phalcon など実装されてたと思う(Phalcon はうろ覚え)

CakePHP においてバリデート時にフィルタを掛けるなら、beforeValidate というコールバックメソッドを用意してあげれば良いみたい。

汎用的に使おうと思ったらこんな感じ?

テスト環境:
CakePHP 2.6.2
PHP 5.6.6

▼ AppModel.php

<?php
/**
 * Application model for Cake.
 *
 * This file is application-wide model file. You can put all
 * application-wide model-related methods here.
 *
 * @link          http://cakephp.org CakePHP(tm) Project
 * @package       app.Model
 * @since         CakePHP(tm) v 0.2.9
 */

App::uses('Model', 'Model');

/**
 * Application model for Cake.
 *
 * Add your application-wide methods in the class below, your models
 * will inherit them.
 *
 * @package       app.Model
 */
class AppModel extends Model {

/**
 * バリデート前に実行するフィルタ一覧
 *
 * @var array
 */
    public $filters = [];

/**
 * バリデート前に実行するイベント
 *
 * @param array $options オプション
 * @return bool
 */
    public function beforeValidate($options = []) {
        $result = parent::beforeValidate($options);
        if ($result && $this->filters) {
            array_walk($this->data[$this->name], function (&$value, $key) {
                if (!isset($this->filters[$key])) {
                    return;
                }
                foreach ($this->filters[$key] as $filter) {
                    if (is_string($filter)) {
                        $value = call_user_func($filter, $value);
                    } elseif (is_array($filter)) {
                        $callback = array_shift($filter);
                        array_unshift($filter, $value);
                        $value = call_user_func_array($callback, $filter);
                    }
                }
            });
        }
        return $result;
    }
}

あとは継承先で、$filters プロパティを用意して指定してあげるだけ。

<?php
class Hoge extends AppModel {
    public $filters = [
        'title' => ['trim', [['self', 'upper']]],
    ];

/**
 * 小文字を大文字に変換します。
 *
 * @param string $str 対象の文字列
 * @return string 変換後の文字列
 */
    public static function upper($str) {
        return strtoupper($str);
    }
}

この場合、title というデータにビルトイン関数 trim と、Hoge#upper を実行することになる。

Windows で Flow を触ってみる

追記: 2014/11/21 19:34
こんなページが出来てました。
http://www.ocamlpro.com/pub/ocpwin/flow-builds/
更にバージョンアップしているようです。


今巷で話題の flow を触ろうと思ったのに Windows 版が無い!と嘆いてたら、こんなコメントが投稿されてた。

Please provide Windows binaries · Issue #6 · facebook/flow · GitHub

Windows 用にビルドしてくれているみたいなので早速試してみる。
まずはファイルをダウンロード
http://www.ocamlpro.com/pub/ocpwin/flow-builds/flow-simple-windows-20141119.zip

zip を解凍すると中身はこんな感じ

.
├── flow32.exe
├── flow64.exe
├── lib
│   ├── bom.js
│   ├── core.js
│   ├── dom.js
│   └── react.js
└── Readme.txt

次に環境変数 FLOWLIB に lib ディレクトリのパスを指定しておく。

Cygwin:

$ export FLOWLIB=c:/cygwin/home/user/flow-simple-windows/lib

Cmd:

$ set FLOWLIB=c:/cygwin/home/user/flow-simple-windows/lib

後は実行するだけ。

とりあえず、以下のサンプルから 01_HelloWorld を試す。
https://github.com/facebook/flow/tree/master/examples

まずは失敗例

hello.js

/* @flow */

function foo(x) {
  return x*10;
}

foo("Hello, world!");
{ 01_HelloWorld }  » ~/flow-simple-windows/flow64.exe single .

C:\Users\hoge\.babun\cygwin\home\hoge\flow-master\examples\01_HelloWorld\./hello.js:7:5,19: string
This type is incompatible with
  C:\Users\hoge\.babun\cygwin\home\hoge\flow-master\examples\01_HelloWorld\./hello.js:4:10,13: number

Found 1 error

次に成功例

hello.js

/* @flow */

function foo(x) {
  return x*10;
}

// This is fine, because we're passing a number now
foo(10);
{ 01_HelloWorld }  » ~/flow-simple-windows/flow64.exe single .

Found 0 errors

ちゃんと動いているっぽい。

興味のある方は試してみては。
※ただし自己責任でお願いします。

【wxPython】TextCtrl と StyledTextCtrl の change イベントの違い

TextCtrl だと EVT_TEXT を使う。

...

tc_text = wx.TextCtrl(frame, wx.ID_ANY)
tc_text.Bind(wx.EVT_TEXT, OnChange)

...

StyledTextCtrl では EVT_STC_CHANGE を使う。

import wx.stc as stc

...

stc_text = wx.StyledTextCtrl(frame, wx.ID_ANY)
stc_text.Bind(stc.EVT_STC_CHANGE, OnChange)

...

EVT_CHAR で監視とか必要なかった。

【wxPython】メニューバーを付ける

追記: 2014/11/20 21:41
調べてみたら、もっとたくさん ID 用意されてた。
SpecialIDs - wxPyWiki
あと、新たに ID を追加する場合は wx.NewId で生成する方が良いようだ。


メニューを追加する際は ID が必要だが、一部は事前に用意されている。

  • wx.ID_UNDO
  • wx.ID_REDO
  • wx.ID_CUT
  • wx.ID_COPY
  • wx.ID_PASTE

新たに ID を用意するなら、別にこれらの ID を必ず使う必要はない。

メニューの UI の更新に関しては、wx.EVT_UPDATE_UI イベントを使うと良い。

# -*- coding: utf-8 -*-

import wxversion
wxversion.select("3.0")
import wx
import wx.html2
import wx.stc as stc
import markdown


#==============================================================================
# 定数群
#------------------------------------------------------------------------------

MENU_FILE_CLOSE = 1


#==============================================================================
# 関数群
#------------------------------------------------------------------------------

def update():
    timer.Start(milliseconds=100, oneShot=True)

def update_preview():
    # 変数 html が空文字だと SetPage で反映されないので、html 要素など加えておく
    html = """\
        <!DOCTYPE html>
        <html lang="ja">
            <head><meta charset="utf-8"></head>
            <body>%s</body>
        </html>
    """ % convert_str_to_html(text.GetValue())
    browser.SetPage(html, "")

def convert_str_to_html(str):
    codehilite = "codehilite(force_linenos=True, guess_lang=False, css_class=syntax)"
    return markdown.markdown(str, ["extra", codehilite])

def exit_app():
    dialog = wx.MessageDialog(frame,
                              u"ウィンドウを閉じますか?", u"確認",
                              wx.OK|wx.CANCEL|wx.ICON_QUESTION)
    result = dialog.ShowModal()
    if result == wx.ID_OK:
        frame.Destroy()

# イベントハンドラ
#------------------------------------------------------------------------------

def close_handler(event):
    exit_app()

def char_handler(event):
    update()
    event.Skip()

def timer_handler(event):
    update_preview()
    event.Skip()

def update_ui_handler(event):
    menu_edit.Enable(id=wx.ID_UNDO,  enable=text.CanUndo())
    menu_edit.Enable(id=wx.ID_REDO,  enable=text.CanRedo())
    menu_edit.Enable(id=wx.ID_CUT,   enable=text.CanCut())
    menu_edit.Enable(id=wx.ID_COPY,  enable=text.CanCopy())
    menu_edit.Enable(id=wx.ID_PASTE, enable=text.CanPaste())

def menu_handler(event):
    id = event.GetId()
    if id == MENU_FILE_CLOSE:
        exit_app()
        return
    elif id == wx.ID_UNDO:
        text.Undo()
    elif id == wx.ID_REDO:
        text.Redo()
    elif id == wx.ID_CUT:
        text.Cut()
    elif id == wx.ID_COPY:
        text.Copy()
    elif id == wx.ID_PASTE:
        text.Paste()
    update()


#==============================================================================
# 処理
#------------------------------------------------------------------------------

if __name__ == "__main__":
    app = wx.App()

    # 多重起動チェック
    instance_name = u"%s-%s" % (app.GetAppName(), wx.GetUserId())
    instance = wx.SingleInstanceChecker(instance_name)
    if instance.IsAnotherRunning():
        exit()

    frame = wx.Frame(None, wx.ID_ANY, "Markdown test", size=(800, 600))

    menu_bar = wx.MenuBar()

    menu_file = wx.Menu()
    menu_file.Append(MENU_FILE_CLOSE, u"閉じる\tCtrl+Q")
    menu_bar.Append(menu_file, u"ファイル")

    menu_edit = wx.Menu()
    menu_edit.Append(wx.ID_UNDO, u"取り消し\tCtrl+Z")
    menu_edit.Append(wx.ID_REDO, u"やり直し\tCtrl+Shift+Z")
    menu_edit.AppendSeparator()
    menu_edit.Append(wx.ID_CUT, u"カット\tCtrl+X")
    menu_edit.Append(wx.ID_COPY, u"コピー\tCtrl+C")
    menu_edit.Append(wx.ID_PASTE, u"ペースト\tCtrl+V")
    menu_bar.Append(menu_edit, u"編集")
    frame.Bind(wx.EVT_MENU, menu_handler)
    frame.SetMenuBar(menu_bar)

    sw    = wx.SplitterWindow(frame, -1, style=wx.SP_LIVE_UPDATE)
    pane1 = wx.Panel(sw, -1)

    text = stc.StyledTextCtrl(pane1, wx.ID_ANY)
    text.SetMarginType(1, stc.STC_MARGIN_NUMBER)
    text.SetMarginWidth(1, 30)
    text.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "fore:#999999")
    text.SetLexer(stc.STC_LEX_MARKDOWN)
    text.Bind(wx.EVT_CHAR, char_handler)

    pane1_sizer = wx.BoxSizer()
    pane1_sizer.Add(text, 1, wx.EXPAND, 10)
    pane1.SetSizer(pane1_sizer)

    pane2 = wx.Panel(sw, -1)
    browser = wx.html2.WebView.New(pane2)

    pane2_sizer = wx.BoxSizer()
    pane2_sizer.Add(browser, 1, wx.EXPAND, 10)
    pane2.SetSizer(pane2_sizer)

    sw.SetSashGravity(0.5)
    sw.SplitVertically(pane1, pane2)

    timer = wx.Timer(frame)
    frame.Bind(wx.EVT_TIMER, timer_handler, timer)
    frame.Bind(wx.EVT_UPDATE_UI, update_ui_handler)
    frame.Bind(wx.EVT_CLOSE, close_handler)

    frame.Show()
    app.MainLoop()

f:id:wakuworks:20141119124037p:plain