PAB@やっぱり求職中なんだよなあ…

求職活動中とありますが、活動報告というよりは、技術の話題を多めでいきます。Scalaが大好きなので特にScalaの話が中心になると思います。

今回はこの間作ったdispatch-tumblrライブラリの応用編として、 コマンドラインからTumblrに投稿できるコマンドをConscriptを使って作ってみたいと思います。

しかし、あらかじめ書いておきますが、Conscriptでコマンドを作るのは、 RubyやPythonといったスクリプト言語でスクリプトを書くのに比べるとちょっと面倒臭いところがあります。 とにかく何でもScalaで書きたいという(僕みたいな)ちょっと偏屈な人にしかお勧めできません。

前回までの記事

Conscriptとは?

Conscriptは、Conscript用に書かれたプログラムをインストールするためのツールです。

n8han/conscript

Twiwt:Blog / jugyo : conscript - Scala で作られたソフトウェアをインストールするためのツール

詳しくは上記のサイトを見ていただくとして、Conscriptにはいくつか特徴があります。

  • sbt経由でコマンドを実行 → sbtで作らないといけない & sbtで取得できないといけない
  • Githubにプロジェクトがないといけない

2番目が地味にきついです。 やっぱり手軽にコマンドを作る用途には向いてない感じですね。

作るコマンドの仕様

% scalamblr --help

Usage: scalamblr [options]

  --hostname <value>
        hostname
  --title <value>
        title
  --tags <value>
        tags
  --markdown
        use markdown
  -h | --help
        help
  --body <value>
        body filename

コマンドの名前はscalamblrとします。できるのはテキストの投稿だけです。 タイトルやタグをオプションとして指定できます。 本文は--bodyでファイル名を指定するか、標準入力から読み取ります。 接続のために必要なユーザー名やパスワードは~/.scalamblr/config.scalaという設定ファイルに書きます。

Giter8とは?

新しくsbtプロジェクトを作るには、Giter8でテンプレートを使うのが便利です。

n8han/giter8

Twiwt:Blog / jugyo : Giter8 - Scala アプリケーションの雛形を作るためのコマンドラインツール

詳しくは上記のサイトを参考にしてください。

しかし、残念なことに、最近のGiter8は仕様の変更があって、g8 --listでテンプレート一覧が見られなくなりました。 今は過渡期だと思うのですが、どんなテンプレートあるのかさっぱりわからない状態になってしまいました。

とりあえず、Giter8コミッターの n8han (Nathan Hamblen) さんと softprops (doug tangren) さんの二人のテンプレートは標準と言えるものなので、まずこれをチェックしましょう。 プロジェクト名の最後に.g8と付いているのがg8テンプレートです。

Giter8を使ってプロジェクトを作る

では、実際にプロジェクトを作ってみましょう。 今回使うテンプレートは https://github.com/n8han/conscript.g8 です。 プロジェクトを作るには次のコマンドを実行します。

% g8 n8han/conscript --orgId=com.github.hexx --name=scalamblr

前回説明したsbtのorganizationnameの設定ですね。 すると、以下のようなファイルが作られます。

scalamblr
├── build.sbt
├── project
│   └── plugins
│       └── build.sbt
└── src
    └── main
        ├── conscript
        │   └── app
        │       └── launchconfig
        └── scala
            └── App.scala

実際に使うにはこれをちょっと変更します。

scalamblr
├── build.sbt
├── project
│   └── plugins.sbt
└── src
    └── main
        ├── conscript
        │   └── scalamblr
        │       └── launchconfig
        └── scala
            └── Scalamblr.scala

scalamblr/project/plugins/build.sbt というディレクトリ構成は最近のsbtではdeprecatedになっているので scalamblr/project/plugins.sbt にしておきます。 あとscalamblr/src/main/conscript/scalamblrのディレクトリ名を変えておきます。 この最後のディレクトリ名がコマンド名になるので重要です。

Conscriptエントリーポイント

Giter8テンプレートを展開したそのままだと以下のようなファイルになっていると思います。

この class App extends xsbti.AppMain というクラスの def run(config: xsbti.AppConfiguration) というメソッドがConscriptから呼び出されるエントリーポイントになります。 このままでもいいのですが、一応AppからScalamblrという名前に変更しておきます。

ソースコード

それではソースコードを解説したいと思います。

大きくわけて以下のことをやってます。

  • 設定ファイルの読み込み
  • コマンドラインオプションの処理
  • 本文の読み込み
  • 送信

