人生シーケンスブレイク

シーケンスブレイク(Sequence breaking、シークエンスブレイクとも)とは、テレビゲームにおいて開発が想定している攻略ルートを逸脱し、ショートカットする行為のことである。

gulpで複数サイトを同時にBrowsersyncで立ち上げる

最近開発にはめっきりBrowsersyncを利用している。 Browsersync - Time-saving synchronised browser testing

通常Webサービスを開発する場合、ユーザ画面と管理画面などと複数サービスを開発するケースが多いので、一つのだけ

$ browser-sync start --config bs-config.js

で立ち上げるのはしんどいものだ。また、他のタスクも走らせるケースが多いので単体でbrowser-syncコマンドで都度立ち上げることもほぼない。

gulpからBrowsersyncを立ち上げる

var gulp = require('gulp');
var browserSync = require('browser-sync').create();
var browserSyncAdmin = require('browser-sync').create();
var browserSyncDefaultSettings = {
  'https': true,
};

function getRewriteRules(matchRule, replaceString) {
  var obj = {};
  obj.match = matchRule;
  obj.fn = function(match) {
    return replaceString;
  };

  return [obj];
}

gulp.task('browser-sync-example.jp', function(){
  var exampleSettings = browserSyncDefaultSettings;
  exampleSettings.files = ['sites/example.jp/public/**/*', 'sites/example.jp/views/**/*.html'];
  exampleSettings.https = true;
  exampleSettings.port  = 3000;
  exampleSettings.proxy = 'https://example.jp';
  exampleSettings.ui = {
    'port': 3001,
    'weinre': {
      'port': 8080
    }
  };
  exampleSettings.rewriteRules = getRewriteRules(/example\(サービス名\)/g, 'さーびすめい');
  browserSync.init(exampleSettings);
});

gulp.task('default', ['browser-sync-example.jp']);

FrameWork内のjs, css, scssファイルとHTMLtemplateファイルを監視対象として、httpsで立ち上げる場合には上記のような形で書いている。 オプション設定は下記を参照。

Browsersync options

$ gulp

複数のサイトをBrowsersyncで立ち上げる

var gulp = require('gulp');
var browserSync = require('browser-sync').create();
var browserSyncAdmin = require('browser-sync').create();
var browserSyncDefaultSettings = {
  'https': true,
};

function getRewriteRules(matchRule, replaceString) {
  var obj = {};
  obj.match = matchRule;
  obj.fn = function(match) {
    return replaceString;
  };

  return [obj];
}

gulp.task('browser-sync-example.jp', function(){
  var exampleSettings = browserSyncDefaultSettings;
  exampleSettings.files = ['sites/example.jp/public/**/*', 'sites/example.jp/views/**/*.html'];
  exampleSettings.https = true;
  exampleSettings.port  = 3000;
  exampleSettings.proxy = 'https://example.jp';
  exampleSettings.ui = {
    'port': 3001,
    'weinre': {
      'port': 8080
    }
  };
  exampleSettings.rewriteRules = getRewriteRules(/example\(サービス名\)/g, 'さーびすめい');
  browserSync.init(exampleSettings);
});

gulp.task('browser-sync-admin.example.jp', function(){
  var adminExampleSettings = browserSyncDefaultSettings;
  adminExampleSettings.files = ['sites/admin.example.jp/public/**/*', 'sites/admin.example.jp/views/**/*.html'];
  adminExampleSettings.https = true;
  adminExampleSettings.port  = 3002;
  adminExampleSettings.proxy = 'https://admin.example.jp';
  adminExampleSettings.ghostMode = false;
  adminExampleSettings.ui = {
    'port': 3003,
    'weinre': {
      'port': 8080
    }
  };
  adminExampleSettings.rewriteRules = getRewriteRules(/サービス名管理画面/g, 'さーびすめい管理画面');
  browserSyncAdmin.init(adminExampleSettings);
});

gulp.task('default', ['browser-sync-example.jp', 'browser-sync-admin.example.jp']);

この場合には gulp実行時に複数サイト同時に稼働するようになる。

Vimのテキストオブジェクトを本気出して纏めてみた

Vimでエディットするにあたり、ダブルクォートで囲まれた部分をシングルクォートにしたいとか、囲まれた部分の内側を置換したいとか、いい加減テキストオブジェクトを使いこなしたいなと思ったので調べつつ憶えてみる。

Vimのコマンドは何らかの単語の頭文字であるケースが殆どなので、コマンドの意味も纏めてみた。

そもそもテキストオブジェクトとは

オブジェクト単位で選択 *object-select* *text-objects*
*v_a* *v_i*

