20201114のGoに関する記事は6件です。

golangのテストでDoAndReturnのところでCall with too many input argumentsエラーが出たときの対処法

m.EXPECT().SomeMethod(name string, number int).DoAndReturn(func() error {
  // 様々な処理
  return nil
})

これだと、panic: reflect: Call with too many input argumentsエラーが出る

なので、以下のようにする

m.EXPECT().SomeMethod(name string, number int).DoAndReturn(func(name string, number int) error {
  // 様々な処理
  return nil
})

つまり、mockにしたいメソッド(SomeMethod)の引数とDoAndReturnの引数の関数の引数は一致させなければならない

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

http.Handle()とhttp.NewServeMux().Handle()について

以下のコードがあったとする。
どちらもやっていること同じじゃないか?と思い調査した結果。

http.Handle("/any/", anyHandler)
http.ListenAndServe(":8080", nil)

mux := http.NewServeMux()
mux.Handle("/any/", anyHandler)
http.ListenAndServe(":8080", mux)

結論

どちらも内部的にやっていることは基本的に同じでした。

http.Handle()

URLパターンと対応するhandlerをDefaultServeMuxの中に登録している。

実装としては以下の形。

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

DefaultServeMuxとは

DefaultServeMux はServeMuxのポインタ型が格納されている変数。

では、ServeMuxとは

HTTPのマルチプレクサ。
実装としては以下のようになっている。

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

http.Handle()の中心はDefaultServeMux.Handle(pattern, handler)

URLパターンと対応するhandlerをDefaultServeMuxの中に登録している。

と記載したがそれはつまり、 DefaultServeMux.Handle(pattern, handler) の部分。
ServeMux.m にURLパターンと対応するハンドラを追加している。

実装としては以下。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

http.NewServeMux().Handle()

URLパターンと対応するhandlerをServeMuxの中に登録している。
実装としては以下。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

DefaultServeMux.Handle(pattern, handler) と全く同じ処理ではないか。。
つまり、内部的に http.Handle() と同じ処理をしている。

改めて結論

冒頭のコードでは全く同じ処理ができる。
ただ、 http.NewServeMux().Handle() は自分でServeMuxを作成するので
その中の値を独自に変更したい場合などに用いられるのかもしれない。

参考文献

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

他言語プログラマが最低限、気にすべきGoのネーミングルール

概要

タイトルの通り、他言語から入門した人が最低限気にするべき、ネーミングルールをまとめました。

対象読者

Go の基本構文を理解している方を対象読者としています。

この記事で説明する事、説明しない事

説明する事

Go のファイル名、変数名等の名前付けに関するルールや慣例等を説明します。

説明しない事

名前付け以外で気をつけるべき Go の書き方がいくつかあります。

しかし、それらに関してはこの記事では説明しません。

筆者のバックグラウンド

プログラマ歴はもうすぐ 8 年程で、Go の他には以下のような言語の経験があります。

  • JavaScript
  • TypeScript
  • PHP
  • Ruby
  • Java
  • Scala

Go は少し前に書いて、一時期書かない時期が続いていましたが、最近また書いています。

トータルすると Go の経験は 1 年半程度です。

意識すべき名前付けルール

package名

利用して良い文字列

Effective Go では簡潔で明確な名前が望ましいとされています。

小文字のみを利用し、1 単語(単数形)で構成されるのが望ましいです。

  • 良い例
    • time
    • http
  • 悪い例
    • computeServiceClient(キャメルケースは使わない)
    • priority_queue(スネークケースは使わない)

ただし 1 つ例外があります。

それは _test サフィックスを付けるパターンです。

Go は同じディレクトリ以下に複数の package 名を許可しませんが、 _test を付けるとそれが可能になります。

異なるパッケージになるので、非公開メソッドなどは参照できなくなります。

しかし、これを利用する事で循環参照の問題を回避出来る等のメリットがあります。

これは標準 package でも利用されているテクニックです。

// (例) heap packageのテストなので、heap_test になる
package heap_test

避けるべき単語

また以下のような名前は使わないようにするのが無難です。

  • base
  • util
  • common
  • lib
  • misc

