django-debug-toolbar は便利(とVirtualboxのVMで表示にハマった件について)

class base view がわからない

存在は前から知っていたけど、まあ別にいいやって思ってて使ってなかった。でも久々の Django でいろいろ忘れてるし、あと 1.5 を使うのはいいけど class base view が mixin しまくってて拡張や仕様変更や保守に強そうというのはいいんだが単に便利セットが増えたという話だった、やっぱり正直どうつかえばどう動くのかよくわからない。資料はあるが実感がわかない。

そういうことでとりあえず発行されている SQL がみたかった。前も ForeignKeyとfilterのメモ - atas でみてたことはあったけどやっぱりだるいのでいい感じのデバッガをいれた。

前提

django-generate-scaffoldをさわってみたメモ - brainstorm
modocache/django-generate-scaffold · GitHub
を参考に導入した。ただテンプレートタグが url の部分辺りで 1.4 と 1.5 で互換性がないので 1.5 対応修正をしてくれたバージョンを使用した
drillbits/django-generate-scaffold · GitHub

# github からのインストール
# egg の部分は任意の名前でいいっぽいけど、いまはよくわかっていない
pip install -e git+https://github.com/drillbits/django-generate-scaffold.git#egg=moge
# 導入した virtualenv 内で git のブランチを切り替える
git checkout django-1.5-support

導入

django-debug-toolbar便利 - 偏った言語信者の垂れ流し

pip install django-debug-toolbar

django-debug-toolbar 0.9.4 : Python Package Index の公式を参考にして、settings.py に以下の項目を追記する。簡単だけどコピペできるくらいのレベルまで落としておく。

INSTALLED_APPS += (
    # これを追加
    'debug_toolbar',
)
# 許可IP
# 開発なのでローカル
INTERNAL_IPS = ('127.0.0.1',)
MIDDLEWARE_CLASSES += (
    # これを追加
    'debug_toolbar.middleware.DebugToolbarMiddleware',
)

とりあえずこれで動く。PyPi にあるドキュメントだとオプションがあるけど、とりあえずデフォルトだと全部有効っぽいのでそのままにしてる。

追記 ローカルの Virtualbox の VM でも表示させる

クライアントは Mac で、開発は Ubuntu Server でやっている。で、そのときパネルが表示されなかった。ローカルで試すと大丈夫ということは、 INTERNAL_IPS の許可 IP あたりの設定という目星はついた。が、クライアントの IP である 192.168.xxx.xxx を許可してもうまく表示できなかった。

探した結果、やはり StakOverFlow にいきついた
python - django-debug-toolbar not showing up - Stack Overflow
ここでも「Debug=True で許可 IP はサーバーじゃなくてクライアントだよ」とあるけど、それでもだめだったとき「request.META["REMOTE_ADDR"]みろ」ってのが助かった。

class base view なのでてきとうに TemplateView とかだと request オブジェクトを拾ってくれないので、てきとうに get メソッドの中でブレークさせた

class MyView(TemplateView):
    
    def get(self, request):
        # debug というサードのモジュールつかってるけど、もちろん pdbでもいい
        import debug 
       # print(request.META["REMOTE_ADDR"])

これの結果が普段ローカルから接続している 192.168.56.101 ではなく 192.168.56.1 となぜか変わっていた。なので、後者を指定することで対応できた。

結果

CRUD するボタンをおすと HttpResponseRedirect して、そのなかで update/delete/craete とかルーティングして SQL としてはそれぞれ発行してるという挙動がわかりやすかった。

まあ

フルスタックで利用者が多いとこういう手軽なデバッグツールとか作ってくれてる人がいて助かる(あるいはこういうツールがないとどんどんブラックボックスになっていくというのもまああれなんだけど)。あといまは大丈夫だけど、どう MIDDLEWARE とか TEMPLATE_CONTEXT_PROSECCOR を通過しているかとか Django が内部的にどう動いているのかがわかりやすいからなんかあったときにトラブルシューティングに役立つ。テンプレートの継承やそこで発行されているクエリもみれる。

grunt を軽く触ってついでに livereload させてみた

