From aa162a247ff2047c9ee20f67820a3c3ff1586bea Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 30 Sep 2024 04:13:48 -0700 Subject: [PATCH] feat: generate tags dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- internal/gui/bindings/bindings.go | 40 ++- internal/gui/gui.go | 15 ++ internal/gui/pack.go | 7 + internal/gui/package.go | 38 +-- internal/gui/tag_gen.go | 385 ++++++++++++++++++++++++++++ internal/gui/translation/en-US.json | 20 ++ internal/gui/unpack.go | 7 + pkg/ddpackage/auto_tags.go | 168 ++++++++++++ pkg/structures/files.go | 2 +- 9 files changed, 652 insertions(+), 30 deletions(-) create mode 100644 internal/gui/tag_gen.go create mode 100644 pkg/ddpackage/auto_tags.go diff --git a/internal/gui/bindings/bindings.go b/internal/gui/bindings/bindings.go index ef6715f..8d49292 100644 --- a/internal/gui/bindings/bindings.go +++ b/internal/gui/bindings/bindings.go @@ -4,6 +4,7 @@ import ( "errors" "fyne.io/fyne/v2/data/binding" + log "github.com/sirupsen/logrus" ) type proxyBinding[B binding.DataItem] struct { @@ -76,6 +77,7 @@ func Listen[T any](data Bound[T], f func(T)) binding.DataListener { listener := binding.NewDataListener(func() { val, err := data.Get() if err != nil { + log.Errorf("listen bind get error %s", err.Error()) return } f(val) @@ -84,6 +86,14 @@ func Listen[T any](data Bound[T], f func(T)) binding.DataListener { return listener } +func AddListenerToAll(f func(), items ...binding.DataItem) { + + listener := binding.NewDataListener(f) + for _, item := range items { + item.AddListener(listener) + } +} + func ListenErr[T any](data Bound[T], f func(T), e func(error)) binding.DataListener { listener := binding.NewDataListener(func() { val, err := data.Get() @@ -99,8 +109,8 @@ func ListenErr[T any](data Bound[T], f func(T), e func(error)) binding.DataListe type boundMapping[F any, T any] struct { proxyBinding[Bound[F]] - f func(F) (T, error) - r func(T) (F, error) + get func(F) (T, error) + set func(T) (F, error) } func NewMapping[F any, T any]( @@ -109,19 +119,19 @@ func NewMapping[F any, T any]( ) Bound[T] { return &boundMapping[F, T]{ proxyBinding: proxyBinding[Bound[F]]{from: from}, - f: f, + get: f, } } func NewReversableMapping[F any, T any]( from Bound[F], - f func(F) (T, error), - r func(T) (F, error), + get func(F) (T, error), + set func(T) (F, error), ) Bound[T] { return &boundMapping[F, T]{ proxyBinding: proxyBinding[Bound[F]]{from: from}, - f: f, - r: r, + get: get, + set: set, } } @@ -129,15 +139,17 @@ func (bm *boundMapping[F, T]) Get() (T, error) { v, err := bm.from.Get() if err != nil { var t T + log.Errorf("mapped bind get err %s", err.Error()) return t, err } - return bm.f(v) + return bm.get(v) } func (bm *boundMapping[F, T]) Set(t T) error { - if bm.r != nil { - rev, err := bm.r(t) + if bm.set != nil { + rev, err := bm.set(t) if err != nil { + log.Errorf("mapped bind set err %s", err.Error()) return err } return bm.from.Set(rev) @@ -154,13 +166,13 @@ var errWrongType = errors.New("wrong type provided") type mappedBinding[T any] struct { Bound[T] - v T + v T counter int - self binding.ExternalInt + self binding.ExternalInt - set func(T) error - get func() (T, error) + set func(T) error + get func() (T, error) } func MappedBind[T any](get func() (T, error), set func(T) error) ExternalBound[T] { diff --git a/internal/gui/gui.go b/internal/gui/gui.go index c27195d..bfe1566 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -6,6 +6,7 @@ import ( "image/color" "net/url" "os" + "path/filepath" "strings" "sync" "time" @@ -186,6 +187,7 @@ func (a *App) buildMainUI() { dlg := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { if err == nil && uc != nil { log.Infof("open path %s", uc.URI().Path()) + a.app.Preferences().SetString("lastPack.path", uc.URI().Path()) a.operatingPath.Set(uc.URI().Path()) } }, a.window) @@ -196,6 +198,12 @@ func (a *App) buildMainUI() { fyne.Min(a.window.Canvas().Size().Height, 580), ), ) + lastOpen := a.app.Preferences().String("lastPack.path") + lastOpenURI := storage.NewFileURI(filepath.Dir(lastOpen)) + lisableLastOpen, err := storage.ListerForURI(lastOpenURI) + if err == nil { + dlg.SetLocation(lisableLastOpen) + } dlg.Show() }) @@ -203,6 +211,7 @@ func (a *App) buildMainUI() { dlg := dialog.NewFolderOpen(func(lu fyne.ListableURI, err error) { if err == nil && lu != nil { log.Infof("open path %s", lu.Path()) + a.app.Preferences().SetString("lastFolder.path", lu.Path()) a.operatingPath.Set(lu.Path()) } }, a.window) @@ -212,6 +221,12 @@ func (a *App) buildMainUI() { fyne.Min(a.window.Canvas().Size().Height, 580), ), ) + lastOpen := a.app.Preferences().String("lastFolder.path") + lastOpenURI := storage.NewFileURI(lastOpen) + lisableLastOpen, err := storage.ListerForURI(lastOpenURI) + if err == nil { + dlg.SetLocation(lisableLastOpen) + } dlg.Show() }) diff --git a/internal/gui/pack.go b/internal/gui/pack.go index b8a3618..90bfb56 100644 --- a/internal/gui/pack.go +++ b/internal/gui/pack.go @@ -13,6 +13,7 @@ import ( "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -260,6 +261,12 @@ func (a *App) setUnpackedContent(pkg *ddpackage.Package) { fyne.Min(a.window.Canvas().Size().Height, 580), ), ) + outPath := a.app.Preferences().String("pack.outPath") + outPathURI := storage.NewFileURI(outPath) + lisableOutPath, err := storage.ListerForURI(outPathURI) + if err == nil { + dlg.SetLocation(lisableOutPath) + } dlg.Show() }) diff --git a/internal/gui/package.go b/internal/gui/package.go index 6d05033..b17864f 100644 --- a/internal/gui/package.go +++ b/internal/gui/package.go @@ -55,25 +55,33 @@ func (a *App) buildPackageTreeAndInfoPane(editable bool) fyne.CanvasObject { }, ) - leftSplit := container.NewPadded( - layouts.NewTopExpandVBox( - layouts.NewBottomExpandVBox( - container.New( - layouts.NewRightExpandHBoxLayout(), - widget.NewLabel(lang.X("tree.label", "Resources")), - filterEntry, - ), - container.NewStack( - &canvas.Rectangle{ - FillColor: theme.Color(theme.ColorNameInputBackground), - }, - container.NewPadded(tree), - ), + leftSplit := layouts.NewTopExpandVBox( + layouts.NewBottomExpandVBox( + container.New( + layouts.NewRightExpandHBoxLayout(), + widget.NewLabel(lang.X("tree.label", "Resources")), + filterEntry, + ), + container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameInputBackground), + }, + container.NewPadded(tree), ), - tagSetsBtn, ), + tagSetsBtn, ) + if editable { + generateTageBtn := widget.NewButton( + lang.X("generateTageBtn.label", "Generate Tags"), + func() { + dlg := a.createTagGenDialog() + dlg.Show() + }) + leftSplit.Add(generateTageBtn) + } + defaultPreview := container.NewCenter( widget.NewLabel(lang.X("preview.defaultText", "Select a resource")), ) diff --git a/internal/gui/tag_gen.go b/internal/gui/tag_gen.go new file mode 100644 index 0000000..c201fed --- /dev/null +++ b/internal/gui/tag_gen.go @@ -0,0 +1,385 @@ +package gui + +import ( + "os" + "slices" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + xlayout "fyne.io/x/fyne/layout" + "github.com/ryex/dungeondraft-gopackager/internal/gui/bindings" + "github.com/ryex/dungeondraft-gopackager/internal/gui/layouts" + "github.com/ryex/dungeondraft-gopackager/internal/utils" + "github.com/ryex/dungeondraft-gopackager/pkg/ddpackage" + "github.com/ryex/dungeondraft-gopackager/pkg/structures" + log "github.com/sirupsen/logrus" +) + +func (a *App) createTagGenDialog() dialog.Dialog { + examplePathParts := []string{"textures", "objects", "[Set A] Tag A", "[Set B] [Set C] Long Tag B", "Tag C", "object.png"} + + var ( + tags []string + tagSets []string + ) + + tagsMap := make(map[string]*structures.Set[string]) + + selectedTag := "" + boundSelectedTag := binding.BindString(&selectedTag) + + boundTags := binding.BindStringList(&tags) + boundTagSets := binding.BindStringList(&tagSets) + + tagsList := widget.NewListWithData( + boundTags, + func() fyne.CanvasObject { + return widget.NewLabel("template") + }, + func(di binding.DataItem, co fyne.CanvasObject) { + l := co.(*widget.Label) + l.Bind(di.(binding.String)) + }, + ) + + selectedTagID := -1 + + tagsList.OnSelected = func(id widget.ListItemID) { + selectedTagID = id + boundSelectedTag.Set(tags[id]) + } + + setsList := widget.NewListWithData( + boundTagSets, + func() fyne.CanvasObject { + return widget.NewLabel("template") + }, + func(di binding.DataItem, co fyne.CanvasObject) { + l := co.(*widget.Label) + l.Bind(di.(binding.String)) + }, + ) + + updateSets := func() { + ts, ok := tagsMap[selectedTag] + if ok && ts != nil { + tagSets = ts.AsSlice() + } else { + tagSets = []string{} + } + slices.Sort(tagSets) + boundTagSets.Reload() + } + + boundSelectedTag.AddListener(binding.NewDataListener(updateSets)) + + updateTags := func() { + tags = utils.MapKeys(tagsMap) + slices.Sort(tags) + boundTags.Reload() + if len(tags) > 0 { + if selectedTagID >= 0 { + boundSelectedTag.Set(tags[selectedTagID]) + tagsList.Select(selectedTagID) + } else { + selectedTagID = 0 + boundSelectedTag.Set(tags[0]) + tagsList.Select(0) + } + } else { + selectedTagID = -1 + boundSelectedTag.Set("") + } + } + + var ( + buildGlobalTagSet = true + globalTagSet = a.pkg.Name() + buildTagSetFrpmPrefix = true + prefixSplitMode = false + prefixSplitSeparator = "|" + tagSetPrefixDelimiter = [2]string{"[", "]"} + stripTagSetPrefix = true + stripExtraPrefix = "" + ) + var generateOptions *ddpackage.GenerateTagsOptions = &ddpackage.GenerateTagsOptions{} + + boundBuildGlobalTagSet := binding.BindBool(&buildGlobalTagSet) + boundGlobalTagSet := binding.BindString(&globalTagSet) + + boundBuildTagSetsFromPrefix := binding.BindBool(&buildTagSetFrpmPrefix) + + boundPrefixSplitMode := binding.BindBool(&prefixSplitMode) + boundPrefixSplitSeparator := binding.BindString(&prefixSplitSeparator) + boundPFDStart := binding.BindString(&tagSetPrefixDelimiter[0]) + boundPFDStop := binding.BindString(&tagSetPrefixDelimiter[1]) + + boundStripTagSetPrefix := binding.BindBool(&stripTagSetPrefix) + + boundStripExtraPrefix := binding.BindString(&stripExtraPrefix) + + updateTagsMap := func() { + generateOptions = &ddpackage.GenerateTagsOptions{ + BuildGlobalTagSet: buildGlobalTagSet, + GlobalTagSet: globalTagSet, + BuildTagSetsFromPrefix: buildTagSetFrpmPrefix, + PrefixSplitMode: prefixSplitMode, + TagSetPrefrixDelimiter: func() [2]string { + if prefixSplitMode { + return [2]string{prefixSplitSeparator, ""} + } + return tagSetPrefixDelimiter + }(), + StripTagSetPrefix: stripTagSetPrefix, + StripExtraPrefix: stripExtraPrefix, + } + generator := ddpackage.NewGenerateTags(generateOptions) + tagsMap = generator.TagsFromPath(strings.Join(examplePathParts, "/")) + updateTags() + updateSets() + } + updateTagsMap() + + bindings.AddListenerToAll( + updateTagsMap, + boundBuildGlobalTagSet, + boundGlobalTagSet, + boundBuildTagSetsFromPrefix, + boundPrefixSplitMode, + boundPrefixSplitSeparator, + boundPFDStart, + boundPFDStop, + boundStripTagSetPrefix, + boundStripExtraPrefix, + ) + + examplePathLbl := widget.NewLabel( + lang.X("pathGen.examplePath.label", "Example Path"), + ) + examplePathEntry := widget.NewEntry() + examplePathEntry.SetText(strings.Join(examplePathParts, string(os.PathSeparator))) + + examplePathEntry.OnChanged = func(path string) { + path = strings.ReplaceAll(path, string(os.PathSeparator), "/") + parts := strings.Split(path, "/") + + if len(parts) < 3 { + examplePathParts = []string{"textures", "objects", "object.png"} + examplePathEntry.SetText(strings.Join(examplePathParts, string(os.PathSeparator))) + } else { + examplePathParts = parts + } + updateTagsMap() + } + + examplePathContainer := container.New( + layout.NewFormLayout(), + examplePathLbl, examplePathEntry, + ) + + listsContainer := container.New( + xlayout.NewHPortion([]float64{50, 0.1, 50}), + layouts.NewBottomExpandVBox( + container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameHeaderBackground), + CornerRadius: 4, + }, + container.NewPadded( + widget.NewLabel(lang.X("pathGen.exampleTags.label", "Example Tags")), + ), + ), + tagsList, + ), + widget.NewSeparator(), + layouts.NewBottomExpandVBox( + container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameHeaderBackground), + CornerRadius: 4, + }, + container.NewPadded( + widget.NewLabel(lang.X("pathGen.exampleSets.label", "Example sets tag is in")), + ), + ), + setsList, + ), + ) + + useGlobalTagCheck := widget.NewCheckWithData( + lang.X("pathGen.useGlobalTagCheck.label", "Add all tags to a global tag set"), + boundBuildGlobalTagSet, + ) + globalTagSetEntry := widget.NewEntryWithData(boundGlobalTagSet) + bindings.Listen(boundBuildGlobalTagSet, func(checked bool) { + if checked { + globalTagSetEntry.Enable() + } else { + globalTagSetEntry.Disable() + } + }) + globalTagSetLbl := widget.NewLabel( + lang.X("pathGen.globalTagSet.label", "Global Tag Set Name"), + ) + + buildFromPrefixCheck := widget.NewCheckWithData( + lang.X("pathGen.buildFromPrefixCheck.label", "Build tag sets from prefixes"), + boundBuildTagSetsFromPrefix, + ) + + globalTagContainer := container.NewVBox( + useGlobalTagCheck, + container.New( + layout.NewFormLayout(), + globalTagSetLbl, globalTagSetEntry, + ), + ) + + prefixSplitModeCheck := widget.NewCheckWithData( + lang.X("pathGen.prefixSplitModeCheck.label", "Use a separator instead of a delimited prefix"), + boundPrefixSplitMode, + ) + + prefixDelimStartEntry := widget.NewEntryWithData(boundPFDStart) + prefixDelimStopEntry := widget.NewEntryWithData(boundPFDStop) + prefixDelimStartLbl := widget.NewLabel( + lang.X("pathGen.prefixDelimStart.label", "Start Delimiter"), + ) + prefixDelimStopLbl := widget.NewLabel( + lang.X("pathGen.prefixDelimStop.label", "Stop Delimiter"), + ) + + prefixDelim := container.New( + xlayout.NewHPortion([]float64{50, 50}), + container.New( + layout.NewFormLayout(), + prefixDelimStartLbl, prefixDelimStartEntry, + ), + container.New( + layout.NewFormLayout(), + prefixDelimStopLbl, prefixDelimStopEntry, + ), + ) + + prefixSepEntry := widget.NewEntryWithData(boundPrefixSplitSeparator) + prefixSplitLbl := widget.NewLabel( + lang.X("pathGen.prefixSplit.label", "Prefix Separator"), + ) + + prefixSplit := container.New( + layout.NewFormLayout(), + prefixSplitLbl, prefixSepEntry, + ) + prefixSplit.Hide() + + bindings.Listen(boundPrefixSplitMode, func(checked bool) { + if checked { + prefixDelim.Hide() + prefixSplit.Show() + } else { + prefixDelim.Show() + prefixSplit.Hide() + } + }) + stripPrefixFromTagCheck := widget.NewCheckWithData( + lang.X("pathGen.stripPrefixFromTagCheck.label", "Strip tag set prefix from generated tag"), + boundStripTagSetPrefix, + ) + + prefixContainer := container.NewVBox( + prefixSplitModeCheck, + prefixDelim, + prefixSplit, + stripPrefixFromTagCheck, + ) + + bindings.Listen(boundBuildTagSetsFromPrefix, func(checked bool) { + if checked { + prefixContainer.Show() + } else { + prefixContainer.Hide() + } + }) + + stripExtraPrefixEntry := widget.NewEntryWithData(boundStripExtraPrefix) + stripExtraPrefixLbl := widget.NewLabel( + lang.X("pathGen.stripExtraPrefix", "Prefix to strip from the generated tags"), + ) + + stripExtraContainer := container.New( + layout.NewFormLayout(), + stripExtraPrefixLbl, stripExtraPrefixEntry, + ) + + generateBtn := widget.NewButtonWithIcon( + lang.X("pathGen.generateBtl.label", "Generate"), + theme.ConfirmIcon(), + func() { + log.Info("Generating tags...") + progressVal := binding.NewFloat() + progressBar := widget.NewProgressBarWithData(progressVal) + + progressDlg := dialog.NewCustomWithoutButtons( + lang.X("pathGen.tagProgressDlg.title", "Generating Tags ..."), + container.NewVBox(progressBar), + a.window, + ) + progressDlg.Show() + generator := ddpackage.NewGenerateTags(generateOptions) + a.pkg.GenerateTagsProgress(generator, func(p float64) { + progressVal.Set(p) + }) + progressDlg.Hide() + doneDlg := dialog.NewInformation( + lang.X("pathGen.doneDialog.title", "Tags Generated"), + lang.X("pathGen.doneDialog.msg", "Tags have finished generating."), + a.window, + ) + doneDlg.Show() + }, + ) + + example := layouts.NewBottomExpandVBox( + examplePathContainer, + listsContainer, + ) + + controls := container.NewVBox( + globalTagContainer, + buildFromPrefixCheck, + prefixContainer, + stripExtraContainer, + generateBtn, + ) + + content := container.NewPadded( + layouts.NewTopExpandVBox( + example, + controls, + ), + ) + + genTagsDlg := dialog.NewCustom( + lang.X("pathGen.dialog.title", "Generate Tags"), + lang.X("pathGen.dialog.dismiss", "Close"), + content, + a.window, + ) + + genTagsDlg.Resize( + fyne.NewSize( + fyne.Min(a.window.Canvas().Size().Width, 800), + fyne.Min(a.window.Canvas().Size().Height, 760), + ), + ) + + return genTagsDlg +} diff --git a/internal/gui/translation/en-US.json b/internal/gui/translation/en-US.json index 8ae140d..ea2062c 100644 --- a/internal/gui/translation/en-US.json +++ b/internal/gui/translation/en-US.json @@ -85,6 +85,26 @@ "tagSets.tagsFor.label.text": "Tags for Set: {{.Set}}", "tagSets.setAddBtn.text": "Add", "tagSets.tagAddBtn.text": "Add", + "generateTageBtn.label": "Generate Tags", + "pathGen.dialog.title": "Generate Tags", + "pathGen.dialog.dismiss": "Close", + "pathGen.doneDialog.title": "Tags Generated", + "pathGen.doneDialog.msg": "Tags have finished generating.", + "pathGen.tagProgressDlg.title": "Generating Tags ...", + "pathGen.exampleTags.label": "Example tags", + "pathGen.exampleSets.label": "Example sets tag is in", + "pathGen.examplePath.label": "Example Path", + "pathGen.buildTagSetsCheck.label": "Use prefix to build tag sets", + "pathGen.useGlobalTagCheck.label": "Add all tags to a global tag set", + "pathGen.globalTagSet.label": "Global Tag Set Name", + "pathGen.buildFromPrefixCheck.label": "Build tag sets from prefixes", + "pathGen.prefixSplitModeCheck.label": "Use a separator instead of a delimited prefix", + "pathGen.prefixDelimStart.label": "Start Delimiter", + "pathGen.prefixDelimStop.label": "Stop Delimiter", + "pathGen.prefixSplit.label": "Prefix Separator", + "pathGen.stripPrefixFromTagCheck.label": "Strip tag set prefix from generated tag", + "pathGen.stripExtraPrefix": "Prefix to strip from the generated tags", + "pathGen.generateBtl.label": "Generate", "Open": "Open", "Cancel": "Cancel", "File": "File", diff --git a/internal/gui/unpack.go b/internal/gui/unpack.go index c23870d..803e5cb 100644 --- a/internal/gui/unpack.go +++ b/internal/gui/unpack.go @@ -11,6 +11,7 @@ import ( "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -96,6 +97,12 @@ func (a *App) setPackContent(pkg *ddpackage.Package) { fyne.Min(a.window.Canvas().Size().Height, 580), ), ) + outPath := a.app.Preferences().String("unpack.outPath") + outPathURI := storage.NewFileURI(outPath) + lisableOutPath, err := storage.ListerForURI(outPathURI) + if err == nil { + dlg.SetLocation(lisableOutPath) + } dlg.Show() }) diff --git a/pkg/ddpackage/auto_tags.go b/pkg/ddpackage/auto_tags.go new file mode 100644 index 0000000..5144ba5 --- /dev/null +++ b/pkg/ddpackage/auto_tags.go @@ -0,0 +1,168 @@ +package ddpackage + +import ( + "fmt" + "regexp" + "slices" + "strings" + + "github.com/ryex/dungeondraft-gopackager/internal/utils" + "github.com/ryex/dungeondraft-gopackager/pkg/structures" + log "github.com/sirupsen/logrus" +) + +func (p *Package) GenerateTags(generator *GenerateTags) { + p.generateTags(generator, nil) +} + +func (p *Package) GenerateTagsProgress(generator *GenerateTags, progressCallback func(p float64)) { + p.generateTags(generator, progressCallback) +} + +func (p *Package) generateTags(generator *GenerateTags, pcb func(p float64)) { + for i, fi := range p.fileList { + if fi.IsTaggable() { + tagsMap := generator.TagsFromPath(fi.CalcRelPath()) + for tag, sets := range tagsMap { + p.Tags().Tag(tag, fi.ResPath) + for _, set := range sets.AsSlice() { + p.Tags().AddTagToSet(set, tag) + } + } + } + if pcb != nil { + pcb(float64(i) / float64(len(p.fileList))) + } + } +} + +type GenerateTagsOptions struct { + BuildGlobalTagSet bool + GlobalTagSet string + BuildTagSetsFromPrefix bool + PrefixSplitMode bool + TagSetPrefrixDelimiter [2]string + StripTagSetPrefix bool + StripExtraPrefix string +} + +type GenerateTags struct { + options *GenerateTagsOptions + tagSetRegex *regexp.Regexp + tagSetSplitter func(string) (string, string) +} + +func NewGenerateTags(options *GenerateTagsOptions) *GenerateTags { + gt := &GenerateTags{options: options} + gt.setupTagSetSplitter() + return gt +} + +// returns a map of tasg to the set of sets they should live in +func (gt *GenerateTags) TagsFromPath(path string) (tagsMap map[string]*structures.Set[string]) { + tagsMap = make(map[string]*structures.Set[string]) + if gt.tagSetSplitter == nil { + return + } + + pathParts := strings.Split(path, "/") + + if len(pathParts) <= 3 { + // no potential tags in path + return + } + + // strip off textures/[objects]/ + pathParts = pathParts[2:] + // strip off file name + pathParts = pathParts[:len(pathParts)-1] + + for _, part := range pathParts { + part = strings.TrimSpace(part) + first := true + var sets []string + var set, rest string + rest = part + for first || (set != "") { + if first { + first = false + } + set, rest = gt.tagSetSplitter(rest) + if set != "" && !slices.Contains(sets, set) { + sets = append(sets, set) + } + rest = strings.TrimSpace(rest) + } + var tag string + if gt.options.StripTagSetPrefix { + tag = strings.TrimSpace(rest) + } else { + tag = strings.TrimSpace(part) + } + if gt.options.StripExtraPrefix != "" { + tag = strings.TrimPrefix(tag, gt.options.StripExtraPrefix) + } + if tag == "" { + continue + } + if _, ok := tagsMap[tag]; !ok { + tagsMap[tag] = structures.NewSet[string]() + } + for _, set := range sets { + tagsMap[tag].Add(set) + } + if gt.options.BuildGlobalTagSet && gt.options.GlobalTagSet != "" { + tagsMap[tag].Add(gt.options.GlobalTagSet) + } + } + return +} + +func (gt *GenerateTags) setupTagSetSplitter() { + gt.tagSetSplitter = gt.splitNoOp + if !gt.options.BuildTagSetsFromPrefix { + return + } + + if gt.options.TagSetPrefrixDelimiter[0] == "" { + return + } + if gt.options.PrefixSplitMode || gt.options.TagSetPrefrixDelimiter[1] == "" { + gt.tagSetSplitter = gt.splitSingleSep + return + } + + pattern := fmt.Sprintf( + `^%s(.*?)%s`, + regexp.QuoteMeta(gt.options.TagSetPrefrixDelimiter[0]), + regexp.QuoteMeta(gt.options.TagSetPrefrixDelimiter[1]), + ) + re, err := regexp.Compile(pattern) + if err != nil { + log.WithError(err).Warnf("generated Tag separator pattern '%s' is not a valid regex", pattern) + } + gt.tagSetRegex = re + gt.tagSetSplitter = gt.splitStartStopSep +} + +func (gt *GenerateTags) splitSingleSep(part string) (set string, rest string) { + set, rest = utils.SplitOne(part, gt.options.TagSetPrefrixDelimiter[0]) + return +} + +func (gt *GenerateTags) splitStartStopSep(part string) (string, string) { + if gt.tagSetRegex == nil { + return "", part + } + match := gt.tagSetRegex.FindStringSubmatch(part) + if len(match) == 1 { + return match[0], strings.Replace(part, match[0], "", 1) + } else if len(match) == 2 { + return match[1], strings.Replace(part, match[0], "", 1) + } + return "", part +} + +func (gt *GenerateTags) splitNoOp(part string) (string, string) { + return "", part +} diff --git a/pkg/structures/files.go b/pkg/structures/files.go index 7a0943d..d5475e5 100644 --- a/pkg/structures/files.go +++ b/pkg/structures/files.go @@ -114,7 +114,7 @@ func (fi *FileInfo) IsMaterial() bool { } func (fi *FileInfo) IsObject() bool { - return strings.HasPrefix(fi.CalcRelPath(), "textures/paths/") + return strings.HasPrefix(fi.CalcRelPath(), "textures/objects/") } func (fi *FileInfo) IsPath() bool {