X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=src%2FTSN%2FXML%2FAutoRacingResults.hs;h=40e199f5464e6ea6f84e655925ebe8f10089d6eb;hb=ee4b98e5b4e9494364604a2e1a999c3d6d5acafb;hp=0e80b11b4702a18756328f2021d0d123a174e5ac;hpb=4ad960facfe0b939e71e4afe4502fce3108d90e3;p=dead%2Fhtsn-import.git diff --git a/src/TSN/XML/AutoRacingResults.hs b/src/TSN/XML/AutoRacingResults.hs index 0e80b11..40e199f 100644 --- a/src/TSN/XML/AutoRacingResults.hs +++ b/src/TSN/XML/AutoRacingResults.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE QuasiQuotes #-} @@ -24,6 +25,7 @@ where -- System imports. import Control.Monad ( forM_ ) import Data.Data ( Data ) +import Data.Maybe ( fromMaybe ) import Data.Time ( UTCTime(..) ) import Data.Tuple.Curry ( uncurryN ) import Data.Typeable ( Typeable ) @@ -39,6 +41,7 @@ import Database.Groundhog.Sqlite ( withSqliteConn ) import Database.Groundhog.TH ( groundhog, mkPersist ) +import qualified GHC.Generics as GHC ( Generic ) import Test.Tasty ( TestTree, testGroup ) import Test.Tasty.HUnit ( (@?=), testCase ) import Text.XML.HXT.Core ( @@ -46,6 +49,7 @@ import Text.XML.HXT.Core ( xp11Tuple, xp13Tuple, xpAttr, + xpDefault, xpElem, xpInt, xpList, @@ -57,6 +61,7 @@ import Text.XML.HXT.Core ( xpWrap ) -- Local imports. +import Generics ( Generic(..), to_tuple ) import TSN.Codegen ( tsn_codegen_config ) import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate ) import TSN.Picklers ( @@ -126,7 +131,11 @@ data Message = xml_listings :: [AutoRacingResultsListingXml], xml_race_information :: AutoRacingResultsRaceInformationXml, xml_time_stamp :: UTCTime } - deriving (Eq, Show) + deriving (Eq, GHC.Generic, Show) + +-- | For 'Generics.to_tuple'. +-- +instance Generic Message instance ToDb Message where @@ -204,8 +213,11 @@ data AutoRacingResultsListingXml = xml_dnf :: Maybe Bool, xml_nc :: Maybe Bool, xml_earnings :: Maybe Int } - deriving (Eq, Show) + deriving (Eq, GHC.Generic, Show) +-- | For 'Generics.to_tuple'. +-- +instance Generic AutoRacingResultsListingXml instance ToDb AutoRacingResultsListingXml where -- | The database analogue of an 'AutoRacingResultsListingXml' is @@ -259,17 +271,27 @@ instance XmlImportFk AutoRacingResultsListingXml -- the \"db_\" prefix since our field namer is going to strip of -- everything before the first underscore. -- +-- We make the three fields optional because the entire +-- \ is apparently optional (although it is +-- usually present). A 'Nothing' in the XML should get turned into +-- three 'Nothing's in the DB. +-- data MostLapsLeading = MostLapsLeading { - db_most_laps_leading_driver_id :: Int, - db_most_laps_leading_driver :: String, - db_most_laps_leading_number_of_laps :: Int } + db_most_laps_leading_driver_id :: Maybe Int, + db_most_laps_leading_driver :: Maybe String, + db_most_laps_leading_number_of_laps :: Maybe Int } deriving (Data, Eq, Show, Typeable) -- | Database representation of a \ contained -- within a \. -- +-- The 'db_most_laps_leading' field is not optional because when we +-- convert from our XML representation, a missing 'MostLapsLeading' +-- will be replaced with a 'MostLapsLeading' with three missing +-- fields. +-- data AutoRacingResultsRaceInformation = AutoRacingResultsRaceInformation { -- Note the apostrophe to disambiguate it from the @@ -306,8 +328,8 @@ data AutoRacingResultsRaceInformationXml = xml_cautions :: Maybe String, xml_lead_changes :: Maybe String, xml_lap_leaders :: Maybe String, - xml_most_laps_leading :: MostLapsLeading } - deriving (Eq,Show) + xml_most_laps_leading :: Maybe MostLapsLeading } + deriving (Eq, Show) instance ToDb AutoRacingResultsRaceInformationXml where @@ -345,7 +367,14 @@ instance FromXmlFk AutoRacingResultsRaceInformationXml where db_cautions = xml_cautions, db_lead_changes = xml_lead_changes, db_lap_leaders = xml_lap_leaders, - db_most_laps_leading = xml_most_laps_leading } + db_most_laps_leading = most_laps_leading } + where + -- If we didn't get a \, indicate that in + -- the database with an (embedded) 'MostLapsLeading' with three + -- missing fields. + most_laps_leading = + fromMaybe (MostLapsLeading Nothing Nothing Nothing) + xml_most_laps_leading -- | This allows us to insert the XML representation @@ -455,19 +484,6 @@ pickle_listing = (xpElem "Earnings" xp_earnings) where from_tuple = uncurryN AutoRacingResultsListingXml - to_tuple m = (xml_finish_position m, - xml_starting_position m, - xml_car_number m, - xml_driver_id m, - xml_driver m, - xml_car_make m, - xml_points m, - xml_laps_completed m, - xml_laps_leading m, - xml_status m, - xml_dnf m, - xml_nc m, - xml_earnings m) -- | Pickler for the top-level 'Message'. @@ -491,44 +507,59 @@ pickle_message = (xpElem "time_stamp" xp_time_stamp) where from_tuple = uncurryN Message - to_tuple m = (xml_xml_file_id m, - xml_heading m, - xml_category m, - xml_sport m, - xml_race_id m, - xml_race_date m, - xml_title m, - xml_track_location m, - xml_laps_remaining m, - xml_checkered_flag m, - xml_listings m, - xml_race_information m, - xml_time_stamp m) -- | Pickler for the \ child of a --- \. +-- \. This is complicated by the fact that the +-- three fields we're trying to parse are not actually optional; +-- only the entire \ is. So we always wrap what +-- we parse in a 'Just', and when converting from the DB to XML, +-- we'll drop the entire element if any of its fields are missing +-- (which they never should be). -- -pickle_most_laps_leading :: PU MostLapsLeading +pickle_most_laps_leading :: PU (Maybe MostLapsLeading) pickle_most_laps_leading = xpElem "Most_Laps_Leading" $ - xpWrap (from_tuple, to_tuple) $ - xpTriple (xpElem "DriverID" xpInt) - (xpElem "Driver" xpText) - (xpElem "NumberOfLaps" xpInt) + xpWrap (from_tuple, to_tuple') $ + xpTriple (xpOption $ xpElem "DriverID" xpInt) + (xpOption $ xpElem "Driver" xpText) + (xpOption $ xpElem "NumberOfLaps" xpInt) where - from_tuple = uncurryN MostLapsLeading - to_tuple m = (db_most_laps_leading_driver_id m, - db_most_laps_leading_driver m, - db_most_laps_leading_number_of_laps m) + from_tuple :: (Maybe Int, Maybe String, Maybe Int) -> Maybe MostLapsLeading + from_tuple (Just x, Just y, Just z) = + Just $ MostLapsLeading (Just x) (Just y) (Just z) + from_tuple _ = Nothing + + -- Sure had to go out of my way to avoid the warnings about unused + -- db_most_laps_foo fields here. + to_tuple' :: Maybe MostLapsLeading -> (Maybe Int, Maybe String, Maybe Int) + to_tuple' Nothing = (Nothing, Nothing, Nothing) + to_tuple' (Just (MostLapsLeading Nothing _ _)) = (Nothing, Nothing, Nothing) + to_tuple' (Just (MostLapsLeading _ Nothing _)) = (Nothing, Nothing, Nothing) + to_tuple' (Just (MostLapsLeading _ _ Nothing)) = (Nothing, Nothing, Nothing) + to_tuple' (Just m) = (db_most_laps_leading_driver_id m, + db_most_laps_leading_driver m, + db_most_laps_leading_number_of_laps m) -- | Pickler for the \ child of \. -- +-- There's so much voodoo going on here. We have a double-layered +-- Maybe on top of the MostLapsLeading. When unpickling, we return a +-- Nothing (i.e. a Maybe MostLapsLeading) if any of its fields are +-- missing. But if the entire element is missing, unpickling +-- fails. 'xpOption' doesn't fix this because it would give us a +-- Maybe (Maybe MostLapsLeading). But we can use 'xpDefault' with a +-- default of (Nothing :: Maybe MostLapsLeading) to stick one in +-- there if unpicking a (Maybe MostLapsLeading) fails because +-- \ is missing. +-- +-- Clear as mud, I know. +-- pickle_race_information :: PU AutoRacingResultsRaceInformationXml pickle_race_information = xpElem "Race_Information" $ - xpWrap (from_tuple, to_tuple) $ + xpWrap (from_tuple, to_tuple') $ xp11Tuple (-- I can't think of another way to get both the -- TrackLength and its KPH attribute. So we shove them -- both in a 2-tuple. This should probably be an embedded type! @@ -544,7 +575,7 @@ pickle_race_information = (xpOption $ xpElem "Cautions" xpText) (xpOption $ xpElem "LeadChanges" xpText) (xpOption $ xpElem "LapLeaders" xpText) - pickle_most_laps_leading + (xpDefault Nothing pickle_most_laps_leading) where -- Derp. Since the first two are paired, we have to -- manually unpack the bazillion arguments. @@ -553,17 +584,17 @@ pickle_race_information = x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 -- And here we have to re-pair the first two. - to_tuple m = ((xml_track_length m, xml_track_length_kph m), - xml_laps m, - xml_average_speed_mph m, - xml_average_speed_kph m, - xml_average_speed m, - xml_time_of_race m, - xml_margin_of_victory m, - xml_cautions m, - xml_lead_changes m, - xml_lap_leaders m, - xml_most_laps_leading m) + to_tuple' m = ((xml_track_length m, xml_track_length_kph m), + xml_laps m, + xml_average_speed_mph m, + xml_average_speed_kph m, + xml_average_speed m, + xml_time_of_race m, + xml_margin_of_victory m, + xml_cautions m, + xml_lead_changes m, + xml_lap_leaders m, + xml_most_laps_leading m) -- -- * Tasty Tests @@ -584,12 +615,15 @@ auto_racing_results_tests = -- test does not mean that unpickling succeeded. -- test_pickle_of_unpickle_is_identity :: TestTree -test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests" $ +test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests" [ check "pickle composed with unpickle is the identity" "test/xml/AutoRacingResultsXML.xml", check "pickle composed with unpickle is the identity (fractional KPH)" - "test/xml/AutoRacingResultsXML-fractional-kph.xml" ] + "test/xml/AutoRacingResultsXML-fractional-kph.xml", + + check "pickle composed with unpickle is the identity (No Most_Laps_Leading)" + "test/xml/AutoRacingResultsXML-no-most-laps-leading.xml"] where check desc path = testCase desc $ do (expected, actual) <- pickle_unpickle pickle_message path @@ -600,12 +634,15 @@ test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests" $ -- | Make sure we can actually unpickle these things. -- test_unpickle_succeeds :: TestTree -test_unpickle_succeeds = testGroup "unpickle tests" $ +test_unpickle_succeeds = testGroup "unpickle tests" [ check "unpickling succeeds" "test/xml/AutoRacingResultsXML.xml", check "unpickling succeeds (fractional KPH)" - "test/xml/AutoRacingResultsXML-fractional-kph.xml" ] + "test/xml/AutoRacingResultsXML-fractional-kph.xml", + + check "unpickling succeeds (no Most_Laps_Leading)" + "test/xml/AutoRacingResultsXML-no-most-laps-leading.xml" ] where check desc path = testCase desc $ do actual <- unpickleable path pickle_message @@ -618,12 +655,16 @@ test_unpickle_succeeds = testGroup "unpickle tests" $ -- record. -- test_on_delete_cascade :: TestTree -test_on_delete_cascade = testGroup "cascading delete tests" $ +test_on_delete_cascade = testGroup "cascading delete tests" [ check "deleting auto_racing_results deletes its children" "test/xml/AutoRacingResultsXML.xml", check "deleting auto_racing_results deletes its children (fractional KPH)" - "test/xml/AutoRacingResultsXML-fractional-kph.xml" ] + "test/xml/AutoRacingResultsXML-fractional-kph.xml", + + check ("deleting auto_racing_results deletes its children " ++ + "(No Most_Laps_Leading)") + "test/xml/AutoRacingResultsXML-no-most-laps-leading.xml" ] where check desc path = testCase desc $ do results <- unsafe_unpickle path pickle_message