20210731のGoに関する記事は4件です。

Go言語とGASで翻訳デスクトップAppを10分で作る。

はじめに Go言語を少しだけ触って簡単な翻訳appを作成したので備忘録 コードはこちら システム構成 OS:Windows10 GUIの記述:Go言語(Walk) 翻訳APIの記述:GAS Go言語のwindowsデスクトップapp作成用ライブラリのWalkを用い簡単なGUIを作成。 また、GASを用いてgoogleのサーバ上に翻訳APIを作成。 あとはGUIの操作に合わせてPOSTを投げてレスポンスを表示させるだけ。超手抜き。 翻訳API まず適当にスプレッドシートを作成してGASのプロジェクトを用意 下記のコードを追記してデプロイする。 もちろんローカル環境からPOSTでアクセスする際にURLが必要になるのでどこかにメモしておく。 以下コード解説。 まずdoPostという関数はGASで用意されている関数で この中に記述した処理がPOSTでアクセスされた際に動作するようになっている。 次にJSON.parse()でリクエストに添付されているpayloadを取得する。 翻訳にはLanguageApp.translate()を使用。こちらもすでにGAS内で提供されている関数で第一引数を翻訳したstringを返却する。 一応補足しておくと第二引数が元の言語、第三引数が翻訳先の言語。 最後にjsonで書き込んでレスポンスとして送り返す。 function doPost(e){ var params = JSON.parse(e.postData.getDataAsString()) var text = params.text var source = params.source var target = params.target Logger.log(text) Logger.log(source) Logger.log(target) var translatedText = LanguageApp.translate(text, source, target) var output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(JSON.stringify({ translatedText: translatedText })); return output; } GUIの作成 まずGUIの設計を見せるがあまり調べても出てこなかったのでwalkの記述についても少し補足する。 go main.go package main import ( "log" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" "github.com/Gyabi/go-translateApp/translatePost" ) type MyMainWindow struct { *walk.MainWindow textArea *walk.TextEdit results *walk.TextEdit } func (mw *MyMainWindow) clickedUpArrow() { text := mw.results.Text() output := translatePost.Translate_post(text, "ja", "en") mw.textArea.SetText(output) } func (mw *MyMainWindow) clickedDownArrow() { text := mw.textArea.Text() output := translatePost.Translate_post(text, "en", "ja") mw.results.SetText(output) } func main() { mw := &MyMainWindow{} if _, err := (MainWindow{ AssignTo: &mw.MainWindow, Title: "todo", MinSize: Size{300, 400}, Layout: VBox{}, Children: []Widget{ Label{ Text: "Please enter the text you want to translate", }, GroupBox{ Layout: VBox{}, Children: []Widget{ Label{ Text: "English", }, TextEdit{ AssignTo: &mw.textArea, }, }, }, HSplitter{ StretchFactor: 20, Children: []Widget{ PushButton{ Text: "↓", OnClicked: mw.clickedDownArrow, }, HSpacer{}, PushButton{ Text: "↑", OnClicked: mw.clickedUpArrow, }, }, }, GroupBox{ Layout: VBox{}, Children: []Widget{ Label{ Text: "Japanese", }, TextEdit{ AssignTo: &mw.results, }, }, }, }, }.Run()); err != nil { log.Fatal(err) } } GUIデザインとWalkの補足 基本的にはmain関数内にGUIの構成を記載している。walkによって提供されている構造体を入れ子の構造で挿入していくことでGUIのデザインを定義できる。例として今回はMainWindowの子要素としてLabel,GroupBox x 2,HSplitterを使用している。 OnClickedという要素には別途定義した関数を渡すことでクリック時に実行できる。 あと使用する構造体は自身の定義した構造体にまとめて格納しておくのがいいらしい。。? type MyMainWindow struct { *walk.MainWindow textArea *walk.TextEdit results *walk.TextEdit } func main() { mw := &MyMainWindow{} if _, err := (MainWindow{ AssignTo: &mw.MainWindow, Title: "todo", MinSize: Size{300, 400}, Layout: VBox{}, Children: []Widget{ Label{ Text: "Please enter the text you want to translate", }, GroupBox{ Layout: VBox{}, Children: []Widget{ Label{ Text: "English", }, TextEdit{ AssignTo: &mw.textArea, }, }, }, HSplitter{ StretchFactor: 20, Children: []Widget{ PushButton{ Text: "↓", OnClicked: mw.clickedDownArrow, }, HSpacer{}, PushButton{ Text: "↑", OnClicked: mw.clickedUpArrow, }, }, }, GroupBox{ Layout: VBox{}, Children: []Widget{ Label{ Text: "Japanese", }, TextEdit{ AssignTo: &mw.results, }, }, }, }, }.Run()); err != nil { log.Fatal(err) } } クリック時に動作する関数 クリック時には先ほど述べたようにすでに定義してある関数を呼び出して自動的に実行させることができる。 今回は別ファイルで定義してるAPIをたたくための関数使用する形で記載した。 func (mw *MyMainWindow) clickedUpArrow() { text := mw.results.Text() output := translatePost.Translate_post(text, "ja", "en") mw.textArea.SetText(output) } func (mw *MyMainWindow) clickedDownArrow() { text := mw.textArea.Text() output := translatePost.Translate_post(text, "en", "ja") mw.results.SetText(output) } API実行関数 APIをたたく関数は別のディレクトリで作成した。(github参照) 引数としてpostするためのパラメータを指定しているのでまとめてjsonに格納してhttpのメソッドでPOSTを投げる。 レスポンスはioutilとbytesで処理して必要な翻訳後の文章のみ取り出してreturn 注意:translatePost/config.iniに先ほどメモしたAPIのurlをコピペしておく go translatePost/translate.go package translatePost import ( "bytes" "encoding/json" "io/ioutil" "net/http" "gopkg.in/ini.v1" ) type RequestBody struct { Text string `json:"text"` Source string `json:"source"` Target string `json:"target"` } type ResBody struct { TranslatedText string `json:"translatedText"` } func Translate_post(text, source, target string) string { // you can set "ja" or "en" in source and target requestBody := &RequestBody{ Text: text, Source: source, Target: target, } jsonString, err := json.Marshal(requestBody) if err != nil { panic("Error") } cfg, err := ini.Load("translatePost/config.ini") if err != nil { panic("Error") } endpoint := cfg.Section("api").Key("url").String() req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(jsonString)) if err != nil { panic("Error") } req.Header.Set("Content-Type", "application/json") client := new(http.Client) resp, err := client.Do(req) if err != nil { panic("Error") } defer resp.Body.Close() byteArray, err := ioutil.ReadAll(resp.Body) if err != nil { panic("Error") } // fmt.Printf(string(byteArray)) var resbody ResBody err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&resbody) if err != nil { panic("Error") } // fmt.Println(resbody.TranslatedText) return resbody.TranslatedText } 実行ファイルの生成 上記のファイルの作成が終わったら実行ファイルを作成する。 まずmanifestファイルを作成。githubのものをそのまま使用してOK。 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/> </dependentAssembly> </dependency> </assembly> 最後に、下記コマンドをたたくとexeファイルが出力される go build -ldflags="-H windowsgui"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSON-TO-GO を使えば、GO 構造体定義がすこぶる簡単!

Teratail の質問 API プログラミングに関する質問ができる Teratail では、API を提供しています。 質問一覧では、下記のようなレスポンスが返ってくるとのことです。 これを一つ一つ Go の構造体を作成するのは結構時間がかかる。。 { "meta": { "message": "success", "total_page": 1, "page": 1, "limit": 10, "hit_num": 2 }, "questions": [ { "id": 10364, "title": "インデントにタブを使うことのデメリット", "created": "2015-05-27 04:49:55", "modified": "2015-06-22 15:17:06", "count_reply": 11, "count_clip": 11, "count_pv": 0, "is_beginner": false, "is_accepted": false, "is_presentation": false, "tags": [ "Eclipse", "タブ", "プログラミング言語", "コーディング規約" ], "user": { "display_name": "miu_ras", "photo": "https://cdn.teratail.com/uploads/avatars/15047/4DVbsgOW_thumbnail.jpg", "score": 101 } }, { "id": 9399, "title": "mysqlに格納された配列データで絞り込みをかけることはできるのでしょうか?", "created": "2015-05-04 15:59:29", "modified": "2015-05-04 15:59:29", "count_reply": 2, "count_clip": 0, "count_pv": 0, "is_beginner": true, "is_accepted": true, "is_presentation": false, "tags": [ "MySQL" ], "user": { "display_name": "mendosa", "photo": "https://tt4.leverages.org/uploads/avatars/13696/4WwsssAn_thumbnail.jpg", "score": 7 } } ] } そこで使うのが、JSON-To-GO 。 さっそく JSON-TO-GO に貼り付ける 単純にコピペするだけで、構造体を作ってくる インラインでネストした場合 type AutoGenerated struct { Meta struct { Message string `json:"message"` TotalPage int `json:"total_page"` Page int `json:"page"` Limit int `json:"limit"` HitNum int `json:"hit_num"` } `json:"meta"` Questions []struct { ID int `json:"id"` Title string `json:"title"` Created string `json:"created"` Modified string `json:"modified"` CountReply int `json:"count_reply"` CountClip int `json:"count_clip"` CountPv int `json:"count_pv"` IsBeginner bool `json:"is_beginner"` IsAccepted bool `json:"is_accepted"` IsPresentation bool `json:"is_presentation"` Tags []string `json:"tags"` User struct { DisplayName string `json:"display_name"` Photo string `json:"photo"` Score int `json:"score"` } `json:"user"` } `json:"questions"` } ネストしない場合 type AutoGenerated struct { Meta Meta `json:"meta"` Questions []Questions `json:"questions"` } type Meta struct { Message string `json:"message"` TotalPage int `json:"total_page"` Page int `json:"page"` Limit int `json:"limit"` HitNum int `json:"hit_num"` } type User struct { DisplayName string `json:"display_name"` Photo string `json:"photo"` Score int `json:"score"` } type Questions struct { ID int `json:"id"` Title string `json:"title"` Created string `json:"created"` Modified string `json:"modified"` CountReply int `json:"count_reply"` CountClip int `json:"count_clip"` CountPv int `json:"count_pv"` IsBeginner bool `json:"is_beginner"` IsAccepted bool `json:"is_accepted"` IsPresentation bool `json:"is_presentation"` Tags []string `json:"tags"` User User `json:"user"` }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go Snippet] "RANDOM USER GENERATOR" からの JSON を Unmarshal する

