305 lines
7.5 KiB
Markdown
305 lines
7.5 KiB
Markdown
|
+++
|
||
|
date = "2023-01-02"
|
||
|
tags = ["rust"]
|
||
|
title = "rustでdeeplのcliを作った"
|
||
|
slug = "rust-json-request"
|
||
|
+++
|
||
|
|
||
|
deeplは翻訳サービスです。
|
||
|
|
||
|
https://www.deepl.com/ja/docs-api
|
||
|
|
||
|
今回は、主にrustのjson, requestのexampleを載せる趣旨で書きます。
|
||
|
|
||
|
### cargo
|
||
|
|
||
|
面倒なので最初に`cargo.toml`に書きそうなpkgを載せておきます。
|
||
|
|
||
|
```rust:Cargo.toml
|
||
|
seahorse = "*"
|
||
|
dotenv = "0.15"
|
||
|
serde_derive = "1.0"
|
||
|
serde_json = "1.0"
|
||
|
serde = "*"
|
||
|
config = { git = "https://github.com/mehcode/config-rs", branch = "master" }
|
||
|
shellexpand = "*"
|
||
|
toml = "*"
|
||
|
reqwest = "*"
|
||
|
tokio = { version = "1", features = ["full"] }
|
||
|
```
|
||
|
|
||
|
### json
|
||
|
|
||
|
|
||
|
```json:~/.config/msr/deepl.json
|
||
|
{
|
||
|
"translations": [
|
||
|
{
|
||
|
"detected_source_language": "JA",
|
||
|
"text": "I'm not sure if I should use reqwest for response as well."
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
`serde_json`を使います。
|
||
|
|
||
|
```rust:src/main.rs
|
||
|
use serde::{Deserialize, Serialize};
|
||
|
use std::fs;
|
||
|
|
||
|
#[derive(Serialize, Deserialize, Debug)]
|
||
|
#[serde(tag = "type")]
|
||
|
struct DeepData {
|
||
|
translations: Vec<Translation>,
|
||
|
}
|
||
|
|
||
|
#[derive(Serialize, Deserialize, Debug)]
|
||
|
struct Translation {
|
||
|
text: String,
|
||
|
detected_source_language : String,
|
||
|
}
|
||
|
|
||
|
fn main() {
|
||
|
let l = shellexpand::tilde("~") + "/.config/msr/deepl.json";
|
||
|
let l = l.to_string();
|
||
|
let o = fs::read_to_string(&l).expect("could not read file");
|
||
|
|
||
|
let p: DeepData = serde_json::from_str(&o).unwrap();
|
||
|
let o = &p.translations[0].text;
|
||
|
|
||
|
println!("{}", o);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### reqwest
|
||
|
|
||
|
`reqwest`を使います。
|
||
|
|
||
|
```rust:src/main.rs
|
||
|
#[tokio::main]
|
||
|
async fn deepl(message: String,lang: String) -> reqwest::Result<()> {
|
||
|
let data = Deeps::new().unwrap();
|
||
|
let data = Deeps {
|
||
|
api: data.api,
|
||
|
};
|
||
|
let api = "DeepL-Auth-Key ".to_owned() + &data.api;
|
||
|
let mut params = HashMap::new();
|
||
|
params.insert("text", &message);
|
||
|
params.insert("target_lang", &lang);
|
||
|
let client = reqwest::Client::new();
|
||
|
let res = client
|
||
|
.post("https://api-free.deepl.com/v2/translate")
|
||
|
.header(AUTHORIZATION, api)
|
||
|
.header(CONTENT_TYPE, "json")
|
||
|
.form(¶ms)
|
||
|
.send()
|
||
|
.await?
|
||
|
.text()
|
||
|
.await?;
|
||
|
let p: DeepData = serde_json::from_str(&res).unwrap();
|
||
|
let o = &p.translations[0].text;
|
||
|
//println!("{}", res);
|
||
|
println!("{}", o);
|
||
|
Ok(())
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### cli:trans-rs
|
||
|
|
||
|
以下は全体のcliの作りです。cli-toolとして動きます。
|
||
|
|
||
|
```sh
|
||
|
$ mkdir -p ~/.config/msr
|
||
|
$ export api="xxx"
|
||
|
|
||
|
$ trans-rs a $api
|
||
|
$ trans-rs tt "テスト" -l en
|
||
|
$ trans-rs tt "test" -l ja
|
||
|
```
|
||
|
|
||
|
```rust:src/data.rs
|
||
|
use config::{Config, ConfigError, File};
|
||
|
use serde_derive::Deserialize;
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
#[allow(unused)]
|
||
|
pub struct Deep {
|
||
|
pub api: String,
|
||
|
}
|
||
|
|
||
|
impl Deep {
|
||
|
pub fn new() -> Result<Self, ConfigError> {
|
||
|
let d = shellexpand::tilde("~") + "/.config/msr/deepl.toml";
|
||
|
let s = Config::builder()
|
||
|
.add_source(File::with_name(&d))
|
||
|
.add_source(config::Environment::with_prefix("APP"))
|
||
|
.build()?;
|
||
|
s.try_deserialize()
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```rust:src/main.rs
|
||
|
pub mod data;
|
||
|
use std::env;
|
||
|
use std::fs;
|
||
|
use std::io::prelude::*;
|
||
|
use data::Deep as Deeps;
|
||
|
use seahorse::{App, Command, Context, Flag, FlagType};
|
||
|
use serde::{Deserialize, Serialize};
|
||
|
use reqwest::header::AUTHORIZATION;
|
||
|
use reqwest::header::CONTENT_TYPE;
|
||
|
use std::collections::HashMap;
|
||
|
|
||
|
#[derive(Serialize, Deserialize, Debug)]
|
||
|
#[serde(tag = "type")]
|
||
|
struct DeepData {
|
||
|
translations: Vec<Translation>,
|
||
|
}
|
||
|
|
||
|
#[derive(Serialize, Deserialize, Debug)]
|
||
|
struct Translation {
|
||
|
text: String,
|
||
|
detected_source_language : String,
|
||
|
}
|
||
|
|
||
|
fn main() {
|
||
|
let args: Vec<String> = env::args().collect();
|
||
|
let app = App::new(env!("CARGO_PKG_NAME"))
|
||
|
.author(env!("CARGO_PKG_AUTHORS"))
|
||
|
.description(env!("CARGO_PKG_DESCRIPTION"))
|
||
|
.version(env!("CARGO_PKG_VERSION"))
|
||
|
.usage("trans-rs [option] [x]")
|
||
|
.command(
|
||
|
Command::new("translate")
|
||
|
.usage("trans-rs tt {}")
|
||
|
.description("translate message, ex: $ trans-rs tt $text -l en")
|
||
|
.alias("tt")
|
||
|
.action(tt)
|
||
|
.flag(
|
||
|
Flag::new("lang", FlagType::String)
|
||
|
.description("Lang flag")
|
||
|
.alias("l"),
|
||
|
)
|
||
|
)
|
||
|
.command(
|
||
|
Command::new("api")
|
||
|
.usage("trans-rs a {}")
|
||
|
.description("api change, ex : $ msr a $api")
|
||
|
.alias("a")
|
||
|
.action(a),
|
||
|
)
|
||
|
;
|
||
|
app.run(args);
|
||
|
}
|
||
|
|
||
|
#[tokio::main]
|
||
|
async fn deepl(message: String,lang: String) -> reqwest::Result<()> {
|
||
|
let data = Deeps::new().unwrap();
|
||
|
let data = Deeps {
|
||
|
api: data.api,
|
||
|
};
|
||
|
let api = "DeepL-Auth-Key ".to_owned() + &data.api;
|
||
|
let mut params = HashMap::new();
|
||
|
params.insert("text", &message);
|
||
|
params.insert("target_lang", &lang);
|
||
|
let client = reqwest::Client::new();
|
||
|
let res = client
|
||
|
.post("https://api-free.deepl.com/v2/translate")
|
||
|
.header(AUTHORIZATION, api)
|
||
|
.header(CONTENT_TYPE, "json")
|
||
|
.form(¶ms)
|
||
|
.send()
|
||
|
.await?
|
||
|
.text()
|
||
|
.await?;
|
||
|
let p: DeepData = serde_json::from_str(&res).unwrap();
|
||
|
let o = &p.translations[0].text;
|
||
|
//println!("{}", res);
|
||
|
println!("{}", o);
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[allow(unused_must_use)]
|
||
|
fn tt(c: &Context) {
|
||
|
let m = c.args[0].to_string();
|
||
|
if let Ok(lang) = c.string_flag("lang") {
|
||
|
deepl(m,lang.to_string());
|
||
|
} else {
|
||
|
let lang = "ja";
|
||
|
deepl(m,lang.to_string());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[allow(unused_must_use)]
|
||
|
fn a(c: &Context) {
|
||
|
let api = c.args[0].to_string();
|
||
|
let o = "api='".to_owned() + &api.to_string() + &"'".to_owned();
|
||
|
let o = o.to_string();
|
||
|
let l = shellexpand::tilde("~") + "/.config/msr/deepl.toml";
|
||
|
let l = l.to_string();
|
||
|
let mut l = fs::File::create(l).unwrap();
|
||
|
if o != "" {
|
||
|
l.write_all(&o.as_bytes()).unwrap();
|
||
|
}
|
||
|
println!("{:#?}", l);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### shell-command
|
||
|
|
||
|
rustでのjsonやrequestの処理について、shell-commandを使う方法もあります。
|
||
|
|
||
|
rustのコンパイラがあまりにうるさかったり、または依存関係の解消が面倒な場合にお使いください。
|
||
|
|
||
|
```sh
|
||
|
$ sudo pacman -S curl jq
|
||
|
```
|
||
|
|
||
|
```rust:src/main.rs
|
||
|
use std::process::Command;
|
||
|
let api = "Authorization: DeepL-Auth-Key ".to_owned() + &data.api;
|
||
|
let txt = "text=".to_owned() + &message.to_string();
|
||
|
let lang = "target_lang=".to_owned() + ⟨
|
||
|
let output = Command::new("curl").arg("-X").arg("POST").arg("https://api-free.deepl.com/v2/translate")
|
||
|
.arg("-H").arg(api)
|
||
|
.arg("-d").arg(txt)
|
||
|
.arg("-d").arg(lang)
|
||
|
.output().expect("curl");
|
||
|
|
||
|
//let p: DeepData = serde_json::from_str(&o)?;
|
||
|
//let o = &p.translations[0].text;
|
||
|
let o = String::from_utf8_lossy(&output.stdout);
|
||
|
let o = o.to_string();
|
||
|
|
||
|
let l = shellexpand::tilde("~") + "/.config/msr/deepl.json";
|
||
|
let l = l.to_string();
|
||
|
let mut l = fs::File::create(l).unwrap();
|
||
|
if o != "" {
|
||
|
l.write_all(&o.as_bytes()).unwrap();
|
||
|
}
|
||
|
|
||
|
let l = shellexpand::tilde("~") + "/.config/msr/deepl.json";
|
||
|
let l = l.to_string();
|
||
|
let output = Command::new("jq").arg("-r")
|
||
|
.arg(".translations|.[]|.text")
|
||
|
.arg(l)
|
||
|
.output().expect("jq");
|
||
|
let o = String::from_utf8_lossy(&output.stdout);
|
||
|
let o = o.to_string();
|
||
|
```
|
||
|
|
||
|
### features
|
||
|
|
||
|
reqwest + tokio + featuresを使う場合、cargo.tomlに以下のようなfeaturesのsupportを追加してください。
|
||
|
|
||
|
> .await? doesn't have a size known at compile-time
|
||
|
|
||
|
```rust:Cargo.toml
|
||
|
reqwest = { version = "*", features = ["json"] }
|
||
|
futures = "0.3.5"
|
||
|
tokio = { version = "*", features = ["full"] }
|
||
|
```
|
||
|
|