Sphinx のビルドをファイル監視して自動で行う - 憧れ駆動開発 でファイル監視してビルドとかやってるように、ウェブ開発でファイル監視してリロードしたいとかはある。古くは location.href に xhr をとばすっていう方法でやってたようだし、それを拡張してるやり方もある。あるいはファイル監視はrubypythonにやらせて、ブラウザのリロードは AppleScript やそういうシェルスクリプトにやらせたりする方法もある。

で、個人的には JS 関係のことだから Node にやらせたいし、あんまり AppleScript みたいにプラットフォーム依存なやりかたは嫌だなぁと*1。xhr で監視も悪くないけど、まあ、grunt ついでにやってみっかってことでやってみた。

しかしぶっちゃけ

いろいろやってたけど、その xhr をみる方法でよしなにしてくれる Chrome 拡張の livepage ってのがあるから、それを使うほうがなにかと楽な気はする。

grunt とは

今や説明不要になりつつあるけど、ざっくりいうと Node 製の便利ユーティリティースクリプトみたいなもの。JS や CSS を minify したり、coffee や sass をコンパイルしたり、ファイル監視したり、圧縮したり。とかもろもろできる。

導入と簡単な設定

grunt は最近リリースされたらしい 0.4 系からはちょっとかきかたが変わったらしいので、情報は最新をあたったほうがいいかもしれない。基本的には「grunt.js から Gruntfile.js にかわった」「grunt はグローバルにはおかないで、かわりにgrunt-cli をグローバルにおく」ってあたりらしい。

導入した package.json をみたほうが早いのではる。npm install grunt-cli -g は別として、これらはそのまま npm install できる

{
  "name": "my-project-name",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": ">=0.4.x",
    "grunt-contrib-coffee": "*",
    "grunt-contrib-compass": "*",
    "grunt-contrib-compress": "*",
    "grunt-contrib-sass": "*",
    "grunt-contrib-uglify": "*",
    "grunt-contrib-watch": "*",
    "grunt-contrib-concat": "*",
    "grunt-contrib-livereload": "*",
    "grunt-contrib-connect": "*",
    "grunt-regarde": "*",
    "grunt-jasmine-node": "*",
    "grunt-contrib-jshint": "*"
  }
}

とりあえず grunt だけ0.4 系以上にしておいて、あとは全部最新版しておいた。

npm install するときは引数に --save-dev をつけようと各種 grunt-contrib-* のREADME にはかいてある。あくまで開発中であることを明示したほうがいいとかなんとからしい。
https://github.com/gruntjs/grunt/wiki/Getting-startedpackage.jsongrunt-cli (grunt.js v0.4.x)で LiveReload を試してみた | Re* Programming

