調整さんで絶対に回答させるline botを作ってみた【1】

早いものでもう11月ですね。ナナオです。 年末に近づいて忘年会シーズンとなってきました。 ただ忘年会の幹事をするとなると全員の日程調整がめんどくさいじゃないですか。 なかなか日程が合わないということもありますが、日程に関する回答をしてくれない人がいると何も決まりませんよね。 ということで今回は、調整さんで絶対に回答させるline botを作っていこうと思います。 仕様 絶対に回答させるのに効果的な方法ってなんだと思いますか? 丁寧にお願いする?相手を信じて待つ? いいえ違います、相手が回答するまで鬼電するのです。 結局これが一番効くんですよね。 ということで、line botの趣旨としては「鬼電」です。 たださすがに電話はできないので、メッセージをちょうどうざいくらいの間隔で送るようにします。 期日を過ぎてからの経過時間でどんどんメッセージ送信量が増えていくみたいな感じにします。 期日まで 未回答者に1日に一回催促のDMが送られる 期日経過~1日 未回答者に1時間に一回催促のDMが送られる 期日経過1日~2日 未回答者に30分に一回催促のDMが送られる 期日経過3日~ 未回答者に15分に一回催促のDMが送られる 設計 簡単にフローにしてみます。 LINEグループにbotを入れる 期日を設定する 期日を確認(全員回答するまで呼び出し続ける) 回答していない人を確認 回答していない人がいる場合、期日に応じた処理(DM送信)を行う なので、プログラムとしては「期日を設定する」ものと「期日を確認する」ものの二つが必要になります。 呼び出される場面がそれぞれ違います。 期日を設定する line botに対して期日を設定するメッセージが送られたとき 期日を確認する 定期的に呼び出す 15分に一回 回答していない人がいる場合、期日からの経過日数によって処理を分ける 期日設定に関してはMessaging APIのWebhookイベントを使用します。 今回はRustアプリを無料でデプロイ可能なクラウドプラットフォームShuttleを利用します! Shuttle - Build Backends Fast Shuttleはよくわからなかったので、事前にちょっと触りつつ予習しておきました。 よければこちらの記事もぜひ。 Rustのshuttleを使ってみる【1】 Rustのshuttleを使ってみる【2】 Rustのshuttleを使ってみる【3】 調整さんから回答していない人の取得実装は、参考になるブログを見つけました。 調整さんリマインダLINE BOTを作ってみた - やらなイカ? どうやら、調整さんのスケジュールIDが分かれば日にち候補をCSV形式でダウンロードができるようです。 (APIキーとか使わなくてもいいのはセキュリティ的にちょっと気になるけど) これを使って「参加者の誰が回答していないか」を取得し、未回答者がいる場合は適切な制裁を加えていきます。 実装のセットアップ まずはLine Messaging APIのチャネルを作成しましょう。 LINE Developers これがline botのベースになります。 ...

2023年11月1日 · にあえん

Windowsでexaを使う

