Pages

2012年8月31日金曜日

exports ってなに?

適当に使っていたけど、良く判らなかった exports 。ググって理解したことは、

  • moduleを輸出(export)する時に使う
  • requireを使って、moduleを呼び出すが、exportされたものしか利用できない
  • CommonJSの中の仕様で、当初のjavascriptにはない

と言うことで、早速 CommonJSを見る。

CommonJS Module 仕様 (英語)

相変わらず、英語に弱くて大変。むりやり、訳してみる。 (流石に、酷い訳なので、後で少しづつ手直しします。すみません。)

(ここから訳開始)

Moduleの状況 (使われ方、仕様)

  1. 一つのmoduleの中は、変数 requireがあり、それ(require)は、クラス(Function)だ。 (javascriptでは、クラスは関数の定義として提供される。この文章では、ある場合にはクラスと訳したほうが良いようだ。)
  2. "require"関数は、モジュールの識別子を(パラメータとして)受け取る。
  3. "require"は、外部のmoduleから輸出されたAPIを(戻り値として)返す。
  4. もし、依存の矛盾(cycle)が発生した場合、外部のmoduleは、その通過中の依存の一つから要求された瞬間に実行を完了することができない(APIを返せない)かもしれない。 この場合、"require"によって戻されたオブジェクトは、少なくとも、現在の(実行遅延中の)モジュールの実行前に外部モジュールによって輸出されたものを含んでいなければならない。
  5. もし、要求されたモジュールがなにも返すことができない場合、"require"は、エラーを投げなければならない。
  6. "require"クラスは、"main"プロパティを持つかもしれない。これ(main)は、読み込み専用で、削除されず、プログラムの最上位"module"であることを示します。もし、このプロパティが提供された場合、それ(main)は、メイン・プログラムの"module"オブジェクトに対し参照独立?でなければならない。

    1. "require" 関数は、"paths"属性を持つかもしれない。"Paths"は、path文字列の優先順位をしめす配列であり、(優先順位の)高いものから低いものへの順で、最上位モジュールの配置位置へのパスを格納している。
    2. "paths"プロパティは、sandboxの中に存在してはいけない。(sandboxは、セキュアなモジュール・システムである)
    3. "paths"属性は、全てのモジュールから、参照され独立していなければならない。referentially identical
    4. "paths"オブジェクトを他のオブジェクトで置き換えることは、無効だろう。
    5. "paths"属性が存在する場合、"paths"の内容の直接の修正は、(それが)対応するモジュールの検索の振る舞いにしたがって、反映されなければならない。
    6. "paths"属性が存在する場合、それは検索パスの消耗的なリストである。それは、ローダが内部的に、問われたパスの前後の他の配置を見るようなものだ。"paths"属性が存在する場合、それはローダの特権であり、パスを解決する、標準化する、正規化するために優先的に(使われる)。
  7. 一つのモジュールの中には、"exports"と呼ばれる変数がある。これは、オブジェクトであり、モジュールは、(モジュールが)実行できるように(自身の)APIを(そのオブジェクトに)加える。
  8. モジュールは、輸出(外部へのAPIの提供)の唯一の手段として、"exports"オブジェクトを利用しなければならない。
  9. モジュールには、変数 "moodule"が存在していなければならず、それはオブジェクトである。
  10. "module"オブジェクトは、読み取り専用であり、その"id"プロパティを削除しないこと。(idは)最上位のモジュールの"id"である。"id"プロパティは次の様なものでなければならない、つまり "require(module.id)"は、exports オブジェクトを返しますし、そのオブジェクトはmodule.idに由来するものです。(言い換えると、module.idは、別のモジュールに渡すことが出来、オリジナルのモジュールを返さなければならないことが要求される。)
  11. "module" オブジェクトは、URI文字列を持つかもしれないが、それはリソースを示す完全な形のURIであり、そのURIから、moduleは作られる。"uri"プロパティは、sandbox内に存在してはいけない。(参照できないので)

