JSCron よりもうちょっと Cron っぽいことをする

JSCron が Cron としてはかなりいまいちで不満だったので、適当にでっちあげてみた。

if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function (elt /* , from */) {
    var len = this.length;
    var from = Number(arguments[1]) || 0;
    from = (from < 0) ? Math.ceil(from) : Math.floor(from);
    if (from < 0) from += len;
    for (;from < len; from++) {
      if (from in this && this[from] === elt) return from;
    }
    return -1;
  }
}

function Cron() {
  var self = arguments.callee;
  if (self.instance == null) {
    this.initialize.apply(this, arguments);
    self.instance = this;
  }
  return self.instance;
}

Cron.prototype.month_mapping = [
  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'
];
Cron.prototype.week_mapping = [
  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
];

Cron.prototype.REGEXP_NUMBER = '((?:\\d|\\*)(?:[\\d\\*\\,\\-\\/]*\\d)?)';
Cron.prototype.REGEXP_MONTH = '((?:\\d|\\*)(?:[\\d\\*\\,\\-\\/]*\\d)?|' 
    + Cron.prototype.month_mapping.join('|') +')';
Cron.prototype.REGEXP_WEEKDAY = '((?:\\d|\\*)(?:[\\d\\*\\,\\-\\/]*\\d)?|'
    + Cron.prototype.week_mapping.join('|') + ')';
Cron.prototype.REGEXP_CRON = new RegExp(
  '^' + [
    Cron.prototype.REGEXP_NUMBER,
    Cron.prototype.REGEXP_NUMBER,
    Cron.prototype.REGEXP_NUMBER,
    Cron.prototype.REGEXP_NUMBER,
    Cron.prototype.REGEXP_MONTH,
    Cron.prototype.REGEXP_WEEKDAY
  ].join(' ') + '$', 'i'
);

Cron.prototype.initialize = function() {
  this.tasks = {};
  this.counter = 0;
  return this;
}

Cron.prototype.wakeUp = function() {
  var self = this;
  this.pid = setInterval(function() {
    self.exec.apply(self);
  },1000);
}
Cron.prototype.shutdown = function() {
  clearInterval(this.pid);
}

Cron.prototype.parse = function(cronfield) {
  function expandAsterisk(max) {
    var expando = [];
    for(var i = 0; i < max; i++) expando.push(i);
    return expando;
  }

  function expandInterval(spec, max) {
    var expando = [];
    var sp = spec.split('/');
    if (sp.length != 2) throw "illigal spec: " + spec;

    var interval = parseInt(sp[1]);
    if (interval <= 0) throw "illigal spec: " + spec;
    var range;
    if (sp[0].charAt(0) == '*') {
      range = [0, max - 1];
    } else {
      range = sp[0].split('-');
      range = [parseInt(range[0]), parseInt(range[1])];
    }
    if (range.length != 2 || range[0] > range[1]) throw "illigal spec: " + spec; 

    for (var pos = range[0], max = range[1]; pos <= max; pos += interval) expando.push(pos);
    return expando;
  }

  function expandRange(spec, max) {
    var expando = [];
    var range = spec.split('-');
    range = [parseInt(range[0]), parseInt(range[1])];

    if (range[0] < 0 || range[1] > max) throw "illigal spec: " + spec;

    for (var i = range[0], rangeMax = range[1]; i <= rangeMax; i++) expando.push(i);
    return expando;
  }

  function expand(spec, max) {
    var expando = [];
    var intValue = parseInt(spec);
    if (typeof spec == 'number') {
      expando.push(spec);
    } else if (spec.indexOf(',') != -1) {
      var specs = spec.split(',');
      for (var i = 0, length = specs.length; i < length; i++)
        expando = expando.concat(expand(specs[i], max));
    } else if (spec.indexOf('/') != -1) {
      expando = expando.concat(expandInterval(spec, max));
    } else if (spec.indexOf('-') != -1) {
      expando = expando.concat(expandRange(spec, max));
    } else if (spec == '*') {
      expando = expando.concat(expandAsterisk(max));
    } else if (intValue >= 0 && intValue <= max) {
      expando.push(intValue);
    } else {
      throw "illigal spec: " + spec;
    }
    return expando;
  }

  var timespec = this.REGEXP_CRON.exec(cronfield);
  if (!timespec) throw 'illigal arguments: ' + cronfield;
  timespec.shift();
  var month = this.month_mapping.indexOf(timespec[4].toLowerCase());
  var weekday = this.week_mapping.indexOf(timespec[5].toLowerCase());
  if (month != -1) timespec[4] = month;
  if (weekday != -1) timespec[5] = weekday;
  if (timespec[5] == 7) timespec[5] = 0;
  return [
    expand(timespec[0], 60), // seconds
    expand(timespec[1], 60), // minutes
    expand(timespec[2], 24), // hours
    expand(timespec[3], 31), // day
    expand(timespec[4], 12), // month
    expand(timespec[5], 7) // weekday
  ];
}
Cron.prototype.register = function (fn, spec) {
  var id = this.counter;
  if (id == 0) this.wakeUp();
  var sp = this.parse(spec);
  this.tasks['_' + id] = {
    callback: fn,
    timespec: sp
  };
  this.counter++;
  return id;
}
Cron.prototype.exec = function () {
  var now = new Date();
  var currents = [now.getSeconds(), now.getMinutes(), now.getHours(), now.getDate(), now.getMonth(), now.getDay()];
  for(var i in this.tasks) if (this.tasks.hasOwnProperty(i)) {
    var spec = this.tasks[i];
    var matched = true;
    for (var l = 0, len = currents.length; l < len; l++) {
      if (spec.timespec[l].indexOf(currents[l]) == -1) {
        matched = false;
        break;
      }
    }
    if (matched) spec.callback.apply();
  }
  return this;
}
Cron.prototype.cancel = function(id) {
  delete this.tasks['_' + id];
  for (var i in this.tasks) if (this.tasks.hasOwnProperty(i)) return this;
  this.shutdown();
  return this;
}

