20190816のGoに関する記事は11件です。

golangで始めるTDD

【この記事の目的】

golang x TDDは「あ、こんな風にやればええんやな」という感覚を掴んでもらう為の記事です。

【対象とする人】

  • golangこれから学ぶぞ
  • golangちょっとだけしってる
  • テストコードってどう書けばいいんだろう
  • TDDやったことない

という人を対象としてます

【やってみよう】

< golang基礎編 >

□ Hello World

言語学習の事始めと言ったらやっぱり「Hello World」ですよね!
golangでも例にもれず、まずはHello Worldでどんな感じなのかを確認することができます。

ご存知の方も多いとお思いますが、環境準備をしなくてもPlaygroundで試すことができるので
簡単な挙動の確認くらいだったら検証できます。
早速Playgroundに行って、「Hello World」を実行してみましょう!

hello.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

□ 環境準備

まずはgoのコードを入れましょう。

↓の公式サイトにアクセスをしてβ版ではない最新のバージョンをダウンロードしてセットアップします。
https://golang.org/dl/

golangをセットアップし終わったら、次は「エディタ」を準備します。
個人的なオススメは「VS Code」です。
https://code.visualstudio.com/

Visual_Studio_Code_-_Code_Editing__Redefined.jpg

軽量かつ様々なプラグインがあるので、デバッグがしやすく扱いやすいです。
今回はこの「VS Code」と「プラグイン」を用いることを前提で話を進めていきます。

プラグインは↓の2つをインストールします。

プラグイン1: Go
用途:vs codeのgolangサポート

プラグイン2: Go Text Explorer
用途:テストに利用

□ 基礎構文

環境も準備できたので、あとは実際に手を動かすばかりではありますが、その前にgolangの基本構文を理解しましょう。
公式に動かして理解する素晴らしい資料があるので、ここでは割愛します。

https://go-tour-jp.appspot.com/welcome/1

□ golangでのテストコード

ここでは詳しいgo testの仕様などは説明しませんが、
とりあえずテストコードが書けるように↓の3つだけざっくりと説明したいと思います。

  • ファイルの名前は「xxx_test.go」で対応させtestingパッケージを利用する
  • 標準関数で評価は十分できる
  • Table Driven Testが推奨されている

※ go testの詳しい概要については↓のような素晴らしい記事を参考にしてください。

https://golang.org/pkg/testing/
https://budougumi0617.github.io/2018/08/19/go-testing2018/
https://swet.dena.com/entry/2018/01/16/211035

ファイルの名前は「xxx_test.go」で対応させtestingパッケージを利用する

大原則となりますが、テストコードを記述するファイルは xxx_test.go という(xxxはテスト対象のコードがあるファイル名が一般的)名前をつけなくてはなりません。
この名前にすることで「go test」を実行する時にテストコードとして認識してくれます。
xxxはテスト対象のコードがあるファイル名と関係ないものでも動きますが、同じ名前をつけるのが一般的なので敢えて変える必要はないかと思います。

buildOK.sh
$ ls
main.go     main_test.go

$ go test
PASS
ok      _/home/debug/test   0.007s

$ go build
$ ls
main.go     main_test.go    test
buildNG.sh
$ ls
aaa.go  main.go

$ go test
?       _/home/debug/test   [no test files]

$ go build
$ ls
aaa.go  main.go test

また、テストを実行するのに「 testing 」というパッケージを利用します。
テストを実行して色々確認したりするのに便利なメソッドがまとまってますので、どんなことができるかはReferenceを確認しておくとよいでしょう。
ちなみにtestingパッケージを使う場合は他にも注意点があります。
テストとして実行するメソッドは必ず TestXxx という命名規則で書かなくてはなりません。
なので記述するとしたらこんな感じになります。

※ Test以降の文字列もキャメルケースである必要があります

example.go
package main

import (
    "testing"
)

func TestXxx(t *testing.T)  {
    ...
}

標準関数で評価は十分できる

javaなどではassert()を利用して、期待との評価を行うのが一般的かと思いますが
golangでは以下のように評価するケースが多いです。
assert()を利用しなくても十分評価できるので、goに入ってはgoに従えということでこの形式でテストは評価します。

https://play.golang.org/p/jUjXNAsmkxD

plus_test.go
package main

import (
    "testing"
)

func Plus(x, y int) int {
    return x + y
}

func TestPlus(t *testing.T) {
    x, y := 1, 1
    expectation := 3

    res := Plus(x, y)
    if res != expectation {
        t.Errorf("Not equal value %v", res)
    }
}

↑を実行するとこんな結果が得られます。
どこで失敗したかなどわかりやすいですね。

=== RUN   TestPlus
--- FAIL: TestPlus (0.00s)
    prog.go:17: Not equal value 2
FAIL

Table Driven Testが推奨されている

すごくざっくりいうと、「 テストケースをJSONっぽい見た目のテーブルで書くとわかりやすくない? 」という感じの書き方です。
ちゃんとした説明や詳しい内容は参考資料や公式を見てください。
ここではざっくりこんな感じの書き方をするんだなーというところだけ掴んでくれればいいので、実例を載せます。

https://play.golang.org/p/sc7HkbaxA4D

plus_test.go
package main

import (
    "testing"
)

func Plus(x, y int) int {
    return x + y
}

func TestPlus(t *testing.T) {
    data := map[string]struct {
        X           int
        Y           int
        Expectation int
    }{
        "正の整数+正の整数": {
            X:           1,
            Y:           1,
            Expectation: 2,
        },
        "正の整数+負の整数": {
            X:           1,
            Y:           -1,
            Expectation: 0,
        },
    }

    for name, d := range data {
        t.Run(name, func(t *testing.T) {
            res := Plus(d.X, d.Y)
            if res != d.Expectation {
                t.Errorf("Not equal value %v", res)
            }
        })
    }

}

↑を実行するとこんな結果が得られます。

=== RUN   TestPlus
=== RUN   TestPlus/正の整数+正の整数
=== RUN   TestPlus/正の整数+負の整数
--- PASS: TestPlus (0.00s)
    --- PASS: TestPlus/正の整数+正の整数 (0.00s)
    --- PASS: TestPlus/正の整数+負の整数 (0.00s)
