Most of this web page is made writing html by hand, but today I decided to create a little helper to help me to create blog entries, and it's name is pager.
So in this entry I'm going to explain how pager works, it also serves as how Haskell can be used for real applications, or at least little helpers.
The code starts with 3 definitions, these are
data Tag = Head Header | Para Paragraph
data Header = H1 String | H2 String | H3 String and
newtype Paragraph = Paragraph String. These are the types with which we are going to parse the pager file.
Then we implement the Show typeclass for every type, where the typeclass of Tag just calls the underlying show function of the subtypes, and the subtypes implement a show method that transform from a Haskell data type to a String representing html.
Then to parse the pager file we use parser combinators, this nifty thing makes parsing files simple, in summary you have lots of parsers and you assume the first is going to work, if it doesn't work we take the second parser and so on until one success or we end up with no other parser to try.
The first function I want to see is parseHN, this function has the type Int -> (String -> Header) -> Parser Header, which we can read as, this function takes an Int and a function with type String -> Header, and returns a Parser of Header, this function is quite simple, but to understand it, we need to know that the data type constructor are just functions that take some parameter and return a value of that type. The function starts with
string $ replicate x '#' this basically says match a string that starts with x characters of '#', then we have
information <- some $ noneOf "\n" which is basically saying take at least one character that isn't '\n', then we use
parseEOForNewline that just consumes eof or a newline and we put into the Parser the Header with information with
return $ constructor information. This is our main function to parse headers.
Then we have a function like
parseH1, parseH2 and parseH3 that just take parseHN and give it some arguments like the constructor and the number of # that needs to match.
The other important function we have is parseParagraph, that has the type Parser Paragraph. This function just takes one or more characters, uses
parseEOForNewline and return the information into a Paragraph.
The function parseHeader uses the parser combination thing I said before, its says try parseH3 if that fails, try parseH2 if that fails, try parseH1 if that fails this isn't a Header.
And lastly, we have parseTag that has type Parse Tag, it tries to parse a header with parseHeader if it fails it tries to parse a paragraph with
parseParagraph and then maps the constructor on the Parser.
Our main function just reads the args from the environment and passes them to handleFile, this function takes a header, a footer and a filename, then it pass the filename to the parser and if that works we concatenate header with the parsed content and the footer, and write to a file. That's all the pager does and it works.
Thanks for reading.