これらの名前は「簡潔な名前」ですが、「明確な名前」ではないからです。

命名に迷ったら、Go の標準 package 名を参考にすると良いでしょう。

ファイル名

Go の公式情報で「ファイル名をどうすべきか」という記載は見つける事が出来ませんでした(もしあったらコメントで教えて下さい)

kubernetesterraform 等の有名 OSS や標準 package でスネークケースの命名が使われているので、それに従うのが無難そうです。

(例)
- addressed_types_test.go
- addressed_types.go

ディレクトリ名

ファイル名と同じく、「Go の公式情報でどうすべきか」という記載は見つける事が出来ませんでした(もしあったらコメントで教えて下さい)

こちらに関しては、package 名と同じく簡潔で明確な 1 単語での名前を付けるのがベストです。

なので小文字の英字のみを利用、というのが正しそうです。

kubernetesterraform 等の有名 OSS でケバブケース(単語の区切りに - を利用)を使っているパターンを見つけたました。

どうしても単語の区切りが欲しい場合はケバブケースを使うのもアリかもです。

(例)
- https://github.com/kubernetes/kubernetes/tree/master/cluster/log-dump

自分はケバブケースを採用せずに、全て小文字で統一するようにしています。

ちなみに package 名とディレクトリ名が違っていても問題はないですが、合わせておいたほうが無難です。

関数、type、構造体

キャメルケースで命名します。

外部に公開する関数や構造体の場合、先頭を大文字にするという言語仕様があるので、それに合わせてアッパーキャメルケース(先頭大文字から始まる)かローワーキャメルケース(先頭小文字から始まる)が決まります。

// packageの外に公開する場合
func Contents(filename string) (string, error) {}

// package内でのみ利用する場合
func contents(filename string) (string, error) {}

レシーバ名

英語 1 文字か 2 文字でなるべく短く命名します。

型が Client であれば c, cl 等です。

レシーバ名は必ず統一する必要があります(場所によって c が使われていたり cl が使われていたりは NG)

また、修飾語を利用しない点が重要です。

例えば httpClient ならレシーバ名は c になりますし、 DBCreator なら c になります。

変数・引数名

こちらも短い変数名を推奨します。

引数に関しては、レシーバ名と同じく 1 文字を使って良いでしょう。

変数名を出来る限り短い名前が理想とはされていますが、スコープには注意しましょう。

スコープが大きな関数で短い変数を使うと可読性が大きく低下してしまいます。

また略語についても注意が必要です。

下記のようにプログラマの間で一般的になっている略称を使う分には問題ないですが、無理やり略してしまい、意味が分からない変数名になると可読性が低下します。

  • Configconf
  • Stringstr

個人的な意見ですが、もしも良い略語が思いつかないなら、普通に英単語を使った変数名が無難でしょう。

短い変数名が推奨されているからと言って、命名規則を軽視して良いという話ではないので、このあたりは注意が必要です。

error変数名

error を変数として宣言する場合は、Err prefix を付けて宣言します。

明確にこうしましょうと、記載されている訳ではないですが、Go の公式ブログ Working with Errors in Go 1.13 でこのような変数名を使っている例が確認出来ます。

また、Effective Go にも以下のような記述があります。

// Error codes returned by failures to parse an expression.
var (
    ErrInternal      = errors.New("regexp: internal error")
    ErrUnmatchedLpar = errors.New("regexp: unmatched '('")
    ErrUnmatchedRpar = errors.New("regexp: unmatched ')'")
    ...
)

ちなみに Go のエラーハンドリングは err をチェックしてエラーが nil かどうかを確認してエラーが起きていないかどうかを確認します。

その為、以下のようなコードがたくさん出てきます。

    data, err := ioutil.ReadFile(src)
    if err != nil {
        return nil, err
    }

この時に err という変数名で受け取るのが慣例です。

Err prefix はあくまでも、error を変数として宣言する場合のルールなので上記のようなエラーハンドリングには利用しません。

以下の記事にも書かれていますが、err のスコープを if 構文の中に閉じ込めたり、 := を使った書き方を利用すれば大体のケースは err だけで済ませる事が出来ます。