で、Gruntfile.js は livereload 以外のことをやらせた。まあ、たいしたことはしていない。

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({

    pkg: '<json:package.json>',

    // ファイル結合
    concat: {
      dist: {
        src: ['../jasmine/src/*'],
        dest: 'main.js'
      }
    },

    // minify と難読化
    uglify: {
      options: {
        // true にすると難読化がかかる。false だと関数や変数の名前はそのまま
        mangle: true
      },
      my_target: {
        files: {
          'main.min.js': ['main.js']
        }
      }
    },

    // 圧縮
    // zip 意外にも tar.gz や gzip とかできる
    compress: {
      options: {
        archive: 'archive.zip'
      },
      files: {
        src: ['main.min.js'],
        dest: 'destzip'
      }
    },

    // 文法チェック
    // jslint はきついので個人的にいつも jshint をつかっている
    jshint: {
      options:{
        jshintrc: '../../.jshintrc' // valid JSON file or dict
      },
      files: ['../jasmine/src/*']
    },

    // ファイル監視
    watch: {
      scripts: {
        files: ['../jasmine/src/*'],
        // 実行タスク
        tasks: ['jshint']
      }
    },

  // load task
  // モジュールを読み込んでやる
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-compress');

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Default task
  // grunt で直接実行すると行われるタスク
  grunt.registerTask('default', ['concat', 'uglify', 'compress']);
  // grunt auto_jshint など引数を与えると実行されるタスク
  grunt.registerTask('auto_jshint', ['watch']);
};

この場合デフォルトだと「JS ファイルを全部1つにまとめて、minify と難読化して、zip にする」というタスクになる。grunt auto_jshint とすると「監視対象のファイルに変更があったら jshint かけて文法チェック」ということができる。ありがちなケースとしては develeropment では coffee, sass, compass のコンパイルだけして、production では JS(coffee), CSS(SCSS) を concat, minify するってのが考えられる。まああとは zip にしてコピーとか。grunt-contrib-* 意外にもプラグインが豊富なのでいろいろできる感じ。

あと path で ~ が使えないっぽい感じがする。面倒なので相対パスで読み込んだけど、実際は require('path') とかして絶対パスを取得するなにかをしたほうがいいかも。

ちなみに jasmine-node をつかったから grunt-jasmine-node を watch しようとしてみたけど、requirejs 関係の config.js が読めないっぽい感じでうまく動かせなかった。まあ、jasmine-node 自体に自動監視があるから別にいらない気もするけど。

livereload する

いままではまえがき。こっからが本番。開発中のファイルを監視して変更があったらブラウザをリロードさせる。ちなみにこの livereload 自体はわりと ruby 発祥らしいので、guard あたりでも同じような話はある。ファイル監視したら websocket でどうこうみたいな。最近は Windows でも Mac でも GUI のサーバーもあるらしい(有償だったりアルファ版っぽいけど)。

Gruntfile.js はこんな感じ。まあ、公式のまんまだけど。

// from grunt-contrib-livereload github README
var path = require('path');
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;

var folderMount = function folderMount(connect, point) {
  return connect.static(path.resolve(point));
};

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({

    pkg: '<json:package.json>',

    connect: {
      livereload: {
        options: {
          // これは connect のポート
          // livereload のポートはデフォルトだと 35729
          port: 9001,
          middleware: function(connect, options) {
            return [lrSnippet, folderMount(connect, '.')];
          }
        }
      }
    },
    // Configuration to be run (and then tested)
    regarde: {
      // fred って名前がなんだかわからないけど、とりあえずそのままにしておいた
      fred: {
        // 監視対象
        files: '../Public/a.html',
        tasks: ['livereload']
      }
    }
  });

  // load task
  grunt.loadNpmTasks('grunt-regarde');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-livereload');

  // Default task
  grunt.registerTask('default', ['livereload-start', 'connect', 'regarde']);
};

grunt-contrib-livereload を動かすには grunt-contrib-connect という grunt タスク中に立ち上げられるサーバーと(Node の http モジュールとはなんだか別らしい)、grunt-regarde というファイル監視ツールが必要。grunt-contrib-watch ではなく regarde をつかう理由はよくわからないけど、まあそういうものらしい(開発が yeoman だし……)。
gruntjs/grunt-contrib-livereload · GitHubgruntjs/grunt-contrib-connect · GitHubyeoman/grunt-regarde · GitHub

これで grunt の監視と livereload サーバーが立っている状態になる。ポートはデフォルトだと35729であって、connect でたちあがってるポートではない。で、その上で開発対象のクライアントになんかしらの方法で接続を確立しなければいけない。ので、開発対象のファイルにスニペットをおくか、専用のブラウザ拡張をいれる必要がある。

スニペットをおく

個人的にはあまりやりたくないけど、README にあるような方法で埋め込めば動く。

<!-- livereload snippet -->
<script>document.write('<script src=\"http://'
+ (location.host || 'localhost').split(':')[0]
+ ':36729/livereload.js?snipver=1\"><\\/script>')
</script>

もちろん全ファイルだとか基底ファイルとか監視用のファイルに埋め込まなきゃいけないから、テンプレートで継承しているならともかく静的にわかれてたら面倒ですね。

ブラウザ拡張をつかう

Chrome, Safari, Firefox には専用の拡張があるのでいれる。注意点としては、おそらくだけど「ファイルのURLアクセスを許可する」みたいなオプションはオンにしておかないと接続が確立できないっぽいので、許可しよう。

