RubyスクリプトからSSHコマンドを実行してみる

ブログの更新がずいぶんしてなかったのですが、
最近ようやくやる気も徐々に出てきて、何かやろうかなぁと思って、
Rubyを使ってリモート操作したいなぁと思い立ってとりあえずSSHコマンドを叩いてみることにしました。

まずは、インストールから

 % jruby -S gem install net-ssh

ここで注意で、依存関係の問題から次が自動でインストールされないので、一緒にインストールします。

 % jruby -S gem install jruby-pageant

LoadError: no such file to load --jruby_pageant
のようなメッセージが出たら、上記のインストールが足りていないと思われます。


準備が整いました。
では、SSHコマンドを叩くhello worldプログラムを動かしてみます。

Net::SSH.start('ホスト名', 'ユーザ名', :password => 'パスワード') do |ssh|
  puts ssh.exec!('ls ~')
end

実行したらちゃんとlsコマンドが動きました。
これは、いろいろと応用ができそうです。

JRubyFXでWebブラウザを作ってみる

JavaFxになってSwingと比べてWebViewのエンジンが強化されているようです。
SwingでJavascriptとかHTML5とかがうまく動かなかったのがJavaFXではちゃんと動くっぽいです。

まずは簡単なWeb表示

require 'jrubyfx'

class App < JRubyFX::Application
  def start(stage)
    with(stage, width: 800, height: 600, title: 'sample') do
      layout_scene do
        web_view do |v| 
          v.engine.load "http://google.com/"
        end 
      end 
      show
    end 
  end 
end

App.launch

BackボタンとURLバーを作ってみる

デザインにこだわっていないので、見た目がよくないですが。。

require 'jrubyfx'

class App < JRubyFX::Application

  DefaultURL = "http://google.com/"

  def start(stage)
    with(stage, width: 800, height: 600, title: 'sample') do
      layout_scene do
        vbox do
          hbox do
            button(id:"back", text:"back", min_width: 80) 
            text_field(id:"url", pref_width: 800)
          end 
          web_view(id:"browser") do |v| 
            v.engine.load DefaultURL
          end 
        end 
      end 
      show
    end 

    #戻るボタン
    stage['#back'].set_on_action do
      stage["#browser"].engine.history.go(-1)
    end 

    #入力したURLへ移動する
    stage['#url'].set_on_action do
      stage["#browser"].engine.load stage['#url'].text
    end 

    # URLを表示する
    stage['#browser'].engine.get_load_worker.state_property.add_change_listener do |ov, os, new_state|
      if new_state == Worker::State::SUCCEEDED
        stage['#url'].text = stage['#browser'].engine.get_location
      end
    end
   end 
end

App.launch


まとめ

JRubyFXということもあって記述は見よう見まねで書いたので適当です。
それでも意外とあっさり作れました。

googleマップも普通に見れてびっくりしました。
YoutubeはさすがにFlashがないと怒られました。
動画はhtml5でのh.264エンコードされたファイルなら見れたりするかもです。。

なんだかいろいろできそうな感じです。

これはいい!JRubyFX+SceneBuilder(fxml)を使ったGUI開発

JRubyFXはJavaFxRubyっぽく書けますが、
そこにプラスしてfxmlを使うとよりGUI開発が簡単になります。
ここではJRubyFXでfxmlを使う方法をメモとして残します。

まずはJRubyでfxmlをロードする

Scene Builderを使って、適当にhello.fxmlというファイルを作成したあと、
JRubyFXにfxmlを読み込まそうとすると以下のようになります。

require 'jrubyfx'
require 'jrubyfx-fxmlloader'

fxml_root File.dirname(__FILE__)

class Main < JRubyFX::Application
  def start(stage)
    with(stage, title: "sample", fxml: SimpleController ) do
    end.show
  end 
end

class SimpleController
  include JRubyFX::Controller
  fxml "hello.fxml"
end

Main.launch

次にonActionでラベルに表示してみる

記述を改良します。

require 'jrubyfx'
require 'jrubyfx-fxmlloader'

