فهرست منبع

Use a typeclass to factor "run" operation

jherve 1 سال پیش
والد
کامیت
f966773a56

+ 10 - 9
src/Content.purs

@@ -7,10 +7,11 @@ import Effect (Effect)
 import Effect.Class.Console (logShow)
 import Effect.Class.Console (logShow)
 import Effect.Console (log)
 import Effect.Console (log)
 import LinkedIn (run, runToDetached)
 import LinkedIn (run, runToDetached)
-import LinkedIn.Page.JobOffer as PageJ
-import LinkedIn.Page.Projects as PageP
-import LinkedIn.Page.Skills as PageS
-import LinkedIn.Page.WorkExperiences as PageWE
+import LinkedIn.Page.JobOffer (JobOfferPage)
+import LinkedIn.Page.Projects (ProjectsPage)
+import LinkedIn.Page.Skills (SkillsPage)
+import LinkedIn.Page.WorkExperiences (WorkExperiencesPage)
+import Type.Proxy (Proxy(..))
 
 
 main :: Effect Unit
 main :: Effect Unit
 main = do
 main = do
@@ -18,9 +19,9 @@ main = do
 
 
   dom <- getBrowserDom
   dom <- getBrowserDom
 
 
-  run PageWE.query PageWE.extract dom >>= logShow
-  run PageP.query PageP.extract dom >>= logShow
-  run PageS.query PageS.extract dom >>= logShow
-  run PageJ.query PageJ.extract dom >>= logShow
+  run (Proxy :: Proxy WorkExperiencesPage) dom >>= logShow
+  run (Proxy :: Proxy SkillsPage) dom >>= logShow
+  run (Proxy :: Proxy ProjectsPage) dom >>= logShow
+  run (Proxy :: Proxy JobOfferPage) dom >>= logShow
 
 
-  runToDetached PageJ.query dom >>= logShow
+  runToDetached (Proxy :: Proxy JobOfferPage) dom >>= logShow

+ 14 - 10
src/LinkedIn.purs

@@ -6,28 +6,32 @@ import Data.Either (Either(..))
 import Data.Traversable (class Traversable, traverse)
 import Data.Traversable (class Traversable, traverse)
 import Effect (Effect)
 import Effect (Effect)
 import LinkedIn.DetachedNode (DetachedNode, toDetached)
 import LinkedIn.DetachedNode (DetachedNode, toDetached)
