├── accountbook ├── .gitignore ├── README.md ├── skeleton │ ├── step01 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── step02 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── step03 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── step04 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── step05 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── step06 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── accountbook.go │ │ ├── go.mod │ │ └── main.go │ ├── step07 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── accountbook.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── step08 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── accountbook.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── step09 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── accountbook.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── handler.go │ │ └── main.go │ ├── step10 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── accountbook.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── handler.go │ │ └── main.go │ └── step11 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── accountbook.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── handler.go │ │ └── main.go └── solution │ ├── step01 │ ├── .gitignore │ ├── README.md │ ├── go.mod │ └── main.go │ ├── step02 │ ├── .gitignore │ ├── README.md │ ├── go.mod │ └── main.go │ ├── step03 │ ├── .gitignore │ ├── README.md │ ├── go.mod │ └── main.go │ ├── step04 │ ├── .gitignore │ ├── README.md │ ├── go.mod │ └── main.go │ ├── step05 │ ├── .gitignore │ ├── README.md │ ├── go.mod │ └── main.go │ ├── step06 │ ├── .gitignore │ ├── README.md │ ├── accountbook.go │ ├── go.mod │ └── main.go │ ├── step07 │ ├── .gitignore │ ├── README.md │ ├── accountbook.go │ ├── go.mod │ ├── go.sum │ └── main.go │ ├── step08 │ ├── .gitignore │ ├── README.md │ ├── accountbook.go │ ├── go.mod │ ├── go.sum │ └── main.go │ ├── step09 │ ├── .gitignore │ ├── README.md │ ├── accountbook.go │ ├── go.mod │ ├── go.sum │ ├── handler.go │ └── main.go │ ├── step10 │ ├── .gitignore │ ├── README.md │ ├── accountbook.go │ ├── go.mod │ ├── go.sum │ ├── handler.go │ └── main.go │ └── step11 │ ├── .gitignore │ ├── README.md │ ├── accountbook.go │ ├── go.mod │ ├── go.sum │ ├── handler.go │ └── main.go ├── goroutine ├── .gitignore ├── README.md ├── skeleton │ ├── step01 │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── step02 │ │ ├── README.md │ │ ├── go.mod │ │ ├── main.go │ │ └── trace.png │ ├── step03 │ │ ├── README.md │ │ ├── go.mod │ │ ├── main.go │ │ └── trace.png │ ├── step04 │ │ ├── README.md │ │ ├── go.mod │ │ ├── main.go │ │ └── trace.png │ ├── step05 │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── step06 │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go └── solution │ ├── step01 │ ├── README.md │ ├── go.mod │ └── main.go │ ├── step02 │ ├── README.md │ ├── go.mod │ ├── main.go │ └── trace.png │ ├── step03 │ ├── README.md │ ├── go.mod │ ├── main.go │ └── trace.png │ ├── step04 │ ├── README.md │ ├── go.mod │ ├── main.go │ └── trace.png │ ├── step05 │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go │ └── step06 │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── greeting ├── README.md ├── skeleton │ ├── step01 │ │ ├── README.md │ │ └── main.go │ ├── step02 │ │ ├── README.md │ │ └── main.go │ ├── step03 │ │ ├── README.md │ │ ├── cmd │ │ │ └── greeting │ │ │ │ └── main.go │ │ ├── go.mod │ │ └── greeting.go │ ├── step04 │ │ ├── README.md │ │ ├── _bin │ │ │ └── greeting │ │ ├── cmd │ │ │ └── greeting │ │ │ │ └── main.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── greeting.go │ ├── step05 │ │ ├── README.md │ │ ├── _bin │ │ │ └── greeting │ │ ├── cmd │ │ │ └── greeting │ │ │ │ └── main.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── greeting.go │ │ └── greeting_test.go │ ├── step06 │ │ ├── README.md │ │ ├── _bin │ │ │ └── greeting │ │ ├── cmd │ │ │ └── greeting │ │ │ │ └── main.go │ │ ├── export_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── greeting.go │ │ └── greeting_test.go │ ├── step07 │ │ ├── README.md │ │ ├── _bin │ │ │ └── greeting │ │ ├── cmd │ │ │ └── greeting │ │ │ │ └── main.go │ │ ├── export_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── greeting.go │ │ └── greeting_test.go │ └── step08 │ │ ├── README.md │ │ ├── _bin │ │ └── greeting │ │ ├── cmd │ │ └── greeting │ │ │ └── main.go │ │ ├── export_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── greeting.go │ │ └── greeting_test.go └── solution │ ├── .gitignore │ ├── step01 │ ├── README.md │ └── main.go │ ├── step02 │ ├── README.md │ └── main.go │ ├── step03 │ ├── README.md │ ├── cmd │ │ └── greeting │ │ │ └── main.go │ ├── go.mod │ └── greeting.go │ ├── step04 │ ├── README.md │ ├── cmd │ │ └── greeting │ │ │ └── main.go │ ├── go.mod │ ├── go.sum │ └── greeting.go │ ├── step05 │ ├── README.md │ ├── cmd │ │ └── greeting │ │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── greeting.go │ └── greeting_test.go │ ├── step06 │ ├── README.md │ ├── cmd │ │ └── greeting │ │ │ └── main.go │ ├── export_test.go │ ├── go.mod │ ├── go.sum │ ├── greeting.go │ └── greeting_test.go │ ├── step07 │ ├── README.md │ ├── cmd │ │ └── greeting │ │ │ └── main.go │ ├── export_test.go │ ├── go.mod │ ├── go.sum │ ├── greeting.go │ └── greeting_test.go │ └── step08 │ ├── README.md │ ├── cmd │ └── greeting │ │ └── main.go │ ├── export_test.go │ ├── go.mod │ ├── go.sum │ ├── greeting.go │ └── greeting_test.go ├── guestbook └── ja │ ├── README.md │ ├── skeleton │ ├── step0 │ │ └── README.md │ ├── step1 │ │ ├── app.yaml │ │ ├── index.go │ │ └── init.go │ ├── step2 │ │ ├── app.yaml │ │ ├── index.go │ │ └── init.go │ ├── step3 │ │ ├── app.yaml │ │ ├── index.go │ │ └── init.go │ ├── step4 │ │ ├── app.yaml │ │ ├── index.go │ │ └── init.go │ ├── step5 │ │ ├── app.yaml │ │ ├── index.go │ │ ├── init.go │ │ ├── message.go │ │ └── post.go │ └── step6 │ │ ├── app.yaml │ │ ├── index.go │ │ ├── init.go │ │ ├── message.go │ │ └── post.go │ └── solution │ ├── step0 │ └── README.md │ ├── step1 │ ├── app.yaml │ ├── index.go │ └── init.go │ ├── step2 │ ├── app.yaml │ ├── index.go │ └── init.go │ ├── step3 │ ├── app.yaml │ ├── index.go │ └── init.go │ ├── step4 │ ├── app.yaml │ ├── index.go │ └── init.go │ ├── step5 │ ├── app.yaml │ ├── index.go │ ├── init.go │ ├── message.go │ └── post.go │ └── step6 │ ├── app.yaml │ ├── index.go │ ├── init.go │ ├── message.go │ └── post.go ├── imgconv └── ja │ ├── README.md │ ├── skeleton │ ├── .gitignore │ └── src │ │ ├── step1 │ │ ├── README.md │ │ └── cmd │ │ │ └── imgconv │ │ │ └── main.go │ │ ├── step2 │ │ ├── README.md │ │ └── cmd │ │ │ └── imgconv │ │ │ └── main.go │ │ ├── step3 │ │ ├── README.md │ │ └── cmd │ │ │ └── imgconv │ │ │ └── main.go │ │ ├── step4 │ │ ├── README.md │ │ └── cmd │ │ │ └── imgconv │ │ │ └── main.go │ │ ├── step5 │ │ ├── README.md │ │ └── cmd │ │ │ └── imgconv │ │ │ └── main.go │ │ ├── step6 │ │ ├── README.md │ │ ├── cmd │ │ │ └── imgconv │ │ │ │ └── main.go │ │ └── imgconv │ │ │ └── imgconv.go │ │ ├── step7 │ │ ├── README.md │ │ ├── cmd │ │ │ └── imgconv │ │ │ │ └── main.go │ │ └── imgconv │ │ │ └── imgconv.go │ │ ├── step8 │ │ ├── README.md │ │ ├── cmd │ │ │ └── imgconv │ │ │ │ └── main.go │ │ └── imgconv │ │ │ └── imgconv.go │ │ └── tools │ │ └── cmd │ │ └── httpget │ │ └── main.go │ └── solution │ ├── .gitignore │ └── src │ ├── step1 │ ├── README.md │ └── cmd │ │ └── imgconv │ │ └── main.go │ ├── step2 │ ├── README.md │ └── cmd │ │ └── imgconv │ │ └── main.go │ ├── step3 │ ├── README.md │ └── cmd │ │ └── imgconv │ │ └── main.go │ ├── step4 │ ├── README.md │ └── cmd │ │ └── imgconv │ │ └── main.go │ ├── step5 │ ├── README.md │ └── cmd │ │ └── imgconv │ │ └── main.go │ ├── step6 │ ├── README.md │ ├── cmd │ │ └── imgconv │ │ │ └── main.go │ └── imgconv │ │ └── imgconv.go │ ├── step7 │ ├── README.md │ ├── cmd │ │ └── imgconv │ │ │ └── main.go │ └── imgconv │ │ └── imgconv.go │ ├── step8 │ ├── README.md │ ├── cmd │ │ └── imgconv │ │ │ └── main.go │ └── imgconv │ │ └── imgconv.go │ └── tools │ └── cmd │ └── httpget │ └── main.go └── slackbot └── ja ├── README.md └── solution ├── step1 ├── README.md ├── img │ ├── 01_create_new_app.png │ ├── 02_create_new_app2.png │ ├── 03_basic_infomation.png │ ├── 04_add_bot_user.png │ ├── 05_add_bot_user_button.png │ ├── 06_install_application.png │ ├── 07_auth_app.png │ ├── 08_verify_token.png │ ├── 09_oauth_token.png │ ├── 10_event_subscription_on.png │ ├── 11_event_subscription_url.png │ └── 12_add_event_type.png └── src │ ├── app.yaml │ └── main.go └── step2 ├── README.md ├── img ├── 01_interactive_component_off.png ├── 02_interactive_component_on.png ├── 03_create_action.png ├── 04_create_action_dialog.png ├── 05_save_changes.png └── 06_uranai.png └── src ├── app.yaml ├── main.go └── uranai.go /accountbook/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /accountbook/README.md: -------------------------------------------------------------------------------- 1 | # 〜家計簿アプリを作ろう〜 2 | 3 | ## ハンズオンのやりかた 4 | 5 | `skeleton`ディレクトリ以下に問題があり、11個のステップに分けられています。 6 | STEP01からSTEP11までステップごとに進めていくことで、GoでWebアプリが作れるようになっています。 7 | 8 | 各ステップに、READMEが用意されていますので、まずは`README`を読みます。 9 | `README`には、そのステップを理解するための解説が書かれています。 10 | 11 | `README`を読んだら、ソースコードを開き`TODO`コメントが書かれている箇所をコメントに従って修正して行きます。 12 | `TODO`コメントをすべて修正し終わったら、`README`に書かれた実行例に従ってプログラムをコンパイルして実行します。 13 | 14 | 途中でわからなくなった場合は、`solution`ディレクトリ以下に解答例を用意していますので、そちらをご覧ください。 15 | 16 | `macOS`の動作結果をもとに解説しています。 17 | `Windows`の方は、パスの区切り文字やコマンド等を適宜読み替えてください。 18 | 19 | ## 目次 20 | 21 | * STEP01: Goに触れる 22 | * STEP02: データの入力 23 | * STEP03: データの記録 24 | * STEP04: 複数データの記録 25 | * STEP05: ファイルへの保存 26 | * STEP06: ブラッシュアップ 27 | * STEP07: データベースへの記録 28 | * STEP08: 品目ごとの集計 29 | * STEP09: 一覧ページの作成 30 | * STEP10: 入力ページの作成 31 | * STEP11: 集計ページの作成 32 | 33 | ## ソースコードの取得 34 | 35 | ``` 36 | $ go env GOPATH 37 | $ cd ↑のディレクトリに移動 38 | $ mkdir -p src/github.com/tenntenn/ 39 | $ cd src/github.com/tenntenn 40 | $ git clone https://github.com/tenntenn/gohandson.git 41 | $ cd accountbook 42 | ``` 43 | -------------------------------------------------------------------------------- /accountbook/skeleton/step01/.gitignore: -------------------------------------------------------------------------------- 1 | step01 2 | step01.exe 3 | -------------------------------------------------------------------------------- /accountbook/skeleton/step01/README.md: -------------------------------------------------------------------------------- 1 | # STEP01: Goに触れる 2 | 3 | ## 新しく学ぶこと 4 | 5 | * Goのプログラムの書き方 6 | * Goのプログラムの実行の仕方 7 | * 文字列の表示方法 8 | 9 | ## 動かし方 10 | 11 | ``` 12 | $ go build -v -o step01 13 | $ ./step01 14 | ``` 15 | 16 | ※ Windowsの方は以下のように後ろに.exeつけてください。 17 | 他のステップでも同様につけておいてください。 18 | 19 | ``` 20 | $ go build -v -o step01.exe 21 | $ .¥step01.exe 22 | ``` 23 | -------------------------------------------------------------------------------- /accountbook/skeleton/step01/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step01 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step01/main.go: -------------------------------------------------------------------------------- 1 | // STEP01: Goに触れる 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // main関数から実行される 7 | func main() { 8 | // 「Hello, 世界」と出力 9 | println("Hello, 世界") 10 | } 11 | -------------------------------------------------------------------------------- /accountbook/skeleton/step02/.gitignore: -------------------------------------------------------------------------------- 1 | step02 2 | step02.exe 3 | -------------------------------------------------------------------------------- /accountbook/skeleton/step02/README.md: -------------------------------------------------------------------------------- 1 | # STEP02: データの入力 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 標準パッケージの使い方 6 | * 変数と型 7 | * fmt.Printを使った表示 8 | * fmt.Scanを使った入力 9 | * fmt.Printlnを使った表示 10 | * fmt.Printfを使った表示 11 | 12 | ## 動かし方 13 | 14 | ``` 15 | $ go build -v -o step02 16 | $ ./step02 17 | ``` 18 | -------------------------------------------------------------------------------- /accountbook/skeleton/step02/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step02 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step02/main.go: -------------------------------------------------------------------------------- 1 | // STEP02: データの入力 2 | 3 | package main 4 | 5 | // fmtパッケージをインポートする 6 | import "fmt" 7 | 8 | func main() { 9 | // TODO: 品目を入れる変数を定義 10 | 11 | // 値段を入れる変数を定義 12 | var price int 13 | 14 | // 「品目>」と表示する 15 | fmt.Print("品目>") 16 | // 入力した結果をcategoryに入れる 17 | fmt.Scan(&category) 18 | 19 | // TODO: 「値段>」と表示する 20 | // TODO: 入力した結果をpriceに入れる 21 | 22 | // 「===========」と出力して改行する 23 | fmt.Println("===========") 24 | 25 | // 品目に「コーヒー」、値段に「100」と入力した場合に 26 | // 「コーヒーに100円使いました」と表示する 27 | fmt.Printf("%sに%d円使いました\n", category, price) 28 | 29 | // 「===========」と出力して改行する 30 | fmt.Println("===========") 31 | } 32 | -------------------------------------------------------------------------------- /accountbook/skeleton/step03/.gitignore: -------------------------------------------------------------------------------- 1 | step03 2 | step03.exe 3 | -------------------------------------------------------------------------------- /accountbook/skeleton/step03/README.md: -------------------------------------------------------------------------------- 1 | # STEP03: データの記録 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 構造体 6 | * 型の定義 7 | * 関数 8 | * :=による代入 9 | 10 | ## 動かし方 11 | 12 | ``` 13 | $ go build -v -o step03 14 | $ ./step03 15 | ``` 16 | -------------------------------------------------------------------------------- /accountbook/skeleton/step03/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step03 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step03/main.go: -------------------------------------------------------------------------------- 1 | // STEP03: データの記録 2 | 3 | package main 4 | 5 | import "fmt" 6 | 7 | // TODO: 8 | // 品目と値段を一緒に扱うために 9 | // Itemという構造体の型を定義する 10 | // Categoryという品目を入れる文字列型のフィールドを持つ 11 | // Priceという値段を入れる整数型のフィールドを持つ 12 | 13 | func main() { 14 | 15 | // TODO: 16 | // inputItemという関数を呼び出し 17 | // 結果をitemという変数に入れる 18 | 19 | fmt.Println("===========") 20 | 21 | // TODO: 22 | // 品目に「コーヒー」、値段に「100」と入力した場合に 23 | // 「コーヒーに100円使いました」と表示する 24 | 25 | fmt.Println("===========") 26 | } 27 | 28 | // 入力を行う関数 29 | // 入力したItemを返す 30 | func inputItem() Item { 31 | // Item型のitemという名前の変数を定義する 32 | var item Item 33 | 34 | fmt.Print("品目>") 35 | // TODO: 入力した値をitemのCategoryフィールドに入れる 36 | 37 | fmt.Print("値段>") 38 | // 入力した値をitemのPriceフィールドに入れる 39 | fmt.Scan(&item.Price) 40 | 41 | // TODO: itemを返す 42 | } 43 | -------------------------------------------------------------------------------- /accountbook/skeleton/step04/.gitignore: -------------------------------------------------------------------------------- 1 | step04 2 | step04.exe 3 | -------------------------------------------------------------------------------- /accountbook/skeleton/step04/README.md: -------------------------------------------------------------------------------- 1 | # STEP04: 複数データの記録 2 | 3 | ## 新しく学ぶこと 4 | 5 | * スライス 6 | * スライスの初期化 7 | * 容量の取得 8 | * スライスの追加 9 | * スライスの長さの取得 10 | * スライスのi番目の要素のアクセス方法 11 | * 関数の引数 12 | 13 | ## 動かし方 14 | 15 | ``` 16 | $ go build -v -o step04 17 | $ ./step04 18 | ``` 19 | -------------------------------------------------------------------------------- /accountbook/skeleton/step04/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step04 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step04/main.go: -------------------------------------------------------------------------------- 1 | // STEP04: 複数データの記録 2 | 3 | package main 4 | 5 | import "fmt" 6 | 7 | type Item struct { 8 | Category string 9 | Price int 10 | } 11 | 12 | func main() { 13 | 14 | // TODO: 入力するデータの件数を入力してもらいnに入れる 15 | var n int 16 | 17 | // TODO: 18 | // 複数のItem型の値を記録するために 19 | // itemsという名前のItem型のスライスの変数を定義 20 | // 長さ0で容量がnのスライスを作る 21 | 22 | // iが0からitemsの容量-1の間繰り返す(n回繰り返す) 23 | // cap(items)はitemsの容量を返す 24 | for i := 0; ; /* TODO: 継続条件 */ i++ { 25 | items = inputItem(items) 26 | } 27 | 28 | // 表示 29 | showItems(items) 30 | } 31 | 32 | // 入力を行う関数 33 | // 追加を行うItemのスライスを受け取る 34 | // 新しく入力したItemをスライスに追加して返す 35 | func inputItem( /* TODO: Itemのスライスを受け取る */ ) []Item { 36 | var item Item 37 | 38 | fmt.Print("品目>") 39 | fmt.Scan(&item.Category) 40 | 41 | fmt.Print("値段>") 42 | fmt.Scan(&item.Price) 43 | 44 | // TODO: 45 | // スライスに新しく入力したitemを追加する 46 | 47 | return items 48 | } 49 | 50 | // 一覧の表示を行う関数 51 | func showItems(items []Item) { 52 | fmt.Println("===========") 53 | 54 | // itemsの長さだけforを回す 55 | // len(items)はitemsの長さを返す 56 | for i := 0; ; /* TODO: 継続条件 */ i++ { 57 | // TODO: 「コーヒー:120円」のように出す 58 | // items[i]はitemsのi番目の要素(0からスタートする) 59 | } 60 | 61 | fmt.Println("===========") 62 | } 63 | -------------------------------------------------------------------------------- /accountbook/skeleton/step05/.gitignore: -------------------------------------------------------------------------------- 1 | step05 2 | step05.exe 3 | *.txt 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step05/README.md: -------------------------------------------------------------------------------- 1 | # STEP05: ファイルへの保存 2 | 3 | ## 新しく学ぶこと 4 | 5 | * ファイルへの保存 6 | * defer 7 | * エラー処理 8 | * bufio.Scanner 9 | * strings.Split 10 | * strconv.Atoi 11 | 12 | ## 動かし方 13 | 14 | ``` 15 | $ go build -v -o step05 16 | $ ./step05 17 | ``` 18 | -------------------------------------------------------------------------------- /accountbook/skeleton/step05/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step05 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step05/main.go: -------------------------------------------------------------------------------- 1 | // STEP05: ファイルへの保存 2 | 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "errors" 8 | "fmt" 9 | "log" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type Item struct { 16 | Category string 17 | Price int 18 | } 19 | 20 | func main() { 21 | 22 | // TODO: "accountbook.txt"という名前のファイルを書き込み用で開く 23 | // 開く場合にエラーが発生した場合 24 | if err != nil { 25 | // エラーを出力して終了する 26 | log.Fatal(err) 27 | } 28 | 29 | // 入力するデータの件数を入力する 30 | var n int 31 | fmt.Print("何件入力しますか>") 32 | fmt.Scan(&n) 33 | 34 | // n回繰り返す 35 | for i := 0; i < n; i++ { 36 | if err := inputItem(file); err != nil { 37 | // エラーを出力して終了する 38 | log.Fatal(err) 39 | } 40 | } 41 | 42 | if err := file.Close(); err != nil { 43 | // エラーを出力して終了する 44 | log.Fatal(err) 45 | } 46 | 47 | // 表示 48 | if err := showItems(); err != nil { 49 | // エラーを出力して終了する 50 | log.Fatal(err) 51 | } 52 | } 53 | 54 | // 入力を行いファイルに保存する 55 | // エラーが発生した場合にはそのまま返す 56 | func inputItem(file *os.File) error { 57 | var item Item 58 | 59 | fmt.Print("品目>") 60 | fmt.Scan(&item.Category) 61 | 62 | fmt.Print("値段>") 63 | fmt.Scan(&item.Price) 64 | 65 | // ファイルに書き出す 66 | // 「品目 値段」のように書き出す 67 | line := fmt.Sprintf("%s %d\n", item.Category, item.Price) 68 | if _, err := file.WriteString(line); err != nil { 69 | // エラーが発生した場合はエラーを返す 70 | return err 71 | } 72 | 73 | // TODO: エラーがなかったことを表すnilを返す 74 | } 75 | 76 | // 一覧の表示を行う関数 77 | func showItems() error { 78 | 79 | // "accountbook.txt"という名前のファイルを読み込み用で開く 80 | file, err := os.Open("accountbook.txt") 81 | // 開く場合にエラーが発生した場合 82 | if err != nil { 83 | return err 84 | } 85 | 86 | fmt.Println("===========") 87 | 88 | scanner := bufio.NewScanner(file) 89 | // 1行ずつ読み込む 90 | for scanner.Scan() { 91 | // TODO: 1行分を取り出す 92 | 93 | // 1行をスペースで分割する 94 | splited := strings.Split(line, " ") 95 | // 2つに分割できなかった場合はエラー 96 | if len(splited) != 2 { 97 | // TODO: 「パースに失敗しました」というエラーを生成して返す 98 | } 99 | 100 | // 1つめが品目 101 | category := splited[0] 102 | 103 | // 2つめが値段 104 | // TODO: string型をint型に変換する 105 | if err != nil { 106 | return err 107 | } 108 | 109 | fmt.Printf("%s:%d円\n", category, price) 110 | } 111 | 112 | // エラーが発生したかどうか調べる 113 | if err := scanner.Err(); err != nil { 114 | return err 115 | } 116 | 117 | fmt.Println("===========") 118 | 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /accountbook/skeleton/step06/.gitignore: -------------------------------------------------------------------------------- 1 | step06 2 | step06.exe 3 | *.txt 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step06/README.md: -------------------------------------------------------------------------------- 1 | # STEP06: ブラッシュアップ 2 | 3 | ## 新しく学ぶこと 4 | 5 | * メソッド 6 | * 複数の戻り値 7 | * fmt.Fprintln 8 | * os.Stderr 9 | * os.Exit 10 | * ポインタ 11 | * 無限ループ 12 | * ラベルつきbreak 13 | * switch 14 | * for range 15 | * スライス演算 16 | 17 | ## 動かし方 18 | 19 | ``` 20 | $ go build -v -o step06 21 | $ ./step06 22 | ``` 23 | -------------------------------------------------------------------------------- /accountbook/skeleton/step06/accountbook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type Item struct { 13 | Category string 14 | Price int 15 | } 16 | 17 | // 家計簿の処理を行う型 18 | type AccountBook struct { 19 | fileName string 20 | } 21 | 22 | // 新しいAccountBookを作成する 23 | func NewAccountBook(fileName string) *AccountBook { 24 | // AccountBookのポインタを返す 25 | return &AccountBook{fileName: fileName} 26 | } 27 | 28 | // ファイルに新しいItemを追加する 29 | func (ab *AccountBook) AddItem(item *Item) error { 30 | 31 | // TODO: 追記用でファイルを開く 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // 「品目 値段」の形式でファイルに出力する 37 | if _, err := /* TODO: fmt.Printlnを使って出力 */; err != nil { 38 | return err 39 | } 40 | 41 | // TODO: ファイルを閉じる 42 | 43 | return nil 44 | } 45 | 46 | // 最近追加したものを最大limit件だけItemを取得する 47 | // エラーが発生したら第2戻り値で返す 48 | func (ab *AccountBook) GetItems(limit int) ([]*Item, error) { 49 | 50 | // 読み込み用でファイルを開く 51 | file, err := os.Open(ab.fileName) 52 | if err != nil { 53 | return nil, err 54 | } 55 | defer file.Close() // 関数終了時にCloseが呼び出される 56 | 57 | scanner := bufio.NewScanner(file) 58 | var items []*Item 59 | 60 | // 1行ずつ読み込む 61 | for scanner.Scan() { 62 | var item Item 63 | 64 | // 1行ずつパースする 65 | if err := ab.parseLine(scanner.Text(), &item); err != nil { 66 | return nil, err 67 | } 68 | items = append(items, &item) 69 | } 70 | 71 | if err = scanner.Err(); err != nil { 72 | return nil, err 73 | } 74 | 75 | // limit件より少ない場合は全件返す 76 | if len(items) < limit { 77 | return items, nil 78 | } 79 | 80 | // TODO: itemsの後方limit件だけを返す 81 | } 82 | 83 | // 1行ずつパースを行う 84 | func (ab *AccountBook) parseLine(line string, /* TODO: 引数を追加する */) error { 85 | // 1行をスペースで分割する 86 | splited := strings.Split(line, " ") 87 | // 2つに分割できなかった場合はエラー 88 | if len(splited) != 2 { 89 | // エラーを生成して返す 90 | return errors.New("パースに失敗しました") 91 | } 92 | 93 | // 1つめが品目 94 | category := splited[0] 95 | 96 | // 2つめが値段 97 | // string型をint型に変換する 98 | price, err := strconv.Atoi(splited[1]) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | item.Category = category 104 | item.Price = price 105 | 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /accountbook/skeleton/step06/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step06 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step06/main.go: -------------------------------------------------------------------------------- 1 | // STEP06: ブラッシュアップ 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | 12 | // AccountBookをNewAccountBookを使って作成 13 | ab := NewAccountBook("accountbook.txt") 14 | 15 | LOOP: // 以下のループにラベル「LOOP」をつける 16 | for { 17 | 18 | // モードを選択して実行する 19 | var mode int 20 | fmt.Println("[1]入力 [2]最新10件 [3]終了") 21 | fmt.Printf(">") 22 | fmt.Scan(&mode) 23 | 24 | // モードによって処理を変える 25 | switch mode { 26 | case 1: // 入力 27 | var n int 28 | fmt.Print("何件入力しますか>") 29 | fmt.Scan(&n) 30 | 31 | for i := 0; i < n; i++ { 32 | if err := ab.AddItem(inputItem()); err != nil { 33 | // TODO: os.Stderrにエラーメッセージを出す 34 | break LOOP 35 | } 36 | } 37 | case 2: // 最新10件 38 | items, err := ab.GetItems(10) 39 | if err != nil { 40 | fmt.Fprintln(os.Stderr, "エラー:", err) 41 | // TODO: LOOPという名前のついたforから抜け出す 42 | } 43 | showItems(items) 44 | case 3: // 終了 45 | // TODO: 3のとき「終了します」と出力して終了する 46 | } 47 | } 48 | } 49 | 50 | // Itemを入力し返す 51 | func inputItem() *Item { 52 | var item Item 53 | 54 | fmt.Print("品目>") 55 | fmt.Scan(&item.Category) 56 | 57 | fmt.Print("値段>") 58 | fmt.Scan(&item.Price) 59 | 60 | return &item 61 | } 62 | 63 | // Itemの一覧を出力する 64 | func showItems(items []*Item) { 65 | fmt.Println("===========") 66 | // itemsの要素を1つずつ取り出してitemに入れて繰り返す 67 | for _, item := range items { 68 | fmt.Printf("%s:%d円\n", item.Category, item.Price) 69 | } 70 | fmt.Println("===========") 71 | } 72 | -------------------------------------------------------------------------------- /accountbook/skeleton/step07/.gitignore: -------------------------------------------------------------------------------- 1 | step07 2 | step07.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step07/README.md: -------------------------------------------------------------------------------- 1 | # STEP07: データベースへの記録 2 | 3 | ## 新しく学ぶこと 4 | 5 | * サードパーティパッケージの使い方 6 | * database/sqlパッケージの使い方 7 | * テーブルの作成 8 | * INSERT 9 | * SELECT 10 | * fmt.Printfの%04d 11 | 12 | ## 動かし方 13 | 14 | ``` 15 | $ go build -v -o step07 16 | $ ./step07 17 | ``` 18 | -------------------------------------------------------------------------------- /accountbook/skeleton/step07/accountbook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | type Item struct { 8 | ID int 9 | Category string 10 | Price int 11 | } 12 | 13 | // 家計簿の処理を行う型 14 | type AccountBook struct { 15 | db *sql.DB 16 | } 17 | 18 | // 新しいAccountBookを作成する 19 | func NewAccountBook(db *sql.DB) *AccountBook { 20 | // AccountBookのポインタを返す 21 | return &AccountBook{db: db} 22 | } 23 | 24 | // テーブルがなかったら作成する 25 | func (ab *AccountBook) CreateTable() error { 26 | const sqlStr = `CREATE TABLE IF NOT EXISTS items( 27 | id INTEGER PRIMARY KEY, 28 | category TEXT NOT NULL, 29 | price INTEGER NOT NULL 30 | );` 31 | 32 | _, err := ab.db.Exec(sqlStr) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // データベースに新しいItemを追加する 41 | func (ab *AccountBook) AddItem(item *Item) error { 42 | // TODO: 43 | // SQLのINSERTを使ってデータベースに保存する 44 | // ?の部分にcategoryやpriceの値が来る 45 | const sqlStr = `INSERT INTO items(category, price) VALUES (?,?);` 46 | if err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | // 最近追加したものを最大limit件だけItemを取得する 53 | // エラーが発生したら第2戻り値で返す 54 | func (ab *AccountBook) GetItems(limit int) ([]*Item, error) { 55 | // TODO: 56 | // SELECTでitemsテーブルの最新limit件を取得lする 57 | // ORDER BY id DESCでidの降順(大きい順)=最近追加したものが先にくる 58 | // LIMITで件数を最大の取得する件数を絞る 59 | const sqlStr = `SELECT * FROM items ORDER BY id DESC LIMIT ?` 60 | if err != nil { 61 | return nil, err 62 | } 63 | defer rows.Close() // 関数終了時にCloseが呼び出される 64 | 65 | var items []*Item 66 | // 1つずつ取得した行をみる 67 | // rows.Nextはすべての行を取得し終わるとfalseを返す 68 | for rows.Next() { 69 | var item Item 70 | // TODO: 71 | // rows.Scanで取得した行からデータを取り出し、itemの各フィールドに入れる 72 | if err != nil { 73 | return nil, err 74 | } 75 | items = append(items, &item) 76 | } 77 | 78 | if err = rows.Err(); err != nil { 79 | return nil, err 80 | } 81 | 82 | return items, nil 83 | } 84 | -------------------------------------------------------------------------------- /accountbook/skeleton/step07/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step07 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/skeleton/step07/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 h1:/NRJ5vAYoqz+7sG51ubIDHXeWO8DlTSrToPu6q11ziA= 4 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 5 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2 h1:gQEyYL/177Pg4mMPsTbNi6CCYxruMBAjgNs9E5kcnE4= 6 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 7 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 8 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 9 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= 10 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 11 | modernc.org/ccgo v1.0.0 h1:aIU6fp+ic9v4M6l6IAb0LD8byPDmtOhKXRnrNwkp88o= 12 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 13 | modernc.org/ccir v1.0.0 h1:fAushdwIOmC+RLDpcFRp26UPHHJbvO4AQ5vt8BUZEyE= 14 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 15 | modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= 16 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 17 | modernc.org/mathutil v1.0.0 h1:93vKjrJopTPrtTNpZ8XIovER7iCIH1QU7wNbOQXC60I= 18 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 19 | modernc.org/memory v1.0.0 h1:Tm1p6vBp/U/SGR9/EeFhMvGzaVpUWeePopZhhIpW2YE= 20 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 21 | modernc.org/sqlite v1.0.0 h1:xr+yDm5hNzINGa4obXy+eq/vr3iYxpeCrHkkwTrqHT0= 22 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 23 | -------------------------------------------------------------------------------- /accountbook/skeleton/step07/main.go: -------------------------------------------------------------------------------- 1 | // STEP07: データベースへの記録 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | // TODO: 9 | // SQLiteのドライバを使うために 10 | // "github.com/tenntenn/sqlite"をインポートする 11 | ) 12 | 13 | func main() { 14 | 15 | // TODO: 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // ドライバ名はsqlite.DriverName 19 | // accountbook.dbというファイルでデータベース接続を行う 20 | if err != nil { 21 | // 標準エラー出力(os.Stderr)にエラーメッセージを出力して終了 22 | fmt.Fprintln(os.Stderr, "エラー:", err) 23 | // ステータスコードを1で終了 24 | os.Exit(1) 25 | } 26 | 27 | // AccountBookをNewAccountBookを使って作成 28 | ab := NewAccountBook(db) 29 | 30 | // テーブルを作成 31 | if err := ab.CreateTable(); err != nil { 32 | // 標準エラー出力(os.Stderr)にエラーメッセージを出力して終了 33 | fmt.Fprintln(os.Stderr, "エラー:", err) 34 | // ステータスコードを1で終了 35 | os.Exit(1) 36 | } 37 | 38 | LOOP: // 以下のループにラベル「LOOP」をつける 39 | for { 40 | 41 | // モードを選択して実行する 42 | var mode int 43 | fmt.Println("[1]入力 [2]最新10件 [3]終了") 44 | fmt.Printf(">") 45 | fmt.Scan(&mode) 46 | 47 | // モードによって処理を変える 48 | switch mode { 49 | case 1: // 入力 50 | var n int 51 | fmt.Print("何件入力しますか>") 52 | fmt.Scan(&n) 53 | 54 | for i := 0; i < n; i++ { 55 | if err := ab.AddItem(inputItem()); err != nil { 56 | fmt.Fprintln(os.Stderr, "エラー:", err) 57 | break LOOP 58 | } 59 | } 60 | case 2: // 最新10件 61 | items, err := ab.GetItems(10) 62 | if err != nil { 63 | fmt.Fprintln(os.Stderr, "エラー:", err) 64 | break LOOP 65 | } 66 | showItems(items) 67 | case 3: // 終了 68 | fmt.Println("終了します") 69 | return 70 | } 71 | } 72 | } 73 | 74 | // Itemを入力し返す 75 | func inputItem() *Item { 76 | var item Item 77 | 78 | fmt.Print("品目>") 79 | fmt.Scan(&item.Category) 80 | 81 | fmt.Print("値段>") 82 | fmt.Scan(&item.Price) 83 | 84 | return &item 85 | } 86 | 87 | // Itemの一覧を出力する 88 | func showItems(items []*Item) { 89 | fmt.Println("===========") 90 | // itemsの要素を1つずつ取り出してitemに入れて繰り返す 91 | for _, item := range items { 92 | fmt.Printf("[%04d] %s:%d円\n", item.ID, item.Category, item.Price) 93 | } 94 | fmt.Println("===========") 95 | } 96 | -------------------------------------------------------------------------------- /accountbook/skeleton/step08/.gitignore: -------------------------------------------------------------------------------- 1 | step08 2 | step08.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step08/README.md: -------------------------------------------------------------------------------- 1 | # STEP08: 品目ごとの集計 2 | 3 | ## 新しく学ぶこと 4 | 5 | * GROUP BYと集約関数(sum, count) 6 | * fmt.Printfの\tと%f 7 | * float64 8 | * キャスト 9 | 10 | ## 動かし方 11 | 12 | ``` 13 | $ go build -v -o step08 14 | $ ./step08 15 | ``` 16 | -------------------------------------------------------------------------------- /accountbook/skeleton/step08/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step08 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/skeleton/step08/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 5 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 7 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 8 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 9 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 10 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 11 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 12 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 13 | -------------------------------------------------------------------------------- /accountbook/skeleton/step09/.gitignore: -------------------------------------------------------------------------------- 1 | step09 2 | step09.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step09/README.md: -------------------------------------------------------------------------------- 1 | # STEP09: 一覧ページの作成 2 | 3 | ## 新しく学ぶこと 4 | 5 | * HTTPハンドラ 6 | * HTTPサーバの起動 7 | * html/templateの使い方 8 | 9 | ## 動かし方 10 | 11 | `accountbook.db`はSTEP07のプログラムで作ったものを利用しましょう。 12 | 13 | ``` 14 | $ go build -v -o step09 15 | $ ./step09 16 | ``` 17 | -------------------------------------------------------------------------------- /accountbook/skeleton/step09/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step09 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/skeleton/step09/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2 h1:gQEyYL/177Pg4mMPsTbNi6CCYxruMBAjgNs9E5kcnE4= 5 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 7 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 8 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 9 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 10 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 11 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 12 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 13 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 14 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 15 | -------------------------------------------------------------------------------- /accountbook/skeleton/step09/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | // HTTPハンドラを集めた型 9 | type Handlers struct { 10 | ab *AccountBook 11 | } 12 | 13 | // Handlersを作成する 14 | func NewHandlers(ab *AccountBook) *Handlers { 15 | return &Handlers{ab: ab} 16 | } 17 | 18 | // ListHandlerで仕様するテンプレート 19 | var listTmpl = template.Must(template.New("list").Parse(` 20 | 21 | 22 | 23 | 家計簿 24 | 25 | 26 |

