gist

2012年2月29日水曜日

[Objective-C] NSMutableArrayのremoveObjectでハマった

    NSNumber *n1 = [NSNumber numberWithInt:1];
    NSNumber *n2 = [NSNumber numberWithInt:2];
    NSNumber *n3 = [NSNumber numberWithInt:3];
    NSNumber *n4 = [NSNumber numberWithFloat:1.00000f];
    
    // ポインタを表示
    NSLog(@"n1=%p", n1);
    NSLog(@"n2=%p", n2);
    NSLog(@"n3=%p", n3);
    NSLog(@"n4=%p", n4);
    // それぞれ違うポインタが表示される
    
    // arr作成。
    NSMutableArray *arr = [NSMutableArray arrayWithObjects:n1, n2, n3, n4, nil];
    
    NSLog(@"削除前:%@", arr); // 1,2,3,4
    
    // arrから最後のオブジェクトを取得する
    id n = [arr lastObject];
    // そのオブジェクトをarrから削除する
    [arr removeObject:n];
    
    NSLog(@"削除後:%@", arr); // 2,3
    
    // removeObjectでは、ポインタが同じものを削除するのではなく、
    // 値が同じオブジェクトを削除する。


最後のオブジェクトだけを削除したい時は、


[arr removeLastObject];

を使うべし。

5時間ハマった。コアな箇所だったので特定に時間がかかってしまいました。ドキュメントを良く調べてなかったのが今日の教訓。おやすみなさい。

2012年2月28日火曜日

[Objective-C] Association Referenceでカテゴリにプロパティを追加する方法

カテゴリにプロパティを追加できれば、継承のように型が変わることなく、もとのクラスの性質を引き継ぎつつ、ちょっとした機能を追加できるので便利です。例えばTAG。

Objective-CのAssociation Referenceを使うと、以下のようなコードで実現できます。

#import <objc/runtime.h>

static char const * const TagKey = "TagKey";

@interface NSObject(TAG) 
+(id)objectWithString:(NSString*)string;
@property(nonatomic,retain) id tag;
@end

@implementation NSObject(TAG)
@dynamic tag;

+(id)objectWithString:(NSString *)string
{
    self = (id)[[[NSObject alloc] init] autorelease];
    [self setTag:string];
    return self;
}

- (id)tag 
{
    return objc_getAssociatedObject(self, TagKey);
}