PASS

さらっとサブテストの実行までやってしまってますが、この書き方をするとどのケースでどういうデータがセットされているかがひと目でわかりやすいので複数ケースを行う場合はおすすめです。

< TDD基礎編 >

□ TDDの基本

さて、ようやくこれでTDDに入っていきます。
TDDも色々わかりやすい説明をしている記事やt_wadaさんというTDDの現人神のような方がいらっしゃるので、詳しくはそちらを見てください。

なので、ここではほんとに入り口だけを紹介します。

TDDは「 まずは作ろう! 」ではなく「 これはどんなことができる子かな? 」という完成品が叶えるべき要件をベースにしてテストケースから考えていく開発手法です。
なので、個人的にはこの「 テストケースを作る 」という段階が重要になってくると考えています。

そして、テストケースが作り終わったら「 3つのサイクルを回して 」開発していきます。
- Red(失敗させて)
- Green(エラーが出ないようにして)
- リファクタリング(正しい形に直していく)

このサイクルを回していくと要件を満たしたことが担保された動くコードが出来上がっていきます。

< golang x TDD >

基本的なやり方は以前書いたエアペアプロにある通りになります。
https://qiita.com/i-dach/items/20b3460f736fb93c53c4#%E3%82%A8%E3%82%A2%E3%83%9A%E3%82%A2%E3%83%97%E3%83%AD%E3%81%AE%E3%82%84%E3%82%8A%E3%81%8B%E3%81%9F

TDDのサイクル通りに
- 「テストケース」を作り、
- 「golangで空のテストを実行してエラーにさせて」
- 「3つのサイクルをぐるぐると回していく」

をひたすら行うだけです。

【まとめ】

golangでTDDをやるとしても特別なことは特にありません。
golangでのテストの書き方とTDDのススメ方さえ把握してれば怖くないので、是非トライしてみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golangとjavascriptで日めくりカレンダー的なのを作った。

やりたかったこと
1・1か月ごとのカレンダーを表示
2・スケジュールの入力ができる(todoアプリ的な)
3・タスクがすべて完了したらマスの色を変える。
4・一か月前、一か月後まではボタンで切り替えて表示

ここまでを約1週間で行いたかった。

実際は?
現在の年月日、曜日を表示、時刻はデジタル時計のように、リアルタイムで更新するようにしました。
日めくりカレンダーのようにしました。

使用言語
golang,javascript,html,css

フォルダ構成

calender
      |_resources 
      |         |_index.html
      |         |
      |         |_styles.css
      |_hello.go


苦戦したところ
goで定義したもの(現在日時など)をどうやってHTMLに埋め込むか
CSSファイルを読み取ってくれなかった。
この二つが特に悩まされた。後はgoにこだわらないでほかの言語なら行けたんじゃね?と思う
(Goを必ず使わなくてはいけなかったのでそこのさじ加減が掴めませんでした)

どうやったか

hello.go
package main 


 import ( 
    "fmt" 
    "html/template" 
    "net/http" 
    "time" 
 ) 


 func main() { 
    fmt.Println("The Server runs with http://localhost:3000/") 
    http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) 
    http.HandleFunc("/", Handler) 
    http.ListenAndServe(":3000", nil) 
 } 


 type Time struct { 
    Year    int 
    Month   string 
    Day1    int 
    Weekday string 
    Hour    int 
    Minute  int 
    Second  int 
 } 


 func Handler(w http.ResponseWriter, r *http.Request) { 


    t := Time{ 
        Year:    time.Now().Year(), 
        Month:   time.Now().Month().String(), 
        Day1:    time.Now().Day(), 
        Hour:    time.Now().Hour(), 
        Minute:  time.Now().Minute(), 
        Second:  time.Now().Second(), 
        Weekday: time.Now().Weekday().String(), 
    } 


    tmpl := template.Must(template.ParseFiles("./resources/index.html")) 
    tmpl.Execute(w, t) 


 } 


苦戦した箇所である、html、cssを読み込む処理は、


tmpl := template.Must(template.ParseFiles("./resources/index.html"))
tmpl.Execute(w, t)

読み込みたいファイルのパスを指定してあげて、
上で定義した変数を引数にして一緒に実行するようにしました。
これでhtml上にgoを埋め込むことに成功。

cssの読み込みは、

http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/"))))

この一文でresourcesフォルダ以下の静的ファイルを探してきてくれ、読み込みができるようになりました。

index.html
<!DOCTYPE html> 
 <html lang="en"> 
 <head> 
     <meta charset="UTF-8"> 
    <title>テスト</title> 
     <link rel="stylesheet" type="text/css" href="../resources/styles.css"> 
      <script> 
         function digitalClock(){ 
             // 現在日時を取得 
             var now = new Date(); 
             // 「時」を取得 
             var hour = now.getHours(); 
             // 「分」を取得 
             var minute = now.getMinutes(); 
             // 「秒」を取得 
             var second = now.getSeconds(); 

             // 0埋めで2桁表示にする 例)08:45:09 
             if(hour < 10) hour = "0" + hour; 
             if(minute < 10) minute = "0" + minute; 
             if(second < 10) second = "0" + second; 

            // 現在時刻を表示する 
             var elem = document.getElementById("clock"); 
             elem.innerHTML = hour + ":" + minute + ":" + second; 

             // 500ミリ秒後に再帰呼び出し 
             setTimeout(digitalClock, 500); 
         } 

         window.onload = function(){ 
             digitalClock(); 
         } 
     </script> 
 </head> 
 <body> 
 <div class="calenderbox"> 
    <pre><h1 class="month_now"><span>{{.Year }}</span></h1></pre> 
   <div id ="box1">  
    <div id="box2">  
         <p class="Date">{{.Month }}</p> 
      <p class="Date">{{.Day1 }}</p> 
     <p class="Date">{{.Weekday }}</p> 
    </div> 
   <div id="box3">  
        <p id="clock"></p> 
    </div> 
   </div> 
 </div> 
 </body> 

hello.goで処理したので、goの変数を以下のような形で受け取ることができます。

