blew several hours on getting the decodeGitFile 100% right with quickcheck

This commit is contained in:
Joey Hess 2010-11-02 15:54:43 -04:00
parent 82056a7921
commit 9e5985ff98

View file

@ -36,7 +36,11 @@ module GitRepo (
inRepo, inRepo,
notInRepo, notInRepo,
stagedFiles, stagedFiles,
checkAttr checkAttr,
decodeGitFile,
encodeGitFile,
prop_idempotent_encode
) where ) where
import Monad (unless) import Monad (unless)
@ -51,6 +55,9 @@ import qualified Data.Map as Map hiding (map, split)
import Network.URI import Network.URI
import Maybe import Maybe
import Char import Char
import Text.Printf
import Data.Word (Word8)
import Codec.Binary.UTF8.String (encode)
import Utility import Utility
@ -332,25 +339,70 @@ checkAttr repo attr files = do
decodeGitFile :: String -> FilePath decodeGitFile :: String -> FilePath
decodeGitFile [] = [] decodeGitFile [] = []
decodeGitFile f@(c:s) decodeGitFile f@(c:s)
| c == '"' = unescape middle | c == '"' = unescape ("", middle)
| otherwise = f | otherwise = f
where where
e = "\\"
middle = take (length s - 1) s middle = take (length s - 1) s
unescape v = foldl (++) beginning $ map decode $ split e rest unescape (b, []) = b
unescape (b, v) = b ++ beginning ++ unescape (decode rest)
where where
pair = span (/= '\\') v pair = span (/= '\\') v
beginning = fst pair beginning = fst pair
rest = snd pair rest = snd pair
decode [] = "" isescape c = c == '\\'
decode n decode (e:n1:n2:n3:rest)
| length num == 3 = (chr $ readoctal num):rest | isescape e && alloctal = (fromoctal, rest)
| otherwise = e++n where
alloctal = isOctDigit n1 &&
isOctDigit n2 &&
isOctDigit n3
fromoctal = [chr $ readoctal (n1:n2:n3:[])]
readoctal o = read $ "0o" ++ o :: Int
decode (e:nc:rest)
| isescape e = ([echar nc], rest)
where where
pair = span isOctDigit n -- special character escapes
num = fst pair echar 'a' = '\a'
rest = snd pair echar 'b' = '\b'
readoctal o = read $ "0o" ++ o :: Int echar 'f' = '\f'
echar 'n' = '\n'
echar 'r' = '\r'
echar 't' = '\t'
echar 'v' = '\v'
echar x = x
decode n = ("", n)
{- Should not need to use this, except for testing decodeGitFile. -}
encodeGitFile :: FilePath -> String
encodeGitFile s = (foldl (++) "\"" (map echar s)) ++ "\""
where
e c = "\\" ++ [c]
echar '\a' = e 'a'
echar '\b' = e 'b'
echar '\f' = e 'f'
echar '\n' = e 'n'
echar '\r' = e 'r'
echar '\t' = e 't'
echar '\v' = e 'v'
echar '\\' = e '\\'
echar '"' = e '"'
echar x
| ord x < 0x20 = e_num x -- low ascii
| ord x >= 256 = e_utf x
| ord x > 0x7E = e_num x -- high ascii
| otherwise = [x] -- printable ascii
where
showoctal i = "\\" ++ (printf "%03o" i)
e_num c = showoctal $ ord c
-- unicode character is decomposed to Word8
-- and each is shown with e_num
e_utf c = foldl (++) "" $ map showoctal $
(encode [c] :: [Word8])
{- for quickcheck -}
prop_idempotent_encode :: String -> Bool
prop_idempotent_encode s = s == (decodeGitFile $ encodeGitFile s)
{- Finds the current git repository, which may be in a parent directory. -} {- Finds the current git repository, which may be in a parent directory. -}
repoFromCwd :: IO Repo repoFromCwd :: IO Repo