doc-review-0.7: src/Config/Format.hs
module Config.Format
( twoColumn
, wrap
, findBreak
, indent
)
where
import Control.Monad ( mplus )
import Data.Char ( isAlphaNum, isSpace )
import Data.Maybe ( fromMaybe )
--------------------------------------------------
-- Laying out documentation
-- XXX: This should be generalized so that you can pass it a list of
-- rows of arbitrary numbers of columns and get a table layout.
-- |Lay out pairs of strings in two columns, under the specified line length
twoColumn :: Int -> [(String, String)] -> [String]
twoColumn w ps = concatMap fmt1 ps
where
firstColN = min (maximum $ map (length . fst) ps) 40
secondColN = w - firstColN - 2
fmt1 (a, b) = hcat firstColN secondColN a b
hcat n1 n2 = go
where
go [] [] = []
go xs ys =
let (c1, xs') = findBreak n1 xs
(c2, ys') = findBreak n2 ys
l = fillCol n1 c1 ++ " " ++ fillCol n2 c2
in l : go xs' ys'
fillCol n xs = take n $ xs ++ repeat ' '
-- |Wrap a string at a certain line length, attempting to break at
-- word boundaries
wrap :: Int -> String -> [String]
wrap n = go
where
go s = case findBreak n s of
([], []) -> []
(l, s') -> l:go s'
-- |Attempt to break a string at a word boundary. As a last resort,
-- arbitrarily break at the specified line limit
findBreak :: Int -> String -> (String, String)
findBreak n s = let (l, s') = fromMaybe (splitAt n s) $ go 0 id s
in (l, dropWhile isSpace s')
where
-- Look for the last word boundary in the first n characters of
-- the String, and if there is a word boundary, return the
-- String up to that last boundary and the remainder of the
-- String
go :: Int -- ^Which character we are examining now
-> ShowS -- ^The String up to this point
-> String -- ^The remainder of the String that we have to examine
-> Maybe (String, String)
-- ^If we found a breaking point before getting to the
-- limit, this is the String up to that breaking point and
-- then the remainder
go l acc xs =
case xs of
-- If we get to the end and we haven't violated the length
-- constraint, then return what we've got.
[] -> ok
(c:cs)
-- This violates the length constraint, so fail.
| l >= n -> Nothing
-- An alphanumeric character is not allowed to be a
-- break point, so try to find a breaking point later
-- on or fail.
| isAlphaNum c -> next c cs
-- This is a valid breaking point, so try to see if
-- there is one further on in the string and if there
-- is not, then this one is the longest.
| otherwise -> next c cs `mplus` ok
where
-- acc [] is what we have processed before examining this
-- character
ok = return (acc [], xs)
-- next looks for a breaking point further on in the
-- String
next c cs = go (l + 1) (acc . (c:)) cs
-- |Prepend the specified number of spaces to each String
indent :: Int -> [String] -> [String]
indent n = map $ showString $ replicate n ' '