まあ

ライフチェンジングかはわからないけど、watchdog 自動ビルドみたいにやっぱり勝手にやってくれるのは気分がいいですね。

*1:とはいえ livereload が対応している拡張は Chrome, Safari, Firefox なのでそれもまたあれなんだけど

JavaScript、奥が深すぎる

今まで JS 関連のことをぽつぽつエントリにしていたけど、それにはみ出るトピックを羅列する。

いちばん印象的だったところ

関数について考えること。いまのところ、JavaScript を復習するにあたってこれがいちばんの大本になると思う。プログラミング全般というか、それこそ手続き型に対して関数型とあるけれど、こうしていろいろやってみるとオブジェクトのプロパティに関数をいれておけるということ、それを使い倒せるというのがどれだけ世界を広げることなのかっていうのが、こう、すこし世界が広がった感がある。同時にいろいろついていけない感もある。

あとは JavaScript をクライアントサイドで使ってると歴史的背景としても(そして自分のプログラミングのきっかけが JS というのもあって)DOM 操作をいかにするかってことを考えてしまって整理がつかなくなる。それを「JavaScript が言語として提供している分野」「DOM を扱うという意味での JavaScript」「サーバーサイドの JavaScript」というのを意識して分けて考えるきっかけになったと思う。

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技


パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)


JavaScript徹底攻略 (WEB+DB PRESS plus)

JavaScript徹底攻略 (WEB+DB PRESS plus)

関数が関数を扱うということ

JS に限らない話で Python でもそうでなくてもなんでもそうだけど、特に JS をいままで扱ってて感じた。関数が関数を引数にとる。関数が関数を返す。関数が関数を合成する。こういったものを高階関数と呼ぶらしい。f(x,y)を f(x)(y) とさせるのがカリー化とするらしい。Python ではよくあるけどデコレータも関数をラップして扱うようなもの。クロージャはとりあえず JS においては状態をもった関数を作成できる。

関数ってのはいくらかの処理をかためたものでそれを再利用しやすくしたりするっていう意味はもちろんあるんだけど、それだけじゃなくて、関数が柔軟に扱えるということは処理をフックしたりラップしたりメソッドチェインさせたりできる。いまの自分の理解だとあまりこれ以上はおもいつかないけど、だいぶ黒魔術ではないが幅が広がる話で、ものすごく柔軟にさせている。これは直感*1

ところでクロージャの話はパーフェクト JavaScript の解説はすごかった。ちょっと正直よくわからない。いまは表面的につかえればいいやというところ。

プロトタイプチェーンの終端

前に JavaScript の名前空間になぜ Function Object をつくるんだろうって話 - atas でぷととタイプチェインの話を書いたけど、たぶん、一番誤解していたのは「プロトタイプチェーンの終端は Object」って思ってたところで、そうではなくて、「Object.prototype」だ。だから Object.create(Object.prototype) すると「純粋なオブジェクトが得られる」というのは、Object 自体は Object.prototype をもつから純粋ではない。というのがいまのところの理解。

メソッドチェインと再帰について

前に いまさら JSDeferred と $.Deferred を少しさわったメモ - atas というのを書いたけど、jQuery でもメソッドチェインで処理をつなげてかけるのがよい。で、具体的には $ にメソッドを生やせば $.func として使え、$.fn = func としてメソッドを生やせば $(selector).func としてつかえる。実装をみてはいないけど、たぶん内部的にはなんらかの状態を変化させた jQuery Object を返しているんだと思っている。

再帰という考え方は知っていても使えることはなかった。でもこういう実装が再帰的なのかなと思っている。関数型言語をちゃんとやったことはないけど、「for ループの代わりに再帰をつかうと考えるとわかりやすい」と本にあったように、for でループさせないならば、再帰的に参照してぐるぐるまわすしかない。

そういうところが似ているなぁと感じた。冒頭の「関数が重要」というのは、ここらへんをみてみて感じたところでもある。

プロパティとアトリビュートについて

