お題
前回、Python3で書いた簡易ツール「ローカルJSONファイルに保存するキーバリューストア」のGolang版。
シリーズ物としては、もともと複数言語で同じ内容のプログラムを書いて比較した「簡単なツール作成を通して各プログラミング言語を比較しつつ学ぶ」からの改善版。
試行Index
- 第1回:簡単なツール作成を通して各プログラミング言語を比較しつつ学ぶ
- 第2回:【改善編】簡単なツール作成を通してRubyを学ぶ
- 第3回:【改善編】簡単なツール作成を通してPython3を学ぶ
- 第4回:【改善編】簡単なツール作成を通してGolangを学ぶ
- 第5回:【改善編】簡単なツール作成を通してJavaを学ぶ
- 第6回:【改善編】簡単なツール作成を通してScalaを学ぶ
- 第7回:簡単なツール作成を通してRustを学ぶ
- 第8回:【改善編】簡単なツール作成を通してRustを学ぶ
実装・動作確認端末
# 言語バージョン
$ go version
go version go1.11.4 linux/amd64
# IDE - Goland
GoLand 2019.2
Build #GO-192.5728.103, built on July 23, 2019
実践
要件
アプリを起動すると、キーバリュー形式でテキスト情報をJSONファイルに保存する機能を持つコンソールアプリ。
オンメモリで保持していた点だけ除けば第1回と同じ仕様なので詳細は以下参照。
https://qiita.com/sky0621/items/32c87aed41cb1c3c67ff#要件
ソース全量
解説
全ソースファイル
Golangソース | 説明 |
---|---|
main.go | アプリ起動エントリーポイント |
store_info.go | キーバリュー情報を保存するストア(JSONファイル)に関する情報を扱う。 現状は「ファイル名」だけ保持 |
commands.go | キーバリューストアからの情報取得や保存、削除といった各コマンドを管理。 コマンドの増減に関する影響は、このソースに閉じる。 |
command.go | 各コマンドに共通のインタフェース |
save.go | キーバリュー情報の保存を担う。 |
get.go | 指定キーに対するバリューの取得を担う。 |
list.go | 全キーバリュー情報の取得を担う。 |
remove.go | 指定キーに対するバリューの削除を担う。 |
clear.go | 全キーバリュー情報の削除を担う。 |
help.go | ヘルプ情報の表示を担う。 |
end.go | アプリの終了を担う。 |
[main.go]アプリ起動エントリーポイント
[main.go]
package main
import (
"bufio"
"log"
"os"
"strings"
)
func main() {
commands := NewCommands(NewStoreInfo("store.json"))
println("Start!")
for {
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
cmds := strings.Split(s.Text(), " ")
commands.Exec(cmds)
}
if s.Err() != nil {
log.Fatal(s.Err())
}
}
}
[store_info.go]ストア情報の管理
[store_info.go]
package main
// アルファベット大文字で始まると定数扱い
const DefaultStoreName = "store.json"
func NewStoreInfo(storeName string) StoreInfo {
if storeName == "" {
return &storeInfo{storeName: DefaultStoreName}
}
return &storeInfo{storeName: storeName}
}
type StoreInfo interface {
GetName() string
}
type storeInfo struct {
storeName string
}
func (s *storeInfo) GetName() string {
return s.storeName
}
[commands.go]各コマンドの管理
[commands.go]
package main
import (
"fmt"
"os"
)
func NewCommands(storeInfo StoreInfo) *Commands {
_, err := os.Stat(storeInfo.GetName())
if err != nil {
NewClearCommand(storeInfo).Exec(nil)
}
return &Commands{
commands: map[string]Command{
"end": NewEndCommand(),
"help": NewHelpCommand(),
"clear": NewClearCommand(storeInfo),
"save": NewSaveCommand(storeInfo),
"get": NewGetCommand(storeInfo),
"remove": NewRemoveCommand(storeInfo),
"list": NewListCommand(storeInfo),
},
}
}
type Commands struct {
commands map[string]Command
}
func (c *Commands) Exec(cmds []string) {
if len(cmds) < 1 {
fmt.Println("no target")
return
}
cmd := c.commands[cmds[0]]
if cmd == nil {
fmt.Println("no target")
return
}
if len(cmds) == 1 {
cmd.Exec(nil)
return
}
cmd.Exec(cmds[1:])
}
[command.go]各コマンドの親クラス
[command.go]
package main
type Command interface {
Exec(args []string)
}
各コマンドクラス
各コマンドについては、ストア情報がオンメモリのハッシュからJSONに変わった点以外はやることは一緒。(なので説明省く。)
■保存
[save.go]
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func NewSaveCommand(storeInfo StoreInfo) Command {
return &saveCommand{storeInfo: storeInfo}
}
type saveCommand struct {
storeInfo StoreInfo
}
func (c *saveCommand) Exec(args []string) {
if len(args) != 2 {
fmt.Printf("not valid: %#v\n", args)
return
}
b, err := ioutil.ReadFile(c.storeInfo.GetName())
if err != nil {
fmt.Printf("error1: %s\n", err.Error())
return
}
var d map[string]string
err = json.Unmarshal(b, &d)
if err != nil {
fmt.Printf("error2: %s\n", err.Error())
return
}
d[args[0]] = args[1]
res, err := json.Marshal(d)
if err != nil {
fmt.Printf("error3: %s\n", err.Error())
return
}
err = ioutil.WriteFile(c.storeInfo.GetName(), res, os.ModePerm)
if err != nil {
fmt.Printf("error4: %s\n", err.Error())
return
}
}
■1件取得
[get.go]
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
func NewGetCommand(storeInfo StoreInfo) Command {
return &getCommand{storeInfo: storeInfo}
}
type getCommand struct {
storeInfo StoreInfo
}
func (c *getCommand) Exec(args []string) {
if len(args) != 1 {
fmt.Printf("not valid: %#v\n", args)
return
}
b, err := ioutil.ReadFile(c.storeInfo.GetName())
if err != nil {
fmt.Printf("error1: %s\n", err.Error())
return
}
var d map[string]string
err = json.Unmarshal(b, &d)
if err != nil {
fmt.Printf("error2: %s\n", err.Error())
return
}
fmt.Println(d[args[0]])
}
■全件取得
[list.go]
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
func NewListCommand(storeInfo StoreInfo) Command {
return &listCommand{storeInfo: storeInfo}
}
type listCommand struct {
storeInfo StoreInfo
}
func (c *listCommand) Exec(args []string) {
b, err := ioutil.ReadFile(c.storeInfo.GetName())
if err != nil {
fmt.Printf("error1: %s\n", err.Error())
return
}
var d map[string]string
err = json.Unmarshal(b, &d)
if err != nil {
fmt.Printf("error2: %s\n", err.Error())
return
}
println(`"key","value"`)
for k, v := range d {
println("\"" + k + "\",\"" + v + "\"")
}
}
■1件削除
[remove.go]
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func NewRemoveCommand(storeInfo StoreInfo) Command {
return &removeCommand{storeInfo: storeInfo}
}
type removeCommand struct {
storeInfo StoreInfo
}
func (c *removeCommand) Exec(args []string) {
if len(args) != 1 {
fmt.Printf("not valid: %#v\n", args)
return
}
b, err := ioutil.ReadFile(c.storeInfo.GetName())
if err != nil {
fmt.Printf("error1: %s\n", err.Error())
return
}
var d map[string]string
err = json.Unmarshal(b, &d)
if err != nil {
fmt.Printf("error2: %s\n", err.Error())
return
}
delete(d, args[0])
res, err := json.Marshal(d)
if err != nil {
fmt.Printf("error3: %s\n", err.Error())
return
}
err = ioutil.WriteFile(c.storeInfo.GetName(), res, os.ModePerm)
if err != nil {
fmt.Printf("error4: %s\n", err.Error())
return
}
}
■全件削除
[clear.go]
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func NewClearCommand(storeInfo StoreInfo) Command {
return &clearCommand{storeInfo: storeInfo}
}
type clearCommand struct {
storeInfo StoreInfo
}
func (c *clearCommand) Exec(args []string) {
_, err := os.Create(c.storeInfo.GetName())
if err != nil {
fmt.Printf("error1: %s\n", err.Error())
return
}
d := make(map[string]string, 1)
b, err := json.Marshal(&d)
if err != nil {
fmt.Printf("error2: %s\n", err.Error())
return
}
err = ioutil.WriteFile(c.storeInfo.GetName(), b, os.ModePerm)
if err != nil {
fmt.Printf("error3: %s\n", err.Error())
return
}
}
■ヘルプ
[help.go]
package main
func NewHelpCommand() Command {
return &helpCommand{}
}
type helpCommand struct {
}
func (c *helpCommand) Exec(args []string) {
msg := `
[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
以下のサブコマンドが利用可能です。
list ... 保存済みの内容を一覧表示します。
save ... keyとvalueを渡して保存します。
get ... keyを渡してvalueを表示します。
remove ... keyを渡してvalueを削除します。
endCommand ... ヘルプ情報(当内容と同じ)を表示します。
`
println(msg)
}
■アプリ終了
[end.go]
package main
import (
"fmt"
"os"
)
func NewEndCommand() Command {
return &endCommand{}
}
type endCommand struct {
}
func (c *endCommand) Exec(args []string) {
fmt.Println("End!")
os.Exit(-1)
}
まとめ
同じことを複数言語で実装するの、だんだんしんどくなってきたな・・・。