- (void)setTag:(id)tag
{
    objc_setAssociatedObject(self, TagKey, tag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

使い方

NSObject *a = [[[NSObject alloc] init] autorelease];
[a setTag:@"abc"];
NSLog(@"%@", [a tag]); // abc

NSObject *b = [NSObject objectWithString:@"def"];
NSLog(@"%@", [b tag]); //def

応用するとNSNumberに有効桁数をもたせたり、NSDateに出力形式をもたせたり、といったことを「先」に定義できます。使い過ぎはアカンですが。

2012年2月27日月曜日

デザイン下手な僕でもクールなサイトができた! Bootstrap2 を使ってみたよ

デザイン下手な僕にとって、Bootstrapの登場は衝撃なわけで。

Bootstrapは、ウェブ画面のレイアウト、ナビゲーション、ボタンなどのフォーム要素が予めカッコよく定義されたCSSの集合です。Twitterからスピンアウトしており、JavaScriptとの親和性も抜群です。プラグインも豊富。見た目のいいモックが手軽にできちゃう。

練習で公式のサンプル見つつ作ってみました。制作にかかった時間はたった10分。コンテンツに集中できます。

bootstrap.zipをダウンロードして展開。ファイル構成はこんな感じ。

/
|- index.html
+-- css/
+-- img/
+-- js/  

index.htmlを作っていきます。

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>株式会社26次元ポケット</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="株式会社26次元ポケットは、スゴイ会社です
。">
    <meta name="author" content="株式会社26次元ポケット">

    <link href="/css/bootstrap.css" rel="stylesheet">
    <style type="text/css">
      body {        padding-top: 60px;        padding-bottom: 40px;      }
    </style>
    <link href="/css/bootstrap-responsive.css" rel="stylesheet">    <!--[if It IE 9>
      <script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->

  </head>

 <body>
    <div class="navbar navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container">
          <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </a>
          <a class="brand" href="#">株式会社26次元ポケット</a>
          <div class="nav-collapse">
            <ul class="nav">
              <li><a href="#products">製品</a></li>
              <li><a href="#services">サービス</a></li>
              <li><a href="#support">サポート</a></li>
              <li><a href="#access">アクセス</a></li>
              <li><a href="#about">会社概要</a></li>
              <li><a href="#contact">お問合わせ</a></li>
            </ul>
          </div>
        </div><!--/.nav-collapse -->
      </div>
    </div>

    <div class="container">
      <div class="hero-unit">
        <h1>5次元ファインダー新発売!</h1>
        <p>新サービス「5次元ファインダー」は、4次元では不可能だったあんなこと>がこんなことを誰でも簡単に体験できるウェブサービスです。無料でお使いいただけます
。
        </p>
        <p><a class="btn btn-primary btn-large" href="#">もっと詳しく &raquo;</a></p>
      </div>
      <div class="row">
        <div class="span4">
          <h2>もしもしボックス</h2>
          <p>もしもしボックスは、新しい箱型電話機です。この斬新なコンセプトは、>22世紀始まって以来の衝撃です。</p>
          <p><a class="btn" href="#">詳細 &raquo;</a></p>
        </div>
        <div class="span4">
          <h2>8次元ポケット</h2>
          <p>22世紀9月に発売から2ヶ月。新しいポケットは従来の容量の4乗です>。しかも価格はかわりません。</p>
          <p><a class="btn" href="#">詳細 &raquo;</a></p>
        </div>
        <div class="span4">
          <h2>いつでもドア3</h2>
          <p>3次元空間上の行き来にうんざりしていませんか?いつでもドア3は、プ>ラス1次元。過去と未来をスマートに移動できます。</p>
          <p><a class="btn" href="#">詳細 &raquo;</a></p>
        </div>
      </div>
      <footer>
        <p>&copy; 株式会社26次元ポケット 2112</p>
      </footer>
    </div>
  </body>
</html>

ポイントはこの辺になるかなあ、と。

  • class="navbar navbar-fixed-top" ... 上部に固定のナビゲーション
  • class="container" ... 1ペインの本体
  • class="hero-unit" ... 上部トップ(5次元ファインダー)
  • class="row" ... グリッドの行の始まりを指定。12列。
  • class="span4" ... 12グリッド中の4つ分を使用することを指定。
  • class="btn" ... ボタン(詳細)
  • class="btn btn-primary btn-large" ... 大きくて優先的なボタン

グリッドデザインというのが素敵です。rowを指定してspanN(Nには1〜12)で12列あるセルの何個使うかを指定するのが基本的な使い方です。

グリッドデザインはウェブデザインが生まれてから長く使われてきましたが、ここまで簡単キレイにできると何か感慨深いものがあります。

ここからオリジナリティを出していくのは大変かもしれませんが、モックには今のところ最適では?ということで。

2012年2月26日日曜日

[Node.js]ExpressのテンプレートにCoffeeKupを使ってみた。

前日のエントリーでは、CoffeeScript, Express, Jade, Mochaでコーディングしました。テンプレートにはJadeを使用していました。折角なので、CoffeeKup で書きなおしてみます。コーヒーで統一です。

CoffeeScriptで統一できる以外、特にアーキテクチャが良いわけでは有りません。急速成長中のNode.jsの世界では、まだまだ鉄板と言えるようなフレームワークがないのが現状だと感じています。

CoffeeKupインストール

$ npm install coffeekup

views/layout.coffee

doctype 5
html ->
  head ->
    meta charset: 'utf-8'
    title "#{@title}"
    link rel: 'stylesheet', href: 'http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css'
    style ''' 
      #content {padding-top: 60px}
    '''
  body ->
    div '.topbar-wrapper', ->
      div '.topbar', ->
        div '.topbar-inner', ->
          div '.container', ->
            h3 -> a href: '/', -> @title
            ul '.nav', ->
              li -> a href: 'post/new', -> 'あたらしい投稿'
    div '#content.container', ->
      @body

views/index.coffee

for post in @posts
  h1 -> a href:"/post/#{post.id}", -> post.title
  p '.content', -> post.body

app.coffeeにテンプレートを登録。

app.coffee

express = require("express")
routes = require("./routes")
mongoose = require('mongoose')

app = module.exports = express.createServer()
app.configure ->
  # .coffee拡張地をCoffeeKupとして登録
  app.register ".coffee", require 'coffeekup'
  app.set "views", __dirname + "/views"
  # coffeeをビューエンジンに指定
  app.set "view engine", "coffee"
  app.use express.bodyParser()
  app.use express.methodOverride()
  app.use app.router
  app.use express.static(__dirname + "/public")

app.configure "development", ->
  mongoose.connect 'mongodb://localhost/coffeepress-dev'
  app.use express.errorHandler(
    dumpExceptions: true
    showStack: true
  )

app.configure "production", ->
  mongoose.connect 'mongodb://localhost/coffeepress-prod'
  app.use express.errorHandler()

app.get "/", routes.index
app.get "/post/new", routes.newPost
app.post "/post/new", routes.addPost
app.get "/post/:id", routes.viewPost
app.listen 3000
console.log "Express server listening on port %d in %s mode", app.address().port, app.settings.env

2012年2月25日土曜日

CoffeeScriptとExpressとMongoDbとMochaでNode.jsするメモ

巷ではCoffeeScriptが流行っているそうで。ExpressとMongoDb、テストにMochaを使った定型文的なメモ。

インストールはこんな感じで。

$ sudo npm install -g coffee-script
$ sudo npm install -g js2coffee
$ express sample
$ cd sample && npm install
$ npm install mocha
$ npm install mongoose

expressで出来たテンプレをcoffeeに変換。


$ js2coffee app.js > app.coffee
$ js2coffee routes/index.js > app.coffee

Mochaのテスト。全文です。本来はコツコツやっていきませう。

test/routes-test.coffee

routes = require "../routes/index"
mongoose = require "mongoose"
Post = require "../models/Post"
require "should"
  
describe "routes", ->
  req = 
    params: {} 
    body: {}
  res =
    redirect: (route) ->
      #do nothing
    render: (view, vars)->
      #do nothing
  before (done) ->
    mongoose.connect 'mongodb://localhost/blog', ->
      Post.remove done

  describe "index", ->
    it "should display index with posts", (done) ->
      res.render = (view, vars) ->
          view.should.equal "index"
          vars.title.should.eql "ぶろぐ"
          vars.posts.should.eql []
          done()
      routes.index(req, res)

  describe "new post", ->
    it "should display the add post page", (done) ->
      res.render = (view, vars) ->
        view.should.equal "add_post"
        vars.title.should.equal "あたらしい投稿"
        done()
      routes.newPost(req, res)
    it "should add a new post when posted to", (done) ->
      req.body.post =
        title: "たいとるです"
        body: "ほんぶんです"

      routes.addPost req, redirect:(route) ->
        route.should.eql "/"
        routes.index req, render: (view, vars) ->
          view.should.equal "index"
          vars.posts[0].title.should.eql "たいとるです"
          vars.posts[0].body.should.eql "ほんぶんです"
          done()

メイン

app.coffee

express = require("express")
routes = require("./routes")
mongoose = require('mongoose')

app = module.exports = express.createServer()
app.configure ->
  app.set "views", __dirname + "/views"
  app.set "view engine", "jade"
  app.use express.bodyParser()
  app.use express.methodOverride()
  app.use app.router
  app.use express.static(__dirname + "/public")

app.configure "development", ->
  mongoose.connect 'mongodb://localhost/blog-dev'
  app.use express.errorHandler(
    dumpExceptions: true
    showStack: true
  )

app.configure "production", ->
  mongoose.connect 'mongodb://localhost/blog-prod'
  app.use express.errorHandler()

app.get "/", routes.index
app.get "/post/new", routes.newPost
app.post "/post/new", routes.addPost
app.get "/post/:id", routes.viewPost
app.listen 3000
console.log "Express server listening on port %d in %s mode", app.address().port, app.settings.env

ルーティング

routes/index.coffee

Post = require '../models/Post'

module.exports =
  index : (req, res) ->
    Post.find {}, (err, posts) ->
      res.render "index",
        title: "ぶろぐ"
        posts: posts
  
  newPost : (req, res) ->
    res.render "add_post", title:"あたらしい投稿"
  
  addPost : (req, res) ->
    new Post(req.body.post).save ->
      res.redirect "/"
  
  viewPost: (req, res) ->
    Post.findById req.params.id, (err, post) ->
      res.render 'post', post: post, title: post.title

モデル

models/Post.coffee

mongoose = require 'mongoose'

Post = new mongoose.Schema(
  title: String,
  body: String
)     
        
module.exports = mongoose.model 'Post', Post

ビュー

views/layout.jade

!!!5  
html    
  head  
    title= title
    link(rel="stylesheet", href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css")
    style(type="text/css")
      #content {padding-top: 60px; }
  body    
    .topbar-wrapper
      .topbar
        .topbar-inner
          .container
            h3: a(href="/")= title
            ul.nav
              li: a(href="/post/new") あたらしい投稿
    #content.container!=body

views/index.jade

- each post in posts
  .page-header
    h1  
      a(href="/post/#{post.id}")= post.title
  .content!=post.body

views/add_post.jade

form(method="post", action="/post/new")
  fieldset
    legend=title
    .clearfix
      label(for="title") たいとる
      .input
        input.xlarge#title(type="text", name="post[title]")
    .clearfix
      label(for="post") ないよう
      .input
        textarea#post.xxlarge(name="post[body]", rows="3")
    .actions
      input.btn.primary(type="submit", value="こうかい!")
      |  
      a.btn(href="/") やめた

views/post.jade

.page-header
  h1= post.title
.content!=post.body

実行


$ coffee app.coffee

次はCoffeeKupも使ってみようと思います。Coffeeで統一。

2012年2月24日金曜日

Node.jsのfs.watchはMac(10.7)で上手く動作しない件

ディレクトリの監視ができれば、便利なんですが。ファイル探すのに苦労する共有フォルダを監視しておいて、仕様書が上がってきたら、メールで通知とかやってみようと思っていたのですが。

Mac OS X(10.7)だと、fs.watchが動作していないようです。公式ドキュメントでもLinuxとWindows対応、とありました。ハマった。

fs.watch not reporting events on osx

要調査。上手くいかない日は何やっても上手くいかないですね。今日は寝ます。

2012年2月23日木曜日

Apache2.4.1のabコマンドをインストールする

Apacheに付属してくる「ab」はhttpのベンチマークをとるコマンドです。ついこの間までMac(Lion)ではバグがあってパッチを当てなければ上手く動作しませんでした。つい先日リリースされたApache2.4.1のabコマンドを試してみたところ、上手く動作したのでインストール方法をメモしておきます。

Apacheのサイトからhttpd-2.4.1.tar.gzをダウンロード。


$ tar xzvf httpd-2.4.1.tar.gz 
$ cd httpd-2.4.1
$ ./configure 
$ make
$ sudo cp support/ab /usr/sbin
$ ab -V
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

2012年2月22日水曜日

Node.jsのメモリ使用量を監視してabで負荷テストしてみる

Node.jsのメモリ使用量を監視するスクリプト。アクセス数と同時に出力します。


var util = require('util');
var http = require('http');

var count = 0;
http.createServer(function(req,res) {
    count++;
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('\n');
}).listen(3000);

setInterval(function(){
    console.log("count="+count+", " + util.inspect(process.memoryUsage()));
}, 1000);


実行結果

$ node server.js 
count=0, { rss: 11026432, heapTotal: 4753920, heapUsed: 2512496 }
count=0, { rss: 11304960, heapTotal: 5030400, heapUsed: 2632336 }
count=0, { rss: 11317248, heapTotal: 5030400, heapUsed: 2649848 }
count=0, { rss: 11325440, heapTotal: 5030400, heapUsed: 2655384 }
count=0, { rss: 11329536, heapTotal: 5030400, heapUsed: 2660768 }
count=0, { rss: 11689984, heapTotal: 5030400, heapUsed: 2668688 }
count=1508, { rss: 17809408, heapTotal: 13507456, heapUsed: 6906912 }
count=6232, { rss: 29663232, heapTotal: 23399744, heapUsed: 9537744 }
count=10000, { rss: 32542720, heapTotal: 23602560, heapUsed: 10994144 }
count=10000, { rss: 32542720, heapTotal: 23602560, heapUsed: 11135688 }
count=10000, { rss: 32542720, heapTotal: 23602560, heapUsed: 11140888 }
count=10000, { rss: 32542720, heapTotal: 23602560, heapUsed: 11146088 }
count=10000, { rss: 32542720, heapTotal: 23602560, heapUsed: 11151288 }
count=10000, { rss: 32542720, heapTotal: 23602560, heapUsed: 11156488 }

負荷テストツールとしてapache付属のabコマンドを使ってみました。100クライアントから同時接続で、合計10000アクセスするようなコマンドはこんな感じ。

$ ab -n 10000 -c 100 http://127.0.0.1:3000/

実行結果(1回目)

$ ab -n 10000 -c 100 http://127.0.0.1:3000/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:        
Server Hostname:        127.0.0.1
Server Port:            3000

Document Path:          /
Document Length:        1 bytes

Concurrency Level:      100
Time taken for tests:   2.016 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      650000 bytes
HTML transferred:       10000 bytes
Requests per second:    4959.72 [#/sec] (mean)
Time per request:       20.162 [ms] (mean)
Time per request:       0.202 [ms] (mean, across all concurrent requests)
Transfer rate:          314.83 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   4.5      3      88
Processing:     0   17  15.9     13     123
Waiting:        0   15  15.4     11     119
Total:          1   20  16.5     17     123

Percentage of the requests served within a certain time (ms)
  50%     17
  66%     20
  75%     23
  80%     26
  90%     31
  95%     36
  98%     94
  99%    107
 100%    123 (longest request)

1秒間に約5000リクエストを処理しているようです。もう少しNode.jsをいじめてみようと思います。

2012年2月21日火曜日

Node.jsでファイルの更新を監視する。これは意外と使えるかも。

Node.jsでファイルの更新を監視するには、fs.watchFile()を使います。


var fs = require('fs');
var http = require('http');

fs.watchFile('message.txt', function(curr, prev) {
    console.log('現在の更新:' + curr.mtime);
    console.log('前の更新:' + prev.mtime);
});

http.createServer(function(req, res) {
    res.writeHead(200);
    res.end('fs.watchFile() test');
}).listen(3000);

console.log('server started.');

空のmessage.txtを作ります。

$ echo '' > message.txt

Nodeを起動します。


$ node app.js &

message.txt に追記してみましょう。

$ echo 'Hello World!' >> message.txt
$ 現在の更新:Tue Feb 21 2012 23:03:20 GMT+0900 (JST)
前の更新:Tue Feb 21 2012 23:02:14 GMT+0900 (JST)

$ 

前回のファイル情報と、更新後のファイル情報を参照できます。

業務アプリケーションの連携なかではCSVファイルを特定のフォルダに保存してデータをやりとりするのもしばしば。意外と使えるかもしれません。ファイル変更をsocket.ioで通知したらもっと面白そうです。

2012年2月20日月曜日

killされる前に一言いうNode.jsの書き方

プロセスがkillされたらログ出力するコードです。process.onを使います。


var http = require('http');

http.Server(function(req,res){
    res.writeHead(200);
    res.end('killされる前に一言いいます');
}).listen(3000);

console.log('server started.');



process.on('SIGTERM', function() {
    console.log('KILLされました');
    process.exit(0);
});


&でnodeを起動します。


$ node app.js &
[1] 46937
$ server started.

killしてみます。


$ kill 46937
$ KILLされました

[1]+  Done                    node app.js
$ 

2012年2月19日日曜日

Node.jsで暗号化を使うためのメモ crypto

var crypt = require('crypto');

var cipher = crypt.createCipher('aes256', 'Gufdre89uCXJijdi');
var text = 'この文字列を暗号化します';

var crypted = cipher.update(text, 'utf8', 'hex');
crypted += cipher.final('hex');

console.log('暗号化した文字列: ' + crypted);

var decipher = crypt.createDecipher('aes256', 'Gufdre89uCXJijdi');
var dec = decipher.update(crypted, 'hex', 'utf8');
dec += decipher.final('utf8');

console.log('復号化した文字列: ', dec);

利用できる暗号化アルゴリズムは openssl list-message-digest-algorithms コマンドで確認できます。

2012年2月18日土曜日

Node.jsでオレオレhttpsサーバを立てる

Node.jsのhttpsサーバを立ててみます。テスト用です。

オレオレ証明書を作ります。

$ openssl genrsa -out key.pem 1024 
$ openssl req -new -key key.pem -out csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Shinagawa
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Lucky And Happy Ltd
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:Tomoyuki Inoue
Email Address []:メールアドレス

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:


$ openssl x509 -req -in csr.pem -signkey key.pem -out cert.pem

key.pemとcert.pemをserver.jsと同じディレクトリに保存しておきます。

https_sample.js

/** ++*[https_sample.js]*++ - node.jsでオレオレhttpsサーバのサンプルプログラム
 *
 * @version 0.0.1
 * @author TOmoyuki Inoue
 */

var https = require('https');
var fs = require('fs');

// 証明書のファイルを指定します
var options = { 
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('cert.pem')
};

// ポート3000でサーバを生成します
https.createServer(options, function(req, res) {
    res.writeHead(200);
    res.end('Hello World!\n');
}).listen(3000);

console.log('Started server https://localhost:3000/');

サーバを起動します。

$ node https_sample.js
Started server https://localhost:3000/

https://localhost:3000/ にアクセス。

Safariの場合

信頼されていない証明書の警告が表示されます。

証明書を表示してみます。

続けるボタンで表示されます。

Chromeの場合

アドレスバーの鍵アイコンをクリックして、証明書情報を表示してみます。

[このまま続行]ボタンで表示されます。

2012年2月16日木曜日

イヌ型ドキュメントで開発効率アップ!? JSDogを使ってみよう。

JavaScriptのドキュメント生成ツールと言えば、JSDoc Toolkitが有名です。

でも何か、物 足 り な い。

そこでJSDogです。

JSDogは、JSDocの書き方で「犬」のドキュメントが生成されます。

インストールはnpmで。

$ sudo npm install -g jsdog

書き方はこんな感じです。

/** ++*[npmtools.js]*++ - npmのpackage.jsonを読み込むプログラム
 * 
 * @version 0.0.1
 * @author Tomoyuki Inoue
 */

var fs = require('fs');

/**
 * 指定したパスからpackage.jsonファイルを探索します。
 * @method readPackageJson
 * @param {string} path 読み込みを開始するパスを指定します。
 * @param {function} cb 読み込み完了かエラー時のコールバックを指定します。
 */
var readPackageJson = function(path, cb){
    // パスの読み込み開始
    var path = path + "/node_modules";
    fs.readdir(path, function(err, dirs){
        if(err) cb(err, null);
        // dirsには指定したパスのファイル名・ディレクトリ名の一覧が入っている
        for(var i = 0; i < dirs.length; i++){

            var dir = dirs[i];
            // 頭文字が.の場合読み込まない
            if(dir.charAt(0) === '.') continue;
    
            // package.jsonのパスを指定
            var jsonPath = path + "/" + dir + "/package.json";
    
            // package.jsonを読み込み開始
            fs.readFile(jsonPath, function(err, data){
                if(err) cb(err, null);
                // dataには読み込んだBufferが入っている
                // toString('utf8')で文字列にする
                var jsonStr = data.toString('utf8');
                // JSONをパースしてコールバック
                cb(null, JSON.parse(jsonStr));
            }); 
        }   
    }); 
};

readPackageJson(".", function(err, json){
    if(err) throw err;
    console.log(json.name +"@" + json.version );
});


使い方はコマンドラインで。

$ jsdog -s npmtools.js > index.html

見事に文字化けします。

テンプレートの最初の数行を修正します。

テンプレートはJadeで書かれています。ファイルは、/usr/local/lib/node_modules/jsdog/default.jade あたりにあると思います。

修正前

!!! 5
html(lang="en")
  head
    title= pageTitle

修正後(lang属性削除、メタcharset追加)

!!! 5
html
  head
    meta(charset="utf-8")
    title= pageTitle

もう一度実行すると文字化けが解消。

まだまだ不安定ですが面白いですね。

テンプレートもJade形式なので簡単です。ちょっと修正すると・・・

な事もできます。

2012年2月15日水曜日

CoffeeScriptのインストールメモ

コマンドだけ。
git clone git://github.com/creationix/nvm.git ~/.nvm
source ~/.nvm/nvm.sh
nvm sync
nvm install v0.4.12
nvm use v0.4.12
node --version
npm install -g coffee-script
coffee -v

2012年2月14日火曜日

JavaScriptの綺麗なドキュメントを作るためのメモ

githubを散策していたら、こんなReadmeを見つけました。「Beautiful Docs」というタイトルでJavaScriptに限らず、様々なAPIのドキュメントリンク集となっています。流行の技術ばかりです。

ドキュメントが綺麗で読みやすいというのも、普及していく上で重要なファクターなのかもしれませんね。

JavaScriptのドキュメント生成ツールは、JSDocというのが有名のようです。(若干Activeでないような気もします。)

面白そうなのは、docco というツール。リンク先のようにコメントとコードを同時に見れるようなHTMLを生成します。

今週はJavaScriptのドキュメントをやってみようと思います。

関連ページ

イヌ型ドキュメントで開発効率アップ!? JSDogを使ってみよう。

2012年2月13日月曜日

[Mac]OS X Lionでアプリ起動時に以前のファイルやサイトが開く機能を無効にする方法

作業時にファイルやサイトを開いたまま、アプリケーションを終了してしまうと、次回起動時に以前のファイルやサイトを復元するという、機能がMac OS X Lionから搭載されました。

SafariでもPhotoshopでもWordでもプレビューでも、この機能が有効です。

強制終了になってもこれで安心!な機能なのですが、作業が完了したドキュメントは開いて欲しくありません。

複数のファイルやサイトも同時に開きますので、起動時に時間がかかって仕方ありません。

この機能やっかいなことに、アプリケーションを終了してから時間が経っていてもご丁寧に以前のファイルを開いてくれます。見たくない報告書をまた見る羽目になったり、最新の仕様書を開いたつもりで前バージョンの仕様書だったり。お客さんの前で提案書開こうとしたら社外秘の企画書だったり。

個人的には、ヤメテけれ!な機能です。

若干副作用がありますが、この機能を無効にする方法を紹介します。

FinderでOptionキーを押しながら、メニューの[移動]>[ライブラリ]を選択します。

下の画面で、Optionキーを押します。移動のメニューの中にライブラリが出現します。

「Saved Application State」を選択して、Command+I で情報を見ます。

「ロック」にチェックを入れます。

これだけです。

副作用は、強制終了になってもドキュメントは助かりません。こまめに保存します。

2012年2月12日日曜日

今すぐできる!Cloud9 IDEカスタマイズ! npmインストーラを作ってみよう。

Cloud9 IDEを拡張して、npmのインストーラを作ってみました。npm installとnpm listができるようになりました。

まず、クライアントのViewです。

cloud9/client/ext/npmtools/npmtools.xml

<a:application xmlns:a="http://ajax.org/2005/aml">
    <a:window
      id        = "winDatagrid"
      title     = "NPM Packages"
      icon      = ""
      center    = "true"
      resizable = "true"
      buttons   = "close"
      modal     = "false"
      skin      = "bk-window"
      width     = "660"
      height    = "400"
      kbclose   = "true"
      draggable = "true">
        <a:vbox anchors="0 0 0 0" edge="0 0 0 0">
            <a:hbox>
                <a:textbox id="tbPackage" flex="1">
                
                </a:textbox>
                <a:button id="btnInstall" caption="Install">
                
                </a:button>
            </a:hbox>
            <a:datagrid id="dgList" flex="1">
                <a:each match="[packages]">
                    <a:column caption="Name" value="[@name]" width="50%" />
                    <a:column caption="Version" value="[@version]" width="50%" />
                </a:each>
            </a:datagrid>
        </a:vbox>
    </a:window>
</a:application>

クライアントのロジック。

cloud9/client/ext/npmtools/npmtools.js

define(function(require, exports, module) {
    var ide = require('core/ide');
    var ext = require('core/ext');
    var panels = require("ext/panels/panels");
    var markup = require('text!ext/npmtools/npmtools.xml');
    var util = require("core/util");
    
module.exports = ext.register('ext/npmtools/npmtools', {
    name   : "NPM Tools",
    dev    : "Tomoyuki Inoue",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,
    command : "npmtools",
    desp   : [panels],

    nodes  : [],
        
    hook : function(){
        var _self = this;

        this.nodes.push(
            mnuWindows.insertBefore(new apf.item({
                caption : "NPM Tools",
                onclick : function(){
                    ext.initExtension(_self);
                    // モデルを作成
                    var model = new apf.model();
                    // datagridのmodel属性にmodelを設定
                    dgList.setModel("model", model);
                    // data要素を作成
                    var data = winDatagrid.ownerDocument.createElement("data");
                    // dataをロード
                    model.load(data);
                    // modelを設定
                    dgList.setModel(model);
                    
                    _self.listPackages();
                    
                    // ボタンクリック時のイベントハンドラ
                    btnInstall.addEventListener("click", function(e){
                        if(tbPackage.value.length > 0){
                            tbPackage.clear();
                            btnInstall.disable();
                            _self.installPackage(tbPackage.value);
                        }
                    });
                    // datagridを表示
                    winDatagrid.show();
                }
            })
        ), mnuWindows.firstChild);
        // サーバから受信した時の処理を記述。
        ide.addEventListener("socketMessage", this.onMessage.bind(this));
    },
    
    onMessage: function(e) {
            var message = e.message;
            // datagrid_test以外なら何もしない。
            if(message.type !== "result" 
            && message.subtype !== "npmtools")
                return;
            
            if(message.body.subcommand === "list"){
                this.addPackageToDatagrid(message.body.name, message.body.version);
            }
            if(message.body.subcommand === "install"){
                m.clear();
                this.listPackages();   
            }
    },
    
    listPackages: function(){
        // npm listの実行結果を取得
        ide.send({
            command: "npmtools",
            subcommand: "list"
        });
    },
    
    installPackage: function(packageName){
        var data = {
            command: "npmtools",
            subcommand: "install",
            packagename :packageName
        };
        // サーバにデータを送信。
        ide.send(data);
    },
    
    addPackageToDatagrid: function(packageName, version){
        // モデルを取得
        var m = dgList.getModel();
        
        // packages要素を作成
        var member = winDatagrid.ownerDocument.createElement('packages');
        // name属性を設定
        member.setAttribute('name', packageName);
        // version属性を設定
        member.setAttribute('version', version);
        // data要素の子要素としてpackages要素を追加
        m.data.appendChild(member); 
        
        btnInstall.enable();
    },
        
    init : function(apfNode){},
        
    enable : function(){
        if (!this.disabled) return;
        
        this.nodes.each(function(item){
            item.enable();
        });
        this.disabled = false;
    },
    
    disable : function(){
        if (this.disabled) return;
        
        this.nodes.each(function(item){
            item.disable();
        });
        this.disabled = true;
    },
    
    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
    }
});

});

サーバ側のプラグインです。

cloud9/server/cloud9/ext/npmtools/npmtools.js

var Plugin = require("cloud9/plugin");
var sys    = require("sys");
var fs     = require("fs");

// 初期設定
var DataGridTestPlugin = module.exports = function(ide) {
    this.ide   = ide;
    // hooksに「command」を追加する
    this.hooks = ["command"];
    // 自分の名前
    this.name  = "npmtools";
};

// Pluginクラスを継承する。
sys.inherits(DataGridTestPlugin, Plugin);

(function() {

    // command を実装。workspaceから実行される。
    this.command = function(user, message, client) {
        // 自分以外のコマンドは受け付けない。
        if(message.command != "npmtools")
            return false;
            
        var _self = this;
        // サブコマンドがlistの場合、作業フォルダのpackage.jsonを探索。
        if(message.subcommand === "list") {
            this.listPackages(this.ide.workspaceDir, function(err, json){
                if(err) throw err;
                console.log(json.name + "," + json.version);
                
                _self.sendResult(0, message.command, {
                    subcommand: message.subcommand,
                    name: json.name,
                    version: json.version
                });
            });
            return true;
        }
        
        if(message.subcommand === "install"){
            var args = [message.subcommand, message.packagename];
            // npm installを実行
            this.spawnCommand("npm", args, this.ide.workspaceDir, 
                function(err) {},
                function(out) {},
                function(code, err, out) {
                    // クライアントに送信
                    _self.sendResult(0, message.command, {
                        subcommand: message.subcommand, 
                        packagename: message.packagename
                    });
                }
            );
            return true;
        }
        
        return false;
    };

    // サーバ終了時に起動。
    this.dispose = function(callback) {
        // TODO kill all running processes!
        callback();
    };
    
    // NPMパッケージのリストを取得する
    this.listPackages = function(path, cb) {
        // パスの読み込み開始
        var path = path + "/node_modules";
        fs.readdir(path, function(err, dirs){
            if(err) cb(err, null);
            // dirsには指定したパスのファイル名・ディレクトリ名の一覧が入っている
            for(var i = 0; i < dirs.length; i++){
     
                var dir = dirs[i];
                // 頭文字が.の場合読み込まない
                if(dir.charAt(0) === '.') continue;
                 
                // package.jsonのパスを指定
                var jsonPath = path + "/" + dir + "/package.json";
                 
                // package.jsonを読み込み開始
                fs.readFile(jsonPath, function(err, data){
                    if(err) cb(err, null);
                    // dataには読み込んだBufferが入っている
                    // toString('utf8')で文字列にする
                    var jsonStr = data.toString('utf8');
                    // JSONをパースしてコールバック
                    cb(null, JSON.parse(jsonStr));
                });
            }
        });        
    };

}).call(DataGridTestPlugin.prototype);

実行結果はこんな感じ。テキストボックスにパッケージ名を入力してInstallボタンで、npmでインストールできます。

関連ページ

2012年2月11日土曜日

コツコツ拡張Cloud9。サーバにデータを送信してみる。

Cloud9 IDEを拡張して、サーバと通信をしてみます。

サーバと通信するには、クライアント側、サーバ側の双方にextensionを追加します。

大まかな処理の流れです。

  1. クライアント側で拡張で ide.send(data) でサーバにデータを送信。
  2. クライアント側の ide.js 内の ide.socket.json.send(data) でサーバにデータ送信される。
  3. サーバ側の ide.js の onUserMessage イベントハンドラが起動。
  4. サーバ側の workspace.js の execHook が起動。引数hookに文字列「command」。
  5. execHook内で、サーバ側のプラグインを検索。プラグイン内の command プロパティを起動。
  6. サーバ側のプラグイン内で sendResult(data)でクライアントにデータ送信。
  7. クライアント側の socketMessage イベントハンドラが起動。

クライアント側の拡張では、ide.send(data)でデータ送信。socketMessageイベントハンドラを実装。サーバ側のプラグインでは、commandプロパティを実装。sendResult(data)でクライアントに送信。ということになります。

インストールボタンを押したら、サーバにデータを送信して、その返信をデータグリッドに表示してみます。

まず、クライアント側。Viewは変わらないので省略。

cloud9/client/ext/datagrid_test/datagrid_test.js


define(function(require, exports, module) {
    var ide = require('core/ide');
    var ext = require('core/ext');
    var panels = require("ext/panels/panels");
    var markup = require('text!ext/datagrid_test/datagrid_test.xml');
    var util = require("core/util");
    
module.exports = ext.register('ext/datagrid_test/datagrid_test', {
    name   : "Datagrid Test",
    dev    : "Tomoyuki Inoue",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,
    command : "datagrid_test",
    desp   : [panels],

    nodes  : [],
        
    hook : function(){
        var _self = this;

        this.nodes.push(
            mnuWindows.insertBefore(new apf.item({
                caption : "Datagrid Test",
                onclick : function(){
                    ext.initExtension(_self);
                    // モデルを作成
                    var model = new apf.model();
                    // datagridのmodel属性にmodelを設定
                    dgList.setModel("model", model);
                    // data要素を作成
                    var data = winDatagrid.ownerDocument.createElement("data");
                    // dataをロード
                    model.load(data);
                    // modelを設定
                    dgList.setModel(model);
                    // ボタンクリック時のイベントハンドラ
                    btnInstall.addEventListener("click", function(e){
                        if(tbPackage.value.length > 0){
                            var data = {
                                command:"datagrid_test",
                                message:tbPackage.value
                            };
                            // サーバにデータを送信。
                            ide.send(data);
                        }
                    });
                    // datagridを表示
                    winDatagrid.show();
                }
            })
        ), mnuWindows.firstChild);
        // サーバから受信した時の処理を記述。
        ide.addEventListener("socketMessage", this.onMessage.bind(this));
    },
    
    // 受信時の処理。
    onMessage: function(e) {
            // datagrid_test以外なら何もしない。
            if(e.message.type !== "result" 
            && e.message.subtype !== "datagrid_test")
                return;
            
            var message = e.message;
            
            // モデルを取得
            var m = dgList.getModel();
            // member要素を作成
            var member = winDatagrid.ownerDocument.createElement('member');
            // name属性を設定
            member.setAttribute('name', message.body.message);
            // cv属性を設定
            member.setAttribute('cv', 'CV1');
            // data要素の子要素としてmember要素を追加
            m.data.appendChild(member);
    },
        
    init : function(apfNode){},
        
    nable : function(){
        if (!this.disabled) return;
        
        this.nodes.each(function(item){
            item.enable();
        });
        this.disabled = false;
    },
    
    disable : function(){
        if (this.disabled) return;
        
        this.nodes.each(function(item){
            item.disable();
        });
        this.disabled = true;
    },
    
    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
    }
});

});

次にサーバ側。

cloud9/server/ext/datagrid_test/datagrid_test.js


var Plugin = require("cloud9/plugin");
var sys    = require("sys");

// 初期設定
var DataGridTestPlugin = module.exports = function(ide) {
    this.ide   = ide;
    // hooksに「command」を追加する
    this.hooks = ["command"];
    // 自分の名前
    this.name  = "datagrid_test";
};

// Pluginクラスを継承する。
sys.inherits(DataGridTestPlugin, Plugin);

(function() {

    // command を実装。workspaceから実行される。
    this.command = function(user, message, client) {
        // 自分以外のコマンドは受け付けない。
        if(message.command != "datagrid_test")
            return false;
        
        // クライアントに送信。受信した文字列に「Hello World!」を追加。
        this.sendResult(0, message.command, {
            message: message.message + ' Hello World!'
        });
        console.log('sendResult.');
        
        // trueを返す。
        return true;
    };

    // サーバ終了時に起動。
    this.dispose = function(callback) {
        // TODO kill all running processes!
        callback();
    };

}).call(DataGridTestPlugin.prototype);

実行結果。

2012年2月10日金曜日

新人たちよ、入社おめでとう。まず (0.1+0.2)===0.3 は false である理由を述べて欲しい。

新人の季節です。
入社、おめでとう。
プログラマとしてスキルを磨き、SE、リーダ、プロマネと日本の技術者軽視の道を歩むことでしょう。

頑張って欲しい。

さて、君たちに問いたい。

(0.1+0.2)==0.3

は、false である理由を。

この就職氷河期と言われるなか、勝ち残ってきた君たちのことだ。難しくはないだろう。

画像処理なんかのfloatな世界で生きてきた人たちには簡単すぎる問題だろう。

もっと考えて欲しい。

画面をそのままデータベーススキーマとしてしまう、愚かさを。

インターフェースの粒度が異なるために、未来に負債を押し付けてしまうことを。

君が今、設計しているものは、動くだろうか?

きっと動くでしょう。そのときは。

動けばいい。

それは「目先の」ビジネスには重要な要素だと思う。

だが、違う。

まず動く、そして、修正しても良いことを保証できる。

あとに負債を残さないこと。それが重要だ。

テストコードを書け。

明日につながるのだ。

チームを大事にして欲しい、そしてお客様を喜ばせて欲しい。

でもその前に、まずは、

(0.1+0.2)==0.3

は false だということを説明して欲しい。

それが第一歩だ。


2012年2月9日木曜日

コツコツ拡張Cloud9。フォームの入力を受け取ってグリッドに表示する。

Cloud9 IDEを拡張して、テキストボックスなどのフォームから受け取った文字列をグリッドに表示してみます。

まずはビューにテキストボックス(a:textbox)とボタン(a:button)を追加します。

cloud9/client/ext/datagrid_test/datagrid_test.xml

<a:application xmlns:a="http://ajax.org/2005/aml">
    <a:window
      id        = "winDatagrid"
      title     = "放課後ティータイム"
      icon      = ""
      center    = "true"
      resizable = "true"
      buttons   = "close"
      modal     = "false"
      skin      = "bk-window"
      width     = "660"
      height    = "400"
      kbclose   = "true"
      draggable = "true">
        <a:vbox anchors="0 0 0 0" edge="0 0 0 0">
            <a:hbox>
                <a:textbox id="tbPackage" flex="1">
                
                </a:textbox>
                <a:button id="btnInstall" caption="インストール">
                
                </a:button>
            </a:hbox>
            <a:datagrid id="dgList" flex="1">
                <a:each match="[member]">
                    <a:column caption="メンバー名" value="[@name]" width="50%" />
                    <a:column caption="CV" value="[@cv]" width="50%" />
                </a:each>
            </a:datagrid>
        </a:vbox>
    </a:window>
</a:application>

次にロジックにイベントハンドラを追加してみます。

cloud9/client/ext/datagrid_test/datagrid_test.js

define(function(require, exports, module) {
    var ide = require('core/ide');
    var ext = require('core/ext');
    var markup = require('text!ext/datagrid_test/datagrid_test.xml');
    
module.exports = ext.register('ext/datagrid_test/datagrid_test', {
    name   : "Datagrid Test",
    dev    : "Tomoyuki Inoue",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,
        
    nodes  : [],
        
    hook : function(){
        var _self = this;

        this.nodes.push(
            mnuWindows.insertBefore(new apf.item({
                caption : "Datagrid Test",
                onclick : function(){
                    ext.initExtension(_self);
                    // モデルを作成
                    var model = new apf.model();
                    // datagridのmodel属性にmodelを設定
                    dgList.setModel("model", model);
                    // data要素を作成
                    var data = winDatagrid.ownerDocument.createElement("data");
                    // dataをロード
                    model.load(data);
                    // modelを設定
                    dgList.setModel(model);
                    // ボタンクリック時のイベントハンドラ
                    btnInstall.addEventListener("click", function(e){
                        if(tbPackage.value.length > 0){
                            // モデルを取得
                            var m = dgList.getModel();
                            // member要素を作成
                            var member = winDatagrid.ownerDocument.createElement('member');
                            // name属性を設定
                            member.setAttribute('name', tbPackage.value);
                            // cv属性を設定
                            member.setAttribute('cv', 'CV1');
                            // data要素の子要素としてmember要素を追加
                            m.data.appendChild(member);
                        }
                    });
                    
                    // datagridを表示
                    winDatagrid.show();
                }
            })
        ), mnuWindows.firstChild);
        

    },
        
    init : function(apfNode){},
        
    nable : function(){
        if (!this.disabled) return;
        
        this.nodes.each(function(item){
            item.enable();
        });
        this.disabled = false;
    },
    
    disable : function(){
        if (this.disabled) return;
        
        this.nodes.each(function(item){
            item.disable();
        });
        this.disabled = true;
    },
    
    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
    }
});

});

モデルに追加すると、勝手にdatagridが更新されます。

実行結果はこんな感じ。テキストボックスに文字列入れてインストールボタンをクリックすると、データグリッドに追加されます。

2012年2月8日水曜日

コツコツ拡張Cloud9。APFの仕様 ui.ajax.org はChromeで見よう。

apfのドキュメント、ありました。

APFは、Ajax.orgで開発されているフレームワークです。

2012/02/08現在、Ajax.org にアクセスすると、Cloud9(c9.io)に飛ばされます。

ui.ajax.org でドキュメントを公開している、ということでアクセスすると、SafariでLoading...と表示されたまま進まず。ドキュメントが無いものと諦めておりました。

で、Chromeで見れた、というオチ。

公式サイトでの最終バージョンは2009年2月で2.1。只今2010年2月で3.0 Beta2 Stable。2年経ってもBeta取れてないように見えますが、GitHubのほうは、そこそこ活発です。Cloud9もGitのAPFを使っているようです。

早速ドキュメントを見つつ、昨日の続き。

まずビューから。変更なし。

cloud9/client/ext/datagrid_test/datagrid_test.xml

<a:application xmlns:a="http://ajax.org/2005/aml">
    <a:window
      id        = "winDatagrid"
      title     = "放課後ティータイム"
      icon      = ""
      center    = "true"
      resizable = "true"
      buttons   = "close"
      modal     = "false"
      skin      = "bk-window"
      width     = "660"
      height    = "400"
      kbclose   = "true"
      draggable = "true">
        <a:vbox anchors="0 0 0 0" edge="0 0 0 0">
            <a:datagrid flex="1">
                <a:each match="[member]">
                    <a:column caption="メンバー名" value="[@name]" width="50%" />
                    <a:column caption="CV" value="[@cv]" width="50%" />
                </a:each>
            </a:datagrid>
        </a:vbox>
    </a:window>
</a:application>

コントローラを修正。

cloud9/client/ext/datagrid_test/datagrid_test.js


define(function(require, exports, module) {
    var ide = require('core/ide');
    var ext = require('core/ext');
    var markup = require('text!ext/datagrid_test/datagrid_test.xml');
    
module.exports = ext.register('ext/datagrid_test/datagrid_test', {
    name   : "Datagrid Test",
    dev    : "Tomoyuki Inoue",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,
        
    nodes  : [],
        
    hook : function(){
        var _self = this;

        this.nodes.push(
            mnuWindows.insertBefore(new apf.item({
                caption : "Datagrid Test",
                onclick : function(){
                    ext.initExtension(_self);
                    // モデルを作成
                    var model = new apf.model();
                    // Documentノードを取得
                    var doc = winDatagrid.ownerDocument;
                    // data 要素を作成
                    var data = doc.createElement('data');
                    // 思い切って1000件追加
                    for(var i=0; i<1000; i++) {
                        // member要素を作成
                        var member = doc.createElement('member');
                        // name属性を設定
                        member.setAttribute('name', 'なまえ' + i);
                        // cv属性を設定
                        member.setAttribute('cv', 'CV' + i);
                        // data要素の子要素としてmember要素を追加
                        data.appendChild(member);
                    }
                    // data要素をmodelに読み込む
                    model.load(data);
                    // datagridのmodel属性にmodelを設定
                    winDatagrid.setAttribute("model", model);
                    // datagridを表示
                    winDatagrid.show();
                }
            })
        ), mnuWindows.firstChild);
        
    },
        
    init : function(apfNode){},
        
    nable : function(){
        if (!this.disabled) return;
        
        this.nodes.each(function(item){
            item.enable();
        });
        this.disabled = false;
    },
    
    disable : function(){
        if (this.disabled) return;
        
        this.nodes.each(function(item){
            item.disable();
        });
        this.disabled = true;
    },
    
    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
    }
});

});

結果

これで取得したJSONデータをマッピングできそうです。APFにはJSONとAMFの変換ないのかしら。

2012年2月7日火曜日

コツコツ拡張。Cloud9 IDEのDataGridでモデルとビューを切り離す。

コツコツ拡張。昨日のエントリーで出来なかった、モデルとビューを分離してみます。

まずはViewから。

cloud9/ext/datagrid_test/datagrid_test.xml

<a:application xmlns:a="http://ajax.org/2005/aml">
    <a:window
      id        = "winDatagrid"
      title     = "放課後ティータイム"
      icon      = ""
      center    = "true"
      resizable = "true"
      buttons   = "close"
      modal     = "false"
      skin      = "bk-window"
      width     = "660"
      height    = "400"
      kbclose   = "true"
      draggable = "true">
        <a:vbox anchors="0 0 0 0" edge="0 0 0 0">
            <a:datagrid flex="1">
                <a:each match="[member]">
                    <a:column caption="メンバー名" value="[@name]" width="50%" />
                    <a:column caption="CV" value="[@cv]" width="50%" />
                </a:each>
            </a:datagrid>
        </a:vbox>
    </a:window>
</a:application>

コントローラにモデルを書くとこんな感じ。

cloud9/ext/datagrid_test/datagrid_test.js

define(function(require, exports, module) {
    var ide = require('core/ide');
    var ext = require('core/ext');
    var markup = require('text!ext/datagrid_test/datagrid_test.xml');
    
module.exports = ext.register('ext/datagrid_test/datagrid_test', {
    name   : "Datagrid Test",
    dev    : "Tomoyuki Inoue",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,
        
    nodes  : [],
        
    hook : function(){
        var _self = this;

        this.nodes.push(
            mnuWindows.insertBefore(new apf.item({
                caption : "Datagrid Test",
                onclick : function(){
                    ext.initExtension(_self);
                    // モデルを記述
                    var model = new apf.model();
                    model.load(
                          "<data>"
                        + "<member name=\"平沢 唯\" cv=\"豊崎 愛生\"></member>"
                        + "<member name=\"秋山 澪\" cv=\"日笠 陽子\"></member>"
                        + "<member name=\"田井中 律\" cv=\"佐藤 聡美\"></member>"
                        + "<member name=\"琴吹 紬\" cv=\"寿 美菜子\"></member>"
                        + "<member name=\"中野 梓\" cv=\"竹達 彩奈\"></member>"
                        + "</data>");
                    winDatagrid.setAttribute("model", model);
                    winDatagrid.show();
                }
            })
        ), mnuWindows.firstChild);
    },
        
    init : function(apfNode){},
        
    nable : function(){
        if (!this.disabled) return;
        
        this.nodes.each(function(item){
            item.enable();
        });
        this.disabled = false;
    },
    
    disable : function(){
        if (this.disabled) return;
        
        this.nodes.each(function(item){
            item.disable();
        });
        this.disabled = true;
    },
    
    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
    }
});

});