次のものはビジュアルモードかオペレータコマンドの後でのみ使うことができる一連の コマンドを示しています。
"a" で始まるコマンドは "a" (1つの) まとまりをホワイトスペースを含めて選択します。
"i" で始まるコマンドはまとまりの "inner" (内部) をホワイトスペースを含まずに選択するか、もしくはホワイトスペースのみを選択します。
ですので、"inner" コマンドは常に "a" コマンドより少なくテキストを選択する ことになります。

:help text-objects より。

端的に言うと、 従来単語の先頭にカーソルをあわせてから cw としてた所を、
先頭にカーソルをあわせずとも caw で「単語全体を編集」をしたり、 ci' で「'で囲われた内部を編集」が可能とするナイスなオブジェクトのことである。

オペレータコマンドのおさらい

一応オペレータコマンドのおさらい。

コマンド 意味 操作
c change 変更
d delete 削除
y yank ヤンク
~ 大文字/小文字入れ替え
g~ 大文字/小文字入れ替え
gu 小文字にする
gU go Uppercase 大文字にする

他にもzfで折り畳みを作成するなどがあるけどテキストオブジェクトと組み合わせそうにはなさそうなので割愛。
詳細は :help operator を参照で。

テキストオブジェクトのaとiコマンド

コマンド 意味 対象
a an object まとまり(記号を含む)
i inner まとまりの内側(記号を含まない)

a + <移動コマンド> のように指定する。aw なら "a word", iwなら "inner word"となる。
移動コマンドの修飾語のような扱いになる。

aとiコマンドの後に利用可能な移動コマンド

コマンド 意味 対象
w word カーソル前後の単語
W WORD カーソル前後の単語(ホワイトスペース含む)
s sentence カーソル前後の文
p paragraph カーソル前後の段落
[ ] カーソル前後の[]block
( ) または b block カーソル前後の()block
< > カーソル前後の<>block
t tag カーソル前後のtag block. ここでいうタグとはhtmlの<div>...</div>など
{ } または B Block カーソル前後の{}block
" や ' や ` カーソル前後の " や ' や ` それぞれのquote block

移動コマンド単体では使い方が良く分からないと思うので次へどうぞ。

テキストオブジェクトの使い方

上述の オペレータコマンド + a/i + 移動コマンド の組み合わせで使うのが一般的なテキストオブジェクトの利用方法である。

以下に実際の使い方の一例。

