1

gyaazzというアウトラインエディタ風wikiを作っている

増井先生が作ってるgyazzが元ネタ。JavaScriptの勉強がしたくてN村ヒロユキっていう人と作った。

使い方は使い方のページを見て欲しい。
ダブルクリックした行から編集できる。方向キーか、ctrlキーを押しながらemacs的操作でカーソル移動できる。
一応アウトラインエディタなので、ctrlやshiftを押しながらキー操作するとブロック毎移動したりインデントしたり、ブロックまるごと入れ替えたりできる。
[[ と ]]で囲んだ部分がURLならリンクに、画像へのURLだったらimgタグに、URLでなければ新しいwikiページへのリンクになる。
しばらく操作しないで置いておくと、右下に「saved!」と表示されて編集が保存される。
あと他の人が編集するとsyncする。


UIはjQueryを使ったので、JSよくわからないけど2、3日でエディタ的機能は実装できた。
サーバー側はSinatraを使っている。ページのデータをJSONで返す簡単なAPIが用意されている。なので一番上の階層に小文字で「api」というページは作れない。

データベースにはTokyoCabinetを使ってる。

ソースコードとインストール方法はhttp://github.com/shokai/gyaazzにある。
でもmongodb使って作り直そうかな

あと、
  • ページ名の変更
  • ページのコピー
  • 認証付きページを作れる機能
が必要だな・・・

1

26歳になったので

8月15日に26歳になったので、新しい自分を探すためにtwitterのプロフィールを自動的に更新するようにした

30ed9b71553d2f33922bc39e7368a008

838d669e96f189702c3f9844965ace8e

人間以外にもなれる

eac518c56870949065eeeed905d34ae4

e85cb1527f60394255fe556f41cc3e62

9c4c1cb1c6fadac1ff09f120b5585892

7d4fcd1cd71ba0e8693133439b4751eb

ソースコードは全部githubに置いた

ランダムに適当な紹介文を取ってくる

wikipedia.rb



# -*- coding: utf-8 -*-

require 'rubygems'

require 'open-uri'

require 'uri'

require 'nokogiri'

require 'kconv'

require 'net/http'

class Wikipedia

def initialize(agent_name)

@agent_name = agent_name

end

def random

get('特別:おまかせ表示')

end



def get(name)

doc = Nokogiri::HTML open(URI.encode("http://ja.wikipedia.org/wiki/#{name}"), 'User-Agent' => @agent_name).read.toutf8



title = doc.xpath('//title').first.text

name = doc.xpath('//h1').first.text

descriptions = doc.xpath('//div[@id="bodyContent"]//p').map{|i|i.text}

{

:title => title,

:name => name,

:descriptions => descriptions

}

end

end


このスクリプトをcronで定期的に実行して更新してる

change-profile.rb



#!/usr/bin/env ruby

# -*- coding: utf-8 -*-

require 'rubygems'

require 'twitter'

require 'yaml'

require File.dirname(__FILE__)+'/lib/wikipedia'

$KCODE = 'u'

begin

conf = YAML::load open(File.dirname(__FILE__) + '/config.yaml')

rescue

STDERR.puts 'config.yaml load error'

exit 1

end

tw = Twitter::Base.new(Twitter::HTTPAuth.new(conf['name'], conf['pass']))

w = Wikipedia.new('shokai')

desc = nil

5.times do

data = w.random



desc = data[:descriptions].first

desc.gsub!(/\[\d+\]/, '')

tmp = desc.split(/(と?は)/)

left = tmp.shift

while left =~ /([^)]+$/ do

tmp.shift

left = tmp.shift

end

desc = "#{conf['your_name']}#{tmp.join('')}".toutf8

puts '-'*10

print data[:name] + ' => '

puts desc

break if desc != conf['your_name']

end

exit if desc == nil or desc == conf['your_name']

tw.update_profile({'description' => desc})



0

ZeroMQでOpenCV cvOpticalFlowのデータを配信する

1VQ9がZeroMQで遊んでたので、俺も橋本商会 cvCalcOpticalFlowBMをZeroMQでpubしてみた。ZeroMQはなんか面倒な事を適当にやってくれるmessaging libraryで、色々な言語のバインディングが出ている。

ZeroMQのpubはセンサーのデータとかを垂れ流しにするのに都合がよさそう。
clientが何台いるかどうかを考えないで良いし、pub/subどちらが先に起動していても適当に接続処理をしてくれる。cookbookを見てるとmulticastやthread間通信にも使ってる。とりあえずセンサーデータ垂れ流しという用途に俺はよく使いそう。