(参考)「Go初心者が気を付けること」の解説 err変数名のバリエーションを増やしすぎる(ほとんどの場合「err」だけで済む)

どうしても err だけで済まないようなケースが出た場合は Err prefix を利用しても良いと思われます。

ちなみに kubernetesterraform 等の有名 OSS では Err prefix を使った方法は採用されていませんでした。

このあたりをどうすべきかは明確な決まりはなさそうなので、プロジェクト毎に合わせるで良いと思われます。

map 等の存在チェック

以下のように特定のキーが存在するかどうかをチェックする際には ok という変数名を使うのが慣例のようです。

id, ok := users[userID]

これはどこかに明記されている訳ではないですが、標準 package の中で、このパターンの書き方が多く使われています。

頭文字や頭文字をとった名前の単語の命名について

下記に記載されている内容です。

https://github.com/golang/go/wiki/CodeReviewComments#initialisms

Go の命名は基本的にキャメルケースですが、元々略語として浸透している単語は一貫した大文字と小文字を使います。

url ではなく URL を使うとか、http ではなく HTTP を使うといった内容です。

個人的意見ですが、このルールを理解するのが最初難しかったです。

例えば、GitHub とか Twitter 等の単語も github とか twitter を使ったらダメなのか、等色々と混乱しました。

golangci-lint というツールでこの表記をチェックしている箇所があるのですがカバーされている単語は下記の通りでした。

(該当箇所、以下抜粋)。
https://github.com/morix1500/lint/blob/master/lint.go#L743

var commonInitialisms = map[string]bool{
    "ACL":   true,
    "API":   true,
    "ASCII": true,
    "CPU":   true,
    "CSS":   true,
    "DNS":   true,
    "EOF":   true,
    "GUID":  true,
    "HTML":  true,
    "HTTP":  true,
    "HTTPS": true,
    "ID":    true,
    "IP":    true,
    "JSON":  true,
    "LHS":   true,
    "QPS":   true,
    "RAM":   true,
    "RHS":   true,
    "RPC":   true,
    "SLA":   true,
    "SMTP":  true,
    "SQL":   true,
    "SSH":   true,
    "TCP":   true,
    "TLS":   true,
    "TTL":   true,
    "UDP":   true,
    "UI":    true,
    "UID":   true,
    "UUID":  true,
    "URI":   true,
    "URL":   true,
    "UTF8":  true,
    "VM":    true,
    "XML":   true,
    "XMPP":  true,
    "XSRF":  true,
    "XSS":   true,
}

このようなルールは golangci-lint で機械的にチェックしたいですが最近よく出てくる gRPCGraphQL 等の単語はチェックされていないようです。

また自動で Go のコードを生成するツールの一部もこのルールを無視したコードが生成されたりする場合もあります。

自分は golangci-lint のこのルールを OFF にして普通にキャメルケースで命名するように統一しています。

参考にした情報

この記事を書く際に以下の情報を参考にさせて頂きました。

公式情報

個人ブログ等

あとがき

Go の命名規則についてまとめました。

ここに上げたルールの一部は golangci-lint でチェック出来ます。

まだ作成中ですが、今後は golangci-lint の設定等も記事にしようと思っています。

最後まで読んで頂きありがとうございます。

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

Go でアラビア語を描画する

TL;DR

Go でアラビア語を描画することに成功しました。アラビア文字の描画には「向きが右から左に流れる」「グリフの形がコードポイントと 1 対 1 対応しない」などの、他の言語とは異なる諸問題があります。 Go のフォントのための準標準ライブラリ golang.org/x は、アラビア文字を考慮していません。そのため独自に描画アルゴリズムを実装する必要がありました。

筆者は Go 用のビットマップフォントライブラリ bitmapfont にアラビア文字およびアラビア文字を描画するための補助的なロジックを実装しました。またそれを用いたゲームが公開されるなど、十分実用性があることを示しました。

使用例

実際に「償いの時計」というゲームでアラビア語版が追加されました。拙作のライブラリ bitmapfont と 2D ゲームライブラリ Ebiten が使われています。

謝辞

実装に協力してくださった Mansour Sorosoro さんに感謝いたします。アラビア文字に関する Unicode の知識を教えてくださり、またグリフを提供してくださいました。また「償いの時計」のアラビア語翻訳もなさったそうです。

