package scenes import ( "bytes" "fmt" "io" "io/fs" "path" "sort" "strings" "time" ) func modulePromptFS(shared fs.FS) (fs.FS, error) { assetFS, err := promptMapFSFromEmbedded(map[string]string{ "assets/prompts/dnd.scenes/dnd.scenes.yaml": "assets/prompts/dnd.scenes.yaml", "assets/prompts/dnd.scenes/task.md": "assets/prompts/task.md", "assets/prompts/dnd.scenes/instructions.md": "assets/prompts/instructions.md", }) if err != nil { return nil, err } assets := assetFS.(promptMapFS) if shared == nil { return assets, nil } for _, name := range []string{ "common-dnd-system.md", "common-dnd-transcript.md", "common-dnd-references.md", } { data, err := fs.ReadFile(shared, name) if err != nil { return nil, fmt.Errorf("read shared prompt asset %s: %w", name, err) } assets["assets/prompts/dnd.scenes/"+name] = append([]byte(nil), data...) } return assets, nil } func promptMapFSFromEmbedded(files map[string]string) (fs.FS, error) { assets := make(promptMapFS, len(files)) for virtualPath, embeddedPath := range files { data, err := fs.ReadFile(embeddedAssets, embeddedPath) if err != nil { return nil, fmt.Errorf("read prompt asset %s: %w", embeddedPath, err) } assets[virtualPath] = append([]byte(nil), data...) } return assets, nil } type promptMapFS map[string][]byte func (m promptMapFS) Open(name string) (fs.File, error) { cleaned, err := cleanPromptPath(name) if err != nil { return nil, &fs.PathError{Op: "open", Path: name, Err: err} } if data, ok := m[cleaned]; ok { return &promptFile{ reader: bytes.NewReader(data), info: promptFileInfo{name: path.Base(cleaned), size: int64(len(data))}, }, nil } entries := m.dirEntries(cleaned) if entries != nil { return &promptDir{name: path.Base(cleaned), entries: entries}, nil } return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} } func (m promptMapFS) ReadDir(name string) ([]fs.DirEntry, error) { cleaned, err := cleanPromptPath(name) if err != nil { return nil, &fs.PathError{Op: "readdir", Path: name, Err: err} } entries := m.dirEntries(cleaned) if entries == nil { return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrNotExist} } return entries, nil } func (m promptMapFS) dirEntries(dir string) []fs.DirEntry { children := map[string]promptDirEntry{} prefix := "" if dir != "." { prefix = dir + "/" } for name, data := range m { if !strings.HasPrefix(name, prefix) { continue } rest := strings.TrimPrefix(name, prefix) if rest == "" { continue } childName, _, hasSlash := strings.Cut(rest, "/") entry := promptDirEntry{name: childName, dir: hasSlash} if !hasSlash { entry.size = int64(len(data)) } children[childName] = entry } if len(children) == 0 { return nil } names := make([]string, 0, len(children)) for name := range children { names = append(names, name) } sort.Strings(names) entries := make([]fs.DirEntry, 0, len(names)) for _, name := range names { entries = append(entries, children[name]) } return entries } func cleanPromptPath(name string) (string, error) { trimmed := strings.TrimSpace(name) if trimmed == "" { return "", fmt.Errorf("path must not be empty") } cleaned := path.Clean(strings.TrimPrefix(trimmed, "/")) if cleaned == "." || !fs.ValidPath(cleaned) { return "", fmt.Errorf("invalid path %q", name) } return cleaned, nil } type promptFile struct { reader *bytes.Reader info promptFileInfo } func (f *promptFile) Stat() (fs.FileInfo, error) { return f.info, nil } func (f *promptFile) Read(p []byte) (int, error) { return f.reader.Read(p) } func (f *promptFile) Close() error { return nil } type promptDir struct { name string offset int entries []fs.DirEntry } func (d *promptDir) Stat() (fs.FileInfo, error) { return promptFileInfo{name: d.name, dir: true}, nil } func (d *promptDir) Read([]byte) (int, error) { return 0, io.EOF } func (d *promptDir) Close() error { return nil } func (d *promptDir) ReadDir(n int) ([]fs.DirEntry, error) { if d.offset >= len(d.entries) { return nil, io.EOF } end := len(d.entries) if n > 0 && d.offset+n < end { end = d.offset + n } out := append([]fs.DirEntry(nil), d.entries[d.offset:end]...) d.offset = end return out, nil } type promptDirEntry struct { name string dir bool size int64 } func (e promptDirEntry) Name() string { return e.name } func (e promptDirEntry) IsDir() bool { return e.dir } func (e promptDirEntry) Type() fs.FileMode { return e.fileInfoMode().Type() } func (e promptDirEntry) Info() (fs.FileInfo, error) { return promptFileInfo{name: e.name, dir: e.dir, size: e.size}, nil } func (e promptDirEntry) fileInfoMode() fs.FileMode { if e.dir { return fs.ModeDir | 0o555 } return 0o444 } type promptFileInfo struct { name string dir bool size int64 } func (i promptFileInfo) Name() string { return i.name } func (i promptFileInfo) Size() int64 { return i.size } func (i promptFileInfo) Mode() fs.FileMode { return promptDirEntry{dir: i.dir}.fileInfoMode() } func (i promptFileInfo) ModTime() time.Time { return time.Time{} } func (i promptFileInfo) IsDir() bool { return i.dir } func (i promptFileInfo) Sys() any { return nil }