ソースコードはgithubに置いた
他にも単純なカウントアップのpub/sub両方をC++/C/Rubyで書いた(6種)のと、twitterのstream APIをZMQ_PUBで中継するのを作ってみた(解説:zeromqインストール、twitter stream APIを中継 – 橋本詳解)。特にstream APIのHUB的存在は便利。

あと、mongrel2WebSocketやXMLSocketとZeroMQの接続をしてくれるようになるらしくて期待してる。



受信側
opticalflow_sub.rb

#!/usr/bin/env ruby
require 'rubygems'
require 'zmq'

ctx = ZMQ::Context.new
sock= ctx.socket(ZMQ::SUB)
sock.connect('tcp://127.0.0.1:5000')
sock.setsockopt(ZMQ::SUBSCRIBE, 'opticalflow')

loop do
puts sock.recv()
end


送信側。これを適当なパソンコにUSBカメラ刺して動かしておけば、別のマシンから動きが取れる!!
opticalflow_pub.cpp
// http://opencv.jp/sample/optical_flow.html
#include <cv.h>
#include <highgui.h>
#include <cxcore.h>
#include <ctype.h>
#include <stdio.h>
#include <iostream>
#include <boost/format.hpp>
#include <zmq.hpp>

using namespace std;
using namespace boost;

void detect_flow(IplImage *img, IplImage *img_p, IplImage *dst);
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, ZMQ_PUB);

int main(int argc, char* argv[]) {
IplImage *img = NULL;
CvCapture *capture = NULL;
capture = cvCreateCameraCapture(0);
//capture = cvCaptureFromAVI("test.mov");
if(capture == NULL){
cerr << "capture device not found!!" << endl;
return -1;
}

sock.bind("tcp://127.0.0.1:5000");

CvSize size = cvSize(320, 240);
IplImage *img_resized = cvCreateImage(size, IPL_DEPTH_8U, 3);
IplImage *img_gray = cvCreateImage(size, IPL_DEPTH_8U, 1);
IplImage *img_gray_p = cvCreateImage(size, IPL_DEPTH_8U, 1);
IplImage *img_dst = cvCreateImage(size, IPL_DEPTH_8U, 3);

char winNameCapture[] = "Capture";
cvNamedWindow(winNameCapture, CV_WINDOW_AUTOSIZE);

while (1) {
img = cvQueryFrame(capture);
cvResize(img, img_resized);
cvCvtColor(img_resized, img_gray, CV_BGR2GRAY);
cvCopy(img_resized, img_dst);
detect_flow(img_gray, img_gray_p, img_dst);
cvShowImage(winNameCapture, img_dst);
cvCopy(img_gray, img_gray_p);
if (cvWaitKey(10) == 'q') break;
}

cvReleaseCapture(&capture);
cvDestroyWindow(winNameCapture);

return 0;
}

void detect_flow(IplImage *src_img1, IplImage *src_img2, IplImage *dst_img){
int i, j, dx, dy, rows, cols;
int block_size = 24;
int shift_size = 10;
CvMat *velx, *vely;
CvSize block = cvSize(block_size, block_size);
CvSize shift = cvSize(shift_size, shift_size);
CvSize max_range = cvSize(50, 50);

rows = int(ceil (double (src_img1->height) / block_size));
cols = int(ceil (double (src_img1->width) / block_size));
velx = cvCreateMat(rows, cols, CV_32FC1);
vely = cvCreateMat(rows, cols, CV_32FC1);
cvSetZero(velx);
cvSetZero(vely);

cvCalcOpticalFlowBM(src_img1, src_img2, block, shift, max_range, 0, velx, vely);
string result_str = string("");
for (i = 0; i < velx->width; i++) {
for (j = 0; j < vely->height; j++) {
dx = (int)cvGetReal2D(velx, j, i);
dy = (int)cvGetReal2D(vely, j, i);
cvLine(dst_img, cvPoint(i * block_size, j * block_size),
cvPoint(i * block_size + dx, j * block_size + dy), CV_RGB(255, 0, 0), 1, CV_AA, 0);
if(dx != 0 || dy != 0){
result_str += str(format("[%d,%d,%d,%d]") % (i*block_size) % (j*block_size) % dx % dy);
}
}
}
if(result_str.size() > 0){
result_str = str(format("opticalflow %s") % result_str);
cout << result_str << endl;
zmq::message_t msg(result_str.size()+1); // ZeroMQ
memcpy(msg.data(), result_str.c_str(), result_str.size()+1);
sock.send(msg);
}
}