また、自分のわがまま (?) で Ebiten を使ってもらい、かつ辛抱強くゲームのアラビア語描画部分デバッグに付き合ってくれた、「償いの時計」の作者でもある Daigo にも感謝いたします。

アラビア文字描画の基礎知識

Go の話の前に、最小限のアラビア語描画について述べます。なお、筆者はアラビア文字およびアラビア語について全く知識がなく、読み書きできません。単に、与えられたコードポイントに対して、機械的にグリフを描画するための最小限の知識を持っているだけです。

アラビア文字を使用する言語はアラビア語だけではありません。ペルシャ語やウルドゥー語などでもアラビア文字が使用されます。言語によって使用するグリフなどが変わるのですが、本稿ではアラビア語を前提とします。

アラビア文字はコードポイントとグリフが 1 対 1 対応せず、しかも向きが反転します。次の図はその対応関係を大雑把に表した図です。上が単に Unicode のコードポイントを並べたもので、下が実際に表示すべき文字の形です。

arabic.png

  • アラビア文字は右から左に描画されます。またここでは詳しく述べませんが、アラビア文字とラテン文字 (英語などのアルファベット) などが混ざる時にどういう順序になるか、は自明な問題ではありません。これを標準化する Bidi というアルゴリズムが Unicode では定義されています。
  • グリフの形が隣接する文字によって変化します。同じコードポイントでも状況によって違うグリフになります。
  • 特定の 2 つのコードポイントが 1 つのグリフになるリガチャがあります。
  • ダイアクリティカルマーク (シャクル) があります。これは母音を表記するための補助的な記号です。アラビア文字においてはフルセットが使用されることはあまりありません。フルセットが必要なのは子供向けの文章です。
  • その他アラビア語用のクエスチョンマーク (U+061F Arabic Question Mark) やカンマ (U+060C Arabic Comma) などが、別コードポイントとして割り当てられています。グリフはラテン文字のそれらと比べて、鏡像反転していたり回転していたりします。

グリフ

arabicglyph.png

アラビア文字は、 1 つの論理コードポイントに対して普通のグリフが存在します。他の隣接する文字とは独立している場合 (Isolated)、結合する文字の始端の場合 (Initial)、中間の場合 (Medial)、終端 (Final) の最大 4 種類です。文字によっては一部のグリフが存在しない場合もあります。結合のルールは複雑で、Isolated と Final だけ存在するパターン、そもそも他の文字と結合しないパターンなどがあります。

リガチャ

arabicligature.png

隣接する文字によっては 1 つのグリフにまとまります。リガチャの種類は言語 (アラビア語か、ペルシャ語か?) によって異なります。

Unicode

Unicode はアラビア文字のために以下のコードポイントを定義しています。

  • Arabic (U+0600 - U+06FF)
  • Arabic Presentation Forms A (U+FB50 - U+FDFF)
  • Arabic Presentation Forms B (U+FE70 - U+FEFF)
  • その他

Arabic は論理的な文字を収録します。普通のアラビア語文章はこの文字だけで表現されます。一方 Presenatation Forms は、異なるグリフごとに違うコードポイントを収録します。またリガチャも別に割り当てられています。 Presentation Forms の文字は文章で使うことはありません。プログラムの内部で使用することはあります。

このうちアラビア語で最低限必要なのは、実は「Arabic」と「Arabic Presentation Forms B」だけです。「Arabic Presentation Forms A」は、アラビア文字を使う他の言語で必要になるようです。

Go における文字描画事情

Go で文字を描画するライブラリは、準標準ライブラリである golang.org/x/image/font がデファクトスタンダードです。 Face インターフェイスが 1 つのフォントフェイスを表します。

type Face interface {
    io.Closer
    Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool)
    GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool)
    GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool)
    Kern(r0, r1 rune) fixed.Int26_6
    Metrics() Metrics
}

