~reesmichael1/chantpointer

2865980869dce3c99048a61a3dbba2816c4a10da — Michael Rees 3 years ago 25adbf8
Save user input between runs
6 files changed, 167 insertions(+), 39 deletions(-)

M handlers.go
M index.html
M psalm.tem
A saved.go
A saved_test.go
M writer_test.go
M handlers.go => handlers.go +39 -4
@@ 15,6 15,8 @@ import (
	"path/filepath"
)

var savedName = "chant.json"

// ErrorHandler handles any errors that occur in the other endpoints
// and nicely displays them to the user
type ErrorHandler func(http.ResponseWriter, *http.Request) error


@@ 24,6 26,7 @@ var templates = template.Must(template.ParseFiles("index.html", "error.html"))
// Page holds the link to the generated PDF, if it exists
type Page struct {
	URL string
	Saved
}

// ErrorPage holds an error message to show to the user


@@ 52,7 55,7 @@ func GenerateHandler(w http.ResponseWriter, r *http.Request) error {
	}

	var buf bytes.Buffer
	file, _, err := r.FormFile("score")
	file, header, err := r.FormFile("score")
	if err != nil {
		return err
	}


@@ 93,7 96,14 @@ func GenerateHandler(w http.ResponseWriter, r *http.Request) error {
	texPath := filepath.Join(dir, "psalm.tex")
	texFile, err := os.OpenFile(texPath, os.O_CREATE|os.O_RDWR, 0644)
	defer texFile.Close()
	if err != nil {
		return err
	}

	err = WriteTexForChant(texFile, &c)
	if err != nil {
		return err
	}

	cmd := exec.Command("pdflatex", texPath)
	cmd.Stdout = &buf


@@ 118,6 128,20 @@ func GenerateHandler(w http.ResponseWriter, r *http.Request) error {
		return err
	}

	savePath := filepath.Join(dir, savedName)
	saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_RDWR, 0644)
	defer saveFile.Close()
	if err != nil {
		return err
	}

	err = SaveInput(
		saveFile, r.FormValue("title"), r.FormValue("subtitle"),
		r.FormValue("chant"), header.Filename, chantPath)
	if err != nil {
		return err
	}

	queryString := u.Query()
	queryString.Set("id", dir[len("/tmp/chant"):])
	u.RawQuery = queryString.Encode()


