From c4cefd5bcb2e2d7a22749a9e582b946139939744 Mon Sep 17 00:00:00 2001 From: Andrey Sokolov Date: Sat, 14 Jul 2018 15:30:49 +0400 Subject: [PATCH] Tags can be changed --- backlog/backlog_item.go | 12 ++++++ backlog/markdown.go | 6 +++ backlog/markdown_metadata.go | 10 +++++ backlog/timeline.go | 13 +++++++ commands/change_tag_command.go | 70 ++++++++++++++++++++++++++++++++++ commands/sync_command.go | 25 ++++++++++-- main.go | 1 + tests/utils_test.go | 9 +++++ utils/utils.go | 30 +++++++++++++++ 9 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 commands/change_tag_command.go diff --git a/backlog/backlog_item.go b/backlog/backlog_item.go index b450933..6c82589 100644 --- a/backlog/backlog_item.go +++ b/backlog/backlog_item.go @@ -157,6 +157,18 @@ func (item *BacklogItem) ClearTimeline(tag string) { item.markdown.RemoveMetadata(fmt.Sprintf("Timeline %s", tag)) } +func (item *BacklogItem) ChangeTimelineTag(oldTag, newTag string) { + oldKey := fmt.Sprintf("Timeline %s", oldTag) + newKey := fmt.Sprintf("Timeline %s", newTag) + + if item.markdown.MetadataValue(newKey) != "" { + item.ClearTimeline(oldTag) + } else { + item.markdown.RemoveMetadata(newKey) + item.markdown.ReplaceMetadataKey(oldKey, newKey) + } +} + func (item *BacklogItem) SetDescription(description string) { if description != "" { description = "\n" + description diff --git a/backlog/markdown.go b/backlog/markdown.go index 06e47db..4490968 100644 --- a/backlog/markdown.go +++ b/backlog/markdown.go @@ -243,6 +243,12 @@ func (content *MarkdownContent) SetMetadataValue(key, value string) { } } +func (content *MarkdownContent) ReplaceMetadataKey(oldKey, newKey string) { + if content.metadata.ReplaceKey(oldKey, newKey) { + content.markDirty() + } +} + func (content *MarkdownContent) RemoveMetadata(key string) { if content.metadata.Remove(key) { content.markDirty() diff --git a/backlog/markdown_metadata.go b/backlog/markdown_metadata.go index 95fda9c..9a186d0 100644 --- a/backlog/markdown_metadata.go +++ b/backlog/markdown_metadata.go @@ -90,6 +90,16 @@ func (m *MarkdownMetadata) SetValue(key, value string) bool { return true } +func (m *MarkdownMetadata) ReplaceKey(oldKey, newKey string) bool { + for _, item := range m.items { + if strings.ToLower(item.key) == strings.ToLower(oldKey) { + item.key = newKey + return true + } + } + return false +} + func (m *MarkdownMetadata) Remove(key string) bool { for i, item := range m.items { if strings.ToLower(item.key) == strings.ToLower(key) { diff --git a/backlog/timeline.go b/backlog/timeline.go index ac0a95f..a05e99d 100644 --- a/backlog/timeline.go +++ b/backlog/timeline.go @@ -99,3 +99,16 @@ func (tg *TimelineGenerator) sortTimelineItems(items []*timelineItem) { return items[i].item.Name() < items[j].item.Name() }) } + +func (tg *TimelineGenerator) RenameTimeline(oldTag, newTag string) error { + timelineDirectory := filepath.Join(tg.rootDir, TimelineDirectoryName) + oldPngPath := filepath.Join(timelineDirectory, fmt.Sprintf("%s.png", oldTag)) + newPngPath := filepath.Join(timelineDirectory, fmt.Sprintf("%s.png", newTag)) + return os.Rename(oldPngPath, newPngPath) +} + +func (tg *TimelineGenerator) RemoveTimeline(tag string) error { + timelineDirectory := filepath.Join(tg.rootDir, TimelineDirectoryName) + pngPath := filepath.Join(timelineDirectory, fmt.Sprintf("%s.png", tag)) + return os.Remove(pngPath) +} diff --git a/commands/change_tag_command.go b/commands/change_tag_command.go new file mode 100644 index 0000000..420361b --- /dev/null +++ b/commands/change_tag_command.go @@ -0,0 +1,70 @@ +package commands + +import ( + "fmt" + "github.com/mreider/agilemarkdown/backlog" + "github.com/mreider/agilemarkdown/utils" + "gopkg.in/urfave/cli.v1" + "path/filepath" + "strings" +) + +var ChangeTagCommand = cli.Command{ + Name: "change-tag", + Usage: "Change a tag", + ArgsUsage: "OLD_TAG NEW_TAG", + Action: func(c *cli.Context) error { + if c.NArg() != 2 { + fmt.Println("old and new tags should be specified") + return nil + } + + rootDir, _ := filepath.Abs(".") + if err := checkIsBacklogDirectory(); err == nil { + rootDir = filepath.Dir(rootDir) + } else if err := checkIsRootDirectory("."); err != nil { + return err + } + + oldTag := strings.ToLower(c.Args()[0]) + newTag := strings.ToLower(c.Args()[1]) + + if oldTag == newTag { + fmt.Println("Old and new tags are equal") + return nil + } + + allTags, itemsTags, ideasTags, _, err := backlog.ItemsAndIdeasTags(rootDir) + if err != nil { + return err + } + + if _, ok := allTags[oldTag]; !ok { + fmt.Printf("Tag '%s' not found.\n", oldTag) + return nil + } + + tagItems := itemsTags[oldTag] + for _, item := range tagItems { + itemTags := item.Tags() + itemTags = utils.RenameItemIgnoreCase(itemTags, oldTag, newTag) + item.SetTags(itemTags) + item.ChangeTimelineTag(oldTag, newTag) + item.Save() + } + + tagIdeas := ideasTags[oldTag] + for _, idea := range tagIdeas { + ideaTags := idea.Tags() + ideaTags = utils.RenameItemIgnoreCase(ideaTags, oldTag, newTag) + idea.SetTags(ideaTags) + idea.Save() + } + + backlog.NewTimelineGenerator(rootDir).RenameTimeline(oldTag, newTag) + + fmt.Printf("Tag '%s' changed to '%s'. Sync to regenerate files.\n", oldTag, newTag) + + return nil + }, +} diff --git a/commands/sync_command.go b/commands/sync_command.go index eaf9755..3787c60 100644 --- a/commands/sync_command.go +++ b/commands/sync_command.go @@ -663,14 +663,31 @@ func (a *SyncAction) updateItemsFileNames(rootDir string) error { } func (a *SyncAction) updateTimeline(rootDir string) error { - timelineDir := filepath.Join(rootDir, backlog.TimelineDirectoryName) - items, err := ioutil.ReadDir(timelineDir) + allTags, itemsTags, _, _, err := backlog.ItemsAndIdeasTags(rootDir) if err != nil { return err } - allTags, _, _, _, err := backlog.ItemsAndIdeasTags(rootDir) - if err != nil { + timelineGenerator := backlog.NewTimelineGenerator(rootDir) + for tag, tagItems := range itemsTags { + hasTimeline := false + for _, item := range tagItems { + startDate, endDate := item.Timeline(tag) + if !startDate.IsZero() && !endDate.IsZero() { + hasTimeline = true + break + } + } + if hasTimeline { + timelineGenerator.ExecuteForTag(tag) + } else { + timelineGenerator.RemoveTimeline(tag) + } + } + + timelineDir := filepath.Join(rootDir, backlog.TimelineDirectoryName) + items, err := ioutil.ReadDir(timelineDir) + if err != nil && !os.IsNotExist(err) { return err } diff --git a/main.go b/main.go index 277c5b9..de96956 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,7 @@ func main() { commands.CreateUserCommand, commands.TimelineCommand, commands.DeleteTagCommand, + commands.ChangeTagCommand, } err = app.Run(os.Args) diff --git a/tests/utils_test.go b/tests/utils_test.go index 7d60bc9..4901dc7 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -89,6 +89,15 @@ func TestRemoveItemIgnoreCase(t *testing.T) { assert.Equal(t, []string{"b", "c"}, utils.RemoveItemIgnoreCase([]string{"A", "b", "c"}, "a")) } +func TestRenameItemIgnoreCase(t *testing.T) { + assert.Equal(t, []string(nil), utils.RenameItemIgnoreCase(nil, "a", "b")) + assert.Equal(t, []string{}, utils.RenameItemIgnoreCase([]string{}, "a", "b")) + assert.Equal(t, []string{"A", "D", "C"}, utils.RenameItemIgnoreCase([]string{"A", "B", "C"}, "b", "D")) + assert.Equal(t, []string{"A", "C", "d"}, utils.RenameItemIgnoreCase([]string{"A", "B", "C", "d"}, "b", "D")) + assert.Equal(t, []string{"A", "B", "C", "d"}, utils.RenameItemIgnoreCase([]string{"A", "B", "C", "d"}, "e", "D")) + assert.Equal(t, []string{"A", "B", "C", "d"}, utils.RenameItemIgnoreCase([]string{"A", "B", "C", "d"}, "d", "D")) +} + func createDate(year int, month time.Month, day int) time.Time { return time.Date(year, month, day, 0, 0, 0, 0, time.Local) } diff --git a/utils/utils.go b/utils/utils.go index ebf09ac..42e24ad 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -169,3 +169,33 @@ func RemoveItemIgnoreCase(items []string, item string) []string { } return result } + +func RenameItemIgnoreCase(items []string, oldItem, newItem string) []string { + if len(items) == 0 { + return items + } + + if strings.ToLower(oldItem) == strings.ToLower(newItem) { + return items + } + + newItemExists := false + for _, it := range items { + if strings.ToLower(it) == strings.ToLower(newItem) { + newItemExists = true + break + } + } + + oldItem = strings.ToLower(oldItem) + result := make([]string, 0, len(items)) + for _, it := range items { + if strings.ToLower(it) != oldItem { + result = append(result, it) + } else if !newItemExists { + result = append(result, newItem) + newItemExists = true + } + } + return result +}