refactor: make API more sane
This commit is contained in:
parent
1ac32ff745
commit
2041858fd9
@ -2,8 +2,11 @@ package:
|
||||
dependencies:
|
||||
- arrays
|
||||
- effect
|
||||
- foreign
|
||||
- foreign-object
|
||||
- functions
|
||||
- maybe
|
||||
- nullable
|
||||
- prelude
|
||||
- psci-support
|
||||
- test-unit
|
||||
|
@ -1,66 +1,53 @@
|
||||
import cheerio from 'cheerio'
|
||||
import {load} from 'cheerio'
|
||||
|
||||
// Attributes
|
||||
export const attrImpl = function (nothing, just, name, cheerioInst) {
|
||||
if (cheerioInst.length > 0) {
|
||||
const value = cheerioInst.attr(name)
|
||||
return value != null ? just(value) : nothing
|
||||
/** @typedef {import('cheerio').Element} Element */
|
||||
/** @typedef {import('cheerio').Cheerio<Element>} CheerioNode */
|
||||
|
||||
/** @type {(_1: string) => () => CheerioNode} */
|
||||
export const loadImpl = html => () => {
|
||||
const root = load(html).root()
|
||||
const htmlC = root.first().children().first()
|
||||
|
||||
if (!htmlC.is('html')) {
|
||||
throw new Error('invariant condition: root node should contain HTML!')
|
||||
}
|
||||
|
||||
return nothing
|
||||
if (htmlC.length !== 1) {
|
||||
throw new Error('invariant condition: HTML element should be only child!')
|
||||
}
|
||||
|
||||
return htmlC
|
||||
}
|
||||
|
||||
export const hasClassImpl = function (className, cheerioInst) {
|
||||
return cheerioInst.hasClass(className)
|
||||
}
|
||||
/** @type {(_: CheerioNode) => () => Array<CheerioNode>} */
|
||||
export const toArrayImpl = n => () => Array(n.length) .fill(undefined) .map((_, ix) => n.slice(ix, ix + 1))
|
||||
|
||||
// Traversing
|
||||
export const findImpl = function (selector, cheerioInst) {
|
||||
return cheerioInst.find(selector)
|
||||
}
|
||||
/** @type {(_: CheerioNode) => () => CheerioNode | null} */
|
||||
export const toNullableImpl = n => () => n.length === 0 ? null : n.first()
|
||||
|
||||
export const parent = function (cheerioInst) {
|
||||
return cheerioInst.parent()
|
||||
}
|
||||
/** @type {(_: CheerioNode) => () => CheerioNode} */
|
||||
export const childrenImpl = n => () => n.children()
|
||||
|
||||
export const next = function (cheerioInst) {
|
||||
return cheerioInst.next()
|
||||
}
|
||||
/** @type {(_: CheerioNode) => () => CheerioNode} */
|
||||
export const siblingsImpl = n => () => n.siblings()
|
||||
|
||||
export const prev = function (cheerioInst) {
|
||||
return cheerioInst.prev()
|
||||
}
|
||||
/** @type {(_: CheerioNode) => () => CheerioNode} */
|
||||
export const parentImpl = n => () => n.parent()
|
||||
|
||||
export const siblings = function (cheerioInst) {
|
||||
return cheerioInst.siblings()
|
||||
}
|
||||
/** @type { (_2: CheerioNode) => () => Record<string, string>} */
|
||||
export const attrsImpl = n => () => n.attr() || {}
|
||||
|
||||
export const children = function (cheerioInst) {
|
||||
return cheerioInst.children()
|
||||
}
|
||||
/** @type {(_1: string) => (_2: CheerioNode) => () => string | null} */
|
||||
export const attrImpl = k => n => () => n.attr(k) || null
|
||||
|
||||
export const first = function (cheerioInst) {
|
||||
return cheerioInst.first()
|
||||
}
|
||||
/** @type {(_2: CheerioNode) => () => Record<string, string>} */
|
||||
export const cssImpl = n => () => n.css() || {}
|
||||
|
||||
export const last = function (cheerioInst) {
|
||||
return cheerioInst.last()
|
||||
}
|
||||
/** @type {(_2: CheerioNode) => () => string} */
|
||||
export const htmlImpl = n => () => n.html() || ''
|
||||
|
||||
export const eqImpl = function (index, cheerioInst) {
|
||||
return cheerioInst.eq(index)
|
||||
}
|
||||
/** @type {(_2: CheerioNode) => () => string} */
|
||||
export const textImpl = n => () => n.text() || ''
|
||||
|
||||
// Rendering
|
||||
export const htmlImpl = function (nothing, just, cheerioInst) {
|
||||
return cheerioInst.length ? just(cheerioInst.html()) : nothing
|
||||
}
|
||||
|
||||
export const text = function (cheerioInst) {
|
||||
return cheerioInst.text()
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
export const length = function (cheerioInst) {
|
||||
return cheerioInst.length
|
||||
}
|
||||
/** @type {(_1: string) => (_2: CheerioNode) => () => CheerioNode} */
|
||||
export const findImpl = s => n => () => n.find(s)
|
||||
|
112
src/Cheerio.purs
112
src/Cheerio.purs
@ -1,84 +1,64 @@
|
||||
module Cheerio
|
||||
( Cheerio
|
||||
, attr
|
||||
, children
|
||||
, eq
|
||||
, first
|
||||
, find
|
||||
, hasClass
|
||||
, html
|
||||
, last
|
||||
, length
|
||||
, next
|
||||
, parent
|
||||
, prev
|
||||
, siblings
|
||||
, text
|
||||
, toArray
|
||||
) where
|
||||
module Cheerio where
|
||||
|
||||
import Prelude hiding (eq)
|
||||
import Prelude
|
||||
|
||||
import Data.Array ((..))
|
||||
import Data.Function.Uncurried (Fn2, Fn3, Fn4, runFn2, runFn3, runFn4)
|
||||
import Data.Maybe (Maybe(..))
|
||||
import Data.Array as Array
|
||||
import Data.Map (Map)
|
||||
import Data.Map as Map
|
||||
import Data.Maybe (Maybe)
|
||||
import Data.Nullable (Nullable)
|
||||
import Data.Nullable as Nullable
|
||||
import Effect (Effect)
|
||||
import Foreign.Object (Object)
|
||||
|
||||
foreign import data Cheerio :: Type
|
||||
foreign import data CheerioNode :: Type
|
||||
|
||||
-- Attributes
|
||||
foreign import attrImpl
|
||||
:: forall a
|
||||
. Fn4 (Maybe a) (a -> Maybe a) String Cheerio (Maybe String)
|
||||
foreign import loadImpl :: String -> Effect CheerioNode
|
||||
|
||||
-- | Gets an attribute value from the first selected element, returning
|
||||
-- | Nothing when there are no selected elements, or when the first selected
|
||||
-- | element does not have the specified attribute.
|
||||
attr :: String -> Cheerio -> Maybe String
|
||||
attr = runFn4 attrImpl Nothing Just
|
||||
foreign import toArrayImpl :: CheerioNode -> Effect (Array CheerioNode)
|
||||
foreign import toNullableImpl :: CheerioNode -> Effect (Nullable CheerioNode)
|
||||
|
||||
foreign import hasClassImpl :: Fn2 String Cheerio Boolean
|
||||
foreign import siblingsImpl :: CheerioNode -> Effect CheerioNode
|
||||
foreign import childrenImpl :: CheerioNode -> Effect CheerioNode
|
||||
foreign import parentImpl :: CheerioNode -> Effect CheerioNode
|
||||
|
||||
hasClass :: String -> Cheerio -> Boolean
|
||||
hasClass = runFn2 hasClassImpl
|
||||
foreign import attrsImpl :: CheerioNode -> Effect (Object String)
|
||||
foreign import attrImpl :: String -> CheerioNode -> Effect (Nullable String)
|
||||
foreign import cssImpl :: CheerioNode -> Effect (Object String)
|
||||
foreign import htmlImpl :: CheerioNode -> Effect String
|
||||
foreign import textImpl :: CheerioNode -> Effect String
|
||||
|
||||
-- Traversing
|
||||
foreign import findImpl :: Fn2 String Cheerio Cheerio
|
||||
foreign import findImpl :: String -> CheerioNode -> Effect CheerioNode
|
||||
|
||||
find :: String -> Cheerio -> Cheerio
|
||||
find = runFn2 findImpl
|
||||
load :: String -> Effect CheerioNode
|
||||
load = loadImpl
|
||||
|
||||
foreign import parent :: Cheerio -> Cheerio
|
||||
foreign import next :: Cheerio -> Cheerio
|
||||
foreign import prev :: Cheerio -> Cheerio
|
||||
foreign import siblings :: Cheerio -> Cheerio
|
||||
foreign import children :: Cheerio -> Cheerio
|
||||
foreign import first :: Cheerio -> Cheerio
|
||||
foreign import last :: Cheerio -> Cheerio
|
||||
parent :: CheerioNode -> Effect (Maybe CheerioNode)
|
||||
parent = map Nullable.toMaybe <<< toNullableImpl <=< parentImpl
|
||||
|
||||
foreign import eqImpl :: Fn2 Int Cheerio Cheerio
|
||||
siblings :: CheerioNode -> Effect (Array CheerioNode)
|
||||
siblings = toArrayImpl <=< siblingsImpl
|
||||
|
||||
eq :: Int -> Cheerio -> Cheerio
|
||||
eq = runFn2 eqImpl
|
||||
children :: CheerioNode -> Effect (Array CheerioNode)
|
||||
children = toArrayImpl <=< childrenImpl
|
||||
|
||||
-- Rendering
|
||||
foreign import htmlImpl
|
||||
:: forall a
|
||||
. Fn3 (Maybe a) (a -> Maybe a) Cheerio (Maybe String)
|
||||
attrs :: CheerioNode -> Effect (Map String String)
|
||||
attrs = map Map.fromFoldableWithIndex <<< attrsImpl
|
||||
|
||||
-- | Gets an html content string from the first selected element, returning
|
||||
-- | Nothing when there are no selected elements.
|
||||
html :: Cheerio -> Maybe String
|
||||
html = runFn3 htmlImpl Nothing Just
|
||||
attr :: String -> CheerioNode -> Effect (Maybe String)
|
||||
attr k = map Nullable.toMaybe <<< attrImpl k
|
||||
|
||||
foreign import text :: Cheerio -> String
|
||||
css :: CheerioNode -> Effect (Map String String)
|
||||
css = map Map.fromFoldableWithIndex <<< cssImpl
|
||||
|
||||
-- Miscellaneous
|
||||
html :: CheerioNode -> Effect String
|
||||
html = htmlImpl
|
||||
|
||||
-- | Get how many elements there are in the given Cheerio
|
||||
foreign import length :: Cheerio -> Int
|
||||
text :: CheerioNode -> Effect String
|
||||
text = textImpl
|
||||
|
||||
-- | Seperate each element out into its own Cheerio
|
||||
toArray :: Cheerio -> Array Cheerio
|
||||
toArray c
|
||||
| length c == 0 = []
|
||||
| otherwise = map (\i -> eq i c) (0 .. (length c - 1))
|
||||
find :: String -> CheerioNode -> Effect (Array (CheerioNode))
|
||||
find s = toArrayImpl <=< findImpl s
|
||||
|
||||
findFirst :: String -> CheerioNode -> Effect (Maybe (CheerioNode))
|
||||
findFirst s = map Array.head <<< find s
|
||||
|
@ -1,24 +0,0 @@
|
||||
import cheerio from 'cheerio'
|
||||
|
||||
// Loading
|
||||
export const load = cheerio.load
|
||||
|
||||
// Selecting
|
||||
export const selectImpl = function (str, cheerioStatic) {
|
||||
return cheerioStatic(str)
|
||||
}
|
||||
|
||||
export const selectDeepImpl = function (strArr, cheerioStatic) {
|
||||
return cheerioStatic.apply(cheerioStatic, strArr)
|
||||
}
|
||||
|
||||
// Rendering
|
||||
export const htmlImpl = function (nothing, just, cheerioInst) {
|
||||
const ret = cheerio.html(cheerioInst)
|
||||
return ret != null ? just(ret) : nothing
|
||||
}
|
||||
|
||||
// Utilities
|
||||
export const root = function (cheerioStatic) {
|
||||
return cheerio.root.call(cheerioStatic)
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
module Cheerio.Static
|
||||
( CheerioStatic
|
||||
, html
|
||||
, load
|
||||
, loadRoot
|
||||
, select
|
||||
, selectDeep
|
||||
, root
|
||||
) where
|
||||
|
||||
import Prelude
|
||||
import Cheerio (Cheerio)
|
||||
import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3)
|
||||
import Data.Maybe (Maybe(..))
|
||||
|
||||
foreign import data CheerioStatic :: Type
|
||||
|
||||
-- Loading
|
||||
foreign import load :: String -> CheerioStatic
|
||||
|
||||
-- Selectors
|
||||
foreign import selectImpl :: Fn2 String CheerioStatic Cheerio
|
||||
|
||||
select :: String -> CheerioStatic -> Cheerio
|
||||
select = runFn2 selectImpl
|
||||
|
||||
foreign import selectDeepImpl :: Fn2 (Array String) CheerioStatic Cheerio
|
||||
|
||||
selectDeep :: Array String -> CheerioStatic -> Cheerio
|
||||
selectDeep = runFn2 selectDeepImpl
|
||||
|
||||
-- Rendering
|
||||
foreign import htmlImpl
|
||||
:: forall a
|
||||
. Fn3 (Maybe a) (a -> Maybe a) Cheerio (Maybe String)
|
||||
|
||||
html :: Cheerio -> Maybe String
|
||||
html = runFn3 htmlImpl Nothing Just
|
||||
|
||||
-- Utilities
|
||||
foreign import root :: CheerioStatic -> Cheerio
|
||||
|
||||
-- Convenience
|
||||
loadRoot :: String -> Cheerio
|
||||
loadRoot = load >>> root
|
@ -2,172 +2,69 @@ module Test.Cheerio where
|
||||
|
||||
import Prelude hiding (eq)
|
||||
|
||||
import Effect (Effect)
|
||||
import Cheerio (CheerioNode)
|
||||
import Cheerio as Cheerio
|
||||
import Control.Monad.Error.Class (liftMaybe)
|
||||
import Data.Array as Array
|
||||
import Data.Maybe (Maybe(..))
|
||||
import Effect (Effect)
|
||||
import Effect.Class (liftEffect)
|
||||
import Effect.Exception (error)
|
||||
import Test.HtmlEx (htmlEx)
|
||||
import Test.Unit (TestSuite, suite, test)
|
||||
import Test.Unit.Assert as Assert
|
||||
import Test.Unit.Main (runTest)
|
||||
|
||||
import Cheerio
|
||||
( Cheerio
|
||||
, attr
|
||||
, children
|
||||
, eq
|
||||
, find
|
||||
, first
|
||||
, hasClass
|
||||
, html
|
||||
, last
|
||||
, length
|
||||
, next
|
||||
, parent
|
||||
, prev
|
||||
, siblings
|
||||
, text
|
||||
, toArray
|
||||
)
|
||||
|
||||
import Cheerio.Static (loadRoot)
|
||||
|
||||
import Test.HtmlEx (htmlEx)
|
||||
|
||||
main :: Effect Unit
|
||||
main = runTest suites
|
||||
|
||||
emptyCheerio :: Cheerio
|
||||
emptyCheerio = loadRoot htmlEx # find ".no-such-element"
|
||||
|
||||
suites :: TestSuite
|
||||
suites = do
|
||||
suite "Attributes" do
|
||||
test "attr" do
|
||||
Assert.equal
|
||||
(Just "fruits")
|
||||
(loadRoot htmlEx # find "ul" # attr "id")
|
||||
|
||||
Assert.equal
|
||||
Nothing
|
||||
(loadRoot htmlEx # find "ul" # attr "no-such-attribute")
|
||||
|
||||
Assert.equal
|
||||
Nothing
|
||||
(emptyCheerio # attr "id")
|
||||
|
||||
test "hasClass" do
|
||||
Assert.equal
|
||||
true
|
||||
(loadRoot htmlEx # find ".pear" # hasClass "pear")
|
||||
|
||||
Assert.equal
|
||||
false
|
||||
(loadRoot htmlEx # find "apple" # hasClass "fruit")
|
||||
|
||||
Assert.equal
|
||||
true
|
||||
(loadRoot htmlEx # find "li" # hasClass "pear")
|
||||
|
||||
Assert.equal
|
||||
false
|
||||
(emptyCheerio # hasClass "pear")
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
fruits <- liftEffect
|
||||
$ liftMaybe (error "ul should have id")
|
||||
=<< Cheerio.attr "id"
|
||||
=<< liftMaybe (error "ul should exist")
|
||||
=<< Cheerio.findFirst "ul" doc
|
||||
Assert.equal "fruits" fruits
|
||||
|
||||
suite "Traversing" do
|
||||
test "find" do
|
||||
Assert.equal
|
||||
3
|
||||
(loadRoot htmlEx # find "#fruits" # find "li" # length)
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
lis <- liftEffect $ Cheerio.find "li" doc
|
||||
Assert.equal 3 (Array.length lis)
|
||||
|
||||
test "parent" do
|
||||
Assert.equal
|
||||
(Just "fruits")
|
||||
(loadRoot htmlEx # find ".pear" # parent # attr "id")
|
||||
|
||||
test "next" do
|
||||
Assert.equal
|
||||
true
|
||||
(loadRoot htmlEx # find ".apple" # next # hasClass "orange")
|
||||
|
||||
test "prev" do
|
||||
Assert.equal
|
||||
true
|
||||
(loadRoot htmlEx # find ".orange" # prev # hasClass "apple")
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
pear <- liftEffect $ liftMaybe (error "pear should exist") =<< Cheerio.findFirst ".pear" doc
|
||||
parentId <- liftEffect
|
||||
$ liftMaybe (error "parent should have id")
|
||||
=<< Cheerio.attr "id"
|
||||
=<< liftMaybe (error "parent should exist")
|
||||
=<< Cheerio.parent pear
|
||||
Assert.equal "fruits" parentId
|
||||
|
||||
test "siblings" do
|
||||
Assert.equal
|
||||
2
|
||||
(loadRoot htmlEx # find ".pear" # siblings # length)
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
pear <- liftEffect $ liftMaybe (error "pear should exist") =<< Cheerio.findFirst ".pear" doc
|
||||
siblings <- liftEffect $ Cheerio.siblings pear
|
||||
Assert.equal 2 (Array.length siblings)
|
||||
|
||||
test "children" do
|
||||
Assert.equal
|
||||
3
|
||||
(loadRoot htmlEx # find "#fruits" # children # length)
|
||||
|
||||
test "first" do
|
||||
Assert.equal
|
||||
"Apple"
|
||||
(loadRoot htmlEx # find "#fruits" # children # first # text)
|
||||
|
||||
test "last" do
|
||||
Assert.equal
|
||||
"Pear"
|
||||
(loadRoot htmlEx # find "#fruits" # children # last # text)
|
||||
|
||||
test "eq" do
|
||||
Assert.equal
|
||||
"Apple"
|
||||
(loadRoot htmlEx # find "li" # eq 0 # text)
|
||||
|
||||
Assert.equal
|
||||
"Pear"
|
||||
(loadRoot htmlEx # find "li" # eq (-1) # text)
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
fruitsRoot <- liftEffect $ liftMaybe (error "fruits should exist") =<< Cheerio.findFirst "#fruits" doc
|
||||
fruits <- liftEffect $ Cheerio.children fruitsRoot
|
||||
Assert.equal 3 (Array.length fruits)
|
||||
|
||||
suite "Rendering" do
|
||||
test "html" do
|
||||
Assert.equal
|
||||
(Just "Apple")
|
||||
(loadRoot htmlEx # find ".apple" # html)
|
||||
|
||||
Assert.equal
|
||||
Nothing
|
||||
(emptyCheerio # html)
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
appleHtml <- liftEffect $ Cheerio.html =<< liftMaybe (error "apple should exist") =<< Cheerio.findFirst ".apple" doc
|
||||
Assert.equal "Apple" appleHtml
|
||||
|
||||
test "text" do
|
||||
Assert.equal
|
||||
"Apple"
|
||||
(loadRoot htmlEx # find ".apple" # text)
|
||||
|
||||
Assert.equal
|
||||
""
|
||||
(emptyCheerio # text)
|
||||
|
||||
suite "Miscellaneous" do
|
||||
test "length" do
|
||||
Assert.equal
|
||||
0
|
||||
(emptyCheerio # length)
|
||||
|
||||
Assert.equal
|
||||
3
|
||||
(loadRoot htmlEx # find "li" # length)
|
||||
|
||||
test "toArray" do
|
||||
Assert.equal
|
||||
[]
|
||||
(emptyCheerio # toArray # map (attr "class"))
|
||||
|
||||
Assert.equal
|
||||
(map Just [ "apple", "orange", "pear" ])
|
||||
(loadRoot htmlEx # find "li" # toArray # map (attr "class"))
|
||||
|
||||
suite "More" do
|
||||
test "Long chain" do
|
||||
Assert.equal
|
||||
"Apple"
|
||||
( loadRoot htmlEx
|
||||
# find ".apple"
|
||||
# siblings
|
||||
# eq 1
|
||||
# parent
|
||||
# children
|
||||
# first
|
||||
# text
|
||||
)
|
||||
doc <- liftEffect $ Cheerio.load htmlEx
|
||||
appleText <- liftEffect $ Cheerio.text =<< liftMaybe (error "apple should exist") =<< Cheerio.findFirst ".apple" doc
|
||||
Assert.equal "Apple" appleText
|
||||
|
@ -1,50 +0,0 @@
|
||||
module Test.Cheerio.Static where
|
||||
|
||||
import Prelude hiding (eq)
|
||||
|
||||
import Effect (Effect)
|
||||
import Data.Maybe (Maybe(..))
|
||||
import Test.Unit (TestSuite, suite, test)
|
||||
import Test.Unit.Assert as Assert
|
||||
import Test.Unit.Main (runTest)
|
||||
|
||||
import Cheerio (attr, find, length, text)
|
||||
|
||||
import Cheerio.Static
|
||||
( load
|
||||
, select
|
||||
, selectDeep
|
||||
, html
|
||||
, root
|
||||
, loadRoot
|
||||
)
|
||||
|
||||
import Test.HtmlEx (htmlEx)
|
||||
|
||||
main :: Effect Unit
|
||||
main = runTest suites
|
||||
|
||||
suites :: TestSuite
|
||||
suites = do
|
||||
suite "Loading" do
|
||||
test "load" do
|
||||
Assert.equal
|
||||
1
|
||||
(load htmlEx # root # find "#fruits" # length)
|
||||
|
||||
suite "Selectors" do
|
||||
test "select" do
|
||||
Assert.equal
|
||||
(Just "pear")
|
||||
(load htmlEx # select "ul .pear" # attr "class")
|
||||
|
||||
test "selectDeep" do
|
||||
Assert.equal
|
||||
"Apple"
|
||||
(load htmlEx # selectDeep [ ".apple", "#fruits" ] # text)
|
||||
|
||||
suite "Rendering" do
|
||||
test "html" do
|
||||
Assert.equal
|
||||
(Just """<li class="apple">Apple</li>""")
|
||||
(loadRoot htmlEx # find ".apple" # html)
|
@ -8,4 +8,4 @@ htmlEx =
|
||||
<li class="orange">Orange</li>
|
||||
<li class="pear">Pear</li>
|
||||
</ul>
|
||||
"""
|
||||
"""
|
||||
|
@ -7,7 +7,6 @@ import Test.Unit (TestSuite)
|
||||
import Test.Unit.Main (runTest)
|
||||
|
||||
import Test.Cheerio as C
|
||||
import Test.Cheerio.Static as CS
|
||||
|
||||
main :: Effect Unit
|
||||
main = runTest suites
|
||||
@ -15,4 +14,3 @@ main = runTest suites
|
||||
suites :: TestSuite
|
||||
suites = do
|
||||
C.suites
|
||||
CS.suites
|
||||
|
Loading…
Reference in New Issue
Block a user