<|start_filename|>functions/pipotron.go<|end_filename|>
package main

import (
	"encoding/json"
	"fmt"
	"math/rand"

	"github.com/ajg/form"
	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/gohugoio/hugo/common/maps"
	yaml "gopkg.in/yaml.v2"
	"moul.io/pipotron/dict"
	"moul.io/pipotron/pipotron"
	"moul.io/srand"
)

func reply(request events.APIGatewayProxyRequest, statusCode int, contentType string, body string) (*events.APIGatewayProxyResponse, error) {
	if contentType == "" {
		contentType = `text/plain; charset=utf-8`
	}
	ret := &events.APIGatewayProxyResponse{
		StatusCode: statusCode,
		Body:       body,
		Headers: map[string]string{
			"Content-Type": contentType,
		},
	}

	if callback := request.QueryStringParameters["callback"]; callback != "" {
		out, _ := json.Marshal(body)
		ret.Headers["Content-Type"] = `application/javascript; charset=utf-8`
		ret.Body = fmt.Sprintf("%s(%s)", callback, string(out))
	}

	return ret, nil
}

func handler(request events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
	var dictFile []byte
	var err error

	switch request.HTTPMethod {
	case "GET":
		dictFile, err = dict.Box.Find(request.QueryStringParameters["dict"] + ".yml")
		if err != nil {
			return reply(request, 404, "", "No such dictionary (?dict=)")
		}
	case "POST":
		type input struct {
			Dict   string `form:"dict"`
			Source string `form:"source"`
		}
		var i input
		d := form.NewDecoder(nil)
		if err := d.DecodeString(&i, request.Body); err != nil {
			return reply(request, 400, "", "invalid input")
		}
		dictFile = []byte(i.Source)
	default:
		return reply(request, 400, "", "No such method (GET or POST only)")
	}

	var context pipotron.Context
	if err = yaml.Unmarshal(dictFile, &context.Dict); err != nil {
		return reply(request, 500, "", fmt.Sprintf("%v", err))
	}
	context.Scratch = maps.NewScratch()

	out, err := pipotron.Generate(&context)
	if err != nil {
		return reply(request, 500, "", fmt.Sprintf("%v", err))
	}

	if request.QueryStringParameters["show-source"] == "1" {
		return reply(request, 200, "", string(dictFile))
	}

	contentType := `text/plain; charset=utf-8`
	if override := context.Scratch.Get("content-type"); override != nil {
		contentType = override.(string)
	}
	if override := request.QueryStringParameters["content-type"]; override != "" {
		contentType = override
	}
	return reply(request, 200, contentType, out)
}

func main() {
	rand.Seed(srand.Fast())
	lambda.Start(handler)
}


<|start_filename|>dict/dict.go<|end_filename|>
package dict // import "moul.io/pipotron/dict"

import packr "github.com/gobuffalo/packr/v2"

var Box = packr.New("dict", ".")


<|start_filename|>pipotron/tmpl.go<|end_filename|>
package pipotron // import "moul.io/pipotron/pipotron"

import (
	"bytes"
	"fmt"
	"math/rand"
	"strings"
	"text/template"

	"github.com/Masterminds/sprig"
	"moul.io/godev"
)

func Generate(ctx *Context) (string, error) {
	_, err := executeTemplate(`{{pick "init"}}`, ctx)
	if err != nil {
		return "", err
	}
	return executeTemplate(`{{pick "output"}}`, ctx)
}

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

func executeTemplate(input string, ctx *Context) (string, error) {
	funcMap := template.FuncMap{}
	for k, v := range sprig.FuncMap() {
		funcMap[k] = v
	}
	funcMap["pick"] = func(key string) string {
		opts := ctx.Dict[key]
		if len(opts) < 1 {
			return "$$$ INVALID OPTION $$$"
		}
		elem := opts[rand.Intn(len(opts))]
		evaluated, err := executeTemplate(elem, ctx)
		if err != nil {
			return elem
		}
		return evaluated
	}
	funcMap["randString"] = randStringBytes
	funcMap["title"] = strings.Title
	funcMap["lower"] = strings.ToLower
	funcMap["upper"] = strings.ToUpper
	funcMap["rand"] = rand.Float64
	funcMap["randIntn"] = rand.Intn
	funcMap["debug"] = func(v interface{}) string {
		fmt.Println(godev.PrettyJSON(v))
		return fmt.Sprintf("%#v", v)
	}
	funcMap["N"] = func(n int) (stream chan int) {
		stream = make(chan int)
		go func() {
			for i := 0; i <= n; i++ {
				stream <- i
			}
			close(stream)
		}()
		return
	}
	funcMap["randMinMax"] = func(min, max int) int { return rand.Intn(max-min) + min + 1 }
	funcMap["pick_once"] = func(key string) string {
		opts := ctx.Dict[key]
		if len(opts) < 1 {
			return "$$$ INVALID OPTION $$$"
		}
		i := rand.Intn(len(opts))
		elem := opts[i]
		opts = append(opts[:i], opts[i+1:]...)
		evaluated, err := executeTemplate(elem, ctx)
		if err != nil {
			return elem
		}
		return evaluated
	}

	tmpl, err := template.New("").Funcs(funcMap).Parse(input)
	if err != nil {
		return "", err
	}

	var tpl bytes.Buffer

	if err = tmpl.Execute(&tpl, ctx); err != nil {
		return "", err
	}

	if tpl.String() == input {
		return input, nil
	}
	return executeTemplate(tpl.String(), ctx)
}