それぞれについて軽く説明します。

設定ファイル

設定ファイルにはTwitterのutil-evalを使います。 名前のとおりScalaのプログラムを動的にEvalできるユーティリティです。 設定ファイルをScalaのプログラムで書き、それをutil-evalで読み込み、Scalaオブジェクトにするというわけです。 詳しくは以下を参照してください。

twitter/util @ GitHub

twitter が Scala 大好きすぎて (?) 設定ファイルまで Scala のソースコードな件 - scalaとか・・・

これがConfigの定義です。

trait Config {
  def defaultHostname: String
  def markdown = false
  def consumerKey: String
  def consumerSecret: String
  def username: String
  def password: String
}

そして、実際の設定ファイルは以下のようになります。

思いっきりScalaのプログラムです。 このファイルを~/.scalamblr/config.scalaに置いて、以下のように読み込むと、Configオブジェクトが取得できます。

val scalamblrConfigFile = scala.util.Properties.userHome + "/.scalamblr/config.scala"
val eval = new Eval
val config = eval[Config](new java.io.File(scalamblrConfigFile))

設定ファイルの形式を考えたり、設定ファイルのパーズをしなくていいので便利です。

コマンドラインオプション

コマンドラインオプションの処理は、正直何のライブラリを使っても大差ないと思いますが、 今回はscoptを使います。

scopt/scopt

parsing parameter using scopt | scalaxb.org

case class CommandLineOption(
    var hostname: String = config.defaultHostname,
    var title: String = "",
    var tags: String = "",
    var body: String = "",
    var markdown: Boolean = false,
    var help: Boolean = false)

これがオプションのデータです。

var option = CommandLineOption()
val parser = new OptionParser("scalamblr") {
  opt("hostname", "hostname", { v => option.hostname = v })
  opt("title", "title", { v => option.title = v })
  opt("tags", "tags", { v => option.tags = v })
  opt("markdown", "use markdown", { option.markdown = true })
  opt("h", "help", "help", { option.help = true })
  opt("body", "body filename", { v => option.body = v})
}
if (!parser.parse(args)) {
  return
}
if (option.help) {
  println(parser.usage)
  return
}

そして、処理です。このへんはscoptのイディオムだと思うので、上記サイトを参考にしてください。

本文の読み込み

def readBody = {
  if (option.body.length > 0) {
    Source.fromFile(option.body).mkString
  } else {
    Source.stdin.mkString
  }
}

IOライブラリが貧弱なScalaですが、単純な読み込みならSourceを使えば簡単です。

送信

def post(body: String) {
  val consumer = Consumer(config.consumerKey, config.consumerSecret)
  val http = new Http
  val access_token = http(Auth.access_token(consumer, config.username, config.password))
  val query = Seq(
    "type" -> "text",
    "title" -> option.title,
    "tags" -> option.tags,
    "markdown" -> option.markdown.toString,
    "body" -> body)
  http(Blog.post(option.hostname, consumer, access_token, query:_*))
}

送信処理です。このへんは前回説明したものと同じですね。

post(readBody)

それで最後に本文を読み込んで送信となります。

起動用の設定

Conscriptで実行するためには起動用の設定を書かないといけません。 src/main/conscript/scalamblr/launchconfigがその設定です。 Giter8でプロジェクトを作った場合はすでにできているので、 エントリーポイントのクラス名を変更した場合はそこを直しておきます。

インストール

最初のほうにも書きましたが、ConscriptのプログラムをインストールするにはGithubに置かないといけません。 今回作ったものをここに置いておきます。

https://github.com/hexx/scalamblr

そして、例によってsbt publish-localでローカルにライブラリをインストールした後で、 以下のコマンドで ~/bin/scalamblr にインストールされます。

% cs hexx/scalamblr

まとめ

今回はConscriptを使ってコマンドラインからTumblrに投稿するプログラムを作ってみました。 やっぱりlaunchconfigを書かなきゃいけなかったり、Githubに置かないといけなかったりするのが面倒臭いですね。 もうちょっと大きいプログラムをGithubで配布したいときに使うツールなんですかね。 あと、本文中では触れなかったんですが、conscript-pluginがメンテナンスされてなくて、動かないんですよね。 そのあたりもちょっと残念なところです。

Giter8とutil-evalはかなり使えるツールなので、参考にしていただければと思います。

  1. takuya71pab-techからリブログしました
  2. pab-techの投稿です