summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2024-11-06 11:11:34 +0200
committerPaul Buetow <paul@buetow.org>2024-11-06 11:11:34 +0200
commit2d652249ca36219853b34d9f55101ed3cbd109a1 (patch)
treeb95562d42c98cff62f1c68e6a20b116614819815 /internal
parent3551fd060242b5c0ca07d4cc371b1032d0983e38 (diff)
initial inline tag support
Diffstat (limited to 'internal')
-rw-r--r--internal/entry/entry.go62
-rw-r--r--internal/entry/entry_test.go64
-rw-r--r--internal/entry/sharetags.go22
-rw-r--r--internal/entry/sharetags_test.go13
-rw-r--r--internal/queue/queue.go17
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
}
}