/**
console.log(new Date());
var timer = new Cron();
var pid = timer.register(function() {
  console.log(new Date());
}, "1,3,10-15,20-59/5 0-33/1,35-37,40-59 * * JUL mon");

setTimeout(function() {
  console.log('cancel: ' + pid);
  timer.cancel(pid);
},10000);
*/

http://github.com/send/misc/tree/master の js/cron.js に置いてありますが、 gist にしなかったのはバカだなーと思いますが、コミットしたら面倒になってしまったので gist や coderepos 等に持っていきたい人は好きに持っていって弄って下さい。
バグは勿論、パフォーマンス面やメモリの使いかた等、不味いところはまだまだあるのはわかってるので、なんかあったら教えて下さると俺が喜びます。

if 文と {} とコーディングスタイル

最近、また if 文における {} 省略の話をよく見掛けるようになった。何年たっても繰り返される話なんだろうなと思う。
なので、人のことをとやかくいう前に、自分のコードの変遷を考えてみると、簡単に言うと以下の様になる。

  • OOP 厨以前
    • {} 必須派
    • {} なしはバグの温床
  • OOP 厨時代
    • {} 以前に分岐ありえない
    • {} 以前に分岐は悪。クラス分けするのが正義
  • OOP 厨脱却後
    • 分岐はあり。{} は基本的に使わない
    • 後々ややこしいことありそうだなと思うところは {} つける
    • {} ありの部分は、将来リファクタリング入る可能性もあるので要注意


三項演算子の話も似たようなことでよく出てくる話なんだけど、どっちの話も同じような話で、そもそも意味がわかってないという人と、使いかたが合理的じゃないという人が槍玉に上げられてるような気がする
で、そういうことが発生しないようなコーディング規約みたいな制約をみんな使っちゃっていて、そういうものの大半は、ダメな人がダメなことをしないようにという排除方向に向かってるとしか思えない。
勿論、それが局所的な状況においては必要なケースもあるだろうし、政治的な理由でそうなることも多いと思う。
でも、俺は十把一絡でそういうことをやるのがいいとは思えないし、その人のレベルによって自由度はある程度以上は絶対必要だと思う。そのスキルに応じて、その人が納得出来るコードや自然な書き方というものを書いていける環境は、ちゃんあるべきだと思う。
そういうことを考えると、プログラミング一つを取っても、習熟度に応じたインタフェースという意味での規約があって、そこを超えたらまた変わってくるコーディングスタイルが出てきてという形が、綺麗なのかなと考えたりしてる。


