2015年11月24日火曜日

learnyounodeを解いてみる(7問目)

learnyounode [ HTTP CLIENT]


1つ目の引数として指定された URL に、 HTTP で GET を送信する (※)アプリを書いてください。 そのリクエストに対するレスポンスを"data"イベントで受け取り、受け取った全ての文字列を1つずつ改行で区切ってコンソールに書き出してください。


問題は少し説明が分かりにくいですが、ヒントを読むとhttpモジュールのgetを使ってurlから内容をそのまま読めば良いようです。ほぼヒントに書かれている通り書けば良いので問題はないでしょう。

http.get(options, [callback])

httpモジュールはNode入門書では大抵出てくるので何をしているかは理解できるでしょう。

使い方は http.get(url, callback) でいつもの通りコールバックを使用しますが、そのコールバック関数は function callback (response) { /* ... */ } と第一引数がエラーではありません。
response.on()で得られたデータを'data' でエラーを 'error' を取得します。

気をつけるのは得られたデータにresponse.setEncoding()でエンコーディングを適用することです。
まとめると以下のようになります。

get.js

var http = require('http');

var url = process.argv[2];

http.get(url, function(res) {
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
      console.log(chunk);
    }).on('error', function(err) {
      console.error("Error: " + err.message);
    });
});


# おめでとう!

「HTTP クライアント」に対するあなたの回答は合格です!

これが正式な回答です(もしあなたの回答と比較してみたいならどうぞ):

──────────────────────────────────
    var http = require('http')
    
    http.get(process.argv[2], function (response) {
      response.setEncoding('utf8')
      response.on('data', console.log)
      response.on('error', console.error)
    })




2015年11月21日土曜日

learnyounodeを解いてみる(6問目)

learnyounode [MAKE IT MODULAR]


指定したディレクトリから、拡張子でフィルタしたファイルのリストを出力するプログラムを書いてください。 コマンドライン引数の1つ目はディレクトリ名、2つ目は拡張子です。 フィルタリングしたファイル名を1行ずつコンソールに出力してください。非同期 I/O を使ってください。 

module ファイルに処理の大部分を書いてください。module には、3つの引数を取る関数を一つだけ定義してください。 その関数の引数は ディレクトリ名・ファイル拡張子・コールバック関数、という順序です。


次は意外に難問です。learnyounode一の難問と言っていいかもしれません。
問題としては先回のものをそのままモジュールにするだけですが、モジュールにするにはNodeのモジュール化とコールバック関数の使い方にある程度慣れなければなりません。
今までrequire関数で呼び出しのみ使っていたモジュールを自作するのです。

モジュール化自体はいたって簡単です。
exportsにプロパティやメソッドを代入すればいいだけです。

calc.js

exports.add = function(n1, n2) {
    return n1 + n2;
};

そして使用する側はファイル名を指定して呼び出します。

main.js

var calc = require('./calc');

console.log(calc.add(1, 2));

$ node main.js
3


簡単です。ファイル名は拡張子あり'./calc.js'でも無し'./calc'でも行けますがローカルモジュールの場合は先頭にパス'./'が必要です。

ただし、上の書き方だとオブジェクトを代入するとエラーが出ます。Nodeでオブジェクトを代入するにはmodule.exportsを使います。module.exportsは上位互換だと思ってください。なのでNodeでモジュール化するならmodule.exportsを使っておけばいいです。

Modules

先ほどのモジュールをオブジェクトで代入するとこうなります。

calc.js

module.exports = {
    add: function(n1, n2) {
      return n1 + n2;
    }
};


こちらを使っても同じ結果になるはずです。JavaScriptのオブジェクトを知っていればそれほど問題ないでしょう。
以下のようにすればメソッドをどんどん追加できます。

calc.js

module.exports = {
    add: function(n1, n2) {
      return n1 + n2;
    },
    sub: function(n1, n2) {
      return n1 - n2;
    },
    mul: function(n1, n2) {
      return n1 * n2;
    },
    div: function(n1, n2) {
      return n1 / n2;
    }
};


main.js

