Created
August 29, 2013 22:54
-
-
Save malleusinferni/6384416 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE RecordWildCards #-} | |
data Species = Species | |
{ speciesName :: String | |
, minHeight :: Int | |
, maxHeight :: Int | |
} deriving (Eq, Show, Ord) | |
instance Tabular Species where | |
readRecord = do | |
speciesName <- copyField "Name" | |
minHeight <- readField "Min height" | |
maxHeight <- readField "Max height" | |
return Species{..} |
We can make this file beautiful and searchable if this error is corrected: It looks like row 2 should actually have 2 columns, instead of 3. in line 1.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Name Min height Max height | |
Merovingian 60 80 | |
goblin 40 55 | |
shoggoth 50 240 | |
unseelie 60 100 | |
manticore 36 48 | |
chalcotaur 80 120 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE RecordWildCards #-} | |
module Table | |
( Tabular | |
, readRecord | |
, readField | |
, copyField | |
, readTSVFile | |
, readTSVString | |
, readTable | |
) where | |
import Text.Parsec | |
import Text.Parsec.Text | |
import qualified Data.Text as T | |
import qualified Data.Text.IO as TIO | |
import Control.Monad.Reader | |
import Control.Monad.Error | |
import Text.Read (readEither) | |
type Key = String | |
type Value = String | |
type Row = [(Key, Value)] | |
type Table = [Row] | |
type RecordReader = ReaderT Row (Either String) | |
class Tabular r where | |
readRecord :: RecordReader r | |
readTSVFile :: Tabular r => String -> IO [r] | |
readTSVFile file = do | |
text <- TIO.readFile file | |
either error return $ readTable text file | |
readTSVString :: Tabular r => String -> [r] | |
readTSVString text = either error id $ readTable (T.pack text) "" | |
readTable :: Tabular r => T.Text -> String -> Either String [r] | |
readTable text source = do | |
rows <- show `onLeft` parse tsvTable source text | |
mapM (runReaderT readRecord) rows | |
onLeft :: (a -> b) -> Either a r -> Either b r | |
onLeft f (Left v) = Left (f v) | |
onLeft _ (Right v) = Right v -- rebind | |
readValue :: Read v => String -> RecordReader v | |
readValue = either throwError return . readEither | |
readField :: Read v => String -> RecordReader v | |
readField k = copyField k >>= readValue | |
copyField :: String -> RecordReader String | |
copyField k = do | |
v <- asks (lookup k) | |
case v of | |
Just v' -> return v' | |
Nothing -> throwError $ "No such key: " ++ k | |
tsvTable :: Parser [Row] | |
tsvTable = do | |
header <- tsvRow | |
newline | |
tsvRecord header `sepEndBy` newline | |
tsvRow :: Parser [Value] | |
tsvRow = tsvField `sepBy` tab | |
tsvZip :: [Key] -> [Value] -> Parser Row | |
tsvZip (k:ks) (v:vs) = ((k, v) :) `fmap` tsvZip ks vs | |
tsvZip [] [] = return [] | |
tsvZip [] _ = fail "too many fields" | |
tsvZip _ [] = fail "not enough fields" | |
tsvRecord :: [Key] -> Parser Row | |
tsvRecord header = do | |
row <- tsvRow | |
tsvZip header row | |
tsvField :: Parser String | |
tsvField = many . noneOf $ "\t\r\n" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment