[ Redmine index ]

Redmineプラグイン開発環境(Fedora)

はじめに

Redmineのプラグインを作成する環境として、最初Windows 7にCRubyをインストールしてRedmineをインストールしましたが、Bundlerによるgemのインストールでいろいろとエラーが発生し、解決が手間どりそうでした。

そこで、たまたまWindows上でVMware Player上にFedora 17をインストールしていたので、そこにRedmineプラグイン開発環境をつくることにしました。Fedora 17は、Ruby 1.9.3が標準搭載されており、CentOS 6よりもRedmineのインストールの手間が少ないです。

インストールメモ

まず、プラグイン開発ではRedmineをdevelopmentモードで動かします。データベースは個人でしか使わないのでSQLite3にします(MySQLでもいいと思いますが)。Rackサーバーは、標準出力がそのままコンソールになっているWebrickを使います。

Redmineの展開

まず、開発作業ディレクトリを決め、そこにRedmineを展開します。ホームディレクトリー下にworkなりdevなりのディレクトリを設け、その下でredmine-2.1.2.tar.gz(またはzip)を展開します。

~$ mkdir dev
~$ cd dev
dev$ tar xzf ~/redmine-2.1.2.tar.gz
dev$ cd redmine-2.1.2
redmine-2.1.2$ 

bundlerジェムのインストール

Rubyは、必要なライブラリをgemと呼ばれるRuby独自のモジュール管理機構でインストールします。このgemを効率よく管理するためにRailsが設けた上位機構がbundlerです。bundlerを使ってgemをインストールします。

bundlerは、Railsアプリケーション個々に別々にgemをインストールし、依存関係を解決するといった機能を提供します。

このbundler自身もgemであるため、最初にbundlerをシステムにインストールします。

~$ gem install bundler
~$

gem達のインストール

Redmineが必要とするgem達をbundlerでインストールします。このとき、Redmine専用にgemをインストールします。

redmine-2.1.3$ bundle install --path vendor/plugin --without postgresql mysql

redmine-2.1.3$

このとき、Fedora OSにコンポーネントが不足しているとエラーになります。

一例では、ruby-devel、ImageMagick、ImaげMagick-develがないためエラーとなっていました。これらをyumでインストール後、再度bundleコマンドを実行し、インストールができました。

設定ファイル

redmine-2.1.2/configディレクトリに2つ設定ファイルを作成します。

database.yml

production:
  adapter: sqlite3
  database: db/test.sqlite3

development:
  adapter: sqlite3
  database: db/test.sqlite3

開発では、主にdevelopmentモードで動かすので、development:に設定を記述します。また、productionモードでの動作確認時に備えて、production: の定義をしています。

SQLiteの場合、別途動くデーモンはないので、adapterとdabase(SQLiteのRedmine用DBファイルのパス)を指定するだけです。

configuration.yml

roduction:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: "localhost"
      port: 25

development:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: "localhost"
      port: 25

データベース初期化

generate_secret_token

redmine-2.1.2$ bundle exec rake generate_secret_token
  :

db:migrate

redmine-2-1.2$ bundle exec rake db:migrate
  :

ファイアウォール停止、SELinux停止

開発環境なので、トラブル時の切り分けを容易にするため、この両者を停止しておきます。

/etc/sysconfig/selinux

変更前

SELINUX=enforcing

変更後

SELINUX=permissive

なお、再起動が面倒なので、

redmine-2.1.2$ sudo setenforce 0
redmine-2.1.2$ getenforce
Permissive
redmine-2.1.2$

で停止

iptables

CentOS系では、伝統的なSystem V系のinitによるサービス(デーモン)起動の仕組みを採用していました。Fedora 17では、systemdという新しい仕組みになっており、サービスの起動/終了/設定の方法が変わってます。

OS起動時にiptablesを起動しないようにする
# systemctl disable iptables.service
現在起動中のiptablesを停止する
# systemctl stop iptables.service

動作確認

Webrickを起動して、redmineを立ち上げ、ブラウザから接続してみます。

