「GitHub」と一致するもの

Google Readerがなくなることが第一の理由ではないのですが、RSSってもっと便利にならないかと以前から考えていて、RSS PipesというWebサービスを作りました。

名前が似ているYahoo! Pipesから連想されるとちょっと困るような、困らないような感じですが、いわゆるRSSアグリゲーターです。フィードを表示するUIはなくて、RSSを出力します。 Feedweaver よりは汎用性があって、 Yahoo! Pipes よりは汎用性がないといったところです。

あまり比較することには意味がないですね。 RSS Pipesを作った理由は、

  • ログインせずにRSSを作れるアグリゲーターが欲しかった
  • JavaScriptでフィルターを書きたかった
  • みんなでRSSを作れるようにしたかった
  • 単に作りたかった

からです。まだできたてほやほやで、この段階でどれだけの人にアピールできるか分かりません。もし要望があればお知らせください。

RSS Pipes

上記リンクからどうぞ。

あまり使うことはないかなと思っていたのですが、ちょっと使うことにしました、PostgreSQL。

Rubyにはdata_mapperがありましたが、Node.jsでは何があるんだろうと思って調べてみました。

とりあえず、見つけたのは3つです。

これが現時点でのGitHubでのStar数順です。

でも、JugglingDBを使ってみることにしました。理由は、adapterにRedisやMongoDBなどのNoSQL系が入っていてちょっとわくわくしたからです。もしかして、将来新しいadapterを作れるのかもしれないと思いました。

しかし、このJugglingDBですが、ドキュメントが見つかりません。RailwayJSのサブプロジェクトみたいで、あまり外に目が向いていないのでしょうか。ところで、RailwayJSはcompound.jsに変わったのですね、client-sideで動くと書いてあります。面白そうですね。

さて、READMEを見ながらなんとなくやってみたら動きました。

var Schema = require('jugglingdb').Schema;
var schema = new Schema('sqlite3', { database: 'xxx.db' });
var SomeObject = schema.define('SomeObject', {
  name: {
    type: String
  },
  content: {
    type: String
  },
  flag: {
    type: Boolean
  }
});
schema.autoupdate();
SomeObject.create();

これで合っていますかね。

他の2つとの比較も知りたいですね。探せば誰かがやっているでしょう、きっと。

http://www.sequelizejs.com/はだいぶ詳しく書いてありますね。少し心が揺らぎます。


docsの下に少しドキュメントがありました。しかし、READMEに書いてあるfindOneが載っていなかったりと、網羅されていないようにも見えます。

JavaScriptにはJavaで言うところのString.startsWithがないです。みんなどうやっているかというと、

if (str.indexOf('prefix') === 0) {
    console.log('prefix found');
}

みたいな感じのコードをよく見ます。AngularJSのソースでも見ました。でも、これって良く考えると無駄ですよね。indexOfというのは文字列を最初から最後までなめて、一致する位置を探すのです。見つかれば一発ですが、見つからなければ最後まで行って-1を返します。本来、prefixを判定するのなら最初だけ検査すればいいのです。

str.substring(0, 'prefix'.length) === 'prefix'

という書き方も見ますが、これもいまひとつです。substringは新たに文字列を作ってしまうので。結論としては、

str.lastIndexOf('prefix', 0) === 0

が一番いいと思います。lastIndexOfは文字列の後ろから検査するのですが、検査開始位置を0にしているので、判定するのは一回だけです。Javaの内部実装には詳しくないですが、String.startsWithも同じような実装でしょう。

さて、これが本当かを確かめるために、benchmark.jsでベンチマークを書こうかと思いました。いや、せっかくなのでjsperfで書いてみようと思って、書き始めたら、あれ、既に存在する。世の中同じこと考える人はいますね。

http://jsperf.com/string-startswith/3

最後の一つのテストケースは私が追加したものです。 想定通り、lastIndexOfが速いです。ん?しかし、Chromeだとcompiled regexの方が速い。いや、そんなはずは、、。compiled regexが速いのは事実ですが、lastIndexOfが遅いのです。こんな単純な関数でChromeがFirefoxより遅いのは想定外でした。

Chromeで遅いということは、v8が遅いのかと思って、node.jsでもテストしてみました。

https://gist.github.com/dai-shi/4950506

案の定、node.jsでも同様の結果です。 それでも、私はlastIndexOfを使うことをおすすめします。 v8で遅いのはv8が直るべきです。ところで、これはissueになっているのかな?

http://code.google.com/p/v8/issues/list

