zilch/lang/go/utils/parser/main.go
Puck Meerburg b59fd781d0 (zilch lang go): fix go file parser with all: go:embed patterns
Technically this isn't quite right, as the full list of files is not
available to the go parser (some files are filtered out too early in
the process), but it should do the job for now.
2024-11-17 18:24:00 +00:00

314 lines
6.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"go/build"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
type WrappedStat struct {
fs.FileInfo
newName string // meow
}
func (s *WrappedStat) Name() string {
return s.newName
}
type DirStat struct {
name string
}
func (s *DirStat) Name() string {
return s.name
}
func (s *DirStat) Size() int64 {
return 1
}
func (s *DirStat) Mode() fs.FileMode {
return fs.FileMode(fs.ModeDir | 0777)
}
func (s *DirStat) ModTime() time.Time {
return time.Time{}
}
func (s *DirStat) IsDir() bool {
return true
}
func (s *DirStat) Sys() any {
return "zilch"
}
type Input struct {
// list of [directory, filename, target path] (target path is empty, if directory marker)
Files [][3]string `json:"files"`
GOARCH string `json:"GOARCH"`
GOOS string `json:"GOOS"`
}
type Output struct {
Name string `json:"name"`
GoFiles []string `json:"goFiles"`
SFiles []string `json:"sFiles"`
Imports []string `json:"imports"`
Embeds map[string][][]string `json:"embeds"`
}
type FileOrDir struct {
Directory []string
File string
}
func main() {
inputFile, err := os.Open(os.Args[1])
if err != nil {
panic(err)
}
var input Input
err = json.NewDecoder(inputFile).Decode(&input)
if err != nil {
panic(err)
}
files := make(map[string]*FileOrDir)
for _, file := range input.Files {
directory := file[0]
filename := file[1]
contents := file[2]
dirslash := directory
if directory != "" {
dirslash = directory + "/"
}
if contents == "" {
_, ok := files[dirslash+filename]
if !ok {
files[dirslash+filename] = &FileOrDir{}
}
} else {
files[dirslash+filename] = &FileOrDir{File: contents}
}
parent, ok := files[directory]
if !ok {
parent = &FileOrDir{}
files[directory] = parent
}
parent.Directory = append(parent.Directory, dirslash+filename)
}
ctx := build.Context{
GOARCH: input.GOARCH,
GOOS: input.GOOS,
Compiler: "gc",
ToolTags: build.Default.ToolTags,
ReleaseTags: build.Default.ReleaseTags,
ReadDir: func(dir string) ([]fs.FileInfo, error) {
fmt.Printf("ReadDir(%q)\n", dir)
if !strings.HasPrefix(dir, "/code") {
return nil, fs.ErrNotExist
}
dir = path.Clean(dir[5:])[1:]
contents, ok := files[dir]
if !ok {
return nil, fs.ErrNotExist
}
infos := make([]fs.FileInfo, len(contents.Directory))
for i, filename := range contents.Directory {
file := files[filename]
if file.File != "" {
stat, err := os.Stat(file.File)
if err != nil {
return nil, err
}
infos[i] = &WrappedStat{FileInfo: stat, newName: path.Base(filename)}
} else {
infos[i] = &DirStat{path.Base(filename)}
}
}
return infos, nil
},
OpenFile: func(pth string) (io.ReadCloser, error) {
opth := pth
fmt.Printf("OpenFile(%q)\n", pth)
if !strings.HasPrefix(pth, "/code") {
return nil, fs.ErrNotExist
}
pth = path.Clean(pth[5:])[1:]
fmt.Printf("-> OpenFile(%q)\n", pth)
fil := files[pth]
data, err := os.Open(fil.File)
if err != nil {
return data, fmt.Errorf("OpenFile(%q; %q): %w", opth, pth, err)
}
return data, err
},
IsDir: func(dir string) bool {
fmt.Printf("IsDir(%q)\n", dir)
if !strings.HasPrefix(dir, "/code") {
return false
}
dir = path.Clean(dir[5:])[1:]
contents, ok := files[dir]
fmt.Printf("IsDir -> %q, %v, %v\n", dir, contents, ok)
return ok && contents.File == ""
},
HasSubdir: func(root, dir string) (rel string, ok bool) {
root = path.Clean(root)
dir = path.Clean(dir)
return strings.CutPrefix(dir, root)
},
}
var filenames []string
for _, filedata := range input.Files {
if filedata[2] != "" {
filenames = append(filenames, filepath.Join(filedata[0], filedata[1]))
}
}
outfiles := make(map[string]Output)
for dir, filelist := range files {
if filelist.File != "" {
continue
}
isGo := false
for _, file := range filelist.Directory {
if strings.HasSuffix(file, ".go") {
isGo = true
break
}
}
fmt.Printf("Checking %q..\n", dir)
if strings.Contains(dir, "/.") || strings.Contains(dir, "/_") || strings.Contains(dir, "/testdata/") {
fmt.Printf(" skipping; \n")
continue
}
base := path.Base(dir)
if base == "." {
base = ""
}
if !isGo || strings.HasPrefix(base, "_") || strings.HasPrefix(base, ".") || base == "testdata" {
fmt.Printf(" skipping (not go)\n")
continue
}
pkg, err := ctx.Import(".", path.Clean("/code/"+dir), 0)
if err != nil {
if _, ok := err.(*build.NoGoError); ok {
continue
}
panic(err)
}
out := Output{
Name: pkg.Name,
Imports: pkg.Imports,
GoFiles: pkg.GoFiles,
SFiles: pkg.SFiles,
Embeds: make(map[string][][]string),
}
// _test only, or so
if len(pkg.GoFiles) == 0 {
continue
}
sort.Strings(out.Imports)
sort.Strings(out.GoFiles)
sort.Strings(out.SFiles)
sort.Strings(pkg.EmbedPatterns)
for _, pattern := range pkg.EmbedPatterns {
matchedFiles := []string{}
storePattern := pattern
// TODO: proper matching
if strings.HasPrefix(pattern, "all:") {
pattern = pattern[4:]
}
fullPattern := filepath.Join(dir, pattern)
for _, file := range filenames {
if ok, _ := filepath.Match(fullPattern, file); ok {
matchedFiles = append(matchedFiles, file)
}
}
if len(matchedFiles) == 0 {
dirpattern := fullPattern + "/"
for _, file := range filenames {
if strings.HasPrefix(file, dirpattern) {
matchedFiles = append(matchedFiles, file)
}
if file == fullPattern {
matchedFiles = append(matchedFiles, file)
}
}
}
sort.Strings(matchedFiles)
var split [][]string
for _, match := range matchedFiles {
dirname := filepath.Dir(match)
if dirname == "." {
dirname = ""
}
split = append(split, []string{match[len(dir)+1:], dirname, filepath.Base(match)})
}
out.Embeds[storePattern] = split
}
outfiles[dir] = out
}
out, err := os.OpenFile(os.Getenv("out"), os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
panic(err)
}
if err := json.NewEncoder(out).Encode(outfiles); err != nil {
panic(err)
}
out.Close()
}