最近メインPCをWindows11にしました。ナナオです。 cargoなどの初期セットアップがだいぶ終わって作業しやすくなってきましたが、lsの高速版であるexaを入れようとしたら詰まったので備忘録を残しておこうと思います。 exaのインストール Windows以外であれば以下のコマンドでインストールは終わります。 cargo install exa ただし、exaはまだ正式にWindows版をリリースしていないため、同じ事をwindowsでやるとエラーになります。 (以下は実際のエラー) > cargo install exa Updating crates.io index Downloaded exa v0.10.1 Downloaded 1 crate (136.6 KB) in 1.23s Installing exa v0.10.1 Updating crates.io index Downloaded number_prefix v0.4.0 Downloaded users v0.11.0 Downloaded term_size v0.3.2 Downloaded byteorder v1.5.0 Downloaded ansi_term v0.12.1 Downloaded scoped_threadpool v0.1.9 Downloaded jobserver v0.1.27 Downloaded libgit2-sys v0.12.26+1.3.0 Downloaded natord v1.0.9 Downloaded term_grid v0.1.7 Downloaded zoneinfo_compiled v0.5.1 Downloaded locale v0.2.2 Downloaded pad v0.1.6 Downloaded datetime v0.5.2 Downloaded git2 v0.13.25 Downloaded 15 crates (1.9 MB) in 1.29s (largest was `libgit2-sys` at 1.5 MB) Compiling libc v0.2.149 Compiling winapi v0.3.9 Compiling jobserver v0.1.27 Compiling pkg-config v0.3.27 Compiling vcpkg v0.2.15 Compiling tinyvec_macros v0.1.1 Compiling unicode-width v0.1.11 Compiling unicode-bidi v0.3.13 Compiling percent-encoding v2.3.0 Compiling log v0.4.20 Compiling bitflags v1.3.2 Compiling byteorder v1.5.0 Compiling glob v0.3.1 Compiling num_cpus v1.16.0 Compiling number_prefix v0.4.0 Compiling natord v1.0.9 Compiling scoped_threadpool v0.1.9 Compiling lazy_static v1.4.0 Compiling tinyvec v1.6.0 Compiling pad v0.1.6 Compiling term_grid v0.1.7 Compiling form_urlencoded v1.2.0 Compiling cc v1.0.83 Compiling locale v0.2.2 Compiling users v0.11.0 error[E0433]: failed to resolve: could not find `unix` in `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:36:14 | 36 | use std::os::unix::ffi::OsStrExt; | ^^^^ could not find `unix` in `os` error[E0432]: unresolved imports `libc::uid_t`, `libc::gid_t` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\lib.rs:128:16 | 128 | pub use libc::{uid_t, gid_t}; | ^^^^^ ^^^^^ no `gid_t` in the root | | | no `uid_t` in the root error[E0432]: unresolved imports `libc::uid_t`, `libc::gid_t` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:45:20 | 45 | use libc::{c_char, uid_t, gid_t, c_int}; | ^^^^^ ^^^^^ no `gid_t` in the root | | | no `uid_t` in the root | = help: consider importing this unresolved item through its public re-export instead: uid_t = help: consider importing this unresolved item through its public re-export instead: gid_t error[E0432]: unresolved import `libc::passwd` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:46:5 | 46 | use libc::passwd as c_passwd; | ^^^^^^^^^^^^^^^^^^^^^^^^ no `passwd` in the root error[E0432]: unresolved import `libc::group` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:47:5 | 47 | use libc::group as c_group; | ^^^^^^^^^^^^^^^^^^^^^^ no `group` in the root error[E0432]: unresolved imports `libc::uid_t`, `libc::gid_t` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\cache.rs:87:12 | 87 | use libc::{uid_t, gid_t}; | ^^^^^ ^^^^^ no `gid_t` in the root | | | no `uid_t` in the root | = help: consider importing this unresolved item through its public re-export instead: uid_t = help: consider importing this unresolved item through its public re-export instead: gid_t error[E0432]: unresolved imports `libc::uid_t`, `libc::gid_t` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\mock.rs:63:16 | 63 | pub use libc::{uid_t, gid_t}; | ^^^^^ ^^^^^ no `gid_t` in the root | | | no `uid_t` in the root | = help: consider importing this unresolved item through its public re-export instead: uid_t = help: consider importing this unresolved item through its public re-export instead: gid_t error[E0432]: unresolved imports `libc::uid_t`, `libc::gid_t` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\switch.rs:4:12 | 4 | use libc::{uid_t, gid_t, c_int}; | ^^^^^ ^^^^^ no `gid_t` in the root | | | no `uid_t` in the root | = help: consider importing this unresolved item through its public re-export instead: uid_t = help: consider importing this unresolved item through its public re-export instead: gid_t error[E0432]: unresolved imports `libc::uid_t`, `libc::gid_t` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\traits.rs:4:12 | 4 | use libc::{uid_t, gid_t}; | ^^^^^ ^^^^^ no `gid_t` in the root | | | no `uid_t` in the root | = help: consider importing this unresolved item through its public re-export instead: uid_t = help: consider importing this unresolved item through its public re-export instead: gid_t error[E0433]: failed to resolve: could not find `UserExtras` in `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:78:26 | 78 | let extras = os::UserExtras::default(); | ^^^^^^^^^^ could not find `UserExtras` in `os` error[E0433]: failed to resolve: could not find `GroupExtras` in `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:192:26 | 192 | let extras = os::GroupExtras::default(); | ^^^^^^^^^^^ could not find `GroupExtras` in `os` error[E0433]: failed to resolve: could not find `UserExtras` in `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:266:28 | 266 | extras: os::UserExtras::from_passwd(passwd), | ^^^^^^^^^^ could not find `UserExtras` in `os` error[E0433]: failed to resolve: could not find `GroupExtras` in `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:280:23 | 280 | extras: os::GroupExtras::from_struct(group), | ^^^^^^^^^^^ could not find `GroupExtras` in `os` error[E0412]: cannot find type `UserExtras` in module `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:57:17 | 57 | extras: os::UserExtras, | ^^^^^^^^^^ not found in `os` | note: found an item that was configured out --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:1176:14 | 1176 | pub type UserExtras = bsd::UserExtras; | ^^^^^^^^^^ note: found an item that was configured out --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:1180:14 | 1180 | pub type UserExtras = unix::UserExtras; | ^^^^^^^^^^ error[E0412]: cannot find type `GroupExtras` in module `os` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:171:17 | 171 | extras: os::GroupExtras, | ^^^^^^^^^^^ not found in `os` | note: found an item that was configured out --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:1184:14 | 1184 | pub type GroupExtras = unix::GroupExtras; | ^^^^^^^^^^^ error[E0425]: cannot find function `getpwuid_r` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:336:19 | 336 | libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result) | ^^^^^^^^^^ not found in `libc` error[E0425]: cannot find function `getpwnam_r` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:398:19 | 398 | libc::getpwnam_r(username.as_ptr(), &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result) | ^^^^^^^^^^ not found in `libc` error[E0425]: cannot find function `getgrgid_r` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:451:19 | 451 | libc::getgrgid_r(gid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result) | ^^^^^^^^^^ not found in `libc` error[E0425]: cannot find function `getgrnam_r` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:513:19 | 513 | libc::getgrnam_r(groupname.as_ptr(), &mut group, buf.as_mut_ptr(), buf.len(), &mut result) | ^^^^^^^^^^ not found in `libc` error[E0425]: cannot find function `getuid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:556:20 | 556 | unsafe { libc::getuid() } | ^^^^^^ help: a function with a similar name exists: `getpid` | ::: C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\libc-0.2.149\src\windows\mod.rs:491:5 | 491 | pub fn getpid() -> ::c_int; | -------------------------- similarly named function `getpid` defined here error[E0425]: cannot find function `geteuid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:603:20 | 603 | unsafe { libc::geteuid() } | ^^^^^^^ help: a function with a similar name exists: `getpid` | ::: C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\libc-0.2.149\src\windows\mod.rs:491:5 | 491 | pub fn getpid() -> ::c_int; | -------------------------- similarly named function `getpid` defined here error[E0425]: cannot find function `getgid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:647:20 | 647 | unsafe { libc::getgid() } | ^^^^^^ help: a function with a similar name exists: `getpid` | ::: C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\libc-0.2.149\src\windows\mod.rs:491:5 | 491 | pub fn getpid() -> ::c_int; | -------------------------- similarly named function `getpid` defined here error[E0425]: cannot find function `getegid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:691:20 | 691 | unsafe { libc::getegid() } | ^^^^^^^ help: a function with a similar name exists: `getpid` | ::: C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\libc-0.2.149\src\windows\mod.rs:491:5 | 491 | pub fn getpid() -> ::c_int; | -------------------------- similarly named function `getpid` defined here error[E0425]: cannot find function `getgroups` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:745:15 | 745 | libc::getgroups(1024, buff.as_mut_ptr()) | ^^^^^^^^^ not found in `libc` error[E0425]: cannot find value `buff` in this scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:783:21 | 783 | let mut count = buff.len() as c_int; | ^^^^ not found in this scope error[E0425]: cannot find value `res` in this scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:799:8 | 799 | if res < 0 { | ^^^ not found in this scope error[E0425]: cannot find value `buff` in this scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:803:9 | 803 | buff.dedup(); | ^^^^ not found in this scope error[E0425]: cannot find value `buff` in this scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:804:9 | 804 | buff.into_iter() | ^^^^ not found in this scope error[E0425]: cannot find function `setpwent` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:858:11 | 858 | libc::setpwent(); | ^^^^^^^^ not found in `libc` error[E0425]: cannot find function `endpwent` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:873:24 | 873 | unsafe { libc::endpwent() }; | ^^^^^^^^ not found in `libc` error[E0425]: cannot find function `getpwent` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:890:37 | 890 | let result = unsafe { libc::getpwent() }; | ^^^^^^^^ not found in `libc` error[E0425]: cannot find function `setuid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\switch.rs:41:26 | 41 | match unsafe { libc::setuid(uid) } { | ^^^^^^ help: a function with a similar name exists: `getpid` | ::: C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\libc-0.2.149\src\windows\mod.rs:491:5 | 491 | pub fn getpid() -> ::c_int; | -------------------------- similarly named function `getpid` defined here error[E0425]: cannot find function `setgid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\switch.rs:72:26 | 72 | match unsafe { libc::setgid(gid) } { | ^^^^^^ help: a function with a similar name exists: `getpid` | ::: C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\libc-0.2.149\src\windows\mod.rs:491:5 | 491 | pub fn getpid() -> ::c_int; | -------------------------- similarly named function `getpid` defined here error[E0425]: cannot find function `seteuid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\switch.rs:103:26 | 103 | match unsafe { libc::seteuid(uid) } { | ^^^^^^^ not found in `libc` error[E0425]: cannot find function `setegid` in crate `libc` --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\switch.rs:134:26 | 134 | match unsafe { libc::setegid(gid) } { | ^^^^^^^ not found in `libc` error[E0599]: no function or associated item named `from_bytes` found for struct `OsStr` in the current scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:252:20 | 252 | T::from(OsStr::from_bytes(CStr::from_ptr(p).to_bytes())) | ^^^^^^^^^^ function or associated item not found in `OsStr` error[E0599]: no method named `as_bytes` found for reference `&OsStr` in the current scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:380:57 | 380 | let username = match CString::new(username.as_ref().as_bytes()) { | ^^^^^^^^ method not found in `&OsStr` Compiling unicode-normalization v0.1.22 error[E0599]: no method named `as_bytes` found for reference `&OsStr` in the current scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:495:59 | 495 | let groupname = match CString::new(groupname.as_ref().as_bytes()) { | ^^^^^^^^ method not found in `&OsStr` error[E0599]: no method named `as_bytes` found for reference `&OsStr` in the current scope --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\base.rs:782:47 | 782 | let name = CString::new(username.as_ref().as_bytes()).unwrap(); | ^^^^^^^^ method not found in `&OsStr` error[E0282]: type annotations needed --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\cache.rs:207:43 | 207 | Occupied(e) => return e.get().as_ref().map(Arc::clone), | ^^^^^^ cannot infer type for type parameter `V` error[E0282]: type annotations needed --> C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\users-0.11.0\src\cache.rs:288:43 | 288 | Occupied(e) => return e.get().as_ref().map(Arc::clone), | ^^^^^^ cannot infer type for type parameter `V` Some errors have detailed explanations: E0282, E0412, E0425, E0432, E0433, E0599. For more information about an error, try `rustc --explain E0282`. error: could not compile `users` (lib) due to 41 previous errors warning: build failed, waiting for other jobs to finish... error: failed to compile `exa v0.10.1`, intermediate artifacts can be found at `C:\Users\Nanao\AppData\Local\Temp\cargo-installiJkMjV`. To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path. C:\Users\Nanao\Project\study\proc-macro-workshop\builder>cargo install libc Updating crates.io index error: there is nothing to install in `libc v0.2.149`, because it has no binaries `cargo install` is only for installing programs, and can't be used with libraries. To use a library crate, add it as a dependency to a Cargo project with `cargo add`. とまあこんな感じなので、バックドア的な方法でインストールしてあげる必要があります。 ...

2023年10月29日 · にあえん

「proc-macro-workshop」で学ぶ手続き型マクロ

Rustにハマりつつあるナナオです。 最近Rustばかり触っているのですが、Rustはpythonと比べるととにかくコーディングに時間がかかってしまうのが欠点だなと思うこの頃… 自前の構造体とか作って運用してると、気をつけないと同じよーな処理がプロジェクト内に点在してしまいがちだなと感じています。 (deriveを活用できていないケースとか) そこで今回はRustのメタプログラミングツール、マクロについて学んでいこうと思います。 -> 僕がマクロを学ぶきっかけになった記事はこちら 前提知識となるマクロの参考資料は以下。 マクロ - The Rust Programming Language 日本語版 Rustのマクロを覚える #Rust - Qiita マクロのチュートリアルを実践する マクロを学ぶためのちょうどいいリポジトリがありました。 GitHub - dtolnay/proc-macro-workshop: Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019] マクロを使用するパターンについて、いくつかのケーススタディが用意されています。 bitfield builder debug seq sorted とりあえず聞き馴染みのあるbuilderからやってみます。 9つテストケースが用意されており、一つづつパスできるようにしていくことで、段階的にBuilderマクロを実装できるという内容になっています。 わかりやすい。。 ここからは自分の備忘録も兼ねて答えとなるコードを載せていきます。 もし自分で解きたいと言う場合はこれから先の閲覧は注意してください。 01-parse.rs これはとりあえずTokenStreamを返却できればいいだけですね。 必要なライブラリを追加してあげます。 cargo add syn quote とりあえずquote!マクロの結果を返却するだけでこのテストは通ります。 use proc_macro::TokenStream; use quote::quote; #[proc_macro_derive(Builder)] pub fn derive(input: TokenStream) -> TokenStream { let _ = input; let q = quote! {} q.into() } 02-create-builder.rs さて、ここからが本番です。 ...

2023年10月10日 · にあえん

Rustのfactorybot的なライブラリ「factori」とsqlxでモックデータを生成する【1】

pythonの単体テストでよく使っていたfactoryboyというツールがあるのですが、これが本当に使いやすい! factory_boy — Factory Boy stable documentation これとfixtureを使えば、大抵のテストケースに必要なデータを引数から取得することができます。 で、これと同じようなことをRustでもやりたいな〜と思っていたんですが、「factori」というライブラリがそれに相当するということで、備忘録を兼ねて触った感想などをここに書いていこうと思います。 使い方 とりあえずCargoパッケージに追加します。 [dependencies] factori = { version = "1.1.0" } fake = "2.8.0" uuid = { version = "1.4.1", features = [ "serde", "v4" ] } 使い方はメチャクチャ簡単で、定義された構造体にfactori!マクロを定義してあげれば、create!マクロから呼び出すことができます。 fakeというライブラリと組み合わせてテストデータを作る実装は以下のようになります。 use factori::{factori, create}; use fake::Fake; use fake::faker::name::raw::*; use fake::locales::JA_JP; use uuid::Uuid; #[derive(Debug)] pub struct User { id: Uuid, name: String, } factori!(User, { default { id = Uuid::new_v4(), name = Name(JA_JP).fake::<String>(), } }); fn main() { // Userを10人作成する let user: Vec<User> = [0; 10].iter().map(|_| create!(User)).collect(); println!("{:?}", user); } これだけでUserをちゃちゃっと作る実装ができました。 ...

2023年10月7日 · にあえん

RustでAsync Dropができないか調べた

Rust、最近使い出しました。 テストを書くにあたって、テスト用のデータベース作成と削除をうまく実装できないか考えていて、調べるとDropというトレイトが使えるとわかりました。 ただ、Dropはasyncに対応していないので、どういう代替案があるかな〜と気になったので調べてみました。 そもそもDropトレイトとは? dropメソッドのみが定義されており、ここに実装を入れるとオブジェクトがメモリから開放される際に勝手に呼び出されるという仕組みです。 メモリ解放 - Rust By Example 日本語版 async-dropper 調べてみたら一応メジャーリリースしているasyncに対応したDropトレイトがありました。 async-dropper ちょっと使ってみましょう。 以下のようにパッケージを追加してあげます。 [dependencies] async-dropper = { version = "0.2.3", features = [ "tokio" ] } async-trait = "0.1.73" sqlx = { version = "0.7", features = [ "runtime-tokio", "postgres" ] } 普段からtokioを使っているので、そのfeaturesも入れてあげます。 tokioの対応バージョンは^1.29.1からとなっているので、比較的新しいバージョンじゃないと動かないようになってますね。 async-traitはトレイトにasyncメソッドを定義できるようになるマクロで、async-dropperを使用する際に役立つので入れておきます。 データベースの作成・削除に使用するためSQLXも入れておきます。 ついでにdocker-composeで使えるpostgresサーバーも作っておきましょう。 version: '3' services: db: image: postgres:15 container_name: postgres ports: - 5432:5432 environment: - POSTGRES_PASSWORD=example 次に実装です。 とりあえずデータベース作成をランダム文字列で行うような実装をしました。 use sqlx::postgres::{PgPool, PgPoolOptions}; use rand::{Rng}; const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; const DBNAME_LEN: usize = 30; struct MockDBServer { pool: PgPool, } impl MockDBServer { async fn new() -> Self { let mut rng = rand::thread_rng(); let dbname: String = (0..DBNAME_LEN) .map(|_| { let idx = rng.gen_range(0..CHARSET.len()); char::from(unsafe { *CHARSET.get_unchecked(idx) }) }) .collect(); let pool = PgPoolOptions::new() .max_connections(5) .connect("postgres://postgres:example@localhost/postgres").await.unwrap(); sqlx::query(format!("CREATE DATABASE {};", dbname).as_str()) .bind(dbname) .execute(&pool).await.unwrap(); Self { pool } } } #[tokio::main] async fn main() { MockDBServer::new().await; } これを実行するとデータベースが作成されます。ただ作成されっぱなしになってしまいます。 ...

2023年10月4日 · にあえん

Windows11でRust使ってたら「error failed to run custom build command for `openssl sys v0.9.93`」がでたとき

新しく使いはじめたWindows11でcargo buildしたらこんなエラーが出ました。 error: failed to run custom build command for `openssl-sys v0.9.93` Caused by: process didn't exit successfully: `C:\Users\Nanao\Project\tools\sync-zaim\target\debug\build\openssl-sys-18a1f426c8f5f296\build-script-main` (exit code: 101) --- stdout cargo:rerun-if-env-changed=X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR unset cargo:rerun-if-env-changed=OPENSSL_LIB_DIR OPENSSL_LIB_DIR unset cargo:rerun-if-env-changed=X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR unset cargo:rerun-if-env-changed=OPENSSL_INCLUDE_DIR OPENSSL_INCLUDE_DIR unset cargo:rerun-if-env-changed=X86_64_PC_WINDOWS_MSVC_OPENSSL_DIR X86_64_PC_WINDOWS_MSVC_OPENSSL_DIR unset cargo:rerun-if-env-changed=OPENSSL_DIR OPENSSL_DIR unset note: vcpkg did not find openssl: Could not look up details of packages in vcpkg tree could not read status file updates dir: 指定されたパスが見つかりません。 (os error 3) --- stderr thread 'main' panicked at ' Could not find directory of OpenSSL installation, and this `-sys` crate cannot proceed without this knowledge. If OpenSSL is installed and this crate had trouble finding it, you can set the `OPENSSL_DIR` environment variable for the compilation process. Make sure you also have the development packages of openssl installed. For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora. If you're in a situation where you think the directory *should* be found automatically, please open a bug at https://github.com/sfackler/rust-openssl and include information about your system as well as this message. $HOST = x86_64-pc-windows-msvc $TARGET = x86_64-pc-windows-msvc openssl-sys = 0.9.93 It looks like you're compiling for MSVC but we couldn't detect an OpenSSL installation. If there isn't one installed then you can try the rust-openssl README for more information about how to download precompiled binaries of OpenSSL: https://github.com/sfackler/rust-openssl#windows ', C:\Users\Nanao\.cargo\registry\src\index.crates.io-6f17d22bba15001f\openssl-sys-0.9.93\build\find_normal.rs:190:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace warning: build failed, waiting for other jobs to finish... OpenSSLが存在しないから出てるエラーっぽい? ...

2023年10月2日 · にあえん

cargo buildしたら「error: linker `link.exe` not found」が出たとき

そのまんまなタイトルですが、備忘録のために解決方法を記載しておきます。 Windows11でRustの開発中、cargo buildしたらこんな感じでエラーが出ました。 error: linker `link.exe` not found | = note: program not found note: the msvc targets depend on the msvc linker but `link.exe` was not found note: please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. note: VS Code is a different product, and is not sufficient. wingetコマンドでビルドツールをインストールしてあげましょう。 winget install Microsoft.VisualStudio.2022.BuildTools これが終わったら、Visual Studio Installerを起動します。 インストール済みのところに先ほどインストールしたビルドツールが表示されています。 ここの変更を押します。 ...

2023年10月1日 · にあえん

RustでZaimと東京ガスを連携させてみた【1】

最近、昨年の医療費控除を受けるために確定申告の準備などをしているのですが、家計簿ソフトで利用しているZaimにガスや水道代が勝手に計上されてくれると便利だな〜と思い、Zaim APIとSeleniumを使って実現してみようと思います。 Zaim Developers Center 東京ガスのCSVファイルをダウンロードする Rustのheadless-chromeを使って構築します。 とりあえずCargoパッケージ作ってライブラリを追加します。 cargo new sync-zaim cargo add headless-chrome 追加できました。 使い勝手を知るために、まずはGoogleにアクセスしてスクショをとってみます。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use std::fs; fn main() { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); tab.navigate_to("https://www.google.com/").unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); } 実行すると、Googleの画面のスクショが保存されました。 この調子で東京ガスのCSVを取得できるようにしましょう。 コードはpythonですがここを参考にします。 Python(Selenium)で東京ガスのcsvをダウンロード|gadgetking 実行すると少しエラーは出ますが、ファイルダウンロードは可能になりました。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use headless_chrome::protocol::cdp::types::Method; use std::sync::Arc; use std::{env, thread, time, error::Error}; use std::{fs, path}; use serde::Serialize; const TOKYO_GAS_LOGIN_URL: &str = "https://members.tokyo-gas.co.jp/login.html"; const GAS_ID: &str = "東京ガスのログインID"; const GAS_PASSWD: &str = "東京ガスのログインパスワード"; // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/187 #[derive(Serialize, Debug)] struct Command { behavior: &'static str, downloadPath: &'static str, } impl Method for Command { const NAME: &'static str = "Page.setDownloadBehavior"; type ReturnObject = serde_json::Value; } fn main() -> Result<(), Box<dyn Error>> { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/280 tab.enable_request_interception(Arc::new(|transport, session_id, params| { println!("request!"); println!("transport: {:?}", transport); println!("session_id: {:?}", session_id); println!("params: {:?}", params); headless_chrome::browser::tab::RequestPausedDecision::Continue(None) }))?; tab.navigate_to(TOKYO_GAS_LOGIN_URL).unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); tab.wait_for_element("#loginId").unwrap().type_into(GAS_ID).unwrap(); tab.wait_for_element("#password").unwrap().type_into(GAS_PASSWD).unwrap(); tab.wait_for_element("#submit-btn").unwrap().click().unwrap(); // tab.wait_until_navigated().unwrap(); thread::sleep(time::Duration::from_secs(5)); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); let path_string = env::current_dir()?.into_os_string().into_string().unwrap(); // println!("pwd: {}", path_string); let static_path_str: &'static str = Box::leak(path_string.into_boxed_str()); let command = Command { behavior: "allow", downloadPath: static_path_str, }; tab.call_method(command)?; // CSVファイルをダウンロード tab.navigate_to("https://members.tokyo-gas.co.jp/api/mieru/chargeAmountCsv.jsp?no=0&target=total") .unwrap(); thread::sleep(time::Duration::from_secs(30)); Ok(()) } 実行すると以下のようなエラーが出ます。 ...

2023年9月10日 · にあえん

Rustで100本ノック【1】

ろくにRustに触らないままでwasmをやってしまっていましたが、ちゃんと触っておこうと思って100本ノックに挑みました。 言語処理100本ノック 2015 その際の備忘録を残します。 Q00: 文字列の逆順 逆順にするだけなのに、結局ググってしまった。。 Conversion between String, str, Vec<u8>, Vec<char> in Rust · GitHub fn main() { // 文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ. let s = "stressed"; let mut s = s.chars().collect::<Vec<_>>(); s.reverse(); print!("{}", s.iter().collect::<String>()); } collectの使い方とか、pythonみたいに容易に行かない感じがする。 Q01 さっきよりかはスムーズにできた。 でもイテレータの使い方とかちょっとググった。 fn main() { // 「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ. let s = "パタトクカシーー"; for (i, c) in s.chars().enumerate() { if !([1,3,5,7].contains(&i)) { continue; } print!("{}", c); } println!(); } Q02 基本的に先程の2問と解き方は一緒だった。 fn main() { // 「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ. let s1 = "パトカー"; let s2 = "タクシー"; let mut s1_vec = s1.chars().collect::<Vec<_>>(); let mut s2_vec = s2.chars().collect::<Vec<_>>(); s1_vec.reverse(); s2_vec.reverse(); let mut result: Vec<char> = vec![]; while s1_vec.len() > 0 && s2_vec.len() > 0 { if s1_vec.len() > 0 { result.push(s1_vec.pop().unwrap()); } if s2_vec.len() > 0 { result.push(s2_vec.pop().unwrap()); } } println!("{}", result.iter().collect::<String>()); } Q03 前の問題に比べると少し楽になりました。 ...

2023年8月20日 · にあえん

オセロゲームなどで自分のコマの周りがどうなっているかを取得する

wasmのチュートリアルを実施した際に見つけたやつ。 詳しくはこちら fn live_neighbor_count(&self, row: u32, column: u32) -> u8 { let mut count = 0; for delta_row in [self.height - 1, 0, 1].iter().cloned() { for delta_col in [self.width - 1, 0, 1].iter().cloned() { if delta_row == 0 && delta_col == 0 { continue; } let neighbor_row = (row + delta_row) % self.height; let neighbor_col = (column + delta_col) % self.width; let idx = self.get_index(neighbor_row, neighbor_col); count += self.cells[idx] as u8; } } count } パフォーマンス改良版はこれ。 (こっちはこの記事を参考) fn live_neighbor_count(&self, row: u32, column: u32) -> u8 { let mut count = 0; let north = if row == 0 { self.height - 1 } else { row - 1 }; let south = if row == self.height - 1 { 0 } else { row + 1 }; let west = if column == 0 { self.width - 1 } else { column - 1 }; let east = if column == self.width - 1 { 0 } else { column + 1 }; let nw = self.get_index(north, west); count += self.cells[nw] as u8; let n = self.get_index(north, column); count += self.cells[n] as u8; let ne = self.get_index(north, east); count += self.cells[ne] as u8; let w = self.get_index(row, west); count += self.cells[w] as u8; let e = self.get_index(row, east); count += self.cells[e] as u8; let sw = self.get_index(south, west); count += self.cells[sw] as u8; let s = self.get_index(south, column); count += self.cells[s] as u8; let se = self.get_index(south, east); count += self.cells[se] as u8; count }

2023年8月19日 · にあえん