fxml_root File.dirname(__FILE__)

class Main < JRubyFX::Application
  def start(stage)
    with(stage, title: "sample", fxml: SimpleController ) do
    end.show
  end 
end

class SimpleController
  include JRubyFX::Controller
  fxml "hello.fxml"

  # fxmlの「onAction="#click_btn"」対応する
  on :click_btn do
    puts "pushed!!!"
    @view.text = "Pushed!!!"    # fxml1の「fx:id="view"」に対応する
  end 
end

Main.launch

SceneBuilderでButtonを作成したあと、SceneBuilder上の右側の情報「Code:」 の中から「On Action」に"click_btn"を書きます。
つぎに、SceneBuilderでLabelを作成したあと、SceneBuilder上の右側の情報「Code:」の中から「fx:id」に"view"を書きます。
このとき、単なる"id"と間違えないようにしてください(「Properties」のほうの"id"じゃないです)

まとめ

Rubyコードとfxmlの関連づけはほとんどインスピレーションでできました。
JRubyFX+fxmlを使うとViewはSceneBuilderを使ってGUIで描け、ControllerはRubyでスマートに書けるので
プログラミングが楽しくなること間違い無しです。

Ruby実装のTclインタプリタを作ってみた

Ruby実装のTclインタプリタを探していたのですが、
libtclのバインディングしか見つからなくって、しかたなく自分でつくってみました。
いろいろと手抜きはありますが、それなりに実装できたっぽいので以下に公開します。

https://github.com/zuk74/pico-tcl

まだコマンドは一部しか実装できていませんが
set, puts, expr, if, for, while
とかはなんとなく動きます。
ただし、exprやwhile文の条件式は、rubyのevalをそのまま使っているのでTcl仕様にはなっていません。
のちのち改良できたらいいなと思っています。

コーディングはいろいろ悩んで結局ここまで作るのに1週間もかかってしまいました。
モチベーションが続けば、コマンドやサポート機能も増やしていければと思います。

JRubyでODEをはじめてみる2

前回に引き続き
今度はGUIで試してみます。

オリジナルのODEにはdrawstuffというデモ用のGUIキット?が含まれています。
ode4jにもありがたいことにdemoのjarの中にdrawstuffが隠れています。
今回はdemoに含まれているdrawstuffを利用します。

ode4jのdrawstuffはLWJGLライブラリが使用されているので準備します。
http://lwjgl.org/download.php
lwjgl-2.9.1.zip

まずはdrawstuffの動作確認

require 'ode4j/core-0.2.8.jar'
require 'ode4j/demo-0.2.8.jar'
require 'lwjgl/jar/lwjgl.jar'
require 'lwjgl/jar/lwjgl_util.jar'

DrawStuff = org.ode4j.drawstuff.DrawStuff
DsFunctions = org.ode4j.drawstuff.DrawStuff.dsFunctions

class TestSim < DsFunctions

  def start
  end

  def command(cmd)
  end

  def step(pause)
  end

  def stop
  end
end

DrawStuff::dsSimulationLoop(ARGV, 400,300, TestSim.new)

実行は次のようにします。
lwjglはネイティブライブラリが必要なためパス指定を付けてあげます。
たとえば以下はMacOS の場合です。

  • 実行
 $ ls lwjgl/macosx
libjinput-osx.jnilib
liblwjgl.jnilib
openal.dylib

 $ jruby-1.7.9 -J-Djava.library.path=lwjgl/macosx hoge2.rb


(GUI)ボールの落下

以下のソースでひとまず格好がつきました。

require 'ode4j/core-0.2.8.jar'
require 'ode4j/demo-0.2.8.jar'
require 'lwjgl/jar/lwjgl.jar'
require 'lwjgl/jar/lwjgl_util.jar'

#for ODE
OdeHelper = org.ode4j.ode.OdeHelper
DContactJoint = org.ode4j.ode.DContactJoint
DContactBuffer = org.ode4j.ode.DContactBuffer
OdeConstants = org.ode4j.ode.OdeConstants
#for Drawstuff
DrawStuff = org.ode4j.drawstuff.DrawStuff
DsFunctions = org.ode4j.drawstuff.DrawStuff.dsFunctions

