module TDisplay.Output.Columns where import TDisplay import Data.List import System.IO.Unsafe {- | The TDisplayColumns object represents simple stdout strings, using separator elements to format the output into columns. It is implemented via unsafePerformIO to avoid requiring render to be a Monad. The \"done\" value of TDisplayColumns is used to cause Haskell to evaluate the rendering method, as well as providing the return status from renderComplete. -} data TDisplayColumns = TDColumns { done::Bool } instance Show TDisplayColumns where show d = if done d then "" else "" instance TDisplay TDisplayColumns where render e _ = unsafePerformIO $ putStr (render' e) >> return (TDColumns True) where render' [] = "" render' ((TDString (TDTag _t f) s) : es) | "text" `elem` f = showString s $ render' es | otherwise = render' es render' ((TDAtom m@(TDTag _t f)) : es) | tdTag_EOL == m = showString "\n" $ render' es | tdTag_sep == m = showString " " $ render' es | "eol" `elem` f = showString "\n" $ render' es | "separator" `elem` f = showString " " $ render' es | otherwise = render' es render' ((TDGroup _ g) : es) = (subrender $ subelems g) $ render' es render' ((TDHsepGroup _ g) : es) = (subrender $ i_sep $ nosep $ subelems g) $ render' es render' ((TDVsepGroup _ g) : es) = (column_render $ i_eol $ noeol $ subelems g) $ render' es subelems (Left g) = g subelems (Right gs) = gs [] subrender = showString . render' nosep = filter ((/=) (TDAtom tdTag_sep)) noeol = filter ((/=) (TDAtom tdTag_EOL)) i_sep = intersperse (head tdSep) i_eol = intersperse (head tdEOL) column_render vgroup = column_render' vgroup where colsizes = colsizes' vgroup where colsizes' [] = [] -- repeat 0 colsizes' ((TDHsepGroup _ g) : es) = adjusted_max where -- render_raw is the raw column entries for the current line render_raw [] = [] render_raw ((TDHsepGroup _ g') : xs) = render_raw ((nosep $ subelems g') ++ xs) render_raw (x:xs) = (render' [x] : render_raw xs) thisrow_colsizes = map ((+) 1 . length) (render_raw $ nosep $ subelems g) adjusted_max = maxjoin 0 thisrow_colsizes (colsizes' es) -- adjusted_max = max thisrow_colsizes (colsizes' es) colsizes' (_:ls) = colsizes' ls column_render' [] = showString "" column_render' ((TDHsepGroup _ g) : es) = showline (nosep $ subelems g) . column_render' es where showline g' = showString (showcol 0 g') showcol _ [] = "" -- trace ("Colsizes:" ++ (show colsizes)) "" -- concatenate horizonal subgroups instead of treating the subgroup as a single element showcol c ((TDHsepGroup _ g') : xs) = showcol c ((nosep $ subelems g') ++ xs) showcol c (x:xs) = let r = render' [x] in showString r $ showString (colfill c r) $ showcol (c+1) xs colfill c x = replicate (fillcnt c (length x) colsizes) ' ' fillcnt :: Int -> Int -> [Int] -> Int fillcnt _ _ [] = 0 fillcnt 0 l (s:_) = s - l fillcnt n l (_:ss) = fillcnt (n-1) l ss column_render' (e' : es) = showString (render' [e']) . column_render' es renderComplete = done maxjoin :: (Ord a) => a -> [a] -> [a] -> [a] -- maxjoin = map (uncurry max) . zipfill maxjoin a b c = map (uncurry max) $ zipfill a b c zipfill :: a -> [a] -> [a] -> [(a,a)] -- zipfill f x y | length x > length y = zip x (y ++ repeat f) -- | otherwise = zip (x ++ repeat f) y zipfill f x y = if length x > length y then zip x (y ++ repeat f) else zip (x ++ repeat f) y --kwq: column headers? --kwq: rows/page, re-issue headers and re-compute columns on a per-page basis (from RunEnv)? {- | runTDisplayIO is used to perform the necessary rendering in an IO Monad (e.g. main). It will render its argument until the display object returns true (for TDisplayColumns, this is usually a single rendering pass), and then output a completion message. The completion message is usually the empty string, indicating that the rendering has completed, but it may be an incomplete message if the renderComplete does not return true. The TDisplayColumns does not require complex, multi-pass rendering, but the variable result value causes Haskell to perform the rendering call to determine the value to pass (via show) to the final putStr IO monad operation, thereby ensuring the render occurs. -} runTDisplayIO :: [TDOutput] -> IO () runTDisplayIO x = return (until renderComplete (render x) (TDColumns False)) >>= putStrLn . show