Things I did while unable to commit
- Log rotation - Execution cancellation - HTTP redirect, error functions - Changed naming of document parsing/loading functions
This commit is contained in:
parent
f112a2e564
commit
bf668b0cf7
@ -15,6 +15,7 @@ import "git.tebibyte.media/sashakoshka/go-cli"
|
||||
import "git.tebibyte.media/sashakoshka/step/providers"
|
||||
import "git.tebibyte.media/sashakoshka/goutil/container"
|
||||
import "git.tebibyte.media/sashakoshka/go-service/daemon"
|
||||
import "git.tebibyte.media/sashakoshka/go-service/rotate"
|
||||
import stephttp"git.tebibyte.media/sashakoshka/step/http"
|
||||
import "git.tebibyte.media/sashakoshka/go-service/routines"
|
||||
|
||||
@ -26,6 +27,10 @@ func main () {
|
||||
'p', "pid-file",
|
||||
"Write the PID to the specified file.",
|
||||
"", cli.ValString)
|
||||
flagLogDirectory := cli.NewInputFlag (
|
||||
'l', "log-directory",
|
||||
"Write logs to the specified directory.",
|
||||
"", cli.ValString)
|
||||
flagHTTPAddress := cli.NewInputFlag (
|
||||
'h', "http-address",
|
||||
"The address to host the HTTP server on.",
|
||||
@ -47,6 +52,7 @@ func main () {
|
||||
cmd := cli.New (
|
||||
"Run an HTTP server that automaticaly executes STEP files.",
|
||||
flagPidFile,
|
||||
flagLogDirectory,
|
||||
flagHTTPAddress,
|
||||
flagHTTPErrorDocument,
|
||||
flagHTTPDirectoryDocument,
|
||||
@ -68,7 +74,17 @@ func main () {
|
||||
pluginPath[index], _ = filepath.Abs(pat)
|
||||
}
|
||||
|
||||
// log header for telling apart separate program runs
|
||||
// set up logging
|
||||
if flagLogDirectory.Value != "" {
|
||||
directory := flagLogDirectory.Value
|
||||
log.Println("(i) logging to", directory)
|
||||
directory, err := filepath.Abs(directory)
|
||||
if err != nil { log.Fatalln("XXX", err) }
|
||||
logger, err := rotate.New(directory)
|
||||
if err != nil { log.Fatalln("XXX", err) }
|
||||
defer logger.Close()
|
||||
log.SetOutput(logger)
|
||||
}
|
||||
log.Println(`==========| STEP |===========`)
|
||||
log.Println(`Scriptable Template Processor`)
|
||||
log.Println(`... initializing`)
|
||||
|
@ -34,10 +34,12 @@ func (this *Document) Execute (output io.Writer, data ExecutionData) error {
|
||||
// TODO catch template errors here and offset their row number by the
|
||||
// number of rows taken up by the front matter
|
||||
err := this.template.Execute(&outputBuilder, data)
|
||||
// ignore ErrExecutionCanceled, because it's meant to gracefully stop
|
||||
// the template execution
|
||||
if err != nil { return err }
|
||||
|
||||
// execute parent with this document's result
|
||||
parent, err := this.environment.ParseRelative(this.Extends, this)
|
||||
parent, err := this.environment.LoadRelative(this.Extends, this)
|
||||
if err != nil { return err }
|
||||
return parent.Execute(output, ExecutionData {
|
||||
Data: data.Data,
|
||||
|
@ -57,17 +57,17 @@ func (this *Environment) Init (ctx context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// Parse parses the named document and returns it. The name is treated as a file
|
||||
// Load loads the named document and returns it. The name is treated as a file
|
||||
// path relative to the current working directory. Documents are cached into the
|
||||
// environment once parsed, and are returned on subsequent calls that have the
|
||||
// environment once loaded, and are returned on subsequent calls that have the
|
||||
// same name.
|
||||
func (this *Environment) Parse (name string) (*Document, error) {
|
||||
return this.ParseRelative(name, nil)
|
||||
func (this *Environment) Load (name string) (*Document, error) {
|
||||
return this.LoadRelative(name, nil)
|
||||
}
|
||||
|
||||
// ParseRelative is like Parse, but treats the name as a path relative to the
|
||||
// LoadRelative is like Load, but treats the name as a path relative to the
|
||||
// given document.
|
||||
func (this *Environment) ParseRelative (name string, document *Document) (*Document, error) {
|
||||
func (this *Environment) LoadRelative (name string, document *Document) (*Document, error) {
|
||||
if document == nil {
|
||||
name = filepath.Clean(name)
|
||||
} else {
|
||||
@ -86,14 +86,14 @@ func (this *Environment) ParseRelative (name string, document *Document) (*Docum
|
||||
defer input.Close()
|
||||
info, err := input.Stat()
|
||||
if err != nil { return nil, err }
|
||||
return this.parse(name, info.ModTime(), input)
|
||||
return this.load(name, info.ModTime(), input)
|
||||
}
|
||||
|
||||
// ParseReader is like Parse, but parses a reader and just takes your word for
|
||||
// it that it corresponds to the file of the given name. It always re-parses.
|
||||
func (this *Environment) ParseReader (name string, input io.Reader) (*Document, error) {
|
||||
// Parse is like Load, but parses a reader and just takes your word for it that
|
||||
// it corresponds to the file of the given name. It always re-parses.
|
||||
func (this *Environment) Parse (name string, input io.Reader) (*Document, error) {
|
||||
name = filepath.Clean(name)
|
||||
return this.parse(name, time.Now(), input)
|
||||
return this.load(name, time.Now(), input)
|
||||
}
|
||||
|
||||
// Unload removes a named document. It will be reloaded if necessary.
|
||||
@ -103,7 +103,7 @@ func (this *Environment) Unload (name string) {
|
||||
delete(documents, name)
|
||||
}
|
||||
|
||||
func (this *Environment) parse (name string, modTime time.Time, input io.Reader) (*Document, error) {
|
||||
func (this *Environment) load (name string, modTime time.Time, input io.Reader) (*Document, error) {
|
||||
documents, done := this.documents.Borrow()
|
||||
defer done()
|
||||
|
||||
|
1
error.go
1
error.go
@ -3,6 +3,7 @@ package step
|
||||
// Error enumerates errors common to this package.
|
||||
type Error string; const (
|
||||
ErrCircularInheritance Error = "circular inheritance"
|
||||
ErrExecutionCanceled Error = "execution canceled"
|
||||
ErrMetaMalformed Error = "metadata is malformed"
|
||||
ErrMetaNeverClosed Error = "metadata is never closed"
|
||||
ErrTypeMismatch Error = "type mismatch"
|
||||
|
@ -4,6 +4,7 @@ import "log"
|
||||
import "fmt"
|
||||
import "path"
|
||||
import "io/fs"
|
||||
import "errors"
|
||||
import "strings"
|
||||
import "strconv"
|
||||
import "net/http"
|
||||
@ -11,11 +12,6 @@ import "path/filepath"
|
||||
import "git.tebibyte.media/sashakoshka/step"
|
||||
import "git.tebibyte.media/sashakoshka/goutil/container"
|
||||
|
||||
type ErrorData struct {
|
||||
Status int
|
||||
Message any
|
||||
}
|
||||
|
||||
type DirectoryData struct {
|
||||
Name string
|
||||
Entries []fs.DirEntry
|
||||
@ -120,7 +116,7 @@ func (this *Handler) serveDirectory (
|
||||
this.serveFile(res, req, pat)
|
||||
return
|
||||
}
|
||||
document, err := this.Environment.Parse(this.DirectoryDocument)
|
||||
document, err := this.Environment.Load(this.DirectoryDocument)
|
||||
if err != nil {
|
||||
this.serveError(res, req, http.StatusInternalServerError, err, false)
|
||||
return
|
||||
@ -143,7 +139,7 @@ func (this *Handler) serveDocument (
|
||||
name string,
|
||||
) {
|
||||
// parse
|
||||
document, err := this.Environment.Parse(name)
|
||||
document, err := this.Environment.Load(name)
|
||||
if err != nil {
|
||||
this.serveError(res, req, http.StatusInternalServerError, err, false)
|
||||
return
|
||||
@ -177,8 +173,17 @@ func (this *Handler) serveDocument (
|
||||
err = document.Execute(&recorder, step.ExecutionData {
|
||||
Data: data,
|
||||
})
|
||||
if errors.Is(err, step.ErrExecutionCanceled) { err = nil }
|
||||
var httpError Error
|
||||
if errors.As(err, &httpError) {
|
||||
this.serveError (
|
||||
res, req,
|
||||
httpError.Status, httpError.Message, false)
|
||||
}
|
||||
if err != nil {
|
||||
this.serveError(res, req, http.StatusInternalServerError, err, false)
|
||||
this.serveError (
|
||||
res, req,
|
||||
http.StatusInternalServerError, err, false)
|
||||
return
|
||||
}
|
||||
|
||||
@ -209,13 +214,13 @@ func (this *Handler) serveError (
|
||||
return
|
||||
}
|
||||
|
||||
document, err := this.Environment.Parse(this.ErrorDocument)
|
||||
document, err := this.Environment.Load(this.ErrorDocument)
|
||||
if err != nil {
|
||||
this.serveError(res, req, http.StatusInternalServerError, err, true)
|
||||
return
|
||||
}
|
||||
err = document.Execute(res, step.ExecutionData {
|
||||
Data: ErrorData {
|
||||
Data: Error {
|
||||
Status: status,
|
||||
Message: message,
|
||||
},
|
||||
|
18
http/http.go
18
http/http.go
@ -1,9 +1,27 @@
|
||||
package http
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "bytes"
|
||||
import "net/http"
|
||||
|
||||
// Error represents an HTTP error. It is passed to error documents as data.
|
||||
// If returned from a template, the server will show the appropriate error page.
|
||||
type Error struct {
|
||||
Status int
|
||||
Message any
|
||||
}
|
||||
|
||||
func (err Error) Error () string {
|
||||
message := fmt.Sprint(err.Message)
|
||||
text := http.StatusText(err.Status)
|
||||
if message == "" {
|
||||
return fmt.Sprintf("%d: %s", err.Status, text)
|
||||
} else {
|
||||
return fmt.Sprintf("%d: %s: %s", err.Status, text, message)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPData represents information about an ongoing HTTP request that is made
|
||||
// available to templates as they are being executed.
|
||||
type HTTPData struct {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package http
|
||||
|
||||
import "net/http"
|
||||
import "net/url"
|
||||
import "net/http"
|
||||
import "html/template"
|
||||
import "git.tebibyte.media/sashakoshka/step"
|
||||
import shttp "git.tebibyte.media/sashakoshka/step/http"
|
||||
|
||||
var _ step.FuncProvider = new(Provider)
|
||||
|
||||
@ -23,6 +24,8 @@ func (this *Provider) FuncMap () template.FuncMap {
|
||||
"statusText": http.StatusText,
|
||||
"parseQuery": funcParseQuery,
|
||||
"parseForm": funcParseForm,
|
||||
"error": funcError,
|
||||
"redirect": funcRedirect,
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,8 +38,22 @@ func funcParseQuery (query string) url.Values {
|
||||
return values
|
||||
}
|
||||
|
||||
func funcError (status int, message any) (string, error) {
|
||||
return "", shttp.Error {
|
||||
Status: status,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func funcRedirect (res shttp.WrappedResponseWriter, status int, pat string) (string, error) {
|
||||
// TODO remove parameters
|
||||
res.Header.Add("Location", pat)
|
||||
res.WriteHeader(status)
|
||||
return "", step.ErrExecutionCanceled
|
||||
}
|
||||
|
||||
func funcParseForm (req *http.Request) url.Values {
|
||||
// FIXME there is already a parse form method lol this can be removed
|
||||
err := req.ParseForm()
|
||||
if err != nil { return nil }
|
||||
return req.Form
|
||||
|
@ -25,6 +25,9 @@ func (this *Provider) FuncMapFor (document *step.Document) template.FuncMap {
|
||||
}
|
||||
return template.FuncMap {
|
||||
"panic": stat.funcPanic,
|
||||
"cancel": stat.funcCancel,
|
||||
"create": stat.funcCreate,
|
||||
"createHTML": stat.funcCreateHTML,
|
||||
"execute": stat.funcExecute,
|
||||
"include": stat.funcInclude,
|
||||
"includeHTML": stat.funcIncludeHTML,
|
||||
@ -43,10 +46,23 @@ func (this *state) funcPanic (message any) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *state) funcCancel () (string, error) {
|
||||
return "", step.ErrExecutionCanceled
|
||||
}
|
||||
|
||||
func (this *state) funcCreate (name string, arguments ...any) (string, error) {
|
||||
return this.funcInclude(name, arguments)
|
||||
}
|
||||
|
||||
func (this *state) funcCreateHTML (name string, arguments ...any) (template.HTML, error) {
|
||||
return this.funcIncludeHTML(name, arguments)
|
||||
}
|
||||
|
||||
func (this *state) funcExecute (name string, data any) (step.ExecutionResult, error) {
|
||||
name, err := this.document.Rel(name)
|
||||
if err != nil { return step.ExecutionResult { }, err }
|
||||
document, err := this.document.Environment().Parse(name)
|
||||
document, err := this.document.Environment().LoadRelative (
|
||||
name, this.document)
|
||||
if err != nil { return step.ExecutionResult { }, err }
|
||||
builder := strings.Builder { }
|
||||
err = document.Execute(&builder, step.ExecutionData { Data: data })
|
||||
|
Loading…
Reference in New Issue
Block a user