class TestSim < DsFunctions

  MAX_CONTACTS = 4 

  def nearCallback(data,o1,o2)
    b1 = o1.getBody
    b2 = o2.getBody

    until b1 or b2 or !OdeHelper.areaConnectedExcluding(b1,b2,DContactJoint.class)
      return
    end 

    contacts = DContactBuffer.new(MAX_CONTACTS)
    
    MAX_CONTACTS.times do |n| 
      contact = contacts.get(n)
      contact.surface.mode = OdeConstants.dContactBounce |
                            OdeConstants.dContactSoftCFM
      contact.surface.mu = OdeConstants.dInfinity
      contact.surface.bounce = 0.1
      contact.surface.bounce_vel = 0.1
      contact.surface.soft_cfm = 0.01
    end

    numc = OdeHelper.collide(o1,o2,MAX_CONTACTS,contacts.getGeomBuffer)
    if numc != 0
      numc.times do |n|
        c = OdeHelper.createContactJoint(@world,@cgroup,contacts.get(n))
        c.attach(b1,b2)
      end
    end
  end

  def init
    @world = OdeHelper.createWorld
    @world.setGravity(0.0, 0.0, -9.8)
    @world.setCFM(0.001)
    @space = OdeHelper.createHashSpace
    #@space = OdeHelper.createSimpleSpace(nil)
    @cgroup = OdeHelper.createJointGroup

    floor = OdeHelper.createPlane(@space, 0.0, 0.0, 1.0, 0)

    ball_body = OdeHelper.createBody(@world)
    ball_body.setPosition(0.0, 0.0, 10.0)
    ball_mass = OdeHelper.createMass
    ball_mass.setSphereTotal(1.0, 0.5)
    ball_body.setMass(ball_mass)
    @ball = OdeHelper.createSphere(@space, 0.5)
    @ball.setBody(ball_body)
  end

  def start
    xyz = [-6.0,0,3.0]
    hpr = [0.0,0.0,0.0]
    DrawStuff::dsSetViewpoint(xyz,hpr)

    init
  end

  def command(cmd)
  end

  def simLoop(pause)
    @space.collide(nil, Proc.new{|data,o1,o2|
        nearCallback(data,o1,o2)
    })
    if !pause
#      @world.step(0.02)
      @world.quickStep(0.02)
    end
    @cgroup.empty

    pos = @ball.getPosition.toFloatArray
    puts "pos - #{pos[0]} #{pos[1]} #{pos[2]}"

    DrawStuff::dsSetColor(1,0,0)
    DrawStuff::dsDrawSphere(
        @ball.getPosition,
        @ball.getRotation,
        0.5)
  end

  def step(pause)
    simLoop(pause)
  end

  def stop
  end
end

#OdeHelper.initODE
OdeHelper.initODE2(0)

DrawStuff::dsSimulationLoop(ARGV, 400,300, TestSim.new)


作りはかなり雑です。
全くといっていいほどRubyらしく書けてません。
つっこむところはいろいろありそうですが、とりあえず、やりたいことの第一歩としてはよいのかなと。

JRuby+ODE という組み合わせは如何なものかと思いますが、利点をあげるとするならば

  • Java環境が入っていればどこでも動く
  • 環境構築からコード実行まで一切コンパイルを必要としない
  • ODEでRubyの楽しさを味わえる

という感じでしょうか。

ただし、おそらくオリジナルODEよりは動作はおそくなるだろうなーと。
その辺はいろいろ試す中でみていこうと思います。

JRubyでODEをはじめてみる

物理計算エンジンOpen Dynamics Engine (ODE) をRubyでやりたい!という前々から願望があり、いろいろ調べているうちにJRubyならどうだろうということで試してみました。

