init
This commit is contained in:
commit
5db9429bd8
22 changed files with 657 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/tpp
|
||||
/exe
|
||||
Session.vim
|
4
build.sh
Executable file
4
build.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
go build -o ./exe/ ./cmd/tht
|
||||
|
10
cmd/tht/main.go
Normal file
10
cmd/tht/main.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"surdeus.su/util/tht/server"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server.Tool.Run(os.Args[1:])
|
||||
}
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
module surdeus.su/util/tht
|
||||
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/d5/tengo/v2 v2.17.0
|
||||
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2
|
||||
surdeus.su/core/cli v0.1.2
|
||||
surdeus.su/core/xgo v0.5.0
|
||||
surdeus.su/util/tpp v0.2.0
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -0,0 +1,10 @@
|
|||
github.com/d5/tengo/v2 v2.17.0 h1:BWUN9NoJzw48jZKiYDXDIF3QrIVZRm1uV1gTzeZ2lqM=
|
||||
github.com/d5/tengo/v2 v2.17.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM=
|
||||
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
surdeus.su/core/cli v0.1.2 h1:qPzjawqPyZsO4Z5SaA1u141recVE65yioA83Qs7Jecs=
|
||||
surdeus.su/core/cli v0.1.2/go.mod h1:r9JtQz3aEJzpYzMaNUNQHJoYkoWKNPi047qhd5uGlmA=
|
||||
surdeus.su/core/xgo v0.5.0 h1:/Rk3scfFkoSb0qjHRlkUNOp9sr/fd7wAvCiT4fBRo+U=
|
||||
surdeus.su/core/xgo v0.5.0/go.mod h1:6C/AHbjfvAMvt3TOzLB4eIZ40eU3ahJXtdY+kr4yXoc=
|
||||
surdeus.su/util/tpp v0.2.0 h1:9N78JPEUovDWdKnrdfrf05Jkz5xMA5hcBEWCIh4RWW0=
|
||||
surdeus.su/util/tpp v0.2.0/go.mod h1:3bcSjGNd3vAevr6gPMI1/qJofxL7x53v0yB+Dr18CRM=
|
3
install.sh
Executable file
3
install.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
go install ./cmd/tht/
|
83
mdx/main.go
Normal file
83
mdx/main.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package mdx
|
||||
import "github.com/gomarkdown/markdown"
|
||||
import "github.com/gomarkdown/markdown/html"
|
||||
import "github.com/gomarkdown/markdown/parser"
|
||||
import "github.com/d5/tengo/v2"
|
||||
import "bytes"
|
||||
|
||||
type Markdown struct {
|
||||
tengo.ObjectImpl
|
||||
MakeParser func() *parser.Parser
|
||||
MakeRenderer func() *html.Renderer
|
||||
}
|
||||
|
||||
func MakeDefaultMarkdown() *Markdown {
|
||||
return &Markdown{
|
||||
MakeParser: MakeDefaultParser,
|
||||
MakeRenderer: MakeDefaultRenderer,
|
||||
}
|
||||
}
|
||||
|
||||
func (md *Markdown) TypeName() string {
|
||||
return "*Markdown"
|
||||
}
|
||||
|
||||
func (md *Markdown) String() string {
|
||||
if md == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return "&Markdown{...}"
|
||||
}
|
||||
|
||||
func (md *Markdown) Render(
|
||||
data []byte,
|
||||
) ([]byte, error) {
|
||||
doc := md.MakeParser().Parse(data)
|
||||
rendered := markdown.Render(doc, md.MakeRenderer())
|
||||
return rendered, nil
|
||||
}
|
||||
|
||||
func (md *Markdown) CanCall() bool {
|
||||
return md != nil
|
||||
}
|
||||
|
||||
func (md *Markdown) Call(
|
||||
args ...tengo.Object,
|
||||
) (tengo.Object, error) {
|
||||
var b bytes.Buffer
|
||||
for _, arg := range args {
|
||||
str, ok := tengo.ToString(arg)
|
||||
if !ok {
|
||||
return nil, tengo.ErrInvalidArgumentType{
|
||||
Name: "v",
|
||||
Expected: "stringer",
|
||||
Found: arg.TypeName(),
|
||||
}
|
||||
}
|
||||
b.Write([]byte(str))
|
||||
}
|
||||
|
||||
rendered, err := md.Render(b.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tengo.FromInterface(rendered)
|
||||
}
|
||||
|
||||
func MakeDefaultParser() *parser.Parser {
|
||||
return parser.NewWithExtensions(
|
||||
parser.CommonExtensions | parser.AutoHeadingIDs |
|
||||
parser.NoEmptyLineBeforeBlock | parser.Attributes |
|
||||
parser.Tables,
|
||||
)
|
||||
}
|
||||
|
||||
func MakeDefaultRenderer() *html.Renderer {
|
||||
return html.NewRenderer(
|
||||
html.RendererOptions{
|
||||
Flags: html.CommonFlags | html.HrefTargetBlank,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
26
mod/post.tengo
Normal file
26
mod/post.tengo
Normal file
|
@ -0,0 +1,26 @@
|
|||
fmt := import("fmt")
|
||||
paths := import("paths")
|
||||
htmExt := func(c){
|
||||
if c.is_compo {
|
||||
return
|
||||
}
|
||||
|
||||
c.pp.print(`</body></html>`)
|
||||
}
|
||||
|
||||
exts := {
|
||||
".htm": htmExt,
|
||||
".html": htmExt
|
||||
}
|
||||
|
||||
export func(c){
|
||||
ext := paths.ext(c.pp.filepath)
|
||||
ext = paths.ext(
|
||||
c.pp.filepath[:len(c.pp.filepath) - len(ext)]
|
||||
)
|
||||
|
||||
cl := exts[ext]
|
||||
if !is_undefined(cl) {
|
||||
cl(c)
|
||||
}
|
||||
}
|
40
mod/pre.tengo
Normal file
40
mod/pre.tengo
Normal file
|
@ -0,0 +1,40 @@
|
|||
fmt := import("fmt")
|
||||
paths := import("paths")
|
||||
html := import("html").new_render()
|
||||
|
||||
htmExt := func(c){
|
||||
if c.is_compo {
|
||||
return
|
||||
}
|
||||
c.pp.print(`<!doctype html><html>`)
|
||||
|
||||
c.pp.print(html.head().body(
|
||||
func(){
|
||||
if c.title {
|
||||
return html.title().body(c.title)
|
||||
}
|
||||
}(),
|
||||
html.script({
|
||||
src: `https://unpkg.com/htmx.org@1.9.2`
|
||||
})
|
||||
))
|
||||
|
||||
c.pp.print(`<body>`)
|
||||
}
|
||||
|
||||
exts := {
|
||||
".htm": htmExt,
|
||||
".html": htmExt
|
||||
}
|
||||
|
||||
export func(c){
|
||||
ext := paths.ext(c.pp.filepath)
|
||||
ext = paths.ext(
|
||||
c.pp.filepath[:len(c.pp.filepath) - len(ext)]
|
||||
)
|
||||
|
||||
cl := exts[ext]
|
||||
if cl {
|
||||
cl(c)
|
||||
}
|
||||
}
|
189
server/handler.go
Normal file
189
server/handler.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
//"github.com/d5/tengo/v2"
|
||||
"surdeus.su/util/tpp/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)
|
||||
|
||||
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?
|
||||
contentType := mime.TypeByExtension(urlExt)
|
||||
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)
|
||||
}
|
53
server/tool.go
Normal file
53
server/tool.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
|
||||
"github.com/d5/tengo/v2"
|
||||
//"github.com/d5/tengo/v2/stdlib"
|
||||
"surdeus.su/util/tpp/mdx"
|
||||
"surdeus.su/core/cli/mtool"
|
||||
"net/http"
|
||||
"log"
|
||||
//"context"
|
||||
)
|
||||
|
||||
// Context key type for internal usage.
|
||||
type CKey string
|
||||
|
||||
const (
|
||||
KeyRequest CKey = "http-request"
|
||||
KeyGlobal = "global"
|
||||
KeyMarkdown = "markdown"
|
||||
KeyHTML = "html"
|
||||
)
|
||||
|
||||
// Simple PHP-like server implementation.
|
||||
var Tool = mtool.T("tht").Func(func(flags *mtool.Flags) {
|
||||
var (
|
||||
addr, ext, src, mod, index string
|
||||
)
|
||||
|
||||
flags.StringVar(&addr, "addr", ":3000", "address to serve at")
|
||||
flags.StringVar(&mod, "mod", "./mod", "path to store Tengo modules")
|
||||
flags.StringVar(&src, "src", "./src", "directory with source files")
|
||||
flags.StringVar(&ext, "ext", ".tpp", "extension for TPP files")
|
||||
flags.StringVar(&index, "index", "index.htm", "index file name")
|
||||
|
||||
flags.Parse()
|
||||
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: NewHandler(
|
||||
DefaultPP(mod),
|
||||
src, ext, index,
|
||||
map[string] tengo.Object{},
|
||||
).SetMD(mdx.MakeDefaultMarkdown()),
|
||||
}
|
||||
|
||||
log.Printf("Listening on %q\n", addr)
|
||||
err := srv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Printf("Error: srv.ListenAndServe(...): %s\n", err)
|
||||
}
|
||||
})
|
5
src/check.htm.tpp
Normal file
5
src/check.htm.tpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
<main>shit</main>
|
||||
{{
|
||||
pp.print("cock")
|
||||
}}
|
||||
<main>later</main>
|
12
src/check/index.htm.tpp
Normal file
12
src/check/index.htm.tpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
{{#
|
||||
html := import("html").new_render()
|
||||
}}
|
||||
{{
|
||||
h := html.main().body(
|
||||
html.p().body(
|
||||
"some paragraph shit"
|
||||
)
|
||||
)
|
||||
|
||||
pp.print(h)
|
||||
}}
|
20
src/contact/edit.htm.tpp
Normal file
20
src/contact/edit.htm.tpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
{{#
|
||||
context.is_compo = true
|
||||
}}
|
||||
|
||||
<form hx-put="/contact/1" hx-target="this" hx-swap="outerHTML">
|
||||
<div>
|
||||
<label>First Name</label>
|
||||
<input type="text" name="firstName" value="Joe">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" name="lastName" value="Blow">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" name="email" value="joe@blow.com">
|
||||
</div>
|
||||
<button class="btn">Submit</button>
|
||||
<button class="btn" hx-get="/contact/view.htm">Cancel</button>
|
||||
</form>
|
13
src/contact/view.htm.tpp
Normal file
13
src/contact/view.htm.tpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
{{#
|
||||
//context.is_compo = true
|
||||
}}
|
||||
|
||||
<div hx-target="this" hx-swap="outerHTML">
|
||||
<div><label>First Name</label>: Joe</div>
|
||||
<div><label>Last Name</label>: Blow</div>
|
||||
<div><label>Email</label>: joe@blow.com</div>
|
||||
<button hx-get="/contact/edit.htm" class="btn btn-primary">
|
||||
Click To Edit
|
||||
</button>
|
||||
</div>
|
||||
|
11
src/incorrect.htm.tpp
Normal file
11
src/incorrect.htm.tpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
html := import("html").new_render()
|
||||
|
||||
pp.print(html.html().body(
|
||||
html.head().body(
|
||||
html.title().body(
|
||||
"some title"
|
||||
)
|
||||
),
|
||||
html.body().body(
|
||||
)
|
||||
))
|
78
src/index.htm.tpp
Normal file
78
src/index.htm.tpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
{{#
|
||||
context.title = "The example.site main page"
|
||||
|
||||
fmt := import("fmt")
|
||||
html := import("html").new_render()
|
||||
}}
|
||||
|
||||
<h1>This is the example page for the THT</h1>
|
||||
{{
|
||||
/*if !context.global.int {
|
||||
context.global.int = 0
|
||||
} else {
|
||||
context.global.int++
|
||||
}*/
|
||||
|
||||
if !context.global.val {
|
||||
context.global.val = 1
|
||||
} else {
|
||||
context.global.val += 1
|
||||
}
|
||||
fmt.println("global: ", context.global)
|
||||
req := context.http.request
|
||||
q := req.url.query
|
||||
// List checking.
|
||||
list := []
|
||||
rng := int(q.range[0])
|
||||
fmt.println("range:", q.range)
|
||||
if rng {
|
||||
pp.print(`<ul name="range">`)
|
||||
for i:=0 ; i < rng ; i++ {
|
||||
pp.printf("<li>%d</li>", i)
|
||||
}
|
||||
pp.print("</ul>")
|
||||
}
|
||||
if q.name {
|
||||
pp.print("<div id=\"name\">", q.name[0], "</div>")
|
||||
}
|
||||
}}
|
||||
<div name="counter">
|
||||
{{ pp.print(context.global.val) }}
|
||||
</div>
|
||||
<div>
|
||||
Hello, Cock!
|
||||
</div>
|
||||
<div check>
|
||||
{{
|
||||
pp.print(req.url.path)
|
||||
}}
|
||||
</div>
|
||||
<ul>{{
|
||||
for v in list {
|
||||
pp.print("<li>", v, "</li>")
|
||||
}
|
||||
}}</ul>
|
||||
|
||||
{{
|
||||
vals := ["die", "with", "them", "as", "you", 1, "could", "do"]
|
||||
pp.print(html.div({
|
||||
id: "the-uniq-shit"
|
||||
}).body(
|
||||
html.ul(
|
||||
).body(
|
||||
func(){
|
||||
ret := []
|
||||
for i:=0 ; i<len(vals) ; i++{
|
||||
ret += [html.li({
|
||||
class: "someclass"
|
||||
}).body(i, ": ", vals[i])]
|
||||
}
|
||||
return ret
|
||||
}()...
|
||||
)
|
||||
))
|
||||
}}
|
||||
|
||||
<script src="/script.js" ></script>
|
||||
|
||||
<div></div>
|
37
src/main.htm.tpp
Normal file
37
src/main.htm.tpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
{{#
|
||||
context.title = "The example.site main page"
|
||||
|
||||
fmt := import("fmt")
|
||||
html := import("html").new_render()
|
||||
}}
|
||||
|
||||
{{
|
||||
pp.print(
|
||||
html.h1().body("This is the example page for THT")
|
||||
)
|
||||
|
||||
if !context.global.val {
|
||||
context.global.val = 1
|
||||
} else {
|
||||
context.global.val += 1
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
{{
|
||||
req := context.http.request
|
||||
q := req.url.query
|
||||
// List checking.
|
||||
rng := int(q.range[0])
|
||||
fmt.println("range:", q.range)
|
||||
if rng {
|
||||
pp.print(`<ul name="range">`)
|
||||
for i:=0 ; i < rng ; i++ {
|
||||
pp.printf("<li>%d</li>", i)
|
||||
}
|
||||
pp.print("</ul>")
|
||||
}
|
||||
if q.name {
|
||||
pp.print("<div id=\"name\">", q.name[0], "</div>")
|
||||
}
|
||||
}}
|
35
src/page.htm.tpp
Normal file
35
src/page.htm.tpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
{{#
|
||||
context.title = "Let's celebrate and suck, SOME DICK"
|
||||
}}
|
||||
|
||||
{{markdown(`
|
||||
|
||||
# Hello, World!
|
||||
|
||||
This is the markdown example shit.
|
||||
|
||||
func main() {
|
||||
print("Fuck you")
|
||||
}
|
||||
|
||||
## Yet another level of header
|
||||
|
||||
And even more text
|
||||
|
||||
> Cheap is talk, show me the code.
|
||||
|
||||
(c) Linus Torvalds
|
||||
|
||||
`)}}
|
||||
|
||||
<div id="uniq">
|
||||
Even <strong>more</strong> HTML
|
||||
</div>
|
||||
|
||||
{{markdown(`
|
||||
### Third level header
|
||||
|
||||
Some shit
|
||||
|
||||
#### `, 135, `
|
||||
`)}}
|
8
src/req.json.tpp
Normal file
8
src/req.json.tpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"name": "Andrey",
|
||||
"surname": "Parhomenko",
|
||||
"age": 22
|
||||
}
|
||||
}
|
3
src/request.json
Normal file
3
src/request.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name":"Andrew"
|
||||
}
|
3
src/script.js
Normal file
3
src/script.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
console.log("Hello, World!")
|
||||
|
Loading…
Reference in a new issue