c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

PHP CLIにて、標準入力からストリームが與へられてゐるかだうか判定する

PHPはわりと便利で、簡単なスクリプトを書くのにも不自由しない。「PHPが動作する」ことを前提にしたシステムでは、PerlRubyPythonを導入したり学習させたりせずに、PHPスクリプトを書いてしまへる。

#!/usr/bin/env php
<?php
echo $argv[1];

シェルでは実行ファイルにオプションを渡せる。オプションは空白區切りになる。PHP CLIではオプションは $argv といふarray變數で受け取れる。$argv[0] にはPHPファイル名が入る。これは php some_file.php arg1 arg2 として呼び出した場合と同じ動作である。
またシェルでは実行プロセスに標準入力と標準出力と標準エラー出力がある。PHP CLIではそれぞれ STDIN, STDOUT, STDERR といふ定數で讀み書きできる。$line = fgets(STDIN); fwrite(STDOUT, $line);のやうにだ。
シェルではオプションと標準入力を同時に受け取ることがある。どちらも受け取ればよい。$argv と STDIN を両方使ふのになにも制限はない。
シェルではオプションと標準入力の、どちらかを受け取ることがある。実は少し困る。
オプションが無い場合は標準入力から受け取ることにする。オプションが無いことは isset($argv[1]) ですぐに判定できる。標準入力を受け取らうとする。stream_get_contents(STDIN) 標準入力が與へられてゐなかった場合、ここでPHPの実行は停止してしまう。EOFが入力されるか、fgets() を使っても改行が入力されるまでPHPは待つ。ユーザーにキーボード操作が必要になってしまふ。
標準入力が無い場合はオプションから受け取ることにする。先と同じである。STDINを読みにいくとPHPはそこで待ってしまふ。ユーザーがキーボードで操作してから、ようやくオプションを見に行く。
よくない。

SELECT

Unixではストリームは基本的な要素だから、うまく扱ふシステムコールがある。SELECTシステムコールは幾つかのストリームを指定すると、順番にタイムアウトするまで見に行き、讀み書き可能なストリームを見つけるとそれを使へるやうにしてくれる。
PHPにもこれと似た函數がある。stream_select() を使へる。

#!/usr/bin/env php
<?php
$read   = [STDIN];
$write  = [];
$except = [];
if (isset($argv[1])) {
  echo file_get_contents($argv[1]);
} elseif (stream_select($read, $write, $except, 0)) {
  echo stream_get_contents(STDIN);
} else {
  throw new \Exception();
}

SELECTのタイムアウトには0を指定できる。SELECTする時点で即座に讀み書き可能なストリームだけを探せる。stream_select() の引數は參照でなければならないから、予め變數にいれておかねばならない。ここではSTDINだけを指定する。
オッケー。

Gulpからこれを使ってみる

Gulpはストリームベースのソフトウェアだ。魔が差して上記のPHPスクリプトをストリームのなかで使ってみる。

/* jshint node:true */
'use strict';
var cp = require('child_process');
var gulp    = require('gulp'),
    through = require('through2');

function execPipe(cmd, options) {
  options = options || [];
  return through.obj(function (file, encoding, callback) {
    var me      = this,
        proc    = cp.spawn(cmd, options),
        stdouts = [];
    proc.on('close', function (code) {
      if (0 !== code) {
        me.emit('error', new Error(cmd + ' ' + options.join(' ') + ' ends with ' + code));
        return callback();
      }
      file.contents = Buffer.concat(stdouts);
      me.push(file);
      callback();
    }).on('error', function (err) {
      me.emit('error', err);
    });
    proc.stdout.on('data', function (chunk) {
      stdouts.push(chunk);
    });
    proc.stderr.on('data', function (chunk) {
      process.stderr.write(chunk);
    });
    proc.stdin.write(file.contents.toString(encoding));
    proc.stdin.end();
  });
}

gulp.task('default', function () {
  return gulp.src('src/').
    pipe(execPipe('some_php_cli')).
    pipe(gulp.dest('dest/'));
});

Gulpはnode.jsの通常のストリームではなく、vinyl-fsベースのストリームなので、おとなしくthrough2を使ふのがよい。child_process.spawn() で作ったプロセスに、起動完了を待たず即座に標準入力に書き込んでゐる。結果は標準出力からでてくるから、vinyl-fsのファイルオブジェクトを書き換へる。
練習にしかならない。おとなしくgulp-execやgulp-shellやgulp-runでも使っておくのがよい。