sbt-plugin入門 Global pluginsメモ

少し前に@tototoshiさんがsbt-pluginについてブログを書いてたのを参考に
グローバルなsbtプラグインを試してみた。
(いつもお世話になってます・・(m´・ω・`)mペコ )
参考:sbtでコマンドを定義する
   はじめての sbt-plugin

でも、試すというほどのものではなく、
https://github.com/harrah/xsbt/wiki/Plugins の最後の方「Global plugins example」に
書いてあることそのままですね。(というか、自分がやり方知らなかっただけかも・・・)

とりあえず、試します。
まずechoプラグインとつくります。
build.sbt

sbtPlugin := true

name := "echo-plugin"

organization := "com.sassunt"

src/main/scala/echo.scala

import sbt._
import Keys._

object EchoPlugin extends Plugin {
  override lazy val settings = Seq(
    commands ++= Seq(
      echo
    )
  )

  lazy val echo = Command.args("echo", "<args>") { (state, args) =>
    println(args.mkString(" "))
    state
  }
}

を作ってsbtでpublish-localコマンドを実行します。

ここまでは参考にした@tototoshiさんのブログにありましたね。

でも、ここまで作成したものは、sbtのプロジェクト毎にaddPluginをしないと使えないです。
(毎回書くのは面倒ですね。)

そこで、Globalなプラグインです。

ホームの.sbtフォルダの下にpluginsフォルダ作ってbuild.sbtに書けばOKです。

~/.sbt/plugins/build.sbt
(存在しない場合は、フォルダ・ファイルともに作ってください。)

addSbtPlugin("com.sassunt" % "echo-plugin" % "0.1")

(・・・バージョンって何も定義しない場合は、0.1になるのか?)
https://github.com/harrah/xsbt/wiki/Pluginsには、
libraryDependencies += "org.example" %% "example-plugin" % "0.1"って書いてあるけど・・・
 サンプル通りだとうまく行かない気がする?・・・保留)

以上で終了です。好きなsbtプロジェクトからechoコマンドが使えるようになりました。


おまけ
~/.sbt/plugins/の下に直接
echo.scalaとか作ってもつかえるようになりますよー

unfilteredのスライドショーPicture-Show にアップロード機能を追加してみた

picture-show使いたいけど・・・いろいろインストールするのが面倒・・・(自分のPCじゃないやつに
でも、picture-showつかいたいお (´・ω・`)

あわよくば、他の人にも気軽に使ってもらえたらいいな・・・

で、とりあえずやりたかったことは、

  1. conf.jsとか含めたZipファイルを(ようするにpshowコマンドするときに必要なもの一式)アップロード
  2. それを解凍してスライドショー

最初はHerokuとかでやろうと思ったけど、アップロードが無理みたいなので、
(アップロードとかする場合は、S3使えとかどこかのブログに書いてあってけど・・そんな金ない)

でもまぁ会社とかで使えればいいやと思った・・・

使い方 (README書けよってことですね)
https://github.com/sassunt/picture-show
機能としてはsoftprops / picture-showに追加してるだけです。


git clone

> git clone git@github.com:sassunt/picture-show.git

起動する前にアップロードするディレクトリを指定する必要があります

> export PSHOW_ARCHIVE=/hoge/foo
> export PICTURE_SHOW=/hoge/bar

PSHOW_ARCHIVEがZipファイルが置かれる場所
PICTURE_SHOWがZipファイルが展開される場所


実行

> cd picture-show
> sbt run

確認

http://lcoalhost:8080/

もしくは

> sbt assembly

picture-show/target/PictureShow-assembly-0.1.0-SNAPSHOT.jar
ができるので

java -jar PictureShow-assembly-0.1.0-SNAPSHOT.jar com.sassunt.Main

で実行
念のため、https://github.com/sassunt/picture-show/downloads にJarをおいておきました。

TODO(むしろ問題点):

  1. 画像表示だけは対応してないので、可能なら対応する・・・(リンクがうまくいかない)
  2. エラーハンドリング
  3. 毎回同じものもZipを解凍するので、修正する
  4. チュートリアル
  5. etc(その他いっぱい・・・・

まだ、未完成です・・・

もう運用回避で・・・・


補足:
ZipファイルはZipを解凍した状態がかきのようにならないとだめです・・・
example.zipを解凍

example-
  -con.js
  -css
  -sample
  -js

Scalaでzipファイルの解凍?2

前回zip解凍を作ったわけだけど・・・微妙でした。
@xuwei_kさんからIteratorやローンパターン使ったほうがいいってつぶやきがあって

とりあえず、手を加えてみた

import java.io.{File => JFile, FileInputStream, FileOutputStream}
import java.util.zip.{ZipEntry, ZipFile, ZipInputStream}
import scala.util.control.Exception._

case class Zip(val path: JFile) {

  private val Extension = """(^.+)\.((?i)zip)$""".r
  val name = path.getName match {
    case Extension(name, _)  => name
    case _ =>
      throw new java.lang.IllegalArgumentException("Not Zip file")
  }

  def unzip(targetPath: JFile = path.getParentFile): Throwable Either JFile = {

    def using[A <: java.io.Closeable] (s: A)(f: A => Unit): Unit = {
      try { f(s) } finally { s.close() }
    }

    val baseDirName = name
    val baseDirPath = new JFile(targetPath, baseDirName)
    baseDirPath.mkdir()

    allCatch either {
      val zis = new ZipInputStream(new FileInputStream(path))
      using(zis){zs =>
        Iterator.continually(zs.getNextEntry).takeWhile(
          _ != null
        ).filterNot(
          _.isDirectory
        ).foreach(e => {
          val file = new JFile(baseDirPath, e.getName)
          file.getParentFile.mkdir()
          val fos =  new FileOutputStream(file)

          using(fos){fs =>
            Iterator.continually(zs.read()).takeWhile(_ != -1).foreach(fs write _)
            zs.closeEntry()
          }
        })
      }
    baseDirPath
    }
  }

}

修正:Zipの正規表現を大文字小文字の区別をなくしました

Scalaでzipファイルの解凍?

Zipの解凍をScalaで書いてみたけど、う〜ん、どうなんだろう。

import java.util.zip._
import java.io._

val zipFile = "./hoge.zip"
val is = new ZipInputStream(new FileInputStream(zipFile))

val curDir = zipFile.substring(0, zipFile.lastIndexOf("."))
new File(curDir).mkdir()

val list = Stream.continually(is.getNextEntry).takeWhile(_ != null).filter(f => !f.isDirectory())
list.foreach(e => {
  val file = new File(curDir, e.getName)
  file.getParentFile.mkdirs()
  val fos = new FileOutputStream(file)
  Stream.continually(is.read()).takeWhile(_ != -1).foreach(i => {
    fos.write(i)
  })
  is.closeEntry()
  fos.close()
})
is.close()

11/4:ちょい修正

※エラーハンドリングとかはやってないので注意・・・

もっといい感じのあったら
教えてください!!!

unfilteredでファイルアップロード2 書き込み編

前回はInputStreamを使用してたけど
今回は、書き込みを行います。ようするにサーバーにファイルをぽいっと保存します。

とりあえず↓に必要なところだけ抜き出しました。

def intent = {
  case req @ Path("/") => req match {
    case GET(_) => 

    case POST(MultiPart(hreq)) => MultiPartParams.Disk(hreq).files("file") match {
       case Seq(file, _*) if !file.name.isEmpty => {
         file.write(new java.io.File("./hoge/%s" format file.name))
       }
       case _ => 
    }
  }
}

前回からの変更点は、

MultiPartParams.Streamed

MultiPartParams.Disk

になって、

file.write(new java.io.File("./hoge/%s" format file.name))

で書き込みが行われるってことです。そのまんまです。

以上。

unfilteredでファイルアップロード

サポートしてくれてるのですごく簡単です。

build.sbtに以下を追加すれば使えるようになります。

"net.databinder" %% "unfiltered-uploads" % "0.5.1"

実際の例は↓な感じ。 アップロードしたファイルの中身を表示するだけですが・・・。

import scala.io.Source
import unfiltered.request._
import unfiltered.response._
import util.Properties

class Uploader extends unfiltered.filter.Plan {
  def intent = {
    case GET(_) => Ok ~> view(None, Nil)
    case POST(Path(_) & MultiPart(req)) => MultiPartParams.Streamed(req).files("file") match {
      case Seq(file, _*) if !file.name.isEmpty  =>
        view(Some(file.name), file.stream(t => Source.fromInputStream(t).getLines.toList))
      case f => view(None, Nil)
    }
  }

  def view(temp: Option[String], text: List[String]) = Html{
    <html>
      <body>
        <form method="POST" enctype="multipart/form-data">
          <input type="file" value="" name="file" />
          <input type="submit" />
        </form>
        { temp.map(t =>
          <div>
            <h1>File Name: {t}</h1>
            <pre>
              {text.zipWithIndex.map{case (line,index) => <div>{"%4d: %s".format(index + 1,  line)}</div>}}
            </pre>
          </div>
          ).toSeq }
      </body>
    </html>
  }
}

必要ないと思うけど、一応githubにあげてみた。
https://github.com/sassunt/unfiltered-upload-sample

上のコードの

file.stream(t => Source.fromInputStream(t).getLines.toList)

java.io.InputStreamを返してくれるけど、Source使う以外ないのかな

補足メモ:
自分が作ったときは、コンパイル時にMultiPartやらServletのクラスが不足してるよ
みたいなエラーが出て困ったけど・・・(エラーの詳細忘れた)
build.sbtに

"javax.servlet" % "servlet-api" % "2.5"

入れたらOKだった。

Herokuに静的ファイルサイトを作る

タイトルと違うけど、ただpicture-showのスライドをHerokuでみたかっただけです。

1.[unfiltered] picture-showをインストール
https://github.com/softprops/picture-show

2.giter8でpicture-showのテンプレートをダウンロード

> g8 softprops/picture-show

3.編集
好きなように編集する、
上のURL見れば方法が書いてます。
どんな感じかみたいだけなら飛ばしてもダイジョブです。

4.offline出力
3で作ったディレクトリに移動して、以下のコマンドを実行

> pshow --offline

すると、outディレクトリができます。

5.Herokuにアップするディレクトリ構成の準備
Herokuにアップしたいディレクトリを作成(HerokuSite)し、その中にoutディレクトリを移動させます。
そこでoutをpublicという名称に変更してください。
次にconfig.ruという名称のファイルを作成します。

config.ru

use Rack::Static,
   :urls => ["/assets", "/css", "/hello","/js"],
   :root => "public"

 run lambda { |env|
   [
     200,
     {
       'Content-Type'  => 'text/html',
       'Cache-Control' => 'public, max-age=86400'
     }, 
     File.open('public/index.html', File::RDONLY)
   ]
}

最終的にはこんな感じになるはずです。

- HerokuSite/
  |- config.ru
  |- public/
    |- assets/
    |- css/
    |- hello/
    |- index.html
    |- js/

6.いざ、Herokuへ

> git init
> git add .
> git commit -m init
> heroku create
> git push heroku master

終了!!!
かっこいいスライドが見えるよ!!!