go/goreleaserで初めてのCLIツール作り
はじめに
初めまして!
普段はvueとかreactなどのウェブフロントエンドを書いている大学生です。
インターンの選考を通るためにgoを身につけなければならなくなったので勉強がてらCLIツールを作成しました。
注意点
goのインストール方法、基本文法、go moduleの設定などは割愛します。
自分が詰まったところを重点的に書いています。
作成したもの
その日の天気と翌日の天気予報を取得できるCLIツールを作成しました。
よかったらみていってください。
mmmommm/tenki
コードについて
cobraなどのパッケージがあるのは知っていましたが、今回はオプションの数も多くないこともあり、flagを使用しました。
極力短くしているつもりですが、少し長いかもしれないです。
func main() {
const defaultPrefecture = ""
var cPrefecture, nPrefecture, string
var showVersion bool
flag.StringVar(&cPrefecture, "current", defaultPrefecture, "--curennt <県名> returns current weather information where you are.")
flag.StringVar(&cPrefecture, "c", defaultPrefecture, "-c <県名> returns current weather information where you are.")
flag.StringVar(&fPrefecture, "forecast", defaultPrefecture, "--forecast <県名> returns current weather information where you are.")
flag.StringVar(&fPrefecture, "f", defaultPrefecture, "-f <県名> returns current weather information where you are.")
flag.Parse()
// -cオプションの時の処理を書く
if cPrefecture != "" && fPrefecture == "" {
tenki.CurrentWeather(cPrefecture)
// -fオプションの時の処理を書く
} else if cPrefecture == "" && fPrefecture != "" && {
tenki.ForecastWeather(fPrefecture)
} else {
fmt.Printf("%s: option arguments is empty !! please type prefecture name. \n", os.Args[0])
fmt.Printf("%s: try 'tenki --help' or 'tenki -h' for more information\n", os.Args[0])
os.Exit(1)
}
}
少し省略していますが、main関数はこのようになっており、オプションごとに分岐してtenkiというディレクトリにある関数が実行されるようになっています。
全てのコードはここにあります。
func CurrentWeather(cPrefecture string) {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
var city string
city = sub.Prefecture(cPrefecture)
token := os.Getenv("WEATHER_TOKEN") // APIトークン
endPoint := "https://api.openweathermap.org/data/2.5/weather" // APIのエンドポイント
// パラメータを設定
values := url.Values{}
values.Set("q", city)
values.Set("APPID", token)
// リクエストを投げる
res, err := http.Get(endPoint + "?" + values.Encode())
if res != nil {
defer res.Body.Close()
}
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
// レスポンスを読み取り
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}
// JSONパース
var apiRes OpenWeatherMapAPIResponse
if err := json.Unmarshal(bytes, &apiRes); err != nil {
fmt.Println(err)
}
aa := sub.ASCIIart(apiRes.Weather[0].Icon)
fmt.Printf("----------------------------------------\n")
fmt.Printf("時刻: %s\n", time.Unix(apiRes.Dt, 0))
fmt.Printf("天気: %s\n", apiRes.Weather[0].Main)
fmt.Printf("詳細: %s\n", apiRes.Weather[0].Description)
fmt.Printf("平均気温: %s °C\n", fmt.Sprintf("%.1f", sub.Change(apiRes.Main.Temp))) // ケルビンで取得される
fmt.Printf("%s\n", aa)
fmt.Printf("----------------------------------------\n")
}
長くなってしまうのでcオプションの時だけ書きますが、今回はopenweathermapAPIを使用したので.envにAPIkeyを渡してそれをgodotenvを使って取ってきています。
jsonの定義のせいで長くなってしまうので省略します。
全てのコードはここにあります。
sub.ASCIIart()
は天気に応じてそれに対応する天気の画像をアスキーアート化したものを取得してくる関数です。
アスキーアートの保存ディレクトリ
sub.Change()
はAPIから気温がケルビンで取得されるので-273.15して摂氏に変換する関数です。
fmt.Sprintf("%.1f", 何かしらのstring)
とすると小数点第一位で切ってくれるので統一するために使用しています。
subディレクトリはこちら
リリースについて
goreleaserを使用しました。
homebrewのformulaを自動で作成してくれるらしいのでやっていたのですが、はじめにGitHubActionsでやっていたところ、artifactのエラーで永遠につまづき、公式のgithubのissueにも解決法が出ていなかったのでCircleCIに変えたところうまく行きました。
以下は私の.goreleaser.yml
と.circleci/config.yml
です
before:
hooks:
- go mod download
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
-
tap:
owner: mmmommm
name: homebrew-tenki
description: Returns weather forecast in perticular prefecture.
homepage: “https://github.com/mmmommm/tenki”
commit_author:
name: goreleaserbot
email: goreleaser@carlosbecker.com
install: |
bin.install "tenki"
version: 2.1
workflows:
main:
jobs:
- release:
# Only run this job on git tag pushes
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
jobs:
release:
docker:
- image: circleci/golang:1.15
steps:
- checkout
- run: curl -sL https://git.io/goreleaser | bash
リリース時に詰まったこと
qiitaなどにたくさんの記事がありますがbrewは更新されてbrewsになっていますし、きちんとドキュメントをみて対応したものを書かないとエラーを吐かれます気をつけてください、現時点(2020-10-17)ではこれで動きました。
goreleaserbotがコミットしてくれなくて詰まりました。
手順としては
-
homebrew-ツール名
のリポジトリを作成する。 -
git init
などしてコミットできるようにしておく。 - goreleaserbotをコラボレータとしてリポジトリに招待する。
- タグ名をつけてツール側のリポジトリのコードをpushする
をすればよいです、この辺ドキュメントに書いてなくて意味わからなかったです。
私のツールのhomebrew-tenki
招待の仕方はhomebrew-ツール名
のリポジトリのsettings
から
Manage access
を押して右側にあるinvite a collaborator
を押し
goreleaserbot
と検索して追加すればできます。
最後に関してはgoreleaserのドキュメントにも書いてありますが
1 何かしらコードを書く
2 git add
git commit
する。
3 git tag v1.0.0
する。(自分のリリースの次のtagです。最初ならv0.0.1)
4 git push origin v1.0.0
する。(それぞれ上のでつけたtagを指定してください)
5 git push origin main
する。(ブランチ切っているのであればgit push origin head
)
をすれば良いです。
CIがtagをプッシュした時に動くようになっているのでtagをプッシュしないと動きません。
tagをプッシュした後にリポジトリをみにいったらgoreleaserbotがcommitしてるはずです。
goreleaserを動かすのにGITHUB_TOKENが必要なので設定する必要があります。
やり方は
dashboardのここからProjectSettingsを開き
こんな画面に行くと思うので
左のEnvironment Variablesを選んでGITHUB_TOKENという名前でAPIKeyを登録すれば良いです。
GITHUB_TOKENはgoreleaserの設定の時に取得しているので大丈夫だと思いますが取得方法がわからない場合はこちらを見ればできると思います。
確認
CIなどの実行がうまくいったらターミナルなどで
brew tap {ユーザー名}/{ツール名}
brew install {ユーザー名}/{ツール名}/{ツール名}
をして動作確認をしてみてください。
これできちんとインストールできればhomebrewに公開されています、おめでとうございます🎉🎉
最後に
ある程度文法はわかっていましたがgoで作成したのは初めてだったので詰まりながらも進めることができました。(まだ完成していませんが)
詰まっている方の手助けになれば幸いです。
Discussion