JS としてはプロパティを持っていて、DOM としてはアトリビュート(属性)を持っている。クラスベースのオブジェクト指向だとフィールドとメソッドをあわせてメンバーと呼ぶっぽいけど、JS だとあくまでプロパティに関数が入っているものが便宜的にメソッドと呼ばれているらしい。

混同しやすいところは、DOM を触るときもプロパティアクセスっぽい感じで記述できるというところだ。具体的には img.src = path とか location.href とかそういう。それらはプロパティアクセスっぽいし実際そうかけるのは楽で直感的だけど、たぶん、JS と DOM を区別して考えようとするときに混乱する。

命名とか名前とかなんて便宜的なものでどうでもいいというかもしれない。でも、JS が DOM をさわるクライアントサイドの言語としての意味合いとサーバーサイドで実行されることもある現在、そしてクラスベースではなくてプロトタイプベースであるのもあって、JS が提供している部分と DOM を扱う部分は別に考えたほうが長期的には意味があると思っている。

同期と非同期って話と、functionかcallbackかは別

処理が同期であるか非同期であるかってのと function なのか callback なのかは別だ。たしかに setTimeout や xhr で飛ばしたあとの関数は callback で非同期だけど、リスナーでイベントを登録された関数は callback だけど同期的に処理されると考えていいとおもう。あんまり厳密に考えなくてもいいのかもしれないけど、なんだろう、意識の違いかな。

名前空間と関数オブジェクト

JavaScript の名前空間になぜ Function Object をつくるんだろうって話 - atas とかいたけど、実際たしかに Object でもいいパターンはあるようだ。でも、たいがいなにか名前空間として渡したいものって関数だし、関数だと call/apply/bind が使えるからそのほうが便利だよねとか。これは直感的に感じた。

JS でも内部を隠したい話

UserScript とか GreeseMonkey とかならともかく、名前空間としてグローバルに1つだけ追加したら別に中身は隠さなくてもいいのでは?と思っていたけど、それでもあくまで private にもっておきたいものは JS でも匿名関数 + クロージャで隠蔽してしまうのが筋のいいやり方なのかなぁと。

構文について

JS は文と式で成り立っている。式は式と演算子で成り立っている。というところまではよかったけど、もっと突き詰めていくと式は演算子リテラルと識別子(変数)になる話。たぶん自分は識別子と演算子ってあたりまでは区別がついてたけど、リテラルってのはまた別物として分けられるんだなぁと。

型変換について

「== の比較は型変換がおこるからバッドなやりかた」とは知ってたから避けるようにはしてたけど、でも if (hoge) したときには型変換がおこっている。静的型付けと動的型付けの話は正直まだそこまでよくわかっていないけど、少なくても JS においても型はあるし、それを動的に変換したり暗黙的に変換することがある。ということは頭の片隅においておこうとおもった。

map, filter, リスト内包、ジェネレーター

map, filter は ES5, リスト内包, ジェネレーターは ES6 な内容だけど、 Python からはいったからそこらへんはとっつきやすかった。ただ JS 以外の言語を知らない状態でやってたらだいぶいろいろ苦しかった気がする。

というかジェネレーターオブジェクトの自作すごい

パーフェクト JavaScript にあったけど、ジェネレーターオブジェクトを実装してみるってところ、すごい。

iframe で子要素孫要素でコールバックさせるテク、すごい

わりと古典的というか XHR2 の前はそういうことを駆使していたっぽいんだけど、初めて知った。ざっくりいうと親 window に対してクロスドメイン通信したい対象を iframe でおいておく(子iframe)。で、その子iframe から受け取った値は親 window と同じドメインの孫 iframe に通信させる。で、その孫 iframe から親 window にコールバックさせて値をとる。すごい。

Node におけるカスタムイベント

JavaScript 関連のちょっとしたメモ - atas でカスタムイベントのことを書いたけど、Node だとそこらへんは emit って形でそもそも提供してるんですね。Socket.io さわったから知ってはたけど、そもそもカスタムイベントはその emit で管理してしまうものなんだなぁと。Node あたりの詳細はあんまり追いかけてないので知らなかった。

状態(state)で考える GUI MVC パターンはいいね