Module識別子について

  1. モジュール識別子は、/(スラッシュ)で構成された一連の単語である。
  2. 単語とは、CamelCaseスタイルの識別子と"."と".."でなければならない。
  3. モジュール識別子は、".js"のようなファイル識別子を持つかもしれない。
  4. モジュール識別子は、相対的か、トップレベルかも知れない。(ファイルパスのこと?)
  5. もし、最初の単語が、"."とか".."だったら、それは相対位置を示しています。
  6. 最上位のモジュール識別子は、概念的なモジュール・名前空間のルートの位置として解決される。
  7. 相対的なモジュール識別子は、"require"が書かれ、呼び出されたモジュールの識別子に対する相対的なものとして、解決される。

あいまいな点

この仕様は、以下の重要な事項で、相対的にあいまいな点を残している。

  1. モジュールは、データベース、ファイルシステム、ファクトリパターンのオブジェクト(Function)やライブラリにリンクし変換可能になっているかどうか
  2. PATHは、モジュール識別子を解決するためのモジュールローダにサポートされているか

サンプルコード

// math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};
// increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2

module.id == "program";

(ここで訳終了)

参考にしたサイト

以下のサイトは、大変参考になりました。ありがとうございます。

module.exports と exportsの違い

モジュールを輸出する - exports

まず、使われる側は、公開したい関数を exports というグローバル変数(?)のプロパティに代入します。 すると、他のプログラムは、エクスポートされた関数を使用できるようになります。逆に exports にセットしていない関数や変数は他から見えないので、グローバル名前空間が汚れるのを気にする必要がありません。素晴らしいですね。 (中略) モジュールを自身に含める - include モジュールを使う手段に include() 関数というのが用意されています。 require()と同じようにモジュールのファイル名(- .js)を渡して実行するのですが、exports変数にセットされた関数たちを、自分とこの名前空間に展開します。ローカル変数とか、名前がかぶっていたりすると、知らないうちに上書きされたりして危険な感じがします。

2012年8月27日月曜日

JenkinsをApacheの背後で動かす

この記事とタイトルは、その多くを以下のJenkins本家の記事から得ました。

Running Jenkins behind Apache

Jenkinsとは

CI(継続的インテグレーション)のための支援ツールとして有名です。実際に用意されている基本の機能は、Web画面で簡単に操作、設定できる拡張cronと理解しても良いかな。 以下は、日本語サイトからの引用です。

Jenkinsとは

Jenkinsは、ソフトウェアのビルドやcronで起動するジョブなどの繰り返しのジョブの実行を監視します。これらのうち、Jenkinsは現在次の2つのジョブに重点を置いています。

  1. 継続的な、ソフトウェアプロジェクトのビルドとテスト: つまり、CruiseControlやDamageControlが行うこと。 一言で言えば、Jenkinsは、容易ないわゆる「継続インテグレーションシステム」を提供し、開発者が変更をプロジェクトに統合でき、ユーザーがより新しいビルドを容易に取得できるようにします。自動化された継続的なビルドは、生産性を向上させます。
  2. 外部で起動するジョブの実行監視: cronによるジョブやprocmailのジョブで、リモートマシンで動作するものも含みます。例えばcronについて言えば、出力をキャプチャーした定期的なメールだけ受信し、こつこつとそれを見ます。おかしくなっていることに気がつくかどうかは、すべてあなた次第です。Jenkinsは出力を保存し、 いつおかしくなったのか容易に把握することができるようになります。

さくらのVPSにインストール

私の環境では、すでにjava ランタイムはインストールしてあります。本家の指示の通り、実行。

$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
$ sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
$ sudo yum install jenkins
$ sudo service jenkins start

インストールが終了すると、起動スクリプトは、既にセットされているので、手動で起動してみます。デフォルトの実行ポートは、8080で起動します。 私の場合、8080ポートは制限しているので、ローカルから、w3m等で実行されていることを確認しました。

Apacheの背後で動かす

さて、80ポートは既にapacheが使っていますので、ディレクトリの一つとしてjenkinsが稼働しているようにみせるため、Apacheのproxy機能を使うことにしました。 具体的には、http://mydomain/jenkins で、jenkinsにアクセスできるようにします。

  1. モジュール mod_proxy, mod_http を有効にする (httpd.conf)
  2. jenkinsをプロキシ機能で利用するための設定を、追加(今回は専用の設定ファイル /etc/httpd/conf.d/jenkins.conf)します。

jenkins.confの内容は、以下の通り。

