diff --git a/category/tags.go b/category/tags.go index 9b99248..a3a092a 100644 --- a/category/tags.go +++ b/category/tags.go @@ -24,3 +24,24 @@ func titleFromHashtag(hashtag string) string { } return t.String() } + +// HashtagFromTitle generates a valid single-word, camelCase hashtag from a title (which might include spaces, +// punctuation, etc.). +func HashtagFromTitle(title string) string { + var t strings.Builder + var prev rune + for _, c := range title { + if !unicode.IsLetter(c) && !unicode.IsNumber(c) { + prev = c + continue + } + if unicode.IsSpace(prev) { + // Uppercase next word + t.WriteRune(unicode.ToUpper(c)) + } else { + t.WriteRune(c) + } + prev = c + } + return t.String() +} diff --git a/category/tags_test.go b/category/tags_test.go index 9889cfc..92a45c1 100644 --- a/category/tags_test.go +++ b/category/tags_test.go @@ -29,3 +29,32 @@ func TestTitleFromHashtag(t *testing.T) { }) } } + +func TestHashtagFromTitle(t *testing.T) { + tests := []struct { + name string + title string + expHashtag string + }{ + {"proper noun", "Jane", "Jane"}, + {"full name", "Jane Doe", "JaneDoe"}, + {"us upper words", "United States", "UnitedStates"}, + {"us lower words", "united states", "unitedStates"}, + {"usa", "USA", "USA"}, + {"100dto", "100 Days To Offload", "100DaysToOffload"}, + {"iphone", "iPhone", "iPhone"}, + {"ilike", "I like this", "ILikeThis"}, + {"abird", "a Bird", "aBird"}, + {"all caps", "URGENT", "URGENT"}, + {"punctuation", "John’s Stories", "JohnsStories"}, + {"smartphone", "スマートフォン", "スマートフォン"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res := HashtagFromTitle(test.title) + if res != test.expHashtag { + t.Fatalf("%s: got '%s' expected '%s'", test.title, res, test.expHashtag) + } + }) + } +}