Hatena::ブログ(Diary)

=== SANDmark 19106 === beginning stress test このページをアンテナに追加 RSSフィード

2017年03月30日

続・OmegaT用: 正規表現のグループ参照を使って訳文を置き換えるスクリプトを書いたよ(GUI対応)


前回の記事からの続きです。

変更点

使い方

前提として、このスクリプト正規表現グループ参照を用いた置き換えを行うものです。通常の正規表現検索・置換はOmegaTのみで可能です。

regexp = /Sample segment named (.+) could be replaced with (.*)./
def replace(m) { "サンプル分節${m[1]}が${m[2]}に置き換えられます。" }

  • とある部分を変更し、検索に使う正規表現と置換後の文字列を設定します。
  • 『実行』をクリックするとプレビューウィンドウが出てきます。
  • メニュー -> 実行を選択すると、実際に置換を行います。

デフォルトでは

  • Sample segment named something could be replaced with RegExp.
  • Sample segment named whatever could be replaced with script.

上記パターンにマッチする分節すべてがそれぞれ

  • サンプル分節somethingがRegExpに置き換えられます。
  • サンプル分節whateverがscriptに置き換えられます。

と置換されます。

謝辞

OmegaTに含まれるGroovyスクリプトをおおいに参考にさせていただきました。感謝!

技術的な話

GUIなんてやるもんじゃない。
Groovy経由だからJavaほど構築が面倒なわけではないけど、やっぱりコード内でUIを定義しようとするとやっぱりすごいことになりました。長年見なかった感じのコードというか。ほとんどコピペなのでSwingなにそれな私でもなんとかなりました。いろんなサンプルを用意してくれるOmegaTプロジェクトに改めて感謝です。未だにimport staticの意味がわかってないけど許して。

ただ、他のスクリプトを見るとあんまりeachイテレータ使ってなくて、トラディショナルなforループが多いです。分節区切りを変更したらeachで拾えなくなる分節があったんですが、インデックスで参照してたら拾えてたのかな?

OmegaTスクリプトを書こうっていう人がどれくらいいるのかわかりませんが、書いてみると意外と簡単だったりします。現在のベータ版(4.1.1)ではシンタックスハイライトが実装されてたりするので、興味のある人は触ってみてくださいねー。

2017年03月29日

OmegaT用: 正規表現のグループ参照を使って訳文を置き換えるスクリプトを書いたよ


2017-03-30: 更新しました -> 続・OmegaT用: 正規表現のグループ参照を使って訳文を置き換えるスクリプトを書いたよ(GUI対応) - === SANDmark 19106 === beginning stress test

経緯と使い方

なんか前の記事から一年以上経ってて引きました。

最近は翻訳のお仕事をしているんですが、支援ツールとして翻訳メモリソフトOmegaTno title)を使ってます。Javaで書かれたオープンソースソフトウェアで、実はTRADOSの体験版ダウンロードしている最中に暇つぶしで触ってみたらそのまま実務用になってしまったという、脅威の機能性を持つあなどれないやつです。TRADOS体験版の試用期間はまだあるんだけど、多分触らないまま終わりそう。基本的な使い方はマニュアルが充実しているのでそっちに任せるとして、今回はややマニアックな需要です。

チャットログなんかを翻訳していると定型文が大量に引っかかるわけで、いくら参考訳文に出てきても200件あったら大変なわけですよ。もちろん置き換え機能はOmegaTにありますし、正規表現も使えるのだけど、これが "John talked to Jessie", "Jack talked to Jimmy" とか、そういうのが大量にあると単純には置き換えられない。

ので、スクリプトを書きました。ダウンロードしてOmegaTインストールディレクトリにあるscripts/にぶち込んでください。あとはOmegaTを起動してツール→スクリプト→regexp_replacement.groovy→実行です。コンソールにプレビューが表示されるので、キャンセルを繰り返しながら置き換え後の文章を確認しつつ、これだと思ったところで置き換えてください。操作は元に戻せないのでtmxファイルのバックアップを強く推奨します。自己責任でどうぞ。

Groovy自体はじめてだったのでいろいろ調べながらのわくわくさんクオリティでごめんなさい。自分でも相当使いづらいのでそのうち直します。