GoF な話らしいけど、具体的に「if でフラグをたくさんもたせたコードがどうみてもスパゲティにしかならないよね」ってときに「それは状態で考えてつくるといいよ」というのはとても実践的だった。デザパタな話は昔座学的に読んで概念としては知っててもどう実際現場で使えるのかよくわからないってことが多いので、ひとつの参考になった。

正規表現の (?:hoge|moge) はマッチしてもつかわないという意味

JS とは関係ないかも。グループしたときに (hoge|moge) ってやるとそれらを RegExp.$1 とかでとれるってのはしってたけど、別にマッチした値をあとで使いたいわけじゃないときはこうして (?:hoge|moge) としたほうが筋がいいとおもう。

まあ

かなりざっくりとかいたけど、「それはちゃうだろ」ってところがあったらコメントか Twitter かなんでもいいのでそれとなく伝えてくれるとうれしい。JavaScript をナメてたつもりはないけど、だいぶすごい。言語として限定した上でも、プログラミング、奥が深すぎる。正直ついていけない。

*1:そういう意味では Python において lambda が貧弱って話をきくけど、そういうことなのかなぁと感じる

jasmine-node で JavaScript のロジックのテストをする

JS でもテストしたい

DOM や非同期はおいといて、ロジックくらいテストしたい。Sinon.js とかモックしたり、非同期処理のテストのライブラリがあったり、それこそ古くからは Selenium とかあるけど、いまはそこまでしなくていいから JS でテストをするってのをやりたかった。で、いろいろあるけど Jasmine がとりあえず楽らしいし、コンソールでやりたいので jasmine-node を利用した。

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

jasmine-node の導入と簡単なテスト

基本的にはこれを参考にした
jasmine-nodeを使ってテスト - デラエモン、カイハツニッキ
てきとうにディレクトリを掘って作業する。

# てきとうにつうる
mkdir jasmine
cd jasmine
# lib でもいいかも
mkdir src
# テストケース、spec
mkdir spec
# 導入
npm install jasmine-node

src 以下に元となるコード、spec 以下にテストケースをかく。spec のことはあまりしらないけど、unittest だと class に HogeTest(unittest.TestCase) っていれたり def test_hoge(self) するように、どうやら Spec では MySpec(mixedCase)か my_spec(lower_case) にするらしい。関係なくマッチさせるには --matchall ってオプションをつければ良い。あと --verbose で詳細がでるとかはまあ --help みればよい。

/path/to/node_modules/jasmine-node/bin/jasmine-node ./spec/ --autotest --color

よい

requirejs で依存している他のモジュールも一括でテストしたい

@__10100__ さんの を参考にした jasmine-node とRequireJS - 断章10100
この記事だと src 以下に config.js を置いているけど、自分はプロジェクトルートにおいておいた。config.js はこんなかんじでほぼ同じ。

var requirejs = require("requirejs");

requirejs.config({
  // node のモジュールはそのまま
  nodeRequire: require,
  // spec 実行時に __dirname が spec のパスにかわるため
  // ちゃんとやるなら require('path') で絶対パス求めたほうがいいかも
  baseUrl: __dirname + '/../src'
});

requirejs の config については以下も参考にした。
がぶっとひとかみ
shim で記述できないか試したけどちょっとうまくいかなかった。まあ、後述の方法で対処した。

RequireJS の内容を踏まえて同時にファイル監視もついでにまわしてやることにした

/path/to/node_modules/jasmine-node/bin/jasmine-node --runWithRequireJs --requireJsSetup config.js ./spec/ --autotest --color

なので、いままでオレオレで習作してたコードを試したりもした。

src/a.js の文字列のやつなら

String.prototype.format = function (arg) {
  // @param {String} or {Object}

  // this = String
  var tmp = this;

  // Object
  if (typeof arg === "object"){
    for (var elem in arg){
      tmp = tmp.replace("{" + elem + "}", arg[elem]);
    }
    // String
  } else {
    for (var i=0; i < arguments.length; i++) {
      tmp = tmp.replace("{" + i + "}", arguments[i]);
    }
  }
  return tmp;
};