コマンド 意味 対象
daw delete a word カーソル前後の単語を削除する
da( delete a block カーソル前後()に囲まれた部分を()も含めて削除する
di( delete inner block カーソル前後の()に囲まれた部分を削除する
ca( change a block カーソル前後()に囲まれた部分を()も含めて変更する
ci( change inner block カーソル前後の()に囲まれた部分を変更する
das delete a sentence カーソル前後の文を削除する
dat delete a tag block カーソル前後のタグを削除する

最もよく使うのは c との組み合わせだろう。
ci' のクォートに囲われた部分の変更を私は既に良く利用している。

s のsentenceはノーマルモードでは( , )で移動する単位のもの。
利用するのであれば日頃からVimの文の単位を理解しておきたいところ。

t がめっちゃ使えそう。と思ったけど、終端タグ </something> があるタグじゃないとダメなので ci< などと使い分けが求められる。
普段からhjkl以外の移動にも慣れておくと、テキストオブジェクトも有効に利用できそうなのでいろいろ使って慣れておきましょう。

Surround.vim

括弧やクォートやタグなどの、文字列を囲むものを簡単に操作するVim Plugin。
テキストオブジェクトとセットで語られることが多いのでこちらも纏めて取り上げる。

github.com

Surround.vim の使い方

<オペレータコマンド> + s + <対象の括弧/クォート/テキストオブジェクト> + (<置換, 挿入する括弧/クォート>) という形で利用する。
英語のSVO型とSVOO型のような形を意識して README.markdown を試すと理解が進む。

まずはじめに"で括られたテキストを用意する

"Hello world!"

cs"' とtypeすると、"を'に置換する

'Hello world!'

cs'<span> とtypeすると、'をspanタグに置換する

<span>Hello world!</span>

cst" とtypeすると、spanタグを"に置換する

"Hello world!"

ds" とtypeすると、"を削除する

Hello world!

ysiw] とtypeすると、inner wordを[]で囲う

[Hello] world!

cs]{ とtypeすると、[]を{}に置換する *1

{ Hello } world!

yssb もしくは yss)とtypeすると、文を()で囲う

({ Hello } world!)

ds{ds{ とtypeすると、括弧を削除する

Hello  world!

ysiw<em> とtypeすると、inner wordをemタグで囲う

<em>Hello</em>  world!

Vで行選択後、S<span class='info'> とtypeすると、行をspan.infoタグで囲う

<span class='info'>
<em>Hello</em>  world!
</span>

あ、これ慣れると最高なやつだ。

  • ccs + <変更対象の括弧> + <変更後の括弧> で括弧の変更
  • dds + <削除対象の括弧> で括弧の削除
  • yys + <括弧で囲う対象のテキストオブジェクト> + 括弧 でテキストオブジェクトを括弧で囲う
  • ビジュアルモードでの呼び出しは S
  • 他のオペレータコマンド ~ とかは使えない*2

を憶えれば基本はOK.

Surround.vim のカスタマイズ

let g:surround_<ASCII code> = "<% \r %"> と.vimrcに書くと yss<ASCII code> などで置換できるようになる。*3

もっと詳しくカスタマイズしたければ :help surround を読めばOK.

まとめ

テキストオブジェクトで括弧内の文字列編集、
Surroud.vimで括弧の編集が楽にできるんや!!

Vimテクニックバイブル ?作業効率をカイゼンする150の技

Vimテクニックバイブル ?作業効率をカイゼンする150の技

参考

*1:cs]}ならスペースなし

*2:というか括弧類に対して働くコマンドではない。

*3:ASCII codeを調べるには :echo char2nr("-")

jQuery Validation Plugin の重複チェックバリデータを作った。

jQuery Validation Plugin で複数のセレクトボックスから項目を選択する場合に、重複チェックするバリデータを作成した。 用途としては、例えば

  • 秘密の質問 Q1〜Q3で同じ質問を選択してないか
  • 好きな○○で重複した項目を選択していないか
  • 経験のある言語で重複した項目を選択していないか

などに利用できるかと思う。

gist.github.com

無駄な空行を一発で削除する

無駄な空行が多いコードから、一発で無駄な部分を消したくなった。

Pythonのflake8をはじめ、他言語のコーディング規約をみても恐らく3行以上の空白を意図的に入れる必要性が感じられないので、4つ以上 <LF> が続いた場合には消す処理で良さそうだ。

command! DeleteUselessBlankLines :%s/\n\{4,}/\r\r\r/

これで :DeleteUselessBlankLines すると3行以上の無駄な空行を削除できるようになる。

もし保存時に自動実行したい場合には、

autocmd BufWritePre * :%s/\n\{4,}/\r\r\r/

とするといいだろう。

Pythonのパッケージ管理ファイル

pip freezeを使う。virtualenv と組み合わせて使うとよい。

$ pip freeze
beautifulsoup4==4.4.1
flake8==2.4.1
mecab-python3==0.7
pep8==1.5.7
pyflakes==0.8.1
PyYAML==3.11
$ pip freeze > requirements.txt  # 現在インストールしているパッケージ一覧をrequirements.txtに書き出し
$ pip install -r requirements.txt  # requirements.txt内のパッケージをインストール

User Guide — pip 7.1.2 documentation

jQuery Validation Plugin のエラーを Bootstrap3 の popover に出力させる。

HTML5 Form Validationのブラウザごとの実装状況が異なることから、 jQuery Validation Plugin | Form validation with jQuery をまだ現役で利用しているケースも多いはず。
先日 jQuery Validation Plugin のエラーを Bootstrap3 の popover(tooltip) に組み込んでみたのでその方法を記録。

実装イメージ。

準備

こんな感じの構成とする

      <form>
        <div class="form-group">
          <label for="exampleInputEmail1">Email address</label>
          <input type="email" class="form-control" name="exampleInputEmail1" placeholder="Email">
        </div>
        <div class="form-group">
          <label for="exampleInputPassword1">Password</label>
          <input type="password" class="form-control" name="exampleInputPassword1" placeholder="Password">
        </div>
        <div class="form-group">
          <label for="exampleInputFile">File input</label>
          <input type="file" id="exampleInputFile">
          <p class="help-block">Example block-level help text here.</p>
        </div>
        <div class="checkbox">
          <label>
            <input type="checkbox"> Check me out
          </label>
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>

jQuery Validation Plugin の showErrors()メソッドを使う

showErrors()というメソッドがある。
これはエラー時の表示処理を制御するメソッドで、defaultでは以下のようなdefaultShowErrors()メソッドが実行される。

       defaultShowErrors: function() {
            var i, elements, error;
            for ( i = 0; this.errorList[ i ]; i++ ) {
                error = this.errorList[ i ];
                if ( this.settings.highlight ) {
                    this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
                }
                this.showLabel( error.element, error.message );
            }
            if ( this.errorList.length ) {
                this.toShow = this.toShow.add( this.containers );
            }
            if ( this.settings.success ) {
                for ( i = 0; this.successList[ i ]; i++ ) {
                    this.showLabel( this.successList[ i ] );
                }
            }
            if ( this.settings.unhighlight ) {
                for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) {
                    this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass );
                }
            }
            this.toHide = this.toHide.not( this.toShow );
            this.hideErrors();
            this.addWrapper( this.toShow ).show();
        },