次はコントローラとモデルを切り離してみます。

2012年2月6日月曜日

コツコツ拡張。Cloud9 IDEでDataGridを表示してみる。

Cloud9 IDEをコツコツ拡張しています。資料が少なくてなかなか進まないのですが。

今日はデータグリッドを表示してみます。

Cloud9ではapf(Ajax Platform Framework)と呼ばれる技術でMVC制御しています。Viewは、http://ajax.org/2005/amf で定義されたマークアップを使います。このapfのドキュメントがほぼ皆無で泣きそうになります。一番の参考資料はソースコード。

早速作っていきます。まずはクライアントの拡張スクリプトから。

cloud9/client/ext/datagrid_test/datagrid_test.js

define(function(require, exports, module) {
    var ide = require('core/ide');
    var ext = require('core/ext');
    var markup = require('text!ext/datagrid_test/datagrid_test.xml');
    
module.exports = ext.register('ext/datagrid_test/datagrid_test', {
    name   : "Datagrid Test",
    dev    : "Tomoyuki Inoue",
    alone  : true,
    type   : ext.GENERAL,
    markup : markup,
        
    nodes  : [],
        
    hook : function(){
        var _self = this;
        this.nodes.push(
            mnuWindows.insertBefore(new apf.item({
                caption : "Datagrid Test",
                onclick : function(){
                    ext.initExtension(_self);
                    winDatagrid.show();
                }
            })
        ), mnuWindows.firstChild);
    },
        
    init : function(apfNode){},
        
    nable : function(){
        if (!this.disabled) return;
        
        this.nodes.each(function(item){
            item.enable();
        });
        this.disabled = false;
    },
    
    disable : function(){
        if (this.disabled) return;
        
        this.nodes.each(function(item){
            item.disable();
        });
        this.disabled = true;
    },
    
    destroy : function(){
        this.nodes.each(function(item){
            item.destroy(true, true);
        });
        this.nodes = [];
    }
});

});