{{.変数名 }}

また、内で時刻を取得し、デジタル時計の見た目に設計したものをhtmlで返しています。

cssファイルは特に変わったことをしていないので割愛させて頂きます。

まとめ
触ったことがないものばかりだったのでここまでできたのも奇跡に等しいです。
ただ、まだ追加したい機能やより良い記述があるはずなのでしばらくこれを継続して取り組みます。
Goはとりあえずチュートリアルを完走します。
あとはjavascriptはじめてまともに使ってマスターしたらかなり便利だと感じました。
こちらもしっかり取り組んでいきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mongo-go-driverがうまく入らなかったので、手動でいれた。

概要

mongo-go-driverをDockerfileなり、自動で入れようとしたら上手くいかず、手動でいれるとうまくいったので、その共有。
誰かが同じ轍を踏まないように。

mongo-go-driver

Go Runtimeコンテナ

base image => golang:1.12.8 ※2019/08/15時点でのstable version

アタッチして下記を実行

パッケージ管理ツール: depのインストール

$ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

Projectの初期化

$ dep init ※project rootで

Mongo Driverのインストール

$ dep ensure --add go.mongodb.org/mongo-driver/mongo go.mongodb.org/mongo-driver/bson go.mongodb.org/mongo-driver/mongo/options

※goファイルでimportしていない場合、「これはまだ仮のインストールでっせ」という警告が出るかもしれませんが、
importすれば、消えるので無視でOK

あとはdocker commitなりなんなり。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のバージョンを切り替え方法 macOS HomeBrew

モチベーション

go言語でビルドしていて、バージョンがマッチしていないとエラーになった。macOSのコンパイル環境で、go言語のバージョンを簡単に変更する方法は無いだろうか?

# math/bits
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# errors
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# runtime/internal/sys
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# unicode/utf8
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# internal/cpu
compile: version "go1.12.8" does not match go tool version "go1.12.9"

HomeBrew を使ってバージョンを確認

go言語のパッケージの詳細を見ると、どうも、切り替えられそうだ...

$ brew info go
go: stable 1.12.9 (bottled), HEAD
Open source programming language to build simple/reliable/efficient software
https://golang.org
/usr/local/Cellar/go/1.12.8 (9,816 files, 452.7MB)
  Poured from bottle on 2019-08-15 at 22:49:03
/usr/local/Cellar/go/1.12.9 (9,819 files, 452.8MB) *
  Poured from bottle on 2019-08-16 at 16:23:19
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/go.rb
==> Requirements
Required: macOS >= 10.10 ✔
==> Options
--HEAD
    Install HEAD version
==> Analytics
install: 84,842 (30 days), 275,543 (90 days), 1,163,983 (365 days)
install_on_request: 65,340 (30 days), 211,527 (90 days), 863,599 (365 days)
build_error: 0 (30 days)

brew switch で切り替え

調べたところ、brew switch という機能があるらしい。
そこで、先ほど、リストされていたバージョンを指定してみる。

$ brew switch go 1.12.8
Cleaning /usr/local/Cellar/go/1.12.8
Cleaning /usr/local/Cellar/go/1.12.9
3 links created for /usr/local/Cellar/go/1.12.8

どうやら、切り替わったみたいだ

$ brew info go
go: stable 1.12.9 (bottled), HEAD
Open source programming language to build simple/reliable/efficient software
https://golang.org
/usr/local/Cellar/go/1.12.8 (9,816 files, 452.7MB) *
  Poured from bottle on 2019-08-15 at 22:49:03
/usr/local/Cellar/go/1.12.9 (9,819 files, 452.8MB)
  Poured from bottle on 2019-08-16 at 16:23:19
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/go.rb
==> Requirements
Required: macOS >= 10.10 ✔
==> Options
--HEAD
    Install HEAD version
==> Analytics
install: 84,842 (30 days), 275,543 (90 days), 1,163,983 (365 days)
install_on_request: 65,340 (30 days), 211,527 (90 days), 863,599 (365 days)
build_error: 0 (30 days)

確認結果

確かに、go言語のバージョンが切り替わった

$ go version
go version go1.12.8 darwin/amd64
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のバージョンを切り替える方法 macOS HomeBrew

モチベーション

go言語でビルドしていて、バージョンがマッチしていないとエラーになった。macOSのコンパイル環境で、go言語のバージョンを簡単に変更する方法は無いだろうか?

# math/bits
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# errors
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# runtime/internal/sys
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# unicode/utf8
compile: version "go1.12.8" does not match go tool version "go1.12.9"
# internal/cpu
compile: version "go1.12.8" does not match go tool version "go1.12.9"

HomeBrew を使ってバージョンを確認

go言語のパッケージの詳細を見ると、どうも、切り替えられそうだ...

$ brew info go
go: stable 1.12.9 (bottled), HEAD
Open source programming language to build simple/reliable/efficient software
https://golang.org
/usr/local/Cellar/go/1.12.8 (9,816 files, 452.7MB)
  Poured from bottle on 2019-08-15 at 22:49:03
/usr/local/Cellar/go/1.12.9 (9,819 files, 452.8MB) *
  Poured from bottle on 2019-08-16 at 16:23:19
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/go.rb
==> Requirements
Required: macOS >= 10.10 ✔
==> Options
--HEAD
    Install HEAD version
==> Analytics
install: 84,842 (30 days), 275,543 (90 days), 1,163,983 (365 days)
install_on_request: 65,340 (30 days), 211,527 (90 days), 863,599 (365 days)
build_error: 0 (30 days)

brew switch で切り替え

調べたところ、brew switch という機能があるらしい。
そこで、先ほど、リストされていたバージョンを指定してみる。

$ brew switch go 1.12.8
Cleaning /usr/local/Cellar/go/1.12.8
Cleaning /usr/local/Cellar/go/1.12.9
3 links created for /usr/local/Cellar/go/1.12.8

どうやら、切り替わったみたいだ

$ brew info go
go: stable 1.12.9 (bottled), HEAD
Open source programming language to build simple/reliable/efficient software
https://golang.org
/usr/local/Cellar/go/1.12.8 (9,816 files, 452.7MB) *
  Poured from bottle on 2019-08-15 at 22:49:03
