Skip to content

Commit

Permalink
[feat] add ParseTitle() for complete play script parsing scenario (#9)
Browse files Browse the repository at this point in the history
* Update readme

* Add parse title

* Add raw tests

* empty string

* Maybe

* Update comments
  • Loading branch information
hyorigo authored Oct 20, 2023
1 parent 3190643 commit bece1da
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 15 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# blink1-go [WIP]

Yet another Go SDK for blink(1) USB RGB LED notification devices
[![Go Reference](https://pkg.go.dev/badge/github.com/b1ug/blink1-go.svg)](https://pkg.go.dev/github.com/b1ug/blink1-go)
[![GitHub Actions](https://github.com/b1ug/blink1-go/actions/workflows/build.yml/badge.svg)](https://github.com/b1ug/blink1-go/actions/workflows/build.yml)

> Yet another Go SDK for blink(1) USB RGB LED notification devices
33 changes: 29 additions & 4 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,18 @@ var colorMap = map[string]color.Color{

var (
regexOnce sync.Once
titleRegexPat *regexp.Regexp
repeatRegexPat *regexp.Regexp
commentRegexPat *regexp.Regexp
colorRegexPats = make(map[string]*regexp.Regexp)
fadeMsecRegexPats = make(map[int]*regexp.Regexp)
ledIdxRegexPats = make(map[int]*regexp.Regexp)

emptyStr string
nameOnce sync.Once
colorNames []string

errNoTitleMatch = errors.New("b1: no title match")
errNoRepeatMatch = errors.New("b1: no repeat times match")
errNoColorMatch = errors.New("b1: no color match")
errNoFadeMatch = errors.New("b1: no fade time match")
Expand All @@ -70,6 +73,7 @@ func initRegex() {
// for simple patterns
repeatRegexPat = regexp.MustCompile(`\brepeat\s*[:=]*\s*(\d+|\bforever|\balways|\binfinite(?:ly)?)\b|\b(infinite(?:ly)?|forever|always)\s+repeat\b`)
commentRegexPat = regexp.MustCompile(`(\/\/.*?$)`)
titleRegexPat = regexp.MustCompile(`\b(title|topic|idea|subject)\s*[:=]*\s*([^\s].*?[^\s])\s*$`)

// for colors
colorWords := make([]string, 0, len(colorMap))
Expand Down Expand Up @@ -120,6 +124,27 @@ func GetColorNames() []string {
return cls
}

// ParseTitle parses the labeled title or topic or idea string from the query string. It returns the title or an error if no title is found.
func ParseTitle(query string) (string, error) {
// init regex
regexOnce.Do(initRegex)

// match
q := strings.TrimSpace(query)
m := titleRegexPat.FindStringSubmatch(q)
if len(m) <= 2 {
// We now need match of length > 2 as our pattern has a second capture group
return emptyStr, errNoTitleMatch
}

// handle match
title := m[2]
if title == emptyStr {
return emptyStr, errNoTitleMatch
}
return title, nil
}

// ParseRepeatTimes parses the case-insensitive unstructured description of repeat times and returns the number of times to repeat.
func ParseRepeatTimes(query string) (uint, error) {
// init regex
Expand All @@ -135,15 +160,15 @@ func ParseRepeatTimes(query string) (uint, error) {
// handle match
var r string
for i := 1; i < len(m); i++ {
if m[i] != "" {
if m[i] != emptyStr {
r = m[i]
break
}
}
switch r {
case "0", "forever", "always", "infinite", "infinitely":
return 0, nil
case "":
case emptyStr:
return 0, errNoRepeatMatch
default:
times, err := strconv.Atoi(r)
Expand All @@ -170,12 +195,12 @@ func ParseStateQuery(query string) (LightState, error) {
// prepare
var state LightState
query = strings.TrimSpace(strings.ToLower(query))
if query == "" {
if query == emptyStr {
return state, errBlankQuery
}

// remove comments
query = commentRegexPat.ReplaceAllString(query, "")
query = commentRegexPat.ReplaceAllString(query, emptyStr)

// parse each part
var err error
Expand Down
102 changes: 92 additions & 10 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ import (
"github.com/b1ug/blink1-go"
)

func BenchmarkParseTitle(b *testing.B) {
q := `title: Crash Course in Go`
blink1.ParseTitle(q) // I've assumed you're calling ParseTitle function directly in the sample. Adjust as necessary.
b.ResetTimer()
for i := 0; i < b.N; i++ {
blink1.ParseTitle(q)
}
}

func BenchmarkParseRepeatTimes(b *testing.B) {
q := `will repeat 5 times infinitely`
blink1.ParseRepeatTimes(q)
b.ResetTimer()
for i := 0; i < b.N; i++ {
blink1.ParseRepeatTimes(q)
}
}

func BenchmarkParseStateQuery_Simple(b *testing.B) {
q := `(led:2, color:pink, time:500ms)`
blink1.ParseStateQuery(q)
Expand All @@ -27,12 +45,76 @@ func BenchmarkParseStateQuery_Complex(b *testing.B) {
}
}

func BenchmarkParseRepeatTimes(b *testing.B) {
q := `will repeat 5 times infinitely`
blink1.ParseRepeatTimes(q)
b.ResetTimer()
for i := 0; i < b.N; i++ {
blink1.ParseRepeatTimes(q)
func TestParseTitle(t *testing.T) {
tests := []struct {
query string
expected string
wantErr bool
}{
{
query: "title: Crash Course in Go ",
expected: "Crash Course in Go",
},
{
query: "topic = Advanced Topics",
expected: "Advanced Topics",
},
{
query: "topic= Another Topics",
expected: "Another Topics",
},
{
query: "topic =Great Topics ",
expected: "Great Topics",
},
{
query: "idea: Revolutionize AI",
expected: "Revolutionize AI",
},
{
query: "idea::: Revolutionize AI",
expected: "Revolutionize AI",
},
{
query: "title=Deep Reinforcement Learning",
expected: "Deep Reinforcement Learning",
},
{
query: "title No Borders",
expected: "No Borders",
},
{
query: "subject: The Future of Quantum Computing",
expected: "The Future of Quantum Computing",
},
{
query: "subj: The Future of Quantum Computing",
wantErr: true,
},
{
query: "title = ",
wantErr: true,
},
{
query: "topic:",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.query, func(t *testing.T) {
got, err := blink1.ParseTitle(tt.query)
if (err != nil) != tt.wantErr {
t.Errorf("ParseTitle(%q) error = %v, wantErr = %v", tt.query, err, tt.wantErr)
return
}
if err != nil {
return
}
if got != tt.expected {
t.Errorf("ParseTitle(%q) got = %q, want = %q", tt.query, got, tt.expected)
}
})
}
}

Expand Down Expand Up @@ -116,14 +198,14 @@ func TestParseRepeatTimes(t *testing.T) {
t.Run(tt.query, func(t *testing.T) {
got, err := blink1.ParseRepeatTimes(tt.query)
if (err != nil) != tt.wantErr {
t.Errorf("ParseRepeatTimes(%q) error = %v, wantErr %v", tt.query, err, tt.wantErr)
t.Errorf("ParseRepeatTimes(%q) error = %v, wantErr = %v", tt.query, err, tt.wantErr)
return
}
if err != nil {
return
}
if got != tt.times {
t.Errorf("ParseRepeatTimes(%q) = %v, want %v", tt.query, got, tt.times)
t.Errorf("ParseRepeatTimes(%q) got = %q, want = %q", tt.query, got, tt.times)
}
})
}
Expand Down Expand Up @@ -488,14 +570,14 @@ func TestParseStateQuery(t *testing.T) {
t.Run(tt.query, func(t *testing.T) {
got, err := blink1.ParseStateQuery(tt.query)
if (err != nil) != tt.wantErr {
t.Errorf("ParseStateQuery(%q) got error = %v, wantErr %v", tt.query, err, tt.wantErr)
t.Errorf("ParseStateQuery(%q) got error = %v, wantErr = %v", tt.query, err, tt.wantErr)
return
}
if err != nil {
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseStateQuery(%q) = %v, want %v", tt.query, got, tt.want)
t.Errorf("ParseStateQuery(%q) got = %q, want = %q", tt.query, got, tt.want)
}
})
}
Expand Down

0 comments on commit bece1da

Please sign in to comment.