tl;dr
$ repo repo_cmd
takagiy/repo_cmd
$ git clone $(repo url repo_cmd)
Cloning into 'repo_cmd'...
動機
昨今のパッケージマネージャでは、パッケージのソースとしてGitHub等のリポジトリを指定するようなものがよくみられるようになってきた。例えば、自分が使っているVimのプラグインマネージャ[Volt
]もそうで、以下のようにしてプラグインを追加する。
$ volt get junegunn/fzf.vim
あとはelm install
とか、そしてgit clone
とかもそういう感じだ。
こういうツールを使うときはリポジトリを同定するために少なくともauthor/name
という形式の指定が必要だが、インストールしたいリポジトリは思い浮かんでも、authorまではいちいち覚えていないことが多い。そのため、なにかをインストールしようとする度にブラウザを開いてauthorを確認するということがよく起きていた。
これは煩わしい。リポジトリ名に対応するauthor/name
という文字列をコマンドラインから取得できるツールがあると便利そうだ。そういうことをしばらく考えていて何度か調べたがその限りでは見当たらなかった(GitHub関連のCLIとしてはhub
がメジャーっぽいけどそういう機能は現状ないらしい)。そういうわけで自分で書いた。
できたもの
与えられたリポジトリ名をGitHub REST API v3/Searchに問い合わせて、一致したリポジトリの情報を表示する。出力は以下のようにシェルのcommand substitution等から利用されることを想定して最小に留めた。
$ volt get $(repo fzf.vim) # OR volt get (repo fzf.vim) IN fish shell
取得できるフォーマットには種類があって、以下の通り。
-
{author}/{name}
-
$ repo repo_cmd # OR repo full-name repo_cmd takagiy/repo_cmd
-
https://"...".git
$ repo url repo_cmd https://github.com/takagiy/repo_cmd.git
-
git://"...".git
$ repo git-url repo_cmd git://github.com/takagiy/repo_cmd.git
-
https://"..."
$ repo link repo_cmd https://github.com/takagiy/repo_cmd
前述のAPIは認証しないと一応のリミットがあるけど、かなり緩くてコマンドを連打しない限り制限がかかることもなかったし、ほんの1分程度解除されるので認証機能は実装していない。1
検索の際、リポジトリ名が一致するかどうかはcase insensitiveに判断している。
$ repo satysfi
gfngfn/SATySFi
インストール
Rust
のパッケージマネージャ[Cargo
]でインストールできる。バイナリ配布は現状ない(ライセンス表記難しい…)。
$ cargo install repo_cmd
(repo
というcrateが既にあったためrepo_cmd
というcrate名になった)
リポジトリ
GitHub←これ
やや詳しいこと
Rust
で書いた。初心者です。
APIのレスポンスはページと呼ばれるまとまりで返ってくるから、ページの中に与えられた名前と一致するリポジトリがあればそれを返し、なければ次のページをリクエストする。ただし、一定数のページを確認しても見つからなければ検索を諦める。以下のようになる。2
let repository = {
let mut repository = None;
for page in 1..MAX_PAGE {
let response: Response = client
.get(API_URL)
.header(header::USER_AGENT, user_agent())
.query(&[
("per_page", "10"),
("page", &page.to_string()),
("q", query),
])
.send()
.context("Connection failed")?
.json()
.context("Invalid API response")?;
let mut candidates = response.items.ok_or(anyhow!(
"Invalid API response, '{}'",
response.message.as_deref().unwrap_or("")
))?;
let found = candidates
.drain(..)
.find(|repo| repo.name.to_ascii_lowercase() == normal_query);
if found.is_some() {
repository = found;
break;
}
}
repository
}
.ok_or(anyhow!("No repository named '{}' found", query))?;
コマンドライン引数のパースは手書きした。[clap
]とかを使ったほうがよかったかもしれないけどclapのAPIを読むのが面倒だったのでRust
はほぼ初めてだったので、どういう書き心地なのか気になって地道に書いてみた。
おわり
最後まで読んでくれてありがとうございました。