<|start_filename|>main.go<|end_filename|>
package main // import "moul.io/pipotron"

import (
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"strings"

	"github.com/gohugoio/hugo/common/maps"
	yaml "gopkg.in/yaml.v2"
	"moul.io/pipotron/dict"
	"moul.io/pipotron/pipotron"
	"moul.io/srand"
)

func main() {
	rand.Seed(srand.Fast())

	if len(os.Args) < 2 {
		for _, file := range dict.Box.List() {
			if strings.HasSuffix(file, ".yml") {
				fmt.Println(file[:len(file)-4])
			}
		}
		return
	}

	dictName := os.Args[1]
	if !strings.HasSuffix(dictName, ".yml") {
		dictName += ".yml"
	}

	// try to get the file content from the packr box first, and fallback to the disk
	dictFile, err := dict.Box.Find(dictName)
	if err != nil {
		dictFile, err = ioutil.ReadFile(dictName)
		if err != nil {
			log.Fatal("failed to open file: %v", err)
		}
	}

	var context pipotron.Context
	context.Scratch = maps.NewScratch()
	if err = yaml.Unmarshal(dictFile, &context.Dict); err != nil {
		log.Fatal("failed to unmarshal yaml: %v", err)
	}

	out, err := pipotron.Generate(&context)
	if err != nil {
		log.Fatal("failed to generate %v", err)
	}

	fmt.Println(out)
}


<|start_filename|>pipotron/dict.go<|end_filename|>
package pipotron // import "moul.io/pipotron/pipotron"

import (
	"encoding/json"

	"github.com/gohugoio/hugo/common/maps"
)

type Context struct {
	Scratch *maps.Scratch
	Dict    map[string][]string
}

// FIXME: an option should be a string or a string with a weight

func (d Context) String() string {
	out, _ := json.MarshalIndent(d, "", "  ")
	return string(out)
}


<|start_filename|>Makefile<|end_filename|>
DICTS := $(basename $(notdir $(wildcard dict/*.yml)))
GORELEASER_GITHUB_TOKEN ?=
GITHUB_TOKEN ?= $(GORELEASER_GITHUB_TOKEN)
DEPS := ./web/dicts.js ./dict/dict-packr.go

.PHONY: install
install: $(DEPS)
	GO111MODULE=on go install


.PHONY: dev
dev: clean
	GO111MODULE=on go install
	cd web; browser-sync

./web/dicts.js: $(wildcard ./dict/*.yml)
	cd dict; fs-bundler --format=js --callback=dicts *.yml > ../$@.tmp
	mv $@.tmp $@

./dict/dict-packr.go: $(wildcard ./dict/*.go) $(wildcard ./dict/*.yml)
	GO111MODULE=off go get github.com/gobuffalo/packr/v2/packr2
	rm -f ./dict/*~
	rm -f ./dict/.#*
	rm -f ./dict/*#
	packr2

.PHONY: examples
examples:
	rm -rf examples
	mkdir -p examples
	for dict in $(DICTS); do \
	  for i in `seq 100`; \
	    do pipotron $$dict >> examples/$$dict.txt; \
	  done; \
	done

.PHONY: clean
clean:
	git clean -fxd
	rm -f $(DEPS)

.PHONY: docker
docker:
	docker build -t moul/pipotron .

.PHONY: functions
functions: $(DEPS)
	mkdir -p functions-build
	GO111MODULE=on go install
	go build -o functions-build/pipotron ./functions/pipotron.go

.PHONY: goreleaser
goreleaser: $(DEPS)
	GORELEASER_GITHUB_TOKEN=$(GORELEASER_GITHUB_TOKEN) GITHUB_TOKEN=$(GITHUB_TOKEN) goreleaser --rm-dist

.PHONY: goreleaser-dry-run
goreleaser-dry-run:
	goreleaser --snapshot --skip-publish --rm-dist

.PHONY: _netlify_prepare
_netlify_prepare:
	go get moul.io/fs-bundler

.PHONY: netlify-dev
netlify-dev:
	netlify dev -c 'make dev'

.PHONY: sam-local
sam-local: $(DEPS)
	@# pip install --user sam-aws-cli
	@echo ""
	@echo "Open: http://localhost:3000/index.html"
	@echo ""
	GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o functions-build/pipotron ./functions/pipotron.go
	sam local start-api --host=0.0.0.0 --static-dir=web
