i++

プログラム系のメモ書きなど

golang : json の pretty print (タブやスペースの指定)

json.EncoderSetIndent を使用するか、json.MarshalIntent を使用する。以下は前者の例。

func writePrettyJson(filePath string, v interface{}) error {
    f, err := os.Create(filePath)
    if err != nil {
        return err
    }
    defer f.Close()
    enc := json.NewEncoder(f)
    enc.SetIndent("", "    ")
    return enc.Encode(v)
}

json.MarshalIntent を使用した例は以下リンク先参照。

golang : RSA キーを含む pem ファイルの読み込み

秘密鍵の読み込み

RSA秘密鍵の pem ファイルは BEGIN RSA PRIVATE KEY で始まる場合(PKCS#1)と BEGIN PRIVATE KEY で始まる場合(PKCS#8)があり、前者の場合は x509.ParsePKCS1PrivateKey を、後者の場合は x509.ParsePKCS8PrivateKey を使ってパースする必要がある。

func readRsaPrivateKey(pemFile string) (*rsa.PrivateKey, error) {
    bytes, err := ioutil.ReadFile(pemFile)
    if err != nil {
        return nil, err
    }

    block, _ := pem.Decode(bytes)
    if block == nil {
        return nil, errors.New("invalid private key data")
    }

    var key *rsa.PrivateKey
    if block.Type == "RSA PRIVATE KEY" {
        key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
        if err != nil {
            return nil, err
        }
    } else if block.Type == "PRIVATE KEY" {
        keyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
        if err != nil {
            return nil, err
        }
        var ok bool
        key, ok = keyInterface.(*rsa.PrivateKey)
        if !ok {
            return nil, errors.New("not RSA private key")
        }
    } else {
        return nil, fmt.Errorf("invalid private key type : %s", block.Type)
    }

    key.Precompute()

    if err := key.Validate(); err != nil {
        return nil, err
    }

    return key, nil
}
公開鍵の読み込み
func readRsaPublicKey(path string) (*rsa.PublicKey, error) {
    bytes, err := ioutil.ReadFile(path)
    if err != nil {
        return nil, err
    }

    block, _ := pem.Decode(bytes)
    if block == nil {
        return nil, errors.New("invalid public key data")
    }
    if block.Type != "PUBLIC KEY" {
        return nil, fmt.Errorf("invalid public key type : %s", block.Type)
    }

    keyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return nil, err
    }

    key, ok := keyInterface.(*rsa.PublicKey)
    if !ok {
        return nil, errors.New("not RSA public key")
    }

    return key, nil
}
参考

Redshift : interval を秒/分/時に変換する

秒に変換
EXTRACT(epoch FROM my_interval)
分に変換

小数点以下を切り捨てて integer にするために FLOOR を使用。

FLOOR(EXTRACT(epoch FROM my_interval) / 60)
時に変換

分に変換と同じ。

FLOOR(EXTRACT(epoch FROM my_interval) / 3600)

参考:datetime - How do I convert an interval into a number of hours with postgres? - Stack Overflow

golang : gmail でメールを送信する

#golang メールを送信するには の通りにやれば良い。

ただし、2段階認証を有効にしている場合には <password> の部分にアプリパスワードを使う必要がある。アプリパスワードの発行 はリンク先から。アプリパスワードについての説明は アプリ パスワードでログイン 辺りを参照。

また、Subject などを含めるには smtp.SendMailmsg に指定する。これについては smtp - The Go Programming Language を参照。

func sendMail(subject, message string) error {
    auth := smtp.PlainAuth(
        "",
        "<sender>@gmail.com", // 送信に使うアカウント
        "<password>", // アカウントのパスワード or アプリケーションパスワード
        "smtp.gmail.com",
    )

    return smtp.SendMail(
        "smtp.gmail.com:587",
        auth,
        "<sender>@gmail.com", // 送信元
        []string{"<recipient>@gmail.com"}, // 送信先
        []byte(
            "To: <recipient>@gmail.com\r\n" +
            "Subject:" + subject + "\r\n" +
            "\r\n" +
            message),
    )
}

golang : 見た目が同じ長さの文字列を作成する(等幅フォント)

等幅フォントで表示する時に同じ長さに見えるような文字列を生成する。

// "github.com/mattn/go-runewidth" を使用。

func makeFixedWidthString(str string, length int) string {
    var buffer bytes.Buffer
    l := 0
    for _, c := range str {
        cl := runewidth.RuneWidth(c)
        if l + cl > length {
            break
        }
        buffer.WriteRune(c)
        l += cl
    }
    for i := 0; i < length - l; i++ {
        buffer.WriteRune(' ')
    }
    return buffer.String()
}

以下に関連しているものの、日本語と英語が交じると見た目上の長さがずれてしまったので、別途対応。あまりきちんと調べていないので、対応出来ていないケースがあるかも。

increment.hatenablog.com

golang : csv.Reader の "wrong number of fields in line" を無視する

Read() を実行する前に FieldsPerRecord を -1 に設定する。

reader := csv.NewReader(f)
reader.Comma = '\t'
reader.FieldsPerRecord = -1 // これ
for {
    record, err := reader.Read()
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
    // 処理...

csv - The Go Programming Language の Reader の FieldsPerRecord の説明を参照。

golang : 固定長文字列出力フォーマット

%(文字数).(文字数)s のような形で、最大精度と最小精度を指定する。. の左側の数字の前に - を付けると左詰め、付けないと右詰め。

// 例えば50文字(左詰め)で固定。
fixedLengthString = fmt.Sprintf("%-50.50s", s)