spec/a_spec.js でたとえばこんな感じ。

requirejs(['a'], function (a) {
  describe("おれおれフォーマッター", function () {
    var str_obj = null;
    var str_ary = null;

    beforeEach(function () {
      str_obj = new a.String("abc {hoge} {moge}");
      str_ary = new a.String("abc {0} {1}");
    });

    it("自作 String.format 1", function () {
      describe("object のとき", function () {
        // なにもしない場合
        expect(str_obj).toEqual("abc {hoge} {moge}");
        // 引数を入れる object
        expect(str_obj.format({hoge: "text"})).toEqual("abc text {moge}");
        // 引数を複数入れる object
        expect(str_obj.format({hoge: "text1", moge: "text2"})).toEqual("abc text1 text2");
      });
      describe("ary っぽくするとき", function () {
        // なにもしない場合
        expect(str_ary).toEqual("abc {0} {1}");
        // 引数を入れる array int
        expect(str_ary.format(1)).toEqual("abc 1 {1}");
        // 引数を入れる array string
        expect(str_ary.format("baba")).toEqual("abc baba {1}");
        // 引数を複数入れる array int
        expect(str_ary.format(4,5)).toEqual("abc 4 5");
        // 引数を複数入れる array string
        expect(str_ary.format("moha", "toge")).toEqual("abc moha toge");
        // 引数を複数入れる array string int
        expect(str_ary.format("moha", 4)).toEqual("abc moha 4");
      });
    });
  });
});

いいですね。あとはゴリゴリかいていくだけです。実際いくつかコケて修正しました。

ついでに - CommonJS, AMD について

RequireJS って単純に依存関係を管理したいだけだと思ってたんだけど、実際はもうちょっとあって、非同期読み込みとモジュール化による名前空間の提供が目的らしい。もうちょっと言えば RequireJS 以前の提案があったらしいが、実装としていまは RequireJS が使える、という話。

CommonJS は Node とも関係があって、歴史経緯的にクライアントサイドで使われてたけど、サーバーサイドでいろいろやるならこの際モジュール管理とかそういうのしないとだめだよねみたいな話がある。で、それの標準化の提案として CommonJS がある。このときに define と require という予約語っぽいのを決めてこう使いましょうという提案がある。で、AMD はもうちょっと発展させて CommonJS を発展させて非同期読み込みを実現するための API という感じらしい。

具体的な設定としては参考資料をみながらやった。元のスクリプトが AMD どころか Node のモジュールでもないただの JS だったので、こんな感じにした。

(function(define) {
  // id, deps, factory(func or obj)
  // id は省略、依存は特にないので空、モジュールを返すために factory とつける
  define([], function () {
    // てきとうな名前空間をつくってやる
    var _module = {};

    // てきとうな関数を用意してやる
    function pf() {
      var tmp = arguments[0];
      for (var i=1; i < arguments.length; i++) {
        tmp = tmp.replace(/%s/, arguments[i]);
      }
      return tmp;
    }

    // モジュールのプロパティに生やしてやる
    _module.pf = pf;

    // で、最終的にモジュール自体を返してやる
    return _module;
  });
})
(
  typeof define !== 'undefined' ?
  // AMD 対応していて define がつかえるならそのまま AMD としてつかう
  define :
  typeof module !== 'undefined' ?
  // If no define, look for module to export as a CommonJS module.
  // で、 module があるのなら Node と判断して module.exports に生やしてやる。
  function(deps, factory) { module.exports = factory(); } :
  // 最終的にどうもブラウザっぽいならそっちに生やす
  // つまり window.a としてグローバルに生やす
  // for this === window
  function(deps, factory) { this['a'] = factory(); }
);

最近のJavaScriptモジュールの書き方 - yo_waka's blog
Dojo道場 ~ 第11回「Dojo 最新動向 - Asynchronous Module Definition」 (1/5):CodeZine

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)

まあ

少なくてもロジックに対してテストができるというのは安心感がある。

JavaScript で文字列や日時フォーマットを自前でやる