var calc = require('./calc');

console.log(calc.add(5, 3));
console.log(calc.sub(5, 3));
console.log(calc.mul(5, 3));
console.log(calc.div(5, 3));


$ node main.js
8
2
15
1.6666666666666667


今回の問題にはいろいろ制約があります。

作成する module には、以下の4つの制約があります。
  • 正しい引数を取る関数を定義してください。
  • エラー、もしくはデータを引数に取るコールバック関数を一度だけ呼び出してください。 
  • 2つ目の制約以外には何も変えないでください(グローバル変数や標準出力など)。
  • 発生する可能性のあるエラーは全てコールバック関数に渡してください。 

関数の引数がディレクトリ名・ファイル拡張子・コールバック関数という指定なので先回作ったものをそのまま引数3つの関数でラップしてみます。

findfile.js


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

var dir = process.argv[2];
var ext = process.argv[3];

/*
  ディレクトリ名・ファイル拡張子・コールバック関数の3つの引数を定義する。
  とりあえずコールバックは定義のみ
 */
var find = function(dir, ext, callback) {   
  //先回作った部分
    fs.readdir(dir, function(err, list) {
     if(err) throw err;
     for(var i in list) {
       if(path.extname(list[i]) == '.' + ext) {
         console.log(list[i]);
       }
     }
   });
}

//作った関数の実行
find(dir, ext, function() {});


$ node findfile.js . txt
test.txt
words.txt


ちゃんと動作しますね。これを出力でなくリストの返り値として変更してみます。問題は得られたリストをどうやって返すかですがここでコールバック関数を使用します。与えられたコールバック関数の引数に渡せば呼び出し先で結果が得られます。

findfile.js

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

var dir = process.argv[2];
var ext = process.argv[3];

/*
  ディレクトリ名・ファイル拡張子・コールバック関数の3つの引数を定義する。
 */
var find = function(dir, ext, callback) {
    fs.readdir(dir, function(err, list) {
      var files = [];
      if(err) throw err;
      for(var i in list) {
       if(path.extname(list[i]) == '.' + ext) {
         files.push(list[i]);
       }
      }
      //ここで得られた結果を引数として渡す。
      callback(null, files);
  });
}

//コールバック関数から取り出します。
find(dir, ext, function(err, list) {
    console.log(list);
});

これを実行すれば配列ですが先ほどと同じ結果が得られるはずです。
ここまで来れば簡単ですね。あとはエラー内容を指定どおりに処理して、モジュール化し使う側で得られた結果をイテレートするだけです。

findfile.js

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

module.exports = function(dir, ext, callback) {
    fs.readdir(dir, function(err, list) {
      var files = [];
      if(err) return callback(err);
      for(var i in list) {
        if(path.extname(list[i]) == '.' + ext) {
          files.push(list[i]);
        }
      }
      callback(null, files);
    });
}


usefindfile.js

var findfile = require('./findfile');
var dir = process.argv[2];
var ext = process.argv[3];

findfile(dir, ext, function(err, list) {
    if(err) throw err;
    list.forEach(function(file) {
      console.log(file);
    });
});




$ learnyounode verify usefindfile.js 

・・・・

# おめでとう!

「モジュラーにしましょう」に対するあなたの回答は合格です!

これが正式な回答です(もしあなたの回答と比較してみたいならどうぞ):

───────────────────────────────────────────
solution.js:

    var filterFn = require('./solution_filter.js')
    var dir = process.argv[2]
    var filterStr = process.argv[3]
    
    filterFn(dir, filterStr, function (err, list) {
      if (err)
        return console.error('There was an error:', err)
    
      list.forEach(function (file) {
        console.log(file)
      })
    })

───────────────────────────────────────────
solution_filter.js

    var fs = require('fs')
    var path = require('path')
    
    module.exports = function (dir, filterStr, callback) {
    
      fs.readdir(dir, function (err, list) {
        if (err)
          return callback(err)
    
        list = list.filter(function (file) {
          return path.extname(file) === '.' + filterStr
        })
    
        callback(null, list)
      })
    }