次にビューです。まだ理解が進んでいないので、悔しいですがViewにモデルを突っ込みます。

<a:application xmlns:a="http://ajax.org/2005/aml">
    <a:window
      id        = "winDatagrid"
      title     = "放課後ティータイム"
      icon      = ""
      center    = "true"
      resizable = "true"
      buttons   = "close"
      modal     = "false"
      skin      = "bk-window"
      width     = "660"
      height    = "400"
      kbclose   = "true"
      draggable = "true">
        <a:model id="mdlDatagridTest">
            <data>
                <member name="平沢 唯" cv="豊崎 愛生"></member>
                <member name="秋山 澪" cv="日笠 陽子"></member>
                <member name="田井中 律" cv="佐藤 聡美"></member>
                <member name="琴吹 紬" cv="寿 美菜子"></member>
                <member name="中野 梓" cv="竹達 彩奈"></member>
            </data>
        </a:model>
        <a:vbox anchors="0 0 0 0" edge="0 0 0 0">
            <a:datagrid model="mdlDatagridTest" flex="1">
                <a:each match="[member]">
                    <a:column caption="メンバー名" value="[@name]" width="50%" />
                    <a:column caption="CV" value="[@cv]" width="50%" />
                </a:each>
            </a:datagrid>
        </a:vbox>
    </a:window>