概要 Random User Generator というサイトで、ランダムなユーザー情報を取得できる JSON の API を提供している。 早速使ってみる。 API はこの URL (https://randomuser.me/api) からアクセスできる。 Unmarshal したい JSON の構造を調べる { "location": { "street": "9278 new road", "city": "kilcoole", "state": "waterford", "postcode": "93027", "coordinates": { "latitude": "20.9267", "longitude": "-7.9310" } } } この構造は、こう書ける type Location struct { Street string `json:"street"` City string `json:"city"` State string `json:"state"` Postcode string `json:"postcode"` Coordinates struct { Latitude string `json:"latitude"` Longitude string `json:"longitude"` } `json:"coordinates"` } 前もって構造体を定義していなくても、type 配下で別の type を直接書くこともできる。 その場合の、json タグは、{} で閉じた後であることに注意。 コード package main import ( "encoding/json" "fmt" "io" "net/http" ) type RespData struct { Results []Result `json:"results"` Info Info `json:"info"` } type Result struct { Gender string `json:"gender"` Name Name `json:"name"` Location Location `json:"location"` Email string `json:"email"` } type Name struct { Title string `json:"title"` First string `json:"first"` Last string `json:"last"` } type Location struct { Street string `json:"street"` City string `json:"city"` State string `json:"state"` Postcode string `json:"postcode"` Coordinates struct { Latitude string `json:"latitude"` Longitude string `json:"longitude"` } `json:"coordinates"` } type Info struct { Seed string `json:"seed"` Results int `json:"results"` Page int `json:"page"` Version string `json:"version"` } func main() { url := "https://randomuser.me/api/" resp, err := http.Get(url) if err != nil { fmt.Println("Error") } defer resp.Body.Close() // resp: Pinter to type Response // fmt.Println("Status", resp.Status) // fmt.Println("StatusCode", resp.StatusCode) body, _ := io.ReadAll(resp.Body) var data RespData _ = json.Unmarshal(body, &data) fmt.Printf("%v\n", data.Results[0]) fmt.Printf("%v\n", data.Info) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gomockで同一メソッドを複数回呼び出す際のレスポンスを変える

背景 最近Goでテストを書く際、mockingフレームワークは golang/mock (通称gomock) を使用し始めました。 スタブとして同一メソッドを複数回呼び出す際に、呼び出しごとにレスポンスを変える方法を調べたので、tipsとしてまとめておきます。 以下で使用するサンプルコードは、次のようなディレクトリ構成を想定します。 animal/ ├ dog.go ├ dog_mock.go └ dog_test.go 簡単のためmockコードは省略しますが、go generate ./...で作成する想定でサンプルコードにgo:generate mockgenを埋め込んでいます。 2回目レスポンスを変える その1 ~引数なし~ アプリケーションコード こちらはBark()メソッドを持つBarkerインタフェースを実装したDogという構造体を表現しています。 Run()メソッドのテスト時にBarker.Bark()をスタブとして置き換える想定となります。 dog.go //go:generate mockgen -source=$GOFILE -destination=dog_mock.go -package=$GOPACKAGE package animal type Barker interface { Bark() string } type Dog struct{} func (d *Dog) Bark() string { return "Bow-wow" } func Run(b Barker) { fmt.Println(1, b.Bark()) fmt.Println(2, b.Bark()) } テストコード 2回目レスポンスを変える場合は、1度スタブを作る場合のBark().Return()の間にDo()を挟み、その中の無名関数内に2回目呼び出し時のスタブを実装します。 dog_test.go package animal import ( "github.com/golang/mock/gomock" "testing" ) func TestRun(t *testing.T) { ctrl := gomock.NewController(t) mock := NewMockBarker(ctrl) mock.EXPECT().Bark().Do(func() { mock.EXPECT().Bark().Return("Woof-woof") }).Return("Ruff-ruff") Run(mock) } このテストコードを実行すると次のような実行結果が出力され、1回目と2回目のレスポンスを変えることに成功します。 === RUN TestRun 1 Ruff-ruff 2 Woof-woof --- PASS: TestRun 2回目レスポンスを変える その2 ~引数あり~ アプリケーションコード その1の改修版として鳴き声を外から渡してあげる形式のBarker.Bark(sound string)を考えてみます。 Dog構造体におけるBarkの実装は大変意味のないものとなっていますがご了承ください。 今回のRun()ではBow-wowという鳴き声を2回指定しています。 dog.go //go:generate mockgen -source=$GOFILE -destination=dog_mock.go -package=$GOPACKAGE package animal import "fmt" type Barker interface { Bark(sound string) string } type Dog struct{} func (d *Dog) Bark(sound string) string { return sound } func Run(b Barker) { fmt.Println(1, b.Bark("Bow-wow")) fmt.Println(2, b.Bark("Bow-wow")) } テストコード 1回目と2回目のレスポンスを変える方法はその1とほぼ同じです。 しかし注意点として、Doに渡す内部関数にはインタフェースのメソッドと同じ形式の引数を持たせてあげる必要があります。 これを実行するとその1と同じ結果が得られます。 dog_test.go package animal import ( "github.com/golang/mock/gomock" "testing" ) func TestRun(t *testing.T) { ctrl := gomock.NewController(t) mock := NewMockBarker(ctrl) mock.EXPECT().Bark("Bow-wow").Do(func(sound string) { mock.EXPECT().Bark("Bow-wow").Return("Woof-woof") }).Return("Ruff-ruff") Run(mock) } 引数が異なる場合にレスポンスを変える その2のアプリケーション側のRunメソッドの引数を1回目と2回目で変更してみます。 dog.go func Run(b Barker) { fmt.Println(1, b.Bark("Bow-wow")) fmt.Println(2, b.Bark("Bow-wow Bow-wow")) } これに伴うテストコードの変更はmocking時のパラメータ変更で、次のようになります。 注意点として、引数の指定は呼び出し順で指定してあげる必要がある点です。 dog_test.go mock := NewMockBarker(ctrl) mock.EXPECT().Bark("Bow-wow").Do(func(sound string) { mock.EXPECT().Bark("Bow-wow Bow-wow").Return("Woof-woof") }).Return("Ruff-ruff") もし、次のように実装してしまうとテストに失敗します。 dog_test.go mock := NewMockBarker(ctrl) mock.EXPECT().Bark("Bow-wow Bow-wow").Do(func(sound string) { mock.EXPECT().Bark("Bow-wow").Return("Woof-woof") }).Return("Ruff-ruff") 所感 他の言語同様gomockでも十分なスタブを作成できます。 インタフェースが必須な点が多少他の言語とは異なりますが、テストのしやすさをメインに考えると設計がより重要になってくるという印象です。 こういった面から他の言語の設計をそのままGoに持ち込むことはできなかったりしますが、パラダイムは大きく変わらないので、たくさん失敗しながらGo力を高めていきたい所存です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む