diff options
| author | Paul Buetow <paul@buetow.org> | 2024-11-06 11:11:34 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2024-11-06 11:11:34 +0200 |
| commit | 2d652249ca36219853b34d9f55101ed3cbd109a1 (patch) | |
| tree | b95562d42c98cff62f1c68e6a20b116614819815 /internal | |
| parent | 3551fd060242b5c0ca07d4cc371b1032d0983e38 (diff) | |
initial inline tag support
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/entry/entry.go | 62 | ||||
| -rw-r--r-- | internal/entry/entry_test.go | 64 | ||||
| -rw-r--r-- | internal/entry/sharetags.go | 22 | ||||
| -rw-r--r-- | internal/entry/sharetags_test.go | 13 | ||||
| -rw-r--r-- | internal/queue/queue.go | 17 |
5 files changed, 132 insertions, 46 deletions
diff --git a/internal/entry/entry.go b/internal/entry/entry.go index 3fd2623..f97cfa8 100644 --- a/internal/entry/entry.go +++ b/internal/entry/entry.go @@ -47,10 +47,10 @@ func (s State) String() string { var Zero = Entry{} type Entry struct { - Path string - Time time.Time - State State - simpleTags []string + Path string + Time time.Time + State State + tags map[string]struct{} } func (en Entry) String() string { @@ -64,7 +64,7 @@ func (en Entry) String() string { // or for inboxed: /foo.txt // or inboxed with tags: /foo.prio.ask.txt func New(filePath string) (Entry, error) { - en := Entry{Path: filePath} + en := Entry{Path: filePath, tags: make(map[string]struct{})} // We want to get the STAMP! parts := strings.Split(filePath, ".") @@ -72,12 +72,7 @@ func New(filePath string) (Entry, error) { // Could be 2 if inboxed return en, fmt.Errorf("not a valid entry path: %s", filePath) } - - for _, part := range parts { - if slices.Contains(validTags, part) { - en.simpleTags = append(en.simpleTags, part) - } - } + en.extractTags(parts) switch parts[len(parts)-1] { case "queued": @@ -93,6 +88,7 @@ func New(filePath string) (Entry, error) { // If not inboxed, must be longer. return en, fmt.Errorf("not a valid entry path: %s", filePath) } + var err error if en.Time, err = timestamp.Parse(parts[len(parts)-2]); err != nil { return en, err @@ -110,11 +106,14 @@ func (en *Entry) Content() (string, []string, error) { return content, extractURLs(content), err } +// Returns the content and also checks for the size limit and also removes any +// inline tags. func (en Entry) ContentWithLimit(sizeLimit int) (string, []string, error) { content, urls, err := en.Content() if err != nil { return "", urls, err } + // TODO: Handle inline tags, use the extractInlineTags method of entry if len(content) > sizeLimit { err := fmt.Errorf("%w (%d > %d): %v", ErrSizeLimitExceeded, len(content), sizeLimit, en) if err2 := prompt.Acknowledge("You need to shorten the content as "+err.Error(), content); err2 != nil { @@ -150,14 +149,15 @@ func (en *Entry) MarkPosted() error { } func (en Entry) HasTag(tag string) bool { - return slices.Contains(en.simpleTags, tag) + _, ok := en.tags[tag] + return ok } // Valid tags are: share:foo[,...] // whereas foo can be a supported plutform such as linkedin, mastodon, etc. // foo can also be prefixed with - to exclude it. See unit tests for examples. func (en Entry) PlatformExcluded(args config.Args, platform string) (bool, error) { - s, err := newShareTags(args, en.Path) + s, err := newShareTags(args, en.tags) return slices.Contains(s.excludes, strings.ToLower(platform)) || !slices.Contains(s.includes, strings.ToLower(platform)), err } @@ -181,8 +181,44 @@ func (en Entry) FileAction(question string) error { return prompt.FileAction(question, content, en.Path) } +func (en Entry) extractTags(parts []string) { + for _, part := range parts { + if slices.Contains(validTags, part) || strings.HasPrefix(part, "share:") { + en.tags[part] = struct{}{} + } + } +} + +func (en Entry) ExtractInlineTags() error { + content, _, err := en.Content() + if err != nil { + return err + } + if tags, _, ok := extractInlineTags(content); ok { + en.extractTags(tags) + } + return nil +} + func extractURLs(input string) []string { urlPattern := `(http://|https://|ftp://)[^\s]+` re := regexp.MustCompile(urlPattern) return re.FindAllString(input, -1) } + +// Returns inline tags, real content. And true when there were inline tags. +func extractInlineTags(content string) ([]string, string, bool) { + parts := strings.Split(content, " ") + // If the first word of the content contains a dot or comma and there are + // more than 2 elems, then there are inline tags! + if strings.Contains(parts[0], ".") || strings.Contains(parts[0], ",") { + var tags []string + for _, elem := range strings.Split(parts[0], ".") { + tags = append(tags, strings.Split(elem, ",")...) + } + if len(tags) > 1 { + return tags, strings.Join(parts[1:], " "), true + } + } + return []string{}, content, false +} diff --git a/internal/entry/entry_test.go b/internal/entry/entry_test.go index 55d2379..d64531f 100644 --- a/internal/entry/entry_test.go +++ b/internal/entry/entry_test.go @@ -18,28 +18,50 @@ func TestEntry(t *testing.T) { for _, stamp := range stamps { queuedPath := fmt.Sprintf("gosdir/db/platforms/linkedin/helloworld.txt.%s.%s", stamp, state) - ent, err := New(queuedPath) + en, err := New(queuedPath) if err != nil { t.Error(err) } - if ent.Path != queuedPath { - t.Errorf("expected path %s but got %s", queuedPath, ent.Path) + if en.Path != queuedPath { + t.Errorf("expected path %s but got %s", queuedPath, en.Path) } - if ent.State != state { - t.Errorf("expected state %s but got %s", state, ent.State) + if en.State != state { + t.Errorf("expected state %s but got %s", state, en.State) } expectedTime, err := timestamp.Parse(stamp) if err != nil { t.Error(err) } - if ent.Time != expectedTime { - t.Errorf("expected time to be %v but got %v", expectedTime, ent.Time) + if en.Time != expectedTime { + t.Errorf("expected time to be %v but got %v", expectedTime, en.Time) } } } } +func TestEntryTags(t *testing.T) { + tagss := []string{"prio", "share:linkedin:mastodon.now", "share:-mastodon", "ask", "invalid"} + + for _, tagsStr := range tagss { + queuedPath := fmt.Sprintf("gosdir/db/platforms/linkedin/helloworld.%s.txt.20241111-111111.20241111-111111", tagsStr) + en, err := New(queuedPath) + if err != nil { + t.Error(err) + } + for _, expectedTag := range strings.Split(tagsStr, ".") { + if expectedTag == "invalid" { + if en.HasTag(expectedTag) { + t.Errorf("didn't expect tag '%s' to be present, but got '%v'", expectedTag, en.tags) + } + continue + } + if !en.HasTag(expectedTag) { + t.Errorf("expected tag '%s' to be present, but got '%v'", expectedTag, en.tags) + } + } + } +} func TestExtractTwoURLs(t *testing.T) { text := `Hello world https://foo.zone Hello universe http://world.universe test 123` @@ -89,21 +111,39 @@ func TestHasTag(t *testing.T) { } for fileName, expectedTags := range table { - ent, err := New(fileName) + en, err := New(fileName) if err != nil { t.Error(err) } - if len(expectedTags) != len(ent.simpleTags) { - t.Errorf("expected '%d' tags but got '%d'", len(expectedTags), len(ent.simpleTags)) + if len(expectedTags) != len(en.tags) { + t.Errorf("expected '%d' tags but got '%d'", len(expectedTags), len(en.tags)) } for _, tag := range expectedTags { - if !ent.HasTag(tag) { - t.Errorf("expected tag '%s' but got '%s'", tag, ent.simpleTags) + if !en.HasTag(tag) { + t.Errorf("expected tag '%s' but got '%s'", tag, en.tags) } } } } +func TestExtractInlineTags(t *testing.T) { + tags, contentWithoutTags, ok := extractInlineTags(`share,foo.bar this is the main content`) + if !ok { + t.Error("expected inline tags") + } + if len(tags) != 3 { + t.Error("expected 3 inline tags") + } + for _, expectedTag := range []string{"share", "foo", "bar"} { + if !slices.Contains(tags, expectedTag) { + t.Errorf("expected '%s' to be an inline tag but got '%v'", expectedTag, tags) + } + } + if contentWithoutTags != "this is the main content" { + t.Errorf("expected the main content to be 'this is the main content' but got '%s'", contentWithoutTags) + } +} + func FuzzExtractURLs(f *testing.F) { f.Add("/path?myjfa=lwsr4imj&dgqeg=m3uwwsak") f.Add("/?amfbm=bwzqu46m&xheuh=nv588d98") diff --git a/internal/entry/sharetags.go b/internal/entry/sharetags.go index 248290f..4087d6a 100644 --- a/internal/entry/sharetags.go +++ b/internal/entry/sharetags.go @@ -1,7 +1,6 @@ package entry import ( - "fmt" "slices" "strings" @@ -14,21 +13,18 @@ type shareTags struct { } // TODO: Inline tag support, like in quicklogger. -func newShareTags(args config.Args, filePath string) (shareTags, error) { +func newShareTags(args config.Args, tags map[string]struct{}) (shareTags, error) { var s shareTags - parts := strings.Split(filePath, ".") - if len(parts) < 4 { - return s, fmt.Errorf("invalid file path: %s", filePath) - } - tagStr := parts[len(parts)-4] - - if len(parts) > 2 && strings.HasPrefix(tagStr, "share:") { - for _, tag := range strings.Split(tagStr[6:], ":") { - if strings.HasPrefix(tag, "-") { - s.excludes = append(s.excludes, strings.ToLower(tag[1:])) + for tag := range tags { + if !strings.HasPrefix(tag, "share:") { + continue + } + for _, t := range strings.Split(tag[6:], ":") { + if strings.HasPrefix(t, "-") { + s.excludes = append(s.excludes, strings.ToLower(t[1:])) } else { - s.includes = append(s.includes, strings.ToLower(tag)) + s.includes = append(s.includes, strings.ToLower(t)) } } } diff --git a/internal/entry/sharetags_test.go b/internal/entry/sharetags_test.go index ba97842..60589f5 100644 --- a/internal/entry/sharetags_test.go +++ b/internal/entry/sharetags_test.go @@ -2,6 +2,7 @@ package entry import ( "slices" + "strings" "testing" "codeberg.org/snonux/gos/internal/config" @@ -35,7 +36,7 @@ func TestShareTagsPositive(t *testing.T) { for filePath, expectedResult := range testTable { t.Run(filePath, func(t *testing.T) { - shareTags, err := newShareTags(args, filePath) + shareTags, err := newShareTags(args, filePathTags(filePath)) if err != nil { t.Error(err) } @@ -74,7 +75,7 @@ func TestShareTagsNegative(t *testing.T) { for filePath, unexpectedResult := range testTable { t.Run(filePath, func(t *testing.T) { - shareTags, err := newShareTags(args, filePath) + shareTags, err := newShareTags(args, filePathTags(filePath)) if err != nil { t.Error(err) } @@ -100,3 +101,11 @@ func sameElements(a, b []string) bool { } return true } + +func filePathTags(filePath string) map[string]struct{} { + tags := make(map[string]struct{}) + for _, tag := range strings.Split(filePath, ".") { + tags[tag] = struct{}{} + } + return tags +} diff --git a/internal/queue/queue.go b/internal/queue/queue.go index 87aae65..9d846e0 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -35,21 +35,26 @@ func queueEntries(args config.Args) error { } for filePath := range ch { - ent, err := entry.New(filePath) + en, err := entry.New(filePath) if err != nil { return err } - if ent.HasTag("ask") { - if err := ent.FileAction("Do you want to queue this content"); err != nil { + // Extract any inline tags, if any! + if err := en.ExtractInlineTags(); err != nil { + return err + } + if en.HasTag("ask") { + // TODO: Handle inline tags + if err := en.FileAction("Do you want to queue this content"); err != nil { return err } } - destPath := fmt.Sprintf("%s/db/%s.%s.queued", args.GosDir, filepath.Base(ent.Path), timestamp.Now()) + destPath := fmt.Sprintf("%s/db/%s.%s.queued", args.GosDir, filepath.Base(en.Path), timestamp.Now()) if args.DryRun { - log.Println("Not queueing entry", ent.Path, "to", destPath, "as dry-run mode enabled") + log.Println("Not queueing entry", en.Path, "to", destPath, "as dry-run mode enabled") continue } - if err := oi.Rename(ent.Path, destPath); err != nil { + if err := oi.Rename(en.Path, destPath); err != nil { return err } } |
