Go×Fyne×ExcelizeでExcelからDDLファイルを生成する簡易ツールを作成しました

f:id:tanabebe:20191118123120p:plain

コロナの影響により保育園への送り迎えと電車通勤がなくなり,空いた時間が出来た.この時間を使って社内用ではあるが,ExcelファイルからPostgreSQL用のDDL作成ツールを作成した.
今回はGo実装のGUI Tool Kit Fyneを使用し,Excelの操作にはExcelizeを使用した.

要件

いずれもExcelファイルを起点とし,以下をクライアントのPCで実現させる.

  1. DDL生成時の除外テーブルを指定させる.
  2. Schema指定を行えるようにする.
  3. Schema未指定時はSchemaの作成はせず,テーブルはpublicのSchemaとする.
  4. 論理名からコメントを取得し設定する.

実際の動作は以下となる.残念なくらい簡素だ.

f:id:tanabebe:20200414141047g:plain

こちらのテンプレートを読み込んで生成したSQLがこちらだ.

DROP TABLE IF EXISTS public.users;
CREATE TABLE public.users (
    id BIGSERIAL,
    name varchar(20),
    password text,
    mailaddress varchar(255),
    created timestamp,
    modified timestamp,
    PRIMARY KEY (id)
);
COMMENT ON TABLE public.users IS 'ユーザー';
COMMENT ON COLUMN public.users.id IS 'ID';
COMMENT ON COLUMN public.users.name IS '名前';
COMMENT ON COLUMN public.users.password IS 'パスワード';
COMMENT ON COLUMN public.users.mailaddress IS 'メールアドレス';
COMMENT ON COLUMN public.users.created IS '作成日';
COMMENT ON COLUMN public.users.modified IS '更新日';

windows実行用にexeファイルとExcelテンプレートはGitHubにあげており,簡易的に使い方も記載している.

github.com

開発準備

Fyneの導入

これだけでOKだ.

go get fyne.io/fyne

Fyneの使い方

導入したところで使い方がわからなければどうしようも出来ない.私はFyne公式のTour of Fyneを一通り流してから始めた.しかしながら今回取り扱うUIは極小なので流さなくても問題はなかったのかもしれない.

これだけはやっておこう

Fyneのレイアウト配置については使う場合に理解がないと苦戦すると思うので使い方を最低限覚えておこう.
Tour of Fyneをこなさずに進めてしまうと悩むはず.面倒かもしれないが,LayoutWidgetについては必ず抑えておこう.

Tour of Fyneを終えたら以下のSampleもとても役に立つのでレイアウトが期待通りとならない場合は眺めてみるのが良い.

github.com

例として以下のようなレイアウトを組みたい時

f:id:tanabebe:20200419215946p:plain

雑ではあるが,以下で実現出来る.

package main

import (
    "fyne.io/fyne"
    "fyne.io/fyne/app"
    "fyne.io/fyne/layout"
    "fyne.io/fyne/widget"
)

func main() {
    ap := app.New()
    w := ap.NewWindow("Layout Sample")
    w.CenterOnScreen()
    lbl := widget.NewLabelWithStyle("0", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true})
    w.SetContent(fyne.NewContainerWithLayout(layout.NewGridLayout(1), lbl,
        fyne.NewContainerWithLayout(layout.NewGridLayout(4),
            widget.NewButton("AC", func() {}),
            widget.NewButton("+-", func() {}),
            widget.NewButton("%", func() {}),
            widget.NewButton("÷", func() {}),
        ),
        fyne.NewContainerWithLayout(layout.NewGridLayout(4),
            widget.NewButton("7", func() {}),
            widget.NewButton("8", func() {}),
            widget.NewButton("9", func() {}),
            widget.NewButton("×", func() {}),
        ),
        fyne.NewContainerWithLayout(layout.NewGridLayout(4),
            widget.NewButton("4", func() {}),
            widget.NewButton("5", func() {}),
            widget.NewButton("6", func() {}),
            widget.NewButton("-", func() {}),
        ),
        fyne.NewContainerWithLayout(layout.NewGridLayout(4),
            widget.NewButton("1", func() {}),
            widget.NewButton("2", func() {}),
            widget.NewButton("3", func() {}),
            widget.NewButton("+", func() {}),
        ),
        fyne.NewContainerWithLayout(layout.NewGridLayout(4),
            widget.NewButton("0", func() {}),
            widget.NewButton(".", func() {}),
            widget.NewButton("C", func() {}),
            widget.NewButton("=", func() {}),
        ),
        fyne.NewContainerWithLayout(layout.NewGridLayout(3),
            widget.NewButton("left", func() {}),
            layout.NewSpacer(),
            widget.NewButton("right", func() {}),
        ),
        fyne.NewContainerWithLayout(layout.NewGridLayout(1),
            widget.NewButton("close", func() {
                w.Close()
            }),
        ),
    ))
    w.ShowAndRun()
}