5行目が検索正規表現とグループ指定、6行目が置き換え後の文字列です。置き換え後の文字列がちょっと読みづらいですが仕様です。""でくくられた部分だけ編集しましょう。${m[1]}がひとつめの括弧、${m[2]}がふたつめの括弧…というように対応してます。

regexp = /(.*) talked to (.*)./
def replace(m) { "${m[1]}さんが${m[2]}さんに言いました。" }

/(.*) talked to (.*)./が例えば "(John) talked to (Jessie)" にヒットして、"JohnさんがJessieさんに言いました。" に置き換えられます。人名はがんばってください。

技術的な話

なんでハードコーディングしてるのかっていうと、OmegaTがグループマッチの部分参照に対応していないからです。どうもJavaのMatcherクラスが十分なメソッドを提供していないからっていう感じはしますが、Twitterでアドバイスをくれたエラリー・ジャンクリストフさんによると



とのことです。ひょっとすると将来的に実装されるかもしれませんが、現時点では他の機能のほうが需要が高い様子。まぁスクリプトでできたわけだしいいよね。

OmegaTはGroovyとJavaScriptの両方でスクリプトが書けますが、今回は好奇心もあってGroovyに挑戦しました。Rubyインスパイアされただけあってとりあえず書き下すのは楽でした。そのぶん処理の最適化とかまったくしていないコード(同じ処理を2回行う手抜き)なので、分節が10万とかになるとどうなるかわかりません。

置き換え後の文字列がStringじゃなくてクロージャになっているのは呼び出しの都合です。"_1さん_2さん"とかで参照したいんですが、面倒なので実装しません。

UIもハードコーディングでローカライズとかまったく考えてないんですが、よし海外ニーズがあったとしても、向こうにはもっといいものがあるでしょうきっと。

2012年04月03日

Capybara-webkitによる高速インテグレーションテストまとめ

このテのエントリも何度目になるかわかりませんが、
実際に使ってきて「これだ!」という環境がある程度確立できた気がするので
一旦ここにまとめておきます。Ruby1.9.3、Rails3.2.2で動作確認。

使うものリスト:

ライブラリちょっとした解説
Sporkテストの高速化をしてくれるDRbサーバ
RSpec2言わずと知れた「動く仕様書
Guardファイルを監視し、変更があればテストを自動実行*1
Capybara独自のDSLインテグレーションテストを可能にする
CapybaraWebkitCapybaraでJavaScriptの動作確認をするためのエンジン
FactoryGirl「fixtureの代わり」とだけ表現するにはあまりにも惜しい多機能なモデルテンプレートエンジン
Headlessブラウザを画面上に表示せずにテストするためのラッパーライブラリ
DatabaseCleanerCapybaraへデータを渡すとき、渡したあとのデータ管理
Rails3GeneratorsFactoryGirl用の定義を自動生成してくれるジェネレータ

autotestからGuardへ乗り換えたのは、Guardが常にプロンプトを表示しており、
rと叩くだけでSporkの再起動から各種specファイルの実行まで行なってくれる他、
Macならgrowl、Linux系ならlibnotifyなど、通知までサポートしてくれる小粋な奴だからです。*2 *3

早速Gemfile:

group :development, :test do
  gem 'spork'
  gem 'rspec-rails'
  gem 'guard-spork'
  gem 'guard-rspec'
  gem 'capybara'
  gem 'capybara-webkit'
  gem 'factory_girl_rails'
  gem 'headless'
  gem 'database_cleaner'
  gem 'rails3-generators'
end

シェル:

$ sudo apt-get install xvfb         # headlessのための仮想ディスプレイライブラリ
$ sudo apt-get install libqt4-dev   # capybara-webkitのためのqt4ライブラリ
$ bundle install
$ bundle update
$ rails g rspec:install
$ spork --bootstrap

ここでSporkの設定が spec/spec_helper.rb の先頭に追加されているので、
Spork.prefork ブロックに元々の spec_helper.rb の内容を全て流し込みます。

そして、各gemの設定もついでにしてしまいましょう。

  • 2012-04-07追記: Sporkはテスト実行時にモデルをリロードしないようなので、
  • no title
  • こちらを参考に Spork.each_run ブロックに追記しました。

