git-annex/Logs/Remote.hs
Joey Hess 928e08904d
avoid two test failures with LANG=C
Log.Remote.prop_parse_show_Config failed on an input of fromList [("\28162","")]
in LANG=C, encodeBS "\28162" == "\STX=", while in UTF-8 locale,
encodeBS "\28162" == "\230\184\130". So in the C locale, the String
that's the parsed Map key ends up being encoded differently than it was
in the input Map.

Logs.Presence.Pure.prop_parse_build_log was failing in LANG=C because
the Arbitrary LogLine for some reason sometimes generated LogInfo values
containing \n or \r, despite using suchThat to prevent that. I don't
understand why at all, but switching the suchThat to filter the
ByteString instead of the String before conversion with encodeBS
somehow avoids the problem.

Both of these suggest something wonky with encodeBS in LANG=C, but
I *think* it's not a problem except for with test data generated by
Arbitrary.
2019-01-18 13:33:48 -04:00

101 lines
2.6 KiB
Haskell

{- git-annex remote log
-
- Copyright 2011 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Logs.Remote (
remoteLog,
readRemoteLog,
configSet,
keyValToConfig,
configToKeyVal,
showConfig,
prop_isomorphic_configEscape,
prop_parse_show_Config,
) where
import Annex.Common
import qualified Annex.Branch
import Types.Remote
import Logs
import Logs.UUIDBased
import qualified Data.Map as M
import Data.Char
import qualified Data.Attoparsec.ByteString.Lazy as A
import Data.ByteString.Builder
{- Adds or updates a remote's config in the log. -}
configSet :: UUID -> RemoteConfig -> Annex ()
configSet u cfg = do
c <- liftIO currentVectorClock
Annex.Branch.change remoteLog $
buildLog (byteString . encodeBS . showConfig)
. changeLog c u cfg
. parseLog remoteConfigParser
{- Map of remotes by uuid containing key/value config maps. -}
readRemoteLog :: Annex (M.Map UUID RemoteConfig)
readRemoteLog = simpleMap . parseLog remoteConfigParser
<$> Annex.Branch.get remoteLog
remoteConfigParser :: A.Parser RemoteConfig
remoteConfigParser = keyValToConfig . words . decodeBS <$> A.takeByteString
showConfig :: RemoteConfig -> String
showConfig = unwords . configToKeyVal
{- Given Strings like "key=value", generates a RemoteConfig. -}
keyValToConfig :: [String] -> RemoteConfig
keyValToConfig ws = M.fromList $ map (/=/) ws
where
(/=/) s = (k, v)
where
k = takeWhile (/= '=') s
v = configUnEscape $ drop (1 + length k) s
configToKeyVal :: M.Map String String -> [String]
configToKeyVal m = map toword $ sort $ M.toList m
where
toword (k, v) = k ++ "=" ++ configEscape v
configEscape :: String -> String
configEscape = concatMap escape
where
escape c
| isSpace c || c `elem` "&" = "&" ++ show (ord c) ++ ";"
| otherwise = [c]
configUnEscape :: String -> String
configUnEscape = unescape
where
unescape [] = []
unescape (c:rest)
| c == '&' = entity rest
| otherwise = c : unescape rest
entity s
| not (null num) && ";" `isPrefixOf` r =
chr (Prelude.read num) : unescape rest
| otherwise =
'&' : unescape s
where
num = takeWhile isNumber s
r = drop (length num) s
rest = drop 1 r
{- for quickcheck -}
prop_isomorphic_configEscape :: String -> Bool
prop_isomorphic_configEscape s = s == (configUnEscape . configEscape) s
prop_parse_show_Config :: RemoteConfig -> Bool
prop_parse_show_Config c
-- whitespace and '=' are not supported in config keys; limit to
-- alphanumerics for simplicity
| any (all isAlphaNum) (M.keys c) = True
| otherwise = A.parseOnly remoteConfigParser (encodeBS $ showConfig c) ~~ Right c
where
normalize v = sort . M.toList <$> v
a ~~ b = normalize a == normalize b