Skip to content

Commit 32af6da

Browse files
authored
Merge pull request #86 from AnshSharma100/Api-tool_Doc
Add documentation for API tool [issue #23]
2 parents cad0192 + 3536109 commit 32af6da

25 files changed

+131
-84
lines changed

parser/astraParser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import (
1111
"github.com/UTDNebula/nebula-api/api/schema"
1212
)
1313

14+
// InputData describes the raw Astra export payload containing fields metadata and row values.
1415
type InputData struct {
1516
Fields string `json:"fields"`
1617
Data [][]interface{} `json:"data"`
1718
}
1819

20+
// ParseAstra reads Astra scrape output and produces structured multi-building event JSON files.
1921
func ParseAstra(inDir string, outDir string) {
2022

2123
astraFile, err := os.ReadFile(inDir + "/astraScraped.json")

parser/courseParser_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/UTDNebula/nebula-api/api/schema"
1010
)
1111

12+
// TestGetCourse checks course parsing from HTML fixtures.
1213
func TestGetCourse(t *testing.T) {
1314
t.Parallel()
1415

@@ -28,6 +29,7 @@ func TestGetCourse(t *testing.T) {
2829
}
2930
}
3031

32+
// TestGetCatalogYear ensures catalog year derivation matches expected academic sessions.
3133
func TestGetCatalogYear(t *testing.T) {
3234
t.Parallel()
3335

@@ -88,6 +90,7 @@ func TestGetCatalogYear(t *testing.T) {
8890
}
8991
}
9092

93+
// TestGetPrefixAndCourseNum verifies extraction of subject prefixes and course numbers.
9194
func TestGetPrefixAndCourseNum(t *testing.T) {
9295
t.Parallel()
9396

parser/mapParser.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import (
1212
"github.com/UTDNebula/nebula-api/api/schema"
1313
)
1414

15-
// Found under "Academic & Administrative" and "Housing" on https://api.concept3d.com/categories/?map=1772&key=0001085cc708b9cef47080f064612ca5
15+
// BUILDINGS_CATEGORY_IDS lists category identifiers for academic, administrative, and housing buildings on Concept3D.
1616
var BUILDINGS_CATEGORY_IDS = []int{42138, 42141}
1717

1818
var acronymRegex = regexp.MustCompile(`.*\((.*)\)`)
1919

20+
// ParseMapLocations filters Concept3D location exports to building records and writes normalized JSON output.
2021
func ParseMapLocations(inDir string, outDir string) {
2122
mapFile, err := os.ReadFile(inDir + "/mapLocationsScraped.json")
2223
if err != nil {

parser/mazevoParser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ var buildingRenames = map[string]string{
1616
"Student Services Addition (SSA)": "SSA",
1717
}
1818

19+
// SourceData represents the Mazevo API response containing booking records.
1920
type SourceData struct {
2021
Bookings []map[string]interface{} `json:"bookings"`
2122
}
2223

24+
// ParseMazevo reads Mazevo scrape output and emits normalized multi-building event JSON.
2325
func ParseMazevo(inDir string, outDir string) {
2426

2527
mazevoFile, err := os.ReadFile(inDir + "/mazevoScraped.json")

parser/parser.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Package parser converts scraped course and scheduling inputs into structured Nebula API schema documents.
12
package parser
23

34
import (
@@ -14,32 +15,32 @@ import (
1415
)
1516

1617
var (
17-
// Sections dictionary for mapping UUIDs to a *schema.Section
18+
// Sections maps section IDs to the associated section records.
1819
Sections = make(map[primitive.ObjectID]*schema.Section)
1920

20-
// Courses dictionary for keys (Internal_course_number + Catalog_year) to a *schema.Course
21+
// Courses maps catalog identifiers to course definitions.
2122
Courses = make(map[string]*schema.Course)
2223

23-
// Professors dictionary for keys (First_name + Last_name) to a *schema.Professor
24+
// Professors maps professor names to professor documents.
2425
Professors = make(map[string]*schema.Professor)
2526

26-
//CourseIDMap auxiliary dictionary for mapping UUIDs to a *schema.Course
27+
// CourseIDMap maps course IDs to their catalog keys.
2728
CourseIDMap = make(map[primitive.ObjectID]string)
2829

29-
//ProfessorIDMap auxiliary dictionary for mapping UUIDs to a *schema.Professor
30+
// ProfessorIDMap maps professor IDs to their lookup keys.
3031
ProfessorIDMap = make(map[primitive.ObjectID]string)
3132

32-
// ReqParsers dictionary mapping course UUIDs to the func() that parsers its Reqs
33+
// ReqParsers maps course IDs to requisite parser functions.
3334
ReqParsers = make(map[primitive.ObjectID]func())
3435

35-
// GradeMap mappings for section grade distributions, mapping is MAP[SEMESTER] -> MAP[SUBJECT + NUMBER + SECTION] -> GRADE DISTRIBUTION
36+
// GradeMap stores grade distributions keyed by semester and section identifier.
3637
GradeMap map[string]map[string][]int
3738

38-
// timeLocation Time location for dates (uses America/Chicago tz database zone for CDT which accounts for daylight saving)
39+
// timeLocation captures the America/Chicago location for timestamp normalization.
3940
timeLocation, timeError = time.LoadLocation("America/Chicago")
4041
)
4142

42-
// Parse Externally exposed parse function
43+
// Parse loads scraped course artifacts, applies parsing and validation, and persists structured results.
4344
func Parse(inDir string, outDir string, csvPath string, skipValidation bool) {
4445

4546
// Panic if timeLocation didn't load properly

parser/parser_test.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"go.mongodb.org/mongo-driver/bson/primitive"
2222
)
2323

24+
// TestData bundles a parser test input with its expected artifacts.
2425
type TestData struct {
2526
Input string
2627
RowInfo map[string]*goquery.Selection
@@ -33,14 +34,7 @@ type TestData struct {
3334
// testData global dictionary containing the data from /testdata by folder name
3435
var testData map[string]TestData
3536

36-
// TestMain entry point for all tests in the parser package.
37-
// The function will load `./testdata` into memory before running
38-
// the tests so that test can run in parallel.
39-
//
40-
// You can optionally provide the flag `update`, which will run
41-
// updateTestData. Example usage
42-
//
43-
// `go test -v ./parser -args -update`
37+
// TestMain loads parser fixtures and handles the -update flag for regenerating expectations.
4438
func TestMain(m *testing.M) {
4539
update := flag.Bool("update", false, "Regenerates the expected output for the provided test inputs. Should only be used when you are 100% sure your code is correct! It will make all test pass :)")
4640

@@ -247,6 +241,7 @@ func clearGlobals() {
247241
ReqParsers = make(map[primitive.ObjectID]func())
248242
}
249243

244+
// TestParse verifies that parsing input fixtures generates the expected JSON exports.
250245
func TestParse(t *testing.T) {
251246
tempDir := t.TempDir()
252247
// todo fix grade data, csvPath = ./grade-data panics
@@ -496,6 +491,7 @@ func unmarshallFile[T any](path string) (T, error) {
496491
return result, nil
497492
}
498493

494+
// TestGetClassInfo validates extraction of class metadata from course pages.
499495
func TestGetClassInfo(t *testing.T) {
500496
t.Parallel()
501497

@@ -519,6 +515,7 @@ func TestGetClassInfo(t *testing.T) {
519515
}
520516
}
521517

518+
// TestGetRowInfo confirms table rows are mapped to labels and content correctly.
522519
func TestGetRowInfo(t *testing.T) {
523520
t.Parallel()
524521
// don't include any weird characters in the content, it's not a bug with getRowInfo but

parser/requisiteParser.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
It's worth noting that I say stack in quotes above because it's not treated as strictly LIFO like a stack would normally be.
2222
*/
2323

24-
// Regex matcher object for requisite group parsing
24+
// Matcher defines a regex-driven handler used during requisite group parsing.
2525
type Matcher struct {
2626
Regex *regexp.Regexp
2727
Handler func(string, []string) interface{}
@@ -31,6 +31,7 @@ type Matcher struct {
3131

3232
var ANDRegex = regexp.MustCompile(`(?i)\s+and\s+`)
3333

34+
// ANDMatcher parses conjunction-separated requisites into an AND collection requirement.
3435
func ANDMatcher(group string, subgroups []string) interface{} {
3536
// Split text along " and " boundaries, then parse subexpressions as groups into an "AND" CollectionRequirement
3637
subExpressions := ANDRegex.Split(group, -1)
@@ -52,12 +53,8 @@ func ANDMatcher(group string, subgroups []string) interface{} {
5253
}
5354
}
5455

55-
// First regex subgroup represents the text to be subgrouped and parsed with parseFnc
56-
// Ex: Text is: "(OPRE 3360 or STAT 3360 or STAT 4351), and JSOM majors and minors only"
57-
// Regex is: "(JSOM majors and minors only)"
58-
// Resulting substituted text would be: "(OPRE 3360 or STAT 3360 or STAT 4351), and @N", where N is some group number
59-
// When @N is dereferenced from the requisite list, it will have a value equivalent to the result of parseFnc(group, subgroups)
60-
56+
// SubstitutionMatcher returns a matcher that replaces a subgroup with parseFnc's result before parsing the outer group.
57+
// For example, "(OPRE 3360 or STAT 3360 or STAT 4351), and JSOM majors and minors only" becomes "... and @N".
6158
func SubstitutionMatcher(parseFnc func(string, []string) interface{}) func(string, []string) interface{} {
6259
// Return a closure that uses parseFnc to substitute subgroups[1]
6360
return func(group string, subgroups []string) interface{} {
@@ -72,6 +69,7 @@ func SubstitutionMatcher(parseFnc func(string, []string) interface{}) func(strin
7269

7370
var ORRegex = regexp.MustCompile(`(?i)\s+or\s+`)
7471

72+
// ORMatcher parses disjunction-separated requisites into an OR collection requirement.
7573
func ORMatcher(group string, subgroups []string) interface{} {
7674
// Split text along " or " boundaries, then parse subexpressions as groups into an "OR" CollectionRequirement
7775
subExpressions := ORRegex.Split(group, -1)
@@ -93,6 +91,7 @@ func ORMatcher(group string, subgroups []string) interface{} {
9391
}
9492
}
9593

94+
// CourseMinGradeMatcher returns a course requirement enforcing a minimum grade when an ICN is found.
9695
func CourseMinGradeMatcher(group string, subgroups []string) interface{} {
9796
icn, err := findICN(subgroups[1], subgroups[2])
9897
if err != nil {
@@ -102,6 +101,7 @@ func CourseMinGradeMatcher(group string, subgroups []string) interface{} {
102101
return schema.NewCourseRequirement(icn, subgroups[3])
103102
}
104103

104+
// CourseMatcher returns a course requirement with the default minimum grade expectation.
105105
func CourseMatcher(group string, subgroups []string) interface{} {
106106
icn, err := findICN(subgroups[1], subgroups[2])
107107
if err != nil {
@@ -111,10 +111,12 @@ func CourseMatcher(group string, subgroups []string) interface{} {
111111
return schema.NewCourseRequirement(icn, "D")
112112
}
113113

114+
// ConsentMatcher captures grantor consent requirements from requisite text.
114115
func ConsentMatcher(group string, subgroups []string) interface{} {
115116
return schema.NewConsentRequirement(subgroups[1])
116117
}
117118

119+
// LimitMatcher produces a limit requirement that caps allowable credit hours.
118120
func LimitMatcher(group string, subgroups []string) interface{} {
119121
hourLimit, err := strconv.Atoi(subgroups[1])
120122
if err != nil {
@@ -123,18 +125,22 @@ func LimitMatcher(group string, subgroups []string) interface{} {
123125
return schema.NewLimitRequirement(hourLimit)
124126
}
125127

128+
// MajorMatcher produces a major-specific requirement.
126129
func MajorMatcher(group string, subgroups []string) interface{} {
127130
return schema.NewMajorRequirement(subgroups[1])
128131
}
129132

133+
// MinorMatcher produces a minor-specific requirement.
130134
func MinorMatcher(group string, subgroups []string) interface{} {
131135
return schema.NewMinorRequirement(subgroups[1])
132136
}
133137

138+
// MajorMinorMatcher builds an OR collection spanning both major and minor requirements.
134139
func MajorMinorMatcher(group string, subgroups []string) interface{} {
135140
return schema.NewCollectionRequirement("OR", 1, []interface{}{*schema.NewMajorRequirement(subgroups[1]), *schema.NewMinorRequirement(subgroups[1])})
136141
}
137142

143+
// CoreMatcher creates a requirement for completion of a specific core course count.
138144
func CoreMatcher(group string, subgroups []string) interface{} {
139145
hourReq, err := strconv.Atoi(subgroups[1])
140146
if err != nil {
@@ -143,10 +149,12 @@ func CoreMatcher(group string, subgroups []string) interface{} {
143149
return schema.NewCoreRequirement(subgroups[2], hourReq)
144150
}
145151

152+
// CoreCompletionMatcher indicates completion of a specific core category without an hour requirement.
146153
func CoreCompletionMatcher(group string, subgroups []string) interface{} {
147154
return schema.NewCoreRequirement(subgroups[1], -1)
148155
}
149156

157+
// ChoiceMatcher converts a subgroup collection into a mutually exclusive choice requirement.
150158
func ChoiceMatcher(group string, subgroups []string) interface{} {
151159
collectionReq, ok := parseGroup(subgroups[1]).(*schema.CollectionRequirement)
152160
if !ok {
@@ -156,6 +164,7 @@ func ChoiceMatcher(group string, subgroups []string) interface{} {
156164
return schema.NewChoiceRequirement(collectionReq)
157165
}
158166

167+
// GPAMatcher represents GPA-based prerequisites.
159168
func GPAMatcher(group string, subgroups []string) interface{} {
160169
GPAFloat, err := strconv.ParseFloat(subgroups[1], 32)
161170
if err != nil {
@@ -164,13 +173,15 @@ func GPAMatcher(group string, subgroups []string) interface{} {
164173
return schema.NewGPARequirement(GPAFloat, "")
165174
}
166175

176+
// ThrowawayMatcher marks text that should be ignored during requisite evaluation.
167177
func ThrowawayMatcher(group string, subgroups []string) interface{} {
168178
return schema.Requirement{Type: "throwaway"}
169179
}
170180

171181
// Regex for group tags
172182
var groupTagRegex = regexp.MustCompile(`@(\d+)`)
173183

184+
// GroupTagMatcher resolves stack-referenced groups by index.
174185
func GroupTagMatcher(group string, subgroups []string) interface{} {
175186
groupIndex, err := strconv.Atoi(subgroups[1])
176187
if err != nil {
@@ -185,13 +196,14 @@ func GroupTagMatcher(group string, subgroups []string) interface{} {
185196
return parsedGrp
186197
}
187198

199+
// OtherMatcher wraps unmatched text in an OtherRequirement.
188200
func OtherMatcher(group string, subgroups []string) interface{} {
189201
return schema.NewOtherRequirement(ungroupText(group), "")
190202
}
191203

192204
/////////////////////// END MATCHER FUNCS ///////////////////////
193205

194-
// Matcher container, matchers must be in order of precedence
206+
// Matchers contains the ordered collection of matcher rules applied during requisite parsing.
195207
// NOTE: PARENTHESES ARE OF HIGHEST PRECEDENCE! (This is due to groupParens() handling grouping of parenthesized text before parsing begins)
196208
var Matchers []Matcher
197209

parser/sectionParser_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/google/go-cmp/cmp"
99
)
1010

11+
// TestGetInternalClassAndCourseNum checks parsing of internal course identifiers.
1112
func TestGetInternalClassAndCourseNum(t *testing.T) {
1213
t.Parallel()
1314

@@ -59,6 +60,7 @@ func TestGetInternalClassAndCourseNum(t *testing.T) {
5960
}
6061
}
6162

63+
// TestGetAcademicSession ensures term metadata is parsed correctly.
6264
func TestGetAcademicSession(t *testing.T) {
6365
t.Parallel()
6466

@@ -78,6 +80,7 @@ func TestGetAcademicSession(t *testing.T) {
7880
}
7981
}
8082

83+
// TestGetSectionNumber validates extraction of section numbers.
8184
func TestGetSectionNumber(t *testing.T) {
8285
t.Parallel()
8386

0 commit comments

Comments
 (0)