端的に言うと、エラー箇所の次の位置にエラーメッセージを含むlabel要素をappendしている。
このshowErrorsメソッドは、以下のような記述でオーバーライド可能。 Validator.showErrors() | jQuery Validation Plugin

$("form").validate({
  rules: {
    exampleInputEmail1: {
      required: true
    },
    exampleInputPassword1: {
      required: true
    }
  },

  showErrors: function(errorMap, errorList) {
    // validationに引っかからなかった要素はpopover非表示にする
    $.each(this.successList, function(index, value) {
      $(value).popover('hide');
    });
    
    // validationに引っかかった要素はpopover表示する
    $.each(errorList, function(index, value) {
      var _popover = $(value.element).popover({
        trigger: 'manual',
        placement: 'bottom',
        content: value.message,
        template: "<div class='popover' role='tooltip'><div class='arrow'></div><h3 class='popover-title'></h3><div class='popover-content'></div></div>"
      });
      _popover.data('bs.popover').options.content = value.message; // popover要素のテキストを更新する
      $(value.element).popover('show');
    });
  }
});

上記を実装するとこんな感じになる。

エラーメッセージなので警告色に

.popover-validation などとclass名を追加して、css

.popover.popover-validation {
  background-color: #f9f2f4;
  color: #c7254e;
}
.popover.popover-validation > .arrow:after {
  border-bottom-color: #f9f2f4;
}

とするとさらにいい感じになった。

showErrors()処理を使いまわしたい

setDefaults()メソッドを利用しましょう。 jQuery.validator.setDefaults() | jQuery Validation Plugin

$.validator.setDefaults({
  // エラーメッセージのBootstrap3: popover表示
  showErrors: function(errorMap, errorList) {
       ...
    });
  }
});

messages: {} も纏めて jquery.validate.settings.js などとして置いとくと、サイト内で共通のエラーメッセージを使い回せます。

纏め

最終的な今回の成果物として、デモ用のリポジトリを用意したのでご参考にどうぞ。

github.com

参考: How to use Twitter Bootstrap popovers for jQuery validation notifications? - Stack Overflow

JavaScript 第6版

JavaScript 第6版

Pythonで破壊的ループをする際はリスト全体のコピーをとる

Pythonで以下のような破壊的ループをしようとすると、indexのズレが発生してすべての要素に対して処理が行われないケースがある。
(この例の処理ではリスト内包表記で充分代替可能だったりするが、あくまで例として単一処理にしている。)

li = [
    {
        "id": "D028xxxxx",
        "is_im": True,
        "user": "USLACKBOT",
        "created": 1397471294,
        "is_user_deleted": False
    },
    {
        "id": "D028xxxxx",
        "is_im": True,
        "user": "U028xxxxx",
        "created": 1397471294,
        "is_user_deleted": False
    },
    {
        "id": "D028xxxxx",
        "is_im": False,
        "user": "U028xxxxx",
        "created": 1397471294,
        "is_user_deleted": False
    }
]

for user in li:
    if user['is_im'] is True:
        li.remove(user)

print(li) # [{'id': 'D028QH1PT', 'created': 1397471294, 'user': 'U028NTG5T', 'is_im': True, 'is_user_deleted': False}, {'id': 'D028QH1PR', 'created': 1397471294, 'user': 'U028P546G', 'is_im': False, 'is_user_deleted': False}]
# 'is_im': Trueの要素が残っている

リスト全体のスライスコピーを取る

正しく破壊的ループをする場合には、リスト全体をコピーして実行すると良い。
ループ処理前に temp_li = li を行うやり方もWebでは散見されたが、
li[:] によるスライスコピーが一番綺麗に記述できる。

for user in li[:]:
    if user['is_im'] is True:
        li.remove(user)

print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]

filter関数を使う

ちなみにfilter()で書くとこう。

li = list(filter(lambda user: user['is_im'] is False, li))

print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]

Python3からはfilter()はリストでなくイテレータを返却するようになったので、list()で囲う必要がある。

リスト内包表記を使う

結局はリスト内包表記がいいのかも知れない。

li = [user for user in li if user['is_im'] is False]

print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]

上記の内容はPythonチュートリアルに記載されていた。数年ぶりに読み返したが、まだ発見があったのでまだまだだなと痛感。

Pythonチュートリアル 第2版

Pythonチュートリアル 第2版

一応速度も計測してみたが、今回のリストだと速度に顕著な差異が見られないので割愛した。 コードはPython3.xに準拠。