forked from Botanical/BotanJS
Removed old impl
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# botanres-go
|
||||
# resolver-go
|
||||
|
||||
Go rewrite for the old BotanJS dynamic resource resolver.
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tgckpg/botanres-go/internal/generated"
|
||||
"github.com/tgckpg/botanres-go/internal/resolver"
|
||||
"github.com/tgckpg/resolver-go/internal/closure"
|
||||
"github.com/tgckpg/resolver-go/internal/generated"
|
||||
"github.com/tgckpg/resolver-go/internal/resolver"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -20,13 +23,24 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
h := handler{r: r}
|
||||
h := handler{
|
||||
r: r,
|
||||
closure: closure.NewCompileCache(2),
|
||||
}
|
||||
http.HandleFunc("/", h.index)
|
||||
log.Printf("botan-api listening on %s", *addr)
|
||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||
}
|
||||
|
||||
type handler struct{ r *resolver.Resolver }
|
||||
type handler struct {
|
||||
r *resolver.Resolver
|
||||
closure *closure.CompileCache
|
||||
}
|
||||
|
||||
func hashStrings(parts []string) string {
|
||||
sum := md5.Sum([]byte(strings.Join(parts, "|")))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (h handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
path := strings.Trim(req.URL.Path, "/")
|
||||
@@ -61,7 +75,46 @@ func (h handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println(res.JSFiles)
|
||||
if outMode == resolver.ModeJS {
|
||||
fileHashes := make([]string, 0, len(res.JSFiles))
|
||||
for _, f := range res.JSFiles {
|
||||
fileHashes = append(fileHashes, f.JSHash)
|
||||
}
|
||||
|
||||
hash := hashStrings(fileHashes)
|
||||
if compiled, ok := h.closure.Get(hash); ok {
|
||||
w.Header().Set("Content-Type", "application/javascript")
|
||||
w.Header().Set("X-Botan-Compiled", "hit")
|
||||
w.Write(compiled)
|
||||
return
|
||||
}
|
||||
|
||||
jsFiles := make([]string, 0, len(res.JSFiles))
|
||||
for _, f := range res.JSFiles {
|
||||
jsFiles = append(jsFiles, f.Src)
|
||||
}
|
||||
|
||||
jsExterns, err := h.r.GetExterns(generated.Externs)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.closure.Enqueue(closure.CompileJob{
|
||||
Hash: res.Hash,
|
||||
Mode: "js",
|
||||
ExternSources: jsExterns,
|
||||
JSSources: []closure.SourceInput{
|
||||
{
|
||||
Name: "botanjs-" + res.Hash + ".js",
|
||||
Source: string(res.Content),
|
||||
},
|
||||
},
|
||||
Defines: map[string]any{
|
||||
"DEBUG": false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Compatibility flags:
|
||||
// rjs/rcss/ojs/ocss => content
|
||||
@@ -73,5 +126,7 @@ func (h handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", res.ContentType)
|
||||
w.Header().Set("X-Botan-Compiled", "miss")
|
||||
|
||||
_, _ = w.Write(res.Content)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/tgckpg/botanres-go/internal/classmap"
|
||||
"github.com/tgckpg/resolver-go/internal/classmap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -36,9 +36,9 @@ func main() {
|
||||
|
||||
func render(pkg string, m *classmap.Map) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("// Code generated by botan-gen; DO NOT EDIT.\n")
|
||||
b.WriteString("// Code generated by classmap-gen; DO NOT EDIT.\n")
|
||||
b.WriteString("package " + pkg + "\n\n")
|
||||
b.WriteString("import \"github.com/tgckpg/botanres-go/internal/classmap\"\n\n")
|
||||
b.WriteString("import \"github.com/tgckpg/resolver-go/internal/classmap\"\n\n")
|
||||
b.WriteString("var ClassMap = &classmap.Map{\nSymbols: map[string]classmap.Symbol{\n")
|
||||
for _, s := range classmap.SortedSymbols(m) {
|
||||
fmt.Fprintf(&b, "%q: {Name:%q, Kind:%q, Parent:%q, Imports:%#v, Resource: classmap.Resource{Src:%q, JSHash:%q, CSSHash:%q}},\n",
|
||||
@@ -61,6 +61,6 @@ func dir(path string) string {
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintln(os.Stderr, "botan-gen:", err)
|
||||
fmt.Fprintln(os.Stderr, "classmap-gen:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
src := flag.String("src", "./src", "BotanJS source root")
|
||||
out := flag.String("out", "internal/generated/externs_gen.go", "generated Go output")
|
||||
pkg := flag.String("pkg", "generated", "generated package name")
|
||||
flag.Parse()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(*out), 0o755); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
code, err := render(*pkg, *src)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
fmted, err := format.Source([]byte(code))
|
||||
if err != nil {
|
||||
_, _ = os.Stderr.WriteString(code)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(*out, fmted, 0o644); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func render(pkg string, root string) (string, error) {
|
||||
var b strings.Builder
|
||||
|
||||
root = filepath.Clean(root)
|
||||
|
||||
b.WriteString("// Code generated by externs-gen; DO NOT EDIT.\n")
|
||||
b.WriteString("package " + pkg + "\n\n")
|
||||
b.WriteString("var Externs = []string{\n")
|
||||
|
||||
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if filepath.Ext(path) != ".js" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if filepath.Base(filepath.Dir(path)) != "externs" {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generated data should be slash-normalized even on Windows.
|
||||
rel = filepath.ToSlash(rel)
|
||||
|
||||
fmt.Fprintf(&b, "\t%q,\n", rel)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString("}\n")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintln(os.Stderr, "externs-gen:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -21,10 +21,13 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
GOARCH=$TARGETARCH \
|
||||
go build -trimpath -o /out/botan-api -ldflags='-s -w' ./cmd/botan-api
|
||||
|
||||
RUN mkdir -p /out/tmp && chmod 1777 /out/tmp
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=build /out/botan-api /usr/local/bin/botan-api
|
||||
COPY "botanjs/src" "./src"
|
||||
COPY --from=build /workspace/src "./src"
|
||||
COPY --from=build /out/tmp /tmp
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
ENTRYPOINT ["/usr/local/bin/botan-api", "-src", "./src", "-addr", ":8080"]
|
||||
|
||||
@@ -14,11 +14,15 @@ COPY "botanjs/src" "./src"
|
||||
COPY "resolver-go/cmd" "./cmd"
|
||||
COPY "resolver-go/internal" "./internal"
|
||||
|
||||
RUN go run ./cmd/botan-gen \
|
||||
RUN go run ./cmd/classmap-gen \
|
||||
-src ./src \
|
||||
-out internal/generated/classmap_gen.go
|
||||
|
||||
RUN mkdir /out && cp internal/generated/classmap_gen.go /out
|
||||
RUN go run ./cmd/externs-gen \
|
||||
-src ./src \
|
||||
-out internal/generated/externs_gen.go
|
||||
|
||||
RUN mkdir /out && cp internal/generated/*_gen.go /out
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /out/classmap_gen.go ./
|
||||
COPY --from=build /out/*_gen.go ./
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
module github.com/tgckpg/botanres-go
|
||||
module github.com/tgckpg/resolver-go
|
||||
|
||||
go 1.26
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package closure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewCompileCache(workers int) *CompileCache {
|
||||
c := &CompileCache{
|
||||
client: NewClientFromEnv(),
|
||||
states: make(map[string]CompileState),
|
||||
results: make(map[string][]byte),
|
||||
errors: make(map[string]error),
|
||||
jobs: make(chan CompileJob, 128),
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go c.worker()
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CompileCache) Get(hash string) ([]byte, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.states[hash] != CompileReady {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return c.results[hash], true
|
||||
}
|
||||
|
||||
func (c *CompileCache) Enqueue(job CompileJob) {
|
||||
c.mu.Lock()
|
||||
|
||||
switch c.states[job.Hash] {
|
||||
case CompilePending, CompileReady:
|
||||
c.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
c.states[job.Hash] = CompilePending
|
||||
c.mu.Unlock()
|
||||
|
||||
select {
|
||||
case c.jobs <- job:
|
||||
default:
|
||||
// Queue full. Don't block request path.
|
||||
c.mu.Lock()
|
||||
c.states[job.Hash] = CompileMissing
|
||||
c.errors[job.Hash] = errors.New("compile queue full")
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CompileCache) worker() {
|
||||
for job := range c.jobs {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
|
||||
req := CompileRequest{
|
||||
ExternSources: job.ExternSources,
|
||||
JSSources: job.JSSources,
|
||||
Defines: job.Defines,
|
||||
}
|
||||
|
||||
c.client.DebugPrintCurl(ctx, req)
|
||||
|
||||
out, err := c.client.Compile(ctx, req)
|
||||
cancel()
|
||||
|
||||
c.mu.Lock()
|
||||
if err != nil {
|
||||
c.states[job.Hash] = CompileFailed
|
||||
c.errors[job.Hash] = err
|
||||
} else {
|
||||
c.states[job.Hash] = CompileReady
|
||||
c.results[job.Hash] = out
|
||||
delete(c.errors, job.Hash)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package closure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
endpoint string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
func NewClientFromEnv() *Client {
|
||||
endpoint := os.Getenv("CLOSURE_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = "http://closure-svc:8080/compile"
|
||||
}
|
||||
|
||||
return &Client{
|
||||
endpoint: endpoint,
|
||||
http: &http.Client{
|
||||
Timeout: 70 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Compile(ctx context.Context, reqBody CompileRequest) ([]byte, error) {
|
||||
body, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
c.endpoint,
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
return nil, fmt.Errorf("closure failed: %s: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (c *Client) DebugPrintCurl(ctx context.Context, reqBody CompileRequest) {
|
||||
if os.Getenv("CLOSURE_DEBUG_CURL") == "" {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := json.MarshalIndent(reqBody, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "closure debug: marshal payload failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tmpDir := os.Getenv("CLOSURE_DEBUG_DIR")
|
||||
if tmpDir == "" {
|
||||
tmpDir = os.TempDir()
|
||||
}
|
||||
|
||||
path := filepath.Join(
|
||||
tmpDir,
|
||||
fmt.Sprintf("closure-request-%d.json", time.Now().UnixNano()),
|
||||
)
|
||||
|
||||
if err := os.WriteFile(path, body, 0o600); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "closure debug: write payload failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"closure debug curl:\n curl -v -X POST %s -H 'Content-Type: application/json' --data-binary @%s\n",
|
||||
shellQuote(c.endpoint),
|
||||
shellQuote(path),
|
||||
)
|
||||
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
fmt.Fprintf(os.Stderr, "closure debug deadline: %s\n", deadline.Format(time.RFC3339Nano))
|
||||
}
|
||||
}
|
||||
|
||||
func shellQuote(s string) string {
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package closure
|
||||
|
||||
import "sync"
|
||||
|
||||
type CompileState int
|
||||
|
||||
const (
|
||||
CompileMissing CompileState = iota
|
||||
CompilePending
|
||||
CompileReady
|
||||
CompileFailed
|
||||
)
|
||||
|
||||
type CompileRequest struct {
|
||||
ExternSources []SourceInput `json:"externSources,omitempty"`
|
||||
JSSources []SourceInput `json:"jsSources"`
|
||||
Defines map[string]any `json:"defines,omitempty"`
|
||||
}
|
||||
|
||||
type SourceInput struct {
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type CompileJob struct {
|
||||
Hash string
|
||||
Mode string
|
||||
|
||||
ExternSources []SourceInput
|
||||
JSSources []SourceInput
|
||||
Defines map[string]any
|
||||
}
|
||||
|
||||
type CompileCache struct {
|
||||
client *Client
|
||||
mu sync.Mutex
|
||||
states map[string]CompileState
|
||||
results map[string][]byte
|
||||
errors map[string]error
|
||||
jobs chan CompileJob
|
||||
}
|
||||
@@ -2,5 +2,5 @@ package generated
|
||||
|
||||
const (
|
||||
IMAGE_TAG = "dev"
|
||||
Timestamp = "20260610.201739"
|
||||
Timestamp = "20260611.192429"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by botan-gen; DO NOT EDIT.
|
||||
// Code generated by classmap-gen; DO NOT EDIT.
|
||||
package generated
|
||||
|
||||
import "github.com/tgckpg/botanres-go/internal/classmap"
|
||||
import "github.com/tgckpg/resolver-go/internal/classmap"
|
||||
|
||||
var ClassMap = &classmap.Map{
|
||||
Symbols: map[string]classmap.Symbol{
|
||||
@@ -84,9 +84,9 @@ var ClassMap = &classmap.Map{
|
||||
"Astro.Common.Element": {Name: "Astro.Common.Element", Kind: "class", Parent: "Astro.Common", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Astro.Common.Element.Footer": {Name: "Astro.Common.Element.Footer", Kind: "class", Parent: "Astro.Common.Element", Imports: []string(nil), Resource: classmap.Resource{Src: "Astro/Common/Element/Footer.js", JSHash: "80f69d85c399bdc04d3b2055d1ebbe9ee77a852a", CSSHash: "c26f4238a6a4f2fc0bcbd9ac86141d12522552a4"}},
|
||||
"Astro.Mechanism": {Name: "Astro.Mechanism", Kind: "class", Parent: "Astro", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Astro.Mechanism.CharacterCloud": {Name: "Astro.Mechanism.CharacterCloud", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"Dandelion"}, Resource: classmap.Resource{Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "540a2085798928418a54e10d56bc037863ee57a9", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"}},
|
||||
"Astro.Mechanism.CharacterCloud": {Name: "Astro.Mechanism.CharacterCloud", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"Dandelion"}, Resource: classmap.Resource{Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "281b59ca621d35d254abbddc1f088870ed61cb28", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"}},
|
||||
"Astro.Mechanism.CharacterCloud.create": {Name: "Astro.Mechanism.CharacterCloud.create", Kind: "method", Parent: "Astro.Mechanism.CharacterCloud", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Astro.Mechanism.Parallax": {Name: "Astro.Mechanism.Parallax", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"System.Cycle", "Dandelion", "Dandelion.IDOMObject"}, Resource: classmap.Resource{Src: "Astro/Mechanism/Parallax.js", JSHash: "ba7b1816fc5d32edfaf5b93b9d35c7b2c297f011", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"}},
|
||||
"Astro.Mechanism.Parallax": {Name: "Astro.Mechanism.Parallax", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"System.Cycle", "Dandelion", "Dandelion.IDOMObject"}, Resource: classmap.Resource{Src: "Astro/Mechanism/Parallax.js", JSHash: "6ac80e95f9e8ba391668e1988fe3586987ea79d0", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"}},
|
||||
"Astro.Mechanism.Parallax.attach": {Name: "Astro.Mechanism.Parallax.attach", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Astro.Mechanism.Parallax.cssSlide": {Name: "Astro.Mechanism.Parallax.cssSlide", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Astro.Mechanism.Parallax.verticalSlideTo": {Name: "Astro.Mechanism.Parallax.verticalSlideTo", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
@@ -117,7 +117,7 @@ var ClassMap = &classmap.Map{
|
||||
"Astro.utils.Date.prettyDay": {Name: "Astro.utils.Date.prettyDay", Kind: "method", Parent: "Astro.utils.Date", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Astro.utils.Date.smstamp": {Name: "Astro.utils.Date.smstamp", Kind: "method", Parent: "Astro.utils.Date", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
|
||||
"Components": {Name: "Components", Kind: "class", Parent: "", Imports: []string(nil), Resource: classmap.Resource{Src: "Components/_this.js", JSHash: "f4cb7babe62a5cdae34d2c4ebcb55071e81da4ff", CSSHash: "1"}},
|
||||
"Components.Console": {Name: "Components.Console", Kind: "class", Parent: "Components", Imports: []string{"System.utils.Perf", "System.Cycle", "System.Cycle.TICK", "System.Global", "System.Log", "System.Debug", "Dandelion", "Dandelion.IDOMElement", "Components.DockPanel"}, Resource: classmap.Resource{Src: "Components/Console.js", JSHash: "115be15db72bd22f8222510e7bf4ce0c741028fb", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"}},
|
||||
"Components.Console": {Name: "Components.Console", Kind: "class", Parent: "Components", Imports: []string{"System.utils.Perf", "System.Cycle", "System.Cycle.TICK", "System.Global", "System.Log", "System.Debug", "Dandelion", "Dandelion.IDOMElement", "Components.DockPanel"}, Resource: classmap.Resource{Src: "Components/Console.js", JSHash: "9e4ba7f921f39c85f264e416669134120d4668ea", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"}},
|
||||
"Components.DockPanel": {Name: "Components.DockPanel", Kind: "class", Parent: "Components", Imports: []string{"System.Cycle", "System.utils.DataKey", "Dandelion", "Dandelion.IDOMElement"}, Resource: classmap.Resource{Src: "Components/DockPanel.js", JSHash: "c74b3081efdcbfa13300e9a49529f5664734b24e", CSSHash: "af0d91982c764ee62c46b243be68c08f2808e82b"}},
|
||||
"Components.MessageBox": {Name: "Components.MessageBox", Kind: "class", Parent: "Components", Imports: []string{"System.Cycle.Trigger", "Dandelion", "Dandelion.IDOMObject", "System.utils.EventKey", "Dandelion.CSSAnimations"}, Resource: classmap.Resource{Src: "Components/MessageBox.js", JSHash: "db0b55a5e0b1a92b9781c2b0b13817355b8e3cdf", CSSHash: "5571fa4c2e1324dcf1f2e18df8b6105cf37dfc53"}},
|
||||
"Components.Mouse": {Name: "Components.Mouse", Kind: "class", Parent: "Components", Imports: []string(nil), Resource: classmap.Resource{Src: "Components/Mouse/_this.js", JSHash: "9ec5ced53a20ea1d057e2142668e27e5df7d49f6", CSSHash: "1"}},
|
||||
@@ -379,8 +379,8 @@ var ClassMap = &classmap.Map{
|
||||
"Astro/Blog/SharedStyle.js": {Src: "Astro/Blog/SharedStyle.js", JSHash: "e946130da823a5b2d089b5b416c13b628eb1637d", CSSHash: "f8ff15304a5e38e199b713bac48e282d2bf10f45"},
|
||||
"Astro/Bootstrap.js": {Src: "Astro/Bootstrap.js", JSHash: "51bfb270eadd20b4b71372ae2d20dcf3d20575db", CSSHash: "e650f4de36f799af80ca0633d66351fb9333d620"},
|
||||
"Astro/Common/Element/Footer.js": {Src: "Astro/Common/Element/Footer.js", JSHash: "80f69d85c399bdc04d3b2055d1ebbe9ee77a852a", CSSHash: "c26f4238a6a4f2fc0bcbd9ac86141d12522552a4"},
|
||||
"Astro/Mechanism/CharacterCloud.js": {Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "540a2085798928418a54e10d56bc037863ee57a9", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"},
|
||||
"Astro/Mechanism/Parallax.js": {Src: "Astro/Mechanism/Parallax.js", JSHash: "ba7b1816fc5d32edfaf5b93b9d35c7b2c297f011", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"},
|
||||
"Astro/Mechanism/CharacterCloud.js": {Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "281b59ca621d35d254abbddc1f088870ed61cb28", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"},
|
||||
"Astro/Mechanism/Parallax.js": {Src: "Astro/Mechanism/Parallax.js", JSHash: "6ac80e95f9e8ba391668e1988fe3586987ea79d0", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"},
|
||||
"Astro/Penguin/Layout/MainFrame.js": {Src: "Astro/Penguin/Layout/MainFrame.js", JSHash: "b2f58fc9394745c1e47dfb95c4043f59f76acc1a", CSSHash: "e87b69ab9e725e4801065850790670d76f0a6d18"},
|
||||
"Astro/Penguin/Page/Docs.js": {Src: "Astro/Penguin/Page/Docs.js", JSHash: "02217e7fa4ebffe90b32002ef07da500ac02bf52", CSSHash: "5230a026499b9ab0f4f530f4975a9df89170c83b"},
|
||||
"Astro/Penguin/Page/_this.js": {Src: "Astro/Penguin/Page/_this.js", JSHash: "66e415e546eb6ca0b84cedfd4d3b6de67c2164f3", CSSHash: "e8afcaa8c75b241cd933cfc99a648adc42f815d7"},
|
||||
@@ -393,7 +393,7 @@ var ClassMap = &classmap.Map{
|
||||
"Astro/Starfall/_this.js": {Src: "Astro/Starfall/_this.js", JSHash: "77ca61d1ba806c3440cec5e26a100581259e1784", CSSHash: "1"},
|
||||
"Astro/utils/Date.js": {Src: "Astro/utils/Date.js", JSHash: "45221fe447d15fd943310fe9d3d3308f884b6aec", CSSHash: "1"},
|
||||
"Astro/utils/_this.js": {Src: "Astro/utils/_this.js", JSHash: "21e99449ec5c1a4b6d25164db9434132aeea5ad3", CSSHash: "1"},
|
||||
"Components/Console.js": {Src: "Components/Console.js", JSHash: "115be15db72bd22f8222510e7bf4ce0c741028fb", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"},
|
||||
"Components/Console.js": {Src: "Components/Console.js", JSHash: "9e4ba7f921f39c85f264e416669134120d4668ea", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"},
|
||||
"Components/DockPanel.js": {Src: "Components/DockPanel.js", JSHash: "c74b3081efdcbfa13300e9a49529f5664734b24e", CSSHash: "af0d91982c764ee62c46b243be68c08f2808e82b"},
|
||||
"Components/MessageBox.js": {Src: "Components/MessageBox.js", JSHash: "db0b55a5e0b1a92b9781c2b0b13817355b8e3cdf", CSSHash: "5571fa4c2e1324dcf1f2e18df8b6105cf37dfc53"},
|
||||
"Components/Mouse/Clipboard.js": {Src: "Components/Mouse/Clipboard.js", JSHash: "574a1ef17878beb21395a2eecfa75d046ff694e0", CSSHash: "1b7f85206ce1560ed23d3b270e093022e25d2e61"},
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Code generated by externs-gen; DO NOT EDIT.
|
||||
package generated
|
||||
|
||||
var Externs = []string{
|
||||
"externs/Astro.Blog.AstroEdit.Article.js",
|
||||
"externs/Astro.Blog.AstroEdit.IPlugin.js",
|
||||
"externs/Astro.Blog.AstroEdit.SmartInput.Definition.js",
|
||||
"externs/Astro.Blog.AstroEdit.SmartInput.ICandidateAction.js",
|
||||
"externs/Astro.Blog.AstroEdit.SmartInput.js",
|
||||
"externs/Astro.Blog.AstroEdit.Visualizer.Snippet.Model.js",
|
||||
"externs/Astro.Blog.AstroEdit.Visualizer.Snippet.js",
|
||||
"externs/Astro.Blog.AstroEdit.Visualizer.js",
|
||||
"externs/Astro.Blog.AstroEdit.js",
|
||||
"externs/Astro.Blog.Components.Bubble.js",
|
||||
"externs/Astro.Blog.Components.Calendar.js",
|
||||
"externs/Astro.Blog.Components.SiteFile.js",
|
||||
"externs/Astro.Blog.Components.js",
|
||||
"externs/Astro.Blog.Config.js",
|
||||
"externs/Astro.Blog.Events.Responsive.js",
|
||||
"externs/Astro.Blog.Events.js",
|
||||
"externs/Astro.Blog.js",
|
||||
"externs/Astro.Bootstrap.js",
|
||||
"externs/Astro.Mechanism.CharacterCloud.js",
|
||||
"externs/Astro.Mechanism.Parallax.js",
|
||||
"externs/Astro.Mechanism.js",
|
||||
"externs/Astro.js",
|
||||
"externs/Astro.utils.Date.js",
|
||||
"externs/Astro.utils.js",
|
||||
"externs/BotanEvent.js",
|
||||
"externs/BotanJS.js",
|
||||
"externs/Components.MessageBox.js",
|
||||
"externs/Components.Mouse.Clipboard.js",
|
||||
"externs/Components.Mouse.ContextMenu.js",
|
||||
"externs/Components.Mouse.js",
|
||||
"externs/Components.Vim.Actions.js",
|
||||
"externs/Components.Vim.Controls.ActionEvent.js",
|
||||
"externs/Components.Vim.Controls.js",
|
||||
"externs/Components.Vim.Cursor.js",
|
||||
"externs/Components.Vim.DateTime.js",
|
||||
"externs/Components.Vim.IAction.js",
|
||||
"externs/Components.Vim.LineBuffer.js",
|
||||
"externs/Components.Vim.LineFeeder.js",
|
||||
"externs/Components.Vim.State.History.js",
|
||||
"externs/Components.Vim.State.Marks.js",
|
||||
"externs/Components.Vim.State.Recorder.js",
|
||||
"externs/Components.Vim.State.Registers.js",
|
||||
"externs/Components.Vim.State.Stack.js",
|
||||
"externs/Components.Vim.State.js",
|
||||
"externs/Components.Vim.StatusBar.js",
|
||||
"externs/Components.Vim.Syntax.Analyzer.js",
|
||||
"externs/Components.Vim.Syntax.TokenMatch.js",
|
||||
"externs/Components.Vim.Syntax.Word.js",
|
||||
"externs/Components.Vim.Syntax.js",
|
||||
"externs/Components.Vim.VimArea.js",
|
||||
"externs/Components.Vim.js",
|
||||
"externs/Components.js",
|
||||
"externs/Dandelion.CSSAnimations.MovieClip.js",
|
||||
"externs/Dandelion.CSSAnimations.js",
|
||||
"externs/Dandelion.IDOMElement.js",
|
||||
"externs/Dandelion.IDOMObject.js",
|
||||
"externs/Dandelion.js",
|
||||
"externs/Libraries.SyntaxHighLighter.Brush.js",
|
||||
"externs/Libraries.SyntaxHighLighter.js",
|
||||
"externs/Libraries.js",
|
||||
"externs/System.Compression.Zlib.js",
|
||||
"externs/System.Compression.js",
|
||||
"externs/System.Cycle.Trigger.js",
|
||||
"externs/System.Cycle.js",
|
||||
"externs/System.Debug.js",
|
||||
"externs/System.Encoding.Base64.js",
|
||||
"externs/System.Encoding.Utf8.js",
|
||||
"externs/System.Encoding.js",
|
||||
"externs/System.Global.js",
|
||||
"externs/System.Log.js",
|
||||
"externs/System.Net.js",
|
||||
"externs/System.Policy.js",
|
||||
"externs/System.Tick.js",
|
||||
"externs/System.js",
|
||||
"externs/System.utils.DataKey.js",
|
||||
"externs/System.utils.EventKey.js",
|
||||
"externs/System.utils.IKey.js",
|
||||
"externs/System.utils.Perf.js",
|
||||
"externs/System.utils.js",
|
||||
"externs/_3rdParty_.Recaptcha.js",
|
||||
"externs/_3rdParty_.js",
|
||||
"externs/_AstConf_.Article.js",
|
||||
"externs/_AstConf_.AstroEdit.js",
|
||||
"externs/_AstConf_.Comment.js",
|
||||
"externs/_AstConf_.Control.js",
|
||||
"externs/_AstConf_.Login.js",
|
||||
"externs/_AstConf_.Notification.js",
|
||||
"externs/_AstConf_.SiteFile.js",
|
||||
"externs/_AstConf_.UserInfo.js",
|
||||
"externs/_AstConf_.js",
|
||||
"externs/_AstJson_.AJaxGetArticle.js",
|
||||
"externs/_AstJson_.AJaxGetDrafts.js",
|
||||
"externs/_AstJson_.AJaxGetFiles.Request.js",
|
||||
"externs/_AstJson_.AJaxGetFiles.js",
|
||||
"externs/_AstJson_.AJaxGetNotis.js",
|
||||
"externs/_AstJson_.SiteFile.js",
|
||||
"externs/_AstJson_.js",
|
||||
"externs/_AstXObject_.AstroEdit.Visualizer.Snippet.Code.js",
|
||||
"externs/_AstXObject_.AstroEdit.Visualizer.Snippet.Spoiler.js",
|
||||
"externs/_AstXObject_.AstroEdit.Visualizer.Snippet.js",
|
||||
"externs/_AstXObject_.AstroEdit.Visualizer.js",
|
||||
"externs/_AstXObject_.AstroEdit.js",
|
||||
"externs/_AstXObject_.js",
|
||||
}
|
||||
@@ -14,8 +14,10 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tgckpg/botanres-go/internal/classmap"
|
||||
"github.com/tgckpg/resolver-go/internal/classmap"
|
||||
"github.com/tgckpg/resolver-go/internal/closure"
|
||||
)
|
||||
|
||||
type OutputMode string
|
||||
@@ -38,6 +40,9 @@ type Result struct {
|
||||
type Resolver struct {
|
||||
Root string
|
||||
Map *classmap.Map
|
||||
|
||||
externMu sync.RWMutex
|
||||
externCache map[string]closure.SourceInput
|
||||
}
|
||||
|
||||
func New(root string, m *classmap.Map) (*Resolver, error) {
|
||||
@@ -45,7 +50,11 @@ func New(root string, m *classmap.Map) (*Resolver, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Resolver{Root: root, Map: m}, nil
|
||||
return &Resolver{
|
||||
Root: root,
|
||||
Map: m,
|
||||
externCache: make(map[string]closure.SourceInput),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) ResolveRequest(code string, mode OutputMode, excludes []string) (Result, error) {
|
||||
@@ -242,6 +251,55 @@ func (r *Resolver) MergeCSS(files []classmap.Resource) ([]byte, string, error) {
|
||||
return b.Bytes(), hashList(files, "css"), nil
|
||||
}
|
||||
|
||||
func (r *Resolver) GetExterns(files []string) ([]closure.SourceInput, error) {
|
||||
sources := make([]closure.SourceInput, 0, len(files))
|
||||
|
||||
for _, f := range files {
|
||||
src, ok, err := r.getExtern(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
sources = append(sources, src)
|
||||
}
|
||||
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) getExtern(f string) (closure.SourceInput, bool, error) {
|
||||
// Fast path: read cache.
|
||||
r.externMu.RLock()
|
||||
src, ok := r.externCache[f]
|
||||
r.externMu.RUnlock()
|
||||
if ok {
|
||||
return src, true, nil
|
||||
}
|
||||
|
||||
// Slow path: read file.
|
||||
b, err := os.ReadFile(filepath.Join(r.Root, filepath.FromSlash(f)))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return closure.SourceInput{}, false, nil
|
||||
}
|
||||
return closure.SourceInput{}, false, err
|
||||
}
|
||||
|
||||
src = closure.SourceInput{
|
||||
Name: f,
|
||||
Source: string(b),
|
||||
}
|
||||
|
||||
// Store cache.
|
||||
r.externMu.Lock()
|
||||
r.externCache[f] = src
|
||||
r.externMu.Unlock()
|
||||
|
||||
return src, true, nil
|
||||
}
|
||||
|
||||
func DecodeRequest(code string) ([]string, error) {
|
||||
sep := "/"
|
||||
decoded := code
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../botanjs/src
|
||||
Reference in New Issue
Block a user