</a:application>

で、拡張を有効に。Extension Managerを使っても良いのですが、Cloud9サーバ起動時に有効になるようにしてみます。90行目から140行目くらいまでのIde.DEFAULT_PLUGINの最後に一行追加します。

client/server/ide.js


Ide.DEFAULT_PLUGINS = [
    "ext/filesystem/filesystem",
    "ext/settings/settings",
    "ext/editors/editors",

    .....

    "ext/tabsessions/tabsessions",
    // 追加
    "ext/datagrid_test/datagrid_test"
];

Cloud9 IDEを再起動して動作を確認してみます。

ひとまず表示に成功。日本語もOK。ヘッダカラムのズレ。。。こういうのは原因探すのに苦労しそう。

2012年2月5日日曜日

Node.jsでpackage.jsonファイルを読み込んでみる

Node.jsのfsモジュールを、ファイルの入出力ができます。

npmでインストールしたモジュールを読み込んでみます。

npmでインストールしたモジュールは、node_modulesディレクトリに保存されます。パッケージ名やバージョン、依存パッケージなどの詳細情報は、node_modules/パッケージ名/package.json に記述されています。fsを使って、package.jsonを読み込んでみます。

read_packages.js

// File Systemモジュールを読み込む
var fs = require('fs');

var readPackageJson = function(path, cb){
    // パスの読み込み開始
    var path = path + "/node_modules";
    fs.readdir(path, function(err, dirs){
        if(err) cb(err, null);
        // dirsには指定したパスのファイル名・ディレクトリ名の一覧が入っている
        for(var i = 0; i < dirs.length; i++){

            var dir = dirs[i];
            // 頭文字が.の場合読み込まない
            if(dir.charAt(0) === '.') continue;
            
            // package.jsonのパスを指定
            var jsonPath = path + "/" + dir + "/package.json";
            
            // package.jsonを読み込み開始
            fs.readFile(jsonPath, function(err, data){
                if(err) cb(err, null);
                // dataには読み込んだBufferが入っている
                // toString('utf8')で文字列にする
                var jsonStr = data.toString('utf8');
                // JSONをパースしてコールバック
                cb(null, JSON.parse(jsonStr));
            });
        }
    });
};

