%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program. If not, see .
\ignore{
\begin{code}
module DBus.Bus.Address
( Address
, addressMethod
, addressParameters
, strAddress
, parseAddresses
) where
import Data.Char (ord, digitToInt, chr)
import qualified Data.Map as M
import Data.List (intercalate)
import Text.Printf (printf)
import qualified Text.Parsec as P
import Text.Parsec ((<|>))
\end{code}
}
\section{Addresses}
\subsection{Formatting}
A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}
where the method may be empty and parameters are optional. An address's
parameter list, if present, may end with a comma. Addresses in environment
variables are separated by semicolons, and the full address list may end
in a semicolon. Multiple parameters may have the same key; in this case,
only the first parameter for each key will be stored.
The bytes allowed in each component of the address are given by the following
chart, where each character is understood to be its ASCII value:
\begin{table}[h]
\begin{center}
\begin{tabular}{ll}
\toprule
Component & Allowed Characters \\
\midrule
Method & Any except {\tt `;'} and {\tt `:'} \\
Param key & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\
Param value & {\tt `0'} to {\tt `9'} \\
& {\tt `a'} to {\tt `z'} \\
& {\tt `A'} to {\tt `Z'} \\
& Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\
\bottomrule
\end{tabular}
\end{center}
\end{table}
In parameter values, any byte may be encoded by prepending the \% character
to its value in hexadecimal. \% is not allowed to appear unless it is
followed by two hexadecimal digits. Every other allowed byte is termed
an ``optionally encoded'' byte, and may appear unescaped in parameter
values.
\begin{code}
optionallyEncoded :: String
optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."
\end{code}
The address simply stores its method and parameter map, with a custom
{\tt Show} instance to provide easier debugging.
\begin{code}
data Address = Address
{ addressMethod :: String
, addressParameters :: M.Map String String
} deriving (Eq)
instance Show Address where
showsPrec d x = showParen (d> 10) $
showString' ["Address \"", strAddress x, "\""] where
showString' = foldr (.) id . map showString
\end{code}
Parsing is straightforward; the input string is divided into addresses by
semicolons, then further by colons and commas. Parsing will fail if any
of the addresses in the input failed to parse.
\begin{code}
parseAddresses :: String -> Maybe [Address]
parseAddresses s = eitherToMaybe $ P.parse parser "" s where
address = do
method <- P.many (P.noneOf ":;")
P.char ':'
params <- P.sepEndBy param (P.char ',')
return $ Address method (M.fromList params)
param = do
key <- P.many1 (P.noneOf "=;,")
P.char '='
value <- P.many1 (encodedValue <|> unencodedValue)
return (key, value)
parser = do
as <- P.sepEndBy1 address (P.char ';')
P.eof
return as
unencodedValue = P.oneOf optionallyEncoded
encodedValue = do
P.char '%'
hex <- P.count 2 P.hexDigit
return . chr . hexToInt $ hex
\end{code}
Converting an {\tt Address} back to a {\tt String} is just the reverse
operation. Note that because the original parameter order is not preserved,
the string produced might differ from the original input.
\begin{code}
strAddress :: Address -> String
strAddress (Address t ps) = t ++ ":" ++ ps' where
ps' = intercalate "," $ do
(k, v) <- M.toList ps
[k ++ "=" ++ (v >>= encode)]
encode c | elem c optionallyEncoded = [c]
| otherwise = printf "%%%02X" (ord c)
\end{code}
Finally, a couple parser utility functions.
\begin{code}
hexToInt :: String -> Int
hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt
\end{code}
\begin{code}
eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe (Left _) = Nothing
eitherToMaybe (Right x) = Just x
\end{code}