ProxyPass /jenkins http://localhost:8080/jenkins
ProxyPassReverse /jenkins http://localhost:8080/jenkins
ProxyRequests Off
<Proxy http://localhost:8080/jenkins*>
Order deny,allow
Allow from all
</Proxy>

この状態で、apacheとjenkinsを再起動して http://mydomain/jenkinsに、アクセスするとアクセス出来ることは出来るのですが、画像とかcssファイルとかが、読み込まれていません。Proxy利用したため、正しいパスを指定していないことが原因です。

インストールされている /etc/sysconfig/jenkins (初期設定記述)を変更します。ポートの変更とかも、このファイルで行います。 path調整のための変更箇所は以下の通り。

JENKINS_ARGS="--prefix=/jenkins"

あと、サーバ起動時に自動起動するようにしておきます。

# chkconfig --level 345 jenkins on
# chkconfig --list | grep jenkins
jenkins         0:off   1:off   2:off   3:on    4:on    5:on    6:off

セキュリティ

初期状態のままでは、誰でもアクセス出来てしまいますので、以下のサイトを参考にセキュリティ設定を追加しておきました。

jenkins セキュリティ設定

  1. 一般ユーザの権限の制限
  2. 管理者の追加
  3. ユーザ追加を不可に などでしょうか。

それにしても、このロゴは間抜けな感じで好きじゃない。有能な執事を意味しているらしいのだが ...........

2012年8月19日日曜日

joinコマンドは使えるか

csvデータなどテキストベースのデータの塊の処理を必要とする仕事が時々まわってくる。 大抵は、検索系なので、grepしたあと、少し手作業で加工するだけ。しかし、たまに複数のデータファイルを特定のキーで結びつけたりしなければならなくなる。 今までは、リレーショナル・データベースにぶち込んでSQLで処理していた。が、先日なんでもUnixコマンドでやりましょう的な記事を見つけて、大変可能性を感じたので、やってみることにした。

用意したのは、以下の2つのCSVファイル。

やりたいことは、sample-地域.csvの県名とsample-ナンバー.csvの出身地を結びつけ、両方の情報を表示すること。SQLで言うjoin

$ cat sample-地域.csv 
ID,地域,県名,
1001,九州,宮崎,
1002,九州,鹿児島,
1003,九州,福岡,
1004,中国,山口,
,,,
,,,
,,,

$ cat sample-メンバー.csv
ID,名前,性別,歳,出身地,,,,,,
1001,山田,男,50,福岡,,,,,,
1002,田中,女,32,宮崎,,,,,,
1003,中山,女,18,福岡,,,,,,
1004,佐藤,女,19,大分,,,,,,
1005,佐藤,男,48,山口,,,,,,
,,,,,,,,,,
,,,,,,,,,,
,,,,,,,,,,
(続く)
,,,,,,,,,,
,,,,,,,,,,

sort , awk, sed そして join

joinコマンドを使うことが今回の目的だ。joinコマンドを使う上で重要なことは、対象ファイルがキーでソートされていること。その下準備のために、sort, awk, sedのコマンドを利用するのだ。

実行したコマンド行は、3行。 実行内容だけ解説します。各コマンドの詳細は helpやmanを見てください。

$ sed -e "/^[,I]/d" sample-メンバー.csv | sort -t , -k 5 | awk -F , '{print $1,$5,$2,$3,$4}' > member.txt
$ sed -e "/^[,I]/d" sample-地域.csv | sort -t , -k 3 | awk -F "," '{print  $1,$3,$2}' > area.txt 
$ join -j 2 area.txt member.txt 

1行目のコマンドライン

sedは、stream editorの略。-eは、"コマンド"を指定するオプション、/^[,I]/d のdは削除(delete)のd、正規表現/^[,I]/に一致する行を削除することを意味する。

sortは、名前の通り並び替え。-t ,でデータが ","で仕切られていることを指定し、-k 5で5番目の項目が並べ替えの基準どすることを指示する。

awkは、万能レコード加工ツールかな。色々出来るらしいので、もはや言語と言って良いかも。{}で囲まれたコマンドを実行する。-F ,はデータのデリミタ(仕切り)が","であることを指定し、{}の中で、項目の並べ替えを行っている。$nが項目のIDだ。 以上のパイプ "|"でつなぎ、仕事をするのが、Unix流だ(そうだ)。