// 実行
readPackageJson(".", function(err, json){
    if(err) throw err;
    console.log(json.name +"@" + json.version );
});

実行結果

$ node read_packages.js
express@2.5.6
socket.io@0.8.7

まあ、npm list コマンドで依存関係まで表示されるので、このプログラム自体に意味はないかも。ファイルを読み込む練習ってことで。

2012年2月4日土曜日

[Node.js] Cloud9 IDEを拡張してnpmのインストーラを作ってみる

Cloud9 IDEには、NideのようなNPM Managerがありません。expressをインストールするには、コマンドラインを使う必要があります。IDEからnpmインストールできるといいですよね。

ローカル環境にインストールしたCloud9 IDEを拡張してみます。

ポイント

  • npmコマンドは結局サーバ側のコマンドなので、クライアントとサーバの両方を拡張する必要がある。
  • gittoolsが参考になる。

全体の流れ

  1. クライアント側からide.send(message)を使ってJSONでコマンド送信。サーバー側のthis.command(user, message, client)で受信。
  2. サーバ側でspawnCommandでコマンド実行。コールバック内でsendResultでストリーム出力をクライアントへ送信。
  3. クライアント側のonMessageで出力を受信。クライアントinit内でideのsocketMessageイベントとバインドしておく。

とりあえずで出来上がったのが、これ。

