0

RocketIO::Linda 1.0.0からのプロトコルが少し変わった

v1.0.0から、Sinatra::RocketIO::Lindaのプロトコルを少し変更しました。
なのでアップデート推奨です。

http://linda.shokai.org/shokaihttp://linda.masuilab.org/delta も既に新しいプロトコルで動いています。


Linda

これです

橋本商会 » Ruby上に並列言語拡張Lindaを実装してWebSocket/Cometで使えるようにした
shokai/sinatra-rocketio-linda
shokai/em-rocketio-linda-client
研究室や家では20ノードぐらいのLindaクライアントがセンサーや色々のデータをやりとりしています。

アップデート

Ruby2.0以上を使っている場合はgemだけアップデートすれば、アプリのコードは変更せずにそのまま動きます。

gem install sinatra-rocketio-linda

変更点


これが
tuple_space.watch [1,2] do |tuple|
p tuple
end

こうなった
tuple_space.watch [1,2] do |tuple, info|
p tuple
puts info.from
end

read/take/watchのコールバック関数の引数が増えた。
全てのタプルに、書き込み元のIPアドレスが付くようになった。
例えば認証とか、LAN内から書き込まれたタプル以外は信用しないLindaクライアントとか、そういうのに使う。

今のところinfoにはfrom以外の値は入っていないけどそのうち増える。

0

ScalaでAndroidアプリを作る

普段Javaで書いている部分をScalaで書けるようになった。
Javaだと、文字列や複雑なデータ構造を処理したり、関数プログラミングがしづらい。lambda無いし。そういう部分だけでもScalaでやりたい。

https://github.com/pfn/android-sdk-pluginというフレームワークを使って、既存のAndroidアプリのプロジェクトでScalaとJavaを混ぜこぜに書けるようにする方法を説明する。

全体の簡単な手順

  1. homebrewでscalaとsbtをインストールする
  2. Androidのプロジェクトを普通に作る or 既存のAndroidプロジェクトを用意する
  3. sbtで使う設定ファイル3つを配置する
  4. sbt初回起動時に必要なライブラリがインストールされて、Androidプロジェクトとsbtプロジェクトが共存した状態になる
  5. sbtでandroid:packageすると、1つのapkになる

という感じ。


参考にしたサイト

この2つしか読んでいない。
もし俺の記事でよくわからない箇所があったら、下手にググって余計なの読む前に下の2つをちゃんと読んだ方がいい。
今回使っている「android-sdk-plugin」と似たような位置づけのフレームワークとして、「android-plugin」と「scaloid」がある。
前者は少し古い物でこれを参考にandroid-sdk-pluginが作られた。
後者はandroid-sdk-pluginの上にさらに乗せるライブラリで、XMLでのレイアウトをscalaだけで書くようになっていたりしていて、既存のAndroidプロジェクト内でScalaも使うという用途にはちょっと向いていないと思った。

このように似たライブラリがあるので、安易に「scala android」とかでググると関係ない情報を読むことになる。公式ドキュメント重要。


試しに作ったもの

ここに置いた
https://github.com/shokai/ScalaAndroidTestApp
「つらい」が「っらぃ」のようになるだけのアプリ。
Scalaっぽい(Javaでは書きにくい)文字列やMapの処理と、Javaで書かれているAndroid SDKのGUI機能両方を使ってみたかったのでやってみた。
元ネタはScalaをちょっと勉強したに書いた研究室の練習問題。

MainActivityのコードを見ればわかるが、scalaだと辞書作って1文字ずつチェックして置換するような処理はJavaよりも書きやすい。


Androidアプリの新規作成

Android開発環境をインストールしなおしたに書いた手法でHomebrewでAndroid SDKをインストールした。
Eclipseは入れていない。emacsとterminalだけで開発する。

brew install android-sdk


まずプロジェクトのビルドターゲットを確認してから
android list
id: 3 or "android-18"
Name: Android 4.3
Type: Platform
API level: 18
Revision: 2

新規プロジェクトを作る。とりあえずビルド、端末にインストールまでする
android create project --target "android-18" --name ScalaTestApp --path `pwd` --activity MainActivity --package org.shokai.scalatestapp
android update project --path `pwd`
ant debug
adb install -r bin/ScalaTestApp-debug.apk