まあ、そんな観点は抜きにしても、 if の後に {} 使ってるかどうかなんてレベルの話であれば、そういうチェッカーは殆どの環境でツールとして存在してるし、そういうツールが無かったとしたら、あなたがその環境のことを知らないのでもっと勉強しなきゃいけないレベルか、逆にアーリーアダブター過ぎるので実装すればみんな幸せあなたも幸せってことなんですよね。


結局、コーディングスタイルレベルだったら、殆どの場合、機械的に抑えられる話だし、大した要件じゃない。
そもそも {} あるなし論争で済むような短いロジックだったらテストコードをちゃんと書かせた方がよっぽどマシだ。
それとも、昨今の状況でも if と {} とかで喧喧囂囂とかするレベルの難しいことがいろいろあるのか?
そんなことが致命的なレベルで重要な話なの?


そんなことより、ウォーターフォールでも TDD でもなんでも良いんだけど、品質保証しづらい一番キツい部分をどう担保するかみたいな部分の方が大切じゃないのかなあなど思ったりする。

プロンプトの色をローテートする

Introduction of the ZSH に載ってるやりかただと乱数を使ってるために同じ色になったりしてちょっと不満だったので zshrc を以下のようにしてみた。

PROMPT_COLOR=32
precmd() { PROMPT_COLOR="$[32 + ($PROMPT_COLOR - 31) % 5]";}
PROMPT=$'%{^[[${PROMPT_COLOR}m%}%U%n@$HOST'"%u%{^[[m%} %(!.#.$) "
RPROMPT=$'%{^[[${PROMPT_COLOR}m%}[%~]%{^[[m%}'

これで、行が変わる毎にプロンプトの色が 32-37 の間でローテートしてくれていい感じになる。

Twitter でベーシック認証越しになにかやるのは控えた方が良い

なんでも JSONP で取れるので、どこかでベーシック認証通していると、認証ダイアログも出ずに DM のぶっこ抜きなんかが出来てしまったりする。


DM を JSONP で取得するデモ(ソース見て貰えればわかるけど、サーバサイドにデータ送ったりしてません)


まあ予想通りといえば予想通りだけど DM なんかが JSONP できるのはちょっと問題なんじゃないかなと思う。
最初から一貫して、セキュリティなどに関しては twitter はあてに出来ないのは何も変わっていないので protected や DM なんかで、公開されたら不味い情報のやりとりをしているような人は気をつけた方が良いでしょう。

PHP6 を使ってみる 1

PHP6 をそろそろ試してみようと思ったので、まずはインストールから。
OS は、 Mac OSX Tiger.
インストール先は /opt/local/php6 .

まずは configure.

./configure   \
--prefix=/opt/local/php6 \
--with-apxs2=/opt/local/apache2/bin/apxs  \
--enable-filter=shared \
--with-openssl=shared  \
--with-bz2=shared  \
--with-curl=shared \
--enable-exif=shared \
--with-gd=shared \
--with-jpeg-dir=/opt/local \
--with-png-dir=/opt/local \
--with-zlib-dir=/opt/local \
--with-freetype-dir=/opt/local \
--enable-gd-native-ttf=shared \
--enable-gd-jis-conv=shared \
--enable-pdo \
--with-pdo-mysql=/opt/local/lib/mysql5 \
--with-ldap=shared \
--with-imap=/opt/local \
--with-kerberos \
--with-imap-ssl=shared \
--enable-mbstring \
--with-xsl=shared  \
--with-xmlrpc=shared \
--enable-zip=shared  \
--enable-intl=shared \
--with-gettext=shared,/opt/local \
--with-mcrypt=shared,/opt/local

いちいち *-dir の類にパス指定するのも馬鹿っぽいから LD_LIBRARY_PATH や LDFLAGS に /opt/local/lib 入れたりしたんだけど、結局上手く行かずにベタに指定したけど、そういうもんなのかな?


あと、入ってると make で失敗するオプションが --enable-phar. /Users/kazuaki/work/php/php6/php6.0-200807250830/ext/phar/phar.php requires PHP extension Phar. って言われても、どうすんだよって感じ。 PHP5 の Phar は入ってるけど、そこじゃないんだろな。
気になるけど、追っかけようと思ったけど、今日は力尽きたので、やる気になったら調べる。
PDO に関しても OSX だと shared は、まだサポートされてなかったりで shared 外したりした。