/usr/local/Cellar/go/1.12.9 (9,819 files, 452.8MB)
  Poured from bottle on 2019-08-16 at 16:23:19
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/go.rb
==> Requirements
Required: macOS >= 10.10 ✔
==> Options
--HEAD
    Install HEAD version
==> Analytics
install: 84,842 (30 days), 275,543 (90 days), 1,163,983 (365 days)
install_on_request: 65,340 (30 days), 211,527 (90 days), 863,599 (365 days)
build_error: 0 (30 days)

確認結果

確かに、go言語のバージョンが切り替わった

$ go version
go version go1.12.8 darwin/amd64
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows環境 Goのrealizeでハマった話

いきなりDocker化すると、動きがわからず、
ハマる可能性大なので一回ローカルで試すのが吉と思う

コンパイルの出力先ファイル名になんでもいいから拡張子つけないとダメ。
windowsなので、exeをつけた。

  commands:
    install:
      status: true
      method: go build -o app.exe
    run:
      status: true
      method: ./app.exe

そうしないと。このエラー

$ realize start
[15:10:53][GO_DEMO2] : Watching 2 file/s 0 folder/s
[15:10:53][GO_DEMO2] : Install started
[15:10:57][GO_DEMO2] : Install completed in 3.978 s
[15:10:57][GO_DEMO2] : Running..
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x8 pc=0x4d006d]

goroutine 52 [running]:
os.(*Process).signal(0x0, 0xa58520, 0xd9a4c0, 0xc000214020, 0xc00007af70)
        c:/go/src/os/exec_windows.go:59 +0x2d
os.(*Process).Signal(...)
        c:/go/src/os/exec.go:131
github.com/oxequa/realize/realize.(*Project).run.func1(0xc00007b6a8)
        C:/Users/N10W78/go/src/github.com/oxequa/realize/realize/projects.go:581 +0x64
github.com/oxequa/realize/realize.(*Project).run(0xc00005e300, 0xc000019650, 0x5, 0xc00020a180, 0xc000058420, 0xa53d40, 0xc0002020c0)
        C:/Users/N10W78/go/src/github.com/oxequa/realize/realize/projects.go:646 +0xc62
github.com/oxequa/realize/realize.(*Project).Reload.func3(0xc00005e300, 0xc00020a180, 0xc000058420)
        C:/Users/N10W78/go/src/github.com/oxequa/realize/realize/projects.go:262 +0x14e
created by github.com/oxequa/realize/realize.(*Project).Reload
        C:/Users/N10W78/go/src/github.com/oxequa/realize/realize/projects.go:260 +0x29e

また、Goでwebサーバしている場合、プロセスを殺してやらないと、実行ファイルが上書きされず
ホットリロードされなかった。
macやlinuxならいけるのだろうか。。。今後調査

    scripts:
      - type: before
        command: taskkill /F /im app.exe

realize.yml サンプル

settings:
  legacy:
    force: true
    interval: 0s
schema:
- name: realize
  path: .
  args:
  - main.go
  commands:
    install:
      status: true
      method: go build -o app.exe
    run:
      status: true
      method: ./app.exe
  watcher:
    extensions:
    - go
    paths:
    - /
    scripts:
      - type: before
        command: taskkill /F /im app.exe
    ignored_paths:
    - .git
    - .realize
    - vendor
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】フィボナッチ数列の値を積み上げ式で計算すると早い話

leetcodeでアルゴリズムを勉強し始めたので初投稿です。

問題

n番目のフィボナッチ数列の値を算出する

直感的に解く

fibonacci.go
func Fibo(n int) int {
    if n < 2 {
        return n
    }
    return Fibo(n-2) + Fibo(n-1)
}

n=4だったら、
Fibo(4) = Fibo(2) + Fibo(3)
Fibo(4) = Fibo(0) + Fibo(1) + Fibo(1) + Fibo(2)
Fibo(4) = 0 + 1 + 1 + Fibo(0) + Fibo(1)
Fibo(4) = 0 + 1 + 1 + 0 + 1 = 3
って感じで計算が進む。計算が重複してるので遅い。

積み上げ式

fibonacci.go
func Fibo2(n int) int {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        x, y = y, x+y
    }
    return x
}

n=4なら、
x,y = 1,1
x,y = 1,2
x,y = 2,3
x,y = 3,5
で 3が返される。計算重複しないので早い。

どのくらい早いのか

benchmarkで計測。
n=0..40まで計算させて、時間を比較してみた。
テストはてきとーにこんな感じで書いてみる

fibonacci_test.go
func BenchmarkFibo(b *testing.B) {
    for i := 0; i < 40; i++ {
        Fibo(i)
    }
}

func BenchmarkFibo2(b *testing.B) {
    for i := 0; i < 40; i++ {
        Fibo2(i)
    }
}

結果は
Fibo(再帰のやつ) => 1.356s
Fibo2(積み上げ式) => 0.008s

早すぎて草。真面目にアルゴリズム勉強しようと思った。
行列を使うともっと早くなるらしい。
もっと早い方法知っていたら教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】フィボナッチ数列をメモ化や積み上げ式で解く

leetcodeでアルゴリズムを勉強し始めたので初投稿です。

問題

n番目のフィボナッチ数列の値を算出する

直感的に解く

fibonacci.go
func Fibo(n int) int {
    if n < 2 {
        return n
    }
    return Fibo(n-2) + Fibo(n-1)
}

n=4だったら、
Fibo(4) = Fibo(2) + Fibo(3)
Fibo(4) = Fibo(0) + Fibo(1) + Fibo(1) + Fibo(2)
Fibo(4) = 0 + 1 + 1 + Fibo(0) + Fibo(1)
Fibo(4) = 0 + 1 + 1 + 0 + 1 = 3
って感じで計算が進む。計算が重複してるので遅い。

積み上げ式

fibonacci.go
func Fibo2(n int) int {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        x, y = y, x+y
    }
    return x
}

n=4なら、
x,y = 1,1
x,y = 1,2
x,y = 2,3
x,y = 3,5
で 3が返される。計算重複しないので早い。

