package seculardb
import (
"strings"
"sync"
"github.com/dustin/go-humanize"
)
// Rating enumerates the nominal rating values
type Rating int
// These are the basic ratings
const (
RatingUnconfirmed Rating = iota
RatingNotSecular
RatingNeutral
RatingMostlySecular
RatingSecular
RatingSuperSecular
)
func (r Rating) String() string {
switch r {
case 5:
return "Super Secular!"
case 4:
return "Secular"
case 3:
return "Mostly Secular"
case 2:
return "Neutral"
case 1:
return "NOT Secular"
case 0:
fallthrough
default:
return "Unconfirmed"
}
}
// Entry encapsulates a single row in the Guide
type Entry struct {
Name, Description, URL string
GradeLevels, Subjects []string
Rating Rating
}
// DB encapsulates the Guide rows
type DB struct {
Entries []Entry
}
// EntryMatcher provides a builder to specify criteria for filtering Entries
type EntryMatcher struct {
// keywords []string
rating int
grades []int
names, subjects []string
}
/*
func (m *EntryMatcher) Keyword(v string) *EntryMatcher {
m.keywords = append(m.keywords, v)
return m
}
*/
// Rating is used to add a minimum entry rating to match on
func (m *EntryMatcher) Rating(v int) *EntryMatcher {
m.rating = v
return m
}
// Grade is used to add a grade level to match on
func (m *EntryMatcher) Grade(v int) *EntryMatcher {
m.grades = append(m.grades, v)
return m
}
// Grades is used to add a slice of grade levels to match on
func (m *EntryMatcher) Grades(v []int) *EntryMatcher {
m.grades = v
return m
}
// Name is used to add a name to match on
func (m *EntryMatcher) Name(v string) *EntryMatcher {
m.names = append(m.names, v)
return m
}
// Names is used to add a slice of names to match on
func (m *EntryMatcher) Names(v []string) *EntryMatcher {
m.names = v
return m
}
// Subject is used to add a subject to match on
func (m *EntryMatcher) Subject(v string) *EntryMatcher {
m.subjects = append(m.subjects, v)
return m
}
// Subjects is used to add a slice of subjects to match on
func (m *EntryMatcher) Subjects(v []string) *EntryMatcher {
m.subjects = v
return m
}
func (m *EntryMatcher) matches(e Entry) (_ bool) {
if e.Rating < Rating(m.rating) {
return
}
if len(m.grades) > 0 {
var found bool
for _, fgrade := range m.grades {
for _, grade := range e.GradeLevels {
switch g := strings.ToLower(grade); {
case g == "pre-k" && fgrade == -1:
fallthrough
case g == "k" && fgrade == 0:
fallthrough
case humanize.Ordinal(fgrade) == g || g == "allages":
found = true
}
}
}
if !found {
return
}
}
if len(m.names) > 0 {
var found bool
for _, fname := range m.names {
if strings.Contains(strings.ToLower(e.Name), strings.ToLower(fname)) {
found = true
}
}
if !found {
return
}
}
if len(m.subjects) > 0 {
var found bool
for _, fsubject := range m.subjects {
fsubject = strings.ToLower(fsubject)
for _, subject := range e.Subjects {
if strings.Contains(strings.ToLower(subject), fsubject) {
found = true
}
}
}
if !found {
return
}
}
return true
}
// NewMatcher creates a new EntryMatcher instance
func NewMatcher() *EntryMatcher {
return new(EntryMatcher)
}
// Filter returns a copy of a DB object with only the Entries which matched against the EntryMatcher
func Filter(db DB, matcher *EntryMatcher) (filtered DB, err error) {
filtered.Entries = make([]Entry, 0)
var mu sync.Mutex
process := make(chan Entry, 5)
var wg sync.WaitGroup
for w := 1; w <= 20; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for e := range process {
if matcher.matches(e) {
mu.Lock()
filtered.Entries = append(filtered.Entries, e)
mu.Unlock()
}
}
}()
}
for _, e := range db.Entries {
process <- e
}
close(process)
wg.Wait()
return
}