3行目のコマンドライン

3行目のjoinコマンドは、今回のポイント。-j 2は、項目の2番目がジョインするためのキーであることを指定している。キーの指定は、各ファイル個別に出来るが、やはり細かいところはmanを見てください。

要約

1行目で、sample-メンバー.csvファイルを 1) 不要な行を削除し、2) キーでソートし、3) 行毎に整形しspace区切りのファイル member.txtを作る。

2行目で、同様に sample-地域.csvから、area.txtをつくる。

3行目で、2つのファイル area.txt member.txt をジョインする。

作成結果、出力結果は、以下の通り。
CodeRider:sample baker$ cat area.txt 
1001 宮崎 九州
1004 山口 中国
1003 福岡 九州
1002 鹿児島 九州

CodeRider:sample baker$ cat member.txt 
1004 大分 佐藤 女 19
1002 宮崎 田中 女 32
1005 山口 佐藤 男 48
1001 福岡 山田 男 50
1003 福岡 中山 女 18

CodeRider:sample baker$ join -j 2 area.txt member.txt 
宮崎 1001 九州 1002 田中 女 32
山口 1004 中国 1005 佐藤 男 48
福岡 1003 九州 1001 山田 男 50
福岡 1003 九州 1003 中山 女 18

rubyでやってみる

これと、ほぼ同様なことをrubyでやってみた。

総当たりでレコードを調べ、キー値が一致していたら、標準出力に出力すると言う結構乱暴なプログラム。

require 'ostruct'
require 'csv'

area = OpenStruct.new
area.filename = "sample-地域.csv"
area.key = 3 - 1

member = OpenStruct.new
member.filename = "sample-メンバー.csv"
member.key = 5 - 1

CSV::open(area.filename,"r") do |a|
    line_a = a.compact
    next if line_a.size == 0
    CSV::open(member.filename,"r") do |b|
        line_b = b.compact
        next if line_b.size == 0
        $stdout.print [line_a + line_b].join(","), "\n" if a[area.key] === b[member.key]
    end
end

実行結果は、
$ ruby another_one.rb 
1001,九州,宮崎,1002,田中,女,32,宮崎
1003,九州,福岡,1001,山田,男,50,福岡
1003,九州,福岡,1003,中山,女,18,福岡
1004,中国,山口,1005,佐藤,男,48,山口

OpenStructを使ったのは、JavaScriptのように自由にプロパティが設定出来て、整理しやすかったから。(使ってみたかったとかではない :-p)

OpenStructマニュアル(英語)

もっと短くできるかもしれないけど、まあサッとつくるプログラムとしてはこんなもんでよいのでは。

Unixコマンドとrubyとどちらが便利だろうか? また、リレーショナル・データベースにぶち込んでSQLで処理すると言う手もあるが。


(余談) Numbersはすごい


余談だが、サンプルは Appleの表計算ソフト Numbersで作成した。Excelと違って、一寸面白い。表計算ソフトではなく、表プレゼンテーションソフトと言ったほうが良い。 あまり使っていなかったのでビックリした。気づいたことは

  1. シートと表は異なる
  2. シートは、ExcelのWorksheetとは全く違う。むしろBookにちかい
  3. 表には、一つの表を描くのが正しい
  4. 表は、シート内で複数配置することが出来、それぞれがExcelのworkseetのような機能をもつ
  5. その構造の御陰で、他の表のレイアウト(例えば幅とか)に影響されず、別の表を作成できる
  6. シートは、表(オブジェクト)を配置する台紙の役割。これがレポート用紙になる

この構造は、慣れると大変便利だ。Excelで文書を書くのは愚の骨頂だと常々思うが、Numbersならば、ページレイアウトを自然に出来るだろう。

ただ、Excelのように使うと問題ある。今回の場合、CSVへの出力だ。

  • 表毎にCSVを出力出来るが、無駄な行や列を作成しておくと、その分 ,, が生成される。(取り除かなければならない)
  • 行末の処理が、ちょっとおかしい(行末項目のソートが出来ないなど)。CSVを作成する場合は、行末に,を付けるように出力するか、後で補完したほうが良いようだ。