LuaRocks で Lua のモジュールを管理する

lua のコードをあちこちに書き散らかすよりも、機能毎にモジュール化するほうが何かと便利なので lua のパッケージマネージャの一つ [LuaRocks](https://github.com/keplerproject/luarocks)（ちなみに、issue を見ると LR って略されてるね）を利用してモジュール化してるんだけど、今回はそのメモ。

まぁ、基本的なことはほぼドキュメント - http://luarocks.org/en/Documentation - に書いてあるし、チュートリアル見たらわかると思うので割愛して、独自リポジトリでのモジュール管理について。

っても、ドキュメントが結構残念な感じに不明な点がチラホラあるんだけど、マイナー言語なので仕方ないのかも。。

## とりあえず LuaRocks をインストール

いつの間にか LuaRocks のバージョンが上がっていたので最新版をインストールする。インストールは手動でもいいけれど簡単なので Homebrew でインストール。

```sh
brew install luarocks
```

それから、基本的に Mac を管理者権限でログインして使うことがないのでホームディレクトリに `~/.luarocks` ディレクトリを作っておく。

```sh
mkdir ~/.luarocks
```

一般ユーザー権限でモジュールをインストールする時はこのディレクトリが使われることになる。あと、この中に `config.lua` という設定用の lua ファイルも置けるけど今回は何もしないので詳しく知りたい人はドキュメントを読むと良いよ。

**Note:** 普段から管理者権限で動かしてる人は気にしないでおk。


## モジュールのインストール方法色々

LuaRocks はモジュール化する時に便利だしデファクトっぽいしって理由で を利用しているのだけれど、作ったモジュールをデフォルトのリポジトリには公開してない。

なんかね、lua いじりだしてまだ1年半くらいでマナーがいまいちよくわからないってのもあるし、npm でもそうだけどモジュール名がかぶるのを気にして変な名前にするのもなんかなんかだしなぁ・・・とか、etc. な理由からなんだけど、でも、このままではものすごく不便でしょうがない。

例えば、普通に `luarocks` コマンドを使ってモジュールをインストールするときは；

```sh
luarocks --local install <module_name>
```
**Note:** 一般ユーザーなので `--local` オプションを付けて `~/.luarocks` にインストールされるように指定しているけど、管理者権限ならいらない。


とすると、デフォルト公開リポジトリからダウンロードされてインストールされる。もちろん検索する時は；

```sh
luarocks search <module_name>
```

とすることでデフォルト公開リポジトリに上がってるモジュールが検索される。  
ちなみに、インストールしたモジュールを `OpenResty` 使いたい時は `luarocks path` で `LUA_PATH` と `LUA_CPATH` が表示されるので、これを `lua_package_path` と `lua_package_cpath` に追加してあげればおk。


次に自分で作ったモジュールのインストール方法として、例えば以下のような `hello` モジュールの `rockspec` ファイル `hello-scm-1.rockspec` があるとする；

```ディレクトリ構成
─ lua-hello
  ├─ hello-scm-1.rockspec
  ├─ hello.lua
  └─ lib
     └─ world.lua
```

```lua:hello.lua
local function hello()
    print( 'hello' );
end

return hello;
```

```lua:lib/world.lua
local function world()
    print( 'hello world' );
end

return world;
```

```lua:hello-scm-1.rockspec
package = "hello"
version = "scm-1"
source = {
    url = "git://example.com/mah0x211/lua-hello.git"
}
description = {
    summary = "example hello module",
    detailed = [[example hello module]],
    homepage = "https://example.com/mah0x211/lua-hello", 
    license = "MIT/X11",
    maintainer = "mah0x211"
}
dependencies = {
    "lua >= 5.1"
}
build = {
    type = "builtin",
    modules = {
        hello = "hello.lua",
        ["hello.world"] = "lib/world.lua"
    }
}
```

これがローカルにある時はディレクトリ内で；

```sh
luarocks --local make
```

でインストールできる。リモートのどこかにある時は `rockspec` の URL を指定して；

```sh
luarocks --local install https://example.com/mah0x211/lua-hello/hello-scm-1.rockspec
```

なんてやると `rockspec` 内に書いてある `source.url` からダウンロードしてインストールされる。でも、これってめんどくさいし、例えば以下のように；

```lua
dependencies = {
    "lua >= 5.1",
    "my_dep_module"
}
```

`my_dep_module` が自分で作ったモジュールで、それに依存してるとデフォルト公開リポジトリから検索されるので上手くいかない。`npm` みたいに URL 指定できればいいんだけどね。こまた。


## GitHub のリポジトリでモジュール管理

ドキュメントがあっさりしすぎてて肝心なトコがよくわからなかったのでずっと放置して、手動インストールをしてたんだけど、さすがにそろそろなんとかしたい。そんなわけで、ふと GitHub で自分用のリポジトリを作って管理できたら良いかもって思いついたのでやってみた。

ドキュメントによると、LuaRocks をインストールすると `luarocks-admin` というツールも一緒にインストールされる。これを使って `manifest` ファイルというリポジトリのインデックスファイルを作って自分の好きな場所に上げて管理できるとのことだったので早速やってみた。

まずは適当に GitHub でリポジトリを作って clone してくる。今回は自分のアカウントで `rocks` というリポジトリを作ってみた。

```sh
git clone https://github.com/mah0x211/rocks.git
cd rocks
```

この中に管理したいモジュールの `rockspec` ファイルをコピーして `luarocks-admin make_manifest` で `manifest` ファイルを生成する。とりあえず、自作のテンプレートエンジン `tsukuyomi` の rockspec をいれてみる。

```sh
cp /path/to/tsukuyomi/tsukuyomi-scm-1.rockspec ./
luarocks-admin make_manifest .
```

こうすると以下のファイルが生成された。

- index.html
- manifest
- manifest-5.1
- manifest-5.2

これをコミット＆プッシュして GitHub に上がったところで `luarocks` から利用してみる。デフォルト以外のリポジトリは `--from=<server>` オプションで指定することで優先して見にいかせられるとヘルプに書いてあったのでやってみる。詳しくは `luarocks help` で。

```sh
luarocks --from=https://github.com/mah0x211/rocks/raw/master/ search tsukuyomi

Warning: Failed searching manifest: Failed loading manifest: Failed extracting manifest file

Search results:
===============

```

おー！　うまくいかない。　おかしい。。

色々ぐぐってみるとヘルプには書かれてない `--verbose` オプションがあるようなので指定してもう一度やってみて確認する。

```sh
... curl -L --user-agent 'LuaRocks/2.1.2 macosx-x86 via curl' 'http://mah0x211.github.io/rocks/manifest-5.1.zip' 2> /dev/null 1> ...
```

ファッ！？なんで `zip` なの？調べてみると [Release history](http://luarocks.org/en/Release_history) にそれっぽいことが書かれている。

> Version 2.1.1 - 29/Oct/2013 - download  
> Remote manifests are now compressed and locally cached, making commands faster

ということは、未実装かまたしてもドキュメント化してないオプションでもあるくさいのでソースを調べてみる。

```sh
cat `which luarocks-admin`
#!/usr/bin/env lua

local loader = require("luarocks.loader")
local command_line = require("luarocks.command_line")

program_description = "LuaRocks repository administration interface"

commands = {
   help = "luarocks.help",
   make_manifest = "luarocks.make_manifest",
   add = "luarocks.add",
   remove = "luarocks.admin_remove",
   refresh_cache = "luarocks.refresh_cache",
}

command_line.run_command(...)
```

`luarocks.make_manifest` が `require` する時のパス表記になってるからモジュールっぽい。`/usr/local/share/lua/5.1/luarocks/make_manifest.lua` の中を覗いてみる。

```sh
cat /usr/local/share/lua/5.1/luarocks/make_manifest.lua 

--- Module implementing the luarocks-admin "make_manifest" command.
-- Compile a manifest file for a repository.
module("luarocks.make_manifest", package.seeall)

local manif = require("luarocks.manif")
local index = require("luarocks.index")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local deps = require("luarocks.deps")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")

help_summary = "Compile a manifest file for a repository."

help = [[
<argument>, if given, is a local repository pathname.

--local-tree  If given, do not write versioned versions of the manifest file.
              Use this when rebuilding the manifest of a local rocks tree.
]]

--- Driver function for "make_manifest" command.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository configured as cfg.rocks_dir is used.
-- @return boolean or (nil, string): True if manifest was generated,
-- or nil and an error message.
function run(...)
   local flags, repo = util.parse_flags(...)

   assert(type(repo) == "string" or not repo)
   repo = repo or cfg.rocks_dir
  
   util.printout("Making manifest for "..repo)
   
   if repo:match("/lib/luarocks") and not flags["local-tree"] then
      util.warning("This looks like a local rocks tree, but you did not pass --local-tree.")
   end
   
   local ok, err = manif.make_manifest(repo, deps.get_deps_mode(flags), not flags["local-tree"])
   if ok and not flags["local-tree"] then
      util.printout("Generating index.html for "..repo)
      index.make_index(repo)
   end
   if flags["local-tree"] then
      for luaver in util.lua_versions() do
         fs.delete(dir.path(repo, "manifest-"..luaver))
      end
   end
   return ok, err
end
```

`manif.make_manifest` って箇所がきっとそれだってことで覗いてみると `zip_manifests` という関数が定義されてるからこれを呼んでるトコを `find /usr/local/share/lua/5.1/luarocks | xargs grep zip_manifests` で探してみると `/usr/local/share/lua/5.1/luarocks/add.lua` からしか呼ばれてない。。

これって `luarocks-admin add` でリモートリポジトリにアップロードするコマンドオプションなはずなので使えないっぽいし。。ここからしか呼ばれてないからこれ以上調べてもしょうがないし、手動で zip するとバージョン番号の付与とかめんどうなので、luarocks のモジュールを直接使うコードを書いて対応する。

```lua:makeZip.lua
local cfg = require("luarocks.cfg")
local manif = require("luarocks.manif");

manif.zip_manifests();
```

これを実行して `manifest-5.1.zip` と `manifest-5.2.zip` が作られてコミット＆プッシュして確認してみると。

```sh
luarocks --from=https://github.com/mah0x211/rocks/raw/master/ search tsukuyomi

Search results:
===============

Rockspecs and source rocks:
---------------------------
tsukuyomi
   scm-1 (rockspec) - https://github.com/mah0x211/rocks/raw/master
```

できた。疲れた。。午前中まるっとついやしたし。。

疲れたけど `index.html` も生成されてるし、せっかくだから `gh-pages` ブランチ作って http://mah0x211.github.io/rocks/ にアクセス出来るとわかりやすいだろなってことで、もうちょっとちょこちょこいじって完成。

生成された `index.html` のデザインが残念すぎるけど、とりあえずこれでいいや。