Qiita CLIで取得した.mdファイルのファイル名をElixirで変更する

# はじめに

私は[Qiita](https://qiita.com/)に投稿する記事を[GitHub](https://github.com/TORIFUKUKaiou/qiita_articles)で管理しています。  
[Qiita CLI](https://github.com/increments/qiita-cli)という「[手元の環境で記事の執筆・プレビュー・投稿ができるツール](https://github.com/increments/qiita-cli#qiita-cliqiita-preview-%E3%81%B8%E3%82%88%E3%81%86%E3%81%93%E3%81%9D)」を使わせていただいております。

本記事では、[Qiita CLI](https://github.com/increments/qiita-cli)で取得した.mdファイルのファイル名を`{id}.md`から`作成年月日-タイトル.md`に変更します。  

# What is [Qiita CLI](https://github.com/increments/qiita-cli) ???

公式から発信されている記事を貼っておきます。

https://blog.qiita.com/release-qiita-cli-general/

https://qiita.com/Qiita/items/666e190490d0af90a92b

https://qiita.com/Qiita/items/32c79014509987541130

そう[GitHub](https://github.com/TORIFUKUKaiou/qiita_articles)で管理できるわけですよ。  
もちろん私も[GitHub](https://github.com/TORIFUKUKaiou/qiita_articles)で管理しています。  

https://github.com/TORIFUKUKaiou/qiita_articles

私が[Qiita CLI](https://github.com/increments/qiita-cli)を使い始めたころの記録（おもいで）は、次の記事に書いています。  

https://qiita.com/torifukukaiou/items/75854acfcb0460d08237

# ファイル名を変えたい

ここからはもうすでに[Qiita CLI](https://github.com/increments/qiita-cli)を使っていらっしゃることを前提に書き進めます。  

`npx qiita preview`や`npx qiita pull -f`をすると記事の一覧を取得できます。  
この際ファイル名は、記事の`id`をもとに`{id}.md`となります。  
記事の`id`とはここです。  
例: [23c85293e673f537b5e4](https://qiita.com/torifukukaiou/items/23c85293e673f537b5e4)  

![スクリーンショット 2023-08-30 9.50.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131808/75d9a467-cada-a105-2fc3-c60708b700c0.png)


私は、`作成年月日-タイトル.md`にファイル名を変えたいとおもいます。  
このファイル名ですと、いつごろ書いたものなのか、どういった内容なのかが一目瞭然だからです。  

![スクリーンショット 2023-08-30 9.53.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131808/0c1c8c67-d1a1-6e55-98d5-8a5370eaa8d6.png)



:::note warn
ファイル名勝手に変えていいの？　という疑問は当然湧くとおもいますし、そこは私も気になるところです。理想は[Qiita CLI](https://github.com/increments/qiita-cli)のソースコードのどこそこがこうなっているから変えていいんですと示すのが理想です。が、[Qiita CLI](https://github.com/increments/qiita-cli)の今後のアップデートでどう影響するのかわかりませんし、私とお友達のニシグチさんはファイル名を変更してもいまのところ不都合は生じていません。また記事を新規作成するときには、`npx qiita new 記事のファイルのベース名`と何でも指定してよいようですので、ファイル名は自由に付けてよい、自由には責任が伴うのだと読むことができます、と自分を納得させています。
:::

https://github.com/mnishiguchi/qiita-posts

この記事は上記リポジトリにあった[bin/normalize-filenames.exs](https://github.com/mnishiguchi/qiita-posts/blob/main/bin/normalize-filenames.exs)に強くインスパイアされて書きました。[Elixir](https://elixir-lang.org/)でプログラミングすることを楽しみました。  

# 作ったもの

[Elixir](https://elixir-lang.org/)という素敵な世俗派関数型言語[^1]でプログラミングを楽しみました。 
その素敵具合は「[Elixir Saves Pinterest $2 Million a Year In Server Costs](https://paraxial.io/blog/elixir-savings)」によく現れています。開発者も経営者もこの事実に瞠目することでしょう。**$2 Million/年の節約ですってよ！、奥さん。**

https://paraxial.io/blog/elixir-savings


[^1]: @kikuyuta 先生の「[世俗派関数型言語 Elixir を関数型言語風に使ってみたらやっぱり関数型言語みたいだった](https://qiita.com/kikuyuta/items/afa4c264720eb29d9760)」より。[Elixir](https://elixir-lang.org/)はコワくないですよ〜。

はい！　プログラムです。

```elixir:bin/normalize-filenames.exs
Mix.install([
  {:req, "~> 0.3.11"},
  {:timex, "~> 3.7"},
  {:yaml_front_matter, "~> 1.0"},
  {:zarex, "~> 1.0"}
])

defmodule Qiita do
  @token System.fetch_env!("QIITA_TOKEN")
  @headers [Authorization: "Bearer #{@token}", Accept: "Application/json; Charset=utf-8"]

  def get_item(item_id) do
    Req.get("https://qiita.com/api/v2/items/#{item_id}", headers: @headers)
  end

  def get_created_at(item_id) do
    {:ok, res} = get_item(item_id)
    Map.get(res, :body) |> Map.get("created_at")
  end
end

defmodule NormalizeFilename do
  def normalize(filename) do
    case Regex.match?(~r/^\d{8}-.+\.md$/, filename) do
      true -> maybe_do_normalize(filename)
      false -> do_normalize(filename)
    end
  end

  defp do_normalize(nil, _title, filename), do: filename

  defp do_normalize(id, title, _filename) do
    created_at =
      Qiita.get_created_at(id)
      |> Timex.parse!("{ISO:Extended}")
      |> Timex.format!("%Y%m%d", :strftime)

    normalized_title = normalized_title(title)

    build_filename(created_at, normalized_title)
  end

  defp do_normalize(filename) do
    {id, title} = filename |> path() |> parse_file()
    do_normalize(id, title, filename)
  end

  defp maybe_do_normalize(title, title, _date, filename), do: filename

  defp maybe_do_normalize(normalized_title, _filename_title, date, _filename),
    do: build_filename(date, normalized_title)

  defp maybe_do_normalize(filename) do
    {_id, raw_title} = filename |> path() |> parse_file()
    normalized_title = normalized_title(raw_title)

    %{"date" => date, "title" => filename_title} =
      Regex.named_captures(~r/^(?<date>\d{8})-(?<title>.+)\.md$/, filename)

    maybe_do_normalize(normalized_title, filename_title, date, filename)
  end

  defp path(filename), do: Path.join(File.cwd!(), "public") |> Path.join(filename)

  defp parse_file(path) do
    {%{"id" => id, "title" => title}, _body} = YamlFrontMatter.parse_file!(path)
    {id, title}
  end

  defp normalized_title(title) do
    Zarex.sanitize(title)
    |> then(&Regex.replace(~r/\[/, &1, "【"))
    |> then(&Regex.replace(~r/\]/, &1, "】"))
    |> String.slice(0, 80)
  end

  defp build_filename(created_at, normalized_title), do: "#{created_at}-#{normalized_title}.md"
end

defmodule Main do
  def main do
    File.ls!("public")
    |> Enum.reject(&File.dir?("public/#{&1}"))
    |> Enum.filter(&String.ends_with?(&1, ".md"))
    |> Enum.each(fn filename ->
      normalized = NormalizeFilename.normalize(filename)

      IO.puts(normalized)
      :ok = File.rename("public/#{filename}", "public/#{normalized}")
    end)
  end
end

Main.main()
```

ここにあります。  

https://github.com/TORIFUKUKaiou/qiita_articles/blob/main/bin/normalize-filenames.exs

# 使い方

[Elixir](https://elixir-lang.org/)をインストールしていない方もいらっしゃるでしょうから、[Docker](https://www.docker.com/)でイゴかせる[^2]ようにコマンド例を書いておきます。


[^2]: 「動かせる」の意。おそらく西日本の方言、たぶん。[NervesJP](https://nerves-jp.connpass.com/)ではおなじみ。2017年ころのオートレースの実況では、実況アナウンサーが「針[^3]イゴきます」とはっきり言っていた。

[^3]: 大時計の針のこと。針がイゴいてある地点まで到達すると選手はスタートを切る。発走の合図。針がイゴきはじめると(おそらく)選手は緊張するし、スタートはその後のレース展開に大きく影響するので、車券を握りしめている観客たちがもっとも緊張する瞬間であるため、先の尖った鋭いものを連想させる針は緊張の暗喩としても言い得て妙。

その前に配置の仕方を書いておきます。  

`tree`の実行結果はこんな感じを想定しています。  

```
.
├── README.md
├── bin
│   └── normalize-filenames.exs
├── public
│   ├── 009fc0559c70e5e69ca7.md
│   ├── 00b5b0a8b8e81ad0ae46.md
│   ├── newArticle007.md
│   └── newArticle008.md
└── qiita.config.json
```

[Docker](https://www.docker.com/)のコマンド例です。  
プロジェクトのルートで実行することを想定しています。  

```bash
docker run \
       --rm \
       --mount type=bind,src=$(pwd),dst=/app \
       -w /app \
       -e QIITA_TOKEN="your token" \
       elixir:1.15.4-otp-25-slim \
       elixir bin/normalize-filenames.exs
```

ちなみに、`--mount`はよく`-v`で見かけることも多いとおもいます。

[New users should use the --mount syntax. Experienced users may be more familiar with the -v or --volume syntax, but are encouraged to use --mount, because research has shown it to be easier to use.](https://docs.docker.com/storage/bind-mounts/#choose-the--v-or---mount-flag)

https://docs.docker.com/storage/bind-mounts/#choose-the--v-or---mount-flag

（Dockerの）新しいユーザーは、`--mount syntax`を`should use`ですと、[Docker](https://www.docker.com/)のドキュメントに書いてあります。  


# 少しだけ解説

なぜ環境変数で`QIITA_TOKEN`を指定する必要があるの？　盗もうとしているのじゃないの？　が気になるとおもいます。  

:::note info
大丈夫です。安心してください。盗みません。
:::

私は『スクール★ウォーズ』と『進撃の巨人』と、プロレス、オートレース、将棋、歴史小説が好きでして、それらを楽しんでいると、あなたの`QIITA_TOKEN`をもらったところで使う時間がありません。  
それは冗談として、話を続けます。  

`npx qiita pull`等で記事を取得すると、.mdファイルの中身はこの写真のように、 **`created_at`が無かとです！！！**

![スクリーンショット 2023-08-30 8.28.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131808/5a7e50eb-93c8-2c94-c89e-5d1b8fd1d80d.png)

じゃあ、どっから持ってくるだ？　ということで[Qiita API](https://qiita.com/api/v2/docs)を使うわけです。  
[GET /api/v2/items/:item_id](https://qiita.com/api/v2/docs#get-apiv2itemsitem_id)の返戻JSONから`created_at`を得ます。  

私ね、[Qiita](https://qiita.com/)に登録した記事が2023-08-30現在531記事あります。  
@kaizen_nagoya さんの6341記事（2023-08-30現在）にはまだまだ遠く及びません。

[Qiita API](https://qiita.com/api/v2/docs)の[利用制限](https://qiita.com/api/v2/docs#%E5%88%A9%E7%94%A8%E5%88%B6%E9%99%90)にこう書いてあるわけです。  

> 認証している状態ではユーザーごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。

この1時間1000回はマヂで弾かれます。体感です。なぜ知っているかというと、1時間に1000回以上リクエストしてエラーになった経験があるからです。[Qiita](https://qiita.com/)さん、たくさんリクエストしてしまってごめんなさい:pray:  

記事の数が少ない人や1時間ごとに60件ずつ実行する気長にやれる人は`QIITA_TOKEN`の指定は不要です。細かいことを念のため言っておくと、`@headers [Accept: "Application/json; Charset=utf-8"]`というふうにアクセストークンの指定は無しとするようプログラムを書き換えてから実行してください。  

私は500記事以上ありましたし、一度の実行で終わらせたかったので、`QIITA_TOKEN`を使用しました。  

そして、この記事を書いていて気づきました。  
[GET /api/v2/authenticated_user/items](https://qiita.com/api/v2/docs#get-apiv2authenticated_useritems)を使えばもっと少ないAPIリクエストで実現できそうです。  

# さいごに

本記事では、[Qiita CLI](https://github.com/increments/qiita-cli)で取得した.mdファイルのファイル名を`{id}.md`から`作成年月日-タイトル.md`に変更することを[Elixir](https://elixir-lang.org/)を使って楽しみました。  
[Qiita CLI](https://github.com/increments/qiita-cli)を使って、[Qiita](https://qiita.com/)ライフをエンジョイしてください。  

私にInspireを与えてくれた @mnishiguchi さんに感謝です。Thank you so much.

それではごいっしょに！
「いやぁ、[Qiita](https://qiita.com/)って本当にいいもんですね～。それではまたご一緒に投稿を楽しみましょう」

<iframe width="960" height="540" src="https://www.youtube.com/embed/TsYL6oN8SXs" title="水野晴郎さん　映画って本当にいいもんですね" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>