From f658442e4e94de1935789caf1c9a5ad61198f54c Mon Sep 17 00:00:00 2001 From: Daniel Rafailov Date: Sat, 26 Sep 2026 16:31:27 -0400 Subject: [PATCH 1/5] refactored parseReqs into a new module Models.Requirement --- app/DynamicGraphs/CourseFinder.hs | 4 ++-- app/Models/Requirement.hs | 22 +++++++++++++++++++ app/WebParsing/ArtSciParser.hs | 4 ++-- app/WebParsing/PostParser.hs | 3 +-- app/WebParsing/ReqParser.hs | 11 ---------- .../RequirementTests/ReqParserTests.hs | 1 + courseography.cabal | 2 ++ 7 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 app/Models/Requirement.hs diff --git a/app/DynamicGraphs/CourseFinder.hs b/app/DynamicGraphs/CourseFinder.hs index d1e93f82c..106f1c7bc 100644 --- a/app/DynamicGraphs/CourseFinder.hs +++ b/app/DynamicGraphs/CourseFinder.hs @@ -14,7 +14,7 @@ import qualified Data.Text.Lazy as T import Database.Requirement (Modifier (..), Req (..)) import DynamicGraphs.GraphOptions (GraphOptions (..)) import Models.Course (prereqsForCourse) -import WebParsing.ReqParser (parseReqs) +import Models.Requirement (parseReqs) lookupCourses :: GraphOptions -> [T.Text] -> IO (Map.Map T.Text Req) lookupCourses options courses = @@ -26,7 +26,7 @@ lookupCourse options code = do case prereqResults of Left _ -> return () Right (courseCode, prereqStr) -> do - let prereqs = parseReqs (T.unpack $ T.fromStrict prereqStr) + let prereqs = parseReqs prereqStr modify $ Map.insert (T.fromStrict courseCode) prereqs lookupReqs options prereqs diff --git a/app/Models/Requirement.hs b/app/Models/Requirement.hs new file mode 100644 index 000000000..f9e3e0703 --- /dev/null +++ b/app/Models/Requirement.hs @@ -0,0 +1,22 @@ +module Models.Requirement ( + parseReqs, +) where + +import Data.Char +import qualified Data.Text as T +import Database.Requirement +import qualified Text.Parsec as Parsec +import WebParsing.ReqParser + +-- | Parses prerequisite strings into the Requirement datatype +parseReqs :: T.Text -> Req +parseReqs reqText = + let reqString = T.unpack reqText + reqStringLower = map toLower reqString + in if all isSpace reqString || reqStringLower == "none" || reqStringLower == "no" + then None + else do + let req = Parsec.parse reqParser "" reqString + in case req of + Right x -> x + Left e -> J (show e) "" diff --git a/app/WebParsing/ArtSciParser.hs b/app/WebParsing/ArtSciParser.hs index 9c9e47dd2..2a64ddb64 100644 --- a/app/WebParsing/ArtSciParser.hs +++ b/app/WebParsing/ArtSciParser.hs @@ -13,6 +13,7 @@ import Database.Persist.Sqlite (SqlPersistM) import Database.Tables (Course (..), Department (..)) import Models.Building (parseBuildings) import Models.Course (insertCourse) +import Models.Requirement (parseReqs) import Network.HTTP.Simple (getResponseBody, httpLBS, parseRequest) import Text.HTML.TagSoup (Tag) import qualified Text.HTML.TagSoup as TS @@ -22,7 +23,6 @@ import qualified Text.Parsec.Char as P import Text.Parsec.Text (Parser) import WebParsing.ParsecCombinators (text) import WebParsing.PostParser (addPostToDatabase) -import WebParsing.ReqParser (parseReqs) parseCalendar :: IO () parseCalendar = do @@ -141,7 +141,7 @@ parseCourses tags = code (Just title) (Just description) - (fmap (T.pack . show . parseReqs . T.unpack) prereqString) + (fmap (T.pack . show . parseReqs) prereqString) prep exclusion Nothing diff --git a/app/WebParsing/PostParser.hs b/app/WebParsing/PostParser.hs index 2b24f09a1..eb5b0f135 100644 --- a/app/WebParsing/PostParser.hs +++ b/app/WebParsing/PostParser.hs @@ -17,12 +17,12 @@ import Database.DataType (ProgramType (..)) import Database.Persist (insertUnique) import Database.Persist.Sqlite (SqlPersistM, insert_) import Database.Tables +import Models.Requirement (parseReqs) import Text.HTML.TagSoup import Text.HTML.TagSoup.Match import qualified Text.Parsec as P import Text.Parsec.Text (Parser) import WebParsing.ParsecCombinators (parseUntil) -import WebParsing.ReqParser (parseReqs) addPostToDatabase :: [Tag T.Text] -> SqlPersistM () addPostToDatabase programElements = do @@ -165,7 +165,6 @@ parseRequirement requirement = map parseSingleReq $ filter isReq requirement T.pack . show . parseReqs - . T.unpack -- Using parser for new Req type . fromRight "" . P.parse getLineText "Reading a requirement line" . T.strip diff --git a/app/WebParsing/ReqParser.hs b/app/WebParsing/ReqParser.hs index 03c9033ef..f24c8ad4c 100644 --- a/app/WebParsing/ReqParser.hs +++ b/app/WebParsing/ReqParser.hs @@ -729,14 +729,3 @@ many1Till p end = do x <- p xs <- Parsec.manyTill p end return $ x : xs - -parseReqs :: String -> Req -parseReqs reqString = do - let reqStringLower = map toLower reqString - if all isSpace reqString || reqStringLower == "none" || reqStringLower == "no" - then None - else do - let req = Parsec.parse reqParser "" reqString - in case req of - Right x -> x - Left e -> J (show e) "" diff --git a/backend-test/RequirementTests/ReqParserTests.hs b/backend-test/RequirementTests/ReqParserTests.hs index d26f7f294..df784517d 100644 --- a/backend-test/RequirementTests/ReqParserTests.hs +++ b/backend-test/RequirementTests/ReqParserTests.hs @@ -5,6 +5,7 @@ module RequirementTests.ReqParserTests (test_requirements) where import Database.Requirement +import Models.Requirement (parseReqs) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertEqual, testCase) import qualified Text.Parsec as Parsec diff --git a/courseography.cabal b/courseography.cabal index 285b9a212..7e44c41c5 100644 --- a/courseography.cabal +++ b/courseography.cabal @@ -48,6 +48,7 @@ library Models.Graph, Models.Meeting, Models.Program, + Models.Requirement, Models.Time, Scripts, Svg.Builder, @@ -198,6 +199,7 @@ executable courseography Models.Graph, Models.Meeting, Models.Program, + Models.Requirement, Models.Time, Response, Response.About, From 8f91014d9679c152fcee1ccfd8610d9abe93c797 Mon Sep 17 00:00:00 2001 From: Daniel Rafailov Date: Sat, 26 Sep 2026 16:39:00 -0400 Subject: [PATCH 2/5] Added changes to Changelog file --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0dfa517..b186db793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### 🔧 Internal changes +- Refactored `parseReqs` into a new module `Models.Requirements` and updated its input type from `String` to `Text` - Migrated JS package manager from yarn to pnpm - Updated `react-leaflet` to v5.0.0 and `@eslint/compat` to v2.1.0 - Added peer dependency overrides for `eslint-plugin-react` (for `eslint` v10) and `@babel/plugin-syntax-*` (for `@babel/core` v8.0.1 From 71ed3447618a69f49c9646b34632944873d55a33 Mon Sep 17 00:00:00 2001 From: Daniel Rafailov Date: Sat, 26 Sep 2026 17:26:21 -0400 Subject: [PATCH 3/5] Fixed type mismatch in ReqParserTests --- backend-test/RequirementTests/ReqParserTests.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend-test/RequirementTests/ReqParserTests.hs b/backend-test/RequirementTests/ReqParserTests.hs index df784517d..4ab6932bd 100644 --- a/backend-test/RequirementTests/ReqParserTests.hs +++ b/backend-test/RequirementTests/ReqParserTests.hs @@ -4,6 +4,7 @@ -- Module containing test cases for Requirement Parsers. module RequirementTests.ReqParserTests (test_requirements) where +import qualified Data.Text as T import Database.Requirement import Models.Requirement (parseReqs) import Test.Tasty (TestTree, testGroup) @@ -36,7 +37,7 @@ createReqParserTest label input = assertEqual ("for (" ++ y ++ "),") z - (parseReqs y) + (parseReqs (T.pack y)) ) [0 ..] input From 11fd919e92b495f3b75dbe9660d61f762f3873a6 Mon Sep 17 00:00:00 2001 From: Daniel Rafailov Date: Sun, 27 Sep 2026 13:48:29 -0400 Subject: [PATCH 4/5] Addressed review feedback: explicit imports, used T.toLower, and moved test module --- app/Models/Requirement.hs | 12 ++++++------ .../RequirementModelTests.hs} | 10 +++++----- courseography.cabal | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) rename backend-test/{RequirementTests/ReqParserTests.hs => Models/RequirementModelTests.hs} (98%) diff --git a/app/Models/Requirement.hs b/app/Models/Requirement.hs index f9e3e0703..ad8151781 100644 --- a/app/Models/Requirement.hs +++ b/app/Models/Requirement.hs @@ -2,18 +2,18 @@ module Models.Requirement ( parseReqs, ) where -import Data.Char +import Data.Char (isSpace) import qualified Data.Text as T -import Database.Requirement +import Database.Requirement (Req (J, None)) import qualified Text.Parsec as Parsec -import WebParsing.ReqParser +import WebParsing.ReqParser (reqParser) -- | Parses prerequisite strings into the Requirement datatype parseReqs :: T.Text -> Req parseReqs reqText = - let reqString = T.unpack reqText - reqStringLower = map toLower reqString - in if all isSpace reqString || reqStringLower == "none" || reqStringLower == "no" + let reqLower = T.toLower reqText + reqString = T.unpack reqText + in if all isSpace reqString || reqLower == "none" || reqLower == "no" then None else do let req = Parsec.parse reqParser "" reqString diff --git a/backend-test/RequirementTests/ReqParserTests.hs b/backend-test/Models/RequirementModelTests.hs similarity index 98% rename from backend-test/RequirementTests/ReqParserTests.hs rename to backend-test/Models/RequirementModelTests.hs index 4ab6932bd..b5459bd6f 100644 --- a/backend-test/RequirementTests/ReqParserTests.hs +++ b/backend-test/Models/RequirementModelTests.hs @@ -2,7 +2,7 @@ -- Description: Test Course Requirement Parsers using HUnit Testing Framework. -- -- Module containing test cases for Requirement Parsers. -module RequirementTests.ReqParserTests (test_requirements) where +module Models.RequirementModelTests (test_requirements) where import qualified Data.Text as T import Database.Requirement @@ -28,16 +28,16 @@ createTest parser label input = [0 ..] input -createReqParserTest :: String -> [(String, Req)] -> TestTree +createReqParserTest :: String -> [(T.Text, Req)] -> TestTree createReqParserTest label input = testGroup label $ zipWith ( \(x :: Int) (y, z) -> testCase ("Test " ++ show x) $ assertEqual - ("for (" ++ y ++ "),") + ("for (" ++ T.unpack y ++ "),") z - (parseReqs (T.pack y)) + (parseReqs y) ) [0 ..] input @@ -417,7 +417,7 @@ cgpaInputs = ) ] -noPrereqInputs :: [(String, Req)] +noPrereqInputs :: [(T.Text, Req)] noPrereqInputs = [ ("", None) , ("None", None) diff --git a/courseography.cabal b/courseography.cabal index 7e44c41c5..b54e473d7 100644 --- a/courseography.cabal +++ b/courseography.cabal @@ -125,10 +125,10 @@ test-suite Tests Database.BuildingTests, Database.CourseQueriesTests, Database.TablesTests, + Models.RequirementModelTests, RequirementTests.ModifierTests, RequirementTests.PostParserTests, RequirementTests.PreProcessingTests, - RequirementTests.ReqParserTests, RequirementTests.FilterReqTests, SvgTests.IntersectionTests, TestHelpers, From a33f97dfe3f78b61bc1de654afd1bb7d6538a8a9 Mon Sep 17 00:00:00 2001 From: Daniel Rafailov Date: Sun, 27 Sep 2026 14:57:23 -0400 Subject: [PATCH 5/5] fixed typo in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a715e170..5aafc1fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### 🔧 Internal changes -- Refactored `parseReqs` into a new module `Models.Requirements` and updated its input type from `String` to `Text` +- Refactored `parseReqs` into a new module `Models.Requirement` and updated its input type from `String` to `Text` - Added a test case for the getGraphJSON function in `Controllers/Graph` - Migrated JS package manager from yarn to pnpm - Updated `react-leaflet` to v5.0.0 and `@eslint/compat` to v2.1.0