I am trying to get the basic example from http://egonschiele.github.io/HandsomeSoup/ to work:
main = do
doc <- fromUrl "http://www.google.com/search?q=egon+schiele"
links <- runX $ doc >>> css "h3.r a" ! "href"
mapM_ putStrLn links
I have tried to reproduce the example like so:
module Main (main) where
import Text.HandsomeSoup
import Text.XML.HXT.Core
import Control.Monad
main = do
doc <- fromUrl "http://www.google.com/search?q=egon+schiele"
links <- runX $ doc >>> css "h3.r a" ! "href"
mapM_ putStrLn links
But, I am getting the following error:
$ runhaskell Main.hs
Main.hs:8:21:
Couldn't match expected type `IOSLA (XIOState ()) XmlTree b0'
with actual type `hxt-9.3.0.1:Data.Tree.NTree.TypeDefs.NTree
hxt-9.3.0.1:Text.XML.HXT.DOM.TypeDefs.XNode'
In the first argument of `(>>>)', namely `doc'
In the second argument of `($)', namely
`doc >>> css "h3.r a" ! "href"'
In a stmt of a 'do' block:
links <- runX $ doc >>> css "h3.r a" ! "href"
But I really can't seem to figure out what's going on.
Function fromUrl has type fromUrl :: String -> IOSArrow XmlTree (NTree XNode).
So IOSArrow XmlTree (NTree XNode) is not clear IO-action.
Simplest way fix it - use let statement instead:
import Text.HandsomeSoup
import Text.XML.HXT.Core
main :: IO ()
main = do
let doc = fromUrl "http://www.google.com/search?q=egon+schiele"
links <- runX $ doc >>> css "h3.r a" ! "href"
mapM_ putStrLn links
Related
I have a program that works. However, I wasn't exactly sure why it was compiling.
Here's my main function
module Main where
import System.Environment
import Cover5
main :: IO ()
main = do
args <- getArgs
let numGames = if null args then 13 else read $ head args
putStrLn $ "Making picks for " ++ show numGames ++ " games."
print $ run numGames
where the function run has the signature run :: Int -> RVar [(Int, Char)].
What is confusing is that there is no instance of Show for RVar [(Int,Char)] so I figure it shouldn't compile, but it does (see Travis-CI build and the related source for that commit of Main.hs). I can force a warning with this command:
cabal build --ghc-options="-fforce-recomp -fno-code"
Preprocessing executable 'cover5' for cover5-0.1.0.1...
[1 of 2] Compiling Cover5 ( src/Cover5.hs, nothing )
[2 of 2] Compiling Main ( src/Main.hs, nothing )
src/Main.hs:13:10: error:
• No instance for (Show (RVar [(Int, Char)]))
arising from a use of ‘print’
• In a stmt of a 'do' block: print $ run numGames
In the expression:
do { args <- getArgs;
let numGames = ...;
putStrLn $ "Making picks for " ++ show numGames ++ " games.";
print $ run numGames }
In an equation for ‘main’:
main
= do { args <- getArgs;
let numGames = ...;
putStrLn $ "Making picks for " ++ show numGames ++ " games.";
.... }
I'd like to "fix" this and have followed guidance from the question Convert Data.RVar.RVar [Char] to [Char]
So I add the appropriate imports for StdRandom and runRVar and write the following lines
results <- runRVar (run numGames) StdRandom
putStrLn $ show results
But I'm tripping over myself trying to find instances of MonadRandom IO for my usage:
src/Main.hs:13:21: error:
• No instance for (MonadRandom IO) arising from a use of ‘runRVar’
• In a stmt of a 'do' block:
results <- runRVar (run numGames) StdRandom
In the expression:
do { args <- getArgs;
let numGames = ...;
putStrLn $ "Making picks for " ++ show numGames ++ " games.";
results <- runRVar (run numGames) StdRandom;
.... }
In an equation for ‘main’:
main
= do { args <- getArgs;
let numGames = ...;
putStrLn $ "Making picks for " ++ show numGames ++ " games.";
.... }
So two questions:
Why does the original version compile and work?
What might you suggest I do to remove the warning and write this "correctly"?
The answer(s) were provided by #leftaroundabout in the comments. Posting an answer here for others new to Random in case it proves helpful
First Question
I had written the original program about 3 years ago. I had no idea how to print results from RVars. I chose to use Data.Random.Show.Unsafe. Apparently this module provides an instance of Show for RVar a. I didn't remember that.
In addition, I was implicitly exporting everything from my Cover5 module so the main module whose code is above was importing that instance.
So that's why print was working.
Second Question
Instances are available in Data.Random, which I wasn't importing.
The reason why I was getting errors but was thinking they were warnings is because I changed 2 (actually more than 2) things at once:
I restricted what was exported from Cover5 module so the Unsafe
instance of Show wasn't exported anymore
I ran the build with some arguments which I thought were just going to show more warnings than a traditional build.
How comes that this is working:
module Main where
import System.Environment (getArgs)
import qualified Data.ByteString.Char8 as B
import Data.ByteString.Char8 (ByteString)
type Field = ByteString
type Row = [Field]
type CSV = [Row]
main :: IO ()
main = do
contents <- B.readFile "test.csv"
print (parseCSV contents)
-- used with "./myprogram" to read "test.csv"
But not if I replace "test.csv" with a command-line argument in main:
-- ... same as before ...
main :: IO ()
main = do
contents <- B.readFile $ head getArgs
print (parseCSV contents)
-- used with "./myprogram test.csv" to read "test.csv"
With the latter I get this error at compile time:
csv.hs:20:35: error:
• Couldn't match expected type ‘[FilePath]’
with actual type ‘IO [String]’
• In the first argument of ‘head’, namely ‘getArgs’
In the second argument of ‘($)’, namely ‘head getArgs’
In a stmt of a 'do' block: contents <- B.readFile $ head getArgs
EDIT --
I initially omitted a part of the code, which I thought was not necessary to understand the question. It's corrected now.
Remember that getArgs :: IO [String] is also monadic, so you need to extract it first in the do block before trying to get the head of it:
main :: IO ()
main = do
args <- getArgs -- now `args :: [String]`
contents <- B.readFile (head args)
print (parseCSV contents)
To be clear, I am only interested in using heist, not snap. I'm reading through ocharles's tutorial (https://ocharles.org.uk/blog/posts/2013-12-11-24-days-of-hackage-heist.html) and trying to adapt his first example. It is a simple bind tag. My code is as follows:
-- main.hs
main :: IO ()
main = billy
billy :: IO ()
billy = do
heistState <- either (error . concat) id <$>
(runEitherT $ initHeist myConfig)
builder <- maybe (error "oops2") fst $
renderTemplate heistState "billy"
toByteStringIO BS.putStr builder
BS.putStr "\n"
myConfig = (set hcNamespace "") $
(set hcInterpretedSplices defaultInterpretedSplices) $
(set hcTemplateLocations [loadTemplates "templates"]) $
emptyHeistConfig
And the template I'm using:
<bind tag="kiddo">Billy</bind>
Merry Christmas, <kiddo/>!
The output I get is this:
<bind tag='kiddo'>Billy</bind>
Merry Christmas, <kiddo></kiddo>!
I cannot see why the bind tag doesn't work. I've actually updated his code to use the new lens-style heist config, and I know about the namespace trickery that was introduced somewhat recently in heist, but I can't see what else needs to change to get this example working.
Here is what I was able to get to work:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as B
import Blaze.ByteString.Builder (toByteStringIO)
import Control.Applicative
import Control.Monad.Trans.Either (runEitherT)
import Heist
import Heist.Compiled (renderTemplate)
import Control.Lens
heistConfig =
(set hcNamespace "") $
-- (set hcInterpretedSplices defaultInterpretedSplices) $
(set hcLoadTimeSplices defaultLoadTimeSplices) $
(set hcTemplateLocations [loadTemplates "."]) $
emptyHeistConfig
main = do
heistState <- either (error "oops") id <$>
(runEitherT $ initHeist heistConfig)
builder <- maybe (error "oops") fst $
renderTemplate heistState "billy"
toByteStringIO B.putStr builder
Apparently bind is a load time splice, not an interpreted splice.
Here is my attempt at finding all inputs of type "text" on a webpage. I have since figured out I could use xpath, but I'd like to know how to make the way I attempted work. I'm most interested in how I would lifft my [Element] into the [WD Element] and make this program valid.
However if my approach is just wrong or unidiomatic, feel free to totally rewrite it. Here is the code:
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad
import Control.Monad.IO.Class
import Test.WebDriver
import Test.WebDriver.Classes (WebDriver (..))
import Test.WebDriver.Commands
import Test.WebDriver.Commands.Wait
main = do
runSession defaultSession capabilities $ do
openPage "http://www.appnitro.com/demo/view.php?id=1"
inputs <- findElems $ ByTag "input"
textElems <- filterM (liftM $ ((==) "text" . (`attr` "type"))) inputs
-- wait 20 seconds
waitUntil 20 (getText <=< findElem $ ByCSS ".doesnotexist")
`onTimeout` return ""
liftIO $ putStrLn "done"
where
capabilities = allCaps { browser=firefox }
-- [1 of 1] Compiling Main ( src/Main.hs, interpreted )
-- src/Main.hs:168:70:
-- Couldn't match type `Element' with `WD Element'
-- Expected type: [WD Element]
-- Actual type: [Element]
-- In the second argument of `filterM', namely `inputs'
-- In a stmt of a 'do' block:
-- textElems <- filterM
-- (liftM $ ((==) "text" . (`attr` "type"))) inputs
-- In the second argument of `($)', namely
-- `do { openPage "http://www.appnitro.com/demo/view.php?id=1";
-- inputs <- findElems $ ByTag "input";
-- textElems <- filterM
-- (liftM $ ((==) "text" . (`attr` "type"))) inputs;
-- waitUntil 20 (getText <=< findElem $ ByCSS ".doesnotexist")
-- `onTimeout` return "" }'
-- Failed, modules loaded: none.
This might not be the answer you were looking for, but I find it best to express these kinds of constraints on elements directly in the locators, in order not to clutter the haskell sources. As you mention using XPath is one possibility:
textElems <- findElems $ ByXPath "//input[#type='text']"
but I often prefer CSS selectors, which tend to be briefer (alas a bit less powerful - e.g. you cannot traverse from given element to its parent etc.) and work just as well in these simple cases
textElems <- findElems $ ByCSS "input[type='text']"
Replace your text elem filtering with this piece:
textElems <- filterM textElem inputs
And then add this where:
textElem e = (== Just "text") `fmap` (e `attr` "type")
But then you will still have the line with waitUntil that doesn't compile. That seems unrelated though as it has nothing to do with the filtering.
You have two errors in your code:
attr returns a Maybe value, thus you must compare with Just "text".
And given that attr already returns a value in the WD monad, you just have to lift just the first function (has you had it, you were lifting everything, including the attr function):
textElems <- filterM (liftM ((==) (Just "text")) . (`attr` "type")) inputs
Or alternatively, since WD is also a Functor (I actually find this easier to understand, you are applying a pure function inside the monad returned by attr):
textElems <- filterM (fmap ((==) (Just "text")) . (`attr` "type")) inputs
I know there is a Paginator package for Yesod but I prefer a simpler UI so I was creating a simple pagination logic for my app. However, I couldn't figure out a way to convert the parameter value to Integer.
import Data.Text (unpack, singleton)
import Data.Maybe
one = singleton '1' -- convert char to Text, required by fromMaybe
getTestPanelR :: Handler Html
getTestPanelR = do
ptext <- lookupGetParam "p" -- guessing returns Maybe Text
p <- fromMaybe one ptext -- ??? does not work
-- pn <- ??? Once p is extracted successfully, how to convert to an integer?
s <- runDB $ selectList [] [Asc PersonName, LimitTo 10 , OffsetBy $ (pn - 1) * 10]
(widget, enctype) <- generateFormPost $ entryForm Nothing
defaultLayout $ do
$(widgetFile "person")
When I run the above Code I get the following error message:
No instance for (MonadHandler Maybe)
arising from a use of `lookupGetParam'
Possible fix: add an instance declaration for (MonadHandler Maybe)
In the second argument of `($)', namely `lookupGetParam "p"'
In a stmt of a 'do' block:
p <- fromMaybe one $ lookupGetParam "p"
In the expression:
...
When I write out 'ptext' using #{show ptext} it shows Just "1". Having gotten the GET parameter, how do I convert it to an integer so I can do pagination? (need to add 1 for 'next' and subtract 1 for 'prev')
FWIW, when I try this using GHCi, it works fine:
Prelude Data.Maybe Data.Text> let one = singleton '1'
Prelude Data.Maybe Data.Text> let x = Just $ singleton '5'
Prelude Data.Maybe Data.Text> let y = fromMaybe one x
Prelude Data.Maybe Data.Text> y
"5"
Prelude Data.Maybe Data.Text> read $ Data.Text.unpack y ::Int -- This is probably unsafe because I cannot trust 'y' in my web app
5
Update:
I tired #Ankur's suggestion pageNumber <- (lookupGetParam "p" >>= return . (read :: String -> Int) . fromMaybe "1") and I get the following error:
Couldn't match expected type `String' with actual type `Text'
Expected type: Maybe Text -> String
Actual type: Maybe Text -> Text
In the return type of a call of `fromMaybe'
In the second argument of `(.)', namely `fromMaybe "1"'
Build failure, pausing...
If change the "1" to one (Data.Text.singleton '1'), I still get the exact same error message.
Thanks!
lookupGetParam returns ParamValue which is type ParamValue = String. So basically it is String rather than Text.
Try this:
pageNumber <- (lookupGetParam "p" >>= return . (read :: String -> Int) . fromMaybe "1")
UPDATE:
Actually the latest version of lookupGetParam is Text based so adding the OverloadedStrings language extension should get the job done:
Put this {-# LANGUAGE OverloadedStrings #-} at the start of the code file and use:
pageNumber <- (lookupGetParam "p" >>= return . (read :: String -> Int) . unpack . fromMaybe "1")