昨年から Oracle Cloud の無料枠を使っています。2 vCPU な VM を2台も無料で使わせて頂けるという Oracle Cloud さんの大盤振る舞いに感謝しつつ、Oracle Cloud Function でしりとりをしてみました。
Oracle Cloud Function は Fn Project というサーバレスプラットフォームをベースにしており、同プロジェクトの fn というツールを使う事で、他の Fn Project を使うクラウドと同様に操作を行う事ができます。Fn Project
Open Source. Container-native. Serverless platform.
https://fnproject.io/
fn コマンドは以下の手順でインストールする事ができます。
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
MacOS の場合は homebrew からインストールが可能。
brew update && brew install fn
Windows の場合は僕が送っている pull-request を使わないとエラーになります。ソースを git clone して go build して下さい。
Fix atomicwrite for Windows by mattn - Pull Request #605 - fnproject/cli
https://github.com/fnproject/cli/pull/605
fn コマンドを使える様になるまでは sugimount さんが Qiita に書いている記事を参考にしました。
サーバレスな Oracle Functions (Fn) をやってみた - Qiita
https://qiita.com/sugimount/items/018e08f575ecefb1546c
注意点としては、Oracle Cloud はデフォルトで VCN が作られていますが、Function を使う場合は別途 VCN を作らないと DHCP オプションが一致しないというエラーが出てしまいます。新しく作って下さい。あと sugimount さんが書かれているリポジトリ名と異なる物が実際には用意されるので、詳細は Oracle Cloud のダッシュボードからファンクションを選択し、アプリケーション詳細の「開始」タブを見るとほぼやるべき事が書いてあります。
アプリケーションを作る所まで出来たら、しりとりサーバを作ります。短いのでコード全体を載せます。
package main
import (
"bufio"
"context"
"encoding/json"
"errors"
"io"
"math/rand"
"strings"
_ "func/statik"
fdk "github.com/fnproject/fdk-go"
"github.com/rakyll/statik/fs"
)
var upper = strings.NewReplacer(
"ぁ", "あ",
"ぃ", "い",
"ぅ", "う",
"ぇ", "え",
"ぉ", "お",
"ゃ", "や",
"ゅ", "ゆ",
"ょ", "よ",
)
func kana2hira(s string) string {
return strings.Map(func(r rune) rune {
if 0x30A1 <= r && r <= 0x30F6 {
return r - 0x0060
}
return r
}, s)
}
func hira2kana(s string) string {
return strings.Map(func(r rune) rune {
if 0x3041 <= r && r <= 0x3096 {
return r + 0x0060
}
return r
}, s)
}
func search(text string) (string, error) {
rs := []rune(text)
r := rs[len(rs)-1]
statikFS, err := fs.New()
if err != nil {
return "", err
}
f, err := statikFS.Open("/dict.txt")
if err != nil {
return "", err
}
defer f.Close()
buf := bufio.NewReader(f)
words := []string{}
for {
b, _, err := buf.ReadLine()
if err != nil {
break
}
line := string(b)
if ([]rune(line))[0] == r {
words = append(words, line)
}
}
if len(words) == 0 {
return "", errors.New("empty dictionary")
}
return words[rand.Int()%len(words)], nil
}
func shiritori(text string) (string, error) {
text = strings.Replace(text, "ー", "", -1)
if rand.Int()%2 == 0 {
text = hira2kana(text)
} else {
text = kana2hira(text)
}
return search(text)
}
func handleText(text string) (string, error) {
rs := []rune(strings.TrimSpace(text))
if len(rs) == 0 {
return "", errors.New("なんやねん")
}
if rs[len(rs)-1] == 'ん' || rs[len(rs)-1] == 'ン' {
return "", errors.New("出直して来い")
}
s, err := shiritori(text)
if err != nil {
return "", err
}
if s == "" {
return "", errors.New("わかりません")
}
rs = []rune(s)
if rs[len(rs)-1] == 'ん' || rs[len(rs)-1] == 'ン' {
s += "\nあっ..."
}
return s, nil
}
func main() {
fdk.Handle(fdk.HandlerFunc(myHandler))
}
type Siritori struct {
Word string `json:"word"`
}
func myHandler(ctx context.Context, in io.Reader, out io.Writer) {
var s Siritori
json.NewDecoder(in).Decode(&s)
var err error
s.Word, err = handleText(s.Word)
if err != nil {
s.Word = err.Error()
}
json.NewEncoder(out).Encode(&s)
}
辞書ファイルは statik を使ってバイナリに埋め込みました。ソースコードは GitHub に置いておきます。
mattn/oracle-cloud-function-siritori
https://github.com/mattn/oracle-cloud-function-siritori/
デプロイは以下の手順で行います。
$ fn --verbose deploy --app [your-app]
デプロイが完了すると標準入力で JSON を受け取り、標準出力で JSON を出力するコマンドが動く様になっています。
VCN を作らないといけない事に気付くまで結構時間を使ってしまったけど、動く事が分かってからは結構サクサク操作できる様になりました。これだけ遊んでもまだ無料範囲内らしいので、もう少し遊んでみたいと思います。