どうやらなっていないようです。ちょっとソースを覗いてみますか。v8はsubversionです。bleeding edgeブランチが最新のようです。

http://v8.googlecode.com/svn/branches/bleeding_edge/

checkoutしてStringLastIndexOfのソースコードを追ってみましたが、特に単純な問題があるようには見えません。久しぶりのCのソースで頭が疲れます。そりゃそうですよね、天下のGoogleが作っているものがそんな自明なバグを残しているわけないですよね。というわけで、しばらくは様子をみることにします。

ところで、jsperf.comにstring-startswithがあったのが悔しかったので、真似してstring-endswithを作ってみました。

http://jsperf.com/string-endswith

傾向はstartsWithと同様です。ここまで読んでくれたみなさん、suffixチェックするときに、間違ってもnaive indexOfのような書き方はしないように。

Gunosy RSS はオープンソースで提供されています。ライセンスの範囲内で自由に使うことができます。

慣れている人は説明なしですぐにできると思いますが、どれだけ簡単にできるかを示すため手順を書いておきます。

前提:

  • herokuのアカウントがあること(なければ作る)
  • heroku toolbeltがインストールされ、SSH鍵が登録済であること
  • GitHubのアカウントがあること(なければ作る)
  • gitがインストールされていること

まず、GitHubからソースコードを取得します。

https://github.com/dai-shi/gunosy-rss

にアクセスして、右上のForkボタンを押して、自分のリポジトリにします。自分のリポジトリのページに行ってリポジトリのURLを確認します。 仮に、git@github.com:<username>/gunosy-rss.gitだったとします。

% git clone git@github.com:<username>/gunosy-rss.git

をコマンドラインで実行するとダウンロードされます。

% cd gunosy-rss

でディレクトリを移動します。

次に、herokuにアプリケーションを作ります。

% heroku apps:create <appname>

を実行するだけです。<appname>は省略も可能です。 作ったアプリケーションの情報を確認するため、

% heroku apps:info

とします。そこで表示される、Git URLとWeb URLが重要です。 それぞれ、git@heroku.com:<appname>.gitとhttp://<appname>.herokuapp.com/だったとします。

% heroku config:set SITE_PREFIX=http://<appname>.herokuapp.com/

として、環境変数を登録します。

% git remote add heroku git@heroku.com:<appname>.git

として、リモートリポジトリを登録します。

% git push heroku

として、herokuにアップロードして、完了です。 http://<appname>.herokuapp.com/<gunosyid>.rssでRSSフィードが生成されます。

いかがでしょう?


補足

実は、GitHubのアカウント作らずForkもしなくても、上記のことはできます。ですが、せっかくなのでpull requestが送れるようにForkするのが、GitHubらしいのではないでしょうか。

herokuのアプリケーション情報を表示するところでは、

% heroku apps:info --app <appname>

とする必要があるかもしれません。

connect-prerendererが一応完成

  • 投稿日:
  • by

AngularJSのマイルストーンにserver-side prerenderingというのがあるのですが、当分できそうにないので自分で作ってしまっています。

https://github.com/dai-shi/connect-prerenderer

express.jsのmiddlewareで使うことを想定していますが、express.jsには依存しないようにしたので、connect-prerendererです。expressとconnectの違いは、他のブログなどでも説明されていることでしょう。

何をやっているかを一言で言うと、サーバ側でjsdomを使ってJavaScriptを走らせて、レンダリング後のHTMLページをクライアントに返すというものです。

基本的なところはしばらく前にできていて、簡単なテストケースは動いていたのですが、AngularJSを動かすのがなかなか大変でした。結局、angular.jsのソースに手を入れることになりました。

AngularJSを使った簡単なテストケースも動いて、満足です。 もってきてすぐ使えるようなライブラリではないですが、たぶん世界初の取り組みでしょう。

あとははAngulaJS側でもっとうまく取り込んでもらわないとつらいところです。今は決めうち(ng-repeat="..."はOKだが、class="ng-repeat:..."は動かないとか)でやっている部分があるため。

あまり説明できずに意味不明かと思いますが、これにて。

jsdomからAngularJSの不具合二つ目

  • 投稿日:
  • by

jsdomからAngularJSで作ったページにアクセスしたら、こんなエラーがでました。

Error: 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["fn: $locationWatch; newVal:18; oldVal: 17"],["fn: $locationWatch; newVal: 19; oldVal:18"],["fn: $locationWatch; newVal: 20; oldVal: 19"],["fn:$locationWatch; newVal: 21; oldVal: 20"],["fn: $locationWatch;newVal: 22; oldVal: 21"]]