メモ化で解く

fibonacci.go
func Memorize(n int, memo map[int]int) int {
    if n < 2 {
        return n
    }
    if _, ok := memo[n]; !ok {
        memo[n] = Memorize(n-2, memo) + Memorize(n-1, memo)
    }
    return memo[n]
}

マップに保存して、すでに計算してる場合はマップから取り出して使うようにする。

どのくらい早いのか

benchmarkで計測。
n=0..40まで計算させて、時間を比較してみた。
テストはてきとーにこんな感じで書いてみる

fibonacci_test.go
func BenchmarkFibo(b *testing.B) {
    for i := 0; i < 40; i++ {
        Fibo(i)
    }
}

func BenchmarkFibo2(b *testing.B) {
    for i := 0; i < 40; i++ {
        Fibo2(i)
    }
}

func BenchmarkFibo3(b *testing.B) {
    memo := make(map[int]int)
    for i := 0; i < 40; i++ {
        Memorize(i, memo)
    }
}

結果は
Fibo(ふつうのやつ) => 1.356s
Fibo2(積み上げ式) => 0.008s
Fibo3(メモ化) => 0.008s

早すぎて草。真面目にアルゴリズム勉強しようと思った。
行列を使うともっと早くなるらしい。
もっと早い方法知っていたら教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超訳Effective Goその1

重要だからEffective Go読めよ。
でもダラダラ書いてあるから超訳してみた。

とりあえずControl structures(制御構造)まで。

イントロダクション

Goで実装するなら他の言語のことは一旦忘れろ。既存コードはそのまま移植しようとしても上手くいかない。
Goに入ってはGoに従え

サンプル

Goのパッケージのソースは良サンプルだからそのまま手本にしろ。

フォーマッティング

gofmtしろ

コメント

C++スタイルだ。/* comment */ // comment
トップレベルの宣言の直前行に書いたコメントはそのままgodocでドキュメントになるぞ。
最初の行は宣言名で始まる1行サマリーを書くんだ。
英語のプレーンテキストで書くのがベストだ。余計な書式タグとかはいらないぞ。

名前

Go命名規則は重要な意味がある(例えば頭大文字でパッケージ外に公開)から手間を惜しむなよ。
ちなみに名前は短いほうがいい。超絶長い名前付けるくらいならドキュメントを書くんだ。

パッケージ名

パッケージ名はそのソースファイルのディレクトリ名だ。
名前は小文字の1単語にしろ。
万が一インポートするパッケージ名が衝突しても使うときに選択可能だし、実際混乱することなんて稀だ。

src/encoding/base64ににあるソースのパッケージ名はbase64

import (
  "encoding/base64"
  "fmt"
)

func main() {
    str := base64.StdEncoding.EncodeToString([]byte("data"))
    fmt.Println(str)
}

ってな感じで使うぞ。

ゲッター

Goにゲッター、セッター的な規約はないが実装しても構わない。
ゲッターはエクスポートされていないownerフィールドに対して大文字で始まるOwner()って書いてエクスポートされたフィールドっぽくアクセスできるようにするんだ。
セッターのほうはSetOwner()でいいぞ。

owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}

インターフェース名

one-method interfaces(メソッドが1個しないインターフェース)は、メソッド名に接尾辞「er」つけてインターフェース名にするんだ。
ReaderWriterFormatterCloseNotifierこういうやつ。

ReadWriteCloseFlushStringみたいなメソッドは上記のような既知のone-method interfaceがあるから、そのインターフェースを使えるように命名することも大事だ。例えば文字列として出力するメソッド名はtoString()じゃなくてString()にしてStringerとして扱えるようにするんだ。
既知のインターフェースと別の用途で使うなら被らない別の名前にしてくれ。

大文字混合

Goではスネークケースsnake_casing的なやつじゃなくて、いわゆるキャメルケースcamelCasingとかパスカルケースPascalCasingってやつを使うんだ。

セミコロン

いらん。

正確には改行直前のトークン後にコンパイラが自動的にセミコロンを入れているんだ。
だから、{の前とかに改行入れると不要なセミコロン扱いでコンパイルエラーになるぞ。

if i < f()  // wrong! ;
{           // wrong!
    g()
}

制御構造

Cに似てるがいろいろ違うぞ。

  • dowhileはない
  • ifswitchはいろいろ初期化できる
  • breakcontinueはラベルを使って抜ける場所を決められる
  • selectっていうのが増えた

if

カッコつけんじゃない

if x > 0 {
    return y
}

もちろんreturnbreakで制御ブロックから抜けられる。

forみたいにローカル変数として初期化できる

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

breakcontinuegotoreturnで終了するんだったらelseは省略して次の処理を書いていい。

f, err := os.Open(name) // err宣言
if err != nil {
    return err
}
d, err := f.Stat()  // d宣言、err再割当て(代入)
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

再宣言と再割り当て

余談だが、前項の例でerrが2回宣言されているように見えるが問題ない。2回目はスコープそのままに普通の代入だ。
:=で複数宣言するときは、新しく宣言する変数が最低1つあればいい。

for

Goのfor ループはCと似て非なるものだ。Cのwhileとかもforに統合されていて、3つの形式があるぞ。

// ふつうのCっぽいfor
for 初期化; 条件; ループ後 { }

// whileっぽいやつ 
for 条件 { }

// for(;;)っぽいやつ 無条件ループ
for { }

インデックス変数はループ初期化で簡単に宣言できるぞ。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

arrayslicestringmap、またはchanから読取る場合はrangeを使ってループするぞ。

for key, value := range oldMap {
    newMap[key] = value
}

rangeでキーだけが必要なら代入する変数1個だけにしたらいいぞ。

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

値の方だけ必要なら、1番目の変数を_(blank identifier)にしてキーを無視すればいい。
_(blank identifier)はよく使うので後で説明する。

sum := 0
for _, value := range array {
    sum += value
}

文字列の場合はrange使うといろいろ(UTF-8を解析して個々のUnicodeコードポイントに分割、誤ったエンコードはU+FFFDに置換)やってruneを生成してくれるぞ。
(runeの詳細は言語仕様をみてくれ)

for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
prints

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

forで複数の式は使えないから、複数の変数を実行する場合は1つの式で複数割当るんだ。

// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

switch

switchの式はCのと違って定数か整数じゃなくてもいいぞ。
caseは上から順にswitchの式と一致するまで評価されるが、switchに式が無いときは上から順にcasetrueになるまで評価される。
これはif-else-if-elseチェーンをswitchで書けるってことだ。

func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

複数caseをフォールスルーはしないんだけど、1つのcaseに複数の条件を書けるぞ。

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

breakステートメントを使ってswitchを抜けることができるけど、スイッチじゃなくて外のforループから抜け出したいときは、forループにラベル付けをしておいて、そのラベルをbreakしてやればいい。
もちろんcontinueステートメントもラベルが使えるぞ。

Loop:
    for n := 0; n < len(src); n += size {
        switch {
        case src[n] < sizeOne:
            if validateOnly {
                break // switchを抜ける
            }
            size = 1
            update(src[n])

        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                err = errShortInput
                break Loop // forを抜ける
            }
            if validateOnly {
                break // switchを抜ける
            }
            size = 2
            update(src[n] + src[n+1]<<shift)
        }
    }

