~hokiegeek/seculardb

ref: 7dd2adc13a694996b1db0b1011bb29acbd3549c3 seculardb/db.go -rw-r--r-- 3.8 KiB
7dd2adc1HokieGeek Added colors to the Rating column in the HTML page as a finishing touch 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
}