ところが Face インターフェイスは、コードポイントとグリフが 1 対 1 対応が前提となっている作りになっています。そのため、アラビア文字やタイ文字などを描画するのには使えません。例えば Glyph 関数は rune 1 つに対して 1 つのグリフを返しますが、隣接する文字の状態を持たないため、アラビア文字のようなグリフの変形に対応できません。また、右から左に流れる言語について Advance や Kern の値をどう扱えば良いのかについて、規定がありません。アラビア文字の OpenType フォントを golang.org/x/image/font/opentype をつかって Face インターフェイスにしたところで、正しく使えるものにはなりません。

また双方向テキストのアルゴリズムである Bidi についても準標準ライブラリ golang.org/x/text/unicode/bidi があるのですが、肝心のアルゴリズム部分は空っぽです。

こういったグリフの処理は、 Go 以外では HarfBuzz がデファクトスタンダードなライブラリです。 Cgo をつかって HarfBuzz で (準標準は使わないで) なんとかすることも可能ですが、 Pure Go ではなくなってしまいます。

というわけで Go でアラビア文字を描画するために、自分で色々拵えないといけません。

Go でアラビア語を描画するための実装

Unicode の節で述べたとおり、表示グリフ用のコードポイントは Arabic Presentation Forms A/B として別に割り当てられています。アラビア文字のテキストを、何かしらの手段で Presemtation Forms の文字に変換し、且つ文字列の方向もよしなに補正すると、実は golang.org/x/image/font ライブラリでも使用可能な文字列になります。コードポイントと表示される文字とグリフが 1 対 1 対応し、向きについても常に左から右に流れると仮定して処理できるからです。

筆者の実装

bitmapfont ライブラリの PresentationForms 関数がその実装です。

func PresentationForms(input string, defaultDirection Direction, lang language.Tag) string

input は入力する論理テキストです。 defaultDirection はデフォルトの方向です。 lang は言語タグで、例えばアラビア語かペルシャ語でリガチャが異なるパターンを想定しています。いまのところ lang は実装で何にも使われていません。戻り値の文字列は、 Bidi 処理済みの Presentation Forms で構成された文字列です。そのままたとえば fontDrawString 関数に渡せます。

いまのところアラビア文字やヘブライ文字 (同じく右から左へ記述する文字) 以外をこの関数に渡しても素通りするだけです。そのためどんな文字列でも安全にこの関数に渡せます。

この関数は bitmapfontFace 専用です。他の Face では動かない可能性があります。そのためこの関数は残念ながら汎用的なソリューションではありません。

筆者が必要なのは 12px なビットマップフォントでした。そのため実装は割と簡略化されています。たとえばリガチャについては一部しか考慮していません。また、アラビア文字でもアラビア語のことしか考慮していないため、一部の Presentation Forms には対応していません。また、 Bidi についても手抜き実装になっています。

Presentation Forms への変換

筆者は Python Arabic Resharper というライブラリの実装を参考にしました。ざっくりいうと、変換テーブルを用意し文字を順に見ていって適切な Presentation Form のグリフを選んであげればよいのです

リガチャ

リガチャは bitmapfont の用意しているリガチャに合わせて、 4 種類しか実装していません。これも予めテーブルを用意し、変換してあげるだけです

Bidi

Bidi については極めて手抜きの実装になっています。真面目にやろうとするのは大変だからです。ざっくりいうと、方向ごとに別のパーツに分けて、パーツごとにグリフの順序を入れ替えたりしています。時々おかしな結果になることがあることが確認されていますが、ゲームでの使用範囲では問題ありません。時間があるならば、ちゃんとした Bidi 実装を提案して、準標準ライブラリである golang.org/x/text/unicode/bidi に貢献するのが筋であると思います。

Face の定義

ダイアクリティカルマークは、他のグリフと重ねて表示しなければなりませんが、 カーニングを負の値にすることで実現しています。 bitmapfont はすべてのダイアクリティカルマークをサポートするわけではありません。これも手抜き実装の一つです。ひょっとしたらマークによっては単純に重ねるだけではうまく描画できず、うまくグリフを変形させ無ければならないでしょう。bitmapfont がサポートするダイアクリティカルマークは、単純に重ねて問題ない一部のマークのみです。

使用例

package main