家計簿

27 |

最新{{len .}}件

28 | {{- if . -}} 29 | 30 | 31 | {{- range .}} 32 | 33 | {{- end}} 34 |
品目値段
{{- /* TODO: 品目を埋め込む */ -}}{{.Price}}円
35 | {{- else}} 36 | データがありません 37 | {{- end}} 38 | 39 | 40 | `)) 41 | 42 | // 最新の入力データを表示するハンドラ 43 | func (hs *Handlers) ListHandler(w http.ResponseWriter, r *http.Request) { 44 | // TODO: 最新の10件を取得し、itemsに入れる 45 | if err != nil { 46 | // TODO: http.Errorを使って、InternalServerErrorでエラーを返す 47 | return 48 | } 49 | 50 | // TODO: 取得したitemsをテンプレートに埋め込む 51 | if /* ここに書く */; err != nil { 52 | http.Error(w, err.Error(), http.StatusInternalServerError) 53 | return 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /accountbook/skeleton/step09/main.go: -------------------------------------------------------------------------------- 1 | // STEP09: 一覧ページの作成 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/tenntenn/sqlite" 12 | ) 13 | 14 | func main() { 15 | 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // accountbook.dbというファイルでデータベース接続を行う 19 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // AccountBookをNewAccountBookを使って作成 25 | ab := NewAccountBook(db) 26 | 27 | // テーブルを作成 28 | if err := ab.CreateTable(); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // HandlersをNewHandlersを使って作成 33 | hs := NewHandlers(ab) 34 | 35 | // TODO: ハンドラの登録 36 | 37 | fmt.Println("http://localhost:8080 で起動中...") 38 | // HTTPサーバを起動する 39 | log.Fatal(http.ListenAndServe(":8080", nil)) 40 | } 41 | -------------------------------------------------------------------------------- /accountbook/skeleton/step10/.gitignore: -------------------------------------------------------------------------------- 1 | step10 2 | step10.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step10/README.md: -------------------------------------------------------------------------------- 1 | # STEP10: 入力ページの作成 2 | 3 | ## 新しく学ぶこと 4 | 5 | * HTMLのform 6 | * HTTPメソッドの取得 7 | * POSTされたデータの取得 8 | * リダイレクト 9 | 10 | ## 動かし方 11 | 12 | ``` 13 | $ go build -v -o step09 14 | $ ./step09 15 | ``` 16 | -------------------------------------------------------------------------------- /accountbook/skeleton/step10/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step10 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/skeleton/step10/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 5 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 7 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 8 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 9 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 10 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 11 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 12 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 13 | -------------------------------------------------------------------------------- /accountbook/skeleton/step10/main.go: -------------------------------------------------------------------------------- 1 | // STEP10: 入力ページの作成 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/tenntenn/sqlite" 12 | ) 13 | 14 | func main() { 15 | 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // accountbook.dbというファイルでデータベース接続を行う 19 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // AccountBookをNewAccountBookを使って作成 25 | ab := NewAccountBook(db) 26 | 27 | // テーブルを作成 28 | if err := ab.CreateTable(); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // HandlersをNewHandlersを使って作成 33 | hs := NewHandlers(ab) 34 | 35 | // ハンドラの登録 36 | http.HandleFunc("/", hs.ListHandler) 37 | // TODO: SaveHandlerの登録 38 | 39 | fmt.Println("http://localhost:8080 で起動中...") 40 | // HTTPサーバを起動する 41 | log.Fatal(http.ListenAndServe(":8080", nil)) 42 | } 43 | -------------------------------------------------------------------------------- /accountbook/skeleton/step11/.gitignore: -------------------------------------------------------------------------------- 1 | step11 2 | step11.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/skeleton/step11/README.md: -------------------------------------------------------------------------------- 1 | # STEP11: 集計ページの作成 2 | 3 | ## 新しく学ぶこと 4 | 5 | * Google Chart API 6 | 7 | 8 | ``` 9 | $ go build -v -o step11 10 | $ ./step11 11 | ``` 12 | -------------------------------------------------------------------------------- /accountbook/skeleton/step11/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step10 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/skeleton/step11/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 5 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 7 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 8 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 9 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 10 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 11 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 12 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 13 | -------------------------------------------------------------------------------- /accountbook/skeleton/step11/main.go: -------------------------------------------------------------------------------- 1 | // STEP11: 集計ページの作成 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/tenntenn/sqlite" 12 | ) 13 | 14 | func main() { 15 | 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // accountbook.dbというファイルでデータベース接続を行う 19 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // AccountBookをNewAccountBookを使って作成 25 | ab := NewAccountBook(db) 26 | 27 | // テーブルを作成 28 | if err := ab.CreateTable(); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // HandlersをNewHandlersを使って作成 33 | hs := NewHandlers(ab) 34 | 35 | // ハンドラの登録 36 | http.HandleFunc("/", hs.ListHandler) 37 | http.HandleFunc("/save", hs.SaveHandler) 38 | http.HandleFunc("/summary", hs.SummaryHandler) 39 | 40 | fmt.Println("http://localhost:8080 で起動中...") 41 | // HTTPサーバを起動する 42 | log.Fatal(http.ListenAndServe(":8080", nil)) 43 | } 44 | -------------------------------------------------------------------------------- /accountbook/solution/step01/.gitignore: -------------------------------------------------------------------------------- 1 | step01 2 | step01.exe 3 | -------------------------------------------------------------------------------- /accountbook/solution/step01/README.md: -------------------------------------------------------------------------------- 1 | # STEP01: Goに触れる 2 | 3 | ## 新しく学ぶこと 4 | 5 | * Goのプログラムの書き方 6 | * Goのプログラムの実行の仕方 7 | * 文字列の表示方法 8 | 9 | ## 動かし方 10 | 11 | ``` 12 | $ go build -v -o step01 13 | $ ./step01 14 | ``` 15 | -------------------------------------------------------------------------------- /accountbook/solution/step01/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step01 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/solution/step01/main.go: -------------------------------------------------------------------------------- 1 | // STEP01: Goに触れる 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // main関数から実行される 7 | func main() { 8 | // 「Hello, 世界」と出力 9 | println("Hello, 世界") 10 | } 11 | -------------------------------------------------------------------------------- /accountbook/solution/step02/.gitignore: -------------------------------------------------------------------------------- 1 | step02 2 | step02.exe 3 | -------------------------------------------------------------------------------- /accountbook/solution/step02/README.md: -------------------------------------------------------------------------------- 1 | # STEP02: データの入力 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 標準パッケージの使い方 6 | * 変数と型 7 | * fmt.Printを使った表示 8 | * fmt.Scanを使った入力 9 | * fmt.Printlnを使った表示 10 | * fmt.Printfを使った表示 11 | 12 | ## 動かし方 13 | 14 | ``` 15 | $ go build -v -o step02 16 | $ ./step02 17 | ``` 18 | -------------------------------------------------------------------------------- /accountbook/solution/step02/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step02 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/solution/step02/main.go: -------------------------------------------------------------------------------- 1 | // STEP02: データの入力 2 | 3 | package main 4 | 5 | // fmtパッケージをインポートする 6 | import "fmt" 7 | 8 | func main() { 9 | // 品目を入れる変数を定義 10 | var category string 11 | // 値段を入れる変数を定義 12 | var price int 13 | 14 | // 「品目>」と表示する 15 | fmt.Print("品目>") 16 | // 入力した結果をcategoryに入れる 17 | fmt.Scan(&category) 18 | 19 | // 「値段>」と表示する 20 | fmt.Print("値段>") 21 | // 入力した結果をpriceに入れる 22 | fmt.Scan(&price) 23 | 24 | // 「===========」と出力して改行する 25 | fmt.Println("===========") 26 | 27 | // 品目に「コーヒー」、値段に「100」と入力した場合に 28 | // 「コーヒーに100円使いました」と表示する 29 | fmt.Printf("%sに%d円使いました\n", category, price) 30 | 31 | // 「===========」と出力して改行する 32 | fmt.Println("===========") 33 | } 34 | -------------------------------------------------------------------------------- /accountbook/solution/step03/.gitignore: -------------------------------------------------------------------------------- 1 | step03 2 | step03.exe 3 | -------------------------------------------------------------------------------- /accountbook/solution/step03/README.md: -------------------------------------------------------------------------------- 1 | # STEP03: データの記録 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 構造体 6 | * 型の定義 7 | * 関数 8 | * :=による代入 9 | 10 | ## 動かし方 11 | 12 | ``` 13 | $ go build -v -o step03 14 | $ ./step03 15 | ``` 16 | -------------------------------------------------------------------------------- /accountbook/solution/step03/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step03 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/solution/step03/main.go: -------------------------------------------------------------------------------- 1 | // STEP03: データの記録 2 | 3 | package main 4 | 5 | import "fmt" 6 | 7 | // 品目と値段を一緒に扱うために 8 | // Itemという構造体の型を定義する 9 | type Item struct { 10 | // Categoryは文字列型のフィールド 11 | Category string 12 | // Priceは整数型のフィールド 13 | Price int 14 | } 15 | 16 | func main() { 17 | 18 | // inputItemという関数を呼び出し 19 | // 結果をitemという変数に入れる 20 | item := inputItem() 21 | 22 | fmt.Println("===========") 23 | 24 | // 品目に「コーヒー」、値段に「100」と入力した場合に 25 | // 「コーヒーに100円使いました」と表示する 26 | fmt.Printf("%sに%d円使いました\n", item.Category, item.Price) 27 | 28 | fmt.Println("===========") 29 | } 30 | 31 | // 入力を行う関数 32 | // 入力したItemを返す 33 | func inputItem() Item { 34 | // Item型のitemという名前の変数を定義する 35 | var item Item 36 | 37 | fmt.Print("品目>") 38 | // 入力した値をitemのCategoryフィールドに入れる 39 | fmt.Scan(&item.Category) 40 | 41 | fmt.Print("値段>") 42 | // 入力した値をitemのPriceフィールドに入れる 43 | fmt.Scan(&item.Price) 44 | 45 | return item 46 | } 47 | -------------------------------------------------------------------------------- /accountbook/solution/step04/.gitignore: -------------------------------------------------------------------------------- 1 | step04 2 | step04.exe 3 | -------------------------------------------------------------------------------- /accountbook/solution/step04/README.md: -------------------------------------------------------------------------------- 1 | # STEP04: 複数データの記録 2 | 3 | ## 新しく学ぶこと 4 | 5 | * スライス 6 | * スライスの初期化 7 | * 容量の取得 8 | * スライスの追加 9 | * スライスの長さの取得 10 | * スライスのi番目の要素のアクセス方法 11 | * 関数の引数 12 | 13 | ## 動かし方 14 | 15 | ``` 16 | $ go build -v -o step04 17 | $ ./step04 18 | ``` 19 | -------------------------------------------------------------------------------- /accountbook/solution/step04/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step04 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/solution/step04/main.go: -------------------------------------------------------------------------------- 1 | // STEP04: 複数データの記録 2 | 3 | package main 4 | 5 | import "fmt" 6 | 7 | type Item struct { 8 | Category string 9 | Price int 10 | } 11 | 12 | func main() { 13 | 14 | // 入力するデータの件数を入力する 15 | var n int 16 | fmt.Print("何件入力しますか>") 17 | fmt.Scan(&n) 18 | 19 | // 複数のItem型の値を記録するために 20 | // itemsという名前のItem型のスライスの変数を定義 21 | // 長さ0で容量がnのスライスを作る 22 | items := make([]Item, 0, n) 23 | 24 | // iが0からitemsの容量-1の間繰り返す(n回繰り返す) 25 | // cap(items)はitemsの容量を返す 26 | for i := 0; i < cap(items); i++ { 27 | items = inputItem(items) 28 | } 29 | 30 | // 表示 31 | showItems(items) 32 | } 33 | 34 | // 入力を行う関数 35 | // 追加を行うItemのスライスを受け取る 36 | // 新しく入力したItemをスライスに追加して返す 37 | func inputItem(items []Item) []Item { 38 | var item Item 39 | 40 | fmt.Print("品目>") 41 | fmt.Scan(&item.Category) 42 | 43 | fmt.Print("値段>") 44 | fmt.Scan(&item.Price) 45 | 46 | // スライスに新しく入力したitemを追加する 47 | items = append(items, item) 48 | 49 | return items 50 | } 51 | 52 | // 一覧の表示を行う関数 53 | func showItems(items []Item) { 54 | fmt.Println("===========") 55 | 56 | // itemsの長さだけforを回す 57 | // len(items)はitemsの長さを返す 58 | for i := 0; i < len(items); i++ { 59 | // items[i]はitemsのi番目の要素(0からスタートする) 60 | fmt.Printf("%s:%d円\n", items[i].Category, items[i].Price) 61 | } 62 | 63 | fmt.Println("===========") 64 | } 65 | -------------------------------------------------------------------------------- /accountbook/solution/step05/.gitignore: -------------------------------------------------------------------------------- 1 | step05 2 | step05.exe 3 | *.txt 4 | -------------------------------------------------------------------------------- /accountbook/solution/step05/README.md: -------------------------------------------------------------------------------- 1 | # STEP05: ファイルへの保存 2 | 3 | ## 新しく学ぶこと 4 | 5 | * ファイルへの保存 6 | * defer 7 | * エラー処理 8 | * bufio.Scanner 9 | * strings.Split 10 | * strconv.Atoi 11 | 12 | ## 動かし方 13 | 14 | ``` 15 | $ go build -v -o step05 16 | $ ./step05 17 | ``` 18 | -------------------------------------------------------------------------------- /accountbook/solution/step05/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step05 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/solution/step06/.gitignore: -------------------------------------------------------------------------------- 1 | step06 2 | step06.exe 3 | *.txt 4 | -------------------------------------------------------------------------------- /accountbook/solution/step06/README.md: -------------------------------------------------------------------------------- 1 | # STEP06: ブラッシュアップ 2 | 3 | ## 新しく学ぶこと 4 | 5 | * メソッド 6 | * 複数の戻り値 7 | * fmt.Fprintln 8 | * os.Stderr 9 | * os.Exit 10 | * ポインタ 11 | * 無限ループ 12 | * ラベルつきbreak 13 | * switch 14 | * for range 15 | * スライス演算 16 | * 追記用のファイルの開き方 17 | 18 | ## 動かし方 19 | 20 | ``` 21 | $ go build -v -o step06 22 | $ ./step06 23 | ``` 24 | -------------------------------------------------------------------------------- /accountbook/solution/step06/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step06 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /accountbook/solution/step06/main.go: -------------------------------------------------------------------------------- 1 | // STEP06: ブラッシュアップ 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | 12 | // AccountBookをNewAccountBookを使って作成 13 | ab := NewAccountBook("accountbook.txt") 14 | 15 | LOOP: // 以下のループにラベル「LOOP」をつける 16 | for { 17 | 18 | // モードを選択して実行する 19 | var mode int 20 | fmt.Println("[1]入力 [2]最新10件 [3]終了") 21 | fmt.Printf(">") 22 | fmt.Scan(&mode) 23 | 24 | // モードによって処理を変える 25 | switch mode { 26 | case 1: // 入力 27 | var n int 28 | fmt.Print("何件入力しますか>") 29 | fmt.Scan(&n) 30 | 31 | for i := 0; i < n; i++ { 32 | if err := ab.AddItem(inputItem()); err != nil { 33 | fmt.Fprintln(os.Stderr, "エラー:", err) 34 | break LOOP 35 | } 36 | } 37 | case 2: // 最新10件 38 | items, err := ab.GetItems(10) 39 | if err != nil { 40 | fmt.Fprintln(os.Stderr, "エラー:", err) 41 | break LOOP 42 | } 43 | showItems(items) 44 | case 3: // 終了 45 | fmt.Println("終了します") 46 | return 47 | } 48 | } 49 | } 50 | 51 | // Itemを入力し返す 52 | func inputItem() *Item { 53 | var item Item 54 | 55 | fmt.Print("品目>") 56 | fmt.Scan(&item.Category) 57 | 58 | fmt.Print("値段>") 59 | fmt.Scan(&item.Price) 60 | 61 | return &item 62 | } 63 | 64 | // Itemの一覧を出力する 65 | func showItems(items []*Item) { 66 | fmt.Println("===========") 67 | // itemsの要素を1つずつ取り出してitemに入れて繰り返す 68 | for _, item := range items { 69 | fmt.Printf("%s:%d円\n", item.Category, item.Price) 70 | } 71 | fmt.Println("===========") 72 | } 73 | -------------------------------------------------------------------------------- /accountbook/solution/step07/.gitignore: -------------------------------------------------------------------------------- 1 | step07 2 | step07.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/solution/step07/README.md: -------------------------------------------------------------------------------- 1 | # STEP07: データベースへの記録 2 | 3 | ## 新しく学ぶこと 4 | 5 | * サードパーティパッケージの使い方 6 | * database/sqlパッケージの使い方 7 | * テーブルの作成 8 | * INSERT 9 | * SELECT 10 | * fmt.Printfの%04d 11 | 12 | ## 動かし方 13 | 14 | ``` 15 | $ go build -v -o step07 16 | $ ./step07 17 | ``` 18 | -------------------------------------------------------------------------------- /accountbook/solution/step07/accountbook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | type Item struct { 8 | ID int 9 | Category string 10 | Price int 11 | } 12 | 13 | // 家計簿の処理を行う型 14 | type AccountBook struct { 15 | db *sql.DB 16 | } 17 | 18 | // 新しいAccountBookを作成する 19 | func NewAccountBook(db *sql.DB) *AccountBook { 20 | // AccountBookのポインタを返す 21 | return &AccountBook{db: db} 22 | } 23 | 24 | // テーブルがなかったら作成する 25 | func (ab *AccountBook) CreateTable() error { 26 | const sqlStr = `CREATE TABLE IF NOT EXISTS items( 27 | id INTEGER PRIMARY KEY, 28 | category TEXT NOT NULL, 29 | price INTEGER NOT NULL 30 | );` 31 | 32 | _, err := ab.db.Exec(sqlStr) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // データベースに新しいItemを追加する 41 | func (ab *AccountBook) AddItem(item *Item) error { 42 | const sqlStr = `INSERT INTO items(category, price) VALUES (?,?);` 43 | _, err := ab.db.Exec(sqlStr, item.Category, item.Price) 44 | if err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | 50 | // 最近追加したものを最大limit件だけItemを取得する 51 | // エラーが発生したら第2戻り値で返す 52 | func (ab *AccountBook) GetItems(limit int) ([]*Item, error) { 53 | // ORDER BY id DESCでidの降順(大きい順)=最近追加したものが先にくる 54 | // LIMITで件数を最大の取得する件数を絞る 55 | const sqlStr = `SELECT * FROM items ORDER BY id DESC LIMIT ?` 56 | rows, err := ab.db.Query(sqlStr, limit) 57 | if err != nil { 58 | return nil, err 59 | } 60 | defer rows.Close() // 関数終了時にCloseが呼び出される 61 | 62 | var items []*Item 63 | // 1つずつ取得した行をみる 64 | // rows.Nextはすべての行を取得し終わるとfalseを返す 65 | for rows.Next() { 66 | var item Item 67 | err := rows.Scan(&item.ID, &item.Category, &item.Price) 68 | if err != nil { 69 | return nil, err 70 | } 71 | items = append(items, &item) 72 | } 73 | 74 | if err = rows.Err(); err != nil { 75 | return nil, err 76 | } 77 | 78 | return items, nil 79 | } 80 | -------------------------------------------------------------------------------- /accountbook/solution/step07/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step07 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/solution/step07/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 h1:/NRJ5vAYoqz+7sG51ubIDHXeWO8DlTSrToPu6q11ziA= 4 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 5 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2 h1:gQEyYL/177Pg4mMPsTbNi6CCYxruMBAjgNs9E5kcnE4= 6 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 7 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 8 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 9 | github.com/tenntenn/sqlite v1.0.2 h1:b7IRA375Ypp80KCkmnhuZdqMI7OFFwjLSU0Q928nc04= 10 | github.com/tenntenn/sqlite v1.0.2/go.mod h1:7MSQ3P3Gefd3Tcj/NSQsisdVcxfciQ7wGU3a+mdvFcQ= 11 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= 12 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 13 | modernc.org/ccgo v1.0.0 h1:aIU6fp+ic9v4M6l6IAb0LD8byPDmtOhKXRnrNwkp88o= 14 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 15 | modernc.org/ccir v1.0.0 h1:fAushdwIOmC+RLDpcFRp26UPHHJbvO4AQ5vt8BUZEyE= 16 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 17 | modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= 18 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 19 | modernc.org/mathutil v1.0.0 h1:93vKjrJopTPrtTNpZ8XIovER7iCIH1QU7wNbOQXC60I= 20 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 21 | modernc.org/memory v1.0.0 h1:Tm1p6vBp/U/SGR9/EeFhMvGzaVpUWeePopZhhIpW2YE= 22 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 23 | modernc.org/sqlite v1.0.0 h1:xr+yDm5hNzINGa4obXy+eq/vr3iYxpeCrHkkwTrqHT0= 24 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 25 | -------------------------------------------------------------------------------- /accountbook/solution/step07/main.go: -------------------------------------------------------------------------------- 1 | // STEP07: データベースへの記録 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "os" 9 | 10 | "github.com/tenntenn/sqlite" 11 | ) 12 | 13 | func main() { 14 | 15 | // データベースへ接続 16 | // ドライバにはSQLiteを使って、 17 | // accountbook.dbというファイルでデータベース接続を行う 18 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 19 | if err != nil { 20 | // 標準エラー出力(os.Stderr)にエラーメッセージを出力して終了 21 | fmt.Fprintln(os.Stderr, "エラー:", err) 22 | // ステータスコードを1で終了 23 | os.Exit(1) 24 | } 25 | 26 | // AccountBookをNewAccountBookを使って作成 27 | ab := NewAccountBook(db) 28 | 29 | // テーブルを作成 30 | if err := ab.CreateTable(); err != nil { 31 | // 標準エラー出力(os.Stderr)にエラーメッセージを出力して終了 32 | fmt.Fprintln(os.Stderr, "エラー:", err) 33 | // ステータスコードを1で終了 34 | os.Exit(1) 35 | } 36 | 37 | LOOP: // 以下のループにラベル「LOOP」をつける 38 | for { 39 | 40 | // モードを選択して実行する 41 | var mode int 42 | fmt.Println("[1]入力 [2]最新10件 [3]終了") 43 | fmt.Printf(">") 44 | fmt.Scan(&mode) 45 | 46 | // モードによって処理を変える 47 | switch mode { 48 | case 1: // 入力 49 | var n int 50 | fmt.Print("何件入力しますか>") 51 | fmt.Scan(&n) 52 | 53 | for i := 0; i < n; i++ { 54 | if err := ab.AddItem(inputItem()); err != nil { 55 | fmt.Fprintln(os.Stderr, "エラー:", err) 56 | break LOOP 57 | } 58 | } 59 | case 2: // 最新10件 60 | items, err := ab.GetItems(10) 61 | if err != nil { 62 | fmt.Fprintln(os.Stderr, "エラー:", err) 63 | break LOOP 64 | } 65 | showItems(items) 66 | case 3: // 終了 67 | fmt.Println("終了します") 68 | return 69 | } 70 | } 71 | } 72 | 73 | // Itemを入力し返す 74 | func inputItem() *Item { 75 | var item Item 76 | 77 | fmt.Print("品目>") 78 | fmt.Scan(&item.Category) 79 | 80 | fmt.Print("値段>") 81 | fmt.Scan(&item.Price) 82 | 83 | return &item 84 | } 85 | 86 | // Itemの一覧を出力する 87 | func showItems(items []*Item) { 88 | fmt.Println("===========") 89 | // itemsの要素を1つずつ取り出してitemに入れて繰り返す 90 | for _, item := range items { 91 | fmt.Printf("[%04d] %s:%d円\n", item.ID, item.Category, item.Price) 92 | } 93 | fmt.Println("===========") 94 | } 95 | -------------------------------------------------------------------------------- /accountbook/solution/step08/.gitignore: -------------------------------------------------------------------------------- 1 | step08 2 | step08.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/solution/step08/README.md: -------------------------------------------------------------------------------- 1 | # STEP08: 品目ごとの集計 2 | 3 | ## 新しく学ぶこと 4 | 5 | * GROUP BYと集約関数(sum, count) 6 | * fmt.Printfの\tと%f 7 | * float64 8 | * キャスト 9 | 10 | ## 動かし方 11 | 12 | ``` 13 | $ go build -v -o step08 14 | $ ./step08 15 | ``` 16 | -------------------------------------------------------------------------------- /accountbook/solution/step08/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step08 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/solution/step08/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 5 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 7 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 8 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 9 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 10 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 11 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 12 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 13 | -------------------------------------------------------------------------------- /accountbook/solution/step09/.gitignore: -------------------------------------------------------------------------------- 1 | step09 2 | step09.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/solution/step09/README.md: -------------------------------------------------------------------------------- 1 | # STEP09: 一覧ページの作成 2 | 3 | ## 新しく学ぶこと 4 | 5 | * HTTPハンドラ 6 | * HTTPサーバの起動 7 | * html/templateの使い方 8 | 9 | ## 動かし方 10 | 11 | `accountbook.db`はSTEP07のプログラムで作ったものを利用しましょう。 12 | 13 | ``` 14 | $ go build -v -o step09 15 | $ ./step09 16 | ``` 17 | -------------------------------------------------------------------------------- /accountbook/solution/step09/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step09 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/solution/step09/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2 h1:gQEyYL/177Pg4mMPsTbNi6CCYxruMBAjgNs9E5kcnE4= 5 | github.com/tenntenn/sqlite v0.0.0-20181202003551-51bf2e1b84b2/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 7 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 8 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 9 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 10 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 11 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 12 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 13 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 14 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 15 | -------------------------------------------------------------------------------- /accountbook/solution/step09/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | // HTTPハンドラを集めた型 9 | type Handlers struct { 10 | ab *AccountBook 11 | } 12 | 13 | // Handlersを作成する 14 | func NewHandlers(ab *AccountBook) *Handlers { 15 | return &Handlers{ab: ab} 16 | } 17 | 18 | // ListHandlerで仕様するテンプレート 19 | var listTmpl = template.Must(template.New("list").Parse(` 20 | 21 | 22 | 23 | 家計簿 24 | 25 | 26 |

