step/document.go

124 lines
3.4 KiB
Go

package step
import "os"
import "io"
import "time"
import "strings"
import "path/filepath"
import "html/template"
// Document represents a STEP file.
type Document struct {
Author string
Title string
Extends string
Meta Meta
// WORM:
environment *Environment
name string
parseTime time.Time
template *template.Template
}
// Execute executes this document and writes the result to the output. This
// method is safe for concurrent use by multiple goroutines.
func (this *Document) Execute (output io.Writer, data ExecutionData) error {
if this.Extends == "" {
// no parent
return this.template.Execute(output, data)
}
// execute into string builder
outputBuilder := strings.Builder { }
// 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.LoadRelative(this.Extends, this)
if err != nil { return err }
return parent.Execute(output, ExecutionData {
Data: data.Data,
Child: &ExecutionResult {
Author: this.Author,
Title: this.Title,
Extends: this.Extends,
Meta: this.Meta,
Body: template.HTML(outputBuilder.String()),
},
})
}
// Environment returns the environment this document is in.
func (this *Document) Environment () *Environment {
return this.environment
}
// Abs returns an absolute representation of the given path. If the path is an
// absolute path already, it is returned as-is. If the path is a relative path,
// it is treated as relative to the current working directory. If the path is a
// relative path beginning with "." or "..", it is treated as relative to this
// document. The result is cleaned.
func (this *Document) Abs (name string) (string, error) {
if filepath.IsAbs(name) {
return filepath.Clean(name), nil
}
if strings.HasPrefix(name, ".") {
directory := this.dir()
return filepath.Abs(filepath.Join(directory, name))
}
return filepath.Abs(name)
}
// Rel returns the given path relative to the current working directory. As a
// special case, any relative path beginning with "." or ".." is treated as
// being relative to this document before it is made relative to the current
// working directory. The result is cleaned.
func (this *Document) Rel (name string) (string, error) {
if filepath.IsAbs(name) {
cwd, err := os.Getwd()
if err != nil { return "", err }
return filepath.Rel(cwd, name)
}
if strings.HasPrefix(name, ".") {
directory := this.dir()
return filepath.Join(directory, name), nil
}
return name, nil
}
// Name returns the document's name, which is a path relative to its
// environment.
func (this *Document) Name () string {
return this.name
}
func (this *Document) dir () string {
directory := this.name
ext := filepath.Ext(directory)
if ext != "" {
directory = filepath.Dir(directory)
}
return directory
}
// ExecutionData is data made available to documents as they are being exeucted.
type ExecutionData struct {
Data any // Custom data
Child *ExecutionResult // The child document's result, if applicable
}
// ExecutionResult is the result of executing a document.
type ExecutionResult struct {
Author string
Title string
Extends string
Meta Meta
Body template.HTML
}