Scotty + Html -> how to intertwine them? - html

How can I work with html pages, including html templates, in Scotty? But not via Blaze because I don't like describing its structure in haskell code. It think I should heist, but how exactly to intertwine it with Scotty?

You can use Heist.renderTemplate to turn a template into Blaze.ByteString.Builder.Builder (that's not blaze-html, I assume that's okay) and then set that via Web.Scotty.raw. For example:
{-# LANGUAGE OverloadedStrings #-}
import Heist
import Heist.Interpreted
import Web.Scotty (scotty, get, raw, setHeader)
import Control.Monad.Trans.Either (runEitherT)
import Control.Monad.IO.Class (liftIO)
import Blaze.ByteString.Builder (toByteString)
import qualified Data.ByteString.Lazy as DBL
import qualified Data.Text.Lazy.Encoding as DT
import Text.XmlHtml
main = scotty 3000 $
get "/" $ do
-- normally you would probably load your templates from a file,
-- but to keep the example small
(Right heist) <- liftIO $ runEitherT $ initHeist emptyHeistConfig
let heist' = addTemplate "foo" [TextNode "Hello world!"] Nothing heist
(Just (builder, mime)) <- renderTemplate heist' "foo"
setHeader "Content-Type" (DT.decodeUtf8 $ DBL.fromStrict mime)
raw $ DBL.fromStrict $ toByteString builder

Related

GHC API: Find all functions (and their types) in scope

I'm trying to list (print) all functions (and their types) that are in scope for a given module.
For example, I have this module:
{-# LANGUAGE NoImplicitPrelude #-}
module Reverse where
import Prelude ((==), String)
myString :: String
myString = "string"
I'm trying to use the GHC API (8.0.2), but I can't seem to find where the information that I'm looking for is stored.
I have managed to find all functions in scope, (but not with their types,) with this code:
import Data.IORef
import DynFlags
import GHC
import GHC.LanguageExtensions
import GHC.Paths (libdir)
import HscTypes
import NameEnv
import OccName
import Outputable
import RdrName
import TcRnTypes
main = runGhc (Just libdir) $ do
liftIO $ print sets
dflags <- getSessionDynFlags
let compdflags =
(foldl xopt_set dflags [Cpp, ImplicitPrelude, MagicHash])
setSessionDynFlags compdflags
target <- guessTarget "Reverse.hs" Nothing
setTargets [target]
load LoadAllTargets
modSum <- getModSummary $ mkModuleName "Reverse"
parsedModule <- parseModule modSum
tmod <- typecheckModule parsedModule
let (tcenv, moddets) = tm_internals_ tmod
printO $ map (map gre_name) $ occEnvElts $ tcg_rdr_env tcenv
printO
:: (GhcMonad m, Outputable a)
=> a -> m ()
printO a = do
dfs <- getProgramDynFlags
liftIO $ putStrLn $ showPpr dfs a
I get this output:
[[String], [==], [myString]]
Of course, this is only half of the way to the data I need.
The GHC API is rather confusing, you have to get used to a lot of abbreviations, type-synonyms and a style-heterogenous codebase, but finding the the name and type of everything in scope should be possible.
Otherwise GHC couldn't tell you that your function is not in scope if you make a typo.
Indeed, once you have type-checked a module, all the relevant information is available.
First you need all the Names of your functions, which you can get with this code:
parsedModule <- parseModule modSum
tmod <- typecheckModule parsedModule
let (tcenv, moddets) = tm_internals_ tmod
let names = concatMap (map gre_name) $ occEnvElts $ tcg_rdr_env tcenv
Then you need to lookup the Names to get TyThings with lookupName.
You'll get a Maybe TyThing (Nothing if the Name is not found), and when the name refers to a function, the TyThing will be AnId i where i is the thing you're looking for.
An Id is just a name with a type.
You can then get the type with varType.
You could argue that all these types make this problem a lot harder, but they've made it possible for me to figure out what I need to do without looking into the code and with no documentation.

Write json to a file in Haskell (with Text rather than [Char])

I'm trying to serialize an object into a JSON string and write it to a file.
In python, I'd do something like:
>>> meowmers = {"name" : "meowmers", "age" : 1}
>>> import json
>>> with open("myfile.json","wb") as f
json.dump(meowmers, f)
$ cat myfile.json
{"age": 1, "name": "meowmers"}
I'm looking at this in Haskell
$ stack ghci
{-# LANGUAGE OverloadedStrings #-}
:set -XOverloadedStrings
import GHC.Generics
import Data.Aeson as A
import Data.Text.Lazy as T
import Data.Text.Lazy.IO as I
:{
data Cat = Cat {
name :: Text
, age :: Int
} deriving Show
:}
let meowmers = Cat {name = "meowmers", age = 1}
writeFile "myfile.json" (encode meowmers)
Oh no!
*A T I GHC.Generics> I.writeFile "myfile2.json" (encode meowmers)
<interactive>:34:29:
Couldn't match expected type ‘Text’
with actual type ‘bytestring-0.10.6.0:Data.ByteString.Lazy.Internal.ByteString’
In the second argument of ‘I.writeFile’, namely ‘(encode meowmers)’
In the expression: I.writeFile "myfile2.json" (encode meowmers)
Two questions:
This appears to be a bytestring. How can I work with that?
If that's not what I want to do, is there a Haskell json serialization solution using Text rather than String that is yet rather simple?
So, to iron everything out (since most of the work has already been done). You actually have two problems:
You are mixing string types
You don't have an instance of ToJSON declared for Cat
Here is a working example that relies on recent versions of aeson and text (for me that is aeson-1.0.0.0 and text-1.2.2.1.
{-# LANGUAGE OverloadedStrings, DeriveGeneric, DeriveAnyClass #-}
import GHC.Generics
import Data.Text.Lazy (Text)
import Data.Text.Lazy.IO as I
import Data.Aeson.Text (encodeToLazyText)
import Data.Aeson (ToJSON)
data Cat = Cat { name :: Text, age :: Int } deriving (Show, Generic, ToJSON)
meowmers = Cat { name = "meowmers", age = 1 }
main = I.writeFile "myfile.json" (encodeToLazyText meowmers)
As you can probably tell from the imports, I rely on aeson to convert between string types through encodeToLazyText. That deals with problem number 1.
Then, I use the language extension DeriveGeneric to get a Generic instance for Cat, and use that in conjunction with the extension DeriveAnyClass to get an instance of ToJSON for Cat. The magic of that instance is again part of aeson.
Running this, I get a new file myfile.json that contains {"age":1,"name":"meowmers"} in it.
You can encode JSON to a lazy Text value directly using Data.Aeson.Text.encodeToLazyText.
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson.Text (encodeToLazyText)
...
I.writeFile "myfile.json" (encodeToLazyText meowmers)
A bytestring is a type for binary data—not necessarily text. To represent textual data in a bytestring, you need to encode it with some encoding like UTF-8. Once you have a bytestring (encoded with UTF-8 or whatever format makes sense), you can write it to a file using Data.ByteString functions:
import qualified Data.ByteString.Lazy as BS
BS.writeFile "myfile.json" (encode meowmers)
To make this work you need to give your Cat type a ToJSON instance that specifies how to encode it in JSON. You can do this automatically with the DeriveGeneric extension:
data Cat = Cat { ... } deriving (Show, Generic)
instance ToJSON Cat
You can also do this manually if you need finer control over what the resulting JSON looks like.
Rolling all the comments and answers into one (credit to the other folks here, please accept one of their answers).
Derive Generic and ToJSON (or manually write a ToJSON instance if you'd like). This required a few more LANGUAGE pragmas.
Use either a newer version of text and encodeToLazyText OR use Data.ByteString.Lazy.writeFile to write out the bytestring.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
import GHC.Generics
import Data.Aeson (encode,ToJSON(..))
import Data.Text.Lazy (Text)
import qualified Data.ByteString.Lazy as BS
data Cat = Cat { name :: Text
, age :: Int
} deriving (Show,Generic,ToJSON)
main =
do let meowmers = Cat {name = "meowmers", age = 1}
BS.writeFile "myfile.json" (encode meowmers)
Resulting in:
tommd#HalfAndHalf /tmp% runhaskell so.hs
tommd#HalfAndHalf /tmp% cat myfile.json
{"age":1,"name":"meowmers"}

How to validate JSON with schema in Haskell?

I want to validate JSON with a schema. hjsonschema seemed like a good choice as it is fairly new and supports the latest draft.
But the plotly json schema always gives me valid responses.
I may be misunderstanding something here but this should not be valid JSON
bad.json
{
"fjsdklj" : 5
}
even though it is considered valid by the following code
module Main where
import Control.Applicative
import Data.Aeson
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H
import Data.Monoid
import qualified Data.ByteString.Lazy as B
import qualified Data.JsonSchema as JS
import Data.Maybe
main :: IO ()
main = do
schemaJSON <- (fromJust . decode) <$> B.readFile "simple-schema.json"
bad <- (fromJust . decode) <$> B.readFile "bad.json"
let schemaData = JS.RawSchema {
JS._rsURI = Nothing,
JS._rsData = schemaJSON
}
schema <- compileSchema (JS.SchemaGraph schemaData H.empty) schemaData
checkResults (JS.validate schema bad)
compileSchema :: JS.SchemaGraph -> JS.RawSchema -> IO (JS.Schema JS.Draft4Failure)
compileSchema graph rs =
case JS.compileDraft4 graph rs of
Left failure -> error $ "Not a valid schema: " <> show failure
Right schema -> return schema
checkResults :: [JS.ValidationFailure JS.Draft4Failure] -> IO ()
checkResults [] = putStrLn "Just fine"
checkResults x = putStrLn $ "ERROR: " ++ show x
simple-schema.json is the plotly schema and bad.json the snippet I posted above.
It's nothing about Haskell.
Your schema doesn't have a required property at top level so an empty json object is acceptable.
Also it contains no "additionalProperties": false property so anything that does not fit into one of the defined patterns are ignored.
BTW I doubt whether it's a valid Draft4 json schema. It passed the validation of the Draft4 meta-schema but the syntax is a little bit different, maybe something Python-specific. You'd better run the test suites which came along with the hjsonscheme package to see if everything works fine.

Creating a csv file in haskell

I suspect this a bit of a noobie question...
So I have a list myList that I would like to write to a csv file, using the cassava library:
import qualified Data.ByteString.Lazy as BL
import qualified Data.Vector as V
import Data.Csv
main = BL.writeFile "myFileLocation" (encode $ V.fromList myList)
as far as I can establish, encode has type ToRecord a => Vector a -> ByteString yet I'm getting the following error:
Couldn't match expected type `[a0]'
with actual type `V.Vector (a,b,c,d)'
In the return type of a call of `V.fromList'
In the second argument of `($)', namely `V.fromList myList'
In the second argument of `BL.writeFile', namely
`(encode $ V.fromList myList)'
I'm confused!

Using blaze-html, how do I create leading non-breaking spaces in html

Using the package blaze-html, I want to create some html that looks like this
<p> Some indented text
I can't figure out how to create the non-breaking spaces. What's the best way to do that?
One way of course is to give a string containing a Haskell-encoded version of that character to toHtml. Another way is to use preEscapedToMarkup:
preEscapedToMarkup " "
Following the advice of ertes, I tried "to give a string containing a Haskell-encoded version of the character". This code is verbose so I could better understand how the types fit together.
{-# LANGUAGE OverloadedStrings #-}
-- * base
import Data.Monoid ((<>))
import Data.Char (chr)
-- * text
import qualified Data.Text.Lazy as L (Text)
import Data.Text (Text, singleton)
import Data.Text.Lazy.Read (hexadecimal)
-- * blaze-html
import Text.Blaze.Html5
import qualified Text.Blaze.Html5 as H
import Text.Blaze.Renderer.String
nbspHex = "00A0" :: L.Text -- Unicode codepoint for nbsp
Right (nbspInt, _) = hexadecimal nbspHex
nbspChar = chr nbspInt :: Char
nbspTxt = singleton nbspChar :: Text
nbspHtml = toHtml nbspTxt :: Html
someHtml :: Html
someHtml = docTypeHtml $
body $ do
p "some text"
p (nbspHtml <> nbspHtml <> "some indented text")
main :: IO ()
main = do
let s = renderHtml $ someHtml
putStrLn s
This code displayed the " some indented text" in the browser as I had hoped, but looking at the html, I didn't see any "#nbsp;", as I had expected. So this isn't really the answer I was looking for.
Thanks to Jukka for pointing out a better solution.