package tht import ( //"github.com/d5/tengo/v2" "surdeus.su/util/tht/mdx" //"surdeus.su/core/xgo/xmodules/htmlx" "surdeus.su/core/xgo/xmodules/httpx" "surdeus.su/core/xgo/xmodules" "surdeus.su/util/tpp" "path/filepath" "net/http" "context" "path" "mime" "log" "os" "io" ) var _ = http.Handler(&Handler{}) // The type describes behaviour // of handling stuff like in PHP. type Handler struct { // THe field represents // where we store site's // source files and request handlers. sourcePath string // Preprocessor must be set by user // to be able to bring custom features in. pp *tpp.Preprocessor // Aditional extension. ".tpp" by default. // For example "file.html.tpp" will be // first preprocessed TPP and sent back as simple HTML. ext string // The global map accessable from all the // request so you can keep the states // between requests. global any md *mdx.Markdown indexSuffix string } // Returns the new Handler with // specified preprocessor, source path, // preprocessor extension and the global value. func NewHandler( pp *tpp.Preprocessor, src, ext, index string, global any, ) *Handler { ret := &Handler{} ret.sourcePath = src ret.ext = ext ret.pp = pp ret.global = global ret.indexSuffix = index return ret } // Returns the default tpp.Preprocessor suitable for // the most needs. func DefaultPP(mod string) *tpp.Preprocessor { t := tpp.NewTengo().SetPreCompile(func( ctx context.Context, s *tpp.Script, ) { s.SetImportDir(mod) s.SetImports(xmodules.Modules) s.EnableFileImport(true) s.Add("__http_request__", ctx.Value(KeyRequest)) s.Add("__global__", ctx.Value(KeyGlobal)) s.Add("__markdown__", ctx.Value(KeyMarkdown)) }).SetPreCode(func(ctx context.Context) []byte { return []byte(` markdown := func(...args) { pp.write_raw(__markdown__(args...)) } __http__ := immutable({ request : __http_request__ }) context.http = __http__ context.pp = pp context.global = __global__ import("./pre")(context) `) }).SetPostCode(func(ctx context.Context) []byte { return []byte(` import("./post")(context) `) }) return tpp.New(t) } func (h *Handler) SetMD(md *mdx.Markdown) *Handler { h.md = md return h } func (h *Handler) ServeHTTP( w http.ResponseWriter, r *http.Request, ) { shouldProcess := true urlPath := r.URL.Path // Cleaning URL path to prevent injections. urlPath = path.Clean(urlPath) urlExt := path.Ext(urlPath) if urlExt == h.ext { http.NotFound(w, r) return } filePath := filepath.Join( filepath.FromSlash(h.sourcePath), filepath.FromSlash(urlPath), ) fileStat, err := os.Stat(filePath) if err == nil { if fileStat.IsDir() { filePath = filepath.Join( filePath, h.indexSuffix, ) } } filePathTpp := filePath + h.ext //log.Println("pth:", urlPath, filePathTpp) file, err := os.Open(filePathTpp) if err != nil { shouldProcess = false file, err = os.Open(filePath) if err != nil { http.NotFound(w, r) return } } //process := true fileData, err := io.ReadAll(file) if err != nil { http.NotFound(w, r) return } ctx := context.WithValue( r.Context(), KeyRequest, &httpx.Request{ Request: r, }, ) ctx = context.WithValue( ctx, KeyGlobal, h.global, ) ctx = context.WithValue( ctx, KeyMarkdown, h.md, ) // Setting before the code to let it change own // content type? fileExt := filepath.Ext(filePath) contentType := mime.TypeByExtension(fileExt) w.Header().Set("Content-Type", contentType) processedData := fileData if shouldProcess { processedData, err = h.pp.Process( ctx, true, filePathTpp, fileData, ) if err != nil { http.NotFound(w, r) log.Printf( "Error: pp.Process(...): %s\n", err, ) return } } w.Write(processedData) }