Rustでファイル名からContentTypeを決定する

JavaにはURLConnectionにguessContentTypeFromNameというメソッドがあり、ファイル名からContentTypeを決定しようとしてくれます。

Rustには似たようなものがないかと調べていたところ、RustのWebフレームワークであるRocketContentType::from_extensionを使えば同等のことが行えます。自分で書いていると抜けの調査だけでかなりの時間がかかるので、ここは一つこれを利用させてもらって、代わりにテストをしっかりかけば良いかと思います。

use rocket::http::ContentType;

fn main() {
    assert_eq!(ContentType::from_extension("txt"), Some(ContentType::Plain));
    assert_eq!(ContentType::from_extension("html"), Some(ContentType::HTML));
    assert_eq!(ContentType::from_extension("htm"), Some(ContentType::HTML));
    assert_eq!(ContentType::from_extension("xml"), Some(ContentType::XML));
    assert_eq!(ContentType::from_extension("csv"), Some(ContentType::CSV));
    assert_eq!(ContentType::from_extension("js"), Some(ContentType::JavaScript));
    assert_eq!(ContentType::from_extension("css"), Some(ContentType::CSS));
    assert_eq!(ContentType::from_extension("json"), Some(ContentType::JSON));
    assert_eq!(ContentType::from_extension("png"), Some(ContentType::PNG));
    assert_eq!(ContentType::from_extension("gif"), Some(ContentType::GIF));
    assert_eq!(ContentType::from_extension("bmp"), Some(ContentType::BMP));
    assert_eq!(ContentType::from_extension("jpeg"), Some(ContentType::JPEG));
    assert_eq!(ContentType::from_extension("jpg"), Some(ContentType::JPEG));
    assert_eq!(ContentType::from_extension("webp"), Some(ContentType::WEBP));
    assert_eq!(ContentType::from_extension("svg"), Some(ContentType::SVG));
    assert_eq!(ContentType::from_extension("ico"), Some(ContentType::Icon));
    assert_eq!(ContentType::from_extension("flac"), Some(ContentType::FLAC));
    assert_eq!(ContentType::from_extension("wav"), Some(ContentType::WAV));
    assert_eq!(ContentType::from_extension("webm"), Some(ContentType::WEBM));
    assert_eq!(ContentType::from_extension("weba"), Some(ContentType::WEBA));
    assert_eq!(ContentType::from_extension("ogg"), Some(ContentType::OGG));
    assert_eq!(ContentType::from_extension("ogv"), Some(ContentType::OGG));
    assert_eq!(ContentType::from_extension("pdf"), Some(ContentType::PDF));
    assert_eq!(ContentType::from_extension("ttf"), Some(ContentType::TTF));
    assert_eq!(ContentType::from_extension("otf"), Some(ContentType::OTF));
    assert_eq!(ContentType::from_extension("woff"), Some(ContentType::WOFF));
    assert_eq!(ContentType::from_extension("woff2"), Some(ContentType::WOFF2));
    assert_eq!(ContentType::from_extension("mp4"), Some(ContentType::MP4));
    assert_eq!(ContentType::from_extension("mpeg4"), Some(ContentType::MP4));
    assert_eq!(ContentType::from_extension("wasm"), Some(ContentType::WASM));
    assert_eq!(ContentType::from_extension("aac"), Some(ContentType::AAC));
    assert_eq!(ContentType::from_extension("ics"), Some(ContentType::Calendar));
    assert_eq!(ContentType::from_extension("bin"), Some(ContentType::Binary));
    assert_eq!(ContentType::from_extension("mpg"), Some(ContentType::MPEG));
    assert_eq!(ContentType::from_extension("mpeg"), Some(ContentType::MPEG));
    assert_eq!(ContentType::from_extension("tar"), Some(ContentType::TAR));
    assert_eq!(ContentType::from_extension("gz"), Some(ContentType::GZIP));
    assert_eq!(ContentType::from_extension("tif"), Some(ContentType::TIFF));
    assert_eq!(ContentType::from_extension("tiff"), Some(ContentType::TIFF));
    assert_eq!(ContentType::from_extension("mov"), Some(ContentType::MOV));
    assert_eq!(ContentType::from_extension("zip"), Some(ContentType::ZIP));
}