有効化するとEditからNPM Managerを選択できます。

ダイアログが開いて、パッケージ名を入力。

コンソールにNPMがインストール結果が表示されます。

もう少し理解を深めたらgithubに公開しようと思います。今回は一部だけ。サーバ側のコマンドの実装です。

 this.command = function(user, message, client) {
        if (message.command != "npmtools")
            return false;

        var _self = this;
        var subcommand = message.subcommand;
        if(subcommand !== "install") return false;
        
        var packagename = message.packagename;
        var argv = [subcommand, packagename];
        var npmRoot = ".";

        this.spawnCommand("npm", argv, npmRoot,
            function(err) { // Error
                _self.sendResult(0, message.command, {
                    code: 0,
                    err: err,
                    out: null
                });
            },
            function(out) { }, // Data
            function(code, err, out) { // Exit
                _self.sendResult(0, message.command, {
                    code: code,
                    err: null,
                    out: out
                });
            }
        );

        return true;
    };

あとは、コマンドラインからnpm打てるようにするのと、画面からパッケージの一覧、アンインストールできるようにしたいと思います。

2012年2月3日金曜日

[Node.js] Cloud9 IDEを拡張するメモ。Hello Worldを表示してみよう。

Cloud9 IDEを拡張する手順

ここに書いてあります。

