forked from Botanical/BotanJS
Rename the components
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
package classmap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
reNamespace = regexp.MustCompile(`__namespace\s*\(\s*['\"]([^'\"]+)['\"]\s*\)`)
|
||||
reImport = regexp.MustCompile(`__import\s*\(\s*['\"]([^'\"]+)['\"]\s*\)`)
|
||||
reInvoke = regexp.MustCompile(`ns\s*\[\s*NS_INVOKE\s*\]\s*\(\s*['\"]([^'\"]+)['\"]\s*\)`)
|
||||
reExport = regexp.MustCompile(`ns\s*\[\s*NS_EXPORT\s*\]\s*\(\s*EX_([A-Z_]+[A-Z])\s*,\s*['\"]([^'\"]+)['\"]\s*,`)
|
||||
)
|
||||
|
||||
type Export struct {
|
||||
Type string
|
||||
Name string
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Namespace string
|
||||
Imports []string
|
||||
Exports []Export
|
||||
}
|
||||
|
||||
func ParseFile(path string) (Meta, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return Meta{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var m Meta
|
||||
s := bufio.NewScanner(f)
|
||||
// Some old JS files can have long one-line wrappers.
|
||||
s.Buffer(make([]byte, 0, 64*1024), 8*1024*1024)
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if x := reNamespace.FindStringSubmatch(line); x != nil {
|
||||
m.Namespace = x[1]
|
||||
continue
|
||||
}
|
||||
if x := reImport.FindStringSubmatch(line); x != nil {
|
||||
m.Imports = append(m.Imports, x[1])
|
||||
continue
|
||||
}
|
||||
if x := reInvoke.FindStringSubmatch(line); x != nil {
|
||||
m.Imports = append(m.Imports, m.Namespace+"."+x[1])
|
||||
continue
|
||||
}
|
||||
if x := reExport.FindStringSubmatch(line); x != nil {
|
||||
m.Exports = append(m.Exports, Export{Type: x[1], Name: x[2]})
|
||||
continue
|
||||
}
|
||||
}
|
||||
return m, s.Err()
|
||||
}
|
||||
|
||||
func Build(root string) (*Map, error) {
|
||||
root, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := &Map{Symbols: map[string]Symbol{}, Files: map[string]Resource{}}
|
||||
head := filepath.Join(root, "_this.js")
|
||||
|
||||
err = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
if path == filepath.Join(root, "externs") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(path) != ".js" || path == head {
|
||||
return nil
|
||||
}
|
||||
meta, err := ParseFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.Namespace == "" {
|
||||
return fmt.Errorf("%s: missing __namespace", path)
|
||||
}
|
||||
rel, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
res := Resource{Src: rel, JSHash: fileHash(path), CSSHash: fileHash(strings.TrimSuffix(path, ".js") + ".css")}
|
||||
m.Files[rel] = res
|
||||
srcEvery := meta.Namespace != className(rel)
|
||||
|
||||
ensureParents(m, meta.Namespace)
|
||||
if !srcEvery {
|
||||
sym := m.Symbols[meta.Namespace]
|
||||
sym.Kind = KindClass
|
||||
sym.Resource = res
|
||||
sym.Imports = appendUnique(sym.Imports, meta.Imports...)
|
||||
m.Symbols[meta.Namespace] = sym
|
||||
}
|
||||
|
||||
for _, ex := range meta.Exports {
|
||||
kind := exportKind(ex.Type)
|
||||
name := meta.Namespace + "." + ex.Name
|
||||
ensureParents(m, name)
|
||||
sym := m.Symbols[name]
|
||||
sym.Kind = kind
|
||||
if srcEvery {
|
||||
sym.Resource = res
|
||||
if kind == KindClass {
|
||||
sym.Imports = appendUnique(sym.Imports, meta.Imports...)
|
||||
}
|
||||
}
|
||||
m.Symbols[name] = sym
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func ensureParents(m *Map, name string) {
|
||||
parts := strings.Split(name, ".")
|
||||
for i := range parts {
|
||||
full := strings.Join(parts[:i+1], ".")
|
||||
if _, ok := m.Symbols[full]; !ok {
|
||||
parent := ""
|
||||
if i > 0 {
|
||||
parent = strings.Join(parts[:i], ".")
|
||||
}
|
||||
m.Symbols[full] = Symbol{Name: full, Kind: KindClass, Parent: parent}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func className(rel string) string {
|
||||
name := strings.TrimSuffix(filepath.ToSlash(rel), ".js")
|
||||
name = strings.ReplaceAll(name, "/", ".")
|
||||
name = strings.ReplaceAll(name, "._this", "")
|
||||
name = strings.ReplaceAll(name, "..BotanJS.", "")
|
||||
return name
|
||||
}
|
||||
|
||||
func exportKind(t string) Kind {
|
||||
switch t {
|
||||
case "CLASS":
|
||||
return KindClass
|
||||
case "FUNC":
|
||||
return KindMethod
|
||||
default:
|
||||
return KindProp
|
||||
}
|
||||
}
|
||||
|
||||
func fileHash(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "1"
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha1.New()
|
||||
_, _ = io.Copy(h, f)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func appendUnique(dst []string, vals ...string) []string {
|
||||
seen := map[string]bool{}
|
||||
for _, v := range dst {
|
||||
seen[v] = true
|
||||
}
|
||||
for _, v := range vals {
|
||||
if v == "" || seen[v] {
|
||||
continue
|
||||
}
|
||||
dst = append(dst, v)
|
||||
seen[v] = true
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func SortedSymbols(m *Map) []Symbol {
|
||||
out := make([]Symbol, 0, len(m.Symbols))
|
||||
for _, s := range m.Symbols {
|
||||
out = append(out, s)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out
|
||||
}
|
||||
|
||||
func SortedFiles(m *Map) []Resource {
|
||||
out := make([]Resource, 0, len(m.Files))
|
||||
for _, r := range m.Files {
|
||||
out = append(out, r)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Src < out[j].Src })
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user