[go] お客様の中にGoを極めた方はいらっしゃいませんかー?

golangでも同じことをやってみた

[c#] SJISというかCP932とかいう呪い

c#でのcp932で化けてしまう一部キャラクタを変換するやつ。それをgoで書いてみたのだけど……。なんだろ、遅い。正規表現は他言語と同じく遅いようで、RegexpではなくReplacer使えーとかそこらへんは考慮して書いたのだが。stopwatchの精度もスリープで確認したりなんだりで問題なさそう。未使用メソッドがあったり、処理の1,2,3を順にループさせたりするとどんどん遅くなる(なので未使用~についてはコメントアウトしてる)のが謎い。

👺 < 処理が遅い!

4826            // -> goでのこのミリセカンド表記はc#版のほうでいうと
00:00:04.0826   // -> これと同値

最終結果は同じだが、文字化け(マッピングできないキャラ)が来るとそこで試合終了っぽい。

結果

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "time"

    "golang.org/x/text/encoding/japanese"
    "golang.org/x/text/transform"
)

// ループの外でReplacerを持つようにした
var rep *strings.Replacer

func main() {
    fmt.Println("start")
    // ファイル読み込み
    list := getFileData()
    /*list2 := make([]string, len(list))
    copy(list2, list)
    list3 := make([]string, len(list))
    copy(list3, list)*/
    // 処理
    start1 := time.Now()
    // ループの外でReplacerを持つようにした
    rep = strings.NewReplacer("〜", "~", "−", "-", "¢", "¢", "£", "£", "¬", "¬", "—", "―", "‖", "∥")
    for i := 0; i < 1000000; i++ {
        replaceUnmappingChars1(list)
    }
    stop1 := time.Now()
    fmt.Println(stop1.Sub(start1).Milliseconds())
    // ファイル書き込み
    outputFile(list)
    fmt.Println("end")
}

// replace unmapping chars3 -> 8293
/*func replaceUnmappingChars3(list []string) {
    for i, str := range list {
        sl := strings.Split(str, "")
        length := len(sl)
        var rep string
        for j := 0; j < length; j++ {
            if sl[j] == "〜" {
                rep += "~"
            } else if sl[j] == "−" {
                rep += "-"
            } else if sl[j] == "¢" {
                rep += "¢"
            } else if sl[j] == "£" {
                rep += "£"
            } else if sl[j] == "¬" {
                rep += "¬"
            } else if sl[j] == "—" {
                rep += "―"
            } else if sl[j] == "‖" {
                rep += "∥"
            } else {
                rep += sl[j]
            }
        }
        list[i] = rep
    }
}*/

// replace unmapping chars2 -> 100万回 9946
/*func replaceUnmappingChars2(list []string) {
    for i, str := range list {
        var rep string
        for _, c := range str {
            if string(c) == "〜" {
                rep += "~"
            } else if string(c) == "−" {
                rep += "-"
            } else if string(c) == "¢" {
                rep += "¢"
            } else if string(c) == "£" {
                rep += "£"
            } else if string(c) == "¬" {
                rep += "¬"
            } else if string(c) == "—" {
                rep += "―"
            } else if string(c) == "‖" {
                rep += "∥"
            } else {
                rep += string(c)
            }
        }
        list[i] = rep
    }
}*/

// replace unmapping chars -> 100万回 4826
func replaceUnmappingChars1(list []string) {
    for i, data := range list {
        //rep := strings.NewReplacer("〜", "~", "−", "-", "¢", "¢", "£", "£", "¬", "¬", "—", "―", "‖", "∥")
        list[i] = rep.Replace(data)
    }
}

// GetFileData return list
func getFileData() []string {
    // file open
    file, err := os.Open("./source.txt")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer file.Close() // c#でのusing - disposeみたいな? todo:あとでもっとよく調べる
    // file read
    var data []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        data = append(data, scanner.Text())
    }
    if err := scanner.Err(); err != nil { // どうやったらここに来るんだ?
        fmt.Println(err)
        os.Exit(1)
    }
    return data
}

// outputFile
func outputFile(list []string) {
    // file create todo:上書きっぽい(あとでよく見る)
    file, err := os.Create("./destination_sjis_fix.txt")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer file.Close()
    // data write
    writer := bufio.NewWriter(transform.NewWriter(file, japanese.ShiftJIS.NewEncoder()))
    for _, data := range list {
        _, err := writer.WriteString(data + "\n") // やっぱり普通はここで変換するよね(c#でも書いたのでこっちにも)
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }
    }
    writer.Flush() // buf(debuggerでみた)の中にWriteStringで溜めてFlushで一気に書くっぽい
}

なんか根本的にミスってるのかなー。わからん。ベンチマークメソッド?みたいなのがあるからそっちでやらないとダメなのかなー。

[c#] SJISというかCP932とかいう呪い

文字変換

未だに「xxx(※1)はシフトジスでくださいねー」とかいうトチ狂った環境がどのような場所にもある。大抵の社畜が躓くのがあのExcelとのデータやり取りなのだが、何故かあのソフトウェアを皆さん本当に大好きなので困る。中途半端に万能なのが悪いのだが、使用者にも問題(例えばまともにcsv開けない(※2)とか)も多く誰を責めていいものやら。

部下(社畜)を殺しておいて、まるで花火見物で雨に振られたときのように「無駄足だった」か……。面白いのう、お前ら。まあ、せっかくだから死んで行けや

という気分ですよ。阿葉山宗介さんもそりゃマジギレですよ。多くの人間が(主にエクセル対策で)死んでいった。それが文字コード問題。まあ俺から言わせればSJISなんて大したこと無い。EBCDIC(IBMメインフレームとか)の地獄をお前らに味わわせてやりたい。

※1 ここにはExcelとかcsvとかtsvとかお好きなものを脳内で入れてください

※2 データインポートとか誰もやらねえよ!

Windows上でのUTF8 -> CP932変換問題

この令和の時代でも上のような需要があるので文字コード変換からは逃げられないのです。で、説明するのも面倒なので"utf8 sjis 文字化け 波線"とかでググってください。簡単に説明すると、複数の「なんじゃこりゃ」仕様が重なって文字化けするんですね。一部が。世の中のシステムは必ず腐っていくものだけど、なぜかといえば必ず"その場しのぎ"でなんかしちゃうからなんですって。どこかで聞いたんだけど。

変なコードを見た

なんか1文字づつユニコードエスケープ文字になおして比較してたんだけど、これ遅くねえか?と思って調べたら遅かったっていうね。しかし、俺が想定していたコードも最速ではなかった。のだが書いた。

気がついたのだけど、エントリ単体のページはちゃんとコードハイライトが聞いているけど、トップとかカテゴリとかの一覧ページで効いてないのよね。WordPressのJQuery外してる影響っぽいけど、レンダリング速度を優先するのでこれからはコードは"続きを読む"以降に書くことにしました。

続きを読む "[c#] SJISというかCP932とかいう呪い"

[go] 既存のPhalcon製mailformをgolangで書き直した

メールフォームを作り直した

[PHP Phalcon 3.4] メールフォーム作った
ちょうど一年くらい前に作ったのですが、迷惑メールに突っ込まれまくっていたため何通か頂いたのに反応できませんでした。せっかくこんな廃墟に来ていただいたというのに。

PHP Phalcon -> GO

そんなわけで自鯖からのphp mb_send_mailではなく、sendgridを使ってgoから飛ばすように変えました。LocalのPHP環境ではPhalconの4.xにしていたのですが、既存の3.4のメールフォーム、なんと全く動かず。上のエントリからリポジトリ見てもらえばわかるのですが、俗に言うMicroでさえこの有様。ちょっと面倒ですね。

というわけでPHPやめて、勉強しようしようと思っていたgoで同じものを書いてみました。CSRF周りはginにもあるようですが、Phalconと同じようにしたかったので自前実装です。ロジックの中でhtmlタグ生成するの嫌いなんですよね……。goでのWebアプリというと、SPAのバックで使う人が多いのでAPI鯖みたいなのが多いのですが、今回は完全にtemplatesを使ったベッタベタなWebアプリです。

https://bitbucket.org/dobusarai/go-mailform/src/master/

readmeかったるくて自動生成まんまなのさすがにマズイな。

噂には聴いていましたが、モダンな言語にある機能?が本当に無いので驚いた。でもこのレベルなら慣れの範疇だなー。ただgo modは前エントリにも書いたけどアレだと思う。あと、この量のコードでhttpd含めて動いてしまうのは凄いな、と。

https://bitbucket.org/dobusarai/go-mailform/src/master/main.go

たった252行だよ。(templates等は除く)

すでに /contact はこれで動いています。いないと思うけど使う人は、settings.ini.dummyをsettings.iniにリネームして中身書き換えればそのまま動きます。

結局

お気づきの方は初っ端からお気づきだと思いますが、sendgridにしようがなんだろうがSPF/DKIMの設定からは逃れられないのです。あー。SendGridのDomain Authenticationまわりを設定しないとダメですね。あー、めんどくさい。

[go] 5分でできる sendgrid apiでメール送信

SendGridさんのアカウント開設

アカウント作成(*1) -> コンパネメニュー -> Settings -> API Keys -> Create API Key(右上ボタン) -> 適当にAPI Keyの名前つけて Create&Viewボタン押下 -> API Keyをメモっておく

*1
アカウントを作成するのに審査があります。私はいくつかの選択肢の中からサイト作成を選択しました。ご担当者さんのアナウンスといい作業はとってもスムーズに終了。ちなみに表題の5分にはこの申請についての時間は含んでいません。つまりインチキです。

SendGridさんのサンプルを参照したが...

Goでメール送信!SendGridを使って簡単に実装する方法
書いててなんかおかしいなと思ったら、この記事5年以上前のものです。

スクラッチで

1: ディレクトリ(sendgrid-example1)作る
2: 上のディレクトリに移動してgomod初期化とパッケージインストール

go mod init sendgrid-example1
go get -u github.com/sendgrid/sendgrid-go

3: コード

package main

import (
    "fmt"

    "github.com/sendgrid/sendgrid-go"
    "github.com/sendgrid/sendgrid-go/helpers/mail"
)

// 記述されてるアドレスは架空のものです
func main() {
    from := mail.NewEmail("dobusarai info", "info@dobudobuo.net")
    subject := "mail test subject"
    to := mail.NewEmail("hogehoge", "dobusarai@dobudobuo.net")
    plainTextContent := "and easy to do anywhere, even with Go"
    htmlContent := "<strong>and easy to do anywhere, even with Go</strong>"
    message := mail.NewSingleEmail(from, subject, to, plainTextContent, htmlContent)
    client := sendgrid.NewSendClient("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<API KEY>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
    response, err := client.Send(message)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(response.StatusCode)
        fmt.Println(response.Body)
        fmt.Println(response.Headers)
    }
}

goをちょっと学びながら書いているが、初学者殺しのクソブログエントリが多すぎる。結局githubのreadmeとソースコードを読むのが一番速く済みますね。英語はDeepLさんの出番です!あ、goはシングル(ファイル)バイナリになったりコンパイル速度とか良いところ多いけど、やはり例外がないので書くのかったるい。あとgo modulesはクソだと思う。これ↓とか。
Go Modules でインターネット上のレポジトリにはないローカルパッケージを import する方法
なんやねんインターネット上にパッケージがあるのが前提て。これ使うな?デファクトスタンダード技術使うなとか、それこそ初学者殺しでしょ……。NameSpace & Classに慣れきっているのでどうすりゃいいんだこれ?ってのがある。goはClassの概念がないので初めは細かくパッケージ切ってやっていこうかと思ったのだけど、この仕様のせいでダルすぎてなー。どうするのが最適解なのか。まーそのうちわかんだろ。
おわり。

Hello DotNet5!

Visual Studio Code縛り

.net5のSDKのインストール、及び単純なコード(エディタで直書き)のビルドに問題は何もなかった。が、VScodeでOmnisharpが古いSDKを見ているようで、ビルドどころか補完すら効かない状態だった。
net5_vscode_failed_load_project.png
VScodeのC#Extensionのreadmeを見たところ、.net5使うにはMSBuildの16.8.0が必要とのこと。ん?MSBuild?仕方がないのでVisual Studio 2019のUpdateを行ったところ、VScodeも.net5のSDKを見に行くようになった。.net5のSDKにincluded MSBuild 16.8.0って書いてあるんだけどなー。"net5.0-windows"などでも検索してみたが、同様な事象で躓いている人間を確認できなかった。やはりCSharperはVisual Studio使いがほとんどなんじゃないじゃろか。

実行時バージョン取得

せっかくなので実行時exeのバージョンを取得したい。PHPだと

phpversion(); //-> 7.2.34
Phalcon\Version::get(); //-> 3.4.5

このように実行時のバージョンが容易く取得できるのだが、.netは昔から何故かめんどくさい。今回は https://github.com/nishy2000/DotNetDetector を参考にして簡易的にバージョンを取得した。.netcore3.1 / 5.0と違う事だけを確認したかったので下記のように書いたが、もっと細かく取得したい人はこのパッケージをNugetでゲットするかリポジトリのSourceを確認してください。

using System;
using System.Reflection;
using System.Runtime.Versioning;

namespace HelloDotNet5
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine(DotNetVersionOnThisProgram.Get());
        }
    }

    // <summary>
    /// via) NishySoftware.DotNetDetector https://github.com/nishy2000/DotNetDetector
    /// </summary>
    public static class DotNetVersionOnThisProgram
    {
        public static string Get()
        {
            return ((TargetFrameworkAttribute)Assembly.GetEntryAssembly().GetCustomAttribute(typeof(TargetFrameworkAttribute))).FrameworkName;
        }
    }
}
Hello World!
.NETCoreApp,Version=v5.0

