├── 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 | {{- /* TODO: 品目を埋め込む */ -}} | {{.Price}}円 |
33 | {{- end}}
34 |
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 | {{.Category}} | {{.Price}}円 |
33 | {{- end}}
34 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------