g++ -O opticalflow_pub.cpp -o opticalflow_pub.bin -I/opt/local/include/opencv -lcv -lcvaux -lcxcore -lhighgui  -I/usr/local/include /usr/local/lib/libzmq.a


これで動いた座標とその方向 [x,y,dx,dy] が連続で送られてくる。
opticalflow [48,216,4,-29][72,216,0,-29][96,216,0,-29][264,216,-9,-29]
opticalflow [48,216,4,-29][96,216,0,-29][120,216,0,-29][264,216,-9,-29]
opticalflow [48,216,4,-29][96,168,0,10][96,192,-10,-20][96,216,0,-29][120,192,0,10][120,216,0,-29][144,216,0,-29][168,216,0,-29][192,48,-10,0][192,216,0,-29][216,216,0,-29][264,216,-9,-29]
opticalflow [96,168,0,10][96,192,-10,-10][96,216,0,-29][120,168,0,10][120,192,0,10][120,216,0,-29][144,216,0,-29][168,48,0,10][168,96,0,10][168,216,0,-29][192,72,0,40][192,96,0,-30][192,216,0,-29][264,216,-9,-29]
opticalflow [48,216,4,-29][96,168,0,10][96,216,0,-29][120,168,0,10][120,192,0,10][120,216,0,-29][144,216,0,-29][168,48,10,0][168,96,0,10][168,216,0,-29][192,96,0,-30][192,216,0,-29][264,216,-9,-29]

0

mongoid使ってみる

mongo単体で使ってみててだいたい分かったので、mongoidというmapperを使ってみる。

mongoidの良いのは

  • default値を入れておきたい場合も簡単に書ける。created_atとか。
  • _idでdocumentを取り出すとき、素のmongoだとcollection.find_one(BSON::ObjectID(id))とかしないとならないけどmongoidだと_idに文字列でID入れればいい
とかがぱっと使ってみて思った。。
そもそもこういうのmongoの機能にあるかもしれないけど。

■ドキュメント
■インストール
sudo gem install mongoid
1.9.1を使う。–pre付けるとRails3対応の2.x系統が入る。

■modelを作る
適当にperson class作って、Mongoid::Documentにする

person.rb
require 'rubygems'

class Person
include Mongoid::Document
field :fullname # 指定無しでtype=>stringになる
field :username
field :age, :type => Integer
field :created_at, :type => DateTime, :default => lambda{Time.now}
end
string以外は型指定する。型はArray, BigDecimal, Boolean, Date, DateTime, Float, Integer, String, Symbol, Timeがある。
Mongoid Documentation: Documents

defaultで現在時刻を入れるようにした。

■mongodbへ接続
Mongoid.configureのブロック内で接続する。
conf.masterに普通のmongoで接続してdbを指定した時の返り値(Mongo::DBオブジェクト)を与えれば、mongoidで使える。
require&nbsp;'rubygems'
require&nbsp;'mongoid'
require&nbsp;File.dirname(__FILE__)+'/person'

Mongoid.configure&nbsp;do&nbsp;|conf|
&nbsp;&nbsp;conf.master&nbsp;=&nbsp;Mongo::Connection.new('localhost',&nbsp;27017).db('mongoid-test')
end
■modelの操作
新しいpersonオブジェクト作って保存
person = Person.new(:fullname => 'sho hashimoto',
:username => 'shokai',
:age => 25)

puts person.fullname
puts person.age

person.save
保存されてるか、mongoのコンソールで確かめる
personで保存したら、自動的に複数形のpeopleになってた。ActiveRecordっぽい。
% mongo
MongoDB&nbsp;shell&nbsp;version:&nbsp;1.4.4
url:&nbsp;test
connecting&nbsp;to:&nbsp;test
type&nbsp;"help"&nbsp;for&nbsp;help
>&nbsp;show&nbsp;dbs
admin
chirpstream_shokai
local
mongoid-test
people
povietest
test
testdb
>&nbsp;use&nbsp;mongoid-test
switched&nbsp;to&nbsp;db&nbsp;mongoid-test
>&nbsp;show&nbsp;collections
people
system.indexes
>&nbsp;db.people.find()
{&nbsp;"_id"&nbsp;:&nbsp;"4c61463c2f7306e9fe000001",&nbsp;"created_at"&nbsp;:&nbsp;"Tue&nbsp;Aug&nbsp;10&nbsp;2010&nbsp;21:29:48&nbsp;GMT+0900&nbsp;(JST)",&nbsp;"fullname"&nbsp;:&nbsp;"sho&nbsp;hashimoto",&nbsp;"username"&nbsp;:&nbsp;"shokai",&nbsp;"age"&nbsp;:&nbsp;25&nbsp;}
{&nbsp;"_id"&nbsp;:&nbsp;"4c614d652f73060653000001",&nbsp;"created_at"&nbsp;:&nbsp;"Tue&nbsp;Aug&nbsp;10&nbsp;2010&nbsp;22:00:21&nbsp;GMT+0900&nbsp;(JST)",&nbsp;"fullname"&nbsp;:&nbsp;"sho&nbsp;hashimoto",&nbsp;"username"&nbsp;:&nbsp;"shokai",&nbsp;"age"&nbsp;:&nbsp;25&nbsp;}
2回保存したから複数保存されてた