思うところがあったのでやった。基礎練習とか素振りみたいなものです。

JavaScript の標準の文字列出力はあまりに貧弱すぎるからそこらへんなんとかなんないかやってみた。ライブラリを使うなら日時フォーマットについては Underscoe.String.js とかあるし、日時は Moment.js あたりがいいらしい。
Underscore.string
Moment.js | Parse, validate, manipulate, and display dates in javascript.

文字列の format

すごく単純な例

JavaScript テクニックバイブルにあったのを参考にした。

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

function pf() {
  var tmp = arguments[0];
  for (var i=1; i < arguments.length; i++) {
    tmp = tmp.replace(/%s/, arguments[i]);
  }
  return tmp;
}
pf("This %s is %s", "name", "ore")
// This name is ore

やってることは可変長引数をとってやって %s と決め打ちしたところを全部置換してるだけ。arguments[0] で元の文字列を受け取って for ループで i=1 以降を使うことで対応する。というやりかた。

もうちょっと拡張する - 自作

Python の "string".format(arg) をやってみた。で、似たようなことをやってるような人はそれなりにいたので結局だいぶ参考にしてしまったけど、まずは頭から消してどうにか自前でやってみた。あんまり String とかグローバルなところは拡張したくなかったけど、まあ、習作。

/**
 *     String.prototype.format
 * @param     String.prototype.arg
 */
String.prototype.format = function (arg) {

  // this = String
  var tmp = this;

  // Object
  if (typeof arg === "object"){
    for (var elem in arg){
      tmp = tmp.replace("{" + elem + "}", arg[elem]);
    }
    // String
  } else {
    for (var i=0; i < arguments.length; i++) {
      tmp = tmp.replace("{" + i + "}", arguments[i]);
    }
  }
  return tmp;
};
// オブジェクトで渡す
"abc {hoge} {moge}".format({hoge: "text1", moge: "text2"}))
// abc text1 text2
// 文字列を複数いれる
"abc {0} {1}".format("text1", "text2");
// abc text1 text2

これもわりと泥臭くて、結局文字列を愚直に組み立てて replace で置換してる。

もうちょっと拡張する - 参考資料

JavaScript で文字列フォーマットを実装してみた(sprintf もどき) | TM Life のままだけど、変数名はちょっとかえた

/**
 *     String.prototype.format2
 * @param     String.prototype.format2arg
 */
String.prototype.format2 = function (arg) {
  // init
  var func = null;

  // Object
  if (typeof arg === "object"){
    func = function (regexp, key) {return arg[key]; };
  }
  // String
  else{
    // 一度格納しないと末尾しか拾わない
    var args = arguments;
    func = function (regexp, key) {return args[key]; };
  }

  // this = String
  // replace の第2引数は関数でもよい!
  // そのとき return された文字列をつかう
  // g で繰り返しているから key が増えていく
  return this.replace(/\{(\w+)\}/g, func);
};

あたまいいなー。なにを知らなかったかというと、 string.repace の第2引数で関数がとれるということ。マッチした第一引数が regexp としてはいってきて、それを args[key] で置き換えてやる。ということをしている。

簡易日時フォーマット

けっこう泥臭いものをつくった。そのかわり、上で自作した string.format 関数をつかった

/**
 * strftime
 * @param d
 */
function strftime(d) {
  var date_format = "{0}年{1}月{2}日:{3}時{4}分{5}秒".format(
    d.getFullYear(),
    d.getMonth() < 10 ? "0" + d.getMonth() : d.getMonth(),
    d.getDate()  < 10 ? "0" + d.getDate() : d.getDate(),
    d.getHours() < 10 ? "0" + d.getHours() : d.getHours(),
    d.getMinutes() < 10 ? "0" + d.getMinutes() : d.getMinutes(),
    d.getSeconds() < 10 ? "0" + d.getSeconds() : d.getSeconds()
  );
  return date_format;
}
// strftime(new Date()))
// "2012年01月02日:03時04分05秒"

いいかんじです

まあ

素振りです。JS の標準機能が貧弱すぎるなら、自分で武器を作れるようにならなくてはいけない。