packages feed

format (empty) → 0.1.0.0

raw patch · 5 files changed

+201/−0 lines, 5 filesdep +QuickCheckdep +haskell2010dep +parsecsetup-changed

Dependencies added: QuickCheck, haskell2010, parsec

Files

+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2012 Scott Lawrence <bytbox@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/Format.hs view
@@ -0,0 +1,129 @@+module Text.Format+( parseFormat+, renderFormat+, renderFormatString+, scanFormat+, scanFormatString+) where++import Text.Parsec++data FormatParsePart = FRaw Char | FVar String++data FormatPart = Var String (Parsec String () ()) | Raw String++type Format = [FormatPart]++mapLeft f (Left x) = Left (f x)+mapLeft _ (Right x) = Right x++{-|+  Parse a format string into a `Format` object ready to be used in+  `renderFormat` and `scanFormat`.+ +  The format string consists of raw tokens (ordinary characters), and+  variables, marked '$varname' or '${varname}'. The dollar sign may be used as+  a raw token by escaping it with another dollar sign, like so: '$$'. (If you+  want a variable named $, use '${$}'.)++  Not all syntactically valid parse strings are semantically valid. In+  particular, two variables must not occur consecutively without interleaving+  raw tokens. (If this were permitted, the resulting grammar would be+  ambiguous.)++  Variable names may be used twice; however, this will make the result of+  `scanFormat` somewhat difficult to deal with.+  +  The functions `renderFormatString` and `scanFormatString` are provided as+  conveniences to make doing this explicitly unnecessary.+ -}+parseFormat :: String -> Either String Format+parseFormat s = +  case runParser formatParser () "format-string" s of+    Left e -> Left $ show e+    Right x -> cleanFormat x+  where cleanFormat :: [FormatParsePart] -> Either String Format+        cleanFormat ps = mergeRaw "" ps >>= sepVars+        mergeRaw :: String -> [FormatParsePart] -> Either String Format+        mergeRaw s [] = case s of "" -> return []+                                  _ -> return [Raw s]+        mergeRaw s (FRaw c : ps) = mergeRaw (s++[c]) ps+        mergeRaw s (FVar v : ps) = do+                                    r <- mergeRaw "" ps+                                    return $ case s of  "" -> Var v eof : r+                                                        _ -> Raw s : Var v eof : r+        sepVars :: [FormatPart] -> Either String Format+        sepVars [] = return []+        sepVars (Raw s : ps) = do rs <- sepVars ps+                                  return $ Raw s : rs+        sepVars (Var v _ : ps) = case ps of+                                  [] -> return $ [Var v eof]+                                  Raw s : _ -> +                                    do  rs <- sepVars ps+                                        return $ Var v (ignore $ string s) : rs+                                  Var _ _ : _ -> Left "Ambiguous format string"++ignore = (>>= const (return ()))+ident = many1 (alphaNum <|> char '_') >>= return . FVar+variable = char '$' >> (ident <|> between (char '{') (char '}') ident)+special = string "$$" >> (return $ FRaw '$')+raw = return . FRaw =<< noneOf ['$']++formatParser :: Parsec String () [FormatParsePart]+formatParser = do+  xs <- many (try raw <|> try special <|> variable)+  eof+  return xs++renderFormat :: Format -> (String -> Maybe String) -> Either String String+renderFormat fmt lu = return . concat =<< (sequence $ flip map fmt $+  \x -> case x of+          Raw s -> return s+          Var v _ -> case lu v of+                        Just o -> Right o+                        Nothing -> Left ("No such variable " ++ v))++-- |A more convenient alternative to using `parseFormat` and `renderFormat`.+renderFormatString :: String -> (String -> Maybe String) -> Either String String+renderFormatString s lu = do+                            f <- parseFormat s+                            renderFormat f lu++{-|+  Parses a string using the given format as a guide, generating a list of pairs+  of variable names and values.+  +  To determine where a variable ends, the entire subsequent string of raw+  tokens (until the next variable or the end of the string) is used as a+  terminator. It must occur verbatim in the scanned string or the parse will+  fail. The smallest match is used: if the format string is '${a}:' and the+  input string is '1:2:', the parse will exit with an error, as only the first+  character will be considered part of the variable+  'a'.+ -}+scanFormat :: Format -> String -> Either String [(String, String)]+scanFormat fmt str = mapLeft show $ runParser scanner () "INPUT" str+  where scanner = do+                    r <- compileScanner fmt+                    eof+                    return r+        compileScanner :: Format -> Parsec String () [(String, String)]+        compileScanner fmt = let ps = map compileScannerPart fmt in+                              foldr (\a as -> do+                                                x <- a+                                                xs <- as+                                                return (x++xs))+                              (return []) ps+        compileScannerPart :: FormatPart -> Parsec String () [(String, String)]+        compileScannerPart p = case p of+                                Raw s -> string s >> return []+                                Var name sep -> do+                                                  d <- manyTill anyChar $ try $ lookAhead sep+                                                  return [(name, d)]++-- |A more convenient alternative to using `parseFormat` and `scanFormat`.+scanFormatString :: String -> String -> Either String [(String, String)]+scanFormatString s str = do+                          f <- parseFormat s+                          scanFormat f str+
+ format.cabal view
@@ -0,0 +1,24 @@+name:                format+version:             0.1.0.0+synopsis:            Rendering from and scanning to format strings+description:         Rendering from and scanning to simple format strings+homepage:            https://github.com/bytbox/hs-format+license:             MIT+license-file:        LICENSE+author:              Scott Lawrence+maintainer:          bytbox@gmail.com+category:            Text+build-type:          Simple+cabal-version:       >=1.10++library+  default-language:  Haskell2010+  exposed-modules:   Text.Format+  build-depends:     haskell2010 ==1.*, parsec ==3.1.*++test-suite test+  default-language:  Haskell2010+  type:              exitcode-stdio-1.0+  main-is:           test.hs+  build-depends:     haskell2010 ==1.*, parsec ==3.1.*, QuickCheck==2.4.*+
+ test.hs view
@@ -0,0 +1,27 @@+module Main where++import System.Exit (exitSuccess, exitFailure)+import Test.QuickCheck+import Text.Format++prop_render_raw_idempotent :: String -> Bool+prop_render_raw_idempotent s =+  let r = renderFormatString "$a" (\_ -> Just s) in+  r == Right s+prop_scan_raw_idempotent s =+  let r = scanFormatString "$a" s in+  r == Right [("a", s)]++tests = [ ("RenderRawIdempotent", prop_render_raw_idempotent)+        , ("ScanRawIdempotent", prop_scan_raw_idempotent)+        ]++isSuccess (Success _ _ _) = True+isSuccess _ = False++main = do+        r <- mapM (\(n, p) -> putStr n >> quickCheckResult p) tests+        if and $ map isSuccess r+          then exitSuccess+          else exitFailure+