{-
Copyright (C) 2007-2010 John MacFarlane <jgm@berkeley.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}{- |
Module : Text.Pandoc.Writers.Man
Copyright : Copyright (C) 2007-2010 John MacFarlane
License : GNU GPL, version 2 or above
Maintainer : John MacFarlane <jgm@berkeley.edu>
Stability : alpha
Portability : portable
Conversion of 'Pandoc' documents to groff man page format.
-}moduleText.Pandoc.Writers.Man(writeMan)whereimportText.Pandoc.DefinitionimportText.Pandoc.TemplatesimportText.Pandoc.SharedimportText.Pandoc.OptionsimportText.Pandoc.Readers.TeXMathimportText.Printf(printf)importData.List(isPrefixOf,intersperse,intercalate)importText.Pandoc.PrettyimportControl.Monad.StatetypeNotes=[[Block]]dataWriterState=WriterState{stNotes::Notes,stHasTables::Bool}-- | Convert Pandoc to Man.writeMan::WriterOptions->Pandoc->StringwriteManoptsdocument=evalState(pandocToManoptsdocument)(WriterState[]False)-- | Return groff man representation of document.pandocToMan::WriterOptions->Pandoc->StateWriterStateStringpandocToManopts(Pandoc(Metatitleauthorsdate)blocks)=dotitleText<-inlineListToManoptstitleauthors'<-mapM(inlineListToManopts)authorsdate'<-inlineListToManoptsdateletcolwidth=ifwriterWrapTextoptsthenJust$writerColumnsoptselseNothingletrender'=rendercolwidthlet(cmdName,rest)=break(==' ')$render'titleTextlet(title',section)=casereversecmdNameof(')':d:'(':xs)|d`elem`['0'..'9']->(text(reversexs),chard)xs->(text(reversexs),doubleQuotesempty)letdescription=hsep$map(doubleQuotes.text.trim)$splitBy(=='|')restbody<-blockListToManoptsblocksnotes<-liftMstNotesgetnotes'<-notesToManopts(reversenotes)letmain=render'$body$$notes'$$text""hasTables<-liftMstHasTablesgetletcontext=writerVariablesopts++[("body",main),("title",render'title'),("section",render'section),("date",render'date'),("description",render'description)]++[("has-tables","yes")|hasTables]++[("author",render'a)|a<-authors']ifwriterStandaloneoptsthenreturn$renderTemplatecontext$writerTemplateoptselsereturnmain-- | Return man representation of notes.notesToMan::WriterOptions->[[Block]]->StateWriterStateDocnotesToManoptsnotes=ifnullnotesthenreturnemptyelsemapM(\(num,note)->noteToManoptsnumnote)(zip[1..]notes)>>=return.(text".SH NOTES"$$).vcat-- | Return man representation of a note.noteToMan::WriterOptions->Int->[Block]->StateWriterStateDocnoteToManoptsnumnote=docontents<-blockListToManoptsnoteletmarker=cr<>text".SS "<>brackets(text(shownum))return$marker$$contents-- | Association list of characters to escape.manEscapes::[(Char,String)]manEscapes=[('\160',"\\ "),('\'',"\\[aq]"),('’',"'"),('\x2014',"\\[em]"),('\x2013',"\\[en]"),('\x2026',"\\&...")]++backslashEscapes"-@\\"-- | Escape special characters for Man.escapeString::String->StringescapeString=escapeStringUsingmanEscapes-- | Escape a literal (code) section for Man.escapeCode::String->StringescapeCode=concat.intersperse"\n".mapescapeLine.lineswhereescapeLinecodeline=caseescapeStringUsing(manEscapes++backslashEscapes"\t ")codelineofa@('.':_)->"\\&"++ab->b-- We split inline lists into sentences, and print one sentence per-- line. groff/troff treats the line-ending period differently.-- See http://code.google.com/p/pandoc/issues/detail?id=148.-- | Returns the first sentence in a list of inlines, and the rest.breakSentence::[Inline]->([Inline],[Inline])breakSentence[]=([],[])breakSentencexs=letisSentenceEndInline(Strys@(_:_))|lastys=='.'=TrueisSentenceEndInline(Strys@(_:_))|lastys=='?'=TrueisSentenceEndInline(LineBreak)=TrueisSentenceEndInline_=False(as,bs)=breakisSentenceEndInlinexsincasebsof[]->(as,[])[c]->(as++[c],[])(c:Space:cs)->(as++[c],cs)(Str".":Str(')':ys):cs)->(as++[Str".",Str(')':ys)],cs)(x@(Str('.':')':_)):cs)->(as++[x],cs)(LineBreak:x@(Str('.':_)):cs)->(as++[LineBreak],x:cs)(c:cs)->(as++[c]++ds,es)where(ds,es)=breakSentencecs-- | Split a list of inlines into sentences.splitSentences::[Inline]->[[Inline]]splitSentencesxs=let(sent,rest)=breakSentencexsinifnullrestthen[sent]elsesent:splitSentencesrest-- | Convert Pandoc block element to man.blockToMan::WriterOptions-- ^ Options->Block-- ^ Block element->StateWriterStateDocblockToMan_Null=returnemptyblockToManopts(Plaininlines)=liftMvcat$mapM(inlineListToManopts)$splitSentencesinlinesblockToManopts(Parainlines)=docontents<-liftMvcat$mapM(inlineListToManopts)$splitSentencesinlinesreturn$text".PP"$$contentsblockToMan_(RawBlock"man"str)=return$textstrblockToMan_(RawBlock__)=returnemptyblockToMan_HorizontalRule=return$text".PP"$$text" * * * * *"blockToManopts(Headerlevel_inlines)=docontents<-inlineListToManoptsinlinesletheading=caselevelof1->".SH "_->".SS "return$textheading<>contentsblockToMan_(CodeBlock_str)=return$text".IP"$$text".nf"$$text"\\f[C]"$$text(escapeCodestr)$$text"\\f[]"$$text".fi"blockToManopts(BlockQuoteblocks)=docontents<-blockListToManoptsblocksreturn$text".RS"$$contents$$text".RE"blockToManopts(Tablecaptionalignmentswidthsheadersrows)=letaligncodeAlignLeft="l"aligncodeAlignRight="r"aligncodeAlignCenter="c"aligncodeAlignDefault="l"indocaption'<-inlineListToManoptscaptionmodify$\st->st{stHasTables=True}letiwidths=ifall(==0)widthsthenrepeat""elsemap(printf"w(%0.2fn)".(70*))widths-- 78n default width - 8n indent = 70nletcoldescriptions=text$intercalate" "(zipWith(\alignwidth->aligncodealign++width)alignmentsiwidths)++"."colheadings<-mapM(blockListToManopts)headersletmakeRowcols=text"T{"$$(vcat$intersperse(text"T}@T{")cols)$$text"T}"letcolheadings'=ifallnullheadersthenemptyelsemakeRowcolheadings$$char'_'body<-mapM(\row->docols<-mapM(blockListToManopts)rowreturn$makeRowcols)rowsreturn$text".PP"$$caption'$$text".TS"$$text"tab(@);"$$coldescriptions$$colheadings'$$vcatbody$$text".TE"blockToManopts(BulletListitems)=docontents<-mapM(bulletListItemToManopts)itemsreturn(vcatcontents)blockToManopts(OrderedListattribsitems)=doletmarkers=take(lengthitems)$orderedListMarkersattribsletindent=1+(maximum$maplengthmarkers)contents<-mapM(\(num,item)->orderedListItemToManoptsnumindentitem)$zipmarkersitemsreturn(vcatcontents)blockToManopts(DefinitionListitems)=docontents<-mapM(definitionListItemToManopts)itemsreturn(vcatcontents)-- | Convert bullet list item (list of blocks) to man.bulletListItemToMan::WriterOptions->[Block]->StateWriterStateDocbulletListItemToMan_[]=returnemptybulletListItemToManopts((Parafirst):rest)=bulletListItemToManopts((Plainfirst):rest)bulletListItemToManopts((Plainfirst):rest)=dofirst'<-blockToManopts(Plainfirst)rest'<-blockListToManoptsrestletfirst''=text".IP \\[bu] 2"$$first'letrest''=ifnullrestthenemptyelsetext".RS 2"$$rest'$$text".RE"return(first''$$rest'')bulletListItemToManopts(first:rest)=dofirst'<-blockToManoptsfirstrest'<-blockListToManoptsrestreturn$text"\\[bu] .RS 2"$$first'$$rest'$$text".RE"-- | Convert ordered list item (a list of blocks) to man.orderedListItemToMan::WriterOptions-- ^ options->String-- ^ order marker for list item->Int-- ^ number of spaces to indent->[Block]-- ^ list item (list of blocks)->StateWriterStateDocorderedListItemToMan___[]=returnemptyorderedListItemToManoptsnumindent((Parafirst):rest)=orderedListItemToManoptsnumindent((Plainfirst):rest)orderedListItemToManoptsnumindent(first:rest)=dofirst'<-blockToManoptsfirstrest'<-blockListToManoptsrestletnum'=printf("%"++show(indent-1)++"s")numletfirst''=text(".IP \""++num'++"\" "++showindent)$$first'letrest''=ifnullrestthenemptyelsetext".RS 4"$$rest'$$text".RE"return$first''$$rest''-- | Convert definition list item (label, list of blocks) to man.definitionListItemToMan::WriterOptions->([Inline],[[Block]])->StateWriterStateDocdefinitionListItemToManopts(label,defs)=dolabelText<-inlineListToManoptslabelcontents<-ifnulldefsthenreturnemptyelseliftMvcat$forMdefs$\blocks->dolet(first,rest)=caseblocksof((Parax):y)->(Plainx,y)(x:y)->(x,y)[]->error"blocks is null"rest'<-liftMvcat$mapM(\item->blockToManoptsitem)restfirst'<-blockToManoptsfirstreturn$first'$$text".RS"$$rest'$$text".RE"return$text".TP"$$text".B "<>labelText$$contents-- | Convert list of Pandoc block elements to man.blockListToMan::WriterOptions-- ^ Options->[Block]-- ^ List of block elements->StateWriterStateDocblockListToManoptsblocks=mapM(blockToManopts)blocks>>=(return.vcat)-- | Convert list of Pandoc inline elements to man.inlineListToMan::WriterOptions->[Inline]->StateWriterStateDoc-- if list starts with ., insert a zero-width character \& so it-- won't be interpreted as markup if it falls at the beginning of a line.inlineListToManoptslst@(Str('.':_):_)=mapM(inlineToManopts)lst>>=(return.(text"\\&"<>).hcat)inlineListToManoptslst=mapM(inlineToManopts)lst>>=(return.hcat)-- | Convert Pandoc inline element to man.inlineToMan::WriterOptions->Inline->StateWriterStateDocinlineToManopts(Emphlst)=docontents<-inlineListToManoptslstreturn$text"\\f[I]"<>contents<>text"\\f[]"inlineToManopts(Stronglst)=docontents<-inlineListToManoptslstreturn$text"\\f[B]"<>contents<>text"\\f[]"inlineToManopts(Strikeoutlst)=docontents<-inlineListToManoptslstreturn$text"[STRIKEOUT:"<>contents<>char']'inlineToManopts(Superscriptlst)=docontents<-inlineListToManoptslstreturn$char'^'<>contents<>char'^'inlineToManopts(Subscriptlst)=docontents<-inlineListToManoptslstreturn$char'~'<>contents<>char'~'inlineToManopts(SmallCapslst)=inlineListToManoptslst-- not supportedinlineToManopts(QuotedSingleQuotelst)=docontents<-inlineListToManoptslstreturn$char'`'<>contents<>char'\''inlineToManopts(QuotedDoubleQuotelst)=docontents<-inlineListToManoptslstreturn$text"\\[lq]"<>contents<>text"\\[rq]"inlineToManopts(Cite_lst)=inlineListToManoptslstinlineToMan_(Code_str)=return$text$"\\f[C]"++escapeCodestr++"\\f[]"inlineToMan_(Strstr)=return$text$escapeStringstrinlineToManopts(MathInlineMathstr)=inlineListToManopts$readTeXMathstrinlineToManopts(MathDisplayMathstr)=docontents<-inlineListToManopts$readTeXMathstrreturn$cr<>text".RS"$$contents$$text".RE"inlineToMan_(RawInline"man"str)=return$textstrinlineToMan_(RawInline__)=returnemptyinlineToMan_(LineBreak)=return$cr<>text".PD 0"$$text".P"$$text".PD"<>crinlineToMan_Space=returnspaceinlineToManopts(Linktxt(src,_))=dolinktext<-inlineListToManoptstxtletsrcSuffix=ifisPrefixOf"mailto:"srcthendrop7srcelsesrcreturn$casetxtof[Strs]|escapeURIs==srcSuffix->char'<'<>textsrcSuffix<>char'>'_->linktext<>text" ("<>textsrc<>char')'inlineToManopts(Imagealternate(source,tit))=dolettxt=if(nullalternate)||(alternate==[Str""])||(alternate==[Strsource])-- to prevent autolinksthen[Str"image"]elsealternatelinkPart<-inlineToManopts(Linktxt(source,tit))return$char'['<>text"IMAGE: "<>linkPart<>char']'inlineToMan_(Notecontents)=do-- add to notes in statemodify$\st->st{stNotes=contents:stNotesst}notes<-liftMstNotesgetletref=show$(lengthnotes)return$char'['<>textref<>char']'