2015年11月20日金曜日

learnyounodeを解いてみる(5問目)

learnyounode [ FILTERED LS]

拡張子によってフィルタしたファイルリストをコンソールに出力するアプリを書いてください。


5問目は、指定した拡張子のファイルを列挙するアプリです。シェルコマンドで言えば「find -name ".拡張子"」の様な結果になるということですね。
 1つめの引数にフォルダのパスを入れ、2つめの引数に拡張子を指定します。2つめの拡張子は拡張子名のみ、("."抜き)で指定とあります。

とりあえず、ファイルの列挙から始めましょう。
非同期でとありますので、ファイル列挙は fs.readdir を使用します。第一引数にパス、第二引数にはコールバック関数です。 Nodeのコールバック関数はcalback(err, list)のパターンが定番なので覚えておきましょう。listは得られたファイルの配列です。

fs.readdir


readdir.js
var fs = require("fs");
var dir = ".";

fs.readdir(dir, function(err, list) {
    if(err) throw err;
    for(var i in list) {
 console.log(list[i]);
    }
});

上の例ではパスがカレントディレクトリで固定されているので引数を指定せず、そのままカレントディレクトリ内のファイルが列挙されるはずです。
if (err) throw err; もエラーをthrowさせるNodeのイディオムなので無くても正常であれば動作します。
forは得られた配列を展開しているだけなので、イテレータ「files.forEach(function (file) { }」でもいけます。

得られたファイルリストから拡張子を抜き出すには、文字列操作を使っても構いませんがヒントに拡張子を抜き出すNodeのpathモジュールを使うと役に立つと書いてあるのでpathモジュールのextname関数を使用します。extname関数は引数にファイルを入れると拡張子だけ抜き出してくれます。それを判定に使えばいけますね。

path.extname


気をつけることは引数には拡張子名だけ指定してextnameで抜き出せる拡張子が"."付きだということです。なので"."を足してから比較します。
あとは引数と得るのは先回使ったprocess.argv[N]なので組み合わせて完了です。

extname.js
var fs = require("fs");
var path = require("path");

var dir = process.argv[2];
var ext = process.argv[3];

fs.readdir(dir, function(err, list) {
    if(err) throw err;
    for(var i in list) {
   if(path.extname(list[i]) == '.' + ext) {
      console.log(list[i]);
   }
    }
});

$ learnyounode verify extname.js 

...................

# おめでとう!

「LSのフィルター」に対するあなたの回答は合格です!

これが正式な回答です(もしあなたの回答と比較してみたいならどうぞ):

─────────────────────────────────────────
    var fs = require('fs')
    var path = require('path')
    
    fs.readdir(process.argv[2], function (err, list) {
      list.forEach(function (file) {
        if (path.extname(file) === '.' + process.argv[3])
          console.log(file)
      })
    })

2015年11月9日月曜日

learnyounodeを解いてみる(4問目)

learnyounode [MY FIRST ASYNC I/O! ]

非同期処理 をするファイルシステムのメソッドを使ってファイルの改行数を出力するアプリを書いてください。 cat file | wc -l と同じようなアプリです。 アプリの1つ目のコマンドライン引数は、そのファイルへのパスです。


4問目は3問目をただ非同期にしただけです。ヒントにある様にNodoでは非同期の文化が前提です。
なぜならNode.jsノンブロッキングなI/Oを実装することで様々な優位性を引き出しているからです。Node自体の解説は置いておくとしてNodeでノンブロッキングな書き方を実装するにはコールバック関数によって行われます。JavaScriptをある程度書いたことのある人ならご存知でしょう。

同期型のコード
var data = $.post(fname);
//上の処理が終わるまで実行されない
console.log(data)


非同期型のコード
$.post(fname, function(data) {
  console.log(data)
});
//上の処理が終わらなくても実行される
console.log("OK")

つまり、3問目の処理をコールバック関数に納めれば良いだけです。


filelinesAsync.js
var fs = require('fs');
var filename = process.argv[2];

fs.readFile(filename, function(err, data) {
    var lines = data.toString().split('\n').length - 1;
    console.log(lines);
});


$ learnyounode verify filelinesAsync.js 

# おめでとう!

「初めての非同期I/O!」に対するあなたの回答は合格です!



2015年11月8日日曜日

PythonでCGIスクリプトページが表示されない時

CGIスクリプトでスクリプトが実行されない時


PythonスクリプトでCGIスクリプトをサーバーから実行させようと思ってWebページを開こうと思っても表示されない時があります。
様々なケースが考えられますが、単純なミスの場合もあります。

サーバーを起動させているシェルのエラーを見ながら何がいけないのか疑ってみましょう。


表示されない場合(エラーに何も出ないケース)


パスは合っているのにWebページに何も表示されない場合。シェルにも異常が出てない場合はContent-Typeに全角文字が混ざっていたりスペル違いがあったりそのあたりを疑ってみましょう。

表示されない場合(エラーに「FileNotFoundError: [Errno 2] No such file or directory: 」が出るケース)


同じくWebページが表示されずシェルに「FileNotFoundError: [Errno 2] No such file or directory: 」のエラーが出る場合。スクリプトの先頭行シェバング行のパスが合っていない可能性が高いです。
通常のスクリプトは実行出来てもCGIサーバ-から実行する場合、相対パスからだと辿れないためそのエラーが発生します。絶対パスに書き換えてみてください。
現在のPythonのパスは「$ which python」で現在のPythonの実行パスを表示させ。出てきたパスをコピーして「ls -la 」で表示させるとリンクされている絶対パスを取り出せるのでそれをコピーしてシェバング行を書き換えてから実行してみてください。ただしローカル環境の絶対パスそのままだと移植性に乏しいので #!/usr/bin/env python3 を使用するかリンクを貼っておいた方が後々楽でしょう。

その他のエラー


スクリプト自体のパスが違っていたり、スペルミスでスクリプトファイルが存在してなかった場合にはシェルに「code 404, message No such CGI script」エラーが出てWebページに「Error response Error code: 404」が表示されます。

後は、スクリプトのパーミッションが(755)などに許可されていなかったり他から移動させたファイルなどやSELinuxのコンテキストが違っていたりしていたりする(code 403, message CGI script is not executable )のでそのあたりを疑ってみましょう。

2015年11月5日木曜日

learnyounodeを解いてみる(3問目)

learnyounode [MY FIRST I/O!]

同期処理 をするファイルシステムの関数を使ってファイルの改行文字(\n)の数を出力するアプリを書いてください。 cat file | wc -l と同じようなアプリです。 アプリの1つ目のコマンドライン引数は、そのファイルへのパスです。テスト用のファイルを作る必要はありません。


3問目はファイルを読み込んでそのファイル内に書かれている文字列の行を表示するというアプリです。ファイル読み込みは各プログラミング言語でも必須の機能なのですが、Nodeの親と言えるJavaScriptにはその特殊な立場やセキュリティの問題からファイルを扱う機能がありませんでした。
ですからビルトインオブジェクトにもFileオブジェクトはありません。
ただしAjaxなどでサーバからファイルを取得する限定的な扱いは存在しましたし、HTML5から制限のある中でローカルファイルを扱えるFileクラスも定義されました。
JavaScriptがそのようなことなのでNodeでどうファイルを扱うか困ってしまいそうですが、幸いNodeでファイルを読み込むにはfsというモジュールを使用することで扱えます。fsはFile Systemの略だそうです。

File System

問題で気をつけることは同期処理が指定されていることです。Nodeでは非同期が売りなので同期処理の方が使用頻度は少ないでしょうが、振る舞いとしては同期処理のほうが簡単なので問題として出されたのでしょう。
ファイルを同期処理で読み込むにはreadFileSyncプロパティを使用します。

fs.readFileSync

それを踏まえてファイルを読み込んで見ましょう。

readFile.js

var fs = require('fs');

var filename = "読み込むファイル"
var data = fs.readFileSync(filename, 'utf8');
console.log(data);


これでファイルの内容が読み込めました。
後は改行('\n')を数えるだけです。ヒントには得られたバッファを文字列に変えてから改行文字をsplitメソッドで区切れば改行ごとに分割された文の配列になるのでその要素数を数えれば改行を数えたことと同じになると解説してくれています。ただし最期の改行で句切られた空要素も入るのでそれは-1することに注意します。

コマンドライン引数からファイルを読み込んでと書いてあるので前回のprocess.argv[2]でファイル名を引き取るようにします。

以上を合わせて

filelines.js

var fs = require('fs');
var filename = process.argv[2];

var data = fs.readFileSync(filename, 'utf8');
var lines = data.toString().split('\n').length - 1;
console.log(lines);


$ learnyounode verify filelines.js 

以上で合格です。

2015年11月4日水曜日

learnyounodeを解いてみる(2問目)

learnyounode [BABY STEP]

問題: 以上の整数をコマンドライン引数として受け取り、それらを足し合わせた値をコンソール(stdout)に出力するコマンドラインアプリを書いてください。


さて、この問題にはコマンドライン引数が必要ということなので、まずはNodeでコマンドライン引数を取る方法を調べます。 ヒントにはNodeでコマンドライン引数を取るにはprocessオブジェクトのargvプロパティを使用すると書いてあります。
  process.argv

processはグローバルオブジェクトなのでモジュールを呼ぶことなしに使えます。

process.argvから取れる引数は配列であり1つ目の引数は"node"で、2つ目の引数は"実行ファイルのパス"で3つ目から入力した引数となっています。

まずこのコードでどのように引数が取れるか試してみましょう。

argv.js

console.log(process.argv)

$ node argv.js 1 2 3
[ 'node',
  '.../node/test/argv.js',
  '1',
  '2',
  '3' ]



コマンドライン引数はスペースごとに一つの要素としてすべて文字列の配列で取られていることが分かります。
従って第三引数から取って、数値に変換することが理解できれば簡単です。

sum.js

var len = process.argv.length;
var sum = 0;
for(var i = 2; i < len; i++) {
    sum += Number(process.argv[i]);
}
console.log(sum);

$ learnyounode verify sum.js


✓ 回答内容は想定回答とマッチしました

# おめでとう!

「ベイビーステップ」に対するあなたの回答は合格です!

正式な回答が出てきますがヒント通りに書いたのでほぼ同じでしたね。

2015年11月3日火曜日

learnyounodeでNode.jsを段階的に学ぶ

learnyounodeとは?

learnyounodeとは、NodeSchoolが提供するnodeモジュールのworkshoperによって作られる対話形式のチュートリアルです。
コンソールさえあれば簡単に学ぶことが出来るのでお手軽です。

元は英語ですが有志たちにより多言語化されており日本語にも対応しているので言語選択により日本語で学ぶことが出来ます。

learnyounodeの難易度としては、JavaScriptの基本はおさえており、Node.jsも入門から基本操作や幾つかのモジュールを触ったことがあれば解けるのではないでしょうか?
問題はNodeの基本といえる機能を一通り出してくれるので全問解ければNodeの概要は理解できたと言えるかもしれません。Nodeの理解や力試しにはもってこいです。

インストール

$ npm install -g learnyounode




言語選択がない場合はlearnyounode-jpをインストールしてください。

$ npm install -g learnyounode-jp

インストールできたらlearnyounodeコマンドで起ち上げます。

$ learnyounode

そこで言語選択をするか -l ja オプション追加で日本語に変換できます。
問題が出されたらその条件のアプリを作成し verify コマンドで正解かどうか検証します。



$ learnyounode verify [作成したファイル]

で正解すると[完了済み]が付きます。

他にもチュートリアルが幾つか用意されています。

NodeSchool



learnyounodeを解いてみる(2問目)
learnyounodeを解いてみる(3問目)
learnyounodeを解いてみる(4問目)
learnyounodeを解いてみる(5問目)
learnyounodeを解いてみる(6問目)
learnyounodeを解いてみる(7問目)
learnyounodeを解いてみる(8問目)