I am working through the blaze-html tutorial. I just want a simple Hello World page.
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad (forM_)
import Text.Blaze.Html5
import Text.Blaze.Html5.Attributes
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Text.Blaze.Html.Renderer.Text
notes :: Html
notes = docTypeHtml $ do
H.head $ do
H.title "John´ s Page"
body $ do
p "Hello World!"
Where is it? How do I get my HTML? Can I just print it to the terminal or a file? That would be a great start.
<html>
<head><title>John's Page</title></head>
<body><p>Hello World!</p></body>
</html>
And are all the import statements really necessary? I just want it to work.
I tried printing using the renderHTML function but I just get an error message:
main = (renderHtml notes) >>= putStrLn
notes.hs:21:9:
Couldn't match expected type `IO String'
with actual type `Data.Text.Internal.Lazy.Text'
In the return type of a call of `renderHtml'
In the first argument of `(>>=)', namely `(renderHtml notes)'
In the expression: (renderHtml notes) >>= putStrLn
The result of "renderHtml" is not wrapped in a monad so you don't need to use >>=
Just print out the result:
main = putStrLn $ show $ renderHtml notes
The result is:
"<!DOCTYPE HTML>\n<html><head><title>John' s
Page</title></head><body><p>Hello World!</p></body></html>"
Generally speaking, the place to start with errors like this is to load the file into GHCI and see what the types are. Here is the session I'd use for this issue:
*Main> :t notes
notes :: Html
*Main> :t renderHtml notes
renderHtml notes :: Data.Text.Internal.Lazy.Text
You can see that the output of renderHtml notes is just an instance of Text. Text has a Show instance so we can just call "putStrLn $ show $ renderHtml notes" to get the desired output.
However, it is usually better to use the Data.Text.[Lazy.]IO package to perform IO when using Text. Note the import for "TIO" and the last line in the code below:
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad (forM_)
import Text.Blaze.Html5
import Text.Blaze.Html5.Attributes
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Text.Blaze.Html.Renderer.Text
import qualified Data.Text.Lazy.IO as TIO
notes :: Html
notes = docTypeHtml $ do
H.head $ do
H.title "John' s Page"
body $ do
p "Hello World!"
--main = putStrLn $ show $ renderHtml notes
main = TIO.putStr $ renderHtml notes
Related
I am aware of this thread and the agreed-upon ghci :browse command, but I am looking for something similar to run from a script.hs file:
Say I have a module that I can import into my script.hs. How do I then view the list of functions I have just gained access to?
What I've settled on for now
Adapting this thread that suggests the now-deprecated ghc-mod command-line program, I am
calling the terminal command ghc -e ':browse <module, e.g. Data.List>'
from my script.hs using Shelly.
My full script:
#!/usr/bin/env runghc
{-# LANGUAGE OverloadedStrings #-}
import Safe (headDef)
import Shelly
import System.Environment (getArgs)
import qualified Data.Text as T
mdl :: IO String
mdl = getArgs >>= return . headDef "Data.List"
runShelly :: String -> IO ()
runShelly mdl = shelly $ silently $ do
out <- run "ghc" ["-e", T.pack (":browse " ++ mdl)]
let lns = T.lines out
liftIO $ mapM_ (putStrLn .T.unpack) $ lns
main :: IO ()
main = mdl >>= runShelly
This way I can pass the module name on the command line as <script> <module> and get back the functions, one per line. It defaults to Data.List if I pass no arguments.
So that's a solution, but surely there must be handier introspection facilities than this?
I have the following code, just copy-pasted and modernised (the original example does not compile with recent versions of Heist anymore) from here.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString.Char8 as BS
import Data.Monoid
import Data.Maybe
import Data.List
import Control.Applicative
import Control.Lens
import Control.Monad.Trans
import Control.Monad.Trans.Either
import Heist
import Heist.Compiled
import Blaze.ByteString.Builder
conf :: HeistConfig IO
conf = set hcTemplateLocations [ loadTemplates "." ] $
set hcInterpretedSplices defaultInterpretedSplices $
emptyHeistConfig
runHeistConf :: Either [String] (HeistState IO) -> IO (HeistState IO)
runHeistConf (Right hs) = return hs
runHeistConf (Left msgs) = error . intercalate "\n" $ map ("[Heist error]: " ++) msgs
main :: IO ()
main = do
heist <- id <$> (runEitherT $ initHeist conf) >>= runHeistConf
output <- fst $ fromMaybe (error "xxx") $ renderTemplate heist "billy"
BS.putStrLn . toByteString $ output
And the following template:
<!-- billy.tpl -->
<bind tag="wanted">Playstation 4</bind>
<bind tag="got">Monopoly board game</bind>
<apply template="letter">
<bind tag="kiddo">Billy</bind>
I regret to inform you the "<wanted />" you have requested is currently
unavailable. I have substituted this with "<got />". I hope this does not
disappoint you.
</apply>
Running this program outputs to the console the whole template (almost) as is. No substistutions are made. Probably there's some function call missing, required by modern Hesit versions. I was trying to track it down in the documentation, but no luck. Why doesn't it work?
Output:
<!-- billy.tpl --><bind tag='wanted'>Playstation 4</bind>
<bind tag='got'>Monopoly board game</bind>
<apply template='letter'>
<bind tag='kiddo'>Billy</bind>
I regret to inform you the "<wanted></wanted>" you have requested is currently
unavailable. I have substituted this with "<got></got>". I hope this does not
disappoint you.
</apply>
It looks like you are using renderTemplate from Heist.Compiled, but defining interpreted splices. I believe if you change this line:
set hcInterpretedSplices defaultInterpretedSplices
to this
set hcLoadTimeSplices defaultLoadTimeSplices
it should work
I am trying to do something similar to this, where for an element in list of strings, I have a checkbox next to it and figure out which checkbox is checked or not. Using examples from the internet, I was able to get an example running
{-# LANGUAGE OverloadedStrings #-}
import Data.Monoid
import Data.String
import Data.List
import qualified Data.Text as T
import Web.Spock.Safe
import Web.Spock.Digestive
import Text.Blaze (ToMarkup(..))
import Text.Blaze.Html5 hiding (html, param, main)
import qualified Text.Blaze.Html5 as H
import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
import Text.Digestive
import Text.Digestive.Blaze.Html5
import System.Directory
import Control.Monad.IO.Class
import Control.Monad (forM_)
gen :: Html -> [Html] -> Html
gen title elts = H.html $ do
H.head $
H.title title
H.body $
H.ul $ mapM_ H.li elts
data CheckBox = CheckBox { postTitle :: T.Text }
checkboxForm = CheckBox
<$> "title" .: Text.Digestive.text Nothing
renderForm :: View Html -> Html
renderForm v = do
Text.Digestive.Blaze.Html5.form v "POST" $ do
H.p $ do
Text.Digestive.Blaze.Html5.label "title" v "Post title: "
inputText "text" v
inputSubmit "Submit Post"
main :: IO ()
main =
runSpock 8080 $ spockT Prelude.id $ do
get root $ do
listing <- liftIO $ getDirectoryContents "/home/hasenov/mydir"
let filteredListing = filter (\l -> not $ isPrefixOf "." l) listing
(view, result) <- runForm "checkboxForm" checkboxForm
case result of
Nothing -> lazyBytes $ renderHtml (renderForm view)
Just newCheckbox -> lazyBytes $ renderHtml (renderForm view)
-- lazyBytes $ renderHtml (gen "My Blog" (Data.List.map fromString filteredListing))
-- get ("hello" <//> var) $ \name ->
-- text ("Hello " <> name <> "!")
However, in the function renderForm, when I change inputText to something like inputCheckbox "True", I get the error True does not exist. I am not able to find an example where inputCheckbox is used, I was hoping someone would help me adapt filteredString so it would display checkboxes next to it, and I can run the form properly. Also, in previous link I posted, I don't know what the function inputCheckBox, since I could only find inputCheckbox. Perhaps this is an outdated function?
I'm answering my own question since I figured out how to get inputCheckbox instead of inputText. Actually, this example helped alot. It was the only one I could find which uses inputCheckbox. What I needed to do was change
data CheckBox = CheckBox { postTitle :: T.Text }
to
data CheckBox = CheckBox Bool
Then I could just initialized
checkboxForm = CheckBox
<$> "title" .: bool (Just False)
Here is the full source:
{-# LANGUAGE OverloadedStrings #-}
import Data.Monoid
import Data.String
import Data.List
import qualified Data.Text as T
import Web.Spock.Safe
import Web.Spock.Digestive
import Text.Blaze (ToMarkup(..))
import Text.Blaze.Html5 hiding (html, param, main)
import qualified Text.Blaze.Html5 as H
import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
import Text.Digestive
import Text.Digestive.Blaze.Html5
import System.Directory
import Control.Monad.IO.Class
import Control.Monad (forM_)
gen :: Html -> [Html] -> Html
gen title elts = H.html $ do
H.head $
H.title title
H.body $
H.ul $ mapM_ H.li elts
data CheckBox = CheckBox Bool
checkboxForm = CheckBox
<$> "title" .: bool (Just False)
renderForm :: View Html -> [Html] -> Html
renderForm v strings = do
Text.Digestive.Blaze.Html5.form v "POST" $ do
H.p $ mapM_ (\string -> do
inputCheckbox "title" v
Text.Digestive.Blaze.Html5.label "title" v string
H.br) strings
inputSubmit "Submit Post"
main :: IO ()
main =
runSpock 8080 $ spockT Prelude.id $ do
get root $ do
listing <- liftIO $ getDirectoryContents "/home/ecks/btsync-gambino"
let filteredListing = filter (\l -> not $ isPrefixOf "." l) listing
(view, result) <- runForm "checkboxForm" checkboxForm
case result of
Nothing -> lazyBytes $ renderHtml (renderForm view (Data.List.map fromString filteredListing))
Just newCheckbox -> lazyBytes $ renderHtml (renderForm view (Data.List.map fromString filteredListing))
-- lazyBytes $ renderHtml (gen "My Blog" (Data.List.map fromString filteredListing))
-- get ("hello" <//> var) $ \name ->
-- text ("Hello " <> name <> "!")
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.
Good day, i have mongodb database filled with some data, i ensured that data stored in correct charset, to fetch data i use following snippet:
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.Wai.Handler.Warp (run)
import Data.Enumerator (Iteratee (..))
import Data.Either (either)
import Control.Monad (join)
import Data.Maybe (fromMaybe)
import Network.HTTP.Types (statusOK, status404)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Char8 (unpack)
import Data.ByteString.Lazy.Char8 (pack)
import qualified Data.Text.Lazy as T
import Data.Text (Text(..))
import Control.Monad.IO.Class (liftIO, MonadIO)
import Data.Aeson (encode)
import qualified Data.Map as Map
import qualified Database.MongoDB as DB
application dbpipe req = do
case unpack $ rawPathInfo req of
"/items" -> itemsJSON dbpipe req
_ -> return $ responseLBS status404 [("Content-Type", "text/plain")] "404"
indexPage :: Iteratee B.ByteString IO Response
indexPage = do
page <- liftIO $ processTemplate "templates/index.html" []
return $ responseLBS statusOK [("Content-Type", "text/html; charset=utf-8")] page
processTemplate f attrs = do
page <- L.readFile f
return page
itemsJSON :: DB.Pipe -> Request -> Iteratee B.ByteString IO Response
itemsJSON dbpipe req = do
dbresult <- liftIO $ rundb dbpipe $ DB.find (DB.select [] $ tu "table") >>= DB.rest
let docs = either (const []) id dbresult
-- liftIO $ L.putStrLn $ encode $ show $ map docToMap docs
return $ responseLBS statusOK [("Content-Type", "text/plain; charset=utf-8")]
(encode $ map docToMap docs)
docToMap doc = Map.fromList $ map (\f -> (T.dropAround (== '"') $ T.pack $ show $ DB.label f, T.dropAround (== '"') $ T.pack $ show $ DB.value f)) doc
main = do
pipe <- DB.runIOE $ DB.connect $ DB.host "127.0.0.1"
run 3000 $ application pipe
rundb pipe act = DB.access pipe DB.master database act
tu :: B.ByteString -> UString
tu = DB.u . C8.unpack
Then the result is suprprising, DB.label works well, but DB.value giving me native characters as some escape codes, so the result is look like:
curl http://localhost:3000/items gives:
[{"Марка": "\1058\1080\1087 \1087\1086\1076",
"Model": "BD-W LG BP06LU10 Slim \1058\1080\1087 \1087\1086\1076\1082\1083\1102\1095\1077\1085\1080\1103"},
...
]
This happens in case i trying to print data and also in case i return data encoded as JSON
Any idea how correctly extract values from MongoDB driver ?
The following line confirms that aeson's encoding works properly (using the utf8-string library to read utf8 data off the lazy bytestring back to a haskell string:
> putStrLn $ Data.ByteString.Lazy.UTF8.toString $ encode $ ("\1058\1080\1087 \1087\1086\1076",12)
["Тип под",12]
Looking at your code more closely I see the real problem. You're calling T.pack $ show $ DB.value -- this will render out as literal codepoints, and then pack those into a text object. The fix is to switch from show to something smarter. Look at this (untested)
smartShow :: DB.Value -> Text
smartShow (String s) = Data.Text.Encoding.decodeUtf8 $ Data.CompactString.UTF8.toByteString s
smartShow x = T.pack $ show x
Obviously to handle the recursive cases, etc. you need to be smarter than that, but that's the general notion...
In fact, the "best" thing to do is to write a function of BSON -> JSON directly, rather than go through any intermediate structures at all.
Everything is working as expected -- only your expectations are wrong. =)
What you're seeing there are not raw Strings; they are String's which have been escaped to exist purely in the printable ASCII range by the show function, called by print:
print = putStrLn . show
Never fear: in memory, the string that prints as "\1058" is in fact a single Unicode codepoint long. You can observe this by printing the length of one of the Strings you're interested in and comparing that to the number of Unicode codepoints you expect.