GeekFactory

int128.hatenablog.com

GitHub APIでリポジトリにファイルをコミットするコマンドを作った

GitHub APIを利用してリポジトリにファイルをコミットするコマンド ghcp を作りました。シングルバイナリでgitコマンドに依存しないため、リリースなどでCIからリポジトリのファイルを書き換えたい場合に便利です。

github.com

使い方

GitHub ReleasesもしくはHomebrewからインストールできます。

# GitHub Releases
curl -L -o /usr/local/bin/ghcp https://github.com/int128/ghcp/releases/download/v1.3.0/ghcp_linux_amd64

# Homebrew
brew tap int128/ghcp
brew install ghcp

# Go
go get github.com/int128/ghcp

あらかじめGitHubの設定ページからトークンを取得して $GITHUB_TOKEN に設定しておく必要があります。

基本的な使い方は以下になります。

デフォルトブランチ(通常はmaster)にファイルをコミットする:

ghcp -u YOUR -r REPO -m MESSAGE files

指定したブランチにファイルをコミットする:

ghcp -u YOUR -r REPO -b BRANCH -m MESSAGE files

デフォルトブランチから新しいブランチを作成して、ファイルをコミットする:

ghcp -u YOUR -r REPO -B BRANCH -m MESSAGE files

指定した親ブランチから新しいブランチを作成して、ファイルをコミットする:

ghcp -u YOUR -r REPO -B BRANCH --parent PARENT -m MESSAGE files

応用例

GitHub Pagesへのリリース

CIでビルドしたファイルをGitHub Pagesにリリースする場合、これまでは git commitgit push を駆使したスクリプトを書く必要がありましたが、ghcpを使うと以下の1コマンドでリリースできます。

ghcp -u int128 -r sandbox -b gh-pages -m "Example commit" index.html

Homebrew tapリポジトリへのリリース

tapリポジトリにformulaをリリースする場合もghcpを使うと簡単に実現できます。

# formulaを生成する
cat > hello.rb <<EOF
class Hello < Formula
  desc "Your awesome application"
  homepage "https://github.com/YOUR/hello"
  url "https://github.com/YOUR/hello/releases/download/v1.0.0/hello_darwin_amd64"
  version "v1.0.0"
  sha256 "$(shasum -a 256 -b hello | cut -f1 -d' ')"

  def install
    bin.install "hello_darwin_amd64" => "hello"
  end

  test do
    system "#{bin}/hello -h"
  end
end
EOF

# tapリポジトリにコピー
ghcp -u int128 -r homebrew-sandbox -m v1.0.0 hello.rb

バージョン文字列を書き換える

READMEやビルドスクリプトなどのバージョン文字列を書き換える用途にも使えます。CIで新バージョンをテストする場合に便利ですね。

# substitute version string in files
sed -i -e "s/version '[0-9.]*'/version '$TAG'/g" README.md build.gradle

# commit the changes to a new branch
ghcp -u YOUR -r REPO -B bump-v1.1.0 -m v1.1.0 README.md build.gradle

まとめ

詳しい使い方は https://github.com/int128/ghcp を参照してください。

Zaimからスプレッドシートへの転記に使えるChrome Extension

Zaimの履歴をGoogleスプレッドシートExcelなどに転記するためのChrome Extension「zaimsheet」を作りました。

chrome.google.com

zaimsheetをインストールすると、Zaim Web版の履歴画面に「スプレッドシート形式」というボタンが追加されます。このボタンをクリックすると以下のような表が表示されます。

f:id:int128:20190214143942p:plain
スクリーンショット

表を選択してコピーすると、GoogleスプレッドシートExcelなどに貼り付けられます。

f:id:int128:20190214144340p:plain
スプレッドシートの例

まだ初版なのでアイコンや説明文などもちゃんと設定していないのですが、そのうち直します。ソースコードhttps://github.com/int128/zaimsheet で公開しています。

我が家は独立採算制を採用しているため、Zaimに入力した履歴から家庭内共通費を月次で集計しています。Zaimに社内取引のような概念があるとよいのですが、今のところスプレッドシートで集計しています。