Scalaが使えるようにする

antの代わりにsbtを使う。

brew update
brew install scala sbt
scala 2.10.2 と sbt 0.13.0 がインストールされた。
sbt(simple-build-tool)は名前に反してシンプルではなく、node.jsのnpmに近い多機能な物で、
  • main()があるファイルを適当に探して、実行してくれる
  • scalaのライブラリのパッケージ管理、バージョン固定
  • 実行するscalaインタプリタのバージョン指定
  • ScalaとJavaを共存させ、.classなどの位置を気にせずに混ぜこぜで実行できる
  • Javaの場合は.classなどの中間ファイルも適当な場所に生成、管理してくれる
  • コマンド名の頭に「~ 」(チルダスペース)を付けると、ソースファイルの変更を検知してコマンドを再実行してくれる
などの機能がある。
いくつかはscala android-sdk-pluginがやってくれている機能なのかもしれない。
まだsbt使い込んでいないから完全に把握しきれていない。

sbt自体がなかなか面白いプロダクトなので、Getting Started — sbt Documentationを読んだ方がいい。


Androidプロジェクト内にファイルを3つ置いてsbt起動すればscalaが使えるようになる。
それぞれのファイル内でAndroid SDKやsbtのバージョンを指定しているので注意。

build.sbt (1行ずつ空けなければならないらしい)
import android.Keys._

android.Plugin.androidBuild

name := "ScalaTestApp"

platformTarget in Android := "android-18"

run <<= run in Android

install <<= install in Android

project/build.properties
sbt.version=0.13.0

project/plugin.sbt
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.0.6")


sbt起動
sbt
sbtのコンソールが起動し、自動的に依存ファイルがインストールされてscalaが使えるようになる。

とりあえずJavaのままだけどビルドする。sbtのコンソール内で
compile
が通ればok。


JavaをScalaに書きなおす

MainActivity.javaをMainActivity.scalaにリネームし、書き直す。
package org.shokai.scalatestapp;

import android.app.Activity;
import android.os.Bundle;

class MainActivity extends Activity{
override def onCreate(savedInstanceState:Bundle){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}

sbtコンソールでビルドする。
android:package-debug
同じ名前の.javaと.scalaが混在しているとビルドが通らないが、別名なら混在していていい。


adbで実機にインストールする
adb install -r bin/ScalaTestApp-debug.apk
scalaでアプリが書けましたね。



大文字ひらがなを小文字ひらがなに変換するアプリを作る

レイアウトのXMLとScalaで書きなおしたMainActivityを編集する。


res/layout/main.xml にボタンとEditTextを追加
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:id="@+id/editTextSource"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textShortMessage" />
<Button
android:id="@+id/btnRun"
android:text="小文字に変換"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<TextView
android:id="@+id/textViewResult"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="result here" />
</LinearLayout>


src/org/shokai/scalatestapp/MainActivity.scala
package org.shokai.scalatestapp;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.Button;
import android.view.View;
import android.view.View.OnClickListener;

class MainActivity extends Activity{

var editTextSource:EditText = _
var textViewResult:TextView = _
var btnRun:Button = _

override def onCreate(savedInstanceState:Bundle){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

editTextSource = findViewById(R.id.editTextSource).asInstanceOf[EditText]
textViewResult = findViewById(R.id.textViewResult).asInstanceOf[TextView]
btnRun = findViewById(R.id.btnRun).asInstanceOf[Button]

btnRun.setOnClickListener( new OnClickListener(){
override def onClick(v:View){
val source = editTextSource.getText().toString()
trace(s"source: $source")
val result = kana_downcase(source)
trace(s"result: $result")
textViewResult.setText(result)
}
})
}

def kana_downcase(str:String):String = {
val chars = scala.collection.immutable.Map[String,String](
"あ" -> "ぁ",
"い" -> "ぃ",
"う" -> "ぅ",
"え" -> "ぇ",
"お" -> "ぉ",
"つ" -> "っ",
"よ" -> "ょ",
"わ" -> "ゎ")

return str.split("").
map(c =>
if(chars.contains(c)) chars(c) else c
).mkString
}

def trace(message:String){
Log.v("ScalaTestApp", message)
}
}
scalaなので、変換表を気軽にMapで作って1つずつチェックして置換して結合とか書けるし、
いちいちtoString()しないで s"$変数名" で変数展開して文字列に埋め込んだりできて便利だった。

ビルドするとこうなる


.gitignore

Android SDKは中間ファイルをたくさん生成するので、Gitにコミットする時にディレクトリそのままaddするとdiffが酷い状態になる。
さらにscala android-sdk-pluginも中間ファイルを生成する。
必要ないファイルはignoreしておくべき。

.gitignore
.DS_Store
*.log
*~
*#*
*.class
.classpath
.project
bin
build.xml
local.properties
proguard.cfg
.settings
target
project/target
tmp
local.propertiesなどもcommitしないようにしているので、リポジトリをcloneしなおして来た直後は
android update project --path `pwd`
を実行して、それぞれの環境に合わせたAndroid SDKのパスを生成しなおすようにしている。
こういうビルド作法はREADMEに書いておくといい

0

git commitと同時に写真を撮ってtweetする

昨日YAPC終わってから研究室の合宿に合流して、酒飲んでたらできてた

https://github.com/shokai/twgit

git cloneしてパスの通っている場所に置いておく。
READMEに従ってimagesnapとtwをインストールする。


ふつうのgit commmitはこうだけど

git commit -m 'implemented great new features'


git commitしつつ、写真を撮ってtweetするのはこうやる
twgit commit -m 'implemented great new features'
あたまにtwをつけるだけでいい。
commit以外のgitコマンドも動く。

会心のreleaseブランチをpushする時などに記念撮影するといいんじゃないでしょうか?

0

rubygems.orgのauthorsとownersの違いと追加方法

複数人でメンテしているrubygemは、AuthorsとOwnersにコントリビューターを追加しておきたい。

こういう状態

nokogiriとかは、Authorsが4人なのにOwnersが2人になっている。

これはどういうことかというと