spec/spec_helper.rb(一部コメントなどを消去しています):

Spork.prefork do
  # headless のための設定。
  # これをしないとテストのたびにブラウザが表示される(かもしれない)
  require "headless"
  headless = Headless.new(display:99)
  headless.start
  at_exit{ headless.destroy }

  # ここから下は元々の spec_helper.rb の内容
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  RSpec.configure do |config|
    config.use_transactional_fixtures = true
    config.infer_base_class_for_anonymous_controllers = false

    # :focus => true のテストのみ実行する
    config.treat_symbols_as_metadata_keys_with_true_values = true
    config.filter_run :focus => true
    config.run_all_when_everything_filtered = true

    # database_cleanerの設定
    config.before :suite do
      DatabaseCleaner.strategy = :truncation
      DatabaseCleaner.clean_with :truncation
    end

    config.before :each do
      if example.metadata[:js]
        self.use_transactional_fixtures = false
        DatabaseCleaner.start
      end
    end

    config.after :each do
      if example.metadata[:js]
        DatabaseCleaner.clean
        self.use_transactional_fixtures = true
      end
    end
  end

  Capybara.javascript_driver = :webkit  # ドライバをcapybara-webkitに(デフォルトは :selenium)
end

Spork.each_run do
  FactoryGirl.reload   # ファクトリの定義が実行ごとにリロードされる
  silence_warnings do                                            # 2012-04-07追記
    Dir[Rails.root.join('app/**/*.rb')].each{ |file| load file } # 
  end                                                            #
end

続いてGuardの設定:

$ bundle exec guard init spork
$ bundle exec guard init rspec

このコマンドで Guardfile というものが作成されているはずです。
一見すると長くて記号だらけで読む気も起きませんが、
単に監視するファイルを正規表現で指定し、
対応するspecファイルを実行するようにしているだけです。

今回はFactoryGirlを使う他、
SporkとはDRbを使って通信してもらわなければならないので、
そこんところをちょっとだけ修正します。

# ↓コメントアウト
# guard 'rspec', :version => 2 do
guard 'rspec', :version => 2, :cli => '--drb' do  # rspecの引数に --drb を渡すことでSporkと通信してくれます
  watch(%r{^spec/factories/(.+)\.rb$})                { "spec/models" }  # 追加
  # ...
end

モデルを作成するとき、rails3-generatorsが spec/factories/ に
ファクトリファイルのひな形を置いてくれるようになっているので、
そのフォルダも監視してあげましょうという感じ。

そしてラスト、config/application.rb:

# Generators
module YourAppName
  class Application < Rails::Application
    # ...略...
    config.generators do |g|
      g.test_framework :rspec, fixture:true, views:false
      g.fixture_replacement :factory_girl
    end
  end
end

これでようやくスタートラインに立てました。
さっそくGuardを実行します。

$ bundle exec guard
Guard uses NotifySend to send notifications.
Guard is now watching at '/home/sandmark/sites/YourAppName'
Starting Spork for RSpec
Using RSpec
Preloading Rails environment
Loading Spork.prefork block...
Spork is ready and listening on 8989!
Spork server for RSpec successfully started
Guard::RSpec is running, with RSpec 2!
Running all specs
Running tests with args ["--drb", "-f", "progress", "-r", "/home/sandmark/.rvm/gems/ruby-1.9.3-p125/gems/guard-rspec-0.7.0/lib/guard/rspec/formatters/notification_rspec.rb", "-f", "Guard::RSpec::Formatter::NotificationRSpec", "--out", "/dev/null", "--failure-exit-code", "2", "spec"]...
Run options: include {:focus=>true}

すでにモデルやコントローラを作成してあった場合は
大量の pending が出迎えてくれることでしょう。
ともあれ、何も問題がなければここでGuardのプロンプトが表示されているはずです。
(詳細については help と入力してEnterしといてください。)

さて、実際のspecファイルはこんな感じになります。
spec/requests/users_spec.rb:

