ドコモメモ帳のバックアップをテキストファイルに変換するコードをGPTで作成

# 経緯

今までdocomoのメモ帳を使っていましたが、ahamoへ切り替えた際に使えなくなりました。
なので、流行りののChat AIでメモをテキストファイルへ変換するRustプログラムを作成してみました。

## 最初の質問

```
以下のようにBEGIN:VEVENT, END:VEVENTで囲まれたものが複数あります。
それぞれのDESCRIPTIONとSUMMARYを抜き出しUnicode文字に変換するRust言語のコードを作成してください。

[docomoメモ帳のバックアップ(vcsファイル)の一部を抜き出して貼り付けしました]
```

## 最初の生成結果
まぁ最初はエラーになりますね。
ライフタイムが分かっていないのと、DESCRIPTIONが複数行の場合の対応ができていませんでした。
Rustが読みやすいからか修正の指示もしやすいですね。

```
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use url::form_urlencoded;

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage: {} <file>", args[0]);
        return Ok(());
    }

    let file = File::open(&args[1])?;
    let reader = BufReader::new(file);

    let mut events = Vec::new();
    let mut event = HashMap::new();

    for line in reader.lines() {
        let line = line?;
        if line == "BEGIN:VEVENT" {
            event = HashMap::new();
        } else if line == "END:VEVENT" {
            events.push(event);
        } else {
            let parts: Vec<&str> = line.splitn(2, ':').collect();
            if parts.len() == 2 {
                let (key, value) = (parts[0], parts[1]);
                if key == "DESCRIPTION;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE" || key == "SUMMARY;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE" {
                    let decoded = form_urlencoded::parse(value.as_bytes())
                        .map(|(key, _)| key)
                        .collect::<String>();
                    event.insert(key.to_string(), decoded);
                }
            }
        }
    }

    for event in events {
        let description = event.get("DESCRIPTION;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE").unwrap_or(&"".to_string());
        let summary = event.get("SUMMARY;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE").unwrap_or(&"".to_string());

        let mut file = File::create(format!("{}.txt", summary))?;
        writeln!(file, "{}", description)?;
    }

    Ok(())
}
```

## 最終的な出力
いくつか修正指示を出して最終的には以下のようになりました。
まぁまぁ汚いですが短時間で目的は達成できたので良しとします。

以下で実行できます。
cargo run [vcsのファイルパス] [出力ディレクトリ]

```
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Write};
use std::path::Path;
use url::form_urlencoded;

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 3 {
        println!("Usage: {} <file> <directory>", args[0]);
        return Ok(());
    }

    let file = File::open(&args[1])?;
    let reader = BufReader::new(file);
    let dir = &args[2];

    // Create the directory if it does not exist
    if !Path::new(dir).exists() {
        fs::create_dir_all(dir)?;
    }

    let mut events = Vec::new();
    let mut event = HashMap::new();
    let mut current_key = String::new();
    let mut current_value = String::new();
    let empty_string = "".to_string();

    for line in reader.lines() {
        let line = line?;
        if line == "BEGIN:VEVENT" {
            event.clear();
        } else if line == "END:VEVENT" {
            events.push(event.clone());
        } else if line.starts_with("DESCRIPTION;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE") || line.starts_with("SUMMARY;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE") {
            let parts: Vec<&str> = line.splitn(2, ':').collect();
            if parts.len() == 2 {
                let (key, value) = (parts[0], parts[1]);
                current_key = key.to_string();
                current_value = value.to_string();
            }
        } else if line.starts_with('=') {
            current_value.push_str(&line);
        } else {
            if !current_key.is_empty() {
                let decoded = form_urlencoded::parse(current_value.replace("=", "").as_bytes())
                    .map(|(key, _)| key)
                    .collect::<String>();
                let decoded_str = String::from_utf8(hex::decode(decoded).unwrap()).unwrap();
                event.insert(current_key.clone(), decoded_str);
                current_key.clear();
                current_value.clear();
            }
        }
    }

    for event in &events {
        let description = event.get("DESCRIPTION;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE").unwrap_or(&empty_string);
        let summary = event.get("SUMMARY;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE").unwrap_or(&empty_string);
        let summary = summary.replace("\n", "_");

        let mut file = File::create(format!("{}/{}.txt", dir, summary))?;
        writeln!(file, "{}", description)?;
    }

    Ok(())
}
```