このエラーは"familiar"らしいです。一番該当しそうなのが、

angular.js Issue #1417

これです。現時点ではまだ解決されていない様子です。

果たして、$routeProviderでredirectToを使うのを止めてみたらエラーが出なくなりました。んー、不便です。

つまらなくならないGunosy RSS

  • 投稿日:
  • by

本家GunosyによるとGunosy RSSを使うと、クリック情報が反映されないためつまらなくなる可能性があるということでした。

これに対処するため、ログインしてGunosyのページを見たのと同じリダイレクトリンクをRSSで生成する機能を3/13に作りました (参照) 。これは設定に相当の知識を必要としたため、「上級者向け」としました。

今回、ちょっと敷居を下げるためにGunosy RSS設定ページを作りました。

依然としてクッキーの知識など必要ですが、多少分かりやすくなったのではないか、と期待します。

Gunosy RSSフィード生成Webサービス

から、「設定ページ」を開いてください。

生成されたRSSリンクの確認方法ですが、

  • 記事のURLがhttp://gunosy.com/redirect?で始まっていれば、クッキーの設定は正常
  • 記事のURLを開いて404エラーにならなければ、ユーザIDの設定は正常

です。このあたりも自動化できたらよさそうとは思っています。

最近、テスト書きながらでコーディングしていますが、 https://github.com/dai-shi/connect-prerenderer のe2eテストで少し困ってます。

AngularJSのブートストラップは通常、

<html ng-app class="ng-app:<modulename>">

とアノテーションをつけることによって行われるのですが、 ある理由からブートストラップを自分で呼びたいと考えています。 これ自体は比較的簡単で、

angular.bootstrap(document, ["<modulename>"]);

を呼び出せばいいだけです。より正確には、

<script>
  angular.element(document).ready(function() {
    angular.bootstrap(document, ["<modulename>"]);
  });
</script>

といったことろです。

ところが!

このブートストラップの方法だと、testacularが動いてくれないのです。困りました。browser().navigateTo(...)を呼び出すと返ってこないのです。

解決策は今のところ見つかっていません。とりあえず、テストするときは、ng-appをつけておくしかありませんね。

y_sanagi's diary で、RSSに画像がないという指摘を見つけたので、対応しました。

http://dai-shi.github.com/gunosy-rss/

特に使い方は変わらないので、説明は変更していません。

もしかして、サムネイル画像は不要という利用者もいるでしょうか?オプション指定できるようにしてもいいですが、ちょっとシンプルさが損なわれてしまうかもしれません。


ところで、「上級者向け」の設定はあまり使われていないのでしょうかね。もうちょっと簡単に設定できるようになればいいのですが。もともとのクッキーに'%'が入っていると'%25'に置換しないといけないので、ちょっと面倒なのです。

https://github.com/dai-shi/connect-prerendererを開発中に出会った事象について書き留めておきます。

jsdomでAngularJSを使ったテストページにアクセスすることになったのですが、 angularが初期化されなくて困ってました。ぐぐったところ、

AngularJSがIE8で動かないときは

を見つけたのですが、この方法では解決せず。結局、次のようにしたら動きました。

<html ng-app class="ng-app">

id="ng-app"はあってもなくても変わらず。モジュールがある場合は、class="ng-app:<modulename>"でもうまくいくのかもしれません。

angularのソースコードを少し眺めると、

function angularInit(element, bootstrap) {
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
//-----snipped-----
  forEach(names, function(name) {
    names[name] = true;
    append(document.getElementById(name));
    name = name.replace(':', '\\:');
    if (element.querySelectorAll) {
      forEach(element.querySelectorAll('.' + name), append);
      forEach(element.querySelectorAll('.' + name + '\\:'), append);
      forEach(element.querySelectorAll('[' + name + ']'), append);
    }
  });

ってな感じで、どんな風に書いても動きそうです。なぜ、id="ng-app"でうまくいかなかったのか疑問が残りますが、追求しないことにします。


追記。

ng-appだけではダメだった理由は、ng-app="ng-app"に展開されてしまうからのようなので、ng-app="<modulename>"にすればうまくいくのかもしれません。

さらに、追記。

結局、ng-app=""で動いたので、ng-appだけだとjsdomでうまくいかないというのは正しそうです。class="ng-app:<modulename>"もうまくいきそう。id="ng-app"で動かなかった理由はおそらく、ng-appが残っていたからでしょう。