2024年7月5日金曜日

serde_yaml を使って Rust で YAML をパースする

やること

設定ファイルとして YAML を採用したので、 Rust での扱い方を確認する。

前提

プロジェクト作成

cargo init
cargo add serde --features="derive"
cargo add serde_yaml

実装

mikoto2000/binp: バイナリファイルからデータを抽出するツール。 の設定ファイルの構造をパースしてみる。

./yaml/setting.yaml:

- name: UINT64_value
  offset: 0
  size: 8
  type: UINT64
  endianness: LITTLE
- name: UINT32_value
  offset: 8
  size: 4
  type: UINT32
  endianness: LITTLE
- name: UINT16_value
  offset: 12
  size: 2
  type: UINT16
  endianness: LITTLE
- name: UINT8_value
  offset: 14
  size: 1
  type: UINT8
  endianness: LITTLE
- name: BIT_FLAG
  offset: 15
  size: 1
  data_type: FLAGS
  layout:
    - name: LED1
      position: 0
    - name: LED2
      position: 1
      true_label: "high"
      false_label: "low"

main.rs:

use std::{fs::File, io::BufReader};

use serde::{Deserialize, Serialize};

// ビットフラグの 1 ビットを表す構造体
#[derive(Serialize, Deserialize, Debug)]
struct LayoutItem {
    // 表示名
    name: String,
    // ビットフラグのビット位置
    position: u8,
    // ビットが 1 だった時に表示する値
    true_label: Option<String>,
    // ビットが 0 だった時に表示する値
    false_label: Option<String>,
}

// パースする単位を表す構造体
// 本当は layout あり・なしで型定義を分けるのが良いのだろうが、
// firststep だしまぁいいかという感じで。
#[derive(Serialize, Deserialize, Debug)]
struct ConfigItem {
    // 表示名
    name: String,
    // ファイル先頭からのオフセット
    offset: u8,
    // オフセットから何バイト読み込むか
    size: u8,
    // データ型
    // UINT,INT 8-64 と FLAGS(ビットフラグ)
    #[serde(alias = "type")]
    data_type: String,
    // エンディアン
    endianness: Option<String>,
    // type が FLAGS の時のみ利用されるフィールド
    layout: Option<Vec<LayoutItem>>,
}

fn main() {
    // yaml ファイルを読み込み、 Reader 化
    let yaml = "./yaml/setting.yaml";
    let yaml_file = File::open(yaml).unwrap();
    let reader = BufReader::new(yaml_file);

    // serde に Reader を渡し、YAML を構造体へデシリアライズ
    // 構造体の定義さえできてしまえば 1 行で完了。
    let config: Vec<ConfigItem> = serde_yaml::from_reader(reader).unwrap();

    // デシリアライズされた構造体を走査して表示
    for ci in config {
        println!("name: {}", ci.name);
        println!("offset: {}", ci.offset);
        println!("type: {}", ci.data_type);
        println!("endianness: {}", ci.endianness.unwrap_or("".to_string()));
        println!("layout:");
        if ci.layout.is_some() {
            for l in ci.layout.unwrap() {
                println!("name: {}", l.name);
                println!("position: {}", l.position);
                println!("true_label: {}", l.true_label.unwrap_or("".to_string()));
                println!("false_label: {}", l.false_label.unwrap_or("".to_string()));
            }
        }
    }
}

動作確認

$ cargo run
   Compiling firststep v0.1.0 (/workspaces/TIL/rust/yaml/serde_yaml/firststep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/firststep`
name: UINT64_value
offset: 0
type: UINT64
endianness: LITTLE
layout:
name: UINT32_value
offset: 8
type: UINT32
endianness: LITTLE
layout:
name: UINT16_value
offset: 12
type: UINT16
endianness: LITTLE
layout:
name: UINT8_value
offset: 14
type: UINT8
endianness: LITTLE
layout:
name: BIT_FLAG
offset: 15
type: FLAGS
endianness: 
layout:
name: LED1
position: 0
true_label: 
false_label: 
name: LED2
position: 1
true_label: high
false_label: low

表示は汚いけどまぁ想定通りにできてはいそう。

以上。

参考資料

0 件のコメント:

コメントを投稿