@@ 125,7 149,7 @@ func GenerateHandler(w http.ResponseWriter, r *http.Request) error {
	baseURL := "/"
	base, err := url.Parse(baseURL)
	if err != nil {
		log.Fatal(err)
		return err
	}

	http.Redirect(w, r, fmt.Sprint(base.ResolveReference(u)), http.StatusFound)


@@ 136,8 160,19 @@ func GenerateHandler(w http.ResponseWriter, r *http.Request) error {
func IndexHandler(w http.ResponseWriter, r *http.Request) error {
	chant := r.URL.Query().Get("id")
	if chant != "" {
		chant = fmt.Sprintf("/chant/%s", chant)
		return templates.ExecuteTemplate(w, "index.html", Page{chant})
		chantDir := fmt.Sprintf("/tmp/chant%s", chant)
		chantURL := fmt.Sprintf("/chant/%s", chant)
		savedPath := filepath.Join(chantDir, savedName)
		savedFile, err := os.Open(savedPath)
		defer savedFile.Close()
		if err != nil {
			return err
		}
		saved := LoadSaved(savedFile)
		return templates.ExecuteTemplate(w, "index.html", Page{
			URL:   chantURL,
			Saved: saved,
		})
	}
	return templates.ExecuteTemplate(w, "index.html", Page{})
}

M index.html => index.html +11 -6
@@ 23,28 23,33 @@ html {
                <div class="field">
                    <label class="label">Chant Score</label>
                    <div class="control">
                        <input class="input" type="file" name="score" accept="image/*" required>
                        <input class="input" type="file" name="score" accept="image/*" 
                          {{ if ne .ScorePath "" }} value="{{ .ScorePath }}" {{ end }} required>
                    </div>
                </div>

                <div class="field">
                    <label class="label">Title</label>
                    <div class="control">
                        <input class="input" name="title" type="text" placeholder="Title">
                        <input class="input" name="title" type="text" placeholder="Title" 
                          {{ if ne .Title "" }} value="{{ .Title }}" {{ end }}
                        >
                    </div>
                </div>

                <div class="field">
                    <label class="label">Subtitle</label>
                    <div class="control">
                        <input class="input" name="subtitle" type="text" placeholder="Subtitle">
                        <input class="input" name="subtitle" type="text" placeholder="Subtitle"
                          {{ if ne .Subtitle "" }} value="{{ .Subtitle }}" {{ end }}
                        >
                    </div>
                </div>

                <div class="field">
                    <label class="label">Chant</label>
                    <div class="control">
                        <textarea name="chant" class="textarea" required></textarea>
                        <textarea name="chant" class="textarea" required>{{ if ne .Chant "" }}{{ .Chant }}{{ end }}</textarea>
                    </div>
                </div>



@@ 58,8 63,8 @@ html {
                <div class="field">
                    <div class="control">
                        <a href="{{ .URL }}">Download PDF</a>
                        <p>This link will work and may be shared for the next 24 hours.</p>
                        <p>(If you need to edit your results, you should be able to go back and make any changes.)</P>
                        <p>This link will work and may be shared for at least the next 24 hours.</p>
                        <p>(If you would like to make any changes, you will need to re-select the chant score.)</p>
                    </div>
                </div>
                {{ end }}

M psalm.tem => psalm.tem +1 -1
@@ 12,7 12,7 @@
\begin{center}
    {\Large \textbf{(( .Title ))}}\\[12pt]
    \textit{((.Subtitle ))}\\[30pt]
    \includegraphics[width=\textwidth]{(( .ScorePath ))}
    (( if ne .ScorePath "" ))\includegraphics[width=\textwidth]{(( .ScorePath ))}(( end ))
\end{center}
\vspace{24pt}


A saved.go => saved.go +49 -0
@@ 0,0 1,49 @@
package main

import (
	"bytes"
	"encoding/json"
	"io"
)

// Saved holds the user input from a previous run
type Saved struct {
	ScorePath string
	ScoreName string
	Chant     string
	Title     string
	Subtitle  string
}

// LoadSaved loads the saved chant from a given io.Reader
func LoadSaved(chant io.Reader) Saved {
	var savedMap map[string]string
	buf := bytes.NewBuffer([]byte{})
	buf.ReadFrom(chant)
	json.Unmarshal(buf.Bytes(), &savedMap)
	return Saved{
		Title:     savedMap["title"],
		Subtitle:  savedMap["subtitle"],
		ScorePath: savedMap["score"],
		ScoreName: savedMap["scoreName"],
		Chant:     savedMap["chant"],
	}
}

// SaveInput saves the submitted input to a given io.Writer
func SaveInput(f io.Writer, title, subtitle, chant, scoreName, scorePath string) error {
	jsMap := map[string]string{
		"title":     title,
		"subtitle":  subtitle,
		"chant":     chant,
		"scoreName": scoreName,
		"score":     scorePath,
	}
	js, err := json.Marshal(jsMap)
	if err != nil {
		return err
	}

	_, err = f.Write(js)
	return err
}

A saved_test.go => saved_test.go +39 -0
@@ 0,0 1,39 @@
package main

import (
	"bytes"
	"testing"
)

var savedJSON = `{"chant":"line 1\nline 2\n\nab(c 1)23\nword | \"word\"","score":"/tmp/abc/chant.png","scoreName":"fake_name.png","subtitle":"Psalm Subtitle","title":"Psalm Title"}`

func TestSaveChant(t *testing.T) {
	buf := bytes.NewBuffer([]byte{})

	SaveInput(buf, "Psalm Title", "Psalm Subtitle", `line 1
line 2

ab(c 1)23
word | "word"`, "fake_name.png", "/tmp/abc/chant.png")

	equals(t, savedJSON, string(buf.Bytes()))
}

func TestLoadSavedChant(t *testing.T) {
	buf := bytes.NewBufferString(savedJSON)
	s := LoadSaved(buf)

	expected := Saved{
		Chant: `line 1
line 2

ab(c 1)23
word | "word"`,
		Title:     "Psalm Title",
		Subtitle:  "Psalm Subtitle",
		ScorePath: "/tmp/abc/chant.png",
		ScoreName: "fake_name.png",
	}

	equals(t, expected, s)
}

M writer_test.go => writer_test.go +28 -28
@@ 5,33 5,7 @@ import (
	"testing"
)

func TestWriteVerses(t *testing.T) {
	buf := bytes.NewBuffer([]byte{})

	c := Chant{
		Title:    "Title",
		Subtitle: "Subtitle",
		Verses: []Verse{
			Verse{
				FirstPart:  "abc",
				SecondPart: "def",
			},

			Verse{
				FirstPart:  "123",
				SecondPart: "456",
			},
			Verse{
				FirstPart:  "ghi",
				SecondPart: "789",
				IsSecond:   true,
			},
		},
		PointSize: 12,
		ScorePath: "/tmp/chant.png",
	}

	expected := `\documentclass[12pt]{article}
var savedTeX = `\documentclass[12pt]{article}

\usepackage[margin=1in]{geometry}
\usepackage[T1]{fontenc}


@@ 73,6 47,32 @@ func TestWriteVerses(t *testing.T) {
\end{document}
`

func TestWriteVerses(t *testing.T) {
	buf := bytes.NewBuffer([]byte{})

	c := Chant{
		Title:    "Title",
		Subtitle: "Subtitle",
		Verses: []Verse{
			Verse{
				FirstPart:  "abc",
				SecondPart: "def",
			},

			Verse{
				FirstPart:  "123",
				SecondPart: "456",
			},
			Verse{
				FirstPart:  "ghi",
				SecondPart: "789",
				IsSecond:   true,
			},
		},
		PointSize: 12,
		ScorePath: "/tmp/chant.png",
	}

	WriteTexForChant(buf, &c)
	equals(t, expected, string(buf.Bytes()))
	equals(t, savedTeX, string(buf.Bytes()))
}