Removed old impl

This commit is contained in:
2026-06-12 04:51:38 +08:00
parent 4fcd58b5ed
commit f532ada7c4
63 changed files with 717 additions and 1359 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# botanres-go
# resolver-go
Go rewrite for the old BotanJS dynamic resource resolver.
+60 -5
View File
@@ -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)
}
+87
View File
@@ -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)
}
+4 -1
View File
@@ -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"]
+7 -3
View File
@@ -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
View File
@@ -1,3 +1,3 @@
module github.com/tgckpg/botanres-go
module github.com/tgckpg/resolver-go
go 1.26
+85
View File
@@ -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()
}
}
+107
View File
@@ -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)
}
+41
View File
@@ -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",
}
+60 -2
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
../botanjs/src