cloud9/client/ext/extension_template/extension_template.js

  1. requireJSをロードする
  2. AMLマークアップextensionをロードする(必要なら)※画面がいるとき
  3. extensionのプロパティを記述する
  4. 変数とメソッドを記述する
  5. ext.registerにextensionのファイルパスを記述する

このextension_templateフォルダをコピーして作っていくと早そうです。

オーバーライドするプロパティ

  1. name ... extension名(必須)
  2. dev ... 開発者名
  3. alone ... 単体動作か、他のextension依存か。boolean指定。
  4. type ... ext.GENERAL なら一般的な extension。 ext.EDITOR ならエディタに対するextension。
  5. markup ... UIを含むなら markup と記述。文字列。
  6. visible ... extensionロード時に表示するかどうか。boolean指定。

オーバーライドするメソッド

  1. init (必須)... 初期設定。hook中に呼び出される。
  2. hook (オプション) ... extensionを登録したとき呼び出される
  3. enable (必須) ... extensionを有効にしたとき呼び出される
  4. disabile (必須) ... extensionを無効にしたとき呼び出される
  5. destroy (必須) ... 後処理。extensionを無効化したとき呼び出される。

extensionを有効化する手順は2通り。

Extension Managerを使う場合

Extension Managerを起動

User Extensionsを選択して、入力欄に ext/extension_template/extension_template と入力。

Addボタンをクリックすると有効になります。

試しに実行。Editから Extension Template Window を選択。

Hello World! とWindowが表示されます。

または、cloud9/server/cloud9/ide.jsに記述