家計簿

27 |

最新{{len .}}件

28 | {{- if . -}} 29 | 30 | 31 | {{- range .}} 32 | 33 | {{- end}} 34 |
品目値段
{{.Category}}{{.Price}}円
35 | {{- else}} 36 | データがありません 37 | {{- end}} 38 | 39 | 40 | `)) 41 | 42 | // 最新の入力データを表示するハンドラ 43 | func (hs *Handlers) ListHandler(w http.ResponseWriter, r *http.Request) { 44 | // 最新の10件を取得する 45 | items, err := hs.ab.GetItems(10) 46 | if err != nil { 47 | http.Error(w, err.Error(), http.StatusInternalServerError) 48 | return 49 | } 50 | 51 | // 取得したitemsをテンプレートに埋め込む 52 | if err := listTmpl.Execute(w, items); err != nil { 53 | http.Error(w, err.Error(), http.StatusInternalServerError) 54 | return 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /accountbook/solution/step09/main.go: -------------------------------------------------------------------------------- 1 | // STEP09: 一覧ページの作成 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/tenntenn/sqlite" 12 | ) 13 | 14 | func main() { 15 | 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // accountbook.dbというファイルでデータベース接続を行う 19 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // AccountBookをNewAccountBookを使って作成 25 | ab := NewAccountBook(db) 26 | 27 | // テーブルを作成 28 | if err := ab.CreateTable(); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // HandlersをNewHandlersを使って作成 33 | hs := NewHandlers(ab) 34 | 35 | // ハンドラの登録 36 | http.HandleFunc("/", hs.ListHandler) 37 | 38 | fmt.Println("http://localhost:8080 で起動中...") 39 | // HTTPサーバを起動する 40 | log.Fatal(http.ListenAndServe(":8080", nil)) 41 | } 42 | -------------------------------------------------------------------------------- /accountbook/solution/step10/.gitignore: -------------------------------------------------------------------------------- 1 | step10 2 | step10.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/solution/step10/README.md: -------------------------------------------------------------------------------- 1 | # STEP10: 入力ページの作成 2 | 3 | ## 新しく学ぶこと 4 | 5 | * HTMLのform 6 | * HTTPメソッドの取得 7 | * POSTされたデータの取得 8 | * リダイレクト 9 | 10 | ## 動かし方 11 | 12 | ``` 13 | $ go build -v -o step09 14 | $ ./step09 15 | ``` 16 | -------------------------------------------------------------------------------- /accountbook/solution/step10/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step10 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/solution/step10/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 5 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 7 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 8 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 9 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 10 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 11 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 12 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 13 | -------------------------------------------------------------------------------- /accountbook/solution/step10/main.go: -------------------------------------------------------------------------------- 1 | // STEP10: 入力ページの作成 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/tenntenn/sqlite" 12 | ) 13 | 14 | func main() { 15 | 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // accountbook.dbというファイルでデータベース接続を行う 19 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // AccountBookをNewAccountBookを使って作成 25 | ab := NewAccountBook(db) 26 | 27 | // テーブルを作成 28 | if err := ab.CreateTable(); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // HandlersをNewHandlersを使って作成 33 | hs := NewHandlers(ab) 34 | 35 | // ハンドラの登録 36 | http.HandleFunc("/", hs.ListHandler) 37 | http.HandleFunc("/save", hs.SaveHandler) 38 | 39 | fmt.Println("http://localhost:8080 で起動中...") 40 | // HTTPサーバを起動する 41 | log.Fatal(http.ListenAndServe(":8080", nil)) 42 | } 43 | -------------------------------------------------------------------------------- /accountbook/solution/step11/.gitignore: -------------------------------------------------------------------------------- 1 | step11 2 | step11.exe 3 | *.db 4 | -------------------------------------------------------------------------------- /accountbook/solution/step11/README.md: -------------------------------------------------------------------------------- 1 | # STEP11: 集計ページの作成 2 | 3 | ## 新しく学ぶこと 4 | 5 | * Google Chart API 6 | 7 | 8 | ``` 9 | $ go build -v -o step11 10 | $ ./step11 11 | ``` 12 | -------------------------------------------------------------------------------- /accountbook/solution/step11/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/accountbook/solution/step10 2 | 3 | go 1.13 4 | 5 | require github.com/tenntenn/sqlite v1.0.2 6 | -------------------------------------------------------------------------------- /accountbook/solution/step11/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 2 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 3 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 4 | github.com/tenntenn/sqlite v1.0.0 h1:O63wXbT+7iSVImX4YVZz1ftUXkoTsbLp29EtM/47lv4= 5 | github.com/tenntenn/sqlite v1.0.0/go.mod h1:/byHRbL6FFHA5gjdVVgAUPVwCxwU7Agywg7cV/K4zCQ= 6 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 7 | modernc.org/ccgo v1.0.0/go.mod h1:dDlyT3H3RutzvIEbd/GY5lg8AVoEKVkR0a4OYjV1A74= 8 | modernc.org/ccir v1.0.0/go.mod h1:U3yOB9KfPrYtLPFWiELNPrwE7GsBs/GR/vC77J8yQYU= 9 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 10 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 11 | modernc.org/memory v1.0.0/go.mod h1:TXr4iJDvK3g0hW+sV+Kohu7BoeHfqw7QEFZWkBExdZc= 12 | modernc.org/sqlite v1.0.0/go.mod h1:ld6H6WphfrVPcJaH5tGDD3LE5KSD2JT6d8/7tKYiKFc= 13 | -------------------------------------------------------------------------------- /accountbook/solution/step11/main.go: -------------------------------------------------------------------------------- 1 | // STEP11: 集計ページの作成 2 | 3 | package main 4 | 5 | import ( 6 | "database/sql" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/tenntenn/sqlite" 12 | ) 13 | 14 | func main() { 15 | 16 | // データベースへ接続 17 | // ドライバにはSQLiteを使って、 18 | // accountbook.dbというファイルでデータベース接続を行う 19 | db, err := sql.Open(sqlite.DriverName, "accountbook.db") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // AccountBookをNewAccountBookを使って作成 25 | ab := NewAccountBook(db) 26 | 27 | // テーブルを作成 28 | if err := ab.CreateTable(); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // HandlersをNewHandlersを使って作成 33 | hs := NewHandlers(ab) 34 | 35 | // ハンドラの登録 36 | http.HandleFunc("/", hs.ListHandler) 37 | http.HandleFunc("/save", hs.SaveHandler) 38 | http.HandleFunc("/summary", hs.SummaryHandler) 39 | 40 | fmt.Println("http://localhost:8080 で起動中...") 41 | // HTTPサーバを起動する 42 | log.Fatal(http.ListenAndServe(":8080", nil)) 43 | } 44 | -------------------------------------------------------------------------------- /goroutine/.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | -------------------------------------------------------------------------------- /goroutine/README.md: -------------------------------------------------------------------------------- 1 | # 分かるゴールーチンとチャネル 2 | 3 | ## はじめに 4 | 5 | このハンズオンでは、コーヒーを淹れるという作業を例にゴールーチンを扱います。 6 | 基本的な文法などは、このハンズオンでは扱いません。 7 | そのため、ハンズオンを始める前に、[A Tour of Go](https://go-tour-jp.appspot.com)を終わらせると良いでしょう。 8 | また、文法や周辺ツール、`GOPATH`などの詳しい説明は[公式ドキュメント](https://golang.org/doc/)の「Learning Go」の項目を読んでください。 9 | 特に他のオブジェクト指向言語などを学習されている方は、「FAQ」に目を通すとよいでしょう。 10 | 11 | 英語が辛い方は、有志によって[翻訳されたドキュメント](http://golang-jp.org/doc/)の「Goを学ぶ」を読んで下さい。 12 | すべてが翻訳されているわけではありませんが、役に立つでしょう。 13 | また、[はじめてのGo](http://gihyo.jp/dev/feature/01/go_4beginners)も日本語で書かれていて分かりやすいのでぜひ読んで下さい。 14 | 15 | 標準パッケージについては、[パッケージドキュメント](https://golang.org/pkg/)を見ると、使い方が説明されています。 16 | 17 | ## 学べることと学べないこと 18 | 19 | このハンズオンを行うと以下のことが学べます。 20 | 21 | * ゴールーチンとチャネルの基本 22 | * トレースの方法 23 | * `sync`パッケージの使い方 24 | * ゴールーチンとエラー処理 25 | * コンテキストとキャンセル処理 26 | 27 | 一方、学べないことは以下のとおりです。 28 | 29 | * Goの開発環境のインストール方法 30 | * IDEやエディタの設定 31 | * 基本的な文法 32 | * コマンドラインツールの作り方 33 | * net/httpパッケージ 34 | 35 | ## ハンズオンのやりかた 36 | 37 | `skeleton`ディレクトリ以下に問題があり、6つのステップに分けられています。 38 | STEP 1からSTEP 6までステップごとに進めていくことで、並行処理に関する知識が学べます。 39 | 40 | 各ステップに、READMEが用意されていますので、まずは`README`を読みます。 41 | `README`には、そのステップを理解するための解説が書かれています。 42 | 43 | `README`を読んだら、ソースコードを開き`TODO`コメントが書かれている箇所をコメントに従って修正して行きます。 44 | `TODO`コメントをすべて修正し終わったら、`README`に書かれた実行例に従ってプログラムをコンパイルして実行します。 45 | 46 | 途中でわからなくなった場合は、`solution`ディレクトリ以下に解答例を用意していますので、そちらをご覧ください。 47 | 48 | `Mac`と`Windows`で動作確認をしていますが、解説は`Mac`の動作結果をもとに解説しています。 49 | `Windows`の方は、パスの区切り文字等を適宜読み替えてください。 50 | 51 | ## 目次 52 | 53 | * STEP 1: [ゴールーチンを使わずに処理する](./skeleton/step01)([解答例](./solution/step01)) 54 | * STEP 2: [ボトルネックを探す](./skeleton/step02)([解答例](./solution/step02)) 55 | * STEP 3: [ゴールーチンとチャネル](./skeleton/step03)([解答例](./solution/step03)) 56 | * STEP 4: [syncパッケージを使う](./skeleton/step04)([解答例](./solution/step04)) 57 | * STEP 5: [ゴールーチンとエラー処理](./skeleton/step05)([解答例](./solution/step05)) 58 | * STEP 6: [コンテキストとキャンセル処理](./skeleton/step06)([解答例](./solution/step06)) 59 | 60 | ## ハンズオンの開催や資料の扱いについて 61 | この資料を元にハンズオンを開催するために@tenntennの許可などはいりません。 62 | 好きに開催してください。 63 | @tenntennの行ける範囲であれば、開催するから解説して欲しいという依頼もウェルカムです。 64 | 65 | なお、forkして変更してもらっても構いませんが、できればPRをいただけると嬉しいです。 66 | 資料に間違えを発見した方もissueやPRを頂ければ対応します。 67 | -------------------------------------------------------------------------------- /goroutine/skeleton/step01/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/skeleton/step02/README.md: -------------------------------------------------------------------------------- 1 | # STEP 2: ボトルネックを探す 2 | 3 | ## トレースを行う 4 | 5 | STEP 1で作成したプログラムはなんとなく遅い気がします。 6 | STEP 2では、`runtime/trace`パッケージを用いてトレースし、ボトルネックとなる部分を探します。 7 | 8 | まずは、`main`関数をトレースするために、`main`関数の中身を`_main`関数に移動させます。 9 | 10 | そして、トレース対象の処理を開始する前に、`trace.Start`関数を呼び出し、終了後に`trace.Stop`を呼び出します。 11 | なお、`defer`を用いることで、関数の終了時に呼び出すことができます。 12 | 13 | ```go 14 | func main() { 15 | f, err := os.Create("trace.out") 16 | if err != nil { 17 | log.Fatalln("Error:", err) 18 | } 19 | defer func() { 20 | if err := f.Close(); err != nil { 21 | log.Fatalln("Error:", err) 22 | } 23 | }() 24 | 25 | if err := trace.Start(f); err != nil { 26 | log.Fatalln("Error:", err) 27 | } 28 | defer trace.Stop() 29 | 30 | _main() 31 | } 32 | ``` 33 | 34 | ## ユーザアノテーション 35 | 36 | Go1.11からユーザアノテーションという機能が入りました。 37 | ユーザが任意のタイミングでトレース情報を出力することができる機能です。 38 | 39 | ユーザアノテーションは、以下の単位で行うことができます。 40 | 41 | * Task 42 | * Region 43 | * Log 44 | 45 | 1つのTaskに複数のRegionがあり、Regionの中でLogを出すというイメージです。 46 | 47 | ここでは、Taskは「コーヒーを淹れること」、Regionは「お湯を沸かす」、「豆を挽く」、「コーヒー淹れる」の3つになります。 48 | 49 | `Task`は次のように作成することができます。 50 | 作成した`Task`は、`End`メソッドを呼び出すまでの処理が対象となります。 51 | 通常は`defer`で呼び出すことで関数の終了時に呼び出します。 52 | 53 | ```go 54 | ctx, task := trace.NewTask(context.Background(), "make coffee") 55 | defer task.End() 56 | ``` 57 | 58 | `trace.NewTask`関数の第1引数で返される`context.Context`型の値には、Taskの情報が保持されています。 59 | 60 | Regionは次のように、`trace.StartRegion`を呼び出すことで作ることができます。 61 | `trace.StartRegion`の第1引数に`trace.NewTask`関数で返ってきた`context.Context`を渡すことで、TaskとRegionを紐付けることができます。 62 | 63 | 作成したRegionは、`End`メソッドを呼び出すまでの処理が対象となります。 64 | 65 | ```go 66 | region := trace.StartRegion(ctx, "region_name") 67 | defer region.End() 68 | ``` 69 | 70 | なお、次のように1行で書くこともできます。 71 | 72 | ```go 73 | defer trace.StartRegion(ctx, "region_name").End() 74 | ``` 75 | 76 | ## 実行とトレースデータの表示 77 | 78 | `TODO`を埋めると次のコマンドで実行することができます。 79 | 80 | ``` 81 | $ go run main.go 82 | ``` 83 | 84 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。 85 | 86 | ``` 87 | $ go tool trace trace.out 88 | ``` 89 | 90 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。 91 | 92 | 93 | 94 | 図を見ると、`boil`、`grind`、`brew`が直列に処理されていることがわかります。 95 | `boil`と`grind`は同時に行っても問題ないので、ここを改善すれば早くなりそうです。 96 | -------------------------------------------------------------------------------- /goroutine/skeleton/step02/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/skeleton/step02/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/goroutine/skeleton/step02/trace.png -------------------------------------------------------------------------------- /goroutine/skeleton/step03/README.md: -------------------------------------------------------------------------------- 1 | # STEP 3: ゴールーチンとチャネル 2 | 3 | ## ゴールーチン 4 | 5 | Goには、ゴールーチンという並行処理を行うための機能があります。 6 | 次のように、`go`というキーワードを用いて関数呼び出しを行うと新しいゴールーチンで関数を呼び出すことができます。 7 | 8 | ```go 9 | go f() 10 | ``` 11 | 12 | ゴールーチンは、非常に軽量でOSのスレッドの上に複数動きます。 13 | デフォルトでは、論理CPU数と同じ数だけ並列に動くことが可能です。 14 | 複数のゴールーチンの処理をどのように切り替えるかについては、Goのランタイムがスケジューリングを行うため、ユーザは気にする必要がありません。 15 | 16 | ## チャネル 17 | 18 | ゴールーチンを用いると関数を独立して動作させることができます。 19 | ゴールーチン間のデータのやりとりはどのように行えばよいのでしょうか? 20 | 21 | 次のように、共有の変数を用いる方法はどうでしょうか。 22 | 23 | ```go 24 | func main() { 25 | var done bool 26 | go func() { 27 | // 重たい処理 28 | time.Sleep(2 * time.Second) 29 | done := true 30 | }() 31 | 32 | // 処理が終わるまで待つ 33 | for done { 34 | time.Sleep(10 * time.Millisecond) 35 | } 36 | } 37 | ``` 38 | 39 | この例では、1つのゴールーチンでしか変数`done`に書き込んでいないので大きな問題は起きませんが、共有の変数に同時に読み書きが発生すると競合が起きてしまいます。 40 | 41 | そこでGoでは、ゴールーチン間のデータのやりとりにチャネルというデータ構造を用います。 42 | チャネルはゴールーチン間の通信を行うデータ型で次のように利用することができます。 43 | 44 | ```go 45 | func main() { 46 | done := make(chan bool) 47 | go func() { 48 | // 重たい処理 49 | time.Sleep(2 * time.Second) 50 | done <- true 51 | }() 52 | 53 | // 処理が終わるまで待つ 54 | <-done 55 | } 56 | ``` 57 | 58 | チャネルの初期化には、組込みの`make`関数を用います。 59 | `make`関数には、`chan bool`のようなチャネルを表す型を指定します。 60 | `chan bool`は`bool`型のデータをやり取りするためのチャネルを表します。 61 | 62 | `done <- true`のように書くことでチャネルに値を送ることができます。 63 | また、`<-done`のように書くと、チャネルから値を受信することができます。 64 | 65 | チャネルの送受信ではデータが受信・送信されるまで処理がブロックされます。 66 | なお、ここでは扱いませんがチャネルに空きバッファがある場合はブロックされずに処理が進みます。 67 | 68 | チャネルは、引数や戻り値に渡すことができます。 69 | その際、`chan bool`のように型を書くと送受信可能なチャネルになってしまうため、 70 | 引数の場合には`chan<- bool`、戻り値の場合には`<-chan bool`と書くことで、受信または送信限定のチャネルとすることができます。 71 | 72 | ## 実行とトレースデータの表示 73 | 74 | `TODO`を埋めると次のコマンドで実行することができます。 75 | 76 | ``` 77 | $ go run main.go 78 | ``` 79 | 80 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。 81 | 82 | ``` 83 | $ go tool trace trace.out 84 | ``` 85 | 86 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。 87 | 88 | 89 | 90 | 実行時間が直列に行うより大幅に改善されていることがわかります。 91 | 92 | `boil`と`grind`が並列に処理され、その後に`grind`が処理されています。 93 | `boil`と`grind`のバーの長さが同じな理由は、`grind`の処理が終わっても`boil`の結果がすべて受け取るまでは、`grind`の結果を受け取ってもらえないためです。 94 | -------------------------------------------------------------------------------- /goroutine/skeleton/step03/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/skeleton/step03/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/goroutine/skeleton/step03/trace.png -------------------------------------------------------------------------------- /goroutine/skeleton/step04/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/skeleton/step04/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/goroutine/skeleton/step04/trace.png -------------------------------------------------------------------------------- /goroutine/skeleton/step05/README.md: -------------------------------------------------------------------------------- 1 | # STEP 5: ゴールーチンとエラー処理 2 | 3 | ## エラー処理 4 | 5 | プログラムにはエラー処理はつきものです。 6 | ファイルが開けなかったり、ネットワークに接続できなかったりする可能性があります。 7 | 予想可能なエラーは適切に処理するようにプログラムを組む必要があります。 8 | コーヒーを淹れるプログラムでも、水が多すぎたり、豆が多すぎたり、お湯が少なすぎたりするかもしれません。 9 | 10 | Goでは、エラーは`error`型で表されます。 11 | エラーがあるかないかは、次のように`if`で比較することが多いでしょう。 12 | 13 | ```go 14 | err := f() 15 | if err != nil { 16 | // エラー処理 17 | } 18 | 19 | // これでもOK 20 | if err := f(); err != nil { 21 | // エラー処理 22 | } 23 | ``` 24 | 25 | 受け取ったエラーはログに吐いたり、情報を付加して呼び出し元にreturnしたりします。 26 | `panic`を起こしたり、無視することは極力さけるようにしましょう。 27 | 28 | ## ゴールーチンとエラー処理 29 | 30 | 別のゴールーチンで行っている処理でエラーが発生する場合はどのようにハンドリングすればよいのでしょうか。 31 | エラーも値なのでチャネルを使って伝搬する方法が考えられます。 32 | 33 | しかし、単純な場合はチャネルでも問題ありませんが、`sync.WaitGroup`などを使っているような複雑な場合については、チャンネルを用いると逆に煩雑になってしまう恐れがあります。 34 | `golang.org/x/sync/errgroup`という準標準なパッケージを使うことで、このようなパターンをうまくハンドリングすることができます。 35 | 36 | `errgroup`パッケージには、`sync.WaitGroup`に似た`errgroup.Group`が提供されています。 37 | `sync.Wait`の`Add`メソッドや`Done`メソッドの代わりに、`Go`メソッドがあります。 38 | `Go`メソッドは引数で渡した関数をゴールーチンで実行してくれます。 39 | この関数の戻り値はerror型でゴールーチンでエラーが発生した場合には適切に処理ができるようになっています。 40 | 41 | `Wait`メソッドを実行すると、`Go`メソッドで呼び出された関数がすべて終了するまで処理がブロックされます。 42 | もし、1つでもエラーを返す関数があれば、`Wait`メソッドは戻り値で最初に発生したエラーを返します。 43 | 44 | ```go 45 | var eg errgroup.Group 46 | 47 | eg.Go(func() error { 48 | if err := f(); err != nil { 49 | return err 50 | } 51 | }) 52 | 53 | eg.Go(func() error { 54 | if err := g(); err != nil { 55 | return err 56 | } 57 | }) 58 | 59 | if err := eg.Wait(); err != nil { 60 | // エラー処理 61 | } 62 | ``` 63 | 64 | ## プログラムの改造 65 | 66 | `boil`関数、`grind`関数、`brew`関数がエラーを返すようになっています。 67 | `sync.WaitGroup`の代わりに`errgroup.Group`を使うことでエラーハンドリングをできるようにしましょう。 68 | 69 | ## 実行 70 | 71 | `errgroup`パッケージは外部パッケージであるため、`go get`コマンドでインストールする必要があります。 72 | 73 | ``` 74 | $ go get -u golang.org/x/sync/errgroup 75 | ``` 76 | 77 | 次のコマンドで実行することができます。 78 | 79 | ``` 80 | $ go run main.go 81 | ``` 82 | 83 | `boil`関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。 84 | -------------------------------------------------------------------------------- /goroutine/skeleton/step05/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | 5 | require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e 6 | -------------------------------------------------------------------------------- /goroutine/skeleton/step05/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 2 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 3 | -------------------------------------------------------------------------------- /goroutine/skeleton/step06/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | 5 | require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e 6 | -------------------------------------------------------------------------------- /goroutine/skeleton/step06/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 2 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 3 | -------------------------------------------------------------------------------- /goroutine/solution/step01/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/solution/step02/README.md: -------------------------------------------------------------------------------- 1 | # STEP 2: ボトルネックを探す 2 | 3 | ## トレースを行う 4 | 5 | STEP 1で作成したプログラムはなんとなく遅い気がします。 6 | STEP 2では、`runtime/trace`パッケージを用いてトレースし、ボトルネックとなる部分を探します。 7 | 8 | まずは、`main`関数をトレースするために、`main`関数の中身を`_main`関数に移動させます。 9 | 10 | そして、トレース対象の処理を開始する前に、`trace.Start`関数を呼び出し、終了後に`trace.Stop`を呼び出します。 11 | なお、`defer`を用いることで、関数の終了時に呼び出すことができます。 12 | 13 | ```go 14 | func main() { 15 | f, err := os.Create("trace.out") 16 | if err != nil { 17 | log.Fatalln("Error:", err) 18 | } 19 | defer func() { 20 | if err := f.Close(); err != nil { 21 | log.Fatalln("Error:", err) 22 | } 23 | }() 24 | 25 | if err := trace.Start(f); err != nil { 26 | log.Fatalln("Error:", err) 27 | } 28 | defer trace.Stop() 29 | 30 | _main() 31 | } 32 | ``` 33 | 34 | ## ユーザアノテーション 35 | 36 | Go1.11からユーザアノテーションという機能が入りました。 37 | ユーザが任意のタイミングでトレース情報を出力することができる機能です。 38 | 39 | ユーザアノテーションは、以下の単位で行うことができます。 40 | 41 | * Task 42 | * Region 43 | * Log 44 | 45 | 1つのTaskに複数のRegionがあり、Regionの中でLogを出すというイメージです。 46 | 47 | ここでは、Taskは「コーヒーを淹れること」、Regionは「お湯を沸かす」、「豆を挽く」、「コーヒー淹れる」の3つになります。 48 | 49 | `Task`は次のように作成することができます。 50 | 作成した`Task`は、`End`メソッドを呼び出すまでの処理が対象となります。 51 | 通常は`defer`で呼び出すことで関数の終了時に呼び出します。 52 | 53 | ```go 54 | ctx, task := trace.NewTask(context.Background(), "make coffee") 55 | defer task.End() 56 | ``` 57 | 58 | `trace.NewTask`関数の第1引数で返される`context.Context`型の値には、Taskの情報が保持されています。 59 | 60 | Regionは次のように、`trace.StartRegion`を呼び出すことで作ることができます。 61 | `trace.StartRegion`の第1引数に`trace.NewTask`関数で返ってきた`context.Context`を渡すことで、TaskとRegionを紐付けることができます。 62 | 63 | 作成したRegionは、`End`メソッドを呼び出すまでの処理が対象となります。 64 | 65 | ```go 66 | region := trace.StartRegion(ctx, "region_name") 67 | defer region.End() 68 | ``` 69 | 70 | なお、次のように1行で書くこともできます。 71 | 72 | ```go 73 | defer trace.StartRegion(ctx, "region_name").End() 74 | ``` 75 | 76 | ## 実行とトレースデータの表示 77 | 78 | `TODO`を埋めると次のコマンドで実行することができます。 79 | 80 | ``` 81 | $ go run main.go 82 | ``` 83 | 84 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。 85 | 86 | ``` 87 | $ go tool trace trace.out 88 | ``` 89 | 90 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。 91 | 92 | 93 | 94 | 図を見ると、`boil`、`grind`、`brew`が直列に処理されていることがわかります。 95 | `boil`と`grind`は同時に行っても問題ないので、ここを改善すれば早くなりそうです。 96 | -------------------------------------------------------------------------------- /goroutine/solution/step02/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/solution/step02/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/goroutine/solution/step02/trace.png -------------------------------------------------------------------------------- /goroutine/solution/step03/README.md: -------------------------------------------------------------------------------- 1 | # STEP 3: ゴールーチンとチャネル 2 | 3 | ## ゴールーチン 4 | 5 | Goには、ゴールーチンという並行処理を行うための機能があります。 6 | 次のように、`go`というキーワードを用いて関数呼び出しを行うと新しいゴールーチンで関数を呼び出すことができます。 7 | 8 | ```go 9 | go f() 10 | ``` 11 | 12 | ゴールーチンは、非常に軽量でOSのスレッドの上に複数動きます。 13 | デフォルトでは、論理CPU数と同じ数だけ並列に動くことが可能です。 14 | 複数のゴールーチンの処理をどのように切り替えるかについては、Goのランタイムがスケジューリングを行うため、ユーザは気にする必要がありません。 15 | 16 | ## チャネル 17 | 18 | ゴールーチンを用いると関数を独立して動作させることができます。 19 | ゴールーチン間のデータのやりとりはどのように行えばよいのでしょうか? 20 | 21 | 次のように、共有の変数を用いる方法はどうでしょうか。 22 | 23 | ```go 24 | func main() { 25 | var done bool 26 | go func() { 27 | // 重たい処理 28 | time.Sleep(2 * time.Second) 29 | done := true 30 | }() 31 | 32 | // 処理が終わるまで待つ 33 | for done { 34 | time.Sleep(10 * time.Millisecond) 35 | } 36 | } 37 | ``` 38 | 39 | この例では、1つのゴールーチンでしか変数`done`に書き込んでいないので大きな問題は起きませんが、共有の変数に同時に読み書きが発生すると競合が起きてしまいます。 40 | 41 | そこでGoでは、ゴールーチン間のデータのやりとりにチャネルというデータ構造を用います。 42 | チャネルはゴールーチン間の通信を行うデータ型で次のように利用することができます。 43 | 44 | ```go 45 | func main() { 46 | done := make(chan bool) 47 | go func() { 48 | // 重たい処理 49 | time.Sleep(2 * time.Second) 50 | done <- true 51 | }() 52 | 53 | // 処理が終わるまで待つ 54 | <-done 55 | } 56 | ``` 57 | 58 | チャネルの初期化には、組込みの`make`関数を用います。 59 | `make`関数には、`chan bool`のようなチャネルを表す型を指定します。 60 | `chan bool`は`bool`型のデータをやり取りするためのチャネルを表します。 61 | 62 | `done <- true`のように書くことでチャネルに値を送ることができます。 63 | また、`<-done`のように書くと、チャネルから値を受信することができます。 64 | 65 | チャネルの送受信ではデータが受信・送信されるまで処理がブロックされます。 66 | なお、ここでは扱いませんがチャネルに空きバッファがある場合はブロックされずに処理が進みます。 67 | 68 | チャネルは、引数や戻り値に渡すことができます。 69 | その際、`chan bool`のように型を書くと送受信可能なチャネルになってしまうため、 70 | 引数の場合には`chan<- bool`、戻り値の場合には`<-chan bool`と書くことで、受信または送信限定のチャネルとすることができます。 71 | 72 | ## 実行とトレースデータの表示 73 | 74 | `TODO`を埋めると次のコマンドで実行することができます。 75 | 76 | ``` 77 | $ go run main.go 78 | ``` 79 | 80 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。 81 | 82 | ``` 83 | $ go tool trace trace.out 84 | ``` 85 | 86 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。 87 | 88 | 89 | 90 | 実行時間が直列に行うより大幅に改善されていることがわかります。 91 | 92 | `boil`と`grind`が並列に処理され、その後に`grind`が処理されています。 93 | `boil`と`grind`のバーの長さが同じな理由は、`grind`の処理が終わっても`boil`の結果がすべて受け取るまでは、`grind`の結果を受け取ってもらえないためです。 94 | -------------------------------------------------------------------------------- /goroutine/solution/step03/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/solution/step03/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/goroutine/solution/step03/trace.png -------------------------------------------------------------------------------- /goroutine/solution/step04/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /goroutine/solution/step04/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/goroutine/solution/step04/trace.png -------------------------------------------------------------------------------- /goroutine/solution/step05/README.md: -------------------------------------------------------------------------------- 1 | # STEP 5: ゴールーチンとエラー処理 2 | 3 | ## エラー処理 4 | 5 | プログラムにはエラー処理はつきものです。 6 | ファイルが開けなかったり、ネットワークに接続できなかったりする可能性があります。 7 | 予想可能なエラーは適切に処理するようにプログラムを組む必要があります。 8 | コーヒーを淹れるプログラムでも、水が多すぎたり、豆が多すぎたり、お湯が少なすぎたりするかもしれません。 9 | 10 | Goでは、エラーは`error`型で表されます。 11 | エラーがあるかないかは、次のように`if`で比較することが多いでしょう。 12 | 13 | ```go 14 | err := f() 15 | if err != nil { 16 | // エラー処理 17 | } 18 | 19 | // これでもOK 20 | if err := f(); err != nil { 21 | // エラー処理 22 | } 23 | ``` 24 | 25 | 受け取ったエラーはログに吐いたり、情報を付加して呼び出し元にreturnしたりします。 26 | `panic`を起こしたり、無視することは極力さけるようにしましょう。 27 | 28 | ## ゴールーチンとエラー処理 29 | 30 | 別のゴールーチンで行っている処理でエラーが発生する場合はどのようにハンドリングすればよいのでしょうか。 31 | エラーも値なのでチャネルを使って伝搬する方法が考えられます。 32 | 33 | しかし、単純な場合はチャネルでも問題ありませんが、`sync.WaitGroup`などを使っているような複雑な場合については、チャンネルを用いると逆に煩雑になってしまう恐れがあります。 34 | `golang.org/x/sync/errgroup`という準標準なパッケージを使うことで、このようなパターンをうまくハンドリングすることができます。 35 | 36 | `errgroup`パッケージには、`sync.WaitGroup`に似た`errgroup.Group`が提供されています。 37 | `sync.Wait`の`Add`メソッドや`Done`メソッドの代わりに、`Go`メソッドがあります。 38 | `Go`メソッドは引数で渡した関数をゴールーチンで実行してくれます。 39 | この関数の戻り値はerror型でゴールーチンでエラーが発生した場合には適切に処理ができるようになっています。 40 | 41 | `Wait`メソッドを実行すると、`Go`メソッドで呼び出された関数がすべて終了するまで処理がブロックされます。 42 | もし、1つでもエラーを返す関数があれば、`Wait`メソッドは戻り値で最初に発生したエラーを返します。 43 | 44 | ```go 45 | var eg errgroup.Group 46 | 47 | eg.Go(func() error { 48 | if err := f(); err != nil { 49 | return err 50 | } 51 | }) 52 | 53 | eg.Go(func() error { 54 | if err := g(); err != nil { 55 | return err 56 | } 57 | }) 58 | 59 | if err := eg.Wait(); err != nil { 60 | // エラー処理 61 | } 62 | ``` 63 | 64 | ## プログラムの改造 65 | 66 | `boil`関数、`grind`関数、`brew`関数がエラーを返すようになっています。 67 | `sync.WaitGroup`の代わりに`errgroup.Group`を使うことでエラーハンドリングをできるようにしましょう。 68 | 69 | ## 実行 70 | 71 | `errgroup`パッケージは外部パッケージであるため、`go get`コマンドでインストールする必要があります。 72 | 73 | ``` 74 | $ go get -u golang.org/x/sync/errgroup 75 | ``` 76 | 77 | 次のコマンドで実行することができます。 78 | 79 | ``` 80 | $ go run main.go 81 | ``` 82 | 83 | `boil`関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。 84 | -------------------------------------------------------------------------------- /goroutine/solution/step05/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | 5 | require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e 6 | -------------------------------------------------------------------------------- /goroutine/solution/step05/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 2 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 3 | -------------------------------------------------------------------------------- /goroutine/solution/step06/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/gohandson/goroutine 2 | 3 | go 1.13 4 | 5 | require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e 6 | -------------------------------------------------------------------------------- /goroutine/solution/step06/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 2 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 3 | -------------------------------------------------------------------------------- /greeting/README.md: -------------------------------------------------------------------------------- 1 | # テストを書こう 2 | 3 | ## ハンズオンのやりかた 4 | 5 | `skeleton`ディレクトリ以下に問題があり、8個のステップに分けられています。 6 | STEP01からSTEP08までステップごとに進めていくことで、Goのパッケージ分けやテストの方法が学べます。 7 | 8 | 各ステップに、READMEが用意されていますので、まずは`README`を読みます。 9 | `README`には、そのステップを理解するための解説が書かれています。 10 | 11 | `README`を読んだら、ソースコードを開き`TODO`コメントが書かれている箇所をコメントに従って修正して行きます。 12 | `TODO`コメントをすべて修正し終わったら、`README`に書かれた実行例に従ってプログラムをコンパイルして実行します。 13 | 14 | 途中でわからなくなった場合は、`solution`ディレクトリ以下に解答例を用意していますので、そちらをご覧ください。 15 | 16 | `macOS`の動作結果をもとに解説しています。 17 | `Windows`の方は、パスの区切り文字やコマンド等を適宜読み替えてください。 18 | 19 | ## 目次 20 | 21 | * STEP01: Goに触れる 22 | * STEP02: 標準パッケージを使ってみよう 23 | * STEP03: パッケージを分けよう 24 | * STEP04: 外部パッケージを使ってみよう 25 | * STEP05: テストを書いてみよう 26 | * STEP06: テストヘルパーを作って見よう 27 | * STEP07: テストのパッケージを分けよう 28 | * STEP08: テーブル駆動テストを行おう 29 | 30 | ## ソースコードの取得 31 | 32 | ``` 33 | $ git clone https://github.com/tenntenn/gohandson.git 34 | $ cd greeting 35 | ``` 36 | -------------------------------------------------------------------------------- /greeting/skeleton/step01/README.md: -------------------------------------------------------------------------------- 1 | # STEP01: Goに触れる 2 | 3 | ## 新しく学ぶこと 4 | 5 | * Goのプログラムの書き方 6 | * Goのプログラムの実行の仕方 7 | * 文字列の表示方法 8 | 9 | ## 動かし方 10 | 11 | ```sh 12 | $ go build -v -o step01 13 | $ ./step01 14 | ``` 15 | -------------------------------------------------------------------------------- /greeting/skeleton/step01/main.go: -------------------------------------------------------------------------------- 1 | // STEP01: Goに触れる 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // main関数から実行される 7 | func main() { 8 | // 「こんにちは」と出力 9 | println("こんにちは") 10 | } 11 | -------------------------------------------------------------------------------- /greeting/skeleton/step02/README.md: -------------------------------------------------------------------------------- 1 | # STEP02: 標準パッケージを使ってみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * `fmt`パッケージの使い方 6 | * `time`パッケージの使い方 7 | 8 | ## 動かし方 9 | 10 | ```sh 11 | $ go build -v -o step02 12 | $ ./step02 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/skeleton/step02/main.go: -------------------------------------------------------------------------------- 1 | // STEP02: 標準パッケージを使ってみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | // fmtパッケージのインポート 8 | "fmt" 9 | // timeパッケージのインポート 10 | "time" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // greet関数を呼び出す 16 | greet() 17 | } 18 | 19 | // greet関数の定義 20 | // 以下のように時間よってメッセージを変える 21 | // 04時00分 〜 09時59分: おはよう 22 | // 10時00分 〜 16時59分: こんにちは 23 | // 17時00分 〜 03時59分: こんばんは 24 | func greet() { 25 | // 現在時刻から何時かを取得 26 | h := time.Now().Hour() 27 | switch { 28 | case h >= 4 && h <= 9: 29 | // TODO: おはようと出す 30 | case /* TODO: 10時00分 〜 16時59分 */: 31 | fmt.Println("こんにちは") 32 | default: 33 | fmt.Println("こんばんは") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /greeting/skeleton/step03/README.md: -------------------------------------------------------------------------------- 1 | # STEP03: パッケージを分けよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * パッケージの分け方 6 | * パッケージ構成 7 | * 名前の付け方 8 | 9 | ## 動かし方 10 | 11 | ```sh 12 | $ export GOBIN=`pwd`/_bin 13 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 14 | $ _bin/step03 15 | ``` 16 | -------------------------------------------------------------------------------- /greeting/skeleton/step03/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP03: パッケージを分けてみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // TODO: greetingパッケージのインポート 7 | 8 | // main関数から実行される 9 | func main() { 10 | // TODO: greeting.Do関数を呼び出す 11 | } 12 | -------------------------------------------------------------------------------- /greeting/skeleton/step03/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | -------------------------------------------------------------------------------- /greeting/skeleton/step03/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP03: パッケージを分けてみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | // timeパッケージのインポート 8 | "time" 9 | ) 10 | 11 | // Do関数の定義 12 | // パッケージ外からアクセスできるように関数名を大文字から始める 13 | // 14 | // 以下のように時間よってメッセージを変える 15 | // 04時00分 〜 09時59分: おはよう 16 | // 10時00分 〜 16時59分: こんにちは 17 | // 17時00分 〜 03時59分: こんばんは 18 | func /* TODO: 関数名を書く */ () { 19 | // 現在時刻から何時かを取得 20 | h := time.Now().Hour() 21 | switch { 22 | case h >= 4 && h <= 9: 23 | fmt.Println("おはよう") 24 | case h >= 10 && h <= 16: 25 | fmt.Println("こんにちは") 26 | default: 27 | fmt.Println("こんばんは") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /greeting/skeleton/step04/README.md: -------------------------------------------------------------------------------- 1 | # STEP04: 外部パッケージを使ってみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * サードパーティパッケージの使い方 6 | * Go Modules 7 | 8 | ## 外部パッケージを取得する 9 | 10 | ```sh 11 | $ export GO111MODULE=on # 1度だけ 12 | $ go get github.com/tenntenn/greeting/v2/text 13 | ``` 14 | 15 | ## 動かし方 16 | 17 | ```sh 18 | $ export GOBIN=`pwd`/_bin # 1度だけ 19 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 20 | $ _bin/step04 21 | ``` 22 | -------------------------------------------------------------------------------- /greeting/skeleton/step04/_bin/greeting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/greeting/skeleton/step04/_bin/greeting -------------------------------------------------------------------------------- /greeting/skeleton/step04/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP04: 外部パッケージを使ってみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // greetingパッケージのインポート 7 | import "github.com/tenntenn/gohandson/greeting" 8 | 9 | // main関数から実行される 10 | func main() { 11 | // greeting.Do関数を呼び出す 12 | greeting.Do() 13 | } 14 | -------------------------------------------------------------------------------- /greeting/skeleton/step04/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/skeleton/step04/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/skeleton/step04/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP04: 外部パッケージを使ってみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | // timeパッケージのインポート 8 | "time" 9 | 10 | // TODO: textパッケージのインポート 11 | ) 12 | 13 | // デフォルトの言語 14 | var lang = text.DefaultLang() 15 | 16 | // Do関数の定義 17 | // パッケージ外からアクセスできるように関数名を大文字から始める 18 | // 19 | // 以下のように時間よってメッセージを変える 20 | // 04時00分 〜 09時59分: おはよう 21 | // 10時00分 〜 16時59分: こんにちは 22 | // 17時00分 〜 03時59分: こんばんは 23 | func Do() { 24 | // 現在時刻から何時かを取得 25 | h := time.Now().Hour() 26 | switch { 27 | case h >= 4 && h <= 9: 28 | // text.GoodMorningを使う 29 | fmt.Println(text.GoodMorning(lang)) 30 | case h >= 10 && h <= 16: 31 | // TODO: text.Helloを使う 32 | default: 33 | // text.GoodEveningを使う 34 | fmt.Println(text.GoodEvening(lang)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /greeting/skeleton/step05/README.md: -------------------------------------------------------------------------------- 1 | # STEP05: テストを書いてみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 抽象化 6 | * テストの書き方 7 | * テスタビリティ 8 | 9 | ## 動かし方 10 | 11 | ```sh 12 | $ export GOBIN=`pwd`/_bin # 1度だけ 13 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 14 | $ _bin/step05 15 | ``` 16 | -------------------------------------------------------------------------------- /greeting/skeleton/step05/_bin/greeting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/greeting/skeleton/step05/_bin/greeting -------------------------------------------------------------------------------- /greeting/skeleton/step05/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP05: テストを書いてみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/skeleton/step05/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/skeleton/step05/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/skeleton/step05/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP05: テストを書いてみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // TODO: レシーバは関数なのでそのまま呼び出す 29 | } 30 | 31 | // 挨拶を行うための構造体型 32 | type Greeting struct { 33 | // Clockインタフェースをフィールドに持つことで 34 | // 時刻の取得を抽象化する 35 | Clock /* TODO: 型を書く */ 36 | } 37 | 38 | // 現在時刻を取得する 39 | // Clockフィールドがnilの場合はtime.Now()の値を使う 40 | // nilじゃない場合はg.Clock.Now()の値を使う 41 | func (g *Greeting) now() time.Time { 42 | if g.Clock == nil { 43 | // TODO: time.Now()の値を使う 44 | } 45 | // TODO: g.Clock.Now()の値を使う 46 | } 47 | 48 | // Do関数の定義 49 | // パッケージ外からアクセスできるように関数名を大文字から始める 50 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 51 | // 52 | // 以下のように時間よってメッセージを変える 53 | // 04時00分 〜 09時59分: おはよう 54 | // 10時00分 〜 16時59分: こんにちは 55 | // 17時00分 〜 03時59分: こんばんは 56 | func (g *Greeting) Do(w io.Writer) error { 57 | h := g.now().Hour() 58 | var msg string 59 | switch { 60 | case h >= 4 && h <= 9: 61 | msg = text.GoodMorning(lang) 62 | case h >= 10 && h <= 16: 63 | msg = text.Hello(lang) 64 | default: 65 | msg = text.GoodEvening(lang) 66 | } 67 | 68 | _, err := fmt.Fprint(w, msg) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /greeting/skeleton/step05/greeting_test.go: -------------------------------------------------------------------------------- 1 | // STEP05: テストを書いてみよう 2 | package greeting 3 | 4 | import ( 5 | "bytes" 6 | "testing" 7 | "time" 8 | 9 | "golang.org/x/text/language" 10 | ) 11 | 12 | // Greeting.Doメソッドのテスト 13 | func TestGreeting_Do(t *testing.T) { 14 | // パッケージのlangを入れ替える 15 | orgLang := lang 16 | lang = language.Japanese 17 | defer func() { 18 | // deferで元に戻す 19 | lang = orgLang 20 | }() 21 | 22 | // Greeting型の値を作る 23 | g := Greeting{ 24 | Clock: ClockFunc(func() time.Time { 25 | // 2018/08/31 06:00:00を返すようにしておく 26 | return time.Date(2018, 8, 31, 06, 0, 0, 0, time.Local) 27 | }), 28 | } 29 | 30 | var buf bytes.Buffer 31 | if err := g.Do(&buf); err != nil { 32 | t.Error("unexpected error:", err) 33 | } 34 | 35 | if expected, actual := "おはよう", buf.String(); expected != actual { 36 | t.Errorf("greeting message wont %s but got %s", expected, actual) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/README.md: -------------------------------------------------------------------------------- 1 | # STEP06: テストのパッケージを分けよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 非公開な機能を使ったテスト 6 | 7 | ## 動かし方 8 | 9 | ```sh 10 | $ export GOBIN=`pwd`/_bin # 1度だけ 11 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 12 | $ _bin/step06 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/_bin/greeting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/greeting/skeleton/step06/_bin/greeting -------------------------------------------------------------------------------- /greeting/skeleton/step06/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/export_test.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | package greeting 3 | 4 | import "golang.org/x/text/language" 5 | 6 | // パッケージ変数langを一時的に変更する関数 7 | // greetingパッケージだがファイル名が_test.goで終わるため 8 | // go testの際しかビルドされない 9 | func ExportSetLang(l language.Tag) func() { 10 | orgLang := lang 11 | lang = l 12 | return func() { 13 | // TODO: langを元に戻す 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /greeting/skeleton/step06/greeting_test.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | package greeting_test 3 | 4 | import ( 5 | "bytes" 6 | "testing" 7 | "time" 8 | 9 | "github.com/tenntenn/gohandson/greeting" 10 | "golang.org/x/text/language" 11 | ) 12 | 13 | // Greeting.Doメソッドのテスト 14 | func TestGreeting_Do(t *testing.T) { 15 | // TODO: 言語を日本語にしておき、関数実行時に元に戻す 16 | defer greeting.ExportSetLang(language.Japanese)() 17 | 18 | // greeting.Greeting型の値を作る 19 | g := greeting.Greeting{ 20 | Clock: greeting.ClockFunc(func() time.Time { 21 | // 2018/08/31 06:00:00を返すようにしておく 22 | return time.Date(2018, 8, 31, 06, 0, 0, 0, time.Local) 23 | }), 24 | } 25 | 26 | var buf bytes.Buffer 27 | if err := g.Do(&buf); err != nil { 28 | t.Error("unexpected error:", err) 29 | } 30 | 31 | if expected, actual := "おはよう", buf.String(); expected != actual { 32 | t.Errorf("greeting message wont %s but got %s", expected, actual) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/README.md: -------------------------------------------------------------------------------- 1 | # STEP07: テストヘルパーを作ってみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * テストヘルパーの作り方 6 | 7 | ## 動かし方 8 | 9 | ```sh 10 | $ export GOBIN=`pwd`/_bin # 1度だけ 11 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 12 | $ _bin/step07 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/_bin/greeting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/greeting/skeleton/step07/_bin/greeting -------------------------------------------------------------------------------- /greeting/skeleton/step07/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/export_test.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | package greeting 3 | 4 | import "golang.org/x/text/language" 5 | 6 | // パッケージ変数langを一時的に変更する関数 7 | // greetingパッケージだがファイル名が_test.goで終わるため 8 | // go testの際しかビルドされない 9 | func ExportSetLang(l language.Tag) func() { 10 | orgLang := lang 11 | lang = l 12 | return func() { 13 | lang = orgLang 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /greeting/skeleton/step07/greeting_test.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | package greeting_test 3 | 4 | import ( 5 | "bytes" 6 | "testing" 7 | "time" 8 | 9 | "github.com/tenntenn/gohandson/greeting" 10 | "golang.org/x/text/language" 11 | ) 12 | 13 | // "YYYY/MM/DD hh:mm:ss" 形式の時刻を返すようなgreeting.Clockを作る 14 | // 引数にtesting.Tと文字列で表した時刻を取得する 15 | func mockClock( /* TODO: 引数を決める */ ) greeting.Clock { 16 | // TODO: ヘルパーであることを明示する 17 | 18 | now, err := time.Parse("2006/01/02 15:04:05", v) 19 | if err != nil { 20 | // TODO: エラーが発生した場合はテスト中断させエラーにする 21 | } 22 | 23 | // TODO: nowを返す関数を作り、greeting.ClockFuncにキャストして返す 24 | } 25 | 26 | // Greeting.Doメソッドのテスト 27 | func TestGreeting_Do(t *testing.T) { 28 | // 言語を日本語にしておく 29 | defer greeting.ExportSetLang(language.Japanese)() 30 | 31 | // greeting.Greeting型の値を作る 32 | g := greeting.Greeting{ 33 | Clock: mockClock(t, "2018/08/31 06:00:00"), 34 | } 35 | 36 | var buf bytes.Buffer 37 | if err := g.Do(&buf); err != nil { 38 | t.Error("unexpected error:", err) 39 | } 40 | 41 | if expected, actual := "おはよう", buf.String(); expected != actual { 42 | t.Errorf("greeting message wont %s but got %s", expected, actual) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /greeting/skeleton/step08/README.md: -------------------------------------------------------------------------------- 1 | # STEP08: テーブル駆動テストを行おう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * テーブル駆動テスト 6 | 7 | ## 動かし方 8 | 9 | ```sh 10 | $ export GOBIN=`pwd`/_bin # 1度だけ 11 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 12 | $ _bin/step08 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/skeleton/step08/_bin/greeting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/greeting/skeleton/step08/_bin/greeting -------------------------------------------------------------------------------- /greeting/skeleton/step08/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP08: テーブル駆動テストを行おう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/skeleton/step08/export_test.go: -------------------------------------------------------------------------------- 1 | // STEP08: テーブル駆動テストを行おう 2 | package greeting 3 | 4 | import "golang.org/x/text/language" 5 | 6 | // パッケージ変数langを一時的に変更する関数 7 | // greetingパッケージだがファイル名が_test.goで終わるため 8 | // go testの際しかビルドされない 9 | func ExportSetLang(l language.Tag) func() { 10 | orgLang := lang 11 | lang = l 12 | return func() { 13 | lang = orgLang 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greeting/skeleton/step08/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/skeleton/step08/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/skeleton/step08/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP08: テーブル駆動テストを行おう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /greeting/solution/.gitignore: -------------------------------------------------------------------------------- 1 | */_bin/ 2 | -------------------------------------------------------------------------------- /greeting/solution/step01/README.md: -------------------------------------------------------------------------------- 1 | # STEP01: Goに触れる 2 | 3 | ## 新しく学ぶこと 4 | 5 | * Goのプログラムの書き方 6 | * Goのプログラムの実行の仕方 7 | * 文字列の表示方法 8 | 9 | ## 動かし方 10 | 11 | ```sh 12 | $ go build -v -o step01 13 | $ ./step01 14 | ``` 15 | -------------------------------------------------------------------------------- /greeting/solution/step01/main.go: -------------------------------------------------------------------------------- 1 | // STEP01: Goに触れる 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // main関数から実行される 7 | func main() { 8 | // 「こんにちは」と出力 9 | println("こんにちは") 10 | } 11 | -------------------------------------------------------------------------------- /greeting/solution/step02/README.md: -------------------------------------------------------------------------------- 1 | # STEP02: 標準パッケージを使ってみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * `fmt`パッケージの使い方 6 | * `time`パッケージの使い方 7 | 8 | ## 動かし方 9 | 10 | ```sh 11 | $ go build -v -o step02 12 | $ ./step02 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/solution/step02/main.go: -------------------------------------------------------------------------------- 1 | // STEP02: 標準パッケージを使ってみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | // fmtパッケージのインポート 8 | "fmt" 9 | // timeパッケージのインポート 10 | "time" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // greet関数を呼び出す 16 | greet() 17 | } 18 | 19 | // greet関数の定義 20 | // 以下のように時間よってメッセージを変える 21 | // 04時00分 〜 09時59分: おはよう 22 | // 10時00分 〜 16時59分: こんにちは 23 | // 17時00分 〜 03時59分: こんばんは 24 | func greet() { 25 | // 現在時刻から何時かを取得 26 | h := time.Now().Hour() 27 | switch { 28 | case h >= 4 && h <= 9: 29 | fmt.Println("おはよう") 30 | case h >= 10 && h <= 16: 31 | fmt.Println("こんにちは") 32 | default: 33 | fmt.Println("こんばんは") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /greeting/solution/step03/README.md: -------------------------------------------------------------------------------- 1 | # STEP03: パッケージを分けよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * パッケージの分け方 6 | * パッケージ構成 7 | * 名前の付け方 8 | 9 | ## 動かし方 10 | 11 | ```sh 12 | $ export GOBIN=`pwd`/_bin 13 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 14 | $ _bin/step03 15 | ``` 16 | -------------------------------------------------------------------------------- /greeting/solution/step03/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP03: パッケージを分けてみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // greetingパッケージのインポート 7 | import "github.com/tenntenn/gohandson/greeting" 8 | 9 | // main関数から実行される 10 | func main() { 11 | // greeting.Do関数を呼び出す 12 | greeting.Do() 13 | } 14 | -------------------------------------------------------------------------------- /greeting/solution/step03/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | -------------------------------------------------------------------------------- /greeting/solution/step03/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP03: パッケージを分けてみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | // timeパッケージのインポート 8 | "time" 9 | ) 10 | 11 | // Do関数の定義 12 | // パッケージ外からアクセスできるように関数名を大文字から始める 13 | // 14 | // 以下のように時間よってメッセージを変える 15 | // 04時00分 〜 09時59分: おはよう 16 | // 10時00分 〜 16時59分: こんにちは 17 | // 17時00分 〜 03時59分: こんばんは 18 | func Do() { 19 | // 現在時刻から何時かを取得 20 | h := time.Now().Hour() 21 | switch { 22 | case h >= 4 && h <= 9: 23 | fmt.Println("おはよう") 24 | case h >= 10 && h <= 16: 25 | fmt.Println("こんにちは") 26 | default: 27 | fmt.Println("こんばんは") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /greeting/solution/step04/README.md: -------------------------------------------------------------------------------- 1 | # STEP04: 外部パッケージを使ってみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * サードパーティパッケージの使い方 6 | * Go Modules 7 | 8 | ## 外部パッケージを取得する 9 | 10 | ```sh 11 | $ export GO111MODULE=on # 1度だけ 12 | $ go get github.com/tenntenn/greeting/v2/text 13 | ``` 14 | 15 | ## 動かし方 16 | 17 | ```sh 18 | $ export GOBIN=`pwd`/_bin # 1度だけ 19 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 20 | $ _bin/step04 21 | ``` 22 | -------------------------------------------------------------------------------- /greeting/solution/step04/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP04: 外部パッケージを使ってみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | // greetingパッケージのインポート 7 | import "github.com/tenntenn/gohandson/greeting" 8 | 9 | // main関数から実行される 10 | func main() { 11 | // greeting.Do関数を呼び出す 12 | greeting.Do() 13 | } 14 | -------------------------------------------------------------------------------- /greeting/solution/step04/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/solution/step04/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/solution/step04/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP04: 外部パッケージを使ってみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | // timeパッケージのインポート 8 | "time" 9 | 10 | // textパッケージのインポート 11 | "github.com/tenntenn/greeting/v2/text" 12 | ) 13 | 14 | // デフォルトの言語 15 | var lang = text.DefaultLang() 16 | 17 | // Do関数の定義 18 | // パッケージ外からアクセスできるように関数名を大文字から始める 19 | // 20 | // 以下のように時間よってメッセージを変える 21 | // 04時00分 〜 09時59分: おはよう 22 | // 10時00分 〜 16時59分: こんにちは 23 | // 17時00分 〜 03時59分: こんばんは 24 | func Do() { 25 | // 現在時刻から何時かを取得 26 | h := time.Now().Hour() 27 | switch { 28 | case h >= 4 && h <= 9: 29 | // text.GoodMorningを使う 30 | fmt.Println(text.GoodMorning(lang)) 31 | case h >= 10 && h <= 16: 32 | // text.Helloを使う 33 | fmt.Println(text.Hello(lang)) 34 | default: 35 | // text.GoodEveningを使う 36 | fmt.Println(text.GoodEvening(lang)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /greeting/solution/step05/README.md: -------------------------------------------------------------------------------- 1 | # STEP05: テストを書いてみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 抽象化 6 | * テストの書き方 7 | * テスタビリティ 8 | 9 | ## 動かし方 10 | 11 | ```sh 12 | $ export GOBIN=`pwd`/_bin # 1度だけ 13 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 14 | $ _bin/step05 15 | ``` 16 | -------------------------------------------------------------------------------- /greeting/solution/step05/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP05: テストを書いてみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/solution/step05/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/solution/step05/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/solution/step05/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP05: テストを書いてみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /greeting/solution/step05/greeting_test.go: -------------------------------------------------------------------------------- 1 | // STEP05: テストを書いてみよう 2 | package greeting 3 | 4 | import ( 5 | "bytes" 6 | "testing" 7 | "time" 8 | 9 | "golang.org/x/text/language" 10 | ) 11 | 12 | // Greeting.Doメソッドのテスト 13 | func TestGreeting_Do(t *testing.T) { 14 | // パッケージのlangを入れ替える 15 | orgLang := lang 16 | lang = language.Japanese 17 | defer func() { 18 | // deferで元に戻す 19 | lang = orgLang 20 | }() 21 | 22 | // Greeting型の値を作る 23 | g := Greeting{ 24 | Clock: ClockFunc(func() time.Time { 25 | // 2018/08/31 06:00:00を返すようにしておく 26 | return time.Date(2018, 8, 31, 06, 0, 0, 0, time.Local) 27 | }), 28 | } 29 | 30 | var buf bytes.Buffer 31 | if err := g.Do(&buf); err != nil { 32 | t.Error("unexpected error:", err) 33 | } 34 | 35 | if expected, actual := "おはよう", buf.String(); expected != actual { 36 | t.Errorf("greeting message wont %s but got %s", expected, actual) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /greeting/solution/step06/README.md: -------------------------------------------------------------------------------- 1 | # STEP06: テストのパッケージを分けよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * 非公開な機能を使ったテスト 6 | 7 | ## 動かし方 8 | 9 | ```sh 10 | $ export GOBIN=`pwd`/_bin # 1度だけ 11 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 12 | $ _bin/step06 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/solution/step06/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/solution/step06/export_test.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | package greeting 3 | 4 | import "golang.org/x/text/language" 5 | 6 | // パッケージ変数langを一時的に変更する関数 7 | // greetingパッケージだがファイル名が_test.goで終わるため 8 | // go testの際しかビルドされない 9 | func ExportSetLang(l language.Tag) func() { 10 | orgLang := lang 11 | lang = l 12 | return func() { 13 | // langを元に戻す 14 | lang = orgLang 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /greeting/solution/step06/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/solution/step06/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/solution/step06/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /greeting/solution/step06/greeting_test.go: -------------------------------------------------------------------------------- 1 | // STEP06: テストのパッケージを分けよう 2 | package greeting_test 3 | 4 | import ( 5 | "bytes" 6 | "testing" 7 | "time" 8 | 9 | "github.com/tenntenn/gohandson/greeting" 10 | "golang.org/x/text/language" 11 | ) 12 | 13 | // Greeting.Doメソッドのテスト 14 | func TestGreeting_Do(t *testing.T) { 15 | // 言語を日本語にしておき、関数実行時に元に戻す 16 | defer greeting.ExportSetLang(language.Japanese)() 17 | 18 | // greeting.Greeting型の値を作る 19 | g := greeting.Greeting{ 20 | Clock: greeting.ClockFunc(func() time.Time { 21 | // 2018/08/31 06:00:00を返すようにしておく 22 | return time.Date(2018, 8, 31, 06, 0, 0, 0, time.Local) 23 | }), 24 | } 25 | 26 | var buf bytes.Buffer 27 | if err := g.Do(&buf); err != nil { 28 | t.Error("unexpected error:", err) 29 | } 30 | 31 | if expected, actual := "おはよう", buf.String(); expected != actual { 32 | t.Errorf("greeting message wont %s but got %s", expected, actual) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /greeting/solution/step07/README.md: -------------------------------------------------------------------------------- 1 | # STEP07: テストヘルパーを作ってみよう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * テストヘルパーの作り方 6 | 7 | ## 動かし方 8 | 9 | ```sh 10 | $ export GOBIN=`pwd`/_bin # 1度だけ 11 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 12 | $ _bin/step07 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/solution/step07/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/solution/step07/export_test.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | package greeting 3 | 4 | import "golang.org/x/text/language" 5 | 6 | // パッケージ変数langを一時的に変更する関数 7 | // greetingパッケージだがファイル名が_test.goで終わるため 8 | // go testの際しかビルドされない 9 | func ExportSetLang(l language.Tag) func() { 10 | orgLang := lang 11 | lang = l 12 | return func() { 13 | lang = orgLang 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greeting/solution/step07/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/solution/step07/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/solution/step07/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /greeting/solution/step07/greeting_test.go: -------------------------------------------------------------------------------- 1 | // STEP07: テストヘルパーを作ってみよう 2 | package greeting_test 3 | 4 | import ( 5 | "bytes" 6 | "testing" 7 | "time" 8 | 9 | "github.com/tenntenn/gohandson/greeting" 10 | "golang.org/x/text/language" 11 | ) 12 | 13 | // "YYYY/MM/DD hh:mm:ss" 形式の時刻を返すようなgreeting.Clockを作る 14 | // 引数にtesting.Tと文字列で表した時刻を取得する 15 | func mockClock(t *testing.T, v string) greeting.Clock { 16 | // ヘルパーであることを明示する 17 | t.Helper() 18 | now, err := time.Parse("2006/01/02 15:04:05", v) 19 | if err != nil { 20 | // エラーが発生した場合はテスト中断させエラーにする 21 | t.Fatal("unexpected error:", err) 22 | } 23 | 24 | // nowを返す関数を作り、greeting.ClockFuncにキャストして返す 25 | return greeting.ClockFunc(func() time.Time { 26 | return now 27 | }) 28 | } 29 | 30 | // Greeting.Doメソッドのテスト 31 | func TestGreeting_Do(t *testing.T) { 32 | // 言語を日本語にしておく 33 | defer greeting.ExportSetLang(language.Japanese)() 34 | 35 | // greeting.Greeting型の値を作る 36 | g := greeting.Greeting{ 37 | Clock: mockClock(t, "2018/08/31 06:00:00"), 38 | } 39 | 40 | var buf bytes.Buffer 41 | if err := g.Do(&buf); err != nil { 42 | t.Error("unexpected error:", err) 43 | } 44 | 45 | if expected, actual := "おはよう", buf.String(); expected != actual { 46 | t.Errorf("greeting message wont %s but got %s", expected, actual) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /greeting/solution/step08/README.md: -------------------------------------------------------------------------------- 1 | # STEP08: テーブル駆動テストを行おう 2 | 3 | ## 新しく学ぶこと 4 | 5 | * テーブル駆動テスト 6 | 7 | ## 動かし方 8 | 9 | ```sh 10 | $ export GOBIN=`pwd`/_bin # 1度だけ 11 | $ go install github.com/tenntenn/gohandson/greeting/cmd/greeting 12 | $ _bin/step08 13 | ``` 14 | -------------------------------------------------------------------------------- /greeting/solution/step08/cmd/greeting/main.go: -------------------------------------------------------------------------------- 1 | // STEP08: テーブル駆動テストを行おう 2 | 3 | // mainパッケージの定義 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | // greetingパッケージのインポート 10 | "github.com/tenntenn/gohandson/greeting" 11 | ) 12 | 13 | // main関数から実行される 14 | func main() { 15 | // Greeting型の変数を定義 16 | var g greeting.Greeting 17 | // 引数にos.Stdoutを渡してDoメソッドを呼び出す 18 | g.Do(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /greeting/solution/step08/export_test.go: -------------------------------------------------------------------------------- 1 | // STEP08: テーブル駆動テストを行おう 2 | package greeting 3 | 4 | import "golang.org/x/text/language" 5 | 6 | // パッケージ変数langを一時的に変更する関数 7 | // greetingパッケージだがファイル名が_test.goで終わるため 8 | // go testの際しかビルドされない 9 | func ExportSetLang(l language.Tag) func() { 10 | orgLang := lang 11 | lang = l 12 | return func() { 13 | lang = orgLang 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greeting/solution/step08/go.mod: -------------------------------------------------------------------------------- 1 | // 以下の手順で作成 2 | // $ export GO111MODULE=on 3 | // $ go mod init github.com/tenntenn/gohandson/greeting 4 | module github.com/tenntenn/gohandson/greeting 5 | 6 | go 1.12 7 | 8 | // 依存するモジュールを記述する 9 | require ( 10 | github.com/tenntenn/greeting/v2 v2.1.0 11 | golang.org/x/text v0.3.2 12 | ) 13 | -------------------------------------------------------------------------------- /greeting/solution/step08/go.sum: -------------------------------------------------------------------------------- 1 | github.com/tenntenn/greeting/v2 v2.1.0 h1:SD0Qx/kzr0MYDglhFPzNR4zk846+Z5T0Ol+Odr9Pk4c= 2 | github.com/tenntenn/greeting/v2 v2.1.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I= 3 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 4 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 5 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /greeting/solution/step08/greeting.go: -------------------------------------------------------------------------------- 1 | // STEP08: テーブル駆動テストを行おう 2 | package greeting 3 | 4 | import ( 5 | // fmtパッケージのインポート 6 | "fmt" 7 | "io" 8 | // timeパッケージのインポート 9 | "time" 10 | 11 | // textパッケージのインポート 12 | "github.com/tenntenn/greeting/v2/text" 13 | ) 14 | 15 | // デフォルトの言語 16 | var lang = text.DefaultLang() 17 | 18 | // 時刻の取得を抽象化したインタフェース 19 | type Clock interface { 20 | Now() time.Time 21 | } 22 | 23 | // 時刻を返すような関数をClockFunc型として定義 24 | type ClockFunc func() time.Time 25 | 26 | // 関数にClockインタフェースを実装させる 27 | func (f ClockFunc) Now() time.Time { 28 | // レシーバは関数なのでそのまま呼び出す 29 | return f() 30 | } 31 | 32 | // 挨拶を行うための構造体型 33 | type Greeting struct { 34 | // Clockインタフェースをフィールドに持つことで 35 | // 時刻の取得を抽象化する 36 | Clock Clock 37 | } 38 | 39 | // 現在時刻を取得する 40 | // Clockフィールドがnilの場合はtime.Now()の値を使う 41 | // nilじゃない場合はg.Clock.Now()の値を使う 42 | func (g *Greeting) now() time.Time { 43 | if g.Clock == nil { 44 | // time.Now()の値を使う 45 | return time.Now() 46 | } 47 | // g.Clock.Now()の値を使う 48 | return g.Clock.Now() 49 | } 50 | 51 | // Do関数の定義 52 | // パッケージ外からアクセスできるように関数名を大文字から始める 53 | // 引数にio.Writerを取ることで出力先を自由に変えることができる 54 | // 55 | // 以下のように時間よってメッセージを変える 56 | // 04時00分 〜 09時59分: おはよう 57 | // 10時00分 〜 16時59分: こんにちは 58 | // 17時00分 〜 03時59分: こんばんは 59 | func (g *Greeting) Do(w io.Writer) error { 60 | h := g.now().Hour() 61 | var msg string 62 | switch { 63 | case h >= 4 && h <= 9: 64 | msg = text.GoodMorning(lang) 65 | case h >= 10 && h <= 16: 66 | msg = text.Hello(lang) 67 | default: 68 | msg = text.GoodEvening(lang) 69 | } 70 | 71 | _, err := fmt.Fprint(w, msg) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /guestbook/ja/README.md: -------------------------------------------------------------------------------- 1 | # ゲストブックを作ろう 2 | 3 | * STEP0: 環境構築 4 | * STEP1: Hello, World 5 | * STEP2: デプロイしてみよう 6 | * STEP3: リクエストを取得する 7 | * STEP4: テンプレートエンジンを用いる 8 | * STEP5: データストアにデータを保存する 9 | * STEP6: データストアからデータを取得する 10 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step0/README.md: -------------------------------------------------------------------------------- 1 | # STEP0: 環境構築 2 | 3 | ハンズオンを行うために、以下の環境を整える必要があります。 4 | ハンズオンをスムーズに行うために、可能な場合は以下の準備をお願いします。 5 | 6 | * Googleアカウントの準備(Gmailが使える状態) 7 | * Google App Engine SDK for Goのインストール 8 | * Python 2.7のインストール 9 | 10 | 上記のインストールが難しい場合は会場のネットワーク環境に負荷を与えないために、以下の準備をお願いします。 11 | 12 | * Googleアカウントの準備(Gmailが使える状態) 13 | * Google App Engine SDK for Goのダウンロード([Mac](https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_darwin_amd64-1.9.62.zip)・[Windows](https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_windows_amd64-1.9.62.zip)) 14 | * Python2.7のダウンロード(Macは不要・[Windowsの場合](https://www.python.org/ftp/python/2.7.4/python-2.7.4.msi)) 15 | 16 | ## Googleアカウントの準備 17 | 18 | Google App Engineを用いるには、Googleアカウントが必要です。 19 | お持ちでない方はGoogleのページから作成をお願いします。 20 | Gmailが使える状態であれば、問題はありません。 21 | 22 | ## Google App Engine SDK for Goのインストール 23 | 24 | ### SDKのダウンロード 25 | 26 | [ダウンロードページ](https://cloud.google.com/appengine/docs/standard/go/download)から 27 | `Download and install the original App Engine SDK for Go.`をクリックし、使用しているOS用のファイルをダウンロードする。 28 | 29 | ダウンロードしたファイルを適切な場所に解凍しておく。 30 | 31 | ### PATHを通す 32 | 33 | 解凍したディレクトリ以下の`go_appengine`へPATHを通します。 34 | 35 | #### Macの場合 36 | .bashrcや.zchrcなどに、以下のように記載してください。 37 | なお、`DIRECTORY_PATH`はダウンロードして解凍したSDKの場所です。 38 | 39 | ``` 40 | export PATH=$PATH:DIRECTORY_PATH/go_appengine/ 41 | ``` 42 | 43 | #### Windowsの場合 44 | 45 | システム環境設定でPATHという環境変数に`DIRECTORY_PATH\go_appengine`を追加してください。 46 | なお、`DIRECTORY_PATH`はダウンロードして解凍したSDKの場所です。 47 | 48 | ### 1.2.3. Python 2.7のインストール 49 | 50 | #### Macの場合 51 | 52 | Macの場合は最初からPython 2.7がインストールされています。 53 | ターミナルでデフォルトのPythonのバージョンを確認してください。 54 | 55 | ``` 56 | /usr/bin/env/python -V 57 | ``` 58 | 59 | #### Windowsの場合 60 | 61 | コマンドプロンプトで以下のコマンドを実行し、Python 2.7がインストールされていることが分からない場合は、以下の手順でPython 2.7をインストールしてください。 62 | 63 | ``` 64 | python -V 65 | ``` 66 | 67 | [PythonのWebサイト](https://www.python.org/download/releases/2.7.4)からPython 2.7をダウンロードし、インストールしてください。 68 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step1/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step1/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/log" 9 | ) 10 | 11 | func index(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | // TODO: "Hello, Google App Engine"とINFOログで出す。 14 | // TODO: "Hello, Google App Engine"とwに書き込む。 15 | } 16 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step1/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | // TODO: ハンドラの登録 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step2/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step2/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/log" 9 | ) 10 | 11 | func index(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | log.Infof(ctx, "Hello, Google App Engine") 14 | fmt.Fprintln(w, "Hello, Google App Engine") 15 | } 16 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step2/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step3/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step3/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func index(w http.ResponseWriter, r *http.Request) { 9 | var msg string 10 | // TODO: msgという名前のリクエストパラメタを取得 11 | if msg == "" { 12 | msg = "NO MESSAGE" 13 | } 14 | fmt.Fprintln(w, msg) 15 | } 16 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step3/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step4/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step4/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | const limitMessages = 10 9 | 10 | var indexTmpl = template.Must(template.New("index").Parse(` 11 | 12 | 13 | ゲストブック 14 | 15 | 16 | {{.}} 17 | 18 | `)) 19 | 20 | func index(w http.ResponseWriter, r *http.Request) { 21 | msg := r.FormValue("msg") 22 | if msg == "" { 23 | msg = "NO MESSAGE" 24 | } 25 | if /* TODO: テンプレートからHTMLを生成し、レスポンスとして返す */; err != nil { 26 | http.Error(w, err.Error(), http.StatusInternalServerError) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step4/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step5/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step5/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | var indexTmpl = template.Must(template.New("index").Parse(` 9 | 10 | 11 | ゲストブック 12 | 13 | 14 |
15 | 16 | 17 | 18 |
19 | 20 | `)) 21 | 22 | func index(w http.ResponseWriter, r *http.Request) { 23 | if err := indexTmpl.Execute(w, nil); err != nil { 24 | http.Error(w, err.Error(), http.StatusInternalServerError) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step5/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/post", post) 7 | http.HandleFunc("/", index) 8 | } 9 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step5/message.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | // Message はゲストブックに投稿されるメッセージです。 6 | type Message struct { 7 | Name string `datastore:"name"` 8 | Text string `datastore:"text"` 9 | CreatedAt time.Time `datastore:"createdAt"` 10 | } 11 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step5/post.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/datastore" 9 | ) 10 | 11 | func post(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | name := r.FormValue("name") 14 | if name == "" { 15 | name = "NO NAME" 16 | } 17 | 18 | text := r.FormValue("message") 19 | if text == "" { 20 | text = "NO MESSAGE" 21 | } 22 | 23 | msg := &Message{ 24 | Name: name, 25 | Text: text, 26 | CreatedAt: time.Now(), 27 | } 28 | 29 | // TODO: Message Kindに保存するために、IncompleteKeyを作成する 30 | if _, err := /* TODO: DatastoreにPutして保存する */ ; err != nil { 31 | http.Error(w, err.Error(), http.StatusInternalServerError) 32 | } 33 | 34 | http.Redirect(w, r, "/", http.StatusFound) 35 | } 36 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step6/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step6/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/datastore" 9 | ) 10 | 11 | var indexTmpl = template.Must(template.New("index").Parse(` 12 | 13 | 14 | ゲストブック 15 | 16 | 17 |
18 | 19 | 20 | 21 |
22 |
{{range .}} 23 |
24 |

{{.Name}}

25 |

{{.Text}}

26 |
27 | {{end}}
28 | 29 | `)) 30 | 31 | func index(w http.ResponseWriter, r *http.Request) { 32 | ctx := appengine.NewContext(r) 33 | 34 | var msgs []*Message 35 | // TODO: Message Kindから作成時間が新しい順に10件取得するためのクエリを作成 36 | if /* TODO: クエリに該当するEntityをすべて取得する */ ; err != nil { 37 | http.Error(w, err.Error(), http.StatusInternalServerError) 38 | return 39 | } 40 | 41 | if err := indexTmpl.Execute(w, msgs); err != nil { 42 | http.Error(w, err.Error(), http.StatusInternalServerError) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step6/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/post", post) 7 | http.HandleFunc("/", index) 8 | } 9 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step6/message.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | // Message はゲストブックに投稿されるメッセージです。 6 | type Message struct { 7 | Name string `datastore:"name"` 8 | Text string `datastore:"text"` 9 | CreatedAt time.Time `datastore:"createdAt"` 10 | } 11 | -------------------------------------------------------------------------------- /guestbook/ja/skeleton/step6/post.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/datastore" 9 | ) 10 | 11 | func post(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | name := r.FormValue("name") 14 | if name == "" { 15 | name = "NO NAME" 16 | } 17 | 18 | text := r.FormValue("message") 19 | if text == "" { 20 | text = "NO MESSAGE" 21 | } 22 | 23 | msg := &Message{ 24 | Name: name, 25 | Text: text, 26 | CreatedAt: time.Now(), 27 | } 28 | 29 | key := datastore.NewIncompleteKey(ctx, "Message", nil) 30 | if _, err := datastore.Put(ctx, key, msg); err != nil { 31 | http.Error(w, err.Error(), http.StatusInternalServerError) 32 | } 33 | 34 | http.Redirect(w, r, "/", http.StatusFound) 35 | } 36 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step0/README.md: -------------------------------------------------------------------------------- 1 | # STEP0: 環境構築 2 | 3 | ハンズオンを行うために、以下の環境を整える必要があります。 4 | ハンズオンをスムーズに行うために、可能な場合は以下の準備をお願いします。 5 | 6 | * Googleアカウントの準備(Gmailが使える状態) 7 | * Google App Engine SDK for Goのインストール 8 | * Python 2.7のインストール 9 | 10 | 上記のインストールが難しい場合は会場のネットワーク環境に負荷を与えないために、以下の準備をお願いします。 11 | 12 | * Googleアカウントの準備(Gmailが使える状態) 13 | * Google App Engine SDK for Goのダウンロード([Mac](https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_darwin_amd64-1.9.62.zip)・[Windows](https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_windows_amd64-1.9.62.zip)) 14 | * Python2.7のダウンロード(Macは不要・[Windowsの場合](https://www.python.org/ftp/python/2.7.4/python-2.7.4.msi)) 15 | 16 | ## Googleアカウントの準備 17 | 18 | Google App Engineを用いるには、Googleアカウントが必要です。 19 | お持ちでない方はGoogleのページから作成をお願いします。 20 | Gmailが使える状態であれば、問題はありません。 21 | 22 | ## Google App Engine SDK for Goのインストール 23 | 24 | ### SDKのダウンロード 25 | 26 | [ダウンロードページ](https://cloud.google.com/appengine/docs/standard/go/download)から 27 | `Download and install the original App Engine SDK for Go.`をクリックし、使用しているOS用のファイルをダウンロードする。 28 | 29 | ダウンロードしたファイルを適切な場所に解凍しておく。 30 | 31 | ### PATHを通す 32 | 33 | 解凍したディレクトリ以下の`go_appengine`へPATHを通します。 34 | 35 | #### Macの場合 36 | .bashrcや.zchrcなどに、以下のように記載してください。 37 | なお、`DIRECTORY_PATH`はダウンロードして解凍したSDKの場所です。 38 | 39 | ``` 40 | export PATH=$PATH:DIRECTORY_PATH/go_appengine/ 41 | ``` 42 | 43 | #### Windowsの場合 44 | 45 | システム環境設定でPATHという環境変数に`DIRECTORY_PATH\go_appengine`を追加してください。 46 | なお、`DIRECTORY_PATH`はダウンロードして解凍したSDKの場所です。 47 | 48 | ### 1.2.3. Python 2.7のインストール 49 | 50 | #### Macの場合 51 | 52 | Macの場合は最初からPython 2.7がインストールされています。 53 | ターミナルでデフォルトのPythonのバージョンを確認してください。 54 | 55 | ``` 56 | /usr/bin/env/python -V 57 | ``` 58 | 59 | #### Windowsの場合 60 | 61 | コマンドプロンプトで以下のコマンドを実行し、Python 2.7がインストールされていることが分からない場合は、以下の手順でPython 2.7をインストールしてください。 62 | 63 | ``` 64 | python -V 65 | ``` 66 | 67 | [PythonのWebサイト](https://www.python.org/download/releases/2.7.4)からPython 2.7をダウンロードし、インストールしてください。 68 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step1/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step1/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/log" 9 | ) 10 | 11 | func index(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | log.Infof(ctx, "Hello, Google App Engine") 14 | fmt.Fprintln(w, "Hello, Google App Engine") 15 | } 16 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step1/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step2/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step2/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/log" 9 | ) 10 | 11 | func index(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | log.Infof(ctx, "Hello, Google App Engine") 14 | fmt.Fprintln(w, "Hello, Google App Engine") 15 | } 16 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step2/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step3/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step3/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func index(w http.ResponseWriter, r *http.Request) { 9 | var msg string 10 | msg = r.FormValue("msg") 11 | if msg == "" { 12 | msg = "NO MESSAGE" 13 | } 14 | fmt.Fprintln(w, msg) 15 | } 16 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step3/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step4/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step4/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | var indexTmpl = template.Must(template.New("index").Parse(` 9 | 10 | 11 | ゲストブック 12 | 13 | 14 | {{.}} 15 | 16 | `)) 17 | 18 | func index(w http.ResponseWriter, r *http.Request) { 19 | msg := r.FormValue("msg") 20 | if msg == "" { 21 | msg = "NO MESSAGE" 22 | } 23 | if err := indexTmpl.Execute(w, msg); err != nil { 24 | http.Error(w, err.Error(), http.StatusInternalServerError) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step4/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/", index) 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step5/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step5/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | var indexTmpl = template.Must(template.New("index").Parse(` 9 | 10 | 11 | ゲストブック 12 | 13 | 14 |
15 | 16 | 17 | 18 |
19 | 20 | `)) 21 | 22 | func index(w http.ResponseWriter, r *http.Request) { 23 | if err := indexTmpl.Execute(w, nil); err != nil { 24 | http.Error(w, err.Error(), http.StatusInternalServerError) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step5/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/post", post) 7 | http.HandleFunc("/", index) 8 | } 9 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step5/message.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | // Message はゲストブックに投稿されるメッセージです。 6 | type Message struct { 7 | Name string `datastore:"name"` 8 | Text string `datastore:"text"` 9 | CreatedAt time.Time `datastore:"createdAt"` 10 | } 11 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step5/post.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/datastore" 9 | ) 10 | 11 | func post(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | name := r.FormValue("name") 14 | if name == "" { 15 | name = "NO NAME" 16 | } 17 | 18 | text := r.FormValue("message") 19 | if text == "" { 20 | text = "NO MESSAGE" 21 | } 22 | 23 | msg := &Message{ 24 | Name: name, 25 | Text: text, 26 | CreatedAt: time.Now(), 27 | } 28 | 29 | key := datastore.NewIncompleteKey(ctx, "Message", nil) 30 | if _, err := datastore.Put(ctx, key, msg); err != nil { 31 | http.Error(w, err.Error(), http.StatusInternalServerError) 32 | } 33 | 34 | http.Redirect(w, r, "/", http.StatusFound) 35 | } 36 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step6/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.8 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | secure: always 8 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step6/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/datastore" 9 | ) 10 | 11 | var indexTmpl = template.Must(template.New("index").Parse(` 12 | 13 | 14 | ゲストブック 15 | 16 | 17 |
18 | 19 | 20 | 21 |
22 |
{{range .}} 23 |
24 |

{{.Name}}

25 |

{{.Text}}

26 |
27 | {{end}}
28 | 29 | `)) 30 | 31 | func index(w http.ResponseWriter, r *http.Request) { 32 | ctx := appengine.NewContext(r) 33 | 34 | var msgs []*Message 35 | q := datastore.NewQuery("Message").Order("-createdAt").Limit(10) 36 | if _, err := q.GetAll(ctx, &msgs); err != nil { 37 | http.Error(w, err.Error(), http.StatusInternalServerError) 38 | return 39 | } 40 | 41 | if err := indexTmpl.Execute(w, msgs); err != nil { 42 | http.Error(w, err.Error(), http.StatusInternalServerError) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step6/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func init() { 6 | http.HandleFunc("/post", post) 7 | http.HandleFunc("/", index) 8 | } 9 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step6/message.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | // Message はゲストブックに投稿されるメッセージです。 6 | type Message struct { 7 | Name string `datastore:"name"` 8 | Text string `datastore:"text"` 9 | CreatedAt time.Time `datastore:"createdAt"` 10 | } 11 | -------------------------------------------------------------------------------- /guestbook/ja/solution/step6/post.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "google.golang.org/appengine" 8 | "google.golang.org/appengine/datastore" 9 | ) 10 | 11 | func post(w http.ResponseWriter, r *http.Request) { 12 | ctx := appengine.NewContext(r) 13 | name := r.FormValue("name") 14 | if name == "" { 15 | name = "NO NAME" 16 | } 17 | 18 | text := r.FormValue("message") 19 | if text == "" { 20 | text = "NO MESSAGE" 21 | } 22 | 23 | msg := &Message{ 24 | Name: name, 25 | Text: text, 26 | CreatedAt: time.Now(), 27 | } 28 | 29 | key := datastore.NewIncompleteKey(ctx, "Message", nil) 30 | if _, err := datastore.Put(ctx, key, msg); err != nil { 31 | http.Error(w, err.Error(), http.StatusInternalServerError) 32 | } 33 | 34 | http.Redirect(w, r, "/", http.StatusFound) 35 | } 36 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | pkg 3 | golang.org 4 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step1/README.md: -------------------------------------------------------------------------------- 1 | # STEP 1: go installしてみよう 2 | 3 | STEP 1では、簡単なコマンドを`go install`でビルドし、`GOPATH`以下にインストールする方法について説明します。 4 | 5 | コマンドラインツールを作るには、`cmd`ディレクトリを作り、その下にコマンド名のディレクトリを作ります。 6 | そして、その下に`main`パッケージの`go`ファイルを置きます。 7 | 8 | ``` 9 | $GOPATH 10 | └── src 11 | └── step1 12 | └── cmd 13 | └── imgconv 14 | └── main.go 15 | ``` 16 | 17 | `GOPATH`は、Javaのクラスパスのようなもので、`import`する際に設定された`GOPATH`以下からパッケージを探します。 18 | `GOPATH`以下には、`src`、`bin`、`pkg`などがあり、`src`にはソースコードが、`bin`にはコンパイル済のバイナリが、`pkg`にはコンパイル済のパッケージが入っています。 19 | 20 | `go get`を行うことで、指定したリポジトリからソースコードを取得し、`GOPATH`以下へパッケージやバイナリをインストールします。 21 | すでにソースコードがある場合は、`go install`でインストールすることができます。 22 | 23 | さて、`GOPATH`がこのハンズオンの`src`よりひとつ上のディレクトリに設定してあった場合、 24 | 以下のように実行すると`step1/cmd/imgconv/main.go`がビルドされ、 25 | `GOPATH`以下の`bin`ディレクトリにバイナリがインストールされます。 26 | 27 | ``` 28 | $ go install step1/cmd/imgconv 29 | $ $GOPATH/bin/imgconv 30 | hello 31 | ``` 32 | 33 | ## 目次 34 | 35 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 36 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 37 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 38 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 39 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 40 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 41 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 42 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 43 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step1/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello") 7 | } 8 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step2/README.md: -------------------------------------------------------------------------------- 1 | # STEP 2: コマンドライン引数を取ろう 2 | 3 | コマンドラインツールは、コマンド実行時にいくつかの引数を取ることが多いでしょう。 4 | STEP 2では、コマンドライン引数で受け取った文字列を標準出力に出力して見ます。 5 | 6 | ここでは、コマンドライン引数の取り方と出力に使用する`fmt`パッケージについて説明します。 7 | 8 | ## コマンドライン引数 9 | `os.Args`は、コマンドライン引数が入った`string`型のスライスです。 10 | 以下のように、コマンドを実行した場合、`["imgconv", "input.png", "output.png"]`のような値が入ります。 11 | 12 | ``` 13 | $ imgconv input.png output.png 14 | ``` 15 | 16 | ## fmtパッケージ 17 | `fmt.Println`や`fmt.Fprintf`などを提供するパッケージです。 18 | 19 | `fmt.Fprintf`は、第1引数に`io.Writer`インタフェースを取ることができ、このインタフェースを実装した(`Writer`メソッドを持つ)型であれば、どのような型に対しても書式を指定して出力できます。 20 | 21 | `fmt.Errorf`は、指定した書式でエラーメッセージを記述し、そのエラーメッセージを`Error`メソッドで返す`error`インタフェース型の値を返します。 22 | 23 | ## 実行例 24 | 25 | ``` 26 | $ pwd 27 | /path/to/gohandson/imgconv/ja/skeleton 28 | $ GOPATH=`pwd` 29 | $ go install step2/cmd/imgconv 30 | $ ./bin/imgconv input.txt output.txt 31 | ./bin/imgconv 32 | input.txt 33 | output.txt 34 | ``` 35 | 36 | ## 目次 37 | 38 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 39 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 40 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 41 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 42 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 43 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 44 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 45 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 46 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step2/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func run() error { 9 | 10 | // TODO: 引数が足りない場合は、エラーを返す 11 | 12 | fmt.Println(os.Args[0]) 13 | fmt.Println(os.Args[1]) 14 | fmt.Println(os.Args[2]) 15 | 16 | return nil 17 | } 18 | 19 | func main() { 20 | if err := run(); err != nil { 21 | // TODO: 標準エラー出力(os.Stderr)にエラーを出力する 22 | 23 | os.Exit(1) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step3/README.md: -------------------------------------------------------------------------------- 1 | # STEP 3: ファイルを扱おう 2 | 3 | コマンドラインツールでファイルを扱うことは少なくないでしょう。 4 | STEP 3では、コマンドライン引数で指定されたテキストファイルを開き、 5 | 行ごとに加工して、コマンドライン引数でもう一つ指定されたテキストファイルへと書きだすコマンドを作ります。 6 | 7 | ここでは、Goでファイルを読みこんだり、書き込む方法について説明します。 8 | 9 | ## ファイルを開いて閉じる 10 | `os.Open`を使うと読み込み専用のファイルが開けます。 11 | また、`os.Create`を使うと読み書き可能なファイルを作ることができます。 12 | どちらの関数もファイルを開くことができると、`*os.File`型の値が返って来ます。 13 | なお、ファイルが開けなかったりすると、`error`が返ってくるので、適切に処理をします。 14 | 15 | 開いたファイルは、必要がなくなったら`Close`メソッドを使って閉じます。 16 | `defer`を使って、`defer f.Close()`のように関数の終わりに閉じる場合が多いでしょう。 17 | なお、`defer`は関数の遅延実行を行う機能で、`defer`の後ろに書いた関数呼び出しは、 18 | 実行中の関数が`return`される直前に呼ばれます。 19 | 1つの関数内で複数`defer`を書いた場合は、最後に記述したものから実行されます。 20 | 21 | ## ファイル型 22 | `os.File`型はファイルを表す型です。 23 | そのポインタ型の`*os.File`型が`io.Writer`インタフェースと`io.Reader`インタフェースを実装しています。 24 | 多くの標準パッケージで、これらのインタフェースを引数に取ったり、戻りに返したりします。 25 | とくに、`io`パッケージ、`bytes`パッケージ、`bufio`パッケージ、`encoding`パッケージなどで多用されているので、 26 | 一度ドキュメントを読むと良いでしょう。 27 | 28 | 29 | ## 実行例 30 | 31 | ``` 32 | $ pwd 33 | /path/to/gohandson/imgconv/ja/skeleton 34 | $ GOPATH=`pwd` 35 | $ go install step3/cmd/imgconv 36 | $ echo foo > input.txt 37 | $ echo bar >> input.txt 38 | $ cat input.txt 39 | foo 40 | bar 41 | $ ./bin/imgconv input.txt output.txt 42 | $ cat output.txt 43 | 1:foo 44 | 2:bar 45 | ``` 46 | 47 | ## 目次 48 | 49 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 50 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 51 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 52 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 53 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 54 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 55 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 56 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 57 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step3/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func run() error { 10 | 11 | if len(os.Args) < 3 { 12 | return fmt.Errorf("引数が足りません。") 13 | } 14 | 15 | src, dst := os.Args[1], os.Args[2] 16 | 17 | sf, err := os.Open(src) 18 | if err != nil { 19 | return fmt.Errorf("ファイルが開けませんでした。%s", src) 20 | } 21 | // TODO: 関数終了時にファイルを閉じる 22 | 23 | df, err := os.Create(dst) 24 | if err != nil { 25 | return fmt.Errorf("ファイルを書き出せませんでした。%s", dst) 26 | } 27 | // TODO: 関数終了時にファイルを閉じる 28 | 29 | scanner := bufio.NewScanner(sf) 30 | // TODO: sfから1行ずつ読み込み、"行数:"を前に付けてdfに書き出す。 31 | 32 | // TODO: scannerから得られたエラーを返す 33 | } 34 | 35 | func main() { 36 | if err := run(); err != nil { 37 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 38 | os.Exit(1) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step4/README.md: -------------------------------------------------------------------------------- 1 | # STEP 4: 画像形式を変換しよう 2 | 3 | STEP 4では、いよいよ画像を扱います。 4 | ここでは、1つめのコマンドライン引数で指定された画像ファイルを開き、 5 | 2つめの引数で指定されたファイル名で画像を保存します。 6 | このとき、拡張子を見て保存する画像形式を判断します。 7 | 8 | ここでは画像を扱うために必要な`image`パッケージとパスを扱う`path`パッケージ、 9 | 文字列処理を行う`strings`パッケージについて説明を行います。 10 | 11 | ## imageパッケージ 12 | `image`パッケージは、画像を扱うパッケージです。 13 | 画像を表す`image.Image`インタフェースやそれを実装する具体的な型が定義されています。 14 | 15 | `image/png`パッケージや`image/jpeg`パッケージでは、`png`や`jpeg`形式の画像を`io.Reader`から`image.Image`にデコードしたり、`image.Image`から`io.Writer`へエンコードする機能が提供されています。 16 | 17 | 18 | ## path/filepathパッケージ 19 | `path/filepath`パッケージは、パスに関する機能を提供しています。 20 | 例えば、`filepath.Ext`はファイル名から拡張子を取得でき、`filepath.Join`はOSごとの適切な区切り文字でパスを結合することができます。 21 | 22 | ## stringsパッケージ 23 | `strings`パッケージは、文字列操作に関する処理を提供するパッケージです。 24 | 例えば、`strings.ToUpper`や`strings.ToLower`など大文字/小文字に変換する関数や、`strings.Join`や`strings.Split`などを文字列を結合/分割する関数が提供されています。 25 | 26 | 多くのパッケージで、引数に`io.Reader`をとっているため、`string`型から`io.Reader`を取得したい場合があります。 27 | その場合には、`strings.NewReader`で`string`型をそのまま`io.Reader`に変換できることができます。 28 | 29 | なお、`bytes`パッケージも`[]byte`向けに、`strings`と似たような機能を提供しています。 30 | 31 | ## 実行例 32 | 33 | ``` 34 | $ pwd 35 | /path/to/gohandson/imgconv/ja/skeleton 36 | $ GOPATH=`pwd` 37 | $ go install step4/cmd/imgconv 38 | $ go install tools/cmd/httpget 39 | $ ./bin/httpget https://raw.githubusercontent.com/tenntenn/gopher-stickers/master/png/hi.png > gopher.png 40 | $ ./bin/imgconv gopher.png gopher.jpg 41 | ``` 42 | 43 | ## 目次 44 | 45 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 46 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 47 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 48 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 49 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 50 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 51 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 52 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 53 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step4/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | // TODO: pngとjpegをデコードできるようにimportする。 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func convert(dst, src string) error { 13 | 14 | sf, err := os.Open(src) 15 | if err != nil { 16 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 17 | } 18 | defer sf.Close() 19 | 20 | df, err := os.Create(dst) 21 | if err != nil { 22 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 23 | } 24 | defer df.Close() 25 | 26 | // TODO: 入力ファイルから画像をメモリ上にデコードする。 27 | if err != nil { 28 | return err 29 | } 30 | 31 | // TODO: 拡張子によって保存する形式を変える。 32 | // ".png"の場合は、png形式で、".jpeg"と".jpg"の場合はjpeg形式で保存する。 33 | // 拡張子は大文字でも小文字でも動作するようにする。 34 | // なお、jpegは`jpeg.DefaultQuality`で保存する。 35 | // エラー処理も忘れないようにする。 36 | 37 | return nil 38 | } 39 | 40 | func run() error { 41 | if len(os.Args) < 3 { 42 | return fmt.Errorf("画像ファイルを指定してください。") 43 | } 44 | 45 | return convert(os.Args[2], os.Args[1]) 46 | } 47 | 48 | func main() { 49 | if err := run(); err != nil { 50 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 51 | os.Exit(1) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step5/README.md: -------------------------------------------------------------------------------- 1 | # STEP 5: `flag`パッケージを使おう 2 | 3 | STEP 5では、`flag`パッケージを使いコマンドライン引数からフラグを取得してみます。 4 | 5 | コマンドライン引数を`flag`パッケージを使ってパースすると、`string`型や`bool`型の値をフラグとして受け取ることができます。 6 | 7 | `flag.StringVar`関数や`flag.IntVar`関数は、引数にその型の変数のポインタとデフォルトの値、使い方を渡します。そして、`flag.Parse`が呼ばれると、コマンドライン引数がパースされ、第1引数で渡したポインタの指す先に値が設定されます。 8 | 9 | フラグのパースは、`init`関数の中で行われることが多いでしょう。`init`関数は、パッケージがインポートされた際に呼ばれる関数で、`main`パッケージの場合も`main`関数が実行される前に呼ばれます。なお、`init`関数は、1つのパッケージ、1つのファイル中にいくつも書くことができます。 10 | 11 | `flag.Args`関数を使うと、フラグとしてパースされた部分以外のコマンドライン引数を取ることができます。 12 | `os.Args`スライスと似たような値を返しますが、`flag.Args`関数が返すスライスは、`0`番目の要素にコマンド名は含まれません。 13 | 14 | ## 実行例 15 | 16 | ``` 17 | $ pwd 18 | /path/to/gohandson/imgconv/ja/skeleton 19 | $ GOPATH=`pwd` 20 | $ go install step5/cmd/imgconv 21 | $ ./bin/imgconv -h 22 | Usage of ./bin/imgconv: 23 | -clip 幅[px|%]x高さ[px|%] 24 | 切り取る画像サイズ(幅[px|%]x高さ[px|%]) 25 | $ go install tools/cmd/httpget 26 | $ ./bin/httpget https://raw.githubusercontent.com/tenntenn/gopher-stickers/master/png/hi.png > gopher.png 27 | $ ./bin/imgconv -clip 10x10 gopher.png gopher.jpg 28 | 切り抜きを行う予定 10x10 29 | ``` 30 | 31 | ## 目次 32 | 33 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 34 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 35 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 36 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 37 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 38 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 39 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 40 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 41 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step5/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | var ( 15 | clip string 16 | ) 17 | 18 | func init() { 19 | // TODO: clipというフラグを追加し、変数clipに入れる。 20 | // デフォルト値は、""。 21 | // 説明は、"切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)"。 22 | 23 | // TODO: フラグをパースする。 24 | } 25 | 26 | func convert(dst, src string) error { 27 | 28 | sf, err := os.Open(src) 29 | if err != nil { 30 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 31 | } 32 | defer sf.Close() 33 | 34 | df, err := os.Create(dst) 35 | if err != nil { 36 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 37 | } 38 | defer df.Close() 39 | 40 | img, _, err := image.Decode(sf) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // TODO: clipで何か指定されていれば、 46 | // 標準出力に"切り抜きを行う予定"という文字列とともにclipの中身を出力する 47 | 48 | switch strings.ToLower(filepath.Ext(dst)) { 49 | case ".png": 50 | err = png.Encode(df, img) 51 | case ".jpeg", ".jpg": 52 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 53 | } 54 | 55 | if err != nil { 56 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func run() error { 63 | // TODO: os.Argsではなく、flag.Args()を使ってコマンドライン引数を取得する。 64 | 65 | // TODO: フラグ(オプション)以外で、引数が2つ以上指定されているかチェックする。 66 | // 引数が2つ以上指定されていない場合は、"画像ファイルを指定してください。"というエラーを返す。 67 | 68 | return convert(args[1], args[0]) 69 | } 70 | 71 | func main() { 72 | if err := run(); err != nil { 73 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 74 | os.Exit(1) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step6/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "step6/imgconv" 14 | ) 15 | 16 | var ( 17 | clip string 18 | ) 19 | 20 | func init() { 21 | flag.StringVar(&clip, "clip", "", "切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)") 22 | flag.Parse() 23 | } 24 | 25 | func convert(dst, src string) error { 26 | 27 | sf, err := os.Open(src) 28 | if err != nil { 29 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 30 | } 31 | defer sf.Close() 32 | 33 | df, err := os.Create(dst) 34 | if err != nil { 35 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 36 | } 37 | defer df.Close() 38 | 39 | _img, _, err := image.Decode(sf) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | // TODO: _imgを埋め込んだ、imgconv.Image型の値を作る 45 | 46 | if clip != "" { 47 | if err := img.Clip(clip); err != nil { 48 | return fmt.Errorf("%s\n", err.Error()) 49 | } 50 | } 51 | 52 | switch strings.ToLower(filepath.Ext(dst)) { 53 | case ".png": 54 | err = png.Encode(df, img) 55 | case ".jpeg", ".jpg": 56 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 57 | } 58 | 59 | if err != nil { 60 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func run() error { 67 | args := flag.Args() 68 | if len(args) < 2 { 69 | return fmt.Errorf("画像ファイルを指定してください。") 70 | } 71 | 72 | return convert(args[1], args[0]) 73 | } 74 | 75 | func main() { 76 | if err := run(); err != nil { 77 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 78 | os.Exit(1) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step7/README.md: -------------------------------------------------------------------------------- 1 | # STEP 7: 画像を縮小/拡大しよう 2 | 3 | STEP 7では、さらにコマンドに機能を追加し、画像の縮小/拡大を行えるようにします。 4 | 拡大するサイズは、`resize`という名前でフラグとしてコマンドライン引数で指定します。 5 | 縮小/拡大を行うには、STEP 6で使用した`image/draw`パッケージの代わりに、`golang.org/x/image/draw`パッケージを使用します。 6 | 7 | ここでは、`golang.org/x/image`パッケージの説明と外部パッケージの`go get`の方法について説明します。 8 | 9 | ## golang.org/x/imageパッケージ 10 | 11 | `golang.org/x/image`パッケージは、標準パッケージの`image`パッケージより機能を増やしたパッケージです。 12 | 特に`draw`パッケージは、スケールなどの機能が追加されています。 13 | 14 | `golang.org/x`以下にあるパッケージは、サブプロジェクトとしてGoチームによって保守されています。 15 | `image`以外にも、`golang.org/x/net`など便利なパッケージがありますので、一度覗いてみると良いでしょう。 16 | 17 | このステップでは、`golang.org/x/image/draw`パッケージを使用しますので、`go get`しておきましょう。 18 | 以下の通り、`go get`を実行すると、`src`と`pkg`以下に`golang.org/x/image/draw`がインストールされている事が分かります。 19 | 20 | ``` 21 | $ go get golang.org/x/image/draw 22 | $ tree pkg 23 | pkg 24 | └── darwin_amd64 25 | └── golang.org 26 | └── x 27 | └── image 28 | ├── draw.a 29 | └── math 30 | └── f64.a 31 | $ ls src/golang.org/x/image/ 32 | AUTHORS LICENSE bmp colornames font testdata vp8l 33 | CONTRIBUTING.md PATENTS cmd draw math tiff webp 34 | CONTRIBUTORS README codereview.cfg example riff vp8 35 | ``` 36 | 37 | ## 実行例 38 | 39 | ``` 40 | $ pwd 41 | /path/to/gohandson/imgconv/ja/skeleton 42 | $ GOPATH=`pwd` 43 | $ go install step7/cmd/imgconv 44 | $ go install tools/cmd/httpget 45 | $ ./bin/httpget https://raw.githubusercontent.com/tenntenn/gopher-stickers/master/png/hi.png > gopher.png 46 | $ ./bin/imgconv -resize 50%x50% gopher.png gopher2.png 47 | ``` 48 | 49 | ## 目次 50 | 51 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 52 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 53 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 54 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 55 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 56 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 57 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 58 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 59 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/step7/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "step7/imgconv" 14 | ) 15 | 16 | var ( 17 | clip string 18 | resize string 19 | ) 20 | 21 | func init() { 22 | flag.StringVar(&clip, "clip", "", "切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)") 23 | flag.StringVar(&resize, "resize", "", "出力する画像サイズ(`幅[px|%]x高さ[px|%]`)") 24 | flag.Parse() 25 | } 26 | 27 | func convert(dst, src string) error { 28 | 29 | sf, err := os.Open(src) 30 | if err != nil { 31 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 32 | } 33 | defer sf.Close() 34 | 35 | df, err := os.Create(dst) 36 | if err != nil { 37 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 38 | } 39 | defer df.Close() 40 | 41 | _img, _, err := image.Decode(sf) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | img := imgconv.Image{_img} 47 | 48 | if clip != "" { 49 | if err := img.Clip(clip); err != nil { 50 | return fmt.Errorf("%s\n", err.Error()) 51 | } 52 | } 53 | 54 | // TODO: resizeが指定されていれば、リサイズを行う。 55 | 56 | switch strings.ToLower(filepath.Ext(dst)) { 57 | case ".png": 58 | err = png.Encode(df, img) 59 | case ".jpeg", ".jpg": 60 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 61 | } 62 | 63 | if err != nil { 64 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func run() error { 71 | args := flag.Args() 72 | if len(args) < 2 { 73 | return fmt.Errorf("画像ファイルを指定してください。") 74 | } 75 | 76 | return convert(args[1], args[0]) 77 | } 78 | 79 | func main() { 80 | if err := run(); err != nil { 81 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 82 | os.Exit(1) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /imgconv/ja/skeleton/src/tools/cmd/httpget/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func run() error { 11 | res, err := http.Get(os.Args[1]) 12 | if err != nil { 13 | return err 14 | } 15 | _, err = io.Copy(os.Stdout, res.Body) 16 | return err 17 | } 18 | 19 | func main() { 20 | if err := run(); err != nil { 21 | fmt.Fprintln(os.Stderr, "Error:", err) 22 | os.Exit(1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /imgconv/ja/solution/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | pkg 3 | golang.org 4 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step1/README.md: -------------------------------------------------------------------------------- 1 | # STEP 1: go installしてみよう 2 | 3 | STEP 1では、簡単なコマンドを`go install`でビルドし、`GOPATH`以下にインストールする方法について説明します。 4 | 5 | コマンドラインツールを作るには、`cmd`ディレクトリを作り、その下にコマンド名のディレクトリを作ります。 6 | そして、その下に`main`パッケージの`go`ファイルを置きます。 7 | 8 | ``` 9 | $GOPATH 10 | └── src 11 | └── step1 12 | └── cmd 13 | └── imgconv 14 | └── main.go 15 | ``` 16 | 17 | `GOPATH`は、Javaのクラスパスのようなもので、`import`する際に設定された`GOPATH`以下からパッケージを探します。 18 | `GOPATH`以下には、`src`、`bin`、`pkg`などがあり、`src`にはソースコードが、`bin`にはコンパイル済のバイナリが、`pkg`にはコンパイル済のパッケージが入っています。 19 | 20 | `go get`を行うことで、指定したリポジトリからソースコードを取得し、`GOPATH`以下へパッケージやバイナリをインストールします。 21 | すでにソースコードがある場合は、`go install`でインストールすることができます。 22 | 23 | さて、`GOPATH`がこのハンズオンの`src`よりひとつ上のディレクトリに設定してあった場合、 24 | 以下のように実行すると`step1/cmd/imgconv/main.go`がビルドされ、 25 | `GOPATH`以下の`bin`ディレクトリにバイナリがインストールされます。 26 | 27 | ``` 28 | $ go install step1/cmd/imgconv 29 | $ $GOPATH/bin/imgconv 30 | hello 31 | ``` 32 | 33 | ## 目次 34 | 35 | * STEP 1: [go installしてみよう](../step1)([解答例](../../../solution/src/step1)) 36 | * STEP 2: [コマンドライン引数を取ろう](../step2)([解答例](../../../solution/src/step2)) 37 | * STEP 3: [ファイルを扱おう](../step3)([解答例](../../../solution/src/step3)) 38 | * STEP 4: [画像形式を変換しよう](../step4)([解答例](../../../solution/src/step4)) 39 | * STEP 5: [`flag`パッケージを使おう](../step5)([解答例](../../../solution/src/step5)) 40 | * STEP 6: [画像を切り抜こう](../step6)([解答例](../../../solution/src/step6)) 41 | * STEP 7: [画像を縮小/拡大しよう](../step7)([解答例](../../../solution/src/step7)) 42 | * STEP 8: [複数のファイルを処理しよう](../step8)([解答例](../../../solution/src/step8)) 43 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step1/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello") 7 | } 8 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step2/README.md: -------------------------------------------------------------------------------- 1 | # STEP 2: コマンドライン引数を取ろう 2 | 3 | コマンドラインツールは、コマンド実行時にいくつかの引数を取ることが多いでしょう。 4 | STEP 2では、コマンドライン引数で受け取った文字列を標準出力に出力して見ます。 5 | 6 | ここでは、コマンドライン引数の取り方と出力に使用する`fmt`パッケージについて説明します。 7 | 8 | ## コマンドライン引数 9 | `os.Args`は、コマンドライン引数が入った`string`型のスライスです。 10 | 以下のように、コマンドを実行した場合、`["imgconv", "input.png", "output.png"]`のような値が入ります。 11 | 12 | ``` 13 | $ imgconv input.png output.png 14 | ``` 15 | 16 | ## fmtパッケージ 17 | `fmt.Println`や`fmt.Fprintf`などを提供するパッケージです。 18 | 19 | `fmt.Fprintf`は、第1引数に`io.Writer`インタフェースを取ることができ、このインタフェースを実装した(`Writer`メソッドを持つ)型であれば、どのような型に対しても書式を指定して出力できます。 20 | 21 | `fmt.Errorf`は、指定した書式でエラーメッセージを記述し、そのエラーメッセージを`Error`メソッドで返す`error`インタフェース型の値を返します。 22 | 23 | ## 実行例 24 | 25 | ``` 26 | $ pwd 27 | /path/to/gohandson/imgconv/ja/solution 28 | $ GOPATH=`pwd` 29 | $ go install step2/cmd/imgconv 30 | $ ./bin/imgconv input.txt output.txt 31 | ./bin/imgconv 32 | input.txt 33 | output.txt 34 | ``` 35 | 36 | ## 目次 37 | 38 | * STEP 1: [go installしてみよう](../../../skeleton/src/step1)([解答例](../step1)) 39 | * STEP 2: [コマンドライン引数を取ろう](../../../skeleton/src/step2)([解答例](../step2)) 40 | * STEP 3: [ファイルを扱おう](../../../skeleton/src/step3)([解答例](../step3)) 41 | * STEP 4: [画像形式を変換しよう](../../../skeleton/src/step4)([解答例](../step4)) 42 | * STEP 5: [`flag`パッケージを使おう](../../../skeleton/src/step5)([解答例](../step5)) 43 | * STEP 6: [画像を切り抜こう](../../../skeleton/src/step6)([解答例](../step6)) 44 | * STEP 7: [画像を縮小/拡大しよう](../../../skeleton/src/step7)([解答例](../step7)) 45 | * STEP 8: [複数のファイルを処理しよう](../../../skeleton/src/step8)([解答例](../step8)) 46 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step2/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func run() error { 9 | 10 | // TODO: 引数が足りない場合は、エラーを返す 11 | if len(os.Args) < 3 { 12 | return fmt.Errorf("引数が足りません。") 13 | } 14 | 15 | fmt.Println(os.Args[0]) 16 | fmt.Println(os.Args[1]) 17 | fmt.Println(os.Args[2]) 18 | 19 | return nil 20 | } 21 | 22 | func main() { 23 | if err := run(); err != nil { 24 | // TODO: 標準エラー出力(os.Stderr)にエラーを出力する 25 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 26 | os.Exit(1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step3/README.md: -------------------------------------------------------------------------------- 1 | # STEP 3: ファイルを扱おう 2 | 3 | コマンドラインツールでファイルを扱うことは少なくないでしょう。 4 | STEP 3では、コマンドライン引数で指定されたテキストファイルを開き、 5 | 行ごとに加工して、コマンドライン引数でもう一つ指定されたテキストファイルへと書きだすコマンドを作ります。 6 | 7 | ここでは、Goでファイルを読みこんだり、書き込む方法について説明します。 8 | 9 | ## ファイルを開いて閉じる 10 | `os.Open`を使うと読み込み専用のファイルが開けます。 11 | また、`os.Create`を使うと読み書き可能なファイルを作ることができます。 12 | どちらの関数もファイルを開くことができると、`*os.File`型の値が返って来ます。 13 | なお、ファイルが開けなかったりすると、`error`が返ってくるので、適切に処理をします。 14 | 15 | 開いたファイルは、必要がなくなったら`Close`メソッドを使って閉じます。 16 | `defer`を使って、`defer f.Close()`のように関数の終わりに閉じる場合が多いでしょう。 17 | なお、`defer`は関数の遅延実行を行う機能で、`defer`の後ろに書いた関数呼び出しは、 18 | 実行中の関数が`return`される直前に呼ばれます。 19 | 1つの関数内で複数`defer`を書いた場合は、最後に記述したものから実行されます。 20 | 21 | ## ファイル型 22 | `os.File`型はファイルを表す型です。 23 | そのポインタ型の`*os.File`型が`io.Writer`インタフェースと`io.Reader`インタフェースを実装しています。 24 | 多くの標準パッケージで、これらのインタフェースを引数に取ったり、戻りに返したりします。 25 | とくに、`io`パッケージ、`bytes`パッケージ、`bufio`パッケージ、`encoding`パッケージなどで多用されているので、 26 | 一度ドキュメントを読むと良いでしょう。 27 | 28 | 29 | ## 実行例 30 | 31 | ``` 32 | $ pwd 33 | /path/to/gohandson/imgconv/ja/solution 34 | $ GOPATH=`pwd` 35 | $ go install step3/cmd/imgconv 36 | $ echo "foo\nbar" > input.txt 37 | $ cat input.txt 38 | foo 39 | bar 40 | $ ./bin/imgconv input.txt output.txt 41 | $ cat output.txt 42 | 1:foo 43 | 2:bar 44 | ``` 45 | 46 | ## 目次 47 | 48 | * STEP 1: [go installしてみよう](../../../skeleton/src/step1)([解答例](../step1)) 49 | * STEP 2: [コマンドライン引数を取ろう](../../../skeleton/src/step2)([解答例](../step2)) 50 | * STEP 3: [ファイルを扱おう](../../../skeleton/src/step3)([解答例](../step3)) 51 | * STEP 4: [画像形式を変換しよう](../../../skeleton/src/step4)([解答例](../step4)) 52 | * STEP 5: [`flag`パッケージを使おう](../../../skeleton/src/step5)([解答例](../step5)) 53 | * STEP 6: [画像を切り抜こう](../../../skeleton/src/step6)([解答例](../step6)) 54 | * STEP 7: [画像を縮小/拡大しよう](../../../skeleton/src/step7)([解答例](../step7)) 55 | * STEP 8: [複数のファイルを処理しよう](../../../skeleton/src/step8)([解答例](../step8)) 56 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step3/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func run() error { 10 | 11 | if len(os.Args) < 3 { 12 | return fmt.Errorf("引数が足りません。") 13 | } 14 | 15 | src, dst := os.Args[1], os.Args[2] 16 | 17 | sf, err := os.Open(src) 18 | if err != nil { 19 | return fmt.Errorf("ファイルが開けませんでした。%s", src) 20 | } 21 | // TODO: 関数終了時にファイルを閉じる 22 | defer sf.Close() 23 | 24 | df, err := os.Create(dst) 25 | if err != nil { 26 | return fmt.Errorf("ファイルを書き出せませんでした。%s", dst) 27 | } 28 | // TODO: 関数終了時にファイルを閉じる 29 | defer df.Close() 30 | 31 | scanner := bufio.NewScanner(sf) 32 | // TODO: sfから1行ずつ読み込み、"行数:"を前に付けてdfに書き出す。 33 | for i := 1; scanner.Scan(); i++ { 34 | fmt.Fprintf(df, "%d:%s\n", i, scanner.Text()) 35 | } 36 | 37 | // TODO: scannerから得られたエラーを返す 38 | return scanner.Err() 39 | } 40 | 41 | func main() { 42 | if err := run(); err != nil { 43 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 44 | os.Exit(1) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step4/README.md: -------------------------------------------------------------------------------- 1 | # STEP 4: 画像形式を変換しよう 2 | 3 | STEP 4では、いよいよ画像を扱います。 4 | ここでは、1つめのコマンドライン引数で指定された画像ファイルを開き、 5 | 2つめの引数で指定されたファイル名で画像を保存します。 6 | このとき、拡張子を見て保存する画像形式を判断します。 7 | 8 | ここでは画像を扱うために必要な`image`パッケージとパスを扱う`path`パッケージ、 9 | 文字列処理を行う`strings`パッケージについて説明を行います。 10 | 11 | ## imageパッケージ 12 | `image`パッケージは、画像を扱うパッケージです。 13 | 画像を表す`image.Image`インタフェースやそれを実装する具体的な型が定義されています。 14 | 15 | `image/png`パッケージや`image/jpeg`パッケージでは、`png`や`jpeg`形式の画像を`io.Reader`から`image.Image`にデコードしたり、`image.Image`から`io.Writer`へエンコードする機能が提供されています。 16 | 17 | 18 | ## path/filepathパッケージ 19 | `path/filepath`パッケージは、パスに関する機能を提供しています。 20 | 例えば、`filepath.Ext`はファイル名から拡張子を取得でき、`filepath.Join`はOSごとの適切な区切り文字でパスを結合することができます。 21 | 22 | ## stringsパッケージ 23 | `strings`パッケージは、文字列操作に関する処理を提供するパッケージです。 24 | 例えば、`strings.ToUpper`や`strings.ToLower`など大文字/小文字に変換する関数や、`strings.Join`や`strings.Split`などを文字列を結合/分割する関数が提供されています。 25 | 26 | 多くのパッケージで、引数に`io.Reader`をとっているため、`string`型から`io.Reader`を取得したい場合があります。 27 | その場合には、`strings.NewReader`で`string`型をそのまま`io.Reader`に変換できることができます。 28 | 29 | なお、`bytes`パッケージも`[]byte`向けに、`strings`と似たような機能を提供しています。 30 | 31 | ## 実行例 32 | 33 | ``` 34 | $ pwd 35 | /path/to/gohandson/imgconv/ja/solution 36 | $ GOPATH=`pwd` 37 | $ go install step4/cmd/imgconv 38 | $ go install tools/cmd/httpget 39 | $ ./bin/httpget https://raw.githubusercontent.com/tenntenn/gopher-stickers/master/png/hi.png > gopher.png 40 | $ ./bin/imgconv gopher.png gopher.jpg 41 | ``` 42 | 43 | ## 目次 44 | 45 | * STEP 1: [go installしてみよう](../../../skeleton/src/step1)([解答例](../step1)) 46 | * STEP 2: [コマンドライン引数を取ろう](../../../skeleton/src/step2)([解答例](../step2)) 47 | * STEP 3: [ファイルを扱おう](../../../skeleton/src/step3)([解答例](../step3)) 48 | * STEP 4: [画像形式を変換しよう](../../../skeleton/src/step4)([解答例](../step4)) 49 | * STEP 5: [`flag`パッケージを使おう](../../../skeleton/src/step5)([解答例](../step5)) 50 | * STEP 6: [画像を切り抜こう](../../../skeleton/src/step6)([解答例](../step6)) 51 | * STEP 7: [画像を縮小/拡大しよう](../../../skeleton/src/step7)([解答例](../step7)) 52 | * STEP 8: [複数のファイルを処理しよう](../../../skeleton/src/step8)([解答例](../step8)) 53 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step4/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | // TODO: pngとjpegをデコードできるようにimportする。 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func convert(dst, src string) error { 15 | 16 | sf, err := os.Open(src) 17 | if err != nil { 18 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 19 | } 20 | defer sf.Close() 21 | 22 | df, err := os.Create(dst) 23 | if err != nil { 24 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 25 | } 26 | defer df.Close() 27 | 28 | // TODO: 入力ファイルから画像をメモリ上にデコードする。 29 | img, _, err := image.Decode(sf) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | // TODO: 拡張子によって保存する形式を変える。 35 | // ".png"の場合は、png形式で、".jpeg"と".jpg"の場合はjpeg形式で保存する。 36 | // 拡張子は大文字でも小文字でも動作するようにする。 37 | // なお、jpegは`jpeg.DefaultQuality`で保存する。 38 | // エラー処理も忘れないようにする。 39 | switch strings.ToLower(filepath.Ext(dst)) { 40 | case ".png": 41 | err = png.Encode(df, img) 42 | case ".jpeg", ".jpg": 43 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 44 | } 45 | 46 | if err != nil { 47 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func run() error { 54 | if len(os.Args) < 3 { 55 | return fmt.Errorf("画像ファイルを指定してください。") 56 | } 57 | 58 | return convert(os.Args[2], os.Args[1]) 59 | } 60 | 61 | func main() { 62 | if err := run(); err != nil { 63 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 64 | os.Exit(1) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step5/README.md: -------------------------------------------------------------------------------- 1 | # STEP 5: `flag`パッケージを使おう 2 | 3 | STEP 5では、`flag`パッケージを使いコマンドライン引数からフラグを取得してみます。 4 | 5 | コマンドライン引数を`flag`パッケージを使ってパースすると、`string`型や`bool`型の値をフラグとして受け取ることができます。 6 | 7 | `flag.StringVar`関数や`flag.IntVar`関数は、引数にその型の変数のポインタとデフォルトの値、使い方を渡します。そして、`flag.Parse`が呼ばれると、コマンドライン引数がパースされ、第1引数で渡したポインタの指す先に値が設定されます。 8 | 9 | フラグのパースは、`init`関数の中で行われることが多いでしょう。`init`関数は、パッケージがインポートされた際に呼ばれる関数で、`main`パッケージの場合も`main`関数が実行される前に呼ばれます。なお、`init`関数は、1つのパッケージ、1つのファイル中にいくつも書くことができます。 10 | 11 | `flag.Args`関数を使うと、フラグとしてパースされた部分以外のコマンドライン引数を取ることができます。 12 | `os.Args`スライスと似たような値を返しますが、`flag.Args`関数が返すスライスは、`0`番目の要素にコマンド名は含まれません。 13 | 14 | ## 実行例 15 | 16 | ``` 17 | $ pwd 18 | /path/to/gohandson/imgconv/ja/solution 19 | $ GOPATH=`pwd` 20 | $ go install step5/cmd/imgconv 21 | $ ./bin/imgconv -h 22 | Usage of ./bin/imgconv: 23 | -clip 幅[px|%]x高さ[px|%] 24 | 切り取る画像サイズ(幅[px|%]x高さ[px|%]) 25 | $ go install tools/cmd/httpget 26 | $ ./bin/httpget https://raw.githubusercontent.com/tenntenn/gopher-stickers/master/png/hi.png > gopher.png 27 | $ ./bin/imgconv -clip 10x10 gopher.png gopher.jpg 28 | 切り抜きを行う予定 10x10 29 | ``` 30 | 31 | ## 目次 32 | 33 | * STEP 1: [go installしてみよう](../../../skeleton/src/step1)([解答例](../step1)) 34 | * STEP 2: [コマンドライン引数を取ろう](../../../skeleton/src/step2)([解答例](../step2)) 35 | * STEP 3: [ファイルを扱おう](../../../skeleton/src/step3)([解答例](../step3)) 36 | * STEP 4: [画像形式を変換しよう](../../../skeleton/src/step4)([解答例](../step4)) 37 | * STEP 5: [`flag`パッケージを使おう](../../../skeleton/src/step5)([解答例](../step5)) 38 | * STEP 6: [画像を切り抜こう](../../../skeleton/src/step6)([解答例](../step6)) 39 | * STEP 7: [画像を縮小/拡大しよう](../../../skeleton/src/step7)([解答例](../step7)) 40 | * STEP 8: [複数のファイルを処理しよう](../../../skeleton/src/step8)([解答例](../step8)) 41 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step5/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | var ( 15 | clip string 16 | ) 17 | 18 | func init() { 19 | // TODO: clipというフラグを追加し、変数clipに入れる。 20 | // デフォルト値は、""。 21 | // 説明は、"切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)"。 22 | flag.StringVar(&clip, "clip", "", "切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)") 23 | // TODO: フラグをパースする。 24 | flag.Parse() 25 | } 26 | 27 | func convert(dst, src string) error { 28 | 29 | sf, err := os.Open(src) 30 | if err != nil { 31 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 32 | } 33 | defer sf.Close() 34 | 35 | df, err := os.Create(dst) 36 | if err != nil { 37 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 38 | } 39 | defer df.Close() 40 | 41 | img, _, err := image.Decode(sf) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | // TODO: clipで何か指定されていれば、 47 | // 標準出力に"切り抜きを行う予定"という文字列とともにclipの中身を出力する 48 | if clip != "" { 49 | fmt.Println("切り抜きを行う予定", clip) 50 | } 51 | 52 | switch strings.ToLower(filepath.Ext(dst)) { 53 | case ".png": 54 | err = png.Encode(df, img) 55 | case ".jpeg", ".jpg": 56 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 57 | } 58 | 59 | if err != nil { 60 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func run() error { 67 | // TODO: os.Argsではなく、flag.Args()を使ってコマンドライン引数を取得する。 68 | args := flag.Args() 69 | // TODO: フラグ(オプション)以外で、引数が2つ以上指定されているかチェックする。 70 | // 引数が2つ以上指定されていない場合は、"画像ファイルを指定してください。"というエラーを返す。 71 | if len(args) < 2 { 72 | return fmt.Errorf("画像ファイルを指定してください。") 73 | } 74 | 75 | return convert(args[1], args[0]) 76 | } 77 | 78 | func main() { 79 | if err := run(); err != nil { 80 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 81 | os.Exit(1) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step6/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "step6/imgconv" 14 | ) 15 | 16 | var ( 17 | clip string 18 | ) 19 | 20 | func init() { 21 | flag.StringVar(&clip, "clip", "", "切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)") 22 | flag.Parse() 23 | } 24 | 25 | func convert(dst, src string) error { 26 | 27 | sf, err := os.Open(src) 28 | if err != nil { 29 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 30 | } 31 | defer sf.Close() 32 | 33 | df, err := os.Create(dst) 34 | if err != nil { 35 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 36 | } 37 | defer df.Close() 38 | 39 | _img, _, err := image.Decode(sf) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | // TODO: _imgを埋め込んだ、imgconv.Image型の値を作る 45 | img := imgconv.Image{_img} 46 | 47 | if clip != "" { 48 | if err := img.Clip(clip); err != nil { 49 | return fmt.Errorf("%s\n", err.Error()) 50 | } 51 | } 52 | 53 | switch strings.ToLower(filepath.Ext(dst)) { 54 | case ".png": 55 | err = png.Encode(df, img) 56 | case ".jpeg", ".jpg": 57 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 58 | } 59 | 60 | if err != nil { 61 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func run() error { 68 | args := flag.Args() 69 | if len(args) < 2 { 70 | return fmt.Errorf("画像ファイルを指定してください。") 71 | } 72 | 73 | return convert(args[1], args[0]) 74 | } 75 | 76 | func main() { 77 | if err := run(); err != nil { 78 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 79 | os.Exit(1) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step7/README.md: -------------------------------------------------------------------------------- 1 | # STEP 7: 画像を縮小/拡大しよう 2 | 3 | STEP 7では、さらにコマンドに機能を追加し、画像の縮小/拡大を行えるようにします。 4 | 拡大するサイズは、`resize`という名前でフラグとしてコマンドライン引数で指定します。 5 | 縮小/拡大を行うには、STEP 6で使用した`image/draw`パッケージの代わりに、`golang.org/x/image/draw`パッケージを使用します。 6 | 7 | ここでは、`golang.org/x/image`パッケージの説明と外部パッケージの`go get`の方法について説明します。 8 | 9 | ## golang.org/x/imageパッケージ 10 | 11 | `golang.org/x/image`パッケージは、標準パッケージの`image`パッケージより機能を増やしたパッケージです。 12 | 特に`draw`パッケージは、スケールなどの機能が追加されています。 13 | 14 | `golang.org/x`以下にあるパッケージは、サブプロジェクトとしてGoチームによって保守されています。 15 | `image`以外にも、`golang.org/x/net`など便利なパッケージがありますので、一度覗いてみると良いでしょう。 16 | 17 | このステップでは、`golang.org/x/image/draw`パッケージを使用しますので、`go get`しておきましょう。 18 | 以下の通り、`go get`を実行すると、`src`と`pkg`以下に`golang.org/x/image/draw`がインストールされている事が分かります。 19 | 20 | ``` 21 | $ go get golang.org/x/image/draw 22 | $ tree pkg 23 | pkg 24 | └── darwin_amd64 25 | └── golang.org 26 | └── x 27 | └── image 28 | ├── draw.a 29 | └── math 30 | └── f64.a 31 | $ ls src/golang.org/x/image/ 32 | AUTHORS LICENSE bmp colornames font testdata vp8l 33 | CONTRIBUTING.md PATENTS cmd draw math tiff webp 34 | CONTRIBUTORS README codereview.cfg example riff vp8 35 | ``` 36 | 37 | ## 実行例 38 | 39 | ``` 40 | $ pwd 41 | /path/to/gohandson/imgconv/ja/solution 42 | $ GOPATH=`pwd` 43 | $ go install step7/cmd/imgconv 44 | $ go install tools/cmd/httpget 45 | $ ./bin/httpget https://raw.githubusercontent.com/tenntenn/gopher-stickers/master/png/hi.png > gopher.png 46 | $ ./bin/imgconv -resize 50%x50% gopher.png gopher2.png 47 | ``` 48 | 49 | ## 目次 50 | 51 | * STEP 1: [go installしてみよう](../../../skeleton/src/step1)([解答例](../step1)) 52 | * STEP 2: [コマンドライン引数を取ろう](../../../skeleton/src/step2)([解答例](../step2)) 53 | * STEP 3: [ファイルを扱おう](../../../skeleton/src/step3)([解答例](../step3)) 54 | * STEP 4: [画像形式を変換しよう](../../../skeleton/src/step4)([解答例](../step4)) 55 | * STEP 5: [`flag`パッケージを使おう](../../../skeleton/src/step5)([解答例](../step5)) 56 | * STEP 6: [画像を切り抜こう](../../../skeleton/src/step6)([解答例](../step6)) 57 | * STEP 7: [画像を縮小/拡大しよう](../../../skeleton/src/step7)([解答例](../step7)) 58 | * STEP 8: [複数のファイルを処理しよう](../../../skeleton/src/step8)([解答例](../step8)) 59 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/step7/cmd/imgconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "image/png" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "step7/imgconv" 14 | ) 15 | 16 | var ( 17 | clip string 18 | resize string 19 | ) 20 | 21 | func init() { 22 | flag.StringVar(&clip, "clip", "", "切り取る画像サイズ(`幅[px|%]x高さ[px|%]`)") 23 | flag.StringVar(&resize, "resize", "", "出力する画像サイズ(`幅[px|%]x高さ[px|%]`)") 24 | flag.Parse() 25 | } 26 | 27 | func convert(dst, src string) error { 28 | 29 | sf, err := os.Open(src) 30 | if err != nil { 31 | return fmt.Errorf("画像ファイルが開けませんでした。%s", src) 32 | } 33 | defer sf.Close() 34 | 35 | df, err := os.Create(dst) 36 | if err != nil { 37 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 38 | } 39 | defer df.Close() 40 | 41 | _img, _, err := image.Decode(sf) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | img := imgconv.Image{_img} 47 | 48 | if clip != "" { 49 | if err := img.Clip(clip); err != nil { 50 | return fmt.Errorf("%s\n", err.Error()) 51 | } 52 | } 53 | 54 | // TODO: resizeが指定されていれば、リサイズを行う。 55 | if resize != "" { 56 | if err := img.Resize(resize); err != nil { 57 | return fmt.Errorf("%s\n", err.Error()) 58 | } 59 | } 60 | 61 | switch strings.ToLower(filepath.Ext(dst)) { 62 | case ".png": 63 | err = png.Encode(df, img) 64 | case ".jpeg", ".jpg": 65 | err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) 66 | } 67 | 68 | if err != nil { 69 | return fmt.Errorf("画像ファイルを書き出せませんでした。%s", dst) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func run() error { 76 | args := flag.Args() 77 | if len(args) < 2 { 78 | return fmt.Errorf("画像ファイルを指定してください。") 79 | } 80 | 81 | return convert(args[1], args[0]) 82 | } 83 | 84 | func main() { 85 | if err := run(); err != nil { 86 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 87 | os.Exit(1) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /imgconv/ja/solution/src/tools/cmd/httpget/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func run() error { 11 | res, err := http.Get(os.Args[1]) 12 | if err != nil { 13 | return err 14 | } 15 | _, err = io.Copy(os.Stdout, res.Body) 16 | return err 17 | } 18 | 19 | func main() { 20 | if err := run(); err != nil { 21 | fmt.Fprintln(os.Stderr, "Error:", err) 22 | os.Exit(1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /slackbot/ja/README.md: -------------------------------------------------------------------------------- 1 | # Slack Botを作ろう 2 | 3 | * [STEP1: Hello Slack Bot](./solution/step1) 4 | * [STEP2: Interactive Message](./solution/step2) 5 | -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/01_create_new_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/01_create_new_app.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/02_create_new_app2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/02_create_new_app2.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/03_basic_infomation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/03_basic_infomation.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/04_add_bot_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/04_add_bot_user.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/05_add_bot_user_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/05_add_bot_user_button.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/06_install_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/06_install_application.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/07_auth_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/07_auth_app.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/08_verify_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/08_verify_token.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/09_oauth_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/09_oauth_token.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/10_event_subscription_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/10_event_subscription_on.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/11_event_subscription_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/11_event_subscription_url.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/img/12_add_event_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step1/img/12_add_event_type.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/src/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.9 3 | module: slackbot 4 | 5 | handlers: 6 | - url: /.* 7 | script: _go_app 8 | secure: always 9 | 10 | env_variables: 11 | SLACK_VERIFY_TOKEN: "Paste your token" 12 | SLACK_BOT_TOKEN: "Paste your token" 13 | -------------------------------------------------------------------------------- /slackbot/ja/solution/step1/src/main.go: -------------------------------------------------------------------------------- 1 | package slackbot 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/nlopes/slack" 11 | "github.com/nlopes/slack/slackevents" 12 | "google.golang.org/appengine" 13 | "google.golang.org/appengine/log" 14 | "google.golang.org/appengine/urlfetch" 15 | ) 16 | 17 | func init() { 18 | http.HandleFunc("/events", eventsHandler) 19 | } 20 | 21 | func eventsHandler(w http.ResponseWriter, r *http.Request) { 22 | ctx := appengine.NewContext(r) 23 | token := os.Getenv("SLACK_BOT_TOKEN") 24 | slack.SetHTTPClient(urlfetch.Client(ctx)) 25 | api := slack.New(token) 26 | 27 | defer r.Body.Close() 28 | var buf bytes.Buffer 29 | buf.ReadFrom(r.Body) 30 | body := buf.String() 31 | 32 | verifytoken := os.Getenv("SLACK_VERIFY_TOKEN") 33 | opt := slackevents.OptionVerifyToken(&slackevents.TokenComparator{verifytoken}) 34 | evt, err := slackevents.ParseEvent(json.RawMessage(body), opt) 35 | if err != nil { 36 | log.Errorf(ctx, "ParseEvent: %v", err) 37 | http.Error(w, err.Error(), http.StatusInternalServerError) 38 | return 39 | } 40 | 41 | if evt.Type == slackevents.URLVerification { 42 | var r *slackevents.ChallengeResponse 43 | err := json.Unmarshal([]byte(body), &r) 44 | if err != nil { 45 | log.Errorf(ctx, "%v", err) 46 | http.Error(w, err.Error(), http.StatusInternalServerError) 47 | return 48 | } 49 | fmt.Fprint(w, r.Challenge) 50 | return 51 | } 52 | 53 | log.Infof(ctx, "Event:%#v", evt) 54 | if evt.Type == slackevents.CallbackEvent { 55 | var postParams slack.PostMessageParameters 56 | switch evt := evt.InnerEvent.Data.(type) { 57 | case *slackevents.AppMentionEvent: 58 | _, _, err := api.PostMessage(evt.Channel, "こんにちは", postParams) 59 | if err != nil { 60 | log.Errorf(ctx, "%v", err) 61 | http.Error(w, err.Error(), http.StatusInternalServerError) 62 | return 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/README.md: -------------------------------------------------------------------------------- 1 | # STEP2: Interactive Message 2 | 3 | ## ソースコードをデプロイする 4 | 5 | STEP1でやった手順と同じ。 6 | 7 | app.yamlの以下の部分をSlack Appの管理画面からコピーして貼り付ける。 8 | 9 | ```yaml 10 | env_variables: 11 | SLACK_VERIFY_TOKEN: "Paste your token" 12 | SLACK_BOT_TOKEN: "Paste your token" 13 | ``` 14 | 15 | ``` 16 | $ cd step2/src 17 | $ goapp deploy --application --version . 18 | ``` 19 | 20 | ## Interactive Componentsの有効化 21 | 22 | 「off」になっているので、「on」にする。 23 | 24 | 25 | 26 | ## Request URLを入れる 27 | 28 | Request URLの部分にデプロイしたアプリケーションのURLを貼り付ける。 29 | 今回増やしたエンドポイントは`interaction`なので、そこにリクエストが来るようにする。 30 | 31 | 例:https://`VERSION`-dot-slackbot-dot-`APP_ID`.appspot.com/interaction 32 | ※ `VERSION`と`APP_ID`はデプロイ時に指定したもの。 33 | 34 | 35 | 36 | ## アクションを作る 37 | 38 | 「Create New Action」をクリックする 39 | 40 | 41 | 42 | 「Details」を入力する。CallbackIDを`select_blood_type`にする。 43 | 44 | 45 | 46 | ## 設定を保存する 47 | 48 | 「Save Changes」をクリックする。 49 | 50 | 51 | 52 | ## 動作を確認する 53 | 54 | Slack Botのいるチャンネルで`@botname 占い`のように話しかける。 55 | 56 | 57 | -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/img/01_interactive_component_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step2/img/01_interactive_component_off.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/img/02_interactive_component_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step2/img/02_interactive_component_on.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/img/03_create_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step2/img/03_create_action.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/img/04_create_action_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step2/img/04_create_action_dialog.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/img/05_save_changes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step2/img/05_save_changes.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/img/06_uranai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenntenn/gohandson/cef9691e4905e38a3ee9935c6874daa0a6a0e7bf/slackbot/ja/solution/step2/img/06_uranai.png -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/src/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1.9 3 | module: slackbot 4 | 5 | handlers: 6 | - url: /.* 7 | script: _go_app 8 | secure: always 9 | 10 | env_variables: 11 | SLACK_VERIFY_TOKEN: "Paste your token" 12 | SLACK_BOT_TOKEN: "Paste your token" 13 | -------------------------------------------------------------------------------- /slackbot/ja/solution/step2/src/main.go: -------------------------------------------------------------------------------- 1 | package slackbot 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/nlopes/slack" 12 | "github.com/nlopes/slack/slackevents" 13 | "google.golang.org/appengine" 14 | "google.golang.org/appengine/log" 15 | "google.golang.org/appengine/urlfetch" 16 | ) 17 | 18 | func init() { 19 | http.HandleFunc("/events", eventsHandler) 20 | http.HandleFunc("/interaction", interactionHandler) 21 | } 22 | 23 | func eventsHandler(w http.ResponseWriter, r *http.Request) { 24 | ctx := appengine.NewContext(r) 25 | token := os.Getenv("SLACK_BOT_TOKEN") 26 | slack.SetHTTPClient(urlfetch.Client(ctx)) 27 | api := slack.New(token) 28 | 29 | defer r.Body.Close() 30 | var buf bytes.Buffer 31 | buf.ReadFrom(r.Body) 32 | body := buf.String() 33 | 34 | verifytoken := os.Getenv("SLACK_VERIFY_TOKEN") 35 | opt := slackevents.OptionVerifyToken(&slackevents.TokenComparator{verifytoken}) 36 | evt, err := slackevents.ParseEvent(json.RawMessage(body), opt) 37 | if err != nil { 38 | log.Errorf(ctx, "ParseEvent: %v", err) 39 | http.Error(w, err.Error(), http.StatusInternalServerError) 40 | return 41 | } 42 | 43 | if evt.Type == slackevents.URLVerification { 44 | var r *slackevents.ChallengeResponse 45 | err := json.Unmarshal([]byte(body), &r) 46 | if err != nil { 47 | log.Errorf(ctx, "%v", err) 48 | http.Error(w, err.Error(), http.StatusInternalServerError) 49 | return 50 | } 51 | fmt.Fprint(w, r.Challenge) 52 | return 53 | } 54 | 55 | log.Infof(ctx, "Event:%#v", evt) 56 | if evt.Type == slackevents.CallbackEvent { 57 | switch evt := evt.InnerEvent.Data.(type) { 58 | case *slackevents.AppMentionEvent: 59 | switch { 60 | case strings.Contains(evt.Text, "占い"): 61 | if err := selectBloodType(api, evt); err != nil { 62 | log.Errorf(ctx, "%v", err) 63 | http.Error(w, err.Error(), http.StatusInternalServerError) 64 | return 65 | } 66 | } 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------