最後に2つのswitchステートメントを使用するbytesliceの比較ルーチンを書いておく。

// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

type switch

switchをつかうことでinterface{}の動的型を検出できるぞ。
switchの式で、type asertion構文を使って変数を宣言すると、caseでその型が評価できる**んだ。
このとき宣言する変数名は、元のinterface{}型の変数と同じ名前にするといい。その名前の変数が評価された型で使えるぞ。

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)     // %T prints whatever type t has
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Calendar API を使って、サンプルをもとに重複イベントの削除アプリを実装してみる

カレンダーの同期アプリや連携サービスを試していた関係上、イベントが100個ちかく量産されてしまっていました…
あまりにもうっとおしいので、重複したイベントを削除するアプリを作ろうと思い至りました。

その過程でサンプルをもとにカスタマイズする箇所も結構あったりして、どうせなのでちゃんとまとめておこうと思います。

まずは、サンプルを動かす

https://developers.google.com/calendar/quickstart/go に動作するサンプルがあります。
まずは、これをもとにして、自分のカレンダーのイベントを一覧表示するところまでやってみます。

Step1 ~APIの有効化~

なにはともあれ、Google のサービスにプログラムからアクセスするための情報を取得します。
Step1 でその処理が簡単に行なえます。

本来であれば Google Developer Console で有効化するところを、簡略化してくれているようです。

image.png

「ENABLE THE GOOGLE CALENDAR API」ボタンを押します。

image.png

これで有効化されました。

ここで、「DOWNLOAD CLIENT CONFIGURATION」ボタンを押して、credentials.json という名前でプロジェクトディレクトリーに配置します。
ファイル名は複数形です。この名前のファイルを読み取るようなプログラムになっていますので、間違いのないように。

Step2 ~go get~

プロジェクトディレクトリーで、必要なパッケージを取得します。

image.png

過去に同パッケージを取得している場合でも、Go のバージョンを上げたり、パッケージの更新をしていないと、「undefined: proto.ProtoPackageIsVersion3」というエラーが出たりするので、ちゃんと実行しておきましょう。

go get -u google.golang.org/api/calendar/v3
go get -u golang.org/x/oauth2/google

Step3 ~サンプルをコピペ~

変なアレンジはせず、まずは動かすことを優先します。

image.png

サンプルのページにある内容を quickstart.go として保存します。

この時点で、プロジェクトディレクトリーには、credentials.json と quickstart.go があるはずです。

Step4 ~動かす~

image.png

go run quickstart.go

最初にコンソールに URL が出力されるので、それをブラウザのアドレスバーにコピペして、アプリのアクセスを承認します。

承認後、ブラウザにアクセスコードが表示されるので、今度はコンソールに貼り付けます

すると、実行した日付以降の10個までのイベントが列挙されます。

その後

プロジェクトディレクトリーを見てみてください。
token.json というファイルが作成されているはずです。

これは、承認済みアプリに対して Google Calendar API にアクセスするための承認情報を記録したものです。
これがあれば、次回以降はアクセス承認をせずに API を実行できるようになります。

サンプルを理解する

quickstart.go を参照して、取得可能なイベントの属性と、処理の流れを確認します。
(実際にソースコードを片手に読んでもらえると理解が用意になるかもしれません)

承認の処理は複雑なので、その部分をカスタマイズするまでお預けです。(token.json がある現状、あまり触れなくても不便はありませんし)

イベント取得の基本形

まずは、アプリの骨子であるイベント取得の流れを理解します。

イベント取得の基本形は以下のようになります。
main() の中の、イベント取得の部分に注目しています。

  1. なにはともあれ、calendar.New*calendar.Serviceを取得する。
  2. イベントのリストは、(*calendar.Service).List().(メソッドチェーン).Do()で取得する。
  3. events.Items(型 []*calendar.Event)で取得したイベントの情報を得る。

イベントに対してできることは、API ドキュメントの https://godoc.org/google.golang.org/api/calendar/v3#EventsService を参照すると、大体どんなものがあるかわかると思います。
いずれの操作でも、Delete()List() の後に条件をメソッドチェーンの形で指定し、最後に Do() を呼び出すと、その操作が実行できるようです。

また、List() に渡している "primary" という文字列ですが、これは承認したアカウントのデフォルトのカレンダーを意味する ID のようです。
複数のカレンダーを管理している場合など、別のカレンダーを指定したい場合は、ここを変更します。
カレンダーIDは、カレンダーごとの設定から確認できます。(概ね、hogehoge@group.calendar.google.com のような形式になっています)

取得できるイベントの属性

上記の API ドキュメントをたどっていくと、取得できるイベントの属性が分かります。
これです→ https://godoc.org/google.golang.org/api/calendar/v3#Events

ただ、属性値自体が struct だったりして、たどっていくとキリがありません。
まあ、大体どんなものがあるかを把握するだけでいいと思います。

以下余談