■find
探す。Mongoid Documentation: Queryingにqueryの書き方が載ってる。
適当にユーザ名shokaiの最初の一件を取得して、表示する
person = Person.first(:conditions => {:username => 'shokai'})
puts person._id
puts person.username
puts person.created_at
他にも、全件とか色々な書き方ができる。
person = Person.find(:first, :conditions => {:username => 'shokai'})
person = Person.all(:conditions => {:username => 'shokai'}).first
person = Person.first(:conditions => {:_id => '4c61463c2f7306e9fe000001'})
person = Person.where(:username => 'shokai').first
allで検索したら結果が1件しか無くても、collectionで返ってくる。eachで回せる。

■modelに書いてない値を入れてみる
modelにない、person.placeを入れてみる
person = Person.new(:fullname => 'sho hashimoto',
:username => 'shokai',
:age => 25,
:place => 'fujisawa')

puts person.fullname
puts person.age
puts person.place

person.save
普通に入ってた。このへんは複数人でやるときは何か考えないとならないな。
でもクロールしてきた値とかを適当にどんどん入れてしまうのにはすごくいい。twitterのchirp streamとか。

数値はlt,gtで大なり小なり条件指定できるらしい。
あとmongodbは空間型があるはずだけどそれは使えないのかな?filedの型になかったけど。

0

ShinagawaSeaside

tokyo tyrantのサーバーを起動したり終了したりするrake taskを作った。

名前は、tokyotyrantの周辺のライブラリがmiyazaki resistanceとかそういう名前ばかりだったのでそういう作法なのかなと思って天王洲アイルと迷いつつ品川シーサイドに決めた。


■インストール

sudo gem isntall shinagawaseaside


■使う
Rakefile
require 'rubygems'
require 'shinagawaseaside'

ttdb = [ { :name => 'users', :port => 20010},
         { :name => 'videos',:port => 20011},
         { :name => 'comments', :port => 20012} ]

ShinagawaSeaside::set_tasks(ttdb, :basedir => File.dirname(__FILE__)+'/ttdb')
ShinagawaSeaside::set_tasks するとrake taskが追加される。

Rakefileのあるディレクトリの下に ttdb というディレクトリが作られて、
その中にusers.tch, videos.tch, comments.tch というDBができる。pidはusers.pid, videos.pid, comments.pidの中に入る。


% rake -T
rake ttrestart # restart TokyoTyrant server
rake ttstart # start TokyoTyrant server
rake ttstop # stop TokyoTyrant server


中身はRakeでTokyoTyrant serverを起動/終了 – 橋本詳解と大体同じ。(複数サーバー起動できるようにした)
tokyotyrantをソースからインストールすると一緒に入る ttservctl を参考にした。



タスクの名前は初期値がttstart, ttstopだけど、変更もできる
ShinagawaSeaside::set_tasks(ttdb,
                            :basedir => File.dirname(__FILE__)+'/ttdb',
                            :start => 'start', # set task name
                            :stop => 'stop',
                            :restart => 'restart'
                            )



俺はyamlで設定ファイルを書いてそこから読み込むようにしている。そうするとアプリからも、どのDBがどのportにあるか見つけやすい。

config.yaml
ttdb : 
     - name : users
       port : 23240
     - name : videos
       port : 23241

Rakefile
require 'rubygems'
require 'yaml'
require 'shinagawaseaside'

begin
  conf = YAML::load open(File.dirname(__FILE__)+'/config.yaml')
rescue
  STDERR.puts 'config.yaml load error'
  exit 1
end

ShinagawaSeaside::set_tasks(conf['ttdb'], :basedir => File.dirname(__FILE__)+'/ttdb')



■ソースコード
githubに置いた