んで configure 終わったら、いつものように make && make test んで、 sudo make install


最後に、cd /opt/local/bin/; sudo ln -s /opt/local/php6/bin/php php6 した。

疲れたので今日はここまで。

URL っぽい文字列にリンク当てる vimperator plugin

URL っぽいのにリンク貼られてない場合、開くのが面倒だなと感じてきたので作った。
こういうのは GM とかの方が良いかなあとか思ったけど、重そうなので vimperator plugin で。

:anc で、その buffer の url っぽい文字列にリンク当てる。

liberator.buffer.evaluateXPath() とか Range.surroundContents() 初めて使ったけど便利。


xpath や URL 判別の正規表現はちょっと適当。

.zlogin にした

前回の続き。
ログイン項目に登録しても上手く動かなかったので、 .zlogin に仕込んで動かすようにしてる。
スクリプト自体もコマンドの取扱いをちょっと神経質にしてみたり、ちょっとした bug fix した。

supervisedium

#!/bin/sh

RM="/bin/rm"
CAT="/bin/cat"
OPEN="/usr/bin/open"
WHOAMI="/usr/bin/whoami"
SLEEP="/bin/sleep"


PIDFILE="/tmp/supervisediumd.`$WHOAMI`"
COMMAND="/Users/kazuaki/bin/supervisediumd"

case $1 in
  "clean" )
    echo "cleaning pidfile: $PIDFILE"
    $RM -rf $PIDFILE
    ;;
  "start" | "" )
    if [ -f $PIDFILE ] ; then
      echo "supervisediumd is already running"
      exit 0
    else
      $COMMAND $PIDFILE &
      echo "starting supervisediumd $!"
    fi
    ;;
  "stop" | "shutdown" )
    echo "stopping supervisediumd..."
    if [ -f $PIDFILE ] ; then
      kill `$CAT $PIDFILE`
    fi
    ;;
  "restart" )
    echo "restarting supervisediumd..."
    if [ -f $PIDFILE ] ; then
      kill `$CAT $PIDFILE`
    fi
    while [ -f $PIDFILE ] ; do
      $SLEEP 1
    done
    $COMMAND $PIDFILE &
    ;;
  * )
    $CAT <<_EOT_
usage:
$0 [start|stop|shutdown|clean|restart]
  start :
    start supervisediumd
  stop, shutdown :
    shutdown supervisediumd
  clean :
    cleaning pidfile
  restart :
    restart supervisediumd
_EOT_
    ;;
esac

supervisediumd

#!/bin/sh

# settings
COUNT_LIMIT=20
CPU_LIMIT=750
APP="/Applications/Adium.app"
AWK="/usr/bin/awk"
GREP="/usr/bin/grep"
PS="/bin/ps"
EXPR="/bin/expr"
OPEN="/usr/bin/open"
SLEEP="/bin/sleep"
RM="/bin/rm"

PSINFO=`$PS ux | $GREP $APP | $GREP -v $GREP`
if [ -z "$PSINFO" ] ; then
  $OPEN -a $APP
fi

PIDFILE=$1
if [ -f $PIDFILE ] ; then
  if [ -z "$PSINFO" ] ; then
    echo "start supervising adium"
  else
    echo "supervisediumd is already running"
  fi
  exit 0
fi
trap "$RM -rf $PIDFILE; echo 'stopped supervisediumd'" EXIT
echo $$ > $PIDFILE
echo "start supervising adium"

COUNT=0
while [ 1 ] ; do
  PSINFO=`$PS ux | $GREP $APP | $GREP -v $GREP`
  PID=`echo $PSINFO | $AWK '{print $2}'`
  CPU=`echo $PSINFO | $AWK '{print $3 * 10}'`
  if [ $CPU -gt $CPU_LIMIT ] ; then
    COUNT=`$EXPR $COUNT + 1`
    if [ $COUNT -gt $COUNT_LIMIT ] ; then
      kill $PID
      $OPEN -a $APP
      COUNT=0
    fi
  else
    COUNT=0
  fi
  $SLEEP 1
done