redmine-2.1.2$ ruby script/rails server webrick
=> Booting WEBrick
=> Rails 3.2.8 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
/home/torutk/dev/redmine-2.1.2/vendor/plugin/ruby/1.9.1/gems/activesupport-3.2.8/lib/active_support/dependencies.rb:251:in `block in require': iconv will be deprecated in the future, use String#encode instead.
[2012-11-11 15:59:50] INFO  WEBrick 1.3.1
[2012-11-11 15:59:50] INFO  ruby 1.9.3 (2012-10-12) [x86_64-linux]
[2012-11-11 15:59:50] INFO  WEBrick::HTTPServer#start: pid=27641 port=3000

port=3000と出てるので、同じマシン上でブラウザを立ち上げ、http://localhost:3000 に接続してみます。

Redmine画面が出ればめでたしです。

最初のプラグイン作成

プラグインのもっとも単純な機能はマクロ(チケットやWikiにおいて使用)のようなので、まず初めてのマクロを作ってみます。

Phoneticマクロ

無線等で、相手に正しく文字を伝えるために、アルファベットA〜Zにそれぞれ決められた単語を取り決め、それを音声で伝えます。

例えば、'RED'を正しく使えたい場合、"Romeo"、"Echo"、"Delta"と音声で伝えます。

今回作るマクロは、A〜Zのいずれかを指定すると、そのフォネティックコードとなる単語を表示するものとします。

Wiki記法  ブラウザでの表示 
{{phonetic(a)}}
 Alfa

プラグインの雛形コードを生成

Railsアプリケーションは、プラグインのコードの雛形を生成するコマンドがあります。

redmine-2.1.2$ ruby script/rails generate redmine_plugin phonetic_macro
/home/torutk/dev/redmine-2.1.2/vendor/plugin/ruby/1.9.1/gems/activesupport-3.2.8/lib/active_support/dependencies.
rb:251:in `block in require': iconv will be deprecated in the future, use String#encode instead.
      create  plugins/phonetic_macro/app
      create  plugins/phonetic_macro/app/controllers
      create  plugins/phonetic_macro/app/helpers
      create  plugins/phonetic_macro/app/models
      create  plugins/phonetic_macro/app/views
      create  plugins/phonetic_macro/db/migrate
      create  plugins/phonetic_macro/lib/tasks
      create  plugins/phonetic_macro/assets/images
      create  plugins/phonetic_macro/assets/javascripts
      create  plugins/phonetic_macro/assets/stylesheets
      create  plugins/phonetic_macro/config/locales
      create  plugins/phonetic_macro/test
      create  plugins/phonetic_macro/test/fixtures
      create  plugins/phonetic_macro/test/unit
      create  plugins/phonetic_macro/test/functional
      create  plugins/phonetic_macro/test/integration
      create  plugins/phonetic_macro/README.rdoc
      create  plugins/phonetic_macro/init.rb
      create  plugins/phonetic_macro/config/routes.rb
      create  plugins/phonetic_macro/config/locales/en.yml
      create  plugins/phonetic_macro/test/test_helper.rb

redmine-2.1.2$

プラグインの説明を記述

生成されたファイル plugins/phonetic_macro/init.rb の内容は次のようになっています。

Redmine::Plugin.register :phonetic_macro do
  name 'Phonetic Macro plugin'
  author 'Author name'
  description 'This is a plugin for Redmine'
  version '0.0.1'
  url 'http://example.com/path/to/plugin'
  author_url 'http://example.com/about'
end

この記述を修正します。

Redmine::Plugin.register :phonetic_macro do
  name 'Phonetic Macro plugin'
  author 'Toru Takahashi'
  description 'This is my first plugin for Redmine'
  version '0.0.1'
  url 'http://www.02.246.ne.jp/~torutk/swetools/redmine/developPlugin.html'
  author_url 'http://www.02.246.ne.jp/~torutk/'
end

マクロの記述(最初の一歩)

Redmineのマクロは、init.rbにRedmine::WikiFormatting::Macros::registerを使ってマクロの置き換え処理を記述するようです。

動作確認の固定文字マクロ

動作確認のため、固定文字列に置き換えるマクロを記述します。

init.rbの後ろに、以下を追記します。

Redmine::WikiFormatting::Macros::register do
  desc "Render the phonetic word for specified alphabet.\n\n" +
       " !{{phoetic(a)}}\n"
  macro :phonetic do |obj, args|
    "Alfa"
  end
