3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

リポジトリ名から“author/”を含んだ名前を確認するCLIを作った

Last updated at Posted at 2020-06-11

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はほぼ初めてだったので、どういう書き心地なのか気になって地道に書いてみた。

おわり

最後まで読んでくれてありがとうございました。

  1. 制限についてドキュメントちゃんと読んでなくてうろ覚え

  2. 記事を書いてて思ったけど1..MAX_PAGEじゃなくて1..(MAX_PAGE + 1)か…?

3
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?