流れとしては以下となる.

// 1. Fyneの起動
ap := app.New()
// 2. Fyneの新規windowsにHello World.を作成
w := ap.NewWindow("Hello World.")
// 3. 配置したいwidgetを作成
lbl := widget.NewLabel("Hello World.")
// 4. widgetをwindowに配置
w.SetContent(lbl)
// 5. アプリケーションの実行
w.ShowAndRun()

流れを覚えていくとレイアウトを組んでいくのが楽しくなるはずだ.

注意点

Fyneは日本語フォントに対応していない.日本語を入力すると文字化けする.
私はまだ試していないのだが以下で日本語に対応出来るとのこと.

qiita.com

Excelizeの導入

こちらもこれだけでOKだ.

go get github.com/360EntSecGroup-Skylar/excelize

Excelizeの使い方

Excelizeはドキュメントが充実しているので迷う事が少ない.こちらを読もう.私が使ったのはExcel文書を読むの処理くらいしかない.一部の処理(layout.go)から抜粋すると以下となる.

// 読み込みたいExcelファイル指定
readFile, err := excelize.OpenFile(importFile)

// 読み込むExcelシート名を保持するスライス
var targetList []string
// Excelの都合上,5スタート
for i := 5; i < len(idxRows); i++ {
    if idxRows[i][constant.SheetIndexTableName] != "" && idxRows[i][constant.Exclude] != "ON" {
                 // 対象とするシート名を格納
        targetList = append(targetList, idxRows[i][constant.TargetSheetName])
    }
}

// シート分の処理を行う
for _, list := range targetList {
    // シート毎にセル内の値を全取得
    rows, err := readFile.GetRows(list)
    // 処理略・・・
}

ビルド

FyneのREADMEにも明記されているが,Windowsアプリケーション実行時のコマンドプロンプトを抑止するため,以下のコマンドでexeファイルを生成している.

go build -o go-excel-export-ddl.exe -ldflags -H=windowsgui

大変だった点

  1. Excelのフォーマットを既存から大きく修正する事が出来なかった.
  2. Excel内で行削除をした場合とセル選択からセルをクリアした場合でGoに入ってくるデータが違った.(nil""
  3. Fyneの扱い方
  4. VBAで良く使うCells(x, y).End(xlDown)のようなセルの終点や始点操作がないため,セル読み込み時のポイントがこれで良いのか悩んだ.
  5. 業務でGoを使った経験がゼロなので,これで良いのかという悩みがとにかく多かった.

今後の改善点

データ定義を設計する際に編集者に自由入力させるのは止めたい.
例えば自動増分したい場合はデータ型をintergerとし,AutoIncrement列に「◯」を選択しているが PostgreSQL準拠ならintegerで自動増分する場合はserialとしたい.
使用しているButton内のfunctionが肥大化しすぎているのでなんかダサい.
自戒ではあるが,もっと勉強する.
PostgreSQL以外にも対応する.

参考

Fyne

Excelize Official Docs

GitHub - sqweek/dialog: Simple cross-platform dialog API for go-lang

FyneでFineな(文字化けしない✙可搬性あり)アプリを作る方法 - Qiita

T2-Wonderland: Go言語製のキレイなGUIアプリをFyneライブラリで作るよ!(*´ω`*)

GoでGUIを作るFyne というライブラリが楽しそうだよ、というご紹介 - Qiita