XPathで a を祖先に持たない b を選択する

最初はXPath2.0のexcept使えないと無理じゃないか?って思っていたけど、よくよく考えたらそんなことなかったのでメモ。(きっとまた忘れると思うので、、、)

たとえば下記のようなXMLがあったとして

<root>
  <a>
    <c>
      <b />
    </c>
  </a>
  <c>
    <b />
  </c>
</root>

aを祖先に持たないbだけを取りたいってときは、XPathは下記のように書ける。

//b[not(ancestor::a)]

ancestorはコンテキストノードの祖先の集合なので、そこにaが無いもの(not)といった指定になる。

WebSocketでカメラの映像を共有する(Shared Camera with WebSocket)

WebSocketを利用してカメラの映像を共有するアプリケーションを作ってみました。 Raspberry Piにカメラをつなげて監視カメラにしたり、ブラウザを通してお互いのカメラの映像を共有するようなことができます。

https://github.com/onozaty/shared-camera-websocket/blob/master/screenshot.png?raw=true

仕組みとしては、WebSocketのバイナリメッセージで、画像をブロードキャストするような感じです。通信部分では特に難しいことはしてないです。Raspberry Piのカメラ周りでは嵌りましたが、、

雑話

使い方や実装内容などは、GitHubの方を見てもらえば良いかと思いますので、ここでは作り始めたきっかけとかを。

WebSocketでバイナリも送れるってのを知ったときに、画像を連続して送りつけることによって動画っぽくできるのではと思ったのですが、ずっと試せないままだったので、今年こそはということで作り始めました。結果として、転送間隔短くすれば、それなりに動画っぽく見えなくないものが出来上がりました。

カメラからの画像取得は、ブラウザでMediaDevices.getUserMediaを通じて取るものを最初作りました。WebRTC周りを以前調べていたことがあって、カメラへのアクセスはあたりがついていたので、特に嵌ることも無くさくっと作れました。で、その次にRaspberry PiにつなげたUSBカメラから画像が取りたくて、Node.js(v4l2camera)、Python(+OpenCV)で書きましたが、自分の実装が悪いのか、どれも連続して画像を取る際に思った以上にパフォーマンスが出ないこともあって、最終的にはJavaで下記のライブラリを使って作りました。

これでも遅い(1秒くらい遅延を感じる)のですが、どうにか見えるレベルにはなりました。また、このライブラリを使ったことによって、同じコードでRaspberry Pi、Windows、Macのどれでも動くクライアントになりました。カメラ周りでOSの差を吸収してくれるのはとても便利で助かりました。

ゆくゆくは、サーバ側のアプリケーションをHerokuあたりに乗せて、クライアントを自宅のRaspberry Piにして、いつでも我が家の様子が見えるようにしようかと考えています。(家族に嫌がられなければ…)

Lombokで生成されたメソッドをJacocoのカバレッジから除外する

