luaでもmagikaを使いたい。


## 概要
どうも、shypkです。
lua基盤にmagikaを導入した流れをお見せしたいと思います。

## magikaとは

[magika](https://github.com/google/magika/)はgoogleが開発したAIでfiletype判別できる技術らしいです。
javascript, rust, python などで対応していて、残念ながら現時点ではluaに対応はしていません。

https://github.com/google/magika/


## mluaでラッピング
過去の経験から、luaで使いたい場合はラッピングしちゃえばなんとかなる気がします。
せっかくrust対応もしているので、速いだろうと信じて、rust版のmagikaをラッピングしたいと思います。

rustには[mlua](https://docs.rs/mlua/latest/mlua/)というcrateがあり、luaで使える動的ライブラリを簡単に作れます。
luastring を受け取れるidentify_content_label関数を登録してみます。

``` rust:lumagika/src/lib.rs
use mlua::{Lua};
use mlua::prelude::*;

fn identify_content_label(_: &Lua, content: LuaString) -> LuaResult<String> { ... }

#[mlua::lua_module]
fn liblumagika(lua: &Lua) -> LuaResult<LuaTable> {
    let exports = lua.create_table()?;
    exports.set("identify_content_label", lua.create_function(identify_content_label)?)?;
    Ok(exports)
}
```


## magikaを呼び出す。

あとはmagikaのcrateを呼び出すだけ。
[こちら](https://github.com/google/magika/blob/main/rust/lib/src/lib.rs)の説明によると

> magika.identify_file_sync("src/lib.rs")?.info().label, "rust");
> magika.identify_content_sync(&b"#!/bin/sh\necho hello"[..])?;

こういった使い方ができるらしいので、
今回はファイル内容を受け取って、そのlabelを返すという動作をしたいと思います。
上で繋いだidentify_content_labelの関数を内容を実装していきます。
そのコードがこちら。

``` rust:lumagika/src/lib.rs
fn identify_content_label(_: &Lua, content: LuaString) -> LuaResult<String> {
    let ret: &str;
    let _ = match Session::new() {
        Ok(magika) => {
            let result = magika.identify_content_sync(&*content.as_bytes());
            match result {
                Ok(result) => {
                    ret = result.content_type().unwrap_or(ContentType::Unknown).info().label;
                },
                Err(e) => {
                    ret = "Error";
                    println!("Error identifying content: {:?}", e);
                },
            }

        },
        Err(e) => {
            ret = "Error";
            println!("Error identifying content: {:?}", e);
        }
    };

    Ok(ret.to_string())
}
```

## cargo
cargoでビルドしたいので、Cargo.tomlに忘れずdependencyを追加しておきます。

``` toml:lumagika/Cargo.toml
[dependencies]
magika = { version = "0.1.0-rc.1" }
mlua = { version = "0.10.0", features = ["lua51","module"] }
mlua-sys = { version = "0.6.3", features = ["lua51","module"] }
```
(mlua::lua_module を使うには features=moduleが必要です。)


## テスト
他のluaスクリプトで呼び出す準備ができました。
実際使っているコードは見せづらいので、ここではlua interpreterで簡単にテストしてみます。

``` lua:
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> package.cpath = package.cpath .. ";./target/release/?.so"
local lumagika = require("liblumagika")
local type = lumagika.identify_content_label("<html><body>some empty body</body><html>")
print(type)
html
> package.cpath = package.cpath .. ";./target/release/?.so"
local lumagika = require("liblumagika")
local type = lumagika.identify_content_label("<note><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don't forget me this weekend!</body></note>")
print(type)
xml
```
ちゃんと**html**, **xml**と出ました。
(都合上テキストだけのテストを表示していますが、pngなどbinaryもちゃんと判別できています。)

## 最後に
上で作られたlumagikaは下のgitにも隔離しています。

https://github.com/shypk/lumagika

実際lumagikaで使われているコードはsasankaで公開しています。

https://github.com/cybersecuritycloud/sasanka