ODEはC/C++のライブラリですが、探してみるとJava版も存在しているようです。
Java版ODE: ode4j
http://www.ode4j.org
ode4jはメンテナンスが行き届いているようでちゃんとこの時点で最新であるODE-0.12.0に対応しています。
また、APIJava版にアレンジされていますがオリジナルよりももしかするときれいにまとまっていて扱いやすそうです。
で、JavaのライブラリならばJRubyでも使えるぞ、ということでチャレンジしてみました。

(CUI)自由落下、衝突なし

まずは、GUIなしで試してみます。

require 'core-0.2.8.jar'

OdeHelper = org.ode4j.ode.OdeHelper


OdeHelper.initODE

world = OdeHelper.createWorld
world.setGravity(0.0, 0.0, -9.8)

body = OdeHelper.createBody(world)
body.setPosition(0.0, 0.0, 10.0)

1000.times do
  world.step(0.01)

  pos = body.getPosition.toFloatArray
  puts " pos - #{pos[0]} #{pos[1]} #{pos[2]}"

  break if pos[2] < 0.0 
end

サイトからcore-0.2.8.jarをダウンロードしてきたら、rubyスクリプトと同じ場所に置きます。
実行すると、

 $ jruby-1.7.9 hoge.rb

(CUI)自由落下、衝突あり

次に、衝突ありを試してみます。

require 'core-0.2.8.jar'

OdeHelper = org.ode4j.ode.OdeHelper
DContactJoint = org.ode4j.ode.DContactJoint
DContactBuffer = org.ode4j.ode.DContactBuffer
OdeConstants = org.ode4j.ode.OdeConstants

MAX_CONTACTS = 4 

def nearCallBack(data,o1,o2)

  b1 = o1.getBody
  b2 = o2.getBody

  until b1 or b2 or !OdeHelper.areaConnectedExcluding(b1,b2,DContactJoint.class)
    return
  end 

  contacts = DContactBuffer.new(MAX_CONTACTS)
  
  MAX_CONTACTS.times do |n| 
    contact = contacts.get(n)
    contact.surface.mode = OdeConstants.dContactBounce |
                           OdeConstants.dContactSoftCFM
    contact.surface.mu = OdeConstants.dInfinity
    contact.surface.bounce = 0.1 
    contact.surface.bounce_vel = 0.1 
    contact.surface.soft_cfm = 0.01
  end 

  numc = OdeHelper.collide(o1,o2,MAX_CONTACTS,contacts.getGeomBuffer)
  if numc != 0
    numc.times do |n|
      c = OdeHelper.createContactJoint(@world,@cgroup,contacts.get(n))
      c.attach(b1,b2)
    end
  end
end

#OdeHelper.initODE
OdeHelper.initODE2(0)

@world = OdeHelper.createWorld
@world.setGravity(0.0, 0.0, -9.8)
@world.setCFM(0.001)
@space = OdeHelper.createHashSpace
#@space = OdeHelper.createSimpleSpace(nil)
@cgroup = OdeHelper.createJointGroup

floor = OdeHelper.createPlane(@space, 0.0, 0.0, 1.0, 0)

ball_body = OdeHelper.createBody(@world)
ball_body.setPosition(0.0, 0.0, 10.0)
ball_mass = OdeHelper.createMass
ball_mass.setSphereTotal(1.0, 0.5)
ball_body.setMass(ball_mass)

ball = OdeHelper.createSphere(@space, 0.5)
ball.setBody(ball_body)

# sim-loop
500.times do |i|

  @space.collide(nil, Proc.new{|data,o1,o2|
      nearCallBack(data,o1,o2)
  })
#  @world.step(0.02)
  @world.quickStep(0.02)
  @cgroup.empty

  pos = ball_body.getPosition.toFloatArray
  puts "#{'%4d'%i}: pos - #{pos[0]} #{pos[1]} #{pos[2]}"

end

collideのnearCallbackの部分はdemoを参考に作成してみました。
いろいろとハマりましたがもとのODEがわかっていればそれほど難しくはなかったです。

JRubyとODEの組み合わせって興味あるひとどのくらいいるのだろうか。

続く