上記はテストって意味ではなくこういう法則でこのようにContentTypeの判定ができましたっていう意味のコードです。ContentType目的でテストを書くならContentType::Hogeの文字列なりを取り出して比較しておくと良いかなと。

[src/main.rs:4] ContentType::from_extension("html") = Some(
    ContentType(
        MediaType {
            source: Known(
                "text/html; charset=utf-8",
            ),
            top: "text",
            sub: "html",
            params: Static(
                [
                    (
                        "charset",
                        "utf-8",
                    ),
                ],
            ),
        },
    ),
)

Rustのコマンドラインパーサstructoptを使う

cargo add structopt

こんな風に書いてコマンドラインから値を受け取ることができます

use structopt::StructOpt;

#[derive(StructOpt, Debug)]
struct Example {
    #[structopt(long)]
    a: String,
    #[structopt(long)]
    b: String,
}

fn main() {
    let example = Example::from_args();
    dbg!(example);
}

下記のように実行することで値が受け渡されていることがわかります

$ cargo run -- --a hoge --b hoge2
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/result --a hoge --b hoge2`
[src/main.rs:13] example = Example {
    a: "hoge",
    b: "hoge2",
}

引数を何も指定しないとこのように必要な引数を教えてくれる

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/result`
error: The following required arguments were not provided:
    --a <a>
    --b <b>

USAGE:
    result --a <a> --b <b>

For more information try --help

余分な引数を指定するとこのようにエラーを返してくれる

$ cargo run -- --a hoge --b hoge2 -c hoge3
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/result --a hoge --b hoge2 -c hoge3`
error: Found argument '-c' which wasn't expected, or isn't valid in this context

USAGE:
    result --a <a> --b <b>

For more information try --help

--help で使い方を表示することができ、下記は拡張できる。これはかなり良い。

$ cargo run -- --help
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/result --help`
result 0.1.0

USAGE:
    result --a <a> --b <b>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
        --a <a>    
        --b <b>

参考

https://qiita.com/dalance/items/56dba0dd54c82f937feb

Rustの疑問符演算子を使いたいが期待するErrorの型が()の場合

?演算子は返り値がResultの時、unwrapを行い、期待するErrorに変換してくれるが、期待するErrorが()などどうしてもFromで実装できないエラーの場合(できたら誰か教えて)は、一旦クロージャの中で展開してからそれをmap_errを経由させることで?を使うことができた。あまりこんなケースなさそうだが。

use std::env;

#[derive(Debug)]
enum Error {
    Example(env::VarError),
}

impl From<env::VarError> for Error {
    fn from(e: env::VarError) -> Self {
        Error::Example(e)
    }
}

fn example() -> Result<Vec<String>, ()> {
    (|| -> Result<_, Error> {
        let a = env::var("HOME")?;
        let b = env::var("USER")?;
        let c = env::var("HOGE")?;
        Ok(vec![a, b, c])
    })()
    .map_err(|_| ())
}

fn main() {
    dbg!(example());
}

Rustのfutures::stream::unfoldで非同期処理を行なう

unfoldというものを見つけた。これは再起処理のようなものを別で定義して呼び出すのではなく、クロージャの中に記述して実行することができる。サンプルではSomeを返す限り繰り返し実行され、Noneを返すことでループを抜けている。上手く使えばコード量を減らせそうだ。

use futures::stream::{self, StreamExt};

#[tokio::main]
async fn main() {

    let stream = stream::unfold(0, |state| async move {
        if state <= 2 {
            let next_state = state + 1;
            let yielded = state * 2;
            Some((yielded, next_state))
        } else {
            None
        }
    });

    assert_eq!(stream.collect::<Vec<i32>>().await, vec![0, 2, 4]);
}

https://doc.rust-lang.org/1.33.0/std/iter/fn.unfold.html

PIXUS TS3130に互換インクを使った場合のインク残量検知無効化方法

ベルカラーの互換インクを使っていたら印刷できなくなって止まった。ストップボタンを長押しすると残量検知を無効化できるそうで、無効化することで問題なく印刷できるようになった。

純正インクBC-310はセット商品で1個あたり2,600円なので、それに比べたらベルカラーのインクは980円で27%増量されているらしいし、こうなると選択肢はベルカラーしかなくなってしまうので、乗り越えなければいけない道のりか。。

参考