Jacocoの0.8.0が2018年1月2日にリリースされ、待ち焦がれていたLombokで生成されたメソッドを除外する機能がはいりました。

  • JaCoCo - Change History

    Methods annotated with @lombok.Generated to better integrate with Lombok >= 1.16.14. Initial analysis and contribution by Rüdiger zu Dohna (GitHub #513).

なお、他にも様々なフィルタが実装(または実装予定)されています。

こういった機能を使うことによって、必要な範囲に絞ったカバレッジが把握しやすくなると思います。

除外方法

Jacoco側では、@lombok.Generatedが付いているメソッドを一律対象外にするといった実装となっており、Jacoco側で何か設定で切り替えるといったものではありませんでした。

@lombok.Generatedは、Lombokの設定lombok.configで付与することになります。デフォルトでは付与されません。 lombok.addLombokGeneratedAnnotation = trueを設定することによって付与されるようになります。

まとめると、、除外のために下記の2つが必要になります。

  • Jacocoのバージョンを 0.8.0 以上にする
  • lombok.configlombok.addLombokGeneratedAnnotation = trueを設定する

Gradleでの設定例

Gradleプロジェクトで試してみました。コードは一式は下記にあります。

GradleでJacocoを使う場合には、build.gradleapply plugin: 'jacoco'を指定するだけですが、2018年1月6日時点だとバージョン0.7.6が使われてしまうので、明示的にJacocoのバージョンを指定する必要があります。

apply plugin: 'java'
apply plugin: 'jacoco'

repositories { jcenter() }

dependencies {
    compileOnly 'org.projectlombok:lombok:1.16.18'
    testCompile 'junit:junit:4.12'
    testCompile 'org.assertj:assertj-core:3.9.0'
}

jacoco {
    toolVersion = "0.8.0"
}

lombok.configは下記のように記載します。config.stopBubbling = trueはプロジェクトのルートであることをLombokに教えるための設定です。(これ以上親ディレクトリを辿って設定ファイルを探さないように)

config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true

これで無事除外されました。

Online Bookmark Incremental Search for Firefox のv1.0.2を公開しました

v1.0.2を公開しました。

変更点は下記の2件です。

  • 入力欄でリターンキーを押下した場合にリロードしてしまう(submitしてしまう)問題修正
  • アプリケーションを開くためのショートカットキー(Ctrl+Shift+O (MacはCommand+Shift+O))追加

Firefoxはアドオンのショートカットキー変更する機能が無いので、他と被ったら終わりです、、、

Online Bookmark Incremental Search for Firefox を公開しました。

オンラインブックマーク(Google Bookmarks, Pinboard, はてなブックマーク)をインクリメンタルサーチする拡張機能を公開しました。WebExtensionsに対応したものとなり、Firefox57以降でも使えます。

f:id:onozaty:20171223235639g:plain

ブックマークをローカルに保存しておいて利用するので、いつでも素早く検索できます。また、ショートカットキーで行やページを移動して、選択行のブックマークを開くことができます。

対応しているサービスは下記の通りです。

旧拡張機能との違い

この拡張機能は、下記のレガシーな拡張(Firefox57以降使えなくなったもの)を置き換えるものになります。

WebExtensionsに対応するにあたって、Chrome版で提供していたものを元にしようとしましたが、Chrome版はWebSQLを利用しており、FirefoxのWebExtensionsでは利用できないため、一から作り直しました。

ブックマークは全てメモリ上で検索するようにしています。数万件くらいのブックマークならば、特に問題なく動いています。

担当者欄にインクリメンタルサーチをつける(Redmine View Customize Plugin)

この記事は、Redmine Advent Calendar 2017 - Qiitaの5日目の記事となります。


更新@2017-12-29
チケット一覧でエラーが発生していたので、コードを少し変更(対象のIDが存在しない場合、何も処理しない)しました。


前田さんの下記スライドみて、Redmine 4.0 で担当者欄にインクリメンタルサーチが付くことを知りました。

とっても便利そうです。4.0が待ち遠しいですね!

で、4.0が出るまで待てないので、View customize plugin for Redmineで同じようなことをやってみることにしました。

Redmineが jQuery UI 使っているので、Autocomplete | jQuery UI を使っています。イメージとしては、もともとあるプルダウンを非表示にして、担当者をインクリメンタルサーチするためのテキストボックスを追加し、非表示としているプルダウンと同期を取るような形にします。

View customize の設定内容

Path pattern

チケット画面を対象にします。

/issues

Code

Type:JavaScriptとして下記を設定します。

$(function() {

  function replaceSelectToAutocomplete(selectElement) {

    var $select = $(selectElement);
    if ($select.length == 0) {
      return;
    }

    var options = $select.find('option[value!=""]')
      .map(function() {
        var $option = $(this);
        return {
          label: $option.text(),
          optionValue: $option.val()
        };
      })
      .toArray();

    var $autocomplete = $('<input type="text" class="ui-autocomplete-input autocomplete" autocomplete="off">')
      .autocomplete({
        source: options,
        minLength: 0,
        select: function(event, ui) {
          $select.val(ui.item.optionValue);
        },
        change: function(event, ui) {
          if (ui.item != null) {
            return;
          }

          var inputValue = $autocomplete.val();
          var matchOption = $.grep(options, function(option) {
            return option.label == inputValue;
          })[0];

          if (matchOption != null) {
            $select.val(matchOption.optionValue);
          } else {
            $autocomplete.val('');
            $select.val('');
          }
        }});

    var currentSelectValue = $select.val();
    if (currentSelectValue != '') {
      var initAutcompleteValue = $.grep(options, function(option) {
        return option.optionValue == currentSelectValue;
      })[0].label;

      $autocomplete.val(initAutcompleteValue);
    }

    $select.hide()
      .after($autocomplete);
  }

  function setupAssignedAutocomplete() {
    replaceSelectToAutocomplete('#issue_assigned_to_id');
  }

  // ステータス変更時などにDOMが差し替えられるので
  // フォームの内容が書き変わるたびに表示切替
  var _replaceIssueFormWith = replaceIssueFormWith;
  replaceIssueFormWith = function(html){

    _replaceIssueFormWith(html);

    setupAssignedAutocomplete();
  };

  setupAssignedAutocomplete();
});

画面イメージ

下記のような感じになりました。

f:id:onozaty:20171203141830g:plain

6日目は @g_maedaさんです!

Redmine から Rocket.Chat (Slack) に通知するプラグイン

RedmineからRocket.Chatに通知するプラグインを調べていたところ、redmine_messenger というプラグインがよさそうに見えました。ちなみにWebhook使っているのでSlackでも同じです。

同じようなもので、 redmine-slack があります。機能的にはほとんど変わりません。というか、redmine_messenger は、redmine-slack のコードも元にしているとのことです。

redmine-slack と比べて良いと思ったのは、、

  • 通知対象はIssuesとWikiで、redmine-slackと同じだが、細かい設定(説明を含めるかなど)ができる
  • プロジェクトの設定画面上で、プロジェクト毎に設定を変えられる
    (サイト上の説明だと、カスタムフィールドを定義してそこで設定と書いてありますが、実際は設定画面として用意されていました。redmine-slackはカスタムフィールド方式です)

プロジェクト毎の設定画面は下記の通りです。(最後の方が切れてしまっていますが、チケットのあとにWikiの設定があります) f:id:onozaty:20171119210833p:plain