describe "Users" do
  describe "GET /users" do
    before do
      @user = FactoryGirl.create(:user)
      visit "/users"
    end

    context "when 'about username' link was clicked," do
      it "should show user's name", :focus => true do
        click_link "about #{@user.name}"
        page.should have_content(@user.name)
      end

      it "should pop-up hidden message for JavaScript", :js => true do
        click_link "about #{@user.name}"
        page.should have_content("Woo Hoo!")
      end
    end  # context
  end  # GET /users
end  # Users

ここで it ディレクティブに渡しているのは :js と :focus ですが、
今回のエントリに直接関係があるのは :js オプションのほうです。

spec_helper.rb にも書きましたが、
:js => true を設定していると capybara-webkit がJavaScriptエンジンとして動き、
Capybaraがブラウザ(今回のケースではwebkit-server)を起動、接続して動作をチェックするわけです。

しかし headless を導入しているのでブラウザのウィンドウは画面に表示されず、
メモリ上の仮想ディスプレイでうまいことテストしてくれる、というからくり。*4

ただ :webkit が呼び出されたときは webkit-server にデータを渡さなければならないので
RSpec2のタイミングでデータをリセットされると困ります。
そのため、 spec_helper.rb の DatabaseCleaner の設定部分で
一時的に RSpec2 でのデータ管理を止め、 DatabaseCleaner に任せています。

そして :focus => true のときは、Guardが変更を感知し、テストを開始しても、
:focus => true が設定されているテストのみを実行してくれるものです。
ひとつの機能を集中的に実装したい場合は特に役立つでしょう。

というわけで以上、もっと小さくまとめるつもりでしたが冗長な説明になってしまいました。
VMwareからネイティブUbuntuに変えたときに異常にテストが遅かった(50秒近くかかった)ので、
あるあ…ねーよwww と思って調査した次第です。

少しでもお役に立てれば。
それでは Happy Testing!

*1:SporkとRSpec2との仲介もしてくれる上に、結果を自動で通知してくれます。

*2Windowsは知らん。

*3Mac環境は持っていないのでわからないけど、growlって有料になったんだっけ…?

*4:capybara-webkitはそもそもX無しで動作するので headless 導入の意味があるかどうかは僕にはわかりません。

2012年03月03日

autotestでの例外ルール記述

autotestについては詳しく説明しませんが、
テストに該当するであろうファイルを予測し、
タイムスタンプが変更されると、自動でテストを実行してくれるツールです。

ただ、デフォルトでは実行したディレクトリの全てのファイルに対して
タイムスタンプのウォッチを行うので、負荷はもちろんのこと、
例えばEmacsでflymakeなんかを使っていると、
ファイルを編集しただけで一時ファイルの生成/変更を検知し、
テストを実行してしまう、なんてこともあります。っていうかありました。

そこで今回は無視(例外)ルールの記述について書いてみます。

.autotest:

Autotest.add_hook :initialize do |autotest|
  ignores = %w{.git .autotest .rspec coverage README.markdown}
  ignores.each do |exception|
    autotest.add_exception(exception)
  end
end

Autotest.add_hook :run do |autotest|
  autotest.exceptions = /.*_flymake\.rb$|.*\.yml$/
end

ずばり現在使っているのがこれです。
使えません。修正バージョンは下のほうに。
Rails用ではないので、その辺は改造して欲しいところではありますが……。

とりあえず :initialize フックで静的に無視するファイルを指定しています。
コードカバレッジやリポジトリの差分ファイルを全てチェックされたのでは
精神衛生上よくないというものです。

次に :run フックで正規表現による無視ルールを追加しています。
調べが足りないと思うんですが、 :initialize フックでは正規表現は使えない(?)
ので、ひとまずはこれでよろしくやってくれているので満足ではあります。


修正版。

Autotest.add_hook :initialize do |autotest|
  %w{.git .autotest .rspec coverage README.markdown}.each do |exception|
    autotest.add_exception(exception)
  end
  autotest.add_exception(/.*\.yml$/)
  autotest.add_exception(/.*_flymake\.rb$/)
end

大嘘ぶっこいてました、 :initialize フックで正規表現使えました。
flymakeの一時ファイルに関してはまだちょっとわからないので、
また追記するかも知れません。