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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user