Windowsで簡単にRedmine on JRubyをデプロイするメモ

WindowsにてRedmineを立ち上げる方法はいろいろあると思います。
ネットで検索かければいろいろ紹介されていると思います。
ここでは、

  • 誰でも手軽に
  • インストールに手間をかけず
  • 持ち運びが容易な(ポータブル)

方法を目標にします。

データベースの選択は、jdbcsqlite。 <= なぜならWindowsにインストールしなくてすむから。
あと環境変数も、今回は設定しないで進めます。

バージョン

ダウンロードしたものは、

ダウンロードからRedmine起動まで

jruby-bin-1.7.4.zip

redmine-2.3.2.zip

ダウンロードできたら、適当な場所に展開します。
説明をわかりやすくするため、ここでは以下のように展開します。

c:\redmine-2.3.2
c:\redmine-2.3.2\jruby-1.7.4

場所を移動。

 % cd c:\redmine-2.3.2

以下のファイルを作成。

  • config\database.yml
production:
  adapter: jdbcsqlite3
  database: db/redmine.db
  encoding: utf-8

以下の手順で実行。

 % jruby-1.7.4\bin\jruby.exe -S gem install bundler --no-rdoc --no-ri
    ↓
 % jruby-1.7.4\bin\jruby.exe -S bundle install --without development test
    ↓
 % jruby-1.7.4\bin\jruby.exe -S rake generate_secret_token
    ↓
 % jruby-1.7.4\bin\jruby.exe -S rake db:migrate RAILS_ENV=production
    ↓
 % jruby-1.7.4\bin\jruby.exe -J-Dfile.encoding=utf-8 -S rake redmine:load_default_data RAILS_ENV=production

Select language: ar, az, bg, bs, ca, cs, da, de, el, en, en-GB, es, et, eu, fa, fi, fr, gl, he, hr, hu, id, it, ja, ko, lt, lv, mk, mn, nl,
no, pl, pt, pt-BR, ro, ru, sk, sl, sq, sr, sr-YU, sv, th, tr, uk, vi, zh, zh-TW [en] ja
<= 日本語ならば"ja"を選択
   ※ここでエラーがでるようならば、下記を参考

    ↓ 
 % jruby-1.7.4\bin\jruby.exe -J-Dfile.encoding=utf-8 script\rails server webrick -e production
    ↓
http://localhost:3000
へアクセス後、admin/adminでログイン


Redmineを立ち上げるたびにコマンドを打つのは面倒なので、バッチを作っておきます。
c:\redmine-2.3.2 の中に

@echo off
set PATH=%CD%\jruby-1.7.4\bin;%PATH%
jruby -J-Dfile.encoding=utf-8 script/rails server webrick -e production

※インストールで嵌った件

意味不明な、以下のメッセージが出る。

Select language: ar, az, bg, bs, ca, cs, da, de, el, en, en-GB, es, et, eu, fa, fi, fr, gl, he, hr, hu, id, it, ja, ko, lt, lv, mk, mn, nl,
no, pl, pt, pt-BR, ro, ru, sk, sl, sq, sr, sr-YU, sv, th, tr, uk, vi, zh, zh-TW [en] ja
====================================
Error: (C:/work/redmine-2.3.2/config/locales/ja.yml): expected ',' or ']', but got Key while parsing a flow sequence at line 20 column 5
Default configuration data was not loaded.

ネットで調べてみたら、どうやらファイル開くときの文字コードがおかしいことがわかりました。
文字コードをちゃんと指定してあげると解消されます。

 % jruby -J-Dfile.encoding=utf-8 ...

v1.6.8だと問題ないんだけども。。

まとめ

ダウンロードからRedmine起動までの所要時間は、だいたい15分ぐらい。
環境移動はredmineのフォルダごと移動すればJavaが動く場所なら別のWindowsでも動くはず。
動作もサクサクです。
代替ファイルサイズは初期時で100MB程度と大きいですが。。。
自分用で使うのであれば、JRuby+Redmineはかなりお手軽で良いと思います。