追記

そんなわけで現在作成中の画面キャプチャツールも.net5に移行した。プロジェクトファイルを

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <ApplicationIcon>resources\camera_icon.ico</ApplicationIcon>
  </PropertyGroup>
  <ItemGroup>
    <Content Include="log\.gitkeep">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <EmbeddedResource Include="resources\camera_icon.ico" LogicalName="camera_icon.ico" />
  </ItemGroup>
</Project>

これを

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <ApplicationIcon>resources\camera_icon.ico</ApplicationIcon>
  </PropertyGroup>
  <ItemGroup>
    <Content Include="log\.gitkeep">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <EmbeddedResource Include="resources\camera_icon.ico" LogicalName="camera_icon.ico" />
  </ItemGroup>
</Project>

こう変えてから、launch.jsonのnetcoreapp3.1を

{
   // Use IntelliSense to find out which attributes exist for C# debugging
   // Use hover for the description of the existing attributes
   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
   "version": "0.2.0",
   "configurations": [
        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            // If you have changed target frameworks, make sure to update the program path.
            "program": "${workspaceFolder}/bin/Debug/net5.0-windows/Butler.exe",
            "args": [],
            "cwd": "${workspaceFolder}/bin/Debug/net5.0-windows/",
            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
            "console": "integratedTerminal",
            "stopAtEntry": false,
            "requireExactSource": false
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }
    ]
}

net5.0-windowsに変更。そしてdotnet cleanをしてからbuildしなおした。

dotnet5_messagebox_show_test

適当に実行して5.0を確認。