ここらへんは、アプリの形に応じてどの程度詳細なところまで取得するべきか考えます。

今回つくりたいもので言えば、重複のチェックのためにイベントの属性を使いたいので…

  • 複数の属性を連結できる形に加工したい (連結した単一のキーすると扱いが楽)
  • 本当に重複しているか、デバッグ出力したい
  • 反対に、「特定の属性値を持つイベントを抽出する」ことは不要なので、人間が入力する手間は考えなくて良い

というのを満たせれば良いです。

というわけで、今回は文字列に変換することを念頭に置きます。

カスタマイズする

quickstart.go をカスタマイズして、今回やりたいことである重複したイベントを削除する機能を追加していきます。
ついでに、アプリとしての体を成すようにしていきます。

重複したイベントを除去する

イベントから、キーとする属性値を文字列化する

こんな関数を用意して、*calendar.Eventを文字列にするようにしました。

↓引数fieldsには、キーとする属性の名前を複数指定します。

func UniqKey(e *calendar.Event, fields ...string) string {
    k := ""

    for _, f := range fields {
        switch strings.ToLower(f) {
        case "created":
            k += e.Created
        case "description":
            k += e.Description
        case "end":
            t := e.End.DateTime
            if t != "" {
                k += t
            } else {
                k += e.End.Date
            }
        case "etag":
            k += e.Etag
        case "hangoutLink":
            k += e.HangoutLink
        case "htmllink":
            k += e.HtmlLink
        case "icaluid":
            k += e.ICalUID
        case "id":
            k += e.Id
        case "location":
            k += e.Location
        case "start":
            t := e.Start.DateTime
            if t != "" {
                k += t
            } else {
                k += e.Start.Date
            }
        case "summary":
            k += e.Summary
        case "updated":
            k += e.Updated
        default:
        }
    }

    return k
}

重複の検出をする

重複検知の簡単な実装としては、map があります。

今回キーは文字列ですので、map[string]struct{} を使います。

uniqs := make(map[string]struct{})

 :

key := UniqKey(item, "Description", "Summary", "Start", "End")
if _, found := uniqs[key]; found {
    fmt.Printf("[DEL] %v (%v)\n", item.Summary, date)

    // イベントを削除する
} else {
    fmt.Printf("* %v (%v)\n", item.Summary, date)

    uniqs[key] = struct{}{}
}

重複した場合でも優先して残すべき条件がある場合は、map[string]([]*calendar.Event) のようにしておくと、後で処理がしやすいでしょう。

イベントを削除する

ここまでで srv.Events.List() を使ってきましたが、削除は srv.Events.Delete() を使います。

List() の場合と同様、Delete() で条件をしていして、最後に Do() を呼び出します。

uniqs := make(map[string]struct{})

 :

key := UniqKey(item, "Description", "Summary", "Start", "End")
if _, found := uniqs[key]; found {
    fmt.Printf("[DEL] %v (%v)\n", item.Summary, date)

    delevent := srv.Events.Delete("primary", item.Id)
    err = delevent.Do()
    if err != nil {
        fmt.Printf("  failed to delete: %v", err)
    }
} else {
    fmt.Printf("* %v (%v)\n", item.Summary, date)

    uniqs[key] = struct{}{}
}

ただし、これだけでは削除できません。
ソースコード上でのスコープの変更が必要です。

スコープを変更する

スコープは、いわば、承認時に申請しておく権限のようなものです。

ここになって少し承認の部分を変更します。

サンプルのコードでは、以下のようになっています。

config, err = google.ConfigFromJSON(b, calendar.CalendarReadonlyScope)

あからさまに削除等の操作ができないことがわかるスコープになっていますね。

API ドキュメントを CalendarReadonlyScope で検索するとわかりますが、イベントのみ操作可能なスコープや全体を変更可能なスコープがあります。
https://godoc.org/google.golang.org/api/calendar/v3#pkg-constants

このスコープに応じて、承認時のメッセージが変わってきます。

いちいち考えるのが面倒なので何でもできそうなCalendarScopeにします。
アプリの用途を満たす最低限のスコープを指定するようにしてください。

config, err = google.ConfigFromJSON(b, calendar.CalendarScope /*CalendarReadonlyScope*/)

なお、上記のコード変更で承認時に必要な情報が変わっています。
今までのトークンは使えませんので、ファイル token.json は削除し、再度承認をします。

承認をちょっとだけ自動化する

コピペが2回も必要なのは使い勝手が悪いので、できる範囲で自動化します。

サンプル quickstart.go の getTokenFromWeb() の実装を変更します。

承認URLをブラウザで表示する

これは非常に簡単で、外部のパッケージ github.com/pkg/browser を使うだけでした。

承認ページのURLは変数 authURL に格納されていますので、それを引数に上記パッケージの関数を呼び出します。

authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
browser.OpenURL(authURL)

これで、最初のコピペが不要になります。

次に、アクセスコードをアプリ(コンソール)にコピペする部分を自動化します。

アプリ内にHTTPサーバーを実装してアクセスコードを受け取る

承認の流れで(OAuth2)、アクセスコードを含むリクエストを受け取るためのリダイレクトURLを指定することができます。

今回はこの仕組みを使い、Google の承認ページからアプリがサーブするサイトにリダイレクトさせます。

まずは、authURL を取得する前に、リダイレクト先を指定します。

config.RedirectURL = fmt.Sprintf("http://localhost:%d/", 7878) //これ。ポートは暫定的に7878にする。

authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
browser.OpenURL(authURL)

当然ながら、これだけでは動作しません。
アプリ内にHTTPサーバーの機能を実装しないといけません。

HTTPサーバーを動作させつつ、承認の手続きをすすめるため、goroutine を利用します。
HTTPサーバーで受け取ったアクセスコードは、チャネル経由で承認手続きをしているコードに繋げます。