  • Authorsはgemspecファイル内のauthors配列とemail配列で指定する
  • Ownersはgemコマンドでrubygems.orgにメールアドレスを追加する
という事になっている。

Authorsはただ名前が表示されるだけ。
Ownersに追加された人はそのgemをrubygems.orgにリリースする権限を与えられる。

Authorsの追加


Gem名.gemspec に書けばいい
Gem::Specification.new do |spec|
spec.name = "babascript"
spec.version = BabaScript::VERSION
spec.authors = ["Sho Hashimoto", "Takumi Baba"]
spec.email = ["hashimoto@shokai.org", "contact@mail.takumibaba.com"]


Ownersの追加


gemコマンドで追加できる
gem owner Gem名 --add hashimoto@shokai.org

rubygems.orgにユーザー登録した時のメールアドレスを指定する。

0

nowコマンドを作った

twで写真をつけてtweetするやつを、crontabで定期的に動かしているんだけど
家にいる時は動かしたくなかった。

しかし「家にいるかどうか」を判定するのはけっこう面倒くさくて、まあwifiのAPや位置情報で判定すればいいんだけど、
crontabやservice内で使いやすい、手動でやりやすいツールを作ることにした。


ここにある
https://github.com/shokai/now

git cloneしてパスの通っている場所に置く。


仕様

$HOME/.now を読み書きするだけ。

使い方

now コンテキスト名 で登録、 now ‘コンテキスト名?’ で判定できる。

コマンド自体のexit statusで判定できる。赤い行は直前のコマンドがexit(1)を返している。


crontabやpipeと組み合わせる


0 * * * * now 'home?' &&  say '家にいます'    > /dev/null 2>&1
0 * * * * now 'home?' || (say '家にいません') > /dev/null 2>&1

&& や || でnowのexit statusを判定すればいいですね。


利用例

家にいない時だけ、写真を撮影してtweetするのはこうやる。
now 'home?' || (say 撮影します && imagesnap -w 2.0 capture.jpg && tw 定点観測 --user=shokai --file=capture.jpg --yes && rm capture.jpg)


現在のなうをtweetするとか
tw "`now`なう"