end

return文を書かなくても、最後の式の評価結果が戻り値となるようです。

Redmineの実行

Webrickを実行します。

Wiki上でページを編集で、{{phonetic(a)}} と記述し保存します。

マクロ{{phonetic(a)}}を書いた部分に、Alfa と出れば成功です。

マクロの本格的な記述

では、本格的にマクロを記述します。Phoneticは、'a'なら'alfa'、'b'なら'bravo'のような構造なので、データの定義を保持するには連想配列(ハッシュ)が適しています。

phoneticの定義

連想配列で、最初の数個を定義してみます。

phonetic_macro_map = { 'a'=>'alfa', 'b'=>'bravo', 'c'=>'charlie', 'd'=>'delta', 'e'=>'echo' }

あまりきれいではなさそうですが、グローバル変数で定義します。本当はモジュールとかクラス内のスコープで定義したかったのですが、まずは動くコード追及です。

マクロ定義を引数により置き換えるよう修正

Redmine::WikiFormatting::Macros::register do
  desc "Render the phonetic word for specified alphabet.\n\n" +
       " !{{phoetic(a)}}\n"
  macro :phonetic do |obj, args|
    code = args.first.strip[0]
    word = phonetic_macro_map[code]
  end
end

下線部が変更/追加箇所です。マクロに引数が指定された場合、argsに引数が格納されてきます。

Wikiの記述と表示

マクロ記述  表示結果  備考 
{{phonetic(a)}}
alfa   
{{phonetic( a )}} 
alfa   
{{phonetic(A)}} 
{{phonetic(A)}}
マクロがそのままの文字列で表示される 
{{phonetic( A )}}
{{phonetic( A )}}
マクロがそのままの文字列で表示される 
{{phonetic(abc)}}
alfa  
{{phonetic}}
{{phonetic}} 
マクロがそのままの文字列で表示される 
{{phonetic(4)}}
{{phonetic(4)}} 
 マクロがそのままの文字列で表示される 

Wikiに、表のマクロ記述列にあるようにマクロを記述した場合に、表示される文字を表示結果列に示しました。

大文字のとき、引数指定なしのとき、英字以外のときは、マクロ記述の文字列そのものが表示されます。

phoneticのマクロ処理機能改善

大文字の場合も変換されるように修正します。

修正前

code = args.first.strip[0]

修正後

code = args.first.strip[0].downcase

下線部が追加箇所です。これで、引数に指定した文字(先頭)が大文字でも、小文字にして連想配列を引くようになります。

マクロが変換する文字の先頭1文字を大文字にする(キャピタライズ)

現在、{{phonetic(b)}} は bravo と表示されます。これを Bravo と表示されるようにします。

修正前

word = phonetic_macro_map[code]

修正後

word = phonetic_macro_map[code].capitalize

下線部が追加箇所です。

なお、この修正により、phoneticマクロの引数に非英字で始まる文字列を指定したときの挙動が変わります。

修正前:マクロがそのまま文字列で表示される
修正後:エラーメッセージ(Error executing the phonetic macro (undefined method 'capitalize' for nil:NilClass))が表示される

phoneticのマクロ処理エラー対応

変換できない引数が指定されたとき、マクロがそのまま表示されたり、システムエラー(利用者には意味が分からない)が表示されるのは、あまりいただけないので、エラー処理を加えます。

Redmine::WikiFormatting::Macros::register do
  desc "Render the phonetic word for specified alphabet.\n\n" +
       " !{{phoetic(a)}}\n"
  macro :phonetic do |obj, args|
    if (args.size != 1)
      raise "phonetic macro is given only one argument."
    elsif (args.first[0] =~ /[^a-zA-Z]/)
      raise "phonetic macro is given only argument leading character of '[a-zA-Z]'."
    end
    code = args.first.strip[0].downcase
    word = phonetic_macro_map[code].capitalize
  end
end

最初のif節で、マクロ引数の数がちょうど1個でなければ例外をraiseします。マクロ処理の中で例外をraiseすると、Wiki上にはエラーメッセージとして表示されます。分かりやすいエラーメッセージを書くことができます。

次のelsif節で、マクロ引数の先頭1文字がアルファベットでなければ例外をraiseします。

パッケージ化とインストール