// 簡易HTTPサーバー
// 変数 code, codeChan に注目してください。他はお飾りです。
func launchRedirectionServer(port uint16, codeChan chan string) {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        code := r.FormValue("code")
        codeChan <- code

        var color string
        var icon string
        var result string
        if code != "" {
            //success
            color = "green"
            icon = "&#10003;"
            result = "Successfully authenticated!!"
        } else {
            //fail
            color = "red"
            icon = "&#10008;"
            result = "FAILED!"
        }
        disp := fmt.Sprintf(`<div><span style="font-size:xx-large; color:%s; border:solid thin %s;">%s</span> %s</div>`, color, color, icon, result)

        fmt.Fprintf(w, `
<html>
    <head><title>%s pomi</title></head>
    <body onload="open(location, '_self').close();"> <!-- Chrome won't let me close! -->
        %s
        <hr />
        <p>This is a temporal page.<br />Please close it.</p>
    </body>
</html>
`, icon, disp)
    })
    http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

// 承認手続き
func getTokenFromWeb(config *oauth2.Config) (*oauth2.Token, error) {
    var codeChan chan string
    config.RedirectURL = fmt.Sprintf("http://localhost:%d/", 7878)
    codeChan = make(chan string)
    go launchRedirectionServer(7878, codeChan)

    authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
    browser.OpenURL(authURL)

    var authCode string
    authCode = <-codeChan

    // 以上がカスタマイズ箇所

    tok, err := config.Exchange(context.TODO(), authCode)
     :
}

承認手続きの中で変数 authCodefmt.Scan していた箇所がなくなり、HTTPサーバーから来るチャネルの読み取りに変わっています。
そして、最後の config.Exchange() で、アクセスコードをもとにトークンを取得していますね。

なお、割り切りポイントとして、HTTPサーバーの停止は実装していません。
考えるのが面倒だったんで 常駐せず実行後は直ぐに終了するアプリですので、そこまでする必要はないかな、と。
ただ、そのために、main() で正常終了時も os.Exit(0) を呼ぶ必要がありました。

ちゃんとした CLI アプリにする

以下の対応をしました。

  • キーとする項目を指定できるようにする。
  • 重複チェックの開始日と取得件数、カレンダーIDを指定できるようにする。
  • Dry Runモードをつける。(削除をせずコンソールへの出力のみを行う)
  • 承認時のローカルポートを指定できるようにする。
  • ヘルプメッセージをつける。

ここらへんは、好きな CLI フレームワークを使えばいいと思います。
個人的には、自分の作った gli というパッケージが死ぬほど使いやすいので使っています。

// オプションの定義 (サブコマンドも定義可能ですが、今回はナシ)
type globalCmd struct {
    Start      gli.Date    `cli:"start,s=DATE"  help:"defaults to today"`
    Items      int64       `cli:"items,n=NUMBER"  default:"10"  help:"the number of events from --start"`
    Keys       gli.StrList `cli:"keys,k=LIST_OF_STRINGS"  default:"Description,Summary,Start,End"  help:"comman-separated keys to test uniquity of events"`
    CalendarID string      `cli:"calendar-id,id"  default:"primary"`

    Credential string `cli:"credentials,c=FILE_NAME"  default:"./credentials.json"  help:"your client configuration file from Google Developer Console"`
    Token      string `cli:"token,t=FILE_NAME"  default:"./token.json"  help:"file path to read/write retrieved token"`

    AuthPort uint16 `cli:"auth-port=NUMBER"  default:"7878"`

    DryRun bool `cli:"dry-run,dry"  help:"do not exec"`
}

// 文字列で表現できないデフォルト値は *cmd.Init() の中で代入。gli が勝手に呼び出す。
func (c *globalCmd) Init() {
    c.Start = gli.Date(time.Now())
}

// main() はコマンドラインの初期化だけになる。
func main() {
    app := gli.NewWith(&globalCmd{})
    app.Name = "uniqal"
    app.Desc = "make each event be unique" //英語自信ない...
    app.Version = "0.1.0"
    app.Usage = `uniqal --credential=./my_credentials.json --items=100 --start=` + time.Now().AddDate(0, 0, 7).Format("2006-01-02")
    app.Copyright = "(C) 2019 Shuhei Kubota"

    err := app.Run(os.Args)
    if err != nil {
        os.Exit(1)
    }
    os.Exit(0) //ワークアラウンド: HTTPサーバーを停止させたい。
}

// メインのルーチンはこちらに移動。gli が勝手に呼び出す。
func (c globalCmd) Run() error {
    uniqs := make(map[string]struct{})

    var config *oauth2.Config
    var err error
    if _, err := os.Stat(c.Credential); err != nil { // c.オプション名 でコマンドライン引数を参照
     :
}

殆ど何も設定しなくても使える CLI ライブラリ

出来上がったものがこちらになります

uniqal

ビルドした上で、自前の credentials.json を配置すれば使えるようになります。
カレンダー同期・連携サービスを使って自爆した人にオススメです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のtimeパッケージで夏時間が扱えるか確認

Go言語の標準パッケージtimeで夏時間が扱えるか確認しました。
https://ja.wikipedia.org/wiki/英国夏時間 によると英国の2018年の夏時間は3月25日の1amから10月28日の1amまでだったようなので、その前後を表示してみて確認。

package main

import (
    "fmt"
    "time"
)

func main() {
    london, _ := time.LoadLocation("Europe/London")
    fmt.Println(london)

    t1 := time.Date(2018, time.March, 25, 0, 0, 0, 0, london)
    for i := 0; i < 5; i++ {
        fmt.Println(t1)
        t1 = t1.Add(time.Hour)
    }

    t2 := time.Date(2018, time.October, 28, 0, 0, 0, 0, london)
    for i := 0; i < 5; i++ {
        fmt.Println(t2)
        t2 = t2.Add(time.Hour)
    }
}

Europe/London
2018-03-25 00:00:00 +0000 GMT
2018-03-25 02:00:00 +0100 BST
2018-03-25 03:00:00 +0100 BST
2018-03-25 04:00:00 +0100 BST
2018-03-25 05:00:00 +0100 BST
2018-10-28 00:00:00 +0100 BST
2018-10-28 01:00:00 +0100 BST
2018-10-28 01:00:00 +0000 GMT
2018-10-28 02:00:00 +0000 GMT
2018-10-28 03:00:00 +0000 GMT

扱えるようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む