package bfcodegen import ( "bytes" _ "embed" "errors" "fmt" "go/format" "gogs.mikescher.com/BlackForestBytes/goext" "gogs.mikescher.com/BlackForestBytes/goext/cryptext" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/rext" "io" "os" "path" "path/filepath" "regexp" "strings" "text/template" ) type CSIDDef struct { File string FileRelative string Name string Prefix string } type CSIDGenOptions struct { DebugOutput *bool } var rexCSIDPackage = rext.W(regexp.MustCompile(`^package\s+(?P[A-Za-z0-9_]+)\s*$`)) var rexCSIDDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P[A-Za-z0-9_]+)\s+string\s*//\s*(@csid:type)\s+\[(?P[A-Z0-9]{3})].*$`)) var rexCSIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumCharsetIDGenerator = "(?P[A-Za-z0-9_]*)"`)) //go:embed csid-generate.template var templateCSIDGenerateText string func GenerateCharsetIDSpecs(sourceDir string, destFile string, opt CSIDGenOptions) error { debugOutput := langext.Coalesce(opt.DebugOutput, false) files, err := os.ReadDir(sourceDir) if err != nil { return err } oldChecksum := "N/A" if _, err := os.Stat(destFile); !os.IsNotExist(err) { content, err := os.ReadFile(destFile) if err != nil { return err } if m, ok := rexCSIDChecksumConst.MatchFirst(string(content)); ok { oldChecksum = m.GroupByName("cs").Value() } } files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) }) files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") }) files = langext.ArrFilter(files, func(v os.DirEntry) bool { return !strings.HasSuffix(v.Name(), "_gen.go") }) langext.SortBy(files, func(v os.DirEntry) string { return v.Name() }) newChecksumStr := goext.GoextVersion for _, f := range files { content, err := os.ReadFile(path.Join(sourceDir, f.Name())) if err != nil { return err } newChecksumStr += "\n" + f.Name() + "\t" + cryptext.BytesSha256(content) } newChecksum := cryptext.BytesSha256([]byte(newChecksumStr)) if newChecksum != oldChecksum { fmt.Printf("[CSIDGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum) } else { fmt.Printf("[CSIDGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum) return nil } allIDs := make([]CSIDDef, 0) pkgname := "" for _, f := range files { if debugOutput { fmt.Printf("========= %s =========\n\n", f.Name()) } fileIDs, pn, err := processCSIDFile(sourceDir, path.Join(sourceDir, f.Name()), debugOutput) if err != nil { return err } if debugOutput { fmt.Printf("\n") } allIDs = append(allIDs, fileIDs...) if pn != "" { pkgname = pn } } if pkgname == "" { return errors.New("no package name found in any file") } fdata, err := format.Source([]byte(fmtCSIDOutput(newChecksum, allIDs, pkgname))) if err != nil { return err } err = os.WriteFile(destFile, fdata, 0o755) if err != nil { return err } return nil } func processCSIDFile(basedir string, fn string, debugOutput bool) ([]CSIDDef, string, error) { file, err := os.Open(fn) if err != nil { return nil, "", err } defer func() { _ = file.Close() }() bin, err := io.ReadAll(file) if err != nil { return nil, "", err } lines := strings.Split(string(bin), "\n") ids := make([]CSIDDef, 0) pkgname := "" for i, line := range lines { if i == 0 && strings.HasPrefix(line, "// Code generated by") { break } if match, ok := rexCSIDPackage.MatchFirst(line); i == 0 && ok { pkgname = match.GroupByName("name").Value() continue } if match, ok := rexCSIDDef.MatchFirst(line); ok { rfp, err := filepath.Rel(basedir, fn) if err != nil { return nil, "", err } def := CSIDDef{ File: fn, FileRelative: rfp, Name: match.GroupByName("name").Value(), Prefix: match.GroupByName("prefix").Value(), } if debugOutput { fmt.Printf("Found ID definition { '%s' }\n", def.Name) } ids = append(ids, def) } } return ids, pkgname, nil } func fmtCSIDOutput(cs string, ids []CSIDDef, pkgname string) string { templ := template.Must(template.New("csid-generate").Parse(templateCSIDGenerateText)) buffer := bytes.Buffer{} err := templ.Execute(&buffer, langext.H{ "PkgName": pkgname, "Checksum": cs, "GoextVersion": goext.GoextVersion, "IDs": ids, }) if err != nil { panic(err) } return buffer.String() }