+import LinkedIn.Extractible (class Extractible)
+import LinkedIn.Extractible as LE
 import LinkedIn.QueryRunner (QueryError, QueryRunner', runQuery)
 import LinkedIn.QueryRunner (QueryError, QueryRunner', runQuery)
 import LinkedIn.UI.Elements.Parser (fromDetachedToUI)
 import LinkedIn.UI.Elements.Parser (fromDetachedToUI)
 import LinkedIn.UI.Elements.Types (UIElement)
 import LinkedIn.UI.Elements.Types (UIElement)
+import Type.Proxy (Proxy)
 import Web.DOM (Document, Node)
 import Web.DOM (Document, Node)
 
 
-run :: forall t a.
+run :: forall a t.
   Traversable t
   Traversable t
-  ⇒ QueryRunner' Document (t Node)
-  -> (t UIElement → Either String a)
+  => Extractible t a
+  => Proxy t
   -> Document
   -> Document
   -> Effect (Either String a)
   -> Effect (Either String a)
-run query parse dom = do
-  detached <- runToDetached query dom
-  pure $ extract parse $ toUI detached
+run prox dom = do
+  detached <- runToDetached prox dom
+  pure $ extract LE.extract $ toUI detached
 
 
-runToDetached :: forall t.
+runToDetached :: forall t a.
   Traversable t
   Traversable t
-  ⇒ QueryRunner' Document (t Node)
+  => Extractible t a
+  => Proxy t
   -> Document
   -> Document
   -> Effect (Either QueryError (t DetachedNode))
   -> Effect (Either QueryError (t DetachedNode))
-runToDetached query dom = do
-  qRes <- doQuery query dom
+runToDetached _ dom = do
+  qRes <- doQuery LE.query dom
   detach qRes
   detach qRes
 
 
 doQuery ∷ ∀ b. QueryRunner' Document b → Document → Effect (Either QueryError b)
 doQuery ∷ ∀ b. QueryRunner' Document b → Document → Effect (Either QueryError b)

+ 10 - 0
src/LinkedIn/Extractible.purs

@@ -0,0 +1,10 @@
+module LinkedIn.Extractible where
+
+import Data.Either (Either)
+import LinkedIn.QueryRunner (QueryRunner')
+import LinkedIn.UI.Elements.Types (UIElement)
+import Web.DOM (Document, Node)
+
+class Extractible t out | t -> out where
+  query :: QueryRunner' Document (t Node)
+  extract ∷ t UIElement → Either String out

+ 7 - 10
src/LinkedIn/Page/JobOffer.purs

@@ -2,17 +2,15 @@ module LinkedIn.Page.JobOffer where
 
 
 import Prelude
 import Prelude
 
 
-import Data.Either (Either)
 import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
 import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
 import Data.Generic.Rep (class Generic)
 import Data.Generic.Rep (class Generic)
 import Data.Show.Generic (genericShow)
 import Data.Show.Generic (genericShow)
 import Data.Traversable (class Traversable, sequence, traverseDefault)
 import Data.Traversable (class Traversable, sequence, traverseDefault)
+import LinkedIn.Extractible (class Extractible)
 import LinkedIn.Jobs.JobOffer (JobOffer)
 import LinkedIn.Jobs.JobOffer (JobOffer)
 import LinkedIn.Jobs.JobOffer as JJO
 import LinkedIn.Jobs.JobOffer as JJO
+import LinkedIn.QueryRunner (subQueryOne)
 import LinkedIn.UI.Components.JobsUnifiedTopCard (JobsUnifiedTopCardElement, queryJobsUnifiedTopCardElement)
 import LinkedIn.UI.Components.JobsUnifiedTopCard (JobsUnifiedTopCardElement, queryJobsUnifiedTopCardElement)
-import LinkedIn.QueryRunner (QueryRunner', subQueryOne)
-import LinkedIn.UI.Elements.Types (UIElement)
-import Web.DOM (Document, Node)
 
 
 data JobOfferPage a = JobOfferPage (JobsUnifiedTopCardElement a)
 data JobOfferPage a = JobOfferPage (JobsUnifiedTopCardElement a)
 
 
@@ -35,10 +33,9 @@ instance Traversable JobOfferPage where
 
 
   traverse = \x -> traverseDefault x
   traverse = \x -> traverseDefault x
 
 
-query :: QueryRunner' Document (JobOfferPage Node)
-query n = do
-  card <- subQueryOne queryJobsUnifiedTopCardElement "div.jobs-unified-top-card" n
-  pure $ JobOfferPage card
+instance Extractible JobOfferPage JobOffer where
+  query n = do
+    card <- subQueryOne queryJobsUnifiedTopCardElement "div.jobs-unified-top-card" n
+    pure $ JobOfferPage card
 
 
-extract ∷ JobOfferPage UIElement → Either String JobOffer
-extract (JobOfferPage tabs) = JJO.fromUI tabs
+  extract (JobOfferPage tabs) = JJO.fromUI tabs

+ 9 - 1
src/LinkedIn/Page/Projects.purs

@@ -8,10 +8,11 @@ import Data.Generic.Rep (class Generic)
 import Data.List.Types (NonEmptyList)
 import Data.List.Types (NonEmptyList)
 import Data.Show.Generic (genericShow)
 import Data.Show.Generic (genericShow)
 import Data.Traversable (class Traversable, sequence, traverse, traverseDefault)
 import Data.Traversable (class Traversable, sequence, traverse, traverseDefault)
-import LinkedIn.UI.Components.ArtDecoCard (ArtDecoCardElement, queryArtDecoCard)
+import LinkedIn.Extractible (class Extractible)
 import LinkedIn.Profile.Project (Project)
 import LinkedIn.Profile.Project (Project)
 import LinkedIn.Profile.Project as PP
 import LinkedIn.Profile.Project as PP
 import LinkedIn.QueryRunner (QueryRunner', subQueryMany)
 import LinkedIn.QueryRunner (QueryRunner', subQueryMany)
+import LinkedIn.UI.Components.ArtDecoCard (ArtDecoCardElement, queryArtDecoCard)
 import LinkedIn.UI.Elements.Types (UIElement)
 import LinkedIn.UI.Elements.Types (UIElement)
 import Web.DOM (Document, Node)
 import Web.DOM (Document, Node)
 
 
@@ -43,3 +44,10 @@ query n = do
 
 
 extract ∷ ProjectsPage UIElement → Either String (NonEmptyList Project)
 extract ∷ ProjectsPage UIElement → Either String (NonEmptyList Project)
 extract (ProjectsPage cards) = traverse PP.fromUI cards
 extract (ProjectsPage cards) = traverse PP.fromUI cards
+
+instance Extractible ProjectsPage (NonEmptyList Project) where
+  query n = do
+    cards <- subQueryMany queryArtDecoCard "section.artdeco-card > div ~ div > div > div > ul > li" n
+    pure $ ProjectsPage cards
+
+  extract (ProjectsPage cards) = traverse PP.fromUI cards

+ 8 - 11
src/LinkedIn/Page/Skills.purs

@@ -2,18 +2,16 @@ module LinkedIn.Page.Skills where
 
 
 import Prelude
 import Prelude
 
 
-import Data.Either (Either)
 import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
 import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
 import Data.Generic.Rep (class Generic)
 import Data.Generic.Rep (class Generic)
 import Data.List.Types (NonEmptyList)
 import Data.List.Types (NonEmptyList)
 import Data.Show.Generic (genericShow)
 import Data.Show.Generic (genericShow)
 import Data.Traversable (class Traversable, sequence, traverse, traverseDefault)
 import Data.Traversable (class Traversable, sequence, traverse, traverseDefault)
-import LinkedIn.UI.Components.ArtDecoTab (ArtDecoTabElement, queryArtDecoTab)
+import LinkedIn.Extractible (class Extractible)
 import LinkedIn.Profile.Skill (Skill)
 import LinkedIn.Profile.Skill (Skill)
 import LinkedIn.Profile.Skill as PS
 import LinkedIn.Profile.Skill as PS
-import LinkedIn.QueryRunner (QueryRunner', subQueryMany)
-import LinkedIn.UI.Elements.Types (UIElement)
-import Web.DOM (Document, Node)
+import LinkedIn.QueryRunner (subQueryMany)
+import LinkedIn.UI.Components.ArtDecoTab (ArtDecoTabElement, queryArtDecoTab)
 
 
 data SkillsPage a = SkillsPage (NonEmptyList (ArtDecoTabElement a))
 data SkillsPage a = SkillsPage (NonEmptyList (ArtDecoTabElement a))
 
 
@@ -36,10 +34,9 @@ instance Traversable SkillsPage where
 
 
   traverse = \x -> traverseDefault x
   traverse = \x -> traverseDefault x
 
 
-query :: QueryRunner' Document (SkillsPage Node)
-query n = do
-  tabs <- subQueryMany queryArtDecoTab "div.artdeco-tabs > div > div > div > div > ul > li" n
-  pure $ SkillsPage tabs
+instance Extractible SkillsPage (NonEmptyList Skill) where
+  query n = do
+    tabs <- subQueryMany queryArtDecoTab "div.artdeco-tabs > div > div > div > div > ul > li" n
+    pure $ SkillsPage tabs
 
 
-extract ∷ SkillsPage UIElement → Either String (NonEmptyList Skill)
-extract (SkillsPage tabs) = traverse PS.fromUI tabs
+  extract (SkillsPage tabs) = traverse PS.fromUI tabs

+ 8 - 11
src/LinkedIn/Page/WorkExperiences.purs

@@ -2,18 +2,16 @@ module LinkedIn.Page.WorkExperiences where
 
 
 import Prelude
 import Prelude
 
 
-import Data.Either (Either)
 import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
 import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
 import Data.Generic.Rep (class Generic)
 import Data.Generic.Rep (class Generic)
 import Data.List.Types (NonEmptyList)
 import Data.List.Types (NonEmptyList)
 import Data.Show.Generic (genericShow)
 import Data.Show.Generic (genericShow)
 import Data.Traversable (class Traversable, sequence, traverse, traverseDefault)
 import Data.Traversable (class Traversable, sequence, traverse, traverseDefault)
-import LinkedIn.UI.Components.ArtDecoCard (ArtDecoCardElement, queryArtDecoCard)
+import LinkedIn.Extractible (class Extractible)
 import LinkedIn.Profile.WorkExperience (WorkExperience)
 import LinkedIn.Profile.WorkExperience (WorkExperience)
 import LinkedIn.Profile.WorkExperience as PWE
 import LinkedIn.Profile.WorkExperience as PWE
-import LinkedIn.QueryRunner (QueryRunner', subQueryMany)
-import LinkedIn.UI.Elements.Types (UIElement)
-import Web.DOM (Document, Node)
+import LinkedIn.QueryRunner (subQueryMany)
+import LinkedIn.UI.Components.ArtDecoCard (ArtDecoCardElement, queryArtDecoCard)
 
 
 data WorkExperiencesPage a = WorkExperiencesPage (NonEmptyList (ArtDecoCardElement a))
 data WorkExperiencesPage a = WorkExperiencesPage (NonEmptyList (ArtDecoCardElement a))
 
 
@@ -36,10 +34,9 @@ instance Traversable WorkExperiencesPage where
 
 
   traverse = \x -> traverseDefault x
   traverse = \x -> traverseDefault x
 
 
-query :: QueryRunner' Document (WorkExperiencesPage Node)
-query n = do
-  cards <- subQueryMany queryArtDecoCard "section.artdeco-card > div ~ div > div > div > ul > li" n
-  pure $ WorkExperiencesPage cards
+instance Extractible WorkExperiencesPage (NonEmptyList WorkExperience) where
+  query n = do
+    cards <- subQueryMany queryArtDecoCard "section.artdeco-card > div ~ div > div > div > ul > li" n
+    pure $ WorkExperiencesPage cards
 
 
-extract ∷ WorkExperiencesPage UIElement → Either String (NonEmptyList WorkExperience)
-extract (WorkExperiencesPage cards) = traverse PWE.fromUI cards
+  extract (WorkExperiencesPage cards) = traverse PWE.fromUI cards

+ 4 - 4
test/ArtDecoCard.purs

@@ -11,15 +11,15 @@ import Data.Maybe (Maybe(..), fromJust)
 import Data.NonEmpty (NonEmpty(..))
 import Data.NonEmpty (NonEmpty(..))
 import Data.Traversable (traverse)
 import Data.Traversable (traverse)
 import Effect (Effect)
 import Effect (Effect)
-import LinkedIn.UI.Components.ArtDeco (ArtDecoCenter(..), ArtDecoCenterContent(..), ArtDecoCenterHeader(..), ArtDecoPvsEntity(..), ArtDecoPvsEntitySubComponent(..))
-import LinkedIn.UI.Components.ArtDecoCard (ArtDecoCardElement(..))
 import LinkedIn.DetachedNode (DetachedNode(..), toDetached)
 import LinkedIn.DetachedNode (DetachedNode(..), toDetached)
+import LinkedIn.Extractible (query)
 import LinkedIn.Page.WorkExperiences (WorkExperiencesPage(..))
 import LinkedIn.Page.WorkExperiences (WorkExperiencesPage(..))
-import LinkedIn.Page.WorkExperiences as PageWE
 import LinkedIn.Profile.WorkExperience (WorkExperience(..))
 import LinkedIn.Profile.WorkExperience (WorkExperience(..))
 import LinkedIn.Profile.WorkExperience as PWE
 import LinkedIn.Profile.WorkExperience as PWE
 import LinkedIn.QueryRunner (runQuery)
 import LinkedIn.QueryRunner (runQuery)
 import LinkedIn.UI.Basic.Types (Duration(..), TimeSpan(..))
 import LinkedIn.UI.Basic.Types (Duration(..), TimeSpan(..))
+import LinkedIn.UI.Components.ArtDeco (ArtDecoCenter(..), ArtDecoCenterContent(..), ArtDecoCenterHeader(..), ArtDecoPvsEntity(..), ArtDecoPvsEntitySubComponent(..))
+import LinkedIn.UI.Components.ArtDecoCard (ArtDecoCardElement(..))
 import LinkedIn.UI.Elements.Parser (fromDetachedToUI)
 import LinkedIn.UI.Elements.Parser (fromDetachedToUI)
 import Node.JsDom (jsDomFromFile)
 import Node.JsDom (jsDomFromFile)
 import Partial.Unsafe (unsafePartial)
 import Partial.Unsafe (unsafePartial)
@@ -29,7 +29,7 @@ import Test.Utils (toMonthYear')
 testArtDecoCards :: Effect Unit
 testArtDecoCards :: Effect Unit
 testArtDecoCards = do
 testArtDecoCards = do
   dom <- jsDomFromFile "test/examples/andrew_ng_experiences.html"
   dom <- jsDomFromFile "test/examples/andrew_ng_experiences.html"
-  wep <- runQuery $ PageWE.query dom
+  wep <- runQuery $ query dom
 
 
   assert $ isRight wep
   assert $ isRight wep
 
 

+ 3 - 2
test/JobsUnifiedTopCard.purs

@@ -10,13 +10,14 @@ import Data.NonEmpty (NonEmpty(..))
 import Data.Traversable (traverse)
 import Data.Traversable (traverse)
 import Effect (Effect)
 import Effect (Effect)
 import LinkedIn.DetachedNode (DetachedNode(..), toDetached)
 import LinkedIn.DetachedNode (DetachedNode(..), toDetached)
+import LinkedIn.Extractible (query)
 import LinkedIn.Jobs.JobOffer (JobOffer(..))
 import LinkedIn.Jobs.JobOffer (JobOffer(..))
 import LinkedIn.Jobs.JobOffer as JJO
 import LinkedIn.Jobs.JobOffer as JJO
-import LinkedIn.UI.Components.JobsUnifiedTopCard (JobsUnifiedTopCardElement(..), TopCardAction(..), TopCardInsight(..), TopCardInsightContent(..), TopCardPrimaryDescription(..), TopCardSecondaryInsight(..))
 import LinkedIn.Page.JobOffer (JobOfferPage(..))
 import LinkedIn.Page.JobOffer (JobOfferPage(..))
 import LinkedIn.Page.JobOffer as PageJO
 import LinkedIn.Page.JobOffer as PageJO
 import LinkedIn.QueryRunner (runQuery)
 import LinkedIn.QueryRunner (runQuery)
 import LinkedIn.UI.Basic.Types (JobFlexibility(..))
 import LinkedIn.UI.Basic.Types (JobFlexibility(..))
+import LinkedIn.UI.Components.JobsUnifiedTopCard (JobsUnifiedTopCardElement(..), TopCardAction(..), TopCardInsight(..), TopCardInsightContent(..), TopCardPrimaryDescription(..), TopCardSecondaryInsight(..))
 import LinkedIn.UI.Elements.Parser (fromDetachedToUI)
 import LinkedIn.UI.Elements.Parser (fromDetachedToUI)
 import Node.JsDom (jsDomFromFile)
 import Node.JsDom (jsDomFromFile)
 import Partial.Unsafe (unsafePartial)
 import Partial.Unsafe (unsafePartial)
@@ -26,7 +27,7 @@ main :: Effect Unit
 main = do
 main = do
   dom <- jsDomFromFile "test/examples/job_offer.html"
   dom <- jsDomFromFile "test/examples/job_offer.html"
 
 
-  wep <- runQuery $ PageJO.query dom
+  wep <- runQuery $ query dom
 
 
   assert $ isRight wep
   assert $ isRight wep