はじめに
私はQiitaに投稿する記事をGitHubで管理しています。
Qiita CLIという「手元の環境で記事の執筆・プレビュー・投稿ができるツール」を使わせていただいております。
本記事では、Qiita CLIで取得した.mdファイルのファイル名を{id}.md
から作成年月日-タイトル.md
に変更します。
What is Qiita CLI ???
公式から発信されている記事を貼っておきます。
そうGitHubで管理できるわけですよ。
もちろん私もGitHubで管理しています。
私がQiita CLIを使い始めたころの記録(おもいで)は、次の記事に書いています。
ファイル名を変えたい
ここからはもうすでにQiita CLIを使っていらっしゃることを前提に書き進めます。
npx qiita preview
やnpx qiita pull -f
をすると記事の一覧を取得できます。
この際ファイル名は、記事のid
をもとに{id}.md
となります。
記事のid
とはここです。
例: 23c85293e673f537b5e4
私は、作成年月日-タイトル.md
にファイル名を変えたいとおもいます。
このファイル名ですと、いつごろ書いたものなのか、どういった内容なのかが一目瞭然だからです。
この記事は上記リポジトリにあったbin/normalize-filenames.exsに強くインスパイアされて書きました。Elixirでプログラミングすることを楽しみました。
作ったもの
Elixirという素敵な世俗派関数型言語1でプログラミングを楽しみました。
その素敵具合は「Elixir Saves Pinterest $2 Million a Year In Server Costs」によく現れています。開発者も経営者もこの事実に瞠目することでしょう。$2 Million/年の節約ですってよ!、奥さん。
はい! プログラムです。
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()
ここにあります。
使い方
Elixirをインストールしていない方もいらっしゃるでしょうから、Dockerでイゴかせる2ようにコマンド例を書いておきます。
その前に配置の仕方を書いておきます。
tree
の実行結果はこんな感じを想定しています。
.
├── README.md
├── bin
│ └── normalize-filenames.exs
├── public
│ ├── 009fc0559c70e5e69ca7.md
│ ├── 00b5b0a8b8e81ad0ae46.md
│ ├── newArticle007.md
│ └── newArticle008.md
└── qiita.config.json
Dockerのコマンド例です。
プロジェクトのルートで実行することを想定しています。
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
で見かけることも多いとおもいます。
(Dockerの)新しいユーザーは、--mount syntax
をshould use
ですと、Dockerのドキュメントに書いてあります。
少しだけ解説
なぜ環境変数でQIITA_TOKEN
を指定する必要があるの? 盗もうとしているのじゃないの? が気になるとおもいます。
大丈夫です。安心してください。盗みません。
私は『スクール★ウォーズ』と『進撃の巨人』と、プロレス、オートレース、将棋、歴史小説が好きでして、それらを楽しんでいると、あなたのQIITA_TOKEN
をもらったところで使う時間がありません。
それは冗談として、話を続けます。
npx qiita pull
等で記事を取得すると、.mdファイルの中身はこの写真のように、 created_at
が無かとです!!!
じゃあ、どっから持ってくるだ? ということでQiita APIを使うわけです。
GET /api/v2/items/:item_idの返戻JSONからcreated_at
を得ます。
私ね、Qiitaに登録した記事が2023-08-30現在531記事あります。
@kaizen_nagoya さんの6341記事(2023-08-30現在)にはまだまだ遠く及びません。
認証している状態ではユーザーごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。
この1時間1000回はマヂで弾かれます。体感です。なぜ知っているかというと、1時間に1000回以上リクエストしてエラーになった経験があるからです。Qiitaさん、たくさんリクエストしてしまってごめんなさい
記事の数が少ない人や1時間ごとに60件ずつ実行する気長にやれる人はQIITA_TOKEN
の指定は不要です。細かいことを念のため言っておくと、@headers [Accept: "Application/json; Charset=utf-8"]
というふうにアクセストークンの指定は無しとするようプログラムを書き換えてから実行してください。
私は500記事以上ありましたし、一度の実行で終わらせたかったので、QIITA_TOKEN
を使用しました。
そして、この記事を書いていて気づきました。
GET /api/v2/authenticated_user/itemsを使えばもっと少ないAPIリクエストで実現できそうです。
さいごに
本記事では、Qiita CLIで取得した.mdファイルのファイル名を{id}.md
から作成年月日-タイトル.md
に変更することをElixirを使って楽しみました。
Qiita CLIを使って、Qiitaライフをエンジョイしてください。
私にInspireを与えてくれた @mnishiguchi さんに感謝です。Thank you so much.
それではごいっしょに!
「いやぁ、Qiitaって本当にいいもんですね~。それではまたご一緒に投稿を楽しみましょう」
-
@kikuyuta 先生の「世俗派関数型言語 Elixir を関数型言語風に使ってみたらやっぱり関数型言語みたいだった」より。Elixirはコワくないですよ〜。 ↩
-
「動かせる」の意。おそらく西日本の方言、たぶん。NervesJPではおなじみ。2017年ころのオートレースの実況では、実況アナウンサーが「針3イゴきます」とはっきり言っていた。 ↩
-
大時計の針のこと。針がイゴいてある地点まで到達すると選手はスタートを切る。発走の合図。針がイゴきはじめると(おそらく)選手は緊張するし、スタートはその後のレース展開に大きく影響するので、車券を握りしめている観客たちがもっとも緊張する瞬間であるため、先の尖った鋭いものを連想させる針は緊張の暗喩としても言い得て妙。 ↩