uber-go/dig を使う

GoのDIコンテナ実装である uber-go/dig を使ってみました。

github.com

基本的な使い方はGoDocに書いてあります。あえて3行にまとめると以下になります。

c := dig.New()
c.Provide(func (/* 生成に必要な型... */) /* 生成される型 */ { /* 生成処理 */ })
c.Invoke(func (/* 実行に必要な型 */) { /* 実行処理 */ })

例えば、以下のように Provide メソッドを記述すると、 interface GetUserUsecase を実装するオブジェクトを生成するには interface UserRepository が必要であると宣言できます。

c := dig.New()
c.Provide(func (r UserRepository) GetUserUsecase {
    return &GetUserUsecaseImpl{UserRepository: r}
})

Springでいうconfiguration classの @Bean と同じです。

// これはJava/Springの例
@Bean
public GetUserUsecase(UserRepository r) GetUserUsecase {
  return new UserRepositoryImpl(r)
}

依存関係に基づいて実際に値を取得するには Invoke メソッドを実行します。例えば、以下のように記述すると interface GetUserUsecase を実装するオブジェクトを取得して実行できます。

c.Invoke(func (u GetUserUsecase) {
    u.Do()
})

Provide で宣言した依存関係は再帰的に解決されます。例えば、以下のように記述すると interface GetUserUsecaseinterface UserRepositoryinterface UserAPIClient の依存関係を宣言できます。

c := dig.New()
c.Provide(func (r UserRepository) GetUserUsecase {
    return &GetUserUsecaseImpl{UserRepository: r}
})
c.Provide(func (c UserAPIClient) UserRepository {
    return &UserRepositoryImpl{UserAPIClient: c}
})

実践的な使い方としては、依存関係を別のパッケージに切り出すとよいでしょう。

package di

func New() (*dig.Container, error) {
    c := dig.New()
    if err := c.Provide(func (r UserRepository) GetUserUsecase { return &GetUserUsecaseImpl{UserRepository: r} }); err != nil {
        return nil, errors.WithStack(err)
    }
    if err := c.Provide(func (c UserAPIClient) UserRepository { return &UserRepositoryImpl{UserAPIClient: c} }); err != nil {
        return nil, errors.WithStack(err)
    }
    return c, nil
}

宣言した依存関係が正しく解決できるかテストを書いておくとよいでしょう。

package di_test

func TestInvoke(t *testing.T) {
    c, err := di.New()
    if err != nil {
        t.Fatalf("error while di.New: %+v", err)
    }
    if err := c.Invoke(func(u GetUserUsecase) {
        t.Logf("%+v", u)
    }); err != nil {
        t.Fatalf("error while c.Invoke: %+v", err)
    }
}

構造体に dig.In を埋め込むと構造体のメンバに定義されている型を解決して代入してくれます。この方法を使うと Provide に渡す関数を簡潔に書けます。

type GetUserUsecaseImpl struct {
    dig.In
    UserRepository UserRepository
    FooRepository FooRepository
    BarRepository BarRepository
}
c.Provide(func (i GetUserUsecaseImpl) GetUserUsecase { return &i })

Springでいう @Autowired に近いですね。

// これはJava/Springの例
public class GetUserUsecaseImpl {
  @Autowired private UserRepository userRepository
  @Autowired private FooRepository fooRepository
  @Autowired private BarRepository barRepository
}

なお、小規模なアプリケーションの場合は、digを使わずに手作業で依存関係を解決する方法もあります。例えば、以下のようにmainにすべての依存関係を書いてしまいます。

func main() {
    u := &GetUserUsecaseImpl{
        UserRepository: &UserRepositoryImpl{
            UserAPIClient: &UserAPIClientImpl{
                Client: http.DefaultClient,
            },
        },
    }
    if err := u.Do(context.Background()); err != nil {
        log.Fatalf("Error: %s", err)
    }
}

依存関係が単純な場合は手作業による解決でも十分ですが、複雑になってきた場合はdigのようなDIコンテナの導入を検討するとよいでしょう。