import (
    "flag"
    "image"
    "image/color"
    "image/draw"
    "image/png"
    "os"
    "strings"

    "github.com/hajimehoshi/bitmapfont/v2"
    "github.com/pkg/browser"
    "golang.org/x/image/font"
    "golang.org/x/image/math/fixed"
    "golang.org/x/text/language"
)

const (
    // https://www.unicode.org/udhr/
    text = "يولد جميع الناس أحرارًا متساوين في الكرامة والحقوق."
)

func run() error {
    const (
        ox = 16
        oy = 16
    )

    width := 480
    height := 16*len(strings.Split(strings.TrimSpace(text), "\n")) + 8

    dst := image.NewRGBA(image.Rect(0, 0, width, height))
    draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.ZP, draw.Src)

    face := bitmapfont.Face

    d := font.Drawer{
        Dst:  dst,
        Src:  image.NewUniform(color.Black),
        Face: face,
        Dot:  fixed.P(ox, oy),
    }

    for _, l := range strings.Split(text, "\n") {
        l = bitmapfont.PresentationForms(l, bitmapfont.DirectionRightToLeft, language.Arabic)
        d.DrawString(l)
        d.Dot.X = fixed.I(ox)
        d.Dot.Y += face.Metrics().Height
    }

    path := "example.png"
    fout, err := os.Create(path)
    if err != nil {
        return err
    }
    defer fout.Close()

    if err := png.Encode(fout, d.Dst); err != nil {
        return err
    }

    if err := browser.OpenFile(path); err != nil {
        return err
    }
    return nil
}

func main() {
    flag.Parse()
    if err := run(); err != nil {
        panic(err)
    }
}

結果は次のようになります:

image.png

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

Golang api get

Golangを勉強しています。

やること

  • GolangでRakutenRapidApiを叩く
  • APIKeyをconfig.iniに設定して、main.goファイルから呼び出す。
  • さらに関数を別ファイルに切り分けmain.goファイルから呼び出す。

今回使うパッケージ

  • "fmt"
  • "io/ioutil"
  • "net/http"
  • "log"
  • "os"
  • "gopkg.in/ini.v1"

api

RakutenRapidApiに提供されているFearAndGreed Indexを取得するAPIを使用させていただきます。

クエリもなくレスポンスは1種類のみです。

config.ini config.go

ルートディレクトリにconfig.iniを作成し、api_keyを設置します。

//config.ini
[fgi]
api_key = XXXXXXXXXXXXXXXXXXXXXX
api_host = XXXXXXXXXXXXXXXXXXXXXX
//config.go
package config

import (
    "log"
    "os"

    "gopkg.in/ini.v1"
)

type ConfigList struct {
    FgiAPIKey  string
    FgiAPIHost string
}

// Config グローバル定義
var Config ConfigList

func init() {
    cfg, err := ini.Load("config.ini")
    if err != nil {
        log.Printf("Failed to read file: %v", err)
        os.Exit(1)
    }

    Config = ConfigList{
        FgiAPIKey:  cfg.Section("fgi").Key("api_key").String(),
        FgiAPIHost: cfg.Section("fgi").Key("api_host").String(),
    }
}

structを作成して、 ConfigListに先ほど設定したconfig.iniファイルのkey,hostを
.String()で文字列として格納します。

main.go

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"

    "index-indicator-apis/config"
)

func main() {

    url := "https://fear-and-greed-index.p.rapidapi.com/v1/fgi"

    req, _ := http.NewRequest("GET", url, nil)

    req.Header.Add("x-rapidapi-host", config.Config.FgiAPIHost)
    req.Header.Add("x-rapidapi-key", config.Config.FgiAPIKey)

    res, _ := http.DefaultClient.Do(req)

    defer res.Body.Close()
    body, _ := ioutil.ReadAll(res.Body)

    fmt.Println(string(body))

}

"net/http"の http.NewRequestでGETして、Header.Addで先ほど作成したConfig(ConfigList)のKeyとHostを呼び出します。

ターミナルで実行

$ go run main.go

{"fgi":{"now":{"value":63,"valueText":"Greed"},"previousClose":{"value":56,"valueText":"Greed"},"oneWeekAgo":{"value":40,"valueText":"Fear"},"oneMonthAgo":{"value":56,"valueText":"Greed"},"oneYearAgo":{"value":83,"valueText":"Extreme Greed"}}}

レスポンスを得ることができました。

別ファイルに関数を切り分ける

main.goファイルはよりシンプルに実行するメソッドや関数のみを記述するのがGOの設計思想みたいです。
そこでfgi.goファイルをつくり関数を切り分けました。

// fgi.go
package fgi

import (
    "fmt"
    "io/ioutil"
    "net/http"

    "index-indicator-apis/config"
)

func DoRequest() {

    url := "https://fear-and-greed-index.p.rapidapi.com/v1/fgi"

    req, _ := http.NewRequest("GET", url, nil)

    req.Header.Add("x-rapidapi-host", config.Config.FgiAPIHost)
    req.Header.Add("x-rapidapi-key", config.Config.FgiAPIKey)

    res, _ := http.DefaultClient.Do(req)

    defer res.Body.Close()
    body, _ := ioutil.ReadAll(res.Body)

    fmt.Println(string(body))

}

//main.go
package main

import "index-indicator-apis/fgi"

func main() {
    fgi.DoRequest()
}

以上です!
次はさらに関数をメソッド化して引数を渡して見たいと思います!

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

Golangのクロスコンパイル時にsqlite3がundefinedエラーになる問題の解決

やろうとしたこと

GolangのスクリプトをAmazon Linux上で動すためにクロスコンパイルをしようとした

起きたこと

Mac向けにビルドすると普通にビルドできて動くが、linux向けにビルドするとエラーに。
linux向けのクロスコンパイルの設定が間違っているのかと思って調べたら違い、どうもsqlite3のみで起きていそう

MacOS向けは上手くいく

使用したコマンド

$ GOOS=darwin GOARCH=amd64 go build -o main_for_mac ./main.go

ビルドに成功し、動作も正常だった。

linuxはsqlite3でundefinedが出る

使用したコマンド

$ GOOS=linux GOARCH=amd64 go build -o main_for_linux ./main.go

出たエラー

# github.com/dinedal/textql/storage
../{中略}/.go/pkg/mod/github.com/dinedal/textql{中略}/sqlite.go:30:28: undefined: sqlite3.SQLiteConn
../{中略}/.go/pkg/mod/github.com/dinedal/textql{中略}/sqlite.go:49:4: undefined: sqlite3.SQLiteDriver
...

調べて分かったこと:

go-sqlite3を使っているMacユーザーがたくさんはまっていた。
該当のissues
https://github.com/mattn/go-sqlite3/issues/384

原因はC言語のクロスコンパイルにおいては実行環境がOS依存であることだった。
sqliteの実装においてえ使用されている模様。

Cross-compiling C code (and by extension, cgo code) is tricky because the choice of C compiler depends on both the OS (Windows/Linux/Mac) and architecture (x86, x64, arm) of both the host and target. On top of that, there are multiple flavors of compilers for a given combination. I don't think listing all of them is feasible. But I do think it would be helpful to more thoroughly explain what a cross-compiler is and why it is needed, and list out some of the more common options. It would also be helpful to mention using docker as a means of "cross-compiling" (at least when targeting Linux)

解決した方法

MacにおけるC言語のコンパイル環境に問題があるので gcc-4.8.1-for-linux32-linux64 をインストールすることで解決する。

手順

Mac用のgccをインストール

gcc-4.8.1-for-linux32-linux64 をダウンロードしてインストール。

オプションをつけて実行

実行するコマンドは以下

build as usual env GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=/usr/local/gcc-4.8.1-for-linux64/bin/x86_64-pc-linux-gcc go build

参考

多くの人が上記issueで問題にぶつかっていたため、親切な方が手順を別のissueにまとめてくれていた。

install [http://crossgcc.rts-software.org/download/gcc-4.8.1-for-linux32-linux64/gcc-4.8.1-for-linux64.dmg]gcc-4.8.1-for-linux32-linux64
build as usual env GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=/usr/local/gcc-4.8.1-for-linux64/bin/x86_64-pc-linux-gcc go build
```

該当のissue
https://github.com/mattn/go-sqlite3/issues/797

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