burrito 1.2.0.4 → 2.0.0.0
raw patch · 46 files changed
+2823/−2755 lines, 46 filesdep ~basedep ~containersdep ~template-haskellPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, containers, template-haskell, text
API changes (from Hackage documentation)
Files
- LICENSE.markdown +18/−12
- burrito.cabal +49/−24
- source/library/Burrito.hs +64/−0
- source/library/Burrito/Internal/Expand.hs +312/−0
- source/library/Burrito/Internal/Match.hs +273/−0
- source/library/Burrito/Internal/Parse.hs +176/−0
- source/library/Burrito/Internal/Render.hs +107/−0
- source/library/Burrito/Internal/TH.hs +30/−0
- source/library/Burrito/Internal/Type/Case.hs +12/−0
- source/library/Burrito/Internal/Type/Character.hs +13/−0
- source/library/Burrito/Internal/Type/Digit.hs +105/−0
- source/library/Burrito/Internal/Type/Expression.hs +16/−0
- source/library/Burrito/Internal/Type/Field.hs +13/−0
- source/library/Burrito/Internal/Type/Literal.hs +13/−0
- source/library/Burrito/Internal/Type/Match.hs +15/−0
- source/library/Burrito/Internal/Type/MaxLength.hs +11/−0
- source/library/Burrito/Internal/Type/Modifier.hs +14/−0
- source/library/Burrito/Internal/Type/Name.hs +13/−0
- source/library/Burrito/Internal/Type/Operator.hs +18/−0
- source/library/Burrito/Internal/Type/Template.hs +34/−0
- source/library/Burrito/Internal/Type/Token.hs +14/−0
- source/library/Burrito/Internal/Type/Value.hs +18/−0
- source/library/Burrito/Internal/Type/Variable.hs +15/−0
- source/test-suite/Main.hs +1470/−0
- src/lib/Burrito.hs +0/−65
- src/lib/Burrito/Internal/Expand.hs +0/−312
- src/lib/Burrito/Internal/Match.hs +0/−273
- src/lib/Burrito/Internal/Parse.hs +0/−175
- src/lib/Burrito/Internal/Render.hs +0/−107
- src/lib/Burrito/Internal/TH.hs +0/−28
- src/lib/Burrito/Internal/Type/Case.hs +0/−10
- src/lib/Burrito/Internal/Type/Character.hs +0/−11
- src/lib/Burrito/Internal/Type/Digit.hs +0/−100
- src/lib/Burrito/Internal/Type/Expression.hs +0/−13
- src/lib/Burrito/Internal/Type/Field.hs +0/−11
- src/lib/Burrito/Internal/Type/Literal.hs +0/−11
- src/lib/Burrito/Internal/Type/Match.hs +0/−13
- src/lib/Burrito/Internal/Type/MaxLength.hs +0/−9
- src/lib/Burrito/Internal/Type/Modifier.hs +0/−12
- src/lib/Burrito/Internal/Type/Name.hs +0/−11
- src/lib/Burrito/Internal/Type/Operator.hs +0/−16
- src/lib/Burrito/Internal/Type/Template.hs +0/−31
- src/lib/Burrito/Internal/Type/Token.hs +0/−12
- src/lib/Burrito/Internal/Type/Value.hs +0/−16
- src/lib/Burrito/Internal/Type/Variable.hs +0/−12
- src/test/Main.hs +0/−1471
LICENSE.markdown view
@@ -1,15 +1,21 @@-ISC License (ISC)+MIT License -Copyright 2021 Taylor Fausak+Copyright (c) 2022 Taylor Fausak -Permission to use, copy, modify, and/or distribute this software for any-purpose with or without fee is hereby granted, provided that the above-copyright notice and this permission notice appear in all copies.+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 SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH-REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND-FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,-INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM-LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR-PERFORMANCE OF THIS SOFTWARE.+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.
burrito.cabal view
@@ -1,7 +1,7 @@-cabal-version: >= 1.10+cabal-version: 2.2 name: burrito-version: 1.2.0.4+version: 2.0.0.0 synopsis: Parse and render URI templates. description:@@ -25,23 +25,62 @@ category: Network extra-source-files: README.markdown license-file: LICENSE.markdown-license: ISC+license: MIT maintainer: Taylor Fausak source-repository head location: https://github.com/tfausak/burrito type: git -library+flag pedantic+ default: False+ description: Enables @-Werror@, which turns warnings into errors.+ manual: True++common library build-depends:- base >= 4.13.0 && < 4.16+ , base >= 4.13.0 && < 4.17 , bytestring >= 0.10.10 && < 0.12 , containers >= 0.6.2 && < 0.7 , parsec >= 3.1.14 && < 3.2- , template-haskell >= 2.15.0 && < 2.18+ , template-haskell >= 2.15.0 && < 2.19 , text >= 1.2.4 && < 1.3 , transformers >= 0.5.6 && < 0.6 default-language: Haskell2010+ ghc-options:+ -Weverything+ -Wno-all-missed-specialisations+ -Wno-implicit-prelude+ -Wno-missed-specialisations+ -Wno-missing-deriving-strategies+ -Wno-missing-exported-signatures+ -Wno-safe+ -Wno-unsafe++ if flag(pedantic)+ ghc-options: -Werror++ if impl(ghc >= 8.10)+ ghc-options:+ -Wno-missing-safe-haskell-mode+ -Wno-prepositive-qualified-module++ if impl(ghc >= 9.2)+ ghc-options:+ -Wno-missing-kind-signatures++common executable+ import: library++ build-depends: burrito+ ghc-options:+ -rtsopts+ -threaded+ -Wno-unused-packages++library+ import: library+ exposed-modules: Burrito Burrito.Internal.Expand@@ -64,28 +103,14 @@ Burrito.Internal.Type.Token Burrito.Internal.Type.Value Burrito.Internal.Type.Variable- ghc-options:- -Weverything- -Wno-implicit-prelude- -Wno-missing-deriving-strategies- -Wno-missing-exported-signatures- -Wno-safe- hs-source-dirs: src/lib-- if impl(ghc >= 8.10)- ghc-options:- -Wno-missing-safe-haskell-mode- -Wno-prepositive-qualified-module+ hs-source-dirs: source/library test-suite test+ import: executable+ build-depends:- base -any- , burrito -any- , containers -any , hspec >= 2.7.6 && < 2.10 , QuickCheck >= 2.13.2 && < 2.15- , text -any- default-language: Haskell2010- hs-source-dirs: src/test+ hs-source-dirs: source/test-suite main-is: Main.hs type: exitcode-stdio-1.0
+ source/library/Burrito.hs view
@@ -0,0 +1,64 @@+-- | Burrito is a Haskell library for parsing and rendering URI templates.+--+-- According to [RFC 6570](https://tools.ietf.org/html/rfc6570): "A URI+-- Template is a compact sequence of characters for describing a range of+ -- Uniform Resource Identifiers through variable expansion." Burrito+ -- implements URI templates according to the specification in that RFC.+--+-- The term "uniform resource identifiers" (URI) is often used interchangeably+-- with other related terms like "internationalized resource identifier" (IRI),+-- "uniform resource locator" (URL), and "uniform resource name" (URN). Burrito+-- can be used for all of these. If you want to get technical, its input must+-- be a valid IRI and its output will be a valid URI or URN.+--+-- Although Burrito is primarily intended to be used with HTTP and HTTPS URIs,+-- it should work with other schemes as well.+--+-- If you're not already familiar with URI templates, I recommend reading the+-- overview of the RFC. It's short, to the point, and easy to understand.+--+-- Assuming you're familiar with URI templates, here's a simple example to show+-- you how Burrito works:+--+-- >>> import Burrito+-- >>> let Just template = parse "http://example/search{?query}"+-- >>> expand [("query", stringValue "chorizo")] template+-- "http://example.com/search?query=chorizo"+--+-- In short, use @parse@ to parse templates and @expand@ to render them.+module Burrito+ ( Parse.parse+ , Template.render+ , Expand.expand+ , Expand.expandWith+ , Match.match+ , TH.uriTemplate+ , Template.Template+ , Value.Value+ , stringValue+ , listValue+ , dictionaryValue+ ) where++import qualified Burrito.Internal.Expand as Expand+import qualified Burrito.Internal.Match as Match+import qualified Burrito.Internal.Parse as Parse+import qualified Burrito.Internal.TH as TH+import qualified Burrito.Internal.Type.Template as Template+import qualified Burrito.Internal.Type.Value as Value+import qualified Data.Bifunctor as Bifunctor+import qualified Data.Map as Map+import qualified Data.Text as Text++-- | Constructs a string value.+stringValue :: String -> Value.Value+stringValue = Value.String . Text.pack++-- | Constructs a list value.+listValue :: [String] -> Value.Value+listValue = Value.List . fmap Text.pack++-- | Constructs a dictionary value.+dictionaryValue :: [(String, String)] -> Value.Value+dictionaryValue =+ Value.Dictionary . Map.fromList . fmap (Bifunctor.bimap Text.pack Text.pack)
+ source/library/Burrito/Internal/Expand.hs view
@@ -0,0 +1,312 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module Burrito.Internal.Expand where++import qualified Burrito.Internal.Render as Render+import qualified Burrito.Internal.Type.Character as Character+import qualified Burrito.Internal.Type.Digit as Digit+import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Field as Field+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Template as Template+import qualified Burrito.Internal.Type.Token as Token+import qualified Burrito.Internal.Type.Value as Value+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Control.Monad.Trans.Class as Trans+import qualified Control.Monad.Trans.State as State+import qualified Data.ByteString as ByteString+import qualified Data.Char as Char+import qualified Data.Functor.Identity as Identity+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Text.Lazy as LazyText+import qualified Data.Text.Lazy.Builder as Builder++-- | Expands a template using the given values. Unlike parsing, expansion+-- always succeeds. If no value is given for a variable, it will simply not+-- appear in the output.+--+-- >>> expand [] <$> parse "valid-template"+-- Just "valid-template"+-- >>> expand [] <$> parse "template:{example}"+-- Just "template:"+-- >>> expand [("example", stringValue "true")] <$> parse "template:{example}"+-- Just "template:true"+expand :: [(String, Value.Value)] -> Template.Template -> String+expand values =+ let m = Map.mapKeys Text.pack $ Map.fromList values+ in+ Render.builderToString . Identity.runIdentity . expandWith+ (pure . flip Map.lookup m)++-- | This is like @expand@ except that it gives you more control over how+-- variables are expanded. If you can, use @expand@. It's simpler.+--+-- Instead of passing in a static mapping form names to+-- values, you pass in a function that is used to look up values on the fly.+-- This can be useful if computing values takes a while or requires some impure+-- actions.+--+-- >>> expandWith (\ x -> [Nothing, Just . stringValue $ unpack x]) <$> parse "template:{example}"+-- Just ["template:","template:example"]+-- >>> let Just template = parse "user={USER}"+-- >>> expandWith (fmap (fmap stringValue) . lookupEnv . unpack) template+-- "user=taylor"+--+-- Note that as the RFC specifies, the given function will be called at most+-- once for each variable in the template.+--+-- >>> let Just template = parse "{a}{a}"+-- >>> expandWith (\ x -> do { putStrLn $ "-- expanding " <> show x; pure . Just $ Burrito.stringValue "A" }) template+-- -- expanding "a"+-- "AA"+expandWith+ :: Monad m+ => (Text.Text -> m (Maybe Value.Value))+ -> Template.Template+ -> m Builder.Builder+expandWith f = flip State.evalStateT Map.empty . template (cached f)++type CacheT = State.StateT (Map.Map Text.Text (Maybe Value.Value))++cached+ :: Monad m+ => (Text.Text -> m (Maybe Value.Value))+ -> Name.Name+ -> CacheT m (Maybe Value.Value)+cached f x = do+ let key = LazyText.toStrict . Builder.toLazyText $ name x+ cache <- State.get+ case Map.lookup key cache of+ Just result -> pure result+ Nothing -> do+ result <- Trans.lift $ f key+ State.modify $ Map.insert key result+ pure result++template+ :: Monad m+ => (Name.Name -> CacheT m (Maybe Value.Value))+ -> Template.Template+ -> CacheT m Builder.Builder+template f = fmap mconcat . traverse (token f) . Template.tokens++token+ :: Monad m+ => (Name.Name -> CacheT m (Maybe Value.Value))+ -> Token.Token+ -> CacheT m Builder.Builder+token f x = case x of+ Token.Expression y -> expression f y+ Token.Literal y -> pure $ literal y++expression+ :: Monad m+ => (Name.Name -> CacheT m (Maybe Value.Value))+ -> Expression.Expression+ -> CacheT m Builder.Builder+expression f ex =+ let op = Expression.operator ex+ in+ fmap+ (mconcat+ . (\xs -> if null xs then xs else prefix op : xs)+ . List.intersperse (separator op)+ . Maybe.catMaybes+ )+ . traverse (variable f op)+ . NonEmpty.toList+ $ Expression.variables ex++separator :: Operator.Operator -> Builder.Builder+separator op = Builder.singleton $ case op of+ Operator.Ampersand -> '&'+ Operator.FullStop -> '.'+ Operator.None -> ','+ Operator.NumberSign -> ','+ Operator.PlusSign -> ','+ Operator.QuestionMark -> '&'+ Operator.Semicolon -> ';'+ Operator.Solidus -> '/'++prefix :: Operator.Operator -> Builder.Builder+prefix op = case op of+ Operator.Ampersand -> Builder.singleton '&'+ Operator.FullStop -> Builder.singleton '.'+ Operator.None -> mempty+ Operator.NumberSign -> Builder.singleton '#'+ Operator.PlusSign -> mempty+ Operator.QuestionMark -> Builder.singleton '?'+ Operator.Semicolon -> Builder.singleton ';'+ Operator.Solidus -> Builder.singleton '/'++variable+ :: Monad m+ => (Name.Name -> CacheT m (Maybe Value.Value))+ -> Operator.Operator+ -> Variable.Variable+ -> CacheT m (Maybe Builder.Builder)+variable f op var = do+ res <- f $ Variable.name var+ pure $ case res of+ Nothing -> Nothing+ Just val -> value op var val++value+ :: Operator.Operator+ -> Variable.Variable+ -> Value.Value+ -> Maybe Builder.Builder+value op var val = case val of+ Value.Dictionary xs -> dictionaryValue op var $ Map.toAscList xs+ Value.List xs -> listValue op var xs+ Value.String x -> Just $ stringValue op var x++dictionaryValue+ :: Operator.Operator+ -> Variable.Variable+ -> [(Text.Text, Text.Text)]+ -> Maybe Builder.Builder+dictionaryValue = items $ \op var (k, v) ->+ let f = string op Modifier.None+ in+ case Variable.modifier var of+ Modifier.Asterisk -> [f k <> Builder.singleton '=' <> f v]+ _ -> [f k, f v]++listValue+ :: Operator.Operator+ -> Variable.Variable+ -> [Text.Text]+ -> Maybe Builder.Builder+listValue = items $ \op var -> pure . stringValue+ (case Variable.modifier var of+ Modifier.Asterisk -> op+ _ -> Operator.None+ )+ var { Variable.modifier = Modifier.None }++items+ :: (Operator.Operator -> Variable.Variable -> a -> [Builder.Builder])+ -> Operator.Operator+ -> Variable.Variable+ -> [a]+ -> Maybe Builder.Builder+items f op var xs =+ let+ md = Variable.modifier var+ sep = case md of+ Modifier.Asterisk -> separator op+ _ -> Builder.singleton ','+ p = case md of+ Modifier.Asterisk -> False+ _ -> case op of+ Operator.Ampersand -> True+ Operator.QuestionMark -> True+ Operator.Semicolon -> True+ _ -> False+ in if null xs+ then Nothing+ else+ Just+ . mconcat+ . (if p then (label True var :) else id)+ . List.intersperse sep+ $ concatMap (f op var) xs++label :: Bool -> Variable.Variable -> Builder.Builder+label p v =+ name (Variable.name v) <> if p then Builder.singleton '=' else mempty++name :: Name.Name -> Builder.Builder+name =+ mconcat+ . List.intersperse (Builder.singleton '.')+ . fmap field+ . NonEmpty.toList+ . Name.fields++field :: Field.Field -> Builder.Builder+field = foldMap (character $ const True) . Field.characters++character :: (Char -> Bool) -> Character.Character tag -> Builder.Builder+character f x = case x of+ Character.Encoded y z -> Render.encodedCharacter y z+ Character.Unencoded y -> unencodedCharacter f y++stringValue+ :: Operator.Operator -> Variable.Variable -> Text.Text -> Builder.Builder+stringValue op var str =+ let+ pre = case op of+ Operator.Ampersand -> label True var+ Operator.QuestionMark -> label True var+ Operator.Semicolon -> label (not $ Text.null str) var+ _ -> mempty+ in pre <> string op (Variable.modifier var) str++string+ :: Operator.Operator -> Modifier.Modifier -> Text.Text -> Builder.Builder+string op md =+ let+ allowed x = case op of+ Operator.NumberSign -> isAllowed x+ Operator.PlusSign -> isAllowed x+ _ -> isUnreserved x+ trim = case md of+ Modifier.Colon ml -> Text.take $ MaxLength.count ml+ _ -> id+ in foldMap (unencodedCharacter allowed) . Text.unpack . trim++isAllowed :: Char -> Bool+isAllowed x = isUnreserved x || isReserved x++isUnreserved :: Char -> Bool+isUnreserved x = case x of+ '-' -> True+ '.' -> True+ '_' -> True+ '~' -> True+ _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x++isReserved :: Char -> Bool+isReserved x = case x of+ '!' -> True+ '$' -> True+ '&' -> True+ '\'' -> True+ '(' -> True+ ')' -> True+ '*' -> True+ '+' -> True+ ',' -> True+ ';' -> True+ '=' -> True+ ':' -> True+ '/' -> True+ '?' -> True+ '#' -> True+ '[' -> True+ ']' -> True+ '@' -> True+ _ -> False++unencodedCharacter :: (Char -> Bool) -> Char -> Builder.Builder+unencodedCharacter f x = if f x+ then Builder.singleton x+ else foldMap (uncurry Render.encodedCharacter) $ encodeCharacter x++encodeCharacter :: Char -> [(Digit.Digit, Digit.Digit)]+encodeCharacter =+ fmap Digit.fromWord8 . ByteString.unpack . Text.encodeUtf8 . Text.singleton++literal :: Literal.Literal -> Builder.Builder+literal = foldMap (character isAllowed) . Literal.characters
+ source/library/Burrito/Internal/Match.hs view
@@ -0,0 +1,273 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module Burrito.Internal.Match where++import qualified Burrito.Internal.Expand as Expand+import qualified Burrito.Internal.Render as Render+import qualified Burrito.Internal.Type.Case as Case+import qualified Burrito.Internal.Type.Character as Character+import qualified Burrito.Internal.Type.Digit as Digit+import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Burrito.Internal.Type.Match as Match+import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Template as Template+import qualified Burrito.Internal.Type.Token as Token+import qualified Burrito.Internal.Type.Value as Value+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Control.Monad as Monad+import qualified Data.ByteString as ByteString+import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Maybe as Maybe+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Text.ParserCombinators.ReadP as ReadP++-- | Matches a string against a template. This is essentially the opposite of+-- @expand@.+--+-- Since there isn't always one unique match, this function returns all the+-- possibilities. It's up to you to select the one that makes the most sense,+-- or to simply grab the first one if you don't care.+--+-- >>> match "" <$> parse "no-match"+-- Just []+-- >>> match "no-variables" <$> parse "no-variables"+-- Just [[]]+-- >>> match "1-match" <$> parse "{one}-match"+-- Just [[("one",String "1")]]+--+-- Be warned that the number of possible matches can grow quickly if your+-- template has variables next to each other without any separators.+--+-- >>> let Just template = parse "{a}{b}"+-- >>> mapM_ print $ match "ab" template+-- [("a",String "a"),("b",String "b")]+-- [("a",String "ab"),("b",String "")]+-- [("a",String "ab")]+-- [("a",String ""),("b",String "ab")]+-- [("b",String "ab")]+--+-- Matching supports everything /except/ explode modifiers (@{a*}@), list+-- values, and dictionary values.+match :: String -> Template.Template -> [[(String, Value.Value)]]+match s =+ fmap finalize+ . Maybe.mapMaybe (keepConsistent . fst)+ . flip ReadP.readP_to_S s+ . template++finalize :: [(Name.Name, Match.Match)] -> [(String, Value.Value)]+finalize = Maybe.mapMaybe $ \(n, m) -> case m of+ Match.Defined v ->+ Just (Render.builderToString $ Render.name n, Value.String v)+ Match.Prefix _ v ->+ Just (Render.builderToString $ Render.name n, Value.String v)+ Match.Undefined -> Nothing++keepConsistent+ :: [(Name.Name, Match.Match)] -> Maybe [(Name.Name, Match.Match)]+keepConsistent xs = case xs of+ [] -> Just xs+ (k, v) : ys -> do+ let (ts, fs) = List.partition ((== k) . fst) ys+ w <- combine v $ fmap snd ts+ ((k, w) :) <$> keepConsistent fs++combine :: Match.Match -> [Match.Match] -> Maybe Match.Match+combine x ys = case ys of+ [] -> Just x+ y : zs -> case x of+ Match.Defined t -> case y of+ Match.Defined u | t == u -> combine x zs+ Match.Prefix m u | Text.take (MaxLength.count m) t == u -> combine x zs+ _ -> Nothing+ Match.Prefix n t -> case y of+ Match.Defined u | t == Text.take (MaxLength.count n) u -> combine y zs+ Match.Prefix m u+ | let c = MaxLength.count (min n m) in Text.take c t == Text.take c u+ -> combine (if m > n then y else x) zs+ _ -> Nothing+ Match.Undefined -> case y of+ Match.Undefined -> combine x zs+ _ -> Nothing++template :: Template.Template -> ReadP.ReadP [(Name.Name, Match.Match)]+template x = do+ xs <- fmap mconcat . traverse token $ Template.tokens x+ ReadP.eof+ pure xs++token :: Token.Token -> ReadP.ReadP [(Name.Name, Match.Match)]+token x = case x of+ Token.Expression y -> expression y+ Token.Literal y -> [] <$ literal y++expression :: Expression.Expression -> ReadP.ReadP [(Name.Name, Match.Match)]+expression x = variables (Expression.operator x) (Expression.variables x)++variables+ :: Operator.Operator+ -> NonEmpty.NonEmpty Variable.Variable+ -> ReadP.ReadP [(Name.Name, Match.Match)]+variables op vs = case op of+ Operator.Ampersand -> vars vs (Just '&') '&' varEq+ Operator.FullStop -> vars vs (Just '.') '.' $ variable Expand.isUnreserved+ Operator.None -> vars vs Nothing ',' $ variable Expand.isUnreserved+ Operator.NumberSign -> vars vs (Just '#') ',' $ variable Expand.isAllowed+ Operator.PlusSign -> vars vs Nothing ',' $ variable Expand.isAllowed+ Operator.QuestionMark -> vars vs (Just '?') '&' varEq+ Operator.Semicolon -> vars vs (Just ';') ';' $ \v -> do+ let n = Variable.name v+ name n+ ReadP.option [(n, Match.Defined Text.empty)] $ do+ char_ '='+ variable Expand.isUnreserved v+ Operator.Solidus -> vars vs (Just '/') '/' $ variable Expand.isUnreserved++vars+ :: NonEmpty.NonEmpty Variable.Variable+ -> Maybe Char+ -> Char+ -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])+ -> ReadP.ReadP [(Name.Name, Match.Match)]+vars vs m c f = do+ let+ ctx = case m of+ Nothing -> id+ Just o -> \p -> ReadP.option (undef <$> NonEmpty.toList vs) $ do+ char_ o+ xs <- p+ Monad.guard . not $ all isUndefined xs+ pure xs+ ctx . vars' c f $ NonEmpty.toList vs++isUndefined :: (Name.Name, Match.Match) -> Bool+isUndefined = (== Match.Undefined) . snd++vars'+ :: Char+ -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])+ -> [Variable.Variable]+ -> ReadP.ReadP [(Name.Name, Match.Match)]+vars' c f vs = case vs of+ [] -> pure []+ v : ws ->+ let+ this = do+ x <- f v+ xs <- ReadP.option (undef <$> ws) $ do+ char_ c+ vars' c f ws+ pure $ x <> xs+ that = (undef v :) <$> vars' c f ws+ in this ReadP.+++ that++undef :: Variable.Variable -> (Name.Name, Match.Match)+undef v = (Variable.name v, Match.Undefined)++char_ :: Char -> ReadP.ReadP ()+char_ = Monad.void . ReadP.char++varEq :: Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)]+varEq v = do+ name $ Variable.name v+ char_ '='+ variable Expand.isUnreserved v++name :: Name.Name -> ReadP.ReadP ()+name = Monad.void . ReadP.string . Render.builderToString . Render.name++variable+ :: (Char -> Bool)+ -> Variable.Variable+ -> ReadP.ReadP [(Name.Name, Match.Match)]+variable f x = do+ v <- case Variable.modifier x of+ Modifier.Asterisk -> ReadP.pfail+ Modifier.None -> Match.Defined <$> manyCharacters f+ Modifier.Colon n -> Match.Prefix n <$> manyCharacters f+ pure [(Variable.name x, v)]++manyCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text+manyCharacters f = do+ let+ f1 = (:) <$> someEncodedCharacters <*> ReadP.option [] f2+ f2 = (:) <$> someUnencodedCharacters f <*> ReadP.option [] f1+ fmap mconcat . ReadP.option [] $ f1 ReadP.<++ f2++someEncodedCharacters :: ReadP.ReadP Text.Text+someEncodedCharacters = do+ xs <- some anEncodedCharacter+ either (fail . show) pure+ . Text.decodeUtf8'+ . ByteString.pack+ . fmap (uncurry Digit.toWord8)+ $ NonEmpty.toList xs++some :: ReadP.ReadP a -> ReadP.ReadP (NonEmpty.NonEmpty a)+some p = (NonEmpty.:|) <$> p <*> ReadP.many p++someUnencodedCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text+someUnencodedCharacters f = do+ xs <- some $ ReadP.satisfy f+ pure . Text.pack $ NonEmpty.toList xs++anEncodedCharacter :: ReadP.ReadP (Digit.Digit, Digit.Digit)+anEncodedCharacter = do+ char_ '%'+ (,) <$> aDigit <*> aDigit++aDigit :: ReadP.ReadP Digit.Digit+aDigit = do+ x <- ReadP.satisfy Char.isHexDigit+ maybe (fail "invalid Digit") pure $ Digit.fromChar x++literal :: Literal.Literal -> ReadP.ReadP ()+literal = mapM_ literalCharacter . Literal.characters++literalCharacter :: Character.Character Literal.Literal -> ReadP.ReadP ()+literalCharacter = character Expand.isAllowed++character :: (Char -> Bool) -> Character.Character tag -> ReadP.ReadP ()+character f x = case x of+ Character.Encoded y z -> encodedCharacter y z+ Character.Unencoded y -> unencodedCharacter f y++encodedCharacter :: Digit.Digit -> Digit.Digit -> ReadP.ReadP ()+encodedCharacter x y = char_ '%' *> digit x *> digit y++digit :: Digit.Digit -> ReadP.ReadP ()+digit x = char_ $ case x of+ Digit.Ox0 -> '0'+ Digit.Ox1 -> '1'+ Digit.Ox2 -> '2'+ Digit.Ox3 -> '3'+ Digit.Ox4 -> '4'+ Digit.Ox5 -> '5'+ Digit.Ox6 -> '6'+ Digit.Ox7 -> '7'+ Digit.Ox8 -> '8'+ Digit.Ox9 -> '9'+ Digit.OxA Case.Upper -> 'A'+ Digit.OxB Case.Upper -> 'B'+ Digit.OxC Case.Upper -> 'C'+ Digit.OxD Case.Upper -> 'D'+ Digit.OxE Case.Upper -> 'E'+ Digit.OxF Case.Upper -> 'F'+ Digit.OxA Case.Lower -> 'a'+ Digit.OxB Case.Lower -> 'b'+ Digit.OxC Case.Lower -> 'c'+ Digit.OxD Case.Lower -> 'd'+ Digit.OxE Case.Lower -> 'e'+ Digit.OxF Case.Lower -> 'f'++unencodedCharacter :: (Char -> Bool) -> Char -> ReadP.ReadP ()+unencodedCharacter f x = if f x+ then char_ x+ else mapM_ (uncurry encodedCharacter) $ Expand.encodeCharacter x
+ source/library/Burrito/Internal/Parse.hs view
@@ -0,0 +1,176 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++{-# LANGUAGE FlexibleContexts #-}++module Burrito.Internal.Parse where++import qualified Burrito.Internal.Type.Character as Character+import qualified Burrito.Internal.Type.Digit as Digit+import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Field as Field+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Template as Template+import qualified Burrito.Internal.Type.Token as Token+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Data.Char as Char+import qualified Data.Ix as Ix+import qualified Data.List.NonEmpty as NonEmpty+import qualified Text.Parsec as Parsec+import qualified Text.Read as Read++-- | Attempts to parse a string as a URI template. If parsing fails, this will+-- return @Nothing@. Otherwise it will return @Just@ the parsed template.+--+-- Parsing will usually succeed, but it can fail if the input string contains+-- characters that are not valid in IRIs (like @^@) or if the input string+-- contains an invalid template expression (like @{!}@). To include characters+-- that aren't valid in IRIs, percent encode them (like @%5E@).+--+-- >>> parse "invalid template"+-- Nothing+-- >>> parse "valid-template"+-- Just (Template ...)+parse :: String -> Maybe Template.Template+parse = either (const Nothing) Just . Parsec.parse template ""++template :: Parsec.Stream s m Char => Parsec.ParsecT s u m Template.Template+template = Template.Template <$> Parsec.many token <* Parsec.eof++token :: Parsec.Stream s m Char => Parsec.ParsecT s u m Token.Token+token = choice (Token.Expression <$> expression) (Token.Literal <$> literal)++choice+ :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a+choice = (Parsec.<|>)++expression+ :: Parsec.Stream s m Char => Parsec.ParsecT s u m Expression.Expression+expression =+ Parsec.between (Parsec.char '{') (Parsec.char '}')+ $ Expression.Expression+ <$> operator+ <*> sepBy1 variable (Parsec.char ',')++operator :: Parsec.Stream s m Char => Parsec.ParsecT s u m Operator.Operator+operator = Parsec.option Operator.None $ Parsec.choice+ [ Operator.Ampersand <$ Parsec.char '&'+ , Operator.FullStop <$ Parsec.char '.'+ , Operator.NumberSign <$ Parsec.char '#'+ , Operator.PlusSign <$ Parsec.char '+'+ , Operator.QuestionMark <$ Parsec.char '?'+ , Operator.Semicolon <$ Parsec.char ';'+ , Operator.Solidus <$ Parsec.char '/'+ ]++sepBy1+ :: Parsec.ParsecT s u m a+ -> Parsec.ParsecT s u m x+ -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)+sepBy1 p s = (NonEmpty.:|) <$> p <*> Parsec.many (s *> p)++variable :: Parsec.Stream s m Char => Parsec.ParsecT s u m Variable.Variable+variable = Variable.Variable <$> name <*> modifier++name :: Parsec.Stream s m Char => Parsec.ParsecT s u m Name.Name+name = Name.Name <$> sepBy1 field (Parsec.char '.')++field :: Parsec.Stream s m Char => Parsec.ParsecT s u m Field.Field+field = Field.Field <$> nonEmpty fieldCharacter++nonEmpty+ :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)+nonEmpty p = (NonEmpty.:|) <$> p <*> Parsec.many p++fieldCharacter+ :: Parsec.Stream s m Char+ => Parsec.ParsecT s u m (Character.Character Field.Field)+fieldCharacter = choice encodedCharacter (unencodedCharacter isFieldCharacter)++encodedCharacter+ :: Parsec.Stream s m Char => Parsec.ParsecT s u m (Character.Character tag)+encodedCharacter = Parsec.char '%' >> Character.Encoded <$> digit <*> digit++digit :: Parsec.Stream s m Char => Parsec.ParsecT s u m Digit.Digit+digit = do+ x <- Parsec.satisfy Char.isHexDigit+ maybe (fail "invalid Digit") pure $ Digit.fromChar x++unencodedCharacter+ :: Parsec.Stream s m Char+ => (Char -> Bool)+ -> Parsec.ParsecT s u m (Character.Character tag)+unencodedCharacter = fmap Character.Unencoded . Parsec.satisfy++isFieldCharacter :: Char -> Bool+isFieldCharacter x = case x of+ '_' -> True+ _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x++modifier :: Parsec.Stream s m Char => Parsec.ParsecT s u m Modifier.Modifier+modifier = Parsec.option Modifier.None $ Parsec.choice+ [ Modifier.Asterisk <$ Parsec.char '*'+ , Parsec.char ':' >> Modifier.Colon <$> maxLength+ ]++maxLength+ :: Parsec.Stream s m Char => Parsec.ParsecT s u m MaxLength.MaxLength+maxLength = do+ x <- Parsec.satisfy $ Ix.inRange ('1', '9')+ xs <- Parsec.many $ Parsec.satisfy Char.isDigit+ n <- maybe (fail "invalid MaxLength") pure . Read.readMaybe $ x : xs+ if isMaxLength n+ then pure $ MaxLength.MaxLength n+ else fail "invalid MaxLength"++isMaxLength :: Int -> Bool+isMaxLength = Ix.inRange (1, 9999)++literal :: Parsec.Stream s m Char => Parsec.ParsecT s u m Literal.Literal+literal = Literal.Literal <$> nonEmpty literalCharacter++literalCharacter+ :: Parsec.Stream s m Char+ => Parsec.ParsecT s u m (Character.Character Literal.Literal)+literalCharacter =+ choice encodedCharacter (unencodedCharacter isLiteralCharacter)++isLiteralCharacter :: Char -> Bool+isLiteralCharacter x = case x of+ ' ' -> False+ '"' -> False+ '\'' -> False+ '%' -> False+ '<' -> False+ '>' -> False+ '\\' -> False+ '^' -> False+ '`' -> False+ '{' -> False+ '|' -> False+ '}' -> False+ _ ->+ Ix.inRange ('\x20', '\x7e') x+ || Ix.inRange ('\xa0', '\xd7ff') x+ || Ix.inRange ('\xe000', '\xf8ff') x+ || Ix.inRange ('\xf900', '\xfdcf') x+ || Ix.inRange ('\xfdf0', '\xffef') x+ || Ix.inRange ('\x10000', '\x1fffd') x+ || Ix.inRange ('\x20000', '\x2fffd') x+ || Ix.inRange ('\x30000', '\x3fffd') x+ || Ix.inRange ('\x40000', '\x4fffd') x+ || Ix.inRange ('\x50000', '\x5fffd') x+ || Ix.inRange ('\x60000', '\x6fffd') x+ || Ix.inRange ('\x70000', '\x7fffd') x+ || Ix.inRange ('\x80000', '\x8fffd') x+ || Ix.inRange ('\x90000', '\x9fffd') x+ || Ix.inRange ('\xa0000', '\xafffd') x+ || Ix.inRange ('\xb0000', '\xbfffd') x+ || Ix.inRange ('\xc0000', '\xcfffd') x+ || Ix.inRange ('\xd0000', '\xdfffd') x+ || Ix.inRange ('\xe1000', '\xefffd') x+ || Ix.inRange ('\xf0000', '\xffffd') x+ || Ix.inRange ('\x100000', '\x10fffd') x
+ source/library/Burrito/Internal/Render.hs view
@@ -0,0 +1,107 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module Burrito.Internal.Render where++import qualified Burrito.Internal.Type.Case as Case+import qualified Burrito.Internal.Type.Character as Character+import qualified Burrito.Internal.Type.Digit as Digit+import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Field as Field+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Token as Token+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Text.Lazy as LazyText+import qualified Data.Text.Lazy.Builder as Builder++builderToString :: Builder.Builder -> String+builderToString = LazyText.unpack . Builder.toLazyText++token :: Token.Token -> Builder.Builder+token x = case x of+ Token.Expression y -> expression y+ Token.Literal y -> literal y++expression :: Expression.Expression -> Builder.Builder+expression x =+ Builder.singleton '{'+ <> operator (Expression.operator x)+ <> sepBy1 variable (Builder.singleton ',') (Expression.variables x)+ <> Builder.singleton '}'++operator :: Operator.Operator -> Builder.Builder+operator x = case x of+ Operator.Ampersand -> Builder.singleton '&'+ Operator.FullStop -> Builder.singleton '.'+ Operator.None -> mempty+ Operator.NumberSign -> Builder.singleton '#'+ Operator.PlusSign -> Builder.singleton '+'+ Operator.QuestionMark -> Builder.singleton '?'+ Operator.Semicolon -> Builder.singleton ';'+ Operator.Solidus -> Builder.singleton '/'++sepBy1+ :: (a -> Builder.Builder)+ -> Builder.Builder+ -> NonEmpty.NonEmpty a+ -> Builder.Builder+sepBy1 f x = mconcat . List.intersperse x . fmap f . NonEmpty.toList++variable :: Variable.Variable -> Builder.Builder+variable x = name (Variable.name x) <> modifier (Variable.modifier x)++name :: Name.Name -> Builder.Builder+name = sepBy1 field (Builder.singleton '.') . Name.fields++field :: Field.Field -> Builder.Builder+field = foldMap character . Field.characters++character :: Character.Character tag -> Builder.Builder+character x = case x of+ Character.Encoded y z -> encodedCharacter y z+ Character.Unencoded y -> Builder.singleton y++encodedCharacter :: Digit.Digit -> Digit.Digit -> Builder.Builder+encodedCharacter x y = Builder.singleton '%' <> digit x <> digit y++digit :: Digit.Digit -> Builder.Builder+digit x = Builder.singleton $ case x of+ Digit.Ox0 -> '0'+ Digit.Ox1 -> '1'+ Digit.Ox2 -> '2'+ Digit.Ox3 -> '3'+ Digit.Ox4 -> '4'+ Digit.Ox5 -> '5'+ Digit.Ox6 -> '6'+ Digit.Ox7 -> '7'+ Digit.Ox8 -> '8'+ Digit.Ox9 -> '9'+ Digit.OxA Case.Upper -> 'A'+ Digit.OxB Case.Upper -> 'B'+ Digit.OxC Case.Upper -> 'C'+ Digit.OxD Case.Upper -> 'D'+ Digit.OxE Case.Upper -> 'E'+ Digit.OxF Case.Upper -> 'F'+ Digit.OxA Case.Lower -> 'a'+ Digit.OxB Case.Lower -> 'b'+ Digit.OxC Case.Lower -> 'c'+ Digit.OxD Case.Lower -> 'd'+ Digit.OxE Case.Lower -> 'e'+ Digit.OxF Case.Lower -> 'f'++modifier :: Modifier.Modifier -> Builder.Builder+modifier x = case x of+ Modifier.Asterisk -> Builder.singleton '*'+ Modifier.Colon y -> Builder.singleton ':' <> maxLength y+ Modifier.None -> mempty++maxLength :: MaxLength.MaxLength -> Builder.Builder+maxLength = Builder.fromString . show . MaxLength.count++literal :: Literal.Literal -> Builder.Builder+literal = foldMap character . Literal.characters
+ source/library/Burrito/Internal/TH.hs view
@@ -0,0 +1,30 @@+module Burrito.Internal.TH+ ( uriTemplate+ ) where++import qualified Burrito.Internal.Parse as Parse+import qualified Language.Haskell.TH.Quote as TH+import qualified Language.Haskell.TH.Syntax as TH++-- | This can be used together with the @QuasiQuotes@ language extension to+-- parse a URI template at compile time. This is convenient because it allows+-- you to verify the validity of the template when you compile your file as+-- opposed to when you run it.+--+-- >>> :set -XQuasiQuotes+-- >>> import Burrito+-- >>> let template = [uriTemplate|http://example/search{?query}|]+-- >>> let values = [("query", stringValue "chorizo")]+-- >>> expand values template+-- "http://example/search?query=chorizo"+--+-- Note that you cannot use escape sequences in this quasi-quoter. For example,+-- this is invalid: @[uriTemplate|\\xa0|]@. You can however use percent encoded+-- triples as normal. So this is valid: @[uriTemplate|%c2%a0|]@.+uriTemplate :: TH.QuasiQuoter+uriTemplate = TH.QuasiQuoter+ { TH.quoteDec = const $ fail "cannot be used as a declaration"+ , TH.quoteExp = maybe (fail "invalid URI template") TH.liftData . Parse.parse+ , TH.quotePat = const $ fail "cannot be used as a pattern"+ , TH.quoteType = const $ fail "cannot be used as a type"+ }
+ source/library/Burrito/Internal/Type/Case.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Case+ ( Case(..)+ ) where++import qualified Data.Data as Data++data Case+ = Lower+ | Upper+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Character.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Character+ ( Character(..)+ ) where++import qualified Burrito.Internal.Type.Digit as Digit+import qualified Data.Data as Data++data Character tag+ = Encoded Digit.Digit Digit.Digit+ | Unencoded Char+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Digit.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Digit+ ( Digit(..)+ , fromChar+ , fromWord8+ , toWord8+ ) where++import qualified Burrito.Internal.Type.Case as Case+import qualified Data.Bits as Bits+import qualified Data.Data as Data+import qualified Data.Word as Word++data Digit+ = Ox0+ | Ox1+ | Ox2+ | Ox3+ | Ox4+ | Ox5+ | Ox6+ | Ox7+ | Ox8+ | Ox9+ | OxA Case.Case+ | OxB Case.Case+ | OxC Case.Case+ | OxD Case.Case+ | OxE Case.Case+ | OxF Case.Case+ deriving (Data.Data, Eq, Ord, Show)++fromChar :: Char -> Maybe Digit+fromChar x = case x of+ '0' -> Just Ox0+ '1' -> Just Ox1+ '2' -> Just Ox2+ '3' -> Just Ox3+ '4' -> Just Ox4+ '5' -> Just Ox5+ '6' -> Just Ox6+ '7' -> Just Ox7+ '8' -> Just Ox8+ '9' -> Just Ox9+ 'A' -> Just $ OxA Case.Upper+ 'B' -> Just $ OxB Case.Upper+ 'C' -> Just $ OxC Case.Upper+ 'D' -> Just $ OxD Case.Upper+ 'E' -> Just $ OxE Case.Upper+ 'F' -> Just $ OxF Case.Upper+ 'a' -> Just $ OxA Case.Lower+ 'b' -> Just $ OxB Case.Lower+ 'c' -> Just $ OxC Case.Lower+ 'd' -> Just $ OxD Case.Lower+ 'e' -> Just $ OxE Case.Lower+ 'f' -> Just $ OxF Case.Lower+ _ -> Nothing++fromWord8 :: Word.Word8 -> (Digit, Digit)+fromWord8 x =+ let+ f :: Word.Word8 -> Digit+ f y = case y of+ 0x0 -> Ox0+ 0x1 -> Ox1+ 0x2 -> Ox2+ 0x3 -> Ox3+ 0x4 -> Ox4+ 0x5 -> Ox5+ 0x6 -> Ox6+ 0x7 -> Ox7+ 0x8 -> Ox8+ 0x9 -> Ox9+ 0xA -> OxA Case.Upper+ 0xB -> OxB Case.Upper+ 0xC -> OxC Case.Upper+ 0xD -> OxD Case.Upper+ 0xE -> OxE Case.Upper+ 0xF -> OxF Case.Upper+ _ -> error $ "invalid nibble: " <> show y+ in (f $ Bits.shiftR x 4, f $ x Bits..&. 0x0F)++toWord8 :: Digit -> Digit -> Word.Word8+toWord8 x y =+ let+ f :: Digit -> Word.Word8+ f z = case z of+ Ox0 -> 0x0+ Ox1 -> 0x1+ Ox2 -> 0x2+ Ox3 -> 0x3+ Ox4 -> 0x4+ Ox5 -> 0x5+ Ox6 -> 0x6+ Ox7 -> 0x7+ Ox8 -> 0x8+ Ox9 -> 0x9+ OxA _ -> 0xA+ OxB _ -> 0xB+ OxC _ -> 0xC+ OxD _ -> 0xD+ OxE _ -> 0xE+ OxF _ -> 0xF+ in Bits.shiftL (f x) 4 Bits..|. f y
+ source/library/Burrito/Internal/Type/Expression.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Expression+ ( Expression(..)+ ) where++import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++data Expression = Expression+ { operator :: Operator.Operator+ , variables :: NonEmpty.NonEmpty Variable.Variable+ }+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Field.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Field+ ( Field(..)+ ) where++import qualified Burrito.Internal.Type.Character as Character+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++newtype Field = Field+ { characters :: NonEmpty.NonEmpty (Character.Character Field)+ } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Literal.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Literal+ ( Literal(..)+ ) where++import qualified Burrito.Internal.Type.Character as Character+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++newtype Literal = Literal+ { characters :: NonEmpty.NonEmpty (Character.Character Literal)+ } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Match.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Match+ ( Match(..)+ ) where++import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Data.Data as Data+import qualified Data.Text as Text++data Match+ = Defined Text.Text+ | Prefix MaxLength.MaxLength Text.Text+ | Undefined+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/MaxLength.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.MaxLength+ ( MaxLength(..)+ ) where++import qualified Data.Data as Data++newtype MaxLength = MaxLength+ { count :: Int+ } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Modifier.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Modifier+ ( Modifier(..)+ ) where++import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Data.Data as Data++data Modifier+ = Asterisk+ | Colon MaxLength.MaxLength+ | None+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Name.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Name+ ( Name(..)+ ) where++import qualified Burrito.Internal.Type.Field as Field+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++newtype Name = Name+ { fields :: NonEmpty.NonEmpty Field.Field+ } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Operator.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Operator+ ( Operator(..)+ ) where++import qualified Data.Data as Data++data Operator+ = Ampersand+ | FullStop+ | None+ | NumberSign+ | PlusSign+ | QuestionMark+ | Semicolon+ | Solidus+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Template.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Template+ ( Template(..)+ , render+ ) where++import qualified Burrito.Internal.Render as Render+import qualified Burrito.Internal.Type.Token as Token+import qualified Data.Data as Data+import qualified Data.Text.Lazy.Builder as Builder++-- | Represents a URI template.+newtype Template = Template+ { tokens :: [Token.Token]+ } deriving (Data.Data, Eq, Ord)++instance Show Template where+ show = render++-- | Renders a template back into a string. This is essentially the opposite of+-- @parse@. Usually you'll want to use @expand@ to actually substitute+-- variables in the template, but this can be useful for printing out the+-- template itself+--+-- >>> render <$> parse "valid-template"+-- Just "valid-template"+-- >>> render <$> parse "{var}"+-- Just "{var}"+render :: Template -> String+render = Render.builderToString . template++template :: Template -> Builder.Builder+template = foldMap Render.token . tokens
+ source/library/Burrito/Internal/Type/Token.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Token+ ( Token(..)+ ) where++import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Data.Data as Data++data Token+ = Expression Expression.Expression+ | Literal Literal.Literal+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Value.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Value+ ( Value(..)+ ) where++import qualified Data.Data as Data+import qualified Data.Map as Map+import qualified Data.Text as Text++-- | Represents a value that can be substituted into a template. Can be a+-- string, a list, or dictionary (which is called an associative array in the+-- RFC).+data Value+ = Dictionary (Map.Map Text.Text Text.Text)+ | List [Text.Text]+ | String Text.Text+ deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Variable.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Variable+ ( Variable(..)+ ) where++import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Data.Data as Data++data Variable = Variable+ { name :: Name.Name+ , modifier :: Modifier.Modifier+ }+ deriving (Data.Data, Eq, Ord, Show)
+ source/test-suite/Main.hs view
@@ -0,0 +1,1470 @@+{-# OPTIONS_GHC -Wno-monomorphism-restriction #-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module Main+ ( main+ ) where++import qualified Burrito+import qualified Burrito.Internal.Parse as Parse+import qualified Burrito.Internal.Render as Render+import qualified Burrito.Internal.Type.Case as Case+import qualified Burrito.Internal.Type.Character as Character+import qualified Burrito.Internal.Type.Digit as Digit+import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Field as Field+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Template as Template+import qualified Burrito.Internal.Type.Token as Token+import qualified Burrito.Internal.Type.Value as Value+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Control.Monad as Monad+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Set as Set+import qualified Data.String as String+import qualified Data.Text as Text+import qualified GHC.Stack as Stack+import qualified Test.Hspec as Hspec+import qualified Test.Hspec.QuickCheck as Hspec+import qualified Test.QuickCheck as QC++main :: IO ()+main = Hspec.hspec . Hspec.describe "Burrito" $ do++ Monad.forM_ tests $ \test ->+ Hspec.it (show (testInput test, unwrapOutput $ testOutput test))+ $ runTest test++ Hspec.describe "match" $ do+ let+ matchTest+ :: Stack.HasCallStack+ => String+ -> [(String, Burrito.Value)]+ -> String+ -> Hspec.Spec+ matchTest input values output = Hspec.it (show (input, output)) $ do+ template <- maybe (fail "invalid Template") pure $ Burrito.parse input+ let matches = Burrito.match output template+ matches `Hspec.shouldSatisfy` elem values+ Monad.forM_ matches $ \match -> do+ let it = Burrito.expand match template+ Monad.when (it /= output) . fail $ show+ (input, values, output, matches, match, it)++ matchTest "" [] ""+ matchTest "a" [] "a"+ matchTest "!" [] "!"+ matchTest "\xa0" [] "%C2%A0"++ matchTest "{a}" ["a" =: s ""] ""+ matchTest "{a}" ["a" =: s "A"] "A"+ matchTest "{a}" ["a" =: s "AB"] "AB"++ matchTest "a{b}" ["b" =: s ""] "a"+ matchTest "a{b}" ["b" =: s "B"] "aB"+ matchTest "a{b}" ["b" =: s "BC"] "aBC"++ matchTest "{a}b" ["a" =: s ""] "b"+ matchTest "{a}b" ["a" =: s "A"] "Ab"+ matchTest "{a}b" ["a" =: s "AB"] "ABb"++ matchTest "{a}{a}" ["a" =: s "A"] "AA"+ matchTest "{a}{a}" ["a" =: s "AB"] "ABAB"++ matchTest "{a}" ["a" =: s "%"] "%25"+ matchTest "{a}" ["a" =: s "/"] "%2F"+ matchTest "{a}" ["a" =: s "\xa0"] "%C2%A0"+ matchTest "{a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"+ matchTest "{a}" ["a" =: s "\x10000"] "%F0%90%80%80"+ matchTest "{a}" ["a" =: s "A/B"] "A%2FB"+ matchTest "{a}" ["a" =: s "WX\xa0\xa1YZ"] "WX%C2%A0%C2%A1YZ"++ matchTest "{+a}" ["a" =: s "%"] "%25"+ matchTest "{+a}" ["a" =: s "/"] "/"++ matchTest "{#a}" [] ""+ matchTest "{#a}" ["a" =: s ""] "#"+ matchTest "{#a}" ["a" =: s "A"] "#A"+ matchTest "{#a}" ["a" =: s "%"] "#%25"+ matchTest "{#a}" ["a" =: s "/"] "#/"++ matchTest "{.a}" [] ""+ matchTest "{.a}" ["a" =: s ""] "."+ matchTest "{.a}" ["a" =: s "A"] ".A"+ matchTest "{.a}" ["a" =: s "%"] ".%25"+ matchTest "{.a}" ["a" =: s "/"] ".%2F"++ matchTest "{/a}" [] ""+ matchTest "{/a}" ["a" =: s ""] "/"+ matchTest "{/a}" ["a" =: s "A"] "/A"+ matchTest "{/a}" ["a" =: s "%"] "/%25"+ matchTest "{/a}" ["a" =: s "/"] "/%2F"++ matchTest "{;a}" [] ""+ matchTest "{;a}" ["a" =: s ""] ";a"+ matchTest "{;a}" ["a" =: s "A"] ";a=A"+ matchTest "{;a}" ["a" =: s "%"] ";a=%25"+ matchTest "{;a}" ["a" =: s "/"] ";a=%2F"++ matchTest "{?a}" [] ""+ matchTest "{?a}" ["a" =: s ""] "?a="+ matchTest "{?a}" ["a" =: s "A"] "?a=A"+ matchTest "{?a}" ["a" =: s "%"] "?a=%25"+ matchTest "{?a}" ["a" =: s "/"] "?a=%2F"++ matchTest "{&a}" [] ""+ matchTest "{&a}" ["a" =: s ""] "&a="+ matchTest "{&a}" ["a" =: s "A"] "&a=A"+ matchTest "{&a}" ["a" =: s "%"] "&a=%25"+ matchTest "{&a}" ["a" =: s "/"] "&a=%2F"++ matchTest "{a,b}" ["a" =: s "A", "b" =: s "B"] "A,B"+ matchTest "{+a,b}" ["a" =: s "A", "b" =: s "B"] "A,B"+ matchTest "{#a,b}" ["a" =: s "A", "b" =: s "B"] "#A,B"+ matchTest "{.a,b}" ["a" =: s "A", "b" =: s "B"] ".A.B"+ matchTest "{/a,b}" ["a" =: s "A", "b" =: s "B"] "/A/B"+ matchTest "{;a,b}" ["a" =: s "A", "b" =: s "B"] ";a=A;b=B"+ matchTest "{?a,b}" ["a" =: s "A", "b" =: s "B"] "?a=A&b=B"+ matchTest "{&a,b}" ["a" =: s "A", "b" =: s "B"] "&a=A&b=B"++ matchTest "{a,b}" ["a" =: s "A"] "A"+ matchTest "{a,b,c}" ["a" =: s "A"] "A"+ matchTest "{a,b,c}" ["a" =: s "A", "b" =: s "B"] "A,B"+ matchTest "{a}{a,b}" ["b" =: s "B"] "B"+ matchTest "{a}{a,b,c}" ["b" =: s "B"] "B"+ matchTest "{a,b}{a,b,c}" ["c" =: s "C"] "C"+ matchTest "{b}{a,b,c}" ["a" =: s "A", "c" =: s "C"] "A,C"+ matchTest "{a}{a,b,c}" ["b" =: s "B", "c" =: s "C"] "B,C"++ matchTest "{a:1}/{a}" ["a" =: s "AB"] "A/AB"++ -- TODO: Test matching on explode modifier.+ -- matchTest "{a*}" ["a" =: s "A"] "A"++ -- TODO: Test matching on lists.+ -- matchTest "{a}" ["a" =: l ["A", "B"]] "A,B"++ -- TODO: Test matching on dictionaries.+ -- matchTest "{a}" ["a" =: d ["k" =: "v"]] "k,v"++ Hspec.describe "uriTemplate"+ . Hspec.it "works as an expression"+ $ Just [Burrito.uriTemplate|a{b}c|]+ `Hspec.shouldBe` Burrito.parse "a{b}c"++ Hspec.modifyMaxSize (const 10)+ . Hspec.it "round trips"+ . QC.property+ $ \(Template template) -> Burrito.parse (show template) == Just template++-- brittany-next-binding --columns 160+tests :: [Test]+tests = mconcat+ [ [ Test "" [] ""+ , Test "" ["a" =: s ""] ""+ , Test "" ["a" =: l []] ""+ , Test "" ["a" =: d []] ""+ , Test "!" [] "!"+ , Test "#" [] "#"+ , Test "$" [] "$"+ , Test "&" [] "&"+ , Test "(" [] "("+ , Test ")" [] ")"+ , Test "*" [] "*"+ , Test "+" [] "+"+ , Test "," [] ","+ , Test "-" [] "-"+ , Test "." [] "."+ , Test "/" [] "/"+ , Test "0" [] "0"+ , Test "9" [] "9"+ , Test ":" [] ":"+ , Test ";" [] ";"+ , Test "=" [] "="+ , Test "?" [] "?"+ , Test "@" [] "@"+ , Test "A" [] "A"+ , Test "Z" [] "Z"+ , Test "[" [] "["+ , Test "]" [] "]"+ , Test "_" [] "_"+ , Test "a" [] "a"+ , Test "z" [] "z"+ , Test "~" [] "~"+ , Test "\xa0" [] "%C2%A0"+ , Test "\xd7ff" [] "%ED%9F%BF"+ , Test "\xf900" [] "%EF%A4%80"+ , Test "\xfdcf" [] "%EF%B7%8F"+ , Test "\xfdf0" [] "%EF%B7%B0"+ , Test "\xffef" [] "%EF%BF%AF"+ , Test "\x10000" [] "%F0%90%80%80"+ , Test "\x1fffd" [] "%F0%9F%BF%BD"+ , Test "\x20000" [] "%F0%A0%80%80"+ , Test "\x2fffd" [] "%F0%AF%BF%BD"+ , Test "\x30000" [] "%F0%B0%80%80"+ , Test "\x3fffd" [] "%F0%BF%BF%BD"+ , Test "\x40000" [] "%F1%80%80%80"+ , Test "\x4fffd" [] "%F1%8F%BF%BD"+ , Test "\x50000" [] "%F1%90%80%80"+ , Test "\x5fffd" [] "%F1%9F%BF%BD"+ , Test "\x60000" [] "%F1%A0%80%80"+ , Test "\x6fffd" [] "%F1%AF%BF%BD"+ , Test "\x70000" [] "%F1%B0%80%80"+ , Test "\x7fffd" [] "%F1%BF%BF%BD"+ , Test "\x80000" [] "%F2%80%80%80"+ , Test "\x8fffd" [] "%F2%8F%BF%BD"+ , Test "\x90000" [] "%F2%90%80%80"+ , Test "\x9fffd" [] "%F2%9F%BF%BD"+ , Test "\xa0000" [] "%F2%A0%80%80"+ , Test "\xafffd" [] "%F2%AF%BF%BD"+ , Test "\xb0000" [] "%F2%B0%80%80"+ , Test "\xbfffd" [] "%F2%BF%BF%BD"+ , Test "\xc0000" [] "%F3%80%80%80"+ , Test "\xcfffd" [] "%F3%8F%BF%BD"+ , Test "\xd0000" [] "%F3%90%80%80"+ , Test "\xdfffd" [] "%F3%9F%BF%BD"+ , Test "\xe1000" [] "%F3%A1%80%80"+ , Test "\xefffd" [] "%F3%AF%BF%BD"+ , Test "\xe000" [] "%EE%80%80"+ , Test "\xf8ff" [] "%EF%A3%BF"+ , Test "\xf0000" [] "%F3%B0%80%80"+ , Test "\xffffd" [] "%F3%BF%BF%BD"+ , Test "\x100000" [] "%F4%80%80%80"+ , Test "\x10fffd" [] "%F4%8F%BF%BD"+ , Test "%00" [] "%00"+ , Test "%AA" [] "%AA"+ , Test "%Aa" [] "%Aa"+ , Test "%aA" [] "%aA"+ , Test "%aa" [] "%aa"+ , Test "%" [] noParse+ , Test "%0" [] noParse+ , Test "%0z" [] noParse+ , Test "%z" [] noParse+ , Test "%30" [] "%30"+ , Test " " [] noParse+ , Test "\"" [] noParse+ , Test "'" [] noParse+ , Test "%" [] noParse+ , Test "<" [] noParse+ , Test ">" [] noParse+ , Test "\\" [] noParse+ , Test "^" [] noParse+ , Test "`" [] noParse+ , Test "{" [] noParse+ , Test "|" [] noParse+ , Test "}" [] noParse+ , Test "{}" [] noParse+ , Test "{,}" [] noParse+ , Test "{a,,b}" [] noParse+ , Test "{+}" [] noParse+ , Test "{:1}" [] noParse+ , Test "{*}" [] noParse+ , Test "{AZ}" [] ""+ , Test "{az}" [] ""+ , Test "{09}" [] ""+ , Test "{_a}" [] ""+ , Test "{a_}" [] ""+ , Test "{_}" [] ""+ , Test "{A.A}" [] ""+ , Test "{a.a}" [] ""+ , Test "{0.0}" [] ""+ , Test "{_._}" [] ""+ , Test "{%aa.%aa}" [] ""+ , Test "{.}" [] noParse+ , Test "{a.}" [] noParse+ , Test "{+.a}" [] noParse+ , Test "{a..b}" [] noParse+ , Test "{%00}" [] ""+ , Test "{%}" [] noParse+ , Test "{%0}" [] noParse+ , Test "{%0z}" [] noParse+ , Test "{%z}" [] noParse+ , Test "{!}" [] noParse+ , Test "{" [] noParse+ , Test "{{}" [] noParse+ , Test "}" [] noParse+ , Test "{}}" [] noParse+ , Test "{a,b}" [] ""+ , Test "{a,b,c,d}" [] ""+ , Test "{a,a}" [] ""+ , Test "{a:5}" [] ""+ , Test "{a:67}" [] ""+ , Test "{a:801}" [] ""+ , Test "{a:234}" [] ""+ , Test "{a:9999}" [] ""+ , Test "{a:123}" ["a" =: s (replicate 200 'a')] . Output . Just $ replicate 123 'a'+ , Test "{a:}" [] noParse+ , Test "{a:0}" [] noParse+ , Test "{a:10000}" [] noParse+ , Test "{a:-1}" [] noParse+ , Test "{a*}" [] ""+ , Test "{a:1*}" [] noParse+ , Test "{a*:1}" [] noParse+ , Test "{a,b:1,c*}" [] ""+ , Test "{+a}" [] ""+ , Test "{#a}" [] ""+ , Test "{.a}" [] ""+ , Test "{/a}" [] ""+ , Test "{;a}" [] ""+ , Test "{?a}" [] ""+ , Test "{&a}" [] ""+ , Test "{=a}" [] noParse+ , Test "{,a}" [] noParse+ , Test "{!a}" [] noParse+ , Test "{@a}" [] noParse+ , Test "{|a}" [] noParse+ , Test "{+#a}" [] noParse+ , Test "{+a,#b}" [] noParse+ , Test "{+a:1}" [] ""+ , Test "{#a*}" [] ""+ , Test "{+a,b}" [] ""+ , Test "{#a,b}" [] ""+ , Test "{.a,b}" [] ""+ , Test "{/a,b}" [] ""+ , Test "{;a,b}" [] ""+ , Test "{?a,b}" [] ""+ , Test "{&a,b}" [] ""+ , Test "{a}{b}" [] ""+ , Test "{a}{b}{c}{d}" [] ""+ , Test "{a}{a}" [] ""+ , Test "{{}}" [] noParse+ , Test "{a{b}}" [] noParse+ , Test "{{a}b}" [] noParse+ , Test "{a{b}c}" [] noParse+ , Test "a{b}" [] "a"+ , Test "{a}b" [] "b"+ , Test "a{b}c" [] "ac"+ , Test "{a}b{c}" [] "b"+ , Test "{a}" [] ""+ , Test "{+a}" [] ""+ , Test "{#a}" [] ""+ , Test "{.a}" [] ""+ , Test "{/a}" [] ""+ , Test "{;a}" [] ""+ , Test "{?a}" [] ""+ , Test "{&a}" [] ""+ , Test "http://example.com/~{username}/" ["username" =: s "fred"] "http://example.com/~fred/"+ , Test "http://example.com/~{username}/" ["username" =: s "mark"] "http://example.com/~mark/"+ , Test "http://example.com/dictionary/{term:1}/{term}" ["term" =: s "cat"] "http://example.com/dictionary/c/cat"+ , Test "http://example.com/dictionary/{term:1}/{term}" ["term" =: s "dog"] "http://example.com/dictionary/d/dog"+ , Test "http://example.com/search{?q,lang}" ["q" =: s "cat", "lang" =: s "en"] "http://example.com/search?q=cat&lang=en"+ , Test "http://example.com/search{?q,lang}" ["q" =: s "chien", "lang" =: s "fr"] "http://example.com/search?q=chien&lang=fr"+ , Test "http://www.example.com/foo{?query,number}" ["query" =: s "mycelium", "number" =: s "100"] "http://www.example.com/foo?query=mycelium&number=100"+ , Test "http://www.example.com/foo{?query,number}" ["number" =: s "100"] "http://www.example.com/foo?number=100"+ , Test "http://www.example.com/foo{?query,number}" [] "http://www.example.com/foo"+ , Test "{a}" [] ""+ , Test "{a}" ["a" =: l []] ""+ , Test "{a}" ["a" =: d []] ""+ , Test "{a}" ["a" =: s ""] ""+ , Test "{a}" ["a" =: s "A"] "A"+ , Test "{a}" ["a" =: s "~"] "~"+ , Test "{a}" ["a" =: s "%"] "%25"+ , Test "{a}" ["a" =: s "?"] "%3F"+ , Test "{a}" ["a" =: s "&"] "%26"+ , Test "{a}" ["a" =: s "\xa0"] "%C2%A0"+ , Test "{a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"+ , Test "{a}" ["a" =: s "\x10000"] "%F0%90%80%80"+ , Test "{a}" ["a" =: l ["A"]] "A"+ , Test "{a}" ["a" =: l ["A", "B"]] "A,B"+ , Test "{a}" ["a" =: l ["%"]] "%25"+ , Test "{a}" ["a" =: l ["\xa0"]] "%C2%A0"+ , Test "{a}" ["a" =: d ["A" =: "1"]] "A,1"+ , Test "{a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"+ , Test "{a}" ["a" =: d ["A" =: "%"]] "A,%25"+ , Test "{a}" ["a" =: d ["A" =: "\xa0"]] "A,%C2%A0"+ , Test "{a}" ["a" =: d ["%" =: "1"]] "%25,1"+ , Test "{a*}" [] ""+ , Test "{a*}" ["a" =: s ""] ""+ , Test "{a*}" ["a" =: s "A"] "A"+ , Test "{a*}" ["a" =: l []] ""+ , Test "{a*}" ["a" =: l ["A"]] "A"+ , Test "{a*}" ["a" =: l ["A", "B"]] "A,B"+ , Test "{a*}" ["a" =: d []] ""+ , Test "{a*}" ["a" =: d ["A" =: "1"]] "A=1"+ , Test "{a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"+ , Test "{a:1}" [] ""+ , Test "{a:1}" ["a" =: s ""] ""+ , Test "{a:1}" ["a" =: s "A"] "A"+ , Test "{a:1}" ["a" =: s "AB"] "A"+ , Test "{a:1}" ["a" =: s "%B"] "%25"+ , Test "{a:1}" ["a" =: s "\xa0\&B"] "%C2%A0"+ , Test "{a:1}" ["a" =: s "\xd7ff\&B"] "%ED%9F%BF"+ , Test "{a:1}" ["a" =: s "\x10000\&B"] "%F0%90%80%80"+ , Test "{a:1}" ["a" =: l []] ""+ , Test "{a:1}" ["a" =: l ["AB"]] "AB"+ , Test "{a:1}" ["a" =: l ["AB", "CD"]] "AB,CD"+ , Test "{a:1}" ["a" =: d []] ""+ , Test "{a:1}" ["a" =: d ["AB" =: "12"]] "AB,12"+ , Test "{a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34"+ , Test "{a,a}" [] ""+ , Test "{a,a}" ["a" =: l []] ""+ , Test "{a,a}" ["a" =: d []] ""+ , Test "{a,a}" ["a" =: s ""] ","+ , Test "{a,b}" ["a" =: s ""] ""+ , Test "{a,b}" ["b" =: s ""] ""+ , Test "{%aa}" ["%aa" =: s "A"] "A"+ , Test "{%aa}" ["%aa" =: l ["A", "B"]] "A,B"+ , Test "{%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"+ , Test "{%aa*}" ["%aa" =: s "A"] "A"+ , Test "{%aa*}" ["%aa" =: l ["A", "B"]] "A,B"+ , Test "{%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"+ , Test "{+a}" [] ""+ , Test "{+a}" ["a" =: l []] ""+ , Test "{+a}" ["a" =: d []] ""+ , Test "{+a}" ["a" =: s ""] ""+ , Test "{+a}" ["a" =: s "A"] "A"+ , Test "{+a}" ["a" =: s "~"] "~"+ , Test "{+a}" ["a" =: s "%"] "%25"+ , Test "{+a}" ["a" =: s "?"] "?"+ , Test "{+a}" ["a" =: s "&"] "&"+ , Test "{+a}" ["a" =: s "\xa0"] "%C2%A0"+ , Test "{+a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"+ , Test "{+a}" ["a" =: s "\x10000"] "%F0%90%80%80"+ , Test "{+a}" ["a" =: l ["A"]] "A"+ , Test "{+a}" ["a" =: l ["A", "B"]] "A,B"+ , Test "{+a}" ["a" =: l ["%"]] "%25"+ , Test "{+a}" ["a" =: l ["\xa0"]] "%C2%A0"+ , Test "{+a}" ["a" =: d ["A" =: "1"]] "A,1"+ , Test "{+a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"+ , Test "{+a}" ["a" =: d ["A" =: "%"]] "A,%25"+ , Test "{+a}" ["a" =: d ["A" =: "\xa0"]] "A,%C2%A0"+ , Test "{+a}" ["a" =: d ["%" =: "1"]] "%25,1"+ , Test "{+a*}" [] ""+ , Test "{+a*}" ["a" =: s ""] ""+ , Test "{+a*}" ["a" =: s "A"] "A"+ , Test "{+a*}" ["a" =: l []] ""+ , Test "{+a*}" ["a" =: l ["A"]] "A"+ , Test "{+a*}" ["a" =: l ["A", "B"]] "A,B"+ , Test "{+a*}" ["a" =: d []] ""+ , Test "{+a*}" ["a" =: d ["A" =: "1"]] "A=1"+ , Test "{+a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"+ , Test "{+a:1}" [] ""+ , Test "{+a:1}" ["a" =: s ""] ""+ , Test "{+a:1}" ["a" =: s "A"] "A"+ , Test "{+a:1}" ["a" =: s "AB"] "A"+ , Test "{+a:1}" ["a" =: s "%B"] "%25"+ , Test "{+a:1}" ["a" =: s "\xa0\&B"] "%C2%A0"+ , Test "{+a:1}" ["a" =: s "\xd7ff\&B"] "%ED%9F%BF"+ , Test "{+a:1}" ["a" =: s "\x10000\&B"] "%F0%90%80%80"+ , Test "{+a:1}" ["a" =: l []] ""+ , Test "{+a:1}" ["a" =: l ["AB"]] "AB"+ , Test "{+a:1}" ["a" =: l ["AB", "CD"]] "AB,CD"+ , Test "{+a:1}" ["a" =: d []] ""+ , Test "{+a:1}" ["a" =: d ["AB" =: "12"]] "AB,12"+ , Test "{+a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34"+ , Test "{+a,a}" [] ""+ , Test "{+a,a}" ["a" =: l []] ""+ , Test "{+a,a}" ["a" =: d []] ""+ , Test "{+a,a}" ["a" =: s ""] ","+ , Test "{+a,b}" ["a" =: s ""] ""+ , Test "{+a,b}" ["b" =: s ""] ""+ , Test "{+%aa}" ["%aa" =: s "A"] "A"+ , Test "{+%aa}" ["%aa" =: l ["A", "B"]] "A,B"+ , Test "{+%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"+ , Test "{+%aa*}" ["%aa" =: s "A"] "A"+ , Test "{+%aa*}" ["%aa" =: l ["A", "B"]] "A,B"+ , Test "{+%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"+ , Test "{#a}" [] ""+ , Test "{#a}" ["a" =: l []] ""+ , Test "{#a}" ["a" =: d []] ""+ , Test "{#a}" ["a" =: s ""] "#"+ , Test "{#a}" ["a" =: s "A"] "#A"+ , Test "{#a}" ["a" =: s "~"] "#~"+ , Test "{#a}" ["a" =: s "%"] "#%25"+ , Test "{#a}" ["a" =: s "?"] "#?"+ , Test "{#a}" ["a" =: s "&"] "#&"+ , Test "{#a}" ["a" =: s "\xa0"] "#%C2%A0"+ , Test "{#a}" ["a" =: s "\xd7ff"] "#%ED%9F%BF"+ , Test "{#a}" ["a" =: s "\x10000"] "#%F0%90%80%80"+ , Test "{#a}" ["a" =: l ["A"]] "#A"+ , Test "{#a}" ["a" =: l ["A", "B"]] "#A,B"+ , Test "{#a}" ["a" =: l ["%"]] "#%25"+ , Test "{#a}" ["a" =: l ["\xa0"]] "#%C2%A0"+ , Test "{#a}" ["a" =: d ["A" =: "1"]] "#A,1"+ , Test "{#a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "#A,1,B,2"+ , Test "{#a}" ["a" =: d ["A" =: "%"]] "#A,%25"+ , Test "{#a}" ["a" =: d ["A" =: "\xa0"]] "#A,%C2%A0"+ , Test "{#a}" ["a" =: d ["%" =: "1"]] "#%25,1"+ , Test "{#a*}" [] ""+ , Test "{#a*}" ["a" =: s ""] "#"+ , Test "{#a*}" ["a" =: s "A"] "#A"+ , Test "{#a*}" ["a" =: l []] ""+ , Test "{#a*}" ["a" =: l ["A"]] "#A"+ , Test "{#a*}" ["a" =: l ["A", "B"]] "#A,B"+ , Test "{#a*}" ["a" =: d []] ""+ , Test "{#a*}" ["a" =: d ["A" =: "1"]] "#A=1"+ , Test "{#a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "#A=1,B=2"+ , Test "{#a:1}" [] ""+ , Test "{#a:1}" ["a" =: s ""] "#"+ , Test "{#a:1}" ["a" =: s "A"] "#A"+ , Test "{#a:1}" ["a" =: s "AB"] "#A"+ , Test "{#a:1}" ["a" =: s "%B"] "#%25"+ , Test "{#a:1}" ["a" =: s "\xa0\&B"] "#%C2%A0"+ , Test "{#a:1}" ["a" =: s "\xd7ff\&B"] "#%ED%9F%BF"+ , Test "{#a:1}" ["a" =: s "\x10000\&B"] "#%F0%90%80%80"+ , Test "{#a:1}" ["a" =: l []] ""+ , Test "{#a:1}" ["a" =: l ["AB"]] "#AB"+ , Test "{#a:1}" ["a" =: l ["AB", "CD"]] "#AB,CD"+ , Test "{#a:1}" ["a" =: d []] ""+ , Test "{#a:1}" ["a" =: d ["AB" =: "12"]] "#AB,12"+ , Test "{#a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "#AB,12,CD,34"+ , Test "{#a,a}" [] ""+ , Test "{#a,a}" ["a" =: l []] ""+ , Test "{#a,a}" ["a" =: d []] ""+ , Test "{#a,a}" ["a" =: s ""] "#,"+ , Test "{#a,b}" ["a" =: s ""] "#"+ , Test "{#a,b}" ["b" =: s ""] "#"+ , Test "{#%aa}" ["%aa" =: s "A"] "#A"+ , Test "{#%aa}" ["%aa" =: l ["A", "B"]] "#A,B"+ , Test "{#%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "#A,1,B,2"+ , Test "{#%aa*}" ["%aa" =: s "A"] "#A"+ , Test "{#%aa*}" ["%aa" =: l ["A", "B"]] "#A,B"+ , Test "{#%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "#A=1,B=2"+ , Test "{.a}" [] ""+ , Test "{.a}" ["a" =: l []] ""+ , Test "{.a}" ["a" =: d []] ""+ , Test "{.a}" ["a" =: s ""] "."+ , Test "{.a}" ["a" =: s "A"] ".A"+ , Test "{.a}" ["a" =: s "~"] ".~"+ , Test "{.a}" ["a" =: s "%"] ".%25"+ , Test "{.a}" ["a" =: s "?"] ".%3F"+ , Test "{.a}" ["a" =: s "&"] ".%26"+ , Test "{.a}" ["a" =: s "\xa0"] ".%C2%A0"+ , Test "{.a}" ["a" =: s "\xd7ff"] ".%ED%9F%BF"+ , Test "{.a}" ["a" =: s "\x10000"] ".%F0%90%80%80"+ , Test "{.a}" ["a" =: l ["A"]] ".A"+ , Test "{.a}" ["a" =: l ["A", "B"]] ".A,B"+ , Test "{.a}" ["a" =: l ["%"]] ".%25"+ , Test "{.a}" ["a" =: l ["\xa0"]] ".%C2%A0"+ , Test "{.a}" ["a" =: d ["A" =: "1"]] ".A,1"+ , Test "{.a}" ["a" =: d ["A" =: "1", "B" =: "2"]] ".A,1,B,2"+ , Test "{.a}" ["a" =: d ["A" =: "%"]] ".A,%25"+ , Test "{.a}" ["a" =: d ["A" =: "\xa0"]] ".A,%C2%A0"+ , Test "{.a}" ["a" =: d ["%" =: "1"]] ".%25,1"+ , Test "{.a*}" [] ""+ , Test "{.a*}" ["a" =: s ""] "."+ , Test "{.a*}" ["a" =: s "A"] ".A"+ , Test "{.a*}" ["a" =: l []] ""+ , Test "{.a*}" ["a" =: l ["A"]] ".A"+ , Test "{.a*}" ["a" =: l ["A", "B"]] ".A.B"+ , Test "{.a*}" ["a" =: d []] ""+ , Test "{.a*}" ["a" =: d ["A" =: "1"]] ".A=1"+ , Test "{.a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] ".A=1.B=2"+ , Test "{.a:1}" [] ""+ , Test "{.a:1}" ["a" =: s ""] "."+ , Test "{.a:1}" ["a" =: s "A"] ".A"+ , Test "{.a:1}" ["a" =: s "AB"] ".A"+ , Test "{.a:1}" ["a" =: s "%B"] ".%25"+ , Test "{.a:1}" ["a" =: s "\xa0\&B"] ".%C2%A0"+ , Test "{.a:1}" ["a" =: s "\xd7ff\&B"] ".%ED%9F%BF"+ , Test "{.a:1}" ["a" =: s "\x10000\&B"] ".%F0%90%80%80"+ , Test "{.a:1}" ["a" =: l []] ""+ , Test "{.a:1}" ["a" =: l ["AB"]] ".AB"+ , Test "{.a:1}" ["a" =: l ["AB", "CD"]] ".AB,CD"+ , Test "{.a:1}" ["a" =: d []] ""+ , Test "{.a:1}" ["a" =: d ["AB" =: "12"]] ".AB,12"+ , Test "{.a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] ".AB,12,CD,34"+ , Test "{.a,a}" [] ""+ , Test "{.a,a}" ["a" =: l []] ""+ , Test "{.a,a}" ["a" =: d []] ""+ , Test "{.a,a}" ["a" =: s ""] ".."+ , Test "{.a,b}" ["a" =: s ""] "."+ , Test "{.a,b}" ["b" =: s ""] "."+ , Test "{.%aa}" ["%aa" =: s "A"] ".A"+ , Test "{.%aa}" ["%aa" =: l ["A", "B"]] ".A,B"+ , Test "{.%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ".A,1,B,2"+ , Test "{.%aa*}" ["%aa" =: s "A"] ".A"+ , Test "{.%aa*}" ["%aa" =: l ["A", "B"]] ".A.B"+ , Test "{.%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ".A=1.B=2"+ , Test "{/a}" [] ""+ , Test "{/a}" ["a" =: l []] ""+ , Test "{/a}" ["a" =: d []] ""+ , Test "{/a}" ["a" =: s ""] "/"+ , Test "{/a}" ["a" =: s "A"] "/A"+ , Test "{/a}" ["a" =: s "~"] "/~"+ , Test "{/a}" ["a" =: s "%"] "/%25"+ , Test "{/a}" ["a" =: s "?"] "/%3F"+ , Test "{/a}" ["a" =: s "&"] "/%26"+ , Test "{/a}" ["a" =: s "\xa0"] "/%C2%A0"+ , Test "{/a}" ["a" =: s "\xd7ff"] "/%ED%9F%BF"+ , Test "{/a}" ["a" =: s "\x10000"] "/%F0%90%80%80"+ , Test "{/a}" ["a" =: l ["A"]] "/A"+ , Test "{/a}" ["a" =: l ["A", "B"]] "/A,B"+ , Test "{/a}" ["a" =: l ["%"]] "/%25"+ , Test "{/a}" ["a" =: l ["\xa0"]] "/%C2%A0"+ , Test "{/a}" ["a" =: d ["A" =: "1"]] "/A,1"+ , Test "{/a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "/A,1,B,2"+ , Test "{/a}" ["a" =: d ["A" =: "%"]] "/A,%25"+ , Test "{/a}" ["a" =: d ["A" =: "\xa0"]] "/A,%C2%A0"+ , Test "{/a}" ["a" =: d ["%" =: "1"]] "/%25,1"+ , Test "{/a*}" [] ""+ , Test "{/a*}" ["a" =: s ""] "/"+ , Test "{/a*}" ["a" =: s "A"] "/A"+ , Test "{/a*}" ["a" =: l []] ""+ , Test "{/a*}" ["a" =: l ["A"]] "/A"+ , Test "{/a*}" ["a" =: l ["A", "B"]] "/A/B"+ , Test "{/a*}" ["a" =: d []] ""+ , Test "{/a*}" ["a" =: d ["A" =: "1"]] "/A=1"+ , Test "{/a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "/A=1/B=2"+ , Test "{/a:1}" [] ""+ , Test "{/a:1}" ["a" =: s ""] "/"+ , Test "{/a:1}" ["a" =: s "A"] "/A"+ , Test "{/a:1}" ["a" =: s "AB"] "/A"+ , Test "{/a:1}" ["a" =: s "%B"] "/%25"+ , Test "{/a:1}" ["a" =: s "\xa0\&B"] "/%C2%A0"+ , Test "{/a:1}" ["a" =: s "\xd7ff\&B"] "/%ED%9F%BF"+ , Test "{/a:1}" ["a" =: s "\x10000\&B"] "/%F0%90%80%80"+ , Test "{/a:1}" ["a" =: l []] ""+ , Test "{/a:1}" ["a" =: l ["AB"]] "/AB"+ , Test "{/a:1}" ["a" =: l ["AB", "CD"]] "/AB,CD"+ , Test "{/a:1}" ["a" =: d []] ""+ , Test "{/a:1}" ["a" =: d ["AB" =: "12"]] "/AB,12"+ , Test "{/a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "/AB,12,CD,34"+ , Test "{/a,a}" [] ""+ , Test "{/a,a}" ["a" =: l []] ""+ , Test "{/a,a}" ["a" =: d []] ""+ , Test "{/a,a}" ["a" =: s ""] "//"+ , Test "{/a,b}" ["a" =: s ""] "/"+ , Test "{/a,b}" ["b" =: s ""] "/"+ , Test "{/%aa}" ["%aa" =: s "A"] "/A"+ , Test "{/%aa}" ["%aa" =: l ["A", "B"]] "/A,B"+ , Test "{/%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "/A,1,B,2"+ , Test "{/%aa*}" ["%aa" =: s "A"] "/A"+ , Test "{/%aa*}" ["%aa" =: l ["A", "B"]] "/A/B"+ , Test "{/%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "/A=1/B=2"+ , Test "{;a}" [] ""+ , Test "{;a}" ["a" =: l []] ""+ , Test "{;a}" ["a" =: d []] ""+ , Test "{;a}" ["a" =: s ""] ";a"+ , Test "{;a}" ["a" =: s "A"] ";a=A"+ , Test "{;a}" ["a" =: s "~"] ";a=~"+ , Test "{;a}" ["a" =: s "%"] ";a=%25"+ , Test "{;a}" ["a" =: s "?"] ";a=%3F"+ , Test "{;a}" ["a" =: s "&"] ";a=%26"+ , Test "{;a}" ["a" =: s "\xa0"] ";a=%C2%A0"+ , Test "{;a}" ["a" =: s "\xd7ff"] ";a=%ED%9F%BF"+ , Test "{;a}" ["a" =: s "\x10000"] ";a=%F0%90%80%80"+ , Test "{;a}" ["a" =: l ["A"]] ";a=A"+ , Test "{;a}" ["a" =: l ["A", "B"]] ";a=A,B"+ , Test "{;a}" ["a" =: l ["%"]] ";a=%25"+ , Test "{;a}" ["a" =: l ["\xa0"]] ";a=%C2%A0"+ , Test "{;a}" ["a" =: d ["A" =: "1"]] ";a=A,1"+ , Test "{;a}" ["a" =: d ["A" =: "1", "B" =: "2"]] ";a=A,1,B,2"+ , Test "{;a}" ["a" =: d ["A" =: "%"]] ";a=A,%25"+ , Test "{;a}" ["a" =: d ["A" =: "\xa0"]] ";a=A,%C2%A0"+ , Test "{;a}" ["a" =: d ["%" =: "1"]] ";a=%25,1"+ , Test "{;a*}" [] ""+ , Test "{;a*}" ["a" =: s ""] ";a"+ , Test "{;a*}" ["a" =: s "A"] ";a=A"+ , Test "{;a*}" ["a" =: l []] ""+ , Test "{;a*}" ["a" =: l ["A"]] ";a=A"+ , Test "{;a*}" ["a" =: l ["A", "B"]] ";a=A;a=B"+ , Test "{;a*}" ["a" =: d []] ""+ , Test "{;a*}" ["a" =: d ["A" =: "1"]] ";A=1"+ , Test "{;a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] ";A=1;B=2"+ , Test "{;a:1}" [] ""+ , Test "{;a:1}" ["a" =: s ""] ";a"+ , Test "{;a:1}" ["a" =: s "A"] ";a=A"+ , Test "{;a:1}" ["a" =: s "AB"] ";a=A"+ , Test "{;a:1}" ["a" =: s "%B"] ";a=%25"+ , Test "{;a:1}" ["a" =: s "\xa0\&B"] ";a=%C2%A0"+ , Test "{;a:1}" ["a" =: s "\xd7ff\&B"] ";a=%ED%9F%BF"+ , Test "{;a:1}" ["a" =: s "\x10000\&B"] ";a=%F0%90%80%80"+ , Test "{;a:1}" ["a" =: l []] ""+ , Test "{;a:1}" ["a" =: l ["AB"]] ";a=AB"+ , Test "{;a:1}" ["a" =: l ["AB", "CD"]] ";a=AB,CD"+ , Test "{;a:1}" ["a" =: d []] ""+ , Test "{;a:1}" ["a" =: d ["AB" =: "12"]] ";a=AB,12"+ , Test "{;a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] ";a=AB,12,CD,34"+ , Test "{;a,a}" [] ""+ , Test "{;a,a}" ["a" =: l []] ""+ , Test "{;a,a}" ["a" =: d []] ""+ , Test "{;a,a}" ["a" =: s ""] ";a;a"+ , Test "{;a,b}" ["a" =: s ""] ";a"+ , Test "{;a,b}" ["b" =: s ""] ";b"+ , Test "{;%aa}" ["%aa" =: s "A"] ";%aa=A"+ , Test "{;%aa}" ["%aa" =: l ["A", "B"]] ";%aa=A,B"+ , Test "{;%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ";%aa=A,1,B,2"+ , Test "{;%aa*}" ["%aa" =: s "A"] ";%aa=A"+ , Test "{;%aa*}" ["%aa" =: l ["A", "B"]] ";%aa=A;%aa=B"+ , Test "{;%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ";A=1;B=2"+ , Test "{?a}" [] ""+ , Test "{?a}" ["a" =: l []] ""+ , Test "{?a}" ["a" =: d []] ""+ , Test "{?a}" ["a" =: s ""] "?a="+ , Test "{?a}" ["a" =: s "A"] "?a=A"+ , Test "{?a}" ["a" =: s "~"] "?a=~"+ , Test "{?a}" ["a" =: s "%"] "?a=%25"+ , Test "{?a}" ["a" =: s "?"] "?a=%3F"+ , Test "{?a}" ["a" =: s "&"] "?a=%26"+ , Test "{?a}" ["a" =: s "\xa0"] "?a=%C2%A0"+ , Test "{?a}" ["a" =: s "\xd7ff"] "?a=%ED%9F%BF"+ , Test "{?a}" ["a" =: s "\x10000"] "?a=%F0%90%80%80"+ , Test "{?a}" ["a" =: l ["A"]] "?a=A"+ , Test "{?a}" ["a" =: l ["A", "B"]] "?a=A,B"+ , Test "{?a}" ["a" =: l ["%"]] "?a=%25"+ , Test "{?a}" ["a" =: l ["\xa0"]] "?a=%C2%A0"+ , Test "{?a}" ["a" =: d ["A" =: "1"]] "?a=A,1"+ , Test "{?a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "?a=A,1,B,2"+ , Test "{?a}" ["a" =: d ["A" =: "%"]] "?a=A,%25"+ , Test "{?a}" ["a" =: d ["A" =: "\xa0"]] "?a=A,%C2%A0"+ , Test "{?a}" ["a" =: d ["%" =: "1"]] "?a=%25,1"+ , Test "{?a*}" [] ""+ , Test "{?a*}" ["a" =: s ""] "?a="+ , Test "{?a*}" ["a" =: s "A"] "?a=A"+ , Test "{?a*}" ["a" =: l []] ""+ , Test "{?a*}" ["a" =: l ["A"]] "?a=A"+ , Test "{?a*}" ["a" =: l ["A", "B"]] "?a=A&a=B"+ , Test "{?a*}" ["a" =: d []] ""+ , Test "{?a*}" ["a" =: d ["A" =: "1"]] "?A=1"+ , Test "{?a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "?A=1&B=2"+ , Test "{?a:1}" [] ""+ , Test "{?a:1}" ["a" =: s ""] "?a="+ , Test "{?a:1}" ["a" =: s "A"] "?a=A"+ , Test "{?a:1}" ["a" =: s "AB"] "?a=A"+ , Test "{?a:1}" ["a" =: s "%B"] "?a=%25"+ , Test "{?a:1}" ["a" =: s "\xa0\&B"] "?a=%C2%A0"+ , Test "{?a:1}" ["a" =: s "\xd7ff\&B"] "?a=%ED%9F%BF"+ , Test "{?a:1}" ["a" =: s "\x10000\&B"] "?a=%F0%90%80%80"+ , Test "{?a:1}" ["a" =: l []] ""+ , Test "{?a:1}" ["a" =: l ["AB"]] "?a=AB"+ , Test "{?a:1}" ["a" =: l ["AB", "CD"]] "?a=AB,CD"+ , Test "{?a:1}" ["a" =: d []] ""+ , Test "{?a:1}" ["a" =: d ["AB" =: "12"]] "?a=AB,12"+ , Test "{?a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "?a=AB,12,CD,34"+ , Test "{?a,a}" [] ""+ , Test "{?a,a}" ["a" =: l []] ""+ , Test "{?a,a}" ["a" =: d []] ""+ , Test "{?a,a}" ["a" =: s ""] "?a=&a="+ , Test "{?a,b}" ["a" =: s ""] "?a="+ , Test "{?a,b}" ["b" =: s ""] "?b="+ , Test "{?%aa}" ["%aa" =: s "A"] "?%aa=A"+ , Test "{?%aa}" ["%aa" =: l ["A", "B"]] "?%aa=A,B"+ , Test "{?%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "?%aa=A,1,B,2"+ , Test "{?%aa*}" ["%aa" =: s "A"] "?%aa=A"+ , Test "{?%aa*}" ["%aa" =: l ["A", "B"]] "?%aa=A&%aa=B"+ , Test "{?%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "?A=1&B=2"+ , Test "{&a}" [] ""+ , Test "{&a}" ["a" =: l []] ""+ , Test "{&a}" ["a" =: d []] ""+ , Test "{&a}" ["a" =: s ""] "&a="+ , Test "{&a}" ["a" =: s "A"] "&a=A"+ , Test "{&a}" ["a" =: s "~"] "&a=~"+ , Test "{&a}" ["a" =: s "%"] "&a=%25"+ , Test "{&a}" ["a" =: s "?"] "&a=%3F"+ , Test "{&a}" ["a" =: s "&"] "&a=%26"+ , Test "{&a}" ["a" =: s "\xa0"] "&a=%C2%A0"+ , Test "{&a}" ["a" =: s "\xd7ff"] "&a=%ED%9F%BF"+ , Test "{&a}" ["a" =: s "\x10000"] "&a=%F0%90%80%80"+ , Test "{&a}" ["a" =: l ["A"]] "&a=A"+ , Test "{&a}" ["a" =: l ["A", "B"]] "&a=A,B"+ , Test "{&a}" ["a" =: l ["%"]] "&a=%25"+ , Test "{&a}" ["a" =: l ["\xa0"]] "&a=%C2%A0"+ , Test "{&a}" ["a" =: d ["A" =: "1"]] "&a=A,1"+ , Test "{&a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "&a=A,1,B,2"+ , Test "{&a}" ["a" =: d ["A" =: "%"]] "&a=A,%25"+ , Test "{&a}" ["a" =: d ["A" =: "\xa0"]] "&a=A,%C2%A0"+ , Test "{&a}" ["a" =: d ["%" =: "1"]] "&a=%25,1"+ , Test "{&a*}" [] ""+ , Test "{&a*}" ["a" =: s ""] "&a="+ , Test "{&a*}" ["a" =: s "A"] "&a=A"+ , Test "{&a*}" ["a" =: l []] ""+ , Test "{&a*}" ["a" =: l ["A"]] "&a=A"+ , Test "{&a*}" ["a" =: l ["A", "B"]] "&a=A&a=B"+ , Test "{&a*}" ["a" =: d []] ""+ , Test "{&a*}" ["a" =: d ["A" =: "1"]] "&A=1"+ , Test "{&a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "&A=1&B=2"+ , Test "{&a:1}" [] ""+ , Test "{&a:1}" ["a" =: s ""] "&a="+ , Test "{&a:1}" ["a" =: s "A"] "&a=A"+ , Test "{&a:1}" ["a" =: s "AB"] "&a=A"+ , Test "{&a:1}" ["a" =: s "%B"] "&a=%25"+ , Test "{&a:1}" ["a" =: s "\xa0\&B"] "&a=%C2%A0"+ , Test "{&a:1}" ["a" =: s "\xd7ff\&B"] "&a=%ED%9F%BF"+ , Test "{&a:1}" ["a" =: s "\x10000\&B"] "&a=%F0%90%80%80"+ , Test "{&a:1}" ["a" =: l []] ""+ , Test "{&a:1}" ["a" =: l ["AB"]] "&a=AB"+ , Test "{&a:1}" ["a" =: l ["AB", "CD"]] "&a=AB,CD"+ , Test "{&a:1}" ["a" =: d []] ""+ , Test "{&a:1}" ["a" =: d ["AB" =: "12"]] "&a=AB,12"+ , Test "{&a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "&a=AB,12,CD,34"+ , Test "{&a,a}" [] ""+ , Test "{&a,a}" ["a" =: l []] ""+ , Test "{&a,a}" ["a" =: d []] ""+ , Test "{&a,a}" ["a" =: s ""] "&a=&a="+ , Test "{&a,b}" ["a" =: s ""] "&a="+ , Test "{&a,b}" ["b" =: s ""] "&b="+ , Test "{&%aa}" ["%aa" =: s "A"] "&%aa=A"+ , Test "{&%aa}" ["%aa" =: l ["A", "B"]] "&%aa=A,B"+ , Test "{&%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "&%aa=A,1,B,2"+ , Test "{&%aa*}" ["%aa" =: s "A"] "&%aa=A"+ , Test "{&%aa*}" ["%aa" =: l ["A", "B"]] "&%aa=A&%aa=B"+ , Test "{&%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "&A=1&B=2"+ ]+ , let values = ["%AA" =: s "1", "%Aa" =: s "2", "%aA" =: s "3", "%aa" =: s "4"]+ in+ [ Test "{%AA}" values "1"+ , Test "{%Aa}" values "2"+ , Test "{%aA}" values "3"+ , Test "{%aa}" values "4"+ -- This comment forces Brittany to use a multi-line layout.+ ]+ , let values = ["a" =: s "abcdefghijklmnopqrstuvwxyz"]+ in+ [ Test "{a:1}" values "a"+ , Test "{a:5}" values "abcde"+ , Test "{a:10}" values "abcdefghij"+ , Test "{a:15}" values "abcdefghijklmno"+ , Test "{a:20}" values "abcdefghijklmnopqrst"+ , Test "{a:25}" values "abcdefghijklmnopqrstuvwxy"+ , Test "{a:26}" values "abcdefghijklmnopqrstuvwxyz"+ , Test "{a:30}" values "abcdefghijklmnopqrstuvwxyz"+ ]+ , let values = ["a" =: l []]+ in+ [ Test "{a}" values ""+ , Test "{+a}" values ""+ , Test "{#a}" values ""+ , Test "{.a}" values ""+ , Test "{/a}" values ""+ , Test "{;a}" values ""+ , Test "{?a}" values ""+ , Test "{&a}" values ""+ ]+ , let values = ["a" =: d []]+ in+ [ Test "{a}" values ""+ , Test "{+a}" values ""+ , Test "{#a}" values ""+ , Test "{.a}" values ""+ , Test "{/a}" values ""+ , Test "{;a}" values ""+ , Test "{?a}" values ""+ , Test "{&a}" values ""+ ]+ , let values = ["a" =: s ""]+ in+ [ Test "{a}" values ""+ , Test "{+a}" values ""+ , Test "{#a}" values "#"+ , Test "{.a}" values "."+ , Test "{/a}" values "/"+ , Test "{;a}" values ";a"+ , Test "{?a}" values "?a="+ , Test "{&a}" values "&a="+ ]+ , let values = ["a" =: s "A"]+ in+ [ Test "{a}" values "A"+ , Test "{+a}" values "A"+ , Test "{#a}" values "#A"+ , Test "{.a}" values ".A"+ , Test "{/a}" values "/A"+ , Test "{;a}" values ";a=A"+ , Test "{?a}" values "?a=A"+ , Test "{&a}" values "&a=A"+ ]+ , let values = ["b" =: s "B"]+ in+ [ Test "{a,b}" values "B"+ , Test "{+a,b}" values "B"+ , Test "{#a,b}" values "#B"+ , Test "{.a,b}" values ".B"+ , Test "{/a,b}" values "/B"+ , Test "{;a,b}" values ";b=B"+ , Test "{?a,b}" values "?b=B"+ , Test "{&a,b}" values "&b=B"+ ]+ , let values = ["a" =: s ""]+ in+ [ Test "{a,a}" values ","+ , Test "{+a,a}" values ","+ , Test "{#a,a}" values "#,"+ , Test "{.a,a}" values ".."+ , Test "{/a,a}" values "//"+ , Test "{;a,a}" values ";a;a"+ , Test "{?a,a}" values "?a=&a="+ , Test "{&a,a}" values "&a=&a="+ ]+ , let values = ["a" =: s "A", "b" =: s "B"]+ in+ [ Test "{a,b}" values "A,B"+ , Test "{+a,b}" values "A,B"+ , Test "{#a,b}" values "#A,B"+ , Test "{.a,b}" values ".A.B"+ , Test "{/a,b}" values "/A/B"+ , Test "{;a,b}" values ";a=A;b=B"+ , Test "{?a,b}" values "?a=A&b=B"+ , Test "{&a,b}" values "&a=A&b=B"+ ]+ , let values = ["a" =: d ["K! \xa0\xd7ff\x10000" =: "V! \xa0\xd7ff\x10000"]]+ in+ [ Test "{a*}" values "K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{+a*}" values "K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{#a*}" values "#K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{.a*}" values ".K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{/a*}" values "/K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{;a*}" values ";K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{?a*}" values "?K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"+ , Test "{&a*}" values "&K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"+ ]+ , let+ values =+ [ "empty" =: s ""+ , "hello" =: s "Hello World!"+ , "keys" =: d ["semi" =: ";", "dot" =: ".", "comma" =: ","]+ , "list" =: l ["red", "green", "blue"]+ , "path" =: s "/foo/bar"+ , "var" =: s "value"+ , "x" =: s "1024"+ , "y" =: s "768"+ ]+ in+ [ Test "{var}" values "value"+ , Test "{hello}" values "Hello%20World%21"+ , Test "{+var}" values "value"+ , Test "{+hello}" values "Hello%20World!"+ , Test "{+path}/here" values "/foo/bar/here"+ , Test "here?ref={+path}" values "here?ref=/foo/bar"+ , Test "X{#var}" values "X#value"+ , Test "X{#hello}" values "X#Hello%20World!"+ , Test "map?{x,y}" values "map?1024,768"+ , Test "{x,hello,y}" values "1024,Hello%20World%21,768"+ , Test "{+x,hello,y}" values "1024,Hello%20World!,768"+ , Test "{+path,x}/here" values "/foo/bar,1024/here"+ , Test "{#x,hello,y}" values "#1024,Hello%20World!,768"+ , Test "{#path,x}/here" values "#/foo/bar,1024/here"+ , Test "X{.var}" values "X.value"+ , Test "X{.x,y}" values "X.1024.768"+ , Test "{/var}" values "/value"+ , Test "{/var,x}/here" values "/value/1024/here"+ , Test "{;x,y}" values ";x=1024;y=768"+ , Test "{;x,y,empty}" values ";x=1024;y=768;empty"+ , Test "{?x,y}" values "?x=1024&y=768"+ , Test "{?x,y,empty}" values "?x=1024&y=768&empty="+ , Test "?fixed=yes{&x}" values "?fixed=yes&x=1024"+ , Test "{&x,y,empty}" values "&x=1024&y=768&empty="+ , Test "{var:3}" values "val"+ , Test "{var:30}" values "value"+ , Test "{list}" values "red,green,blue"+ , Test "{list*}" values "red,green,blue"+ , Test "{keys}" values "comma,%2C,dot,.,semi,%3B"+ , Test "{keys*}" values "comma=%2C,dot=.,semi=%3B"+ , Test "{+path:6}/here" values "/foo/b/here"+ , Test "{+list}" values "red,green,blue"+ , Test "{+list*}" values "red,green,blue"+ , Test "{+keys}" values "comma,,,dot,.,semi,;"+ , Test "{+keys*}" values "comma=,,dot=.,semi=;"+ , Test "{#path:6}/here" values "#/foo/b/here"+ , Test "{#list}" values "#red,green,blue"+ , Test "{#list*}" values "#red,green,blue"+ , Test "{#keys}" values "#comma,,,dot,.,semi,;"+ , Test "{#keys*}" values "#comma=,,dot=.,semi=;"+ , Test "X{.var:3}" values "X.val"+ , Test "X{.list}" values "X.red,green,blue"+ , Test "X{.list*}" values "X.red.green.blue"+ , Test "X{.keys}" values "X.comma,%2C,dot,.,semi,%3B"+ , Test "X{.keys*}" values "X.comma=%2C.dot=..semi=%3B"+ , Test "{/var:1,var}" values "/v/value"+ , Test "{/list}" values "/red,green,blue"+ , Test "{/list*}" values "/red/green/blue"+ , Test "{/list*,path:4}" values "/red/green/blue/%2Ffoo"+ , Test "{/keys}" values "/comma,%2C,dot,.,semi,%3B"+ , Test "{/keys*}" values "/comma=%2C/dot=./semi=%3B"+ , Test "{;hello:5}" values ";hello=Hello"+ , Test "{;list}" values ";list=red,green,blue"+ , Test "{;list*}" values ";list=red;list=green;list=blue"+ , Test "{;keys}" values ";keys=comma,%2C,dot,.,semi,%3B"+ , Test "{;keys*}" values ";comma=%2C;dot=.;semi=%3B"+ , Test "{?var:3}" values "?var=val"+ , Test "{?list}" values "?list=red,green,blue"+ , Test "{?list*}" values "?list=red&list=green&list=blue"+ , Test "{?keys}" values "?keys=comma,%2C,dot,.,semi,%3B"+ , Test "{?keys*}" values "?comma=%2C&dot=.&semi=%3B"+ , Test "{&var:3}" values "&var=val"+ , Test "{&list}" values "&list=red,green,blue"+ , Test "{&list*}" values "&list=red&list=green&list=blue"+ , Test "{&keys}" values "&keys=comma,%2C,dot,.,semi,%3B"+ , Test "{&keys*}" values "&comma=%2C&dot=.&semi=%3B"+ ]+ , let values = ["var" =: s "value", "semi" =: s ";"]+ in+ [ Test "{var}" values "value"+ , Test "{var:20}" values "value"+ , Test "{var:3}" values "val"+ , Test "{semi}" values "%3B"+ , Test "{semi:2}" values "%3B"+ -- This comment forces Brittany to use a multi-line layout.+ ]+ , let values = ["year" =: l ["1965", "2000", "2012"], "dom" =: l ["example", "com"]]+ in+ [ Test "find{?year*}" values "find?year=1965&year=2000&year=2012"+ , Test "www{.dom*}" values "www.example.com"+ -- This comment forces Brittany to use a multi-line layout.+ ]+ , let+ values =+ [ "base" =: s "http://example.com/home/"+ , "count" =: l ["one", "two", "three"]+ , "dom" =: l ["example", "com"]+ , "dub" =: s "me/too"+ , "empty_keys" =: d []+ , "empty" =: s ""+ , "half" =: s "50%"+ , "hello" =: s "Hello World!"+ , "keys" =: d ["semi" =: ";", "dot" =: ".", "comma" =: ","]+ , "list" =: l ["red", "green", "blue"]+ , "path" =: s "/foo/bar"+ , "v" =: s "6"+ , "var" =: s "value"+ , "who" =: s "fred"+ , "x" =: s "1024"+ , "y" =: s "768"+ ]+ in+ [ Test "{count}" values "one,two,three"+ , Test "{count*}" values "one,two,three"+ , Test "{/count}" values "/one,two,three"+ , Test "{/count*}" values "/one/two/three"+ , Test "{;count}" values ";count=one,two,three"+ , Test "{;count*}" values ";count=one;count=two;count=three"+ , Test "{?count}" values "?count=one,two,three"+ , Test "{?count*}" values "?count=one&count=two&count=three"+ , Test "{&count*}" values "&count=one&count=two&count=three"+ , Test "{var}" values "value"+ , Test "{hello}" values "Hello%20World%21"+ , Test "{half}" values "50%25"+ , Test "O{empty}X" values "OX"+ , Test "O{undef}X" values "OX"+ , Test "{x,y}" values "1024,768"+ , Test "{x,hello,y}" values "1024,Hello%20World%21,768"+ , Test "?{x,empty}" values "?1024,"+ , Test "?{x,undef}" values "?1024"+ , Test "?{undef,y}" values "?768"+ , Test "{var:3}" values "val"+ , Test "{var:30}" values "value"+ , Test "{list}" values "red,green,blue"+ , Test "{list*}" values "red,green,blue"+ , Test "{keys}" values "comma,%2C,dot,.,semi,%3B"+ , Test "{keys*}" values "comma=%2C,dot=.,semi=%3B"+ , Test "{+var}" values "value"+ , Test "{+hello}" values "Hello%20World!"+ , Test "{+half}" values "50%25"+ , Test "{base}index" values "http%3A%2F%2Fexample.com%2Fhome%2Findex"+ , Test "{+base}index" values "http://example.com/home/index"+ , Test "O{+empty}X" values "OX"+ , Test "O{+undef}X" values "OX"+ , Test "{+path}/here" values "/foo/bar/here"+ , Test "here?ref={+path}" values "here?ref=/foo/bar"+ , Test "up{+path}{var}/here" values "up/foo/barvalue/here"+ , Test "{+x,hello,y}" values "1024,Hello%20World!,768"+ , Test "{+path,x}/here" values "/foo/bar,1024/here"+ , Test "{+path:6}/here" values "/foo/b/here"+ , Test "{+list}" values "red,green,blue"+ , Test "{+list*}" values "red,green,blue"+ , Test "{+keys}" values "comma,,,dot,.,semi,;"+ , Test "{+keys*}" values "comma=,,dot=.,semi=;"+ , Test "{#var}" values "#value"+ , Test "{#hello}" values "#Hello%20World!"+ , Test "{#half}" values "#50%25"+ , Test "foo{#empty}" values "foo#"+ , Test "foo{#undef}" values "foo"+ , Test "{#x,hello,y}" values "#1024,Hello%20World!,768"+ , Test "{#path,x}/here" values "#/foo/bar,1024/here"+ , Test "{#path:6}/here" values "#/foo/b/here"+ , Test "{#list}" values "#red,green,blue"+ , Test "{#list*}" values "#red,green,blue"+ , Test "{#keys}" values "#comma,,,dot,.,semi,;"+ , Test "{#keys*}" values "#comma=,,dot=.,semi=;"+ , Test "{.who}" values ".fred"+ , Test "{.who,who}" values ".fred.fred"+ , Test "{.half,who}" values ".50%25.fred"+ , Test "www{.dom*}" values "www.example.com"+ , Test "X{.var}" values "X.value"+ , Test "X{.empty}" values "X."+ , Test "X{.undef}" values "X"+ , Test "X{.var:3}" values "X.val"+ , Test "X{.list}" values "X.red,green,blue"+ , Test "X{.list*}" values "X.red.green.blue"+ , Test "X{.keys}" values "X.comma,%2C,dot,.,semi,%3B"+ , Test "X{.keys*}" values "X.comma=%2C.dot=..semi=%3B"+ , Test "X{.empty_keys}" values "X"+ , Test "X{.empty_keys*}" values "X"+ , Test "{/who}" values "/fred"+ , Test "{/who,who}" values "/fred/fred"+ , Test "{/half,who}" values "/50%25/fred"+ , Test "{/who,dub}" values "/fred/me%2Ftoo"+ , Test "{/var}" values "/value"+ , Test "{/var,empty}" values "/value/"+ , Test "{/var,undef}" values "/value"+ , Test "{/var,x}/here" values "/value/1024/here"+ , Test "{/var:1,var}" values "/v/value"+ , Test "{/list}" values "/red,green,blue"+ , Test "{/list*}" values "/red/green/blue"+ , Test "{/list*,path:4}" values "/red/green/blue/%2Ffoo"+ , Test "{/keys}" values "/comma,%2C,dot,.,semi,%3B"+ , Test "{/keys*}" values "/comma=%2C/dot=./semi=%3B"+ , Test "{;who}" values ";who=fred"+ , Test "{;half}" values ";half=50%25"+ , Test "{;empty}" values ";empty"+ , Test "{;v,empty,who}" values ";v=6;empty;who=fred"+ , Test "{;v,bar,who}" values ";v=6;who=fred"+ , Test "{;x,y}" values ";x=1024;y=768"+ , Test "{;x,y,empty}" values ";x=1024;y=768;empty"+ , Test "{;x,y,undef}" values ";x=1024;y=768"+ , Test "{;hello:5}" values ";hello=Hello"+ , Test "{;list}" values ";list=red,green,blue"+ , Test "{;list*}" values ";list=red;list=green;list=blue"+ , Test "{;keys}" values ";keys=comma,%2C,dot,.,semi,%3B"+ , Test "{;keys*}" values ";comma=%2C;dot=.;semi=%3B"+ , Test "{?who}" values "?who=fred"+ , Test "{?half}" values "?half=50%25"+ , Test "{?x,y}" values "?x=1024&y=768"+ , Test "{?x,y,empty}" values "?x=1024&y=768&empty="+ , Test "{?x,y,undef}" values "?x=1024&y=768"+ , Test "{?var:3}" values "?var=val"+ , Test "{?list}" values "?list=red,green,blue"+ , Test "{?list*}" values "?list=red&list=green&list=blue"+ , Test "{?keys}" values "?keys=comma,%2C,dot,.,semi,%3B"+ , Test "{?keys*}" values "?comma=%2C&dot=.&semi=%3B"+ , Test "{&who}" values "&who=fred"+ , Test "{&half}" values "&half=50%25"+ , Test "?fixed=yes{&x}" values "?fixed=yes&x=1024"+ , Test "{&x,y,empty}" values "&x=1024&y=768&empty="+ , Test "{&x,y,undef}" values "&x=1024&y=768"+ , Test "{&var:3}" values "&var=val"+ , Test "{&list}" values "&list=red,green,blue"+ , Test "{&list*}" values "&list=red&list=green&list=blue"+ , Test "{&keys}" values "&keys=comma,%2C,dot,.,semi,%3B"+ , Test "{&keys*}" values "&comma=%2C&dot=.&semi=%3B"+ ]+ ]++data Test = Test+ { testInput :: String+ , testValues :: [(String, Burrito.Value)]+ , testOutput :: Output+ }+ deriving (Eq, Show)++runTest :: Test -> Hspec.Expectation+runTest test =+ case (Burrito.parse $ testInput test, unwrapOutput $ testOutput test) of+ (Nothing, Nothing) -> pure ()+ (Nothing, Just _) -> Hspec.expectationFailure "should have parsed"+ (Just _, Nothing) -> Hspec.expectationFailure "should not have parsed"+ (Just template, Just expected) -> do+ let+ values = testValues test+ actual = Burrito.expand values template+ actual `Hspec.shouldBe` expected+ Burrito.parse (show template) `Hspec.shouldBe` Just template+ let+ relevant =+ List.sort $ keepRelevant (templateVariables template) values+ Monad.when (isMatchable template relevant) $ do+ let matches = List.sort <$> Burrito.match expected template+ matches `Hspec.shouldSatisfy` elem relevant++isMatchable :: Template.Template -> [(String, Burrito.Value)] -> Bool+isMatchable template values =+ (not . any (isAsterisk . Variable.modifier) $ templateVariables template)+ && all (isString . snd) values++isString :: Burrito.Value -> Bool+isString value = case value of+ Value.String _ -> True+ _ -> False++isAsterisk :: Modifier.Modifier -> Bool+isAsterisk modifier = case modifier of+ Modifier.Asterisk -> True+ _ -> False++keepRelevant+ :: Set.Set Variable.Variable+ -> [(String, Burrito.Value)]+ -> [(String, Burrito.Value)]+keepRelevant variables =+ let+ vs =+ Map.fromListWith+ (\mx my -> case (mx, my) of+ (Just x, Just y) -> Just $ max x y+ _ -> Nothing+ )+ . fmap+ (\v ->+ ( Render.builderToString . Render.name $ Variable.name v+ , case Variable.modifier v of+ Modifier.Colon n -> Just $ MaxLength.count n+ _ -> Nothing+ )+ )+ $ Set.toList variables+ in+ Maybe.mapMaybe $ \(k, v) -> do+ m <- Map.lookup k vs+ pure . (,) k $ case m of+ Nothing -> v+ Just n -> case v of+ Value.String t -> Value.String $ Text.take n t+ _ -> v++templateVariables :: Template.Template -> Set.Set Variable.Variable+templateVariables =+ Set.fromList+ . concatMap+ (\token -> case token of+ Token.Expression expression ->+ NonEmpty.toList $ Expression.variables expression+ Token.Literal _ -> []+ )+ . Template.tokens++newtype Output = Output+ { unwrapOutput :: Maybe String+ } deriving (Eq, Show)++instance String.IsString Output where+ fromString = Output . Just++noParse :: Output+noParse = Output Nothing++(=:) :: a -> b -> (a, b)+(=:) = (,)++s :: String -> Burrito.Value+s = Burrito.stringValue++l :: [String] -> Burrito.Value+l = Burrito.listValue++d :: [(String, String)] -> Burrito.Value+d = Burrito.dictionaryValue++newtype Template = Template+ { unwrapTemplate :: Template.Template+ } deriving (Eq)++instance Show Template where+ show (Template template) =+ unwords [show $ show template, "{-", show (Template.tokens template), "-}"]++instance QC.Arbitrary Template where+ arbitrary = Template <$> arbitraryTemplate+ shrink = fmap Template . shrinkTemplate . unwrapTemplate++type Shrink a = a -> [a]++arbitraryTemplate :: QC.Gen Template.Template+arbitraryTemplate = Template.Template . simplify <$> QC.listOf arbitraryToken++shrinkTemplate :: Shrink Template.Template+shrinkTemplate =+ fmap (Template.Template . simplify)+ . QC.shrinkList shrinkToken+ . Template.tokens++simplify :: [Token.Token] -> [Token.Token]+simplify tokens = case tokens of+ t1 : t2 : ts -> case (t1, t2) of+ (Token.Literal l1, Token.Literal l2) ->+ simplify $ Token.Literal (appendLiteral l1 l2) : ts+ _ -> t1 : simplify (t2 : ts)+ _ -> tokens++appendLiteral :: Literal.Literal -> Literal.Literal -> Literal.Literal+appendLiteral x y =+ Literal.Literal $ Literal.characters x <> Literal.characters y++arbitraryToken :: QC.Gen Token.Token+arbitraryToken = QC.oneof+ [ Token.Expression <$> arbitraryExpression+ , Token.Literal <$> arbitraryLiteral+ ]++shrinkToken :: Shrink Token.Token+shrinkToken x = case x of+ Token.Expression y -> Token.Expression <$> shrinkExpression y+ Token.Literal y -> Token.Literal <$> shrinkLiteral y++arbitraryExpression :: QC.Gen Expression.Expression+arbitraryExpression =+ Expression.Expression+ <$> arbitraryOperator+ <*> arbitraryNonEmpty arbitraryVariable++shrinkExpression :: Shrink Expression.Expression+shrinkExpression x = uncurry Expression.Expression <$> shrinkTuple+ shrinkOperator+ (shrinkNonEmpty shrinkVariable)+ (Expression.operator x, Expression.variables x)++arbitraryOperator :: QC.Gen Operator.Operator+arbitraryOperator = QC.elements+ [ Operator.Ampersand+ , Operator.FullStop+ , Operator.None+ , Operator.NumberSign+ , Operator.PlusSign+ , Operator.QuestionMark+ , Operator.Semicolon+ , Operator.Solidus+ ]++shrinkOperator :: Shrink Operator.Operator+shrinkOperator x = case x of+ Operator.None -> []+ _ -> [Operator.None]++arbitraryNonEmpty :: QC.Gen a -> QC.Gen (NonEmpty.NonEmpty a)+arbitraryNonEmpty g = (NonEmpty.:|) <$> g <*> QC.listOf g++shrinkNonEmpty :: Shrink a -> Shrink (NonEmpty.NonEmpty a)+shrinkNonEmpty f x = uncurry (NonEmpty.:|)+ <$> shrinkTuple f (QC.shrinkList f) (NonEmpty.head x, NonEmpty.tail x)++arbitraryVariable :: QC.Gen Variable.Variable+arbitraryVariable = Variable.Variable <$> arbitraryName <*> arbitraryModifier++shrinkVariable :: Shrink Variable.Variable+shrinkVariable variable = uncurry Variable.Variable <$> shrinkTuple+ shrinkName+ shrinkModifier+ (Variable.name variable, Variable.modifier variable)++shrinkTuple :: Shrink a -> Shrink b -> Shrink (a, b)+shrinkTuple f g (x, y) =+ fmap (\a -> (a, y)) (f x) <> fmap (\b -> (x, b)) (g y)++arbitraryName :: QC.Gen Name.Name+arbitraryName = Name.Name <$> arbitraryNonEmpty arbitraryField++shrinkName :: Shrink Name.Name+shrinkName = fmap Name.Name . shrinkNonEmpty shrinkField . Name.fields++arbitraryField :: QC.Gen Field.Field+arbitraryField = Field.Field <$> arbitraryNonEmpty arbitraryFieldCharacter++shrinkField :: Shrink Field.Field+shrinkField =+ fmap Field.Field . shrinkNonEmpty shrinkFieldCharacter . Field.characters++arbitraryFieldCharacter :: QC.Gen (Character.Character Field.Field)+arbitraryFieldCharacter = QC.oneof+ [ Character.Encoded <$> arbitraryDigit <*> arbitraryDigit+ , Character.Unencoded <$> QC.suchThat QC.arbitrary Parse.isFieldCharacter+ ]++shrinkFieldCharacter :: Shrink (Character.Character Field.Field)+shrinkFieldCharacter x = case x of+ Character.Encoded y z ->+ uncurry Character.Encoded <$> shrinkTuple shrinkDigit shrinkDigit (y, z)+ Character.Unencoded y ->+ fmap Character.Unencoded . filter Parse.isFieldCharacter $ QC.shrink y++arbitraryDigit :: QC.Gen Digit.Digit+arbitraryDigit = QC.oneof+ [ pure Digit.Ox0+ , pure Digit.Ox1+ , pure Digit.Ox2+ , pure Digit.Ox3+ , pure Digit.Ox4+ , pure Digit.Ox5+ , pure Digit.Ox6+ , pure Digit.Ox7+ , pure Digit.Ox8+ , pure Digit.Ox9+ , Digit.OxA <$> arbitraryCase+ , Digit.OxB <$> arbitraryCase+ , Digit.OxC <$> arbitraryCase+ , Digit.OxD <$> arbitraryCase+ , Digit.OxE <$> arbitraryCase+ , Digit.OxF <$> arbitraryCase+ ]++shrinkDigit :: Shrink Digit.Digit+shrinkDigit x = case x of+ Digit.Ox0 -> []+ Digit.Ox1 -> [Digit.Ox0]+ Digit.Ox2 -> [Digit.Ox0]+ Digit.Ox3 -> [Digit.Ox0]+ Digit.Ox4 -> [Digit.Ox0]+ Digit.Ox5 -> [Digit.Ox0]+ Digit.Ox6 -> [Digit.Ox0]+ Digit.Ox7 -> [Digit.Ox0]+ Digit.Ox8 -> [Digit.Ox0]+ Digit.Ox9 -> [Digit.Ox0]+ Digit.OxA y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)+ Digit.OxB y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)+ Digit.OxC y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)+ Digit.OxD y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)+ Digit.OxE y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)+ Digit.OxF y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)++arbitraryCase :: QC.Gen Case.Case+arbitraryCase = QC.elements [Case.Lower, Case.Upper]++shrinkCase :: Shrink Case.Case+shrinkCase x = case x of+ Case.Lower -> []+ Case.Upper -> [Case.Lower]++arbitraryModifier :: QC.Gen Modifier.Modifier+arbitraryModifier = QC.oneof+ [ pure Modifier.Asterisk+ , Modifier.Colon <$> arbitraryMaxLength+ , pure Modifier.None+ ]++shrinkModifier :: Shrink Modifier.Modifier+shrinkModifier x = case x of+ Modifier.Asterisk -> [Modifier.None]+ Modifier.Colon y -> Modifier.None : fmap Modifier.Colon (shrinkMaxLength y)+ Modifier.None -> []++arbitraryMaxLength :: QC.Gen MaxLength.MaxLength+arbitraryMaxLength =+ MaxLength.MaxLength <$> QC.suchThat QC.arbitrary Parse.isMaxLength++shrinkMaxLength :: Shrink MaxLength.MaxLength+shrinkMaxLength =+ fmap MaxLength.MaxLength+ . filter Parse.isMaxLength+ . QC.shrink+ . MaxLength.count++arbitraryLiteral :: QC.Gen Literal.Literal+arbitraryLiteral =+ Literal.Literal <$> arbitraryNonEmpty arbitraryLiteralCharacter++shrinkLiteral :: Shrink Literal.Literal+shrinkLiteral =+ fmap Literal.Literal+ . shrinkNonEmpty shrinkLiteralCharacter+ . Literal.characters++arbitraryLiteralCharacter :: QC.Gen (Character.Character Literal.Literal)+arbitraryLiteralCharacter = QC.oneof+ [ Character.Encoded <$> arbitraryDigit <*> arbitraryDigit+ , Character.Unencoded <$> QC.suchThat QC.arbitrary Parse.isLiteralCharacter+ ]++shrinkLiteralCharacter :: Shrink (Character.Character Literal.Literal)+shrinkLiteralCharacter x = case x of+ Character.Encoded y z ->+ uncurry Character.Encoded <$> shrinkTuple shrinkDigit shrinkDigit (y, z)+ Character.Unencoded y ->+ fmap Character.Unencoded . filter Parse.isLiteralCharacter $ QC.shrink y
− src/lib/Burrito.hs
@@ -1,65 +0,0 @@--- | Burrito is a Haskell library for parsing and rendering URI templates.------ According to [RFC 6570](https://tools.ietf.org/html/rfc6570): "A URI--- Template is a compact sequence of characters for describing a range of- -- Uniform Resource Identifiers through variable expansion." Burrito- -- implements URI templates according to the specification in that RFC.------ The term "uniform resource identifiers" (URI) is often used interchangeably--- with other related terms like "internationalized resource identifier" (IRI),--- "uniform resource locator" (URL), and "uniform resource name" (URN). Burrito--- can be used for all of these. If you want to get technical, its input must--- be a valid IRI and its output will be a valid URI or URN.------ Although Burrito is primarily intended to be used with HTTP and HTTPS URIs,--- it should work with other schemes as well.------ If you're not already familiar with URI templates, I recommend reading the--- overview of the RFC. It's short, to the point, and easy to understand.------ Assuming you're familiar with URI templates, here's a simple example to show--- you how Burrito works:------ >>> import Burrito--- >>> let Just template = parse "http://example/search{?query}"--- >>> expand [("query", stringValue "chorizo")] template--- "http://example.com/search?query=chorizo"------ In short, use @parse@ to parse templates and @expand@ to render them.-module Burrito- ( Parse.parse- , Template.render- , Expand.expand- , Expand.expandWith- , Match.match- , TH.uriTemplate- , Template.Template- , Value.Value- , stringValue- , listValue- , dictionaryValue- )-where--import qualified Burrito.Internal.Expand as Expand-import qualified Burrito.Internal.Match as Match-import qualified Burrito.Internal.Parse as Parse-import qualified Burrito.Internal.TH as TH-import qualified Burrito.Internal.Type.Template as Template-import qualified Burrito.Internal.Type.Value as Value-import qualified Data.Bifunctor as Bifunctor-import qualified Data.Map as Map-import qualified Data.Text as Text---- | Constructs a string value.-stringValue :: String -> Value.Value-stringValue = Value.String . Text.pack---- | Constructs a list value.-listValue :: [String] -> Value.Value-listValue = Value.List . fmap Text.pack---- | Constructs a dictionary value.-dictionaryValue :: [(String, String)] -> Value.Value-dictionaryValue =- Value.Dictionary . Map.fromList . fmap (Bifunctor.bimap Text.pack Text.pack)
− src/lib/Burrito/Internal/Expand.hs
@@ -1,312 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--module Burrito.Internal.Expand where--import qualified Burrito.Internal.Render as Render-import qualified Burrito.Internal.Type.Character as Character-import qualified Burrito.Internal.Type.Digit as Digit-import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Field as Field-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Template as Template-import qualified Burrito.Internal.Type.Token as Token-import qualified Burrito.Internal.Type.Value as Value-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Control.Monad.Trans.Class as Trans-import qualified Control.Monad.Trans.State as State-import qualified Data.ByteString as ByteString-import qualified Data.Char as Char-import qualified Data.Functor.Identity as Identity-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Data.Text.Lazy as LazyText-import qualified Data.Text.Lazy.Builder as Builder---- | Expands a template using the given values. Unlike parsing, expansion--- always succeeds. If no value is given for a variable, it will simply not--- appear in the output.------ >>> expand [] <$> parse "valid-template"--- Just "valid-template"--- >>> expand [] <$> parse "template:{example}"--- Just "template:"--- >>> expand [("example", stringValue "true")] <$> parse "template:{example}"--- Just "template:true"-expand :: [(String, Value.Value)] -> Template.Template -> String-expand values =- let m = Map.mapKeys Text.pack $ Map.fromList values- in- Render.builderToString . Identity.runIdentity . expandWith- (pure . flip Map.lookup m)---- | This is like @expand@ except that it gives you more control over how--- variables are expanded. If you can, use @expand@. It's simpler.------ Instead of passing in a static mapping form names to--- values, you pass in a function that is used to look up values on the fly.--- This can be useful if computing values takes a while or requires some impure--- actions.------ >>> expandWith (\ x -> [Nothing, Just . stringValue $ unpack x]) <$> parse "template:{example}"--- Just ["template:","template:example"]--- >>> let Just template = parse "user={USER}"--- >>> expandWith (fmap (fmap stringValue) . lookupEnv . unpack) template--- "user=taylor"------ Note that as the RFC specifies, the given function will be called at most--- once for each variable in the template.------ >>> let Just template = parse "{a}{a}"--- >>> expandWith (\ x -> do { putStrLn $ "-- expanding " <> show x; pure . Just $ Burrito.stringValue "A" }) template--- -- expanding "a"--- "AA"-expandWith- :: Monad m- => (Text.Text -> m (Maybe Value.Value))- -> Template.Template- -> m Builder.Builder-expandWith f = flip State.evalStateT Map.empty . template (cached f)--type CacheT = State.StateT (Map.Map Text.Text (Maybe Value.Value))--cached- :: Monad m- => (Text.Text -> m (Maybe Value.Value))- -> Name.Name- -> CacheT m (Maybe Value.Value)-cached f x = do- let key = LazyText.toStrict . Builder.toLazyText $ name x- cache <- State.get- case Map.lookup key cache of- Just result -> pure result- Nothing -> do- result <- Trans.lift $ f key- State.modify $ Map.insert key result- pure result--template- :: Monad m- => (Name.Name -> CacheT m (Maybe Value.Value))- -> Template.Template- -> CacheT m Builder.Builder-template f = fmap mconcat . traverse (token f) . Template.tokens--token- :: Monad m- => (Name.Name -> CacheT m (Maybe Value.Value))- -> Token.Token- -> CacheT m Builder.Builder-token f x = case x of- Token.Expression y -> expression f y- Token.Literal y -> pure $ literal y--expression- :: Monad m- => (Name.Name -> CacheT m (Maybe Value.Value))- -> Expression.Expression- -> CacheT m Builder.Builder-expression f ex =- let op = Expression.operator ex- in- fmap- (mconcat- . (\xs -> if null xs then xs else prefix op : xs)- . List.intersperse (separator op)- . Maybe.catMaybes- )- . traverse (variable f op)- . NonEmpty.toList- $ Expression.variables ex--separator :: Operator.Operator -> Builder.Builder-separator op = Builder.singleton $ case op of- Operator.Ampersand -> '&'- Operator.FullStop -> '.'- Operator.None -> ','- Operator.NumberSign -> ','- Operator.PlusSign -> ','- Operator.QuestionMark -> '&'- Operator.Semicolon -> ';'- Operator.Solidus -> '/'--prefix :: Operator.Operator -> Builder.Builder-prefix op = case op of- Operator.Ampersand -> Builder.singleton '&'- Operator.FullStop -> Builder.singleton '.'- Operator.None -> mempty- Operator.NumberSign -> Builder.singleton '#'- Operator.PlusSign -> mempty- Operator.QuestionMark -> Builder.singleton '?'- Operator.Semicolon -> Builder.singleton ';'- Operator.Solidus -> Builder.singleton '/'--variable- :: Monad m- => (Name.Name -> CacheT m (Maybe Value.Value))- -> Operator.Operator- -> Variable.Variable- -> CacheT m (Maybe Builder.Builder)-variable f op var = do- res <- f $ Variable.name var- pure $ case res of- Nothing -> Nothing- Just val -> value op var val--value- :: Operator.Operator- -> Variable.Variable- -> Value.Value- -> Maybe Builder.Builder-value op var val = case val of- Value.Dictionary xs -> dictionaryValue op var $ Map.toAscList xs- Value.List xs -> listValue op var xs- Value.String x -> Just $ stringValue op var x--dictionaryValue- :: Operator.Operator- -> Variable.Variable- -> [(Text.Text, Text.Text)]- -> Maybe Builder.Builder-dictionaryValue = items $ \op var (k, v) ->- let f = string op Modifier.None- in- case Variable.modifier var of- Modifier.Asterisk -> [f k <> Builder.singleton '=' <> f v]- _ -> [f k, f v]--listValue- :: Operator.Operator- -> Variable.Variable- -> [Text.Text]- -> Maybe Builder.Builder-listValue = items $ \op var -> pure . stringValue- (case Variable.modifier var of- Modifier.Asterisk -> op- _ -> Operator.None- )- var { Variable.modifier = Modifier.None }--items- :: (Operator.Operator -> Variable.Variable -> a -> [Builder.Builder])- -> Operator.Operator- -> Variable.Variable- -> [a]- -> Maybe Builder.Builder-items f op var xs =- let- md = Variable.modifier var- sep = case md of- Modifier.Asterisk -> separator op- _ -> Builder.singleton ','- p = case md of- Modifier.Asterisk -> False- _ -> case op of- Operator.Ampersand -> True- Operator.QuestionMark -> True- Operator.Semicolon -> True- _ -> False- in if null xs- then Nothing- else- Just- . mconcat- . (if p then (label True var :) else id)- . List.intersperse sep- $ concatMap (f op var) xs--label :: Bool -> Variable.Variable -> Builder.Builder-label p v =- name (Variable.name v) <> if p then Builder.singleton '=' else mempty--name :: Name.Name -> Builder.Builder-name =- mconcat- . List.intersperse (Builder.singleton '.')- . fmap field- . NonEmpty.toList- . Name.fields--field :: Field.Field -> Builder.Builder-field = foldMap (character $ const True) . Field.characters--character :: (Char -> Bool) -> Character.Character tag -> Builder.Builder-character f x = case x of- Character.Encoded y z -> Render.encodedCharacter y z- Character.Unencoded y -> unencodedCharacter f y--stringValue- :: Operator.Operator -> Variable.Variable -> Text.Text -> Builder.Builder-stringValue op var str =- let- pre = case op of- Operator.Ampersand -> label True var- Operator.QuestionMark -> label True var- Operator.Semicolon -> label (not $ Text.null str) var- _ -> mempty- in pre <> string op (Variable.modifier var) str--string- :: Operator.Operator -> Modifier.Modifier -> Text.Text -> Builder.Builder-string op md =- let- allowed x = case op of- Operator.NumberSign -> isAllowed x- Operator.PlusSign -> isAllowed x- _ -> isUnreserved x- trim = case md of- Modifier.Colon ml -> Text.take $ MaxLength.count ml- _ -> id- in foldMap (unencodedCharacter allowed) . Text.unpack . trim--isAllowed :: Char -> Bool-isAllowed x = isUnreserved x || isReserved x--isUnreserved :: Char -> Bool-isUnreserved x = case x of- '-' -> True- '.' -> True- '_' -> True- '~' -> True- _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x--isReserved :: Char -> Bool-isReserved x = case x of- '!' -> True- '$' -> True- '&' -> True- '\'' -> True- '(' -> True- ')' -> True- '*' -> True- '+' -> True- ',' -> True- ';' -> True- '=' -> True- ':' -> True- '/' -> True- '?' -> True- '#' -> True- '[' -> True- ']' -> True- '@' -> True- _ -> False--unencodedCharacter :: (Char -> Bool) -> Char -> Builder.Builder-unencodedCharacter f x = if f x- then Builder.singleton x- else foldMap (uncurry Render.encodedCharacter) $ encodeCharacter x--encodeCharacter :: Char -> [(Digit.Digit, Digit.Digit)]-encodeCharacter =- fmap Digit.fromWord8 . ByteString.unpack . Text.encodeUtf8 . Text.singleton--literal :: Literal.Literal -> Builder.Builder-literal = foldMap (character isAllowed) . Literal.characters
− src/lib/Burrito/Internal/Match.hs
@@ -1,273 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--module Burrito.Internal.Match where--import qualified Burrito.Internal.Expand as Expand-import qualified Burrito.Internal.Render as Render-import qualified Burrito.Internal.Type.Case as Case-import qualified Burrito.Internal.Type.Character as Character-import qualified Burrito.Internal.Type.Digit as Digit-import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Burrito.Internal.Type.Match as Match-import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Template as Template-import qualified Burrito.Internal.Type.Token as Token-import qualified Burrito.Internal.Type.Value as Value-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Control.Monad as Monad-import qualified Data.ByteString as ByteString-import qualified Data.Char as Char-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Text.ParserCombinators.ReadP as ReadP---- | Matches a string against a template. This is essentially the opposite of--- @expand@.------ Since there isn't always one unique match, this function returns all the--- possibilities. It's up to you to select the one that makes the most sense,--- or to simply grab the first one if you don't care.------ >>> match "" <$> parse "no-match"--- Just []--- >>> match "no-variables" <$> parse "no-variables"--- Just [[]]--- >>> match "1-match" <$> parse "{one}-match"--- Just [[("one",String "1")]]------ Be warned that the number of possible matches can grow quickly if your--- template has variables next to each other without any separators.------ >>> let Just template = parse "{a}{b}"--- >>> mapM_ print $ match "ab" template--- [("a",String "a"),("b",String "b")]--- [("a",String "ab"),("b",String "")]--- [("a",String "ab")]--- [("a",String ""),("b",String "ab")]--- [("b",String "ab")]------ Matching supports everything /except/ explode modifiers (@{a*}@), list--- values, and dictionary values.-match :: String -> Template.Template -> [[(String, Value.Value)]]-match s =- fmap finalize- . Maybe.mapMaybe (keepConsistent . fst)- . flip ReadP.readP_to_S s- . template--finalize :: [(Name.Name, Match.Match)] -> [(String, Value.Value)]-finalize = Maybe.mapMaybe $ \(n, m) -> case m of- Match.Defined v ->- Just (Render.builderToString $ Render.name n, Value.String v)- Match.Prefix _ v ->- Just (Render.builderToString $ Render.name n, Value.String v)- Match.Undefined -> Nothing--keepConsistent- :: [(Name.Name, Match.Match)] -> Maybe [(Name.Name, Match.Match)]-keepConsistent xs = case xs of- [] -> Just xs- (k, v) : ys -> do- let (ts, fs) = List.partition ((== k) . fst) ys- w <- combine v $ fmap snd ts- ((k, w) :) <$> keepConsistent fs--combine :: Match.Match -> [Match.Match] -> Maybe Match.Match-combine x ys = case ys of- [] -> Just x- y : zs -> case x of- Match.Defined t -> case y of- Match.Defined u | t == u -> combine x zs- Match.Prefix m u | Text.take (MaxLength.count m) t == u -> combine x zs- _ -> Nothing- Match.Prefix n t -> case y of- Match.Defined u | t == Text.take (MaxLength.count n) u -> combine y zs- Match.Prefix m u- | let c = MaxLength.count (min n m) in Text.take c t == Text.take c u- -> combine (if m > n then y else x) zs- _ -> Nothing- Match.Undefined -> case y of- Match.Undefined -> combine x zs- _ -> Nothing--template :: Template.Template -> ReadP.ReadP [(Name.Name, Match.Match)]-template x = do- xs <- fmap mconcat . traverse token $ Template.tokens x- ReadP.eof- pure xs--token :: Token.Token -> ReadP.ReadP [(Name.Name, Match.Match)]-token x = case x of- Token.Expression y -> expression y- Token.Literal y -> [] <$ literal y--expression :: Expression.Expression -> ReadP.ReadP [(Name.Name, Match.Match)]-expression x = variables (Expression.operator x) (Expression.variables x)--variables- :: Operator.Operator- -> NonEmpty.NonEmpty Variable.Variable- -> ReadP.ReadP [(Name.Name, Match.Match)]-variables op vs = case op of- Operator.Ampersand -> vars vs (Just '&') '&' varEq- Operator.FullStop -> vars vs (Just '.') '.' $ variable Expand.isUnreserved- Operator.None -> vars vs Nothing ',' $ variable Expand.isUnreserved- Operator.NumberSign -> vars vs (Just '#') ',' $ variable Expand.isAllowed- Operator.PlusSign -> vars vs Nothing ',' $ variable Expand.isAllowed- Operator.QuestionMark -> vars vs (Just '?') '&' varEq- Operator.Semicolon -> vars vs (Just ';') ';' $ \v -> do- let n = Variable.name v- name n- ReadP.option [(n, Match.Defined Text.empty)] $ do- char_ '='- variable Expand.isUnreserved v- Operator.Solidus -> vars vs (Just '/') '/' $ variable Expand.isUnreserved--vars- :: NonEmpty.NonEmpty Variable.Variable- -> Maybe Char- -> Char- -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])- -> ReadP.ReadP [(Name.Name, Match.Match)]-vars vs m c f = do- let- ctx = case m of- Nothing -> id- Just o -> \p -> ReadP.option (undef <$> NonEmpty.toList vs) $ do- char_ o- xs <- p- Monad.guard . not $ all isUndefined xs- pure xs- ctx . vars' c f $ NonEmpty.toList vs--isUndefined :: (Name.Name, Match.Match) -> Bool-isUndefined = (== Match.Undefined) . snd--vars'- :: Char- -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])- -> [Variable.Variable]- -> ReadP.ReadP [(Name.Name, Match.Match)]-vars' c f vs = case vs of- [] -> pure []- v : ws ->- let- this = do- x <- f v- xs <- ReadP.option (undef <$> ws) $ do- char_ c- vars' c f ws- pure $ x <> xs- that = (undef v :) <$> vars' c f ws- in this ReadP.+++ that--undef :: Variable.Variable -> (Name.Name, Match.Match)-undef v = (Variable.name v, Match.Undefined)--char_ :: Char -> ReadP.ReadP ()-char_ = Monad.void . ReadP.char--varEq :: Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)]-varEq v = do- name $ Variable.name v- char_ '='- variable Expand.isUnreserved v--name :: Name.Name -> ReadP.ReadP ()-name = Monad.void . ReadP.string . Render.builderToString . Render.name--variable- :: (Char -> Bool)- -> Variable.Variable- -> ReadP.ReadP [(Name.Name, Match.Match)]-variable f x = do- v <- case Variable.modifier x of- Modifier.Asterisk -> ReadP.pfail- Modifier.None -> Match.Defined <$> manyCharacters f- Modifier.Colon n -> Match.Prefix n <$> manyCharacters f- pure [(Variable.name x, v)]--manyCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text-manyCharacters f = do- let- f1 = (:) <$> someEncodedCharacters <*> ReadP.option [] f2- f2 = (:) <$> someUnencodedCharacters f <*> ReadP.option [] f1- fmap mconcat . ReadP.option [] $ f1 ReadP.<++ f2--someEncodedCharacters :: ReadP.ReadP Text.Text-someEncodedCharacters = do- xs <- some anEncodedCharacter- either (fail . show) pure- . Text.decodeUtf8'- . ByteString.pack- . fmap (uncurry Digit.toWord8)- $ NonEmpty.toList xs--some :: ReadP.ReadP a -> ReadP.ReadP (NonEmpty.NonEmpty a)-some p = (NonEmpty.:|) <$> p <*> ReadP.many p--someUnencodedCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text-someUnencodedCharacters f = do- xs <- some $ ReadP.satisfy f- pure . Text.pack $ NonEmpty.toList xs--anEncodedCharacter :: ReadP.ReadP (Digit.Digit, Digit.Digit)-anEncodedCharacter = do- char_ '%'- (,) <$> aDigit <*> aDigit--aDigit :: ReadP.ReadP Digit.Digit-aDigit = do- x <- ReadP.satisfy Char.isHexDigit- maybe (fail "invalid Digit") pure $ Digit.fromChar x--literal :: Literal.Literal -> ReadP.ReadP ()-literal = mapM_ literalCharacter . Literal.characters--literalCharacter :: Character.Character Literal.Literal -> ReadP.ReadP ()-literalCharacter = character Expand.isAllowed--character :: (Char -> Bool) -> Character.Character tag -> ReadP.ReadP ()-character f x = case x of- Character.Encoded y z -> encodedCharacter y z- Character.Unencoded y -> unencodedCharacter f y--encodedCharacter :: Digit.Digit -> Digit.Digit -> ReadP.ReadP ()-encodedCharacter x y = char_ '%' *> digit x *> digit y--digit :: Digit.Digit -> ReadP.ReadP ()-digit x = char_ $ case x of- Digit.Ox0 -> '0'- Digit.Ox1 -> '1'- Digit.Ox2 -> '2'- Digit.Ox3 -> '3'- Digit.Ox4 -> '4'- Digit.Ox5 -> '5'- Digit.Ox6 -> '6'- Digit.Ox7 -> '7'- Digit.Ox8 -> '8'- Digit.Ox9 -> '9'- Digit.OxA Case.Upper -> 'A'- Digit.OxB Case.Upper -> 'B'- Digit.OxC Case.Upper -> 'C'- Digit.OxD Case.Upper -> 'D'- Digit.OxE Case.Upper -> 'E'- Digit.OxF Case.Upper -> 'F'- Digit.OxA Case.Lower -> 'a'- Digit.OxB Case.Lower -> 'b'- Digit.OxC Case.Lower -> 'c'- Digit.OxD Case.Lower -> 'd'- Digit.OxE Case.Lower -> 'e'- Digit.OxF Case.Lower -> 'f'--unencodedCharacter :: (Char -> Bool) -> Char -> ReadP.ReadP ()-unencodedCharacter f x = if f x- then char_ x- else mapM_ (uncurry encodedCharacter) $ Expand.encodeCharacter x
− src/lib/Burrito/Internal/Parse.hs
@@ -1,175 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--{-# LANGUAGE FlexibleContexts #-}--module Burrito.Internal.Parse where--import qualified Burrito.Internal.Type.Character as Character-import qualified Burrito.Internal.Type.Digit as Digit-import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Field as Field-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Template as Template-import qualified Burrito.Internal.Type.Token as Token-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Data.Char as Char-import qualified Data.Ix as Ix-import qualified Data.List.NonEmpty as NonEmpty-import qualified Text.Parsec as Parsec-import qualified Text.Read as Read---- | Attempts to parse a string as a URI template. If parsing fails, this will--- return @Nothing@. Otherwise it will return @Just@ the parsed template.------ Parsing will usually succeed, but it can fail if the input string contains--- characters that are not valid in IRIs (like @^@) or if the input string--- contains an invalid template expression (like @{!}@). To include characters--- that aren't valid in IRIs, percent encode them (like @%5E@).------ >>> parse "invalid template"--- Nothing--- >>> parse "valid-template"--- Just (Template ...)-parse :: String -> Maybe Template.Template-parse = either (const Nothing) Just . Parsec.parse template ""--template :: Parsec.Stream s m Char => Parsec.ParsecT s u m Template.Template-template = Template.Template <$> Parsec.many token <* Parsec.eof--token :: Parsec.Stream s m Char => Parsec.ParsecT s u m Token.Token-token = choice (Token.Expression <$> expression) (Token.Literal <$> literal)--choice- :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a-choice = (Parsec.<|>)--expression- :: Parsec.Stream s m Char => Parsec.ParsecT s u m Expression.Expression-expression =- Parsec.between (Parsec.char '{') (Parsec.char '}')- $ Expression.Expression- <$> operator- <*> sepBy1 variable (Parsec.char ',')--operator :: Parsec.Stream s m Char => Parsec.ParsecT s u m Operator.Operator-operator = Parsec.option Operator.None $ Parsec.choice- [ Operator.Ampersand <$ Parsec.char '&'- , Operator.FullStop <$ Parsec.char '.'- , Operator.NumberSign <$ Parsec.char '#'- , Operator.PlusSign <$ Parsec.char '+'- , Operator.QuestionMark <$ Parsec.char '?'- , Operator.Semicolon <$ Parsec.char ';'- , Operator.Solidus <$ Parsec.char '/'- ]--sepBy1- :: Parsec.ParsecT s u m a- -> Parsec.ParsecT s u m x- -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)-sepBy1 p s = (NonEmpty.:|) <$> p <*> Parsec.many (s *> p)--variable :: Parsec.Stream s m Char => Parsec.ParsecT s u m Variable.Variable-variable = Variable.Variable <$> name <*> modifier--name :: Parsec.Stream s m Char => Parsec.ParsecT s u m Name.Name-name = Name.Name <$> sepBy1 field (Parsec.char '.')--field :: Parsec.Stream s m Char => Parsec.ParsecT s u m Field.Field-field = Field.Field <$> nonEmpty fieldCharacter--nonEmpty- :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)-nonEmpty p = (NonEmpty.:|) <$> p <*> Parsec.many p--fieldCharacter- :: Parsec.Stream s m Char- => Parsec.ParsecT s u m (Character.Character Field.Field)-fieldCharacter = choice encodedCharacter (unencodedCharacter isFieldCharacter)--encodedCharacter- :: Parsec.Stream s m Char => Parsec.ParsecT s u m (Character.Character tag)-encodedCharacter = Parsec.char '%' >> Character.Encoded <$> digit <*> digit--digit :: Parsec.Stream s m Char => Parsec.ParsecT s u m Digit.Digit-digit = do- x <- Parsec.satisfy Char.isHexDigit- maybe (fail "invalid Digit") pure $ Digit.fromChar x--unencodedCharacter- :: Parsec.Stream s m Char- => (Char -> Bool)- -> Parsec.ParsecT s u m (Character.Character tag)-unencodedCharacter = fmap Character.Unencoded . Parsec.satisfy--isFieldCharacter :: Char -> Bool-isFieldCharacter x = case x of- '_' -> True- _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x--modifier :: Parsec.Stream s m Char => Parsec.ParsecT s u m Modifier.Modifier-modifier = Parsec.option Modifier.None $ Parsec.choice- [ Modifier.Asterisk <$ Parsec.char '*'- , Parsec.char ':' >> Modifier.Colon <$> maxLength- ]--maxLength :: Parsec.Stream s m Char => Parsec.ParsecT s u m MaxLength.MaxLength-maxLength = do- x <- Parsec.satisfy $ Ix.inRange ('1', '9')- xs <- Parsec.many $ Parsec.satisfy Char.isDigit- n <- maybe (fail "invalid MaxLength") pure . Read.readMaybe $ x : xs- if isMaxLength n- then pure $ MaxLength.MaxLength n- else fail "invalid MaxLength"--isMaxLength :: Int -> Bool-isMaxLength = Ix.inRange (1, 9999)--literal :: Parsec.Stream s m Char => Parsec.ParsecT s u m Literal.Literal-literal = Literal.Literal <$> nonEmpty literalCharacter--literalCharacter- :: Parsec.Stream s m Char- => Parsec.ParsecT s u m (Character.Character Literal.Literal)-literalCharacter =- choice encodedCharacter (unencodedCharacter isLiteralCharacter)--isLiteralCharacter :: Char -> Bool-isLiteralCharacter x = case x of- ' ' -> False- '"' -> False- '\'' -> False- '%' -> False- '<' -> False- '>' -> False- '\\' -> False- '^' -> False- '`' -> False- '{' -> False- '|' -> False- '}' -> False- _ ->- Ix.inRange ('\x20', '\x7e') x- || Ix.inRange ('\xa0', '\xd7ff') x- || Ix.inRange ('\xe000', '\xf8ff') x- || Ix.inRange ('\xf900', '\xfdcf') x- || Ix.inRange ('\xfdf0', '\xffef') x- || Ix.inRange ('\x10000', '\x1fffd') x- || Ix.inRange ('\x20000', '\x2fffd') x- || Ix.inRange ('\x30000', '\x3fffd') x- || Ix.inRange ('\x40000', '\x4fffd') x- || Ix.inRange ('\x50000', '\x5fffd') x- || Ix.inRange ('\x60000', '\x6fffd') x- || Ix.inRange ('\x70000', '\x7fffd') x- || Ix.inRange ('\x80000', '\x8fffd') x- || Ix.inRange ('\x90000', '\x9fffd') x- || Ix.inRange ('\xa0000', '\xafffd') x- || Ix.inRange ('\xb0000', '\xbfffd') x- || Ix.inRange ('\xc0000', '\xcfffd') x- || Ix.inRange ('\xd0000', '\xdfffd') x- || Ix.inRange ('\xe1000', '\xefffd') x- || Ix.inRange ('\xf0000', '\xffffd') x- || Ix.inRange ('\x100000', '\x10fffd') x
− src/lib/Burrito/Internal/Render.hs
@@ -1,107 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--module Burrito.Internal.Render where--import qualified Burrito.Internal.Type.Case as Case-import qualified Burrito.Internal.Type.Character as Character-import qualified Burrito.Internal.Type.Digit as Digit-import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Field as Field-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Token as Token-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Text.Lazy as LazyText-import qualified Data.Text.Lazy.Builder as Builder--builderToString :: Builder.Builder -> String-builderToString = LazyText.unpack . Builder.toLazyText--token :: Token.Token -> Builder.Builder-token x = case x of- Token.Expression y -> expression y- Token.Literal y -> literal y--expression :: Expression.Expression -> Builder.Builder-expression x =- Builder.singleton '{'- <> operator (Expression.operator x)- <> sepBy1 variable (Builder.singleton ',') (Expression.variables x)- <> Builder.singleton '}'--operator :: Operator.Operator -> Builder.Builder-operator x = case x of- Operator.Ampersand -> Builder.singleton '&'- Operator.FullStop -> Builder.singleton '.'- Operator.None -> mempty- Operator.NumberSign -> Builder.singleton '#'- Operator.PlusSign -> Builder.singleton '+'- Operator.QuestionMark -> Builder.singleton '?'- Operator.Semicolon -> Builder.singleton ';'- Operator.Solidus -> Builder.singleton '/'--sepBy1- :: (a -> Builder.Builder)- -> Builder.Builder- -> NonEmpty.NonEmpty a- -> Builder.Builder-sepBy1 f x = mconcat . List.intersperse x . fmap f . NonEmpty.toList--variable :: Variable.Variable -> Builder.Builder-variable x = name (Variable.name x) <> modifier (Variable.modifier x)--name :: Name.Name -> Builder.Builder-name = sepBy1 field (Builder.singleton '.') . Name.fields--field :: Field.Field -> Builder.Builder-field = foldMap character . Field.characters--character :: Character.Character tag -> Builder.Builder-character x = case x of- Character.Encoded y z -> encodedCharacter y z- Character.Unencoded y -> Builder.singleton y--encodedCharacter :: Digit.Digit -> Digit.Digit -> Builder.Builder-encodedCharacter x y = Builder.singleton '%' <> digit x <> digit y--digit :: Digit.Digit -> Builder.Builder-digit x = Builder.singleton $ case x of- Digit.Ox0 -> '0'- Digit.Ox1 -> '1'- Digit.Ox2 -> '2'- Digit.Ox3 -> '3'- Digit.Ox4 -> '4'- Digit.Ox5 -> '5'- Digit.Ox6 -> '6'- Digit.Ox7 -> '7'- Digit.Ox8 -> '8'- Digit.Ox9 -> '9'- Digit.OxA Case.Upper -> 'A'- Digit.OxB Case.Upper -> 'B'- Digit.OxC Case.Upper -> 'C'- Digit.OxD Case.Upper -> 'D'- Digit.OxE Case.Upper -> 'E'- Digit.OxF Case.Upper -> 'F'- Digit.OxA Case.Lower -> 'a'- Digit.OxB Case.Lower -> 'b'- Digit.OxC Case.Lower -> 'c'- Digit.OxD Case.Lower -> 'd'- Digit.OxE Case.Lower -> 'e'- Digit.OxF Case.Lower -> 'f'--modifier :: Modifier.Modifier -> Builder.Builder-modifier x = case x of- Modifier.Asterisk -> Builder.singleton '*'- Modifier.Colon y -> Builder.singleton ':' <> maxLength y- Modifier.None -> mempty--maxLength :: MaxLength.MaxLength -> Builder.Builder-maxLength = Builder.fromString . show . MaxLength.count--literal :: Literal.Literal -> Builder.Builder-literal = foldMap character . Literal.characters
− src/lib/Burrito/Internal/TH.hs
@@ -1,28 +0,0 @@-module Burrito.Internal.TH (uriTemplate) where--import qualified Burrito.Internal.Parse as Parse-import qualified Language.Haskell.TH.Quote as TH-import qualified Language.Haskell.TH.Syntax as TH---- | This can be used together with the @QuasiQuotes@ language extension to--- parse a URI template at compile time. This is convenient because it allows--- you to verify the validity of the template when you compile your file as--- opposed to when you run it.------ >>> :set -XQuasiQuotes--- >>> import Burrito--- >>> let template = [uriTemplate|http://example/search{?query}|]--- >>> let values = [("query", stringValue "chorizo")]--- >>> expand values template--- "http://example/search?query=chorizo"------ Note that you cannot use escape sequences in this quasi-quoter. For example,--- this is invalid: @[uriTemplate|\\xa0|]@. You can however use percent encoded--- triples as normal. So this is valid: @[uriTemplate|%c2%a0|]@.-uriTemplate :: TH.QuasiQuoter-uriTemplate = TH.QuasiQuoter- { TH.quoteDec = const $ fail "cannot be used as a declaration"- , TH.quoteExp = maybe (fail "invalid URI template") TH.liftData . Parse.parse- , TH.quotePat = const $ fail "cannot be used as a pattern"- , TH.quoteType = const $ fail "cannot be used as a type"- }
− src/lib/Burrito/Internal/Type/Case.hs
@@ -1,10 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Case (Case(..)) where--import qualified Data.Data as Data--data Case- = Lower- | Upper- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Character.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Character (Character(..)) where--import qualified Burrito.Internal.Type.Digit as Digit-import qualified Data.Data as Data--data Character tag- = Encoded Digit.Digit Digit.Digit- | Unencoded Char- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Digit.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Digit (Digit(..), fromChar, fromWord8, toWord8) where--import qualified Burrito.Internal.Type.Case as Case-import qualified Data.Bits as Bits-import qualified Data.Word as Word-import qualified Data.Data as Data--data Digit- = Ox0- | Ox1- | Ox2- | Ox3- | Ox4- | Ox5- | Ox6- | Ox7- | Ox8- | Ox9- | OxA Case.Case- | OxB Case.Case- | OxC Case.Case- | OxD Case.Case- | OxE Case.Case- | OxF Case.Case- deriving (Data.Data, Eq, Ord, Show)--fromChar :: Char -> Maybe Digit-fromChar x = case x of- '0' -> Just Ox0- '1' -> Just Ox1- '2' -> Just Ox2- '3' -> Just Ox3- '4' -> Just Ox4- '5' -> Just Ox5- '6' -> Just Ox6- '7' -> Just Ox7- '8' -> Just Ox8- '9' -> Just Ox9- 'A' -> Just $ OxA Case.Upper- 'B' -> Just $ OxB Case.Upper- 'C' -> Just $ OxC Case.Upper- 'D' -> Just $ OxD Case.Upper- 'E' -> Just $ OxE Case.Upper- 'F' -> Just $ OxF Case.Upper- 'a' -> Just $ OxA Case.Lower- 'b' -> Just $ OxB Case.Lower- 'c' -> Just $ OxC Case.Lower- 'd' -> Just $ OxD Case.Lower- 'e' -> Just $ OxE Case.Lower- 'f' -> Just $ OxF Case.Lower- _ -> Nothing--fromWord8 :: Word.Word8 -> (Digit, Digit)-fromWord8 x =- let- f :: Word.Word8 -> Digit- f y = case y of- 0x0 -> Ox0- 0x1 -> Ox1- 0x2 -> Ox2- 0x3 -> Ox3- 0x4 -> Ox4- 0x5 -> Ox5- 0x6 -> Ox6- 0x7 -> Ox7- 0x8 -> Ox8- 0x9 -> Ox9- 0xA -> OxA Case.Upper- 0xB -> OxB Case.Upper- 0xC -> OxC Case.Upper- 0xD -> OxD Case.Upper- 0xE -> OxE Case.Upper- 0xF -> OxF Case.Upper- _ -> error $ "invalid nibble: " <> show y- in (f $ Bits.shiftR x 4, f $ x Bits..&. 0x0F)--toWord8 :: Digit -> Digit -> Word.Word8-toWord8 x y =- let- f :: Digit -> Word.Word8- f z = case z of- Ox0 -> 0x0- Ox1 -> 0x1- Ox2 -> 0x2- Ox3 -> 0x3- Ox4 -> 0x4- Ox5 -> 0x5- Ox6 -> 0x6- Ox7 -> 0x7- Ox8 -> 0x8- Ox9 -> 0x9- OxA _ -> 0xA- OxB _ -> 0xB- OxC _ -> 0xC- OxD _ -> 0xD- OxE _ -> 0xE- OxF _ -> 0xF- in Bits.shiftL (f x) 4 Bits..|. f y
− src/lib/Burrito/Internal/Type/Expression.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Expression (Expression(..)) where--import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--data Expression = Expression- { operator :: Operator.Operator- , variables :: NonEmpty.NonEmpty Variable.Variable- } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Field.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Field (Field(..)) where--import qualified Burrito.Internal.Type.Character as Character-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--newtype Field = Field- { characters :: NonEmpty.NonEmpty (Character.Character Field)- } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Literal.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Literal (Literal(..)) where--import qualified Burrito.Internal.Type.Character as Character-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--newtype Literal = Literal- { characters :: NonEmpty.NonEmpty (Character.Character Literal)- } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Match.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Match (Match(..)) where--import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Data.Data as Data-import qualified Data.Text as Text--data Match- = Defined Text.Text- | Prefix MaxLength.MaxLength Text.Text- | Undefined- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/MaxLength.hs
@@ -1,9 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.MaxLength (MaxLength(..)) where--import qualified Data.Data as Data--newtype MaxLength = MaxLength- { count :: Int- } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Modifier.hs
@@ -1,12 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Modifier (Modifier(..)) where--import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Data.Data as Data--data Modifier- = Asterisk- | Colon MaxLength.MaxLength- | None- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Name.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Name (Name(..)) where--import qualified Burrito.Internal.Type.Field as Field-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--newtype Name = Name- { fields :: NonEmpty.NonEmpty Field.Field- } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Operator.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Operator (Operator(..)) where--import qualified Data.Data as Data--data Operator- = Ampersand- | FullStop- | None- | NumberSign- | PlusSign- | QuestionMark- | Semicolon- | Solidus- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Template.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Template (Template (..), render) where--import qualified Burrito.Internal.Render as Render-import qualified Burrito.Internal.Type.Token as Token-import qualified Data.Data as Data-import qualified Data.Text.Lazy.Builder as Builder---- | Represents a URI template.-newtype Template = Template- { tokens :: [Token.Token]- } deriving (Data.Data, Eq, Ord)--instance Show Template where- show = render---- | Renders a template back into a string. This is essentially the opposite of--- @parse@. Usually you'll want to use @expand@ to actually substitute--- variables in the template, but this can be useful for printing out the--- template itself------ >>> render <$> parse "valid-template"--- Just "valid-template"--- >>> render <$> parse "{var}"--- Just "{var}"-render :: Template -> String-render = Render.builderToString . template--template :: Template -> Builder.Builder-template = foldMap Render.token . tokens
− src/lib/Burrito/Internal/Type/Token.hs
@@ -1,12 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Token (Token(..)) where--import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Data.Data as Data--data Token- = Expression Expression.Expression- | Literal Literal.Literal- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Value.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Value (Value(..)) where--import qualified Data.Data as Data-import qualified Data.Map as Map-import qualified Data.Text as Text---- | Represents a value that can be substituted into a template. Can be a--- string, a list, or dictionary (which is called an associative array in the--- RFC).-data Value- = Dictionary (Map.Map Text.Text Text.Text)- | List [Text.Text]- | String Text.Text- deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Variable.hs
@@ -1,12 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Variable (Variable(..)) where--import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Data.Data as Data--data Variable = Variable- { name :: Name.Name- , modifier :: Modifier.Modifier- } deriving (Data.Data, Eq, Ord, Show)
− src/test/Main.hs
@@ -1,1471 +0,0 @@-{-# OPTIONS_GHC -Wno-monomorphism-restriction #-}--{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}--module Main- ( main- )-where--import qualified Burrito-import qualified Burrito.Internal.Parse as Parse-import qualified Burrito.Internal.Render as Render-import qualified Burrito.Internal.Type.Case as Case-import qualified Burrito.Internal.Type.Character as Character-import qualified Burrito.Internal.Type.Digit as Digit-import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Field as Field-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Template as Template-import qualified Burrito.Internal.Type.Token as Token-import qualified Burrito.Internal.Type.Value as Value-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Control.Monad as Monad-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe-import qualified Data.Set as Set-import qualified Data.String as String-import qualified Data.Text as Text-import qualified GHC.Stack as Stack-import qualified Test.Hspec as Hspec-import qualified Test.Hspec.QuickCheck as Hspec-import qualified Test.QuickCheck as QC--main :: IO ()-main = Hspec.hspec . Hspec.describe "Burrito" $ do-- Monad.forM_ tests $ \test ->- Hspec.it (show (testInput test, unwrapOutput $ testOutput test))- $ runTest test-- Hspec.describe "match" $ do- let- matchTest- :: Stack.HasCallStack- => String- -> [(String, Burrito.Value)]- -> String- -> Hspec.Spec- matchTest input values output = Hspec.it (show (input, output)) $ do- template <- maybe (fail "invalid Template") pure $ Burrito.parse input- let matches = Burrito.match output template- matches `Hspec.shouldSatisfy` elem values- Monad.forM_ matches $ \match -> do- let it = Burrito.expand match template- Monad.when (it /= output) . fail $ show- (input, values, output, matches, match, it)-- matchTest "" [] ""- matchTest "a" [] "a"- matchTest "!" [] "!"- matchTest "\xa0" [] "%C2%A0"-- matchTest "{a}" ["a" =: s ""] ""- matchTest "{a}" ["a" =: s "A"] "A"- matchTest "{a}" ["a" =: s "AB"] "AB"-- matchTest "a{b}" ["b" =: s ""] "a"- matchTest "a{b}" ["b" =: s "B"] "aB"- matchTest "a{b}" ["b" =: s "BC"] "aBC"-- matchTest "{a}b" ["a" =: s ""] "b"- matchTest "{a}b" ["a" =: s "A"] "Ab"- matchTest "{a}b" ["a" =: s "AB"] "ABb"-- matchTest "{a}{a}" ["a" =: s "A"] "AA"- matchTest "{a}{a}" ["a" =: s "AB"] "ABAB"-- matchTest "{a}" ["a" =: s "%"] "%25"- matchTest "{a}" ["a" =: s "/"] "%2F"- matchTest "{a}" ["a" =: s "\xa0"] "%C2%A0"- matchTest "{a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"- matchTest "{a}" ["a" =: s "\x10000"] "%F0%90%80%80"- matchTest "{a}" ["a" =: s "A/B"] "A%2FB"- matchTest "{a}" ["a" =: s "WX\xa0\xa1YZ"] "WX%C2%A0%C2%A1YZ"-- matchTest "{+a}" ["a" =: s "%"] "%25"- matchTest "{+a}" ["a" =: s "/"] "/"-- matchTest "{#a}" [] ""- matchTest "{#a}" ["a" =: s ""] "#"- matchTest "{#a}" ["a" =: s "A"] "#A"- matchTest "{#a}" ["a" =: s "%"] "#%25"- matchTest "{#a}" ["a" =: s "/"] "#/"-- matchTest "{.a}" [] ""- matchTest "{.a}" ["a" =: s ""] "."- matchTest "{.a}" ["a" =: s "A"] ".A"- matchTest "{.a}" ["a" =: s "%"] ".%25"- matchTest "{.a}" ["a" =: s "/"] ".%2F"-- matchTest "{/a}" [] ""- matchTest "{/a}" ["a" =: s ""] "/"- matchTest "{/a}" ["a" =: s "A"] "/A"- matchTest "{/a}" ["a" =: s "%"] "/%25"- matchTest "{/a}" ["a" =: s "/"] "/%2F"-- matchTest "{;a}" [] ""- matchTest "{;a}" ["a" =: s ""] ";a"- matchTest "{;a}" ["a" =: s "A"] ";a=A"- matchTest "{;a}" ["a" =: s "%"] ";a=%25"- matchTest "{;a}" ["a" =: s "/"] ";a=%2F"-- matchTest "{?a}" [] ""- matchTest "{?a}" ["a" =: s ""] "?a="- matchTest "{?a}" ["a" =: s "A"] "?a=A"- matchTest "{?a}" ["a" =: s "%"] "?a=%25"- matchTest "{?a}" ["a" =: s "/"] "?a=%2F"-- matchTest "{&a}" [] ""- matchTest "{&a}" ["a" =: s ""] "&a="- matchTest "{&a}" ["a" =: s "A"] "&a=A"- matchTest "{&a}" ["a" =: s "%"] "&a=%25"- matchTest "{&a}" ["a" =: s "/"] "&a=%2F"-- matchTest "{a,b}" ["a" =: s "A", "b" =: s "B"] "A,B"- matchTest "{+a,b}" ["a" =: s "A", "b" =: s "B"] "A,B"- matchTest "{#a,b}" ["a" =: s "A", "b" =: s "B"] "#A,B"- matchTest "{.a,b}" ["a" =: s "A", "b" =: s "B"] ".A.B"- matchTest "{/a,b}" ["a" =: s "A", "b" =: s "B"] "/A/B"- matchTest "{;a,b}" ["a" =: s "A", "b" =: s "B"] ";a=A;b=B"- matchTest "{?a,b}" ["a" =: s "A", "b" =: s "B"] "?a=A&b=B"- matchTest "{&a,b}" ["a" =: s "A", "b" =: s "B"] "&a=A&b=B"-- matchTest "{a,b}" ["a" =: s "A"] "A"- matchTest "{a,b,c}" ["a" =: s "A"] "A"- matchTest "{a,b,c}" ["a" =: s "A", "b" =: s "B"] "A,B"- matchTest "{a}{a,b}" ["b" =: s "B"] "B"- matchTest "{a}{a,b,c}" ["b" =: s "B"] "B"- matchTest "{a,b}{a,b,c}" ["c" =: s "C"] "C"- matchTest "{b}{a,b,c}" ["a" =: s "A", "c" =: s "C"] "A,C"- matchTest "{a}{a,b,c}" ["b" =: s "B", "c" =: s "C"] "B,C"-- matchTest "{a:1}/{a}" ["a" =: s "AB"] "A/AB"-- -- TODO: Test matching on explode modifier.- -- matchTest "{a*}" ["a" =: s "A"] "A"-- -- TODO: Test matching on lists.- -- matchTest "{a}" ["a" =: l ["A", "B"]] "A,B"-- -- TODO: Test matching on dictionaries.- -- matchTest "{a}" ["a" =: d ["k" =: "v"]] "k,v"-- Hspec.describe "uriTemplate"- . Hspec.it "works as an expression"- $ Just [Burrito.uriTemplate|a{b}c|]- `Hspec.shouldBe` Burrito.parse "a{b}c"-- Hspec.modifyMaxSize (const 10)- . Hspec.it "round trips"- . QC.property- $ \(Template template) ->- Burrito.parse (show template) == Just template---- brittany-next-binding --columns 160-tests :: [Test]-tests = mconcat- [ [ Test "" [] ""- , Test "" ["a" =: s ""] ""- , Test "" ["a" =: l []] ""- , Test "" ["a" =: d []] ""- , Test "!" [] "!"- , Test "#" [] "#"- , Test "$" [] "$"- , Test "&" [] "&"- , Test "(" [] "("- , Test ")" [] ")"- , Test "*" [] "*"- , Test "+" [] "+"- , Test "," [] ","- , Test "-" [] "-"- , Test "." [] "."- , Test "/" [] "/"- , Test "0" [] "0"- , Test "9" [] "9"- , Test ":" [] ":"- , Test ";" [] ";"- , Test "=" [] "="- , Test "?" [] "?"- , Test "@" [] "@"- , Test "A" [] "A"- , Test "Z" [] "Z"- , Test "[" [] "["- , Test "]" [] "]"- , Test "_" [] "_"- , Test "a" [] "a"- , Test "z" [] "z"- , Test "~" [] "~"- , Test "\xa0" [] "%C2%A0"- , Test "\xd7ff" [] "%ED%9F%BF"- , Test "\xf900" [] "%EF%A4%80"- , Test "\xfdcf" [] "%EF%B7%8F"- , Test "\xfdf0" [] "%EF%B7%B0"- , Test "\xffef" [] "%EF%BF%AF"- , Test "\x10000" [] "%F0%90%80%80"- , Test "\x1fffd" [] "%F0%9F%BF%BD"- , Test "\x20000" [] "%F0%A0%80%80"- , Test "\x2fffd" [] "%F0%AF%BF%BD"- , Test "\x30000" [] "%F0%B0%80%80"- , Test "\x3fffd" [] "%F0%BF%BF%BD"- , Test "\x40000" [] "%F1%80%80%80"- , Test "\x4fffd" [] "%F1%8F%BF%BD"- , Test "\x50000" [] "%F1%90%80%80"- , Test "\x5fffd" [] "%F1%9F%BF%BD"- , Test "\x60000" [] "%F1%A0%80%80"- , Test "\x6fffd" [] "%F1%AF%BF%BD"- , Test "\x70000" [] "%F1%B0%80%80"- , Test "\x7fffd" [] "%F1%BF%BF%BD"- , Test "\x80000" [] "%F2%80%80%80"- , Test "\x8fffd" [] "%F2%8F%BF%BD"- , Test "\x90000" [] "%F2%90%80%80"- , Test "\x9fffd" [] "%F2%9F%BF%BD"- , Test "\xa0000" [] "%F2%A0%80%80"- , Test "\xafffd" [] "%F2%AF%BF%BD"- , Test "\xb0000" [] "%F2%B0%80%80"- , Test "\xbfffd" [] "%F2%BF%BF%BD"- , Test "\xc0000" [] "%F3%80%80%80"- , Test "\xcfffd" [] "%F3%8F%BF%BD"- , Test "\xd0000" [] "%F3%90%80%80"- , Test "\xdfffd" [] "%F3%9F%BF%BD"- , Test "\xe1000" [] "%F3%A1%80%80"- , Test "\xefffd" [] "%F3%AF%BF%BD"- , Test "\xe000" [] "%EE%80%80"- , Test "\xf8ff" [] "%EF%A3%BF"- , Test "\xf0000" [] "%F3%B0%80%80"- , Test "\xffffd" [] "%F3%BF%BF%BD"- , Test "\x100000" [] "%F4%80%80%80"- , Test "\x10fffd" [] "%F4%8F%BF%BD"- , Test "%00" [] "%00"- , Test "%AA" [] "%AA"- , Test "%Aa" [] "%Aa"- , Test "%aA" [] "%aA"- , Test "%aa" [] "%aa"- , Test "%" [] noParse- , Test "%0" [] noParse- , Test "%0z" [] noParse- , Test "%z" [] noParse- , Test "%30" [] "%30"- , Test " " [] noParse- , Test "\"" [] noParse- , Test "'" [] noParse- , Test "%" [] noParse- , Test "<" [] noParse- , Test ">" [] noParse- , Test "\\" [] noParse- , Test "^" [] noParse- , Test "`" [] noParse- , Test "{" [] noParse- , Test "|" [] noParse- , Test "}" [] noParse- , Test "{}" [] noParse- , Test "{,}" [] noParse- , Test "{a,,b}" [] noParse- , Test "{+}" [] noParse- , Test "{:1}" [] noParse- , Test "{*}" [] noParse- , Test "{AZ}" [] ""- , Test "{az}" [] ""- , Test "{09}" [] ""- , Test "{_a}" [] ""- , Test "{a_}" [] ""- , Test "{_}" [] ""- , Test "{A.A}" [] ""- , Test "{a.a}" [] ""- , Test "{0.0}" [] ""- , Test "{_._}" [] ""- , Test "{%aa.%aa}" [] ""- , Test "{.}" [] noParse- , Test "{a.}" [] noParse- , Test "{+.a}" [] noParse- , Test "{a..b}" [] noParse- , Test "{%00}" [] ""- , Test "{%}" [] noParse- , Test "{%0}" [] noParse- , Test "{%0z}" [] noParse- , Test "{%z}" [] noParse- , Test "{!}" [] noParse- , Test "{" [] noParse- , Test "{{}" [] noParse- , Test "}" [] noParse- , Test "{}}" [] noParse- , Test "{a,b}" [] ""- , Test "{a,b,c,d}" [] ""- , Test "{a,a}" [] ""- , Test "{a:5}" [] ""- , Test "{a:67}" [] ""- , Test "{a:801}" [] ""- , Test "{a:234}" [] ""- , Test "{a:9999}" [] ""- , Test "{a:123}" ["a" =: s (replicate 200 'a')] . Output . Just $ replicate 123 'a'- , Test "{a:}" [] noParse- , Test "{a:0}" [] noParse- , Test "{a:10000}" [] noParse- , Test "{a:-1}" [] noParse- , Test "{a*}" [] ""- , Test "{a:1*}" [] noParse- , Test "{a*:1}" [] noParse- , Test "{a,b:1,c*}" [] ""- , Test "{+a}" [] ""- , Test "{#a}" [] ""- , Test "{.a}" [] ""- , Test "{/a}" [] ""- , Test "{;a}" [] ""- , Test "{?a}" [] ""- , Test "{&a}" [] ""- , Test "{=a}" [] noParse- , Test "{,a}" [] noParse- , Test "{!a}" [] noParse- , Test "{@a}" [] noParse- , Test "{|a}" [] noParse- , Test "{+#a}" [] noParse- , Test "{+a,#b}" [] noParse- , Test "{+a:1}" [] ""- , Test "{#a*}" [] ""- , Test "{+a,b}" [] ""- , Test "{#a,b}" [] ""- , Test "{.a,b}" [] ""- , Test "{/a,b}" [] ""- , Test "{;a,b}" [] ""- , Test "{?a,b}" [] ""- , Test "{&a,b}" [] ""- , Test "{a}{b}" [] ""- , Test "{a}{b}{c}{d}" [] ""- , Test "{a}{a}" [] ""- , Test "{{}}" [] noParse- , Test "{a{b}}" [] noParse- , Test "{{a}b}" [] noParse- , Test "{a{b}c}" [] noParse- , Test "a{b}" [] "a"- , Test "{a}b" [] "b"- , Test "a{b}c" [] "ac"- , Test "{a}b{c}" [] "b"- , Test "{a}" [] ""- , Test "{+a}" [] ""- , Test "{#a}" [] ""- , Test "{.a}" [] ""- , Test "{/a}" [] ""- , Test "{;a}" [] ""- , Test "{?a}" [] ""- , Test "{&a}" [] ""- , Test "http://example.com/~{username}/" ["username" =: s "fred"] "http://example.com/~fred/"- , Test "http://example.com/~{username}/" ["username" =: s "mark"] "http://example.com/~mark/"- , Test "http://example.com/dictionary/{term:1}/{term}" ["term" =: s "cat"] "http://example.com/dictionary/c/cat"- , Test "http://example.com/dictionary/{term:1}/{term}" ["term" =: s "dog"] "http://example.com/dictionary/d/dog"- , Test "http://example.com/search{?q,lang}" ["q" =: s "cat", "lang" =: s "en"] "http://example.com/search?q=cat&lang=en"- , Test "http://example.com/search{?q,lang}" ["q" =: s "chien", "lang" =: s "fr"] "http://example.com/search?q=chien&lang=fr"- , Test "http://www.example.com/foo{?query,number}" ["query" =: s "mycelium", "number" =: s "100"] "http://www.example.com/foo?query=mycelium&number=100"- , Test "http://www.example.com/foo{?query,number}" ["number" =: s "100"] "http://www.example.com/foo?number=100"- , Test "http://www.example.com/foo{?query,number}" [] "http://www.example.com/foo"- , Test "{a}" [] ""- , Test "{a}" ["a" =: l []] ""- , Test "{a}" ["a" =: d []] ""- , Test "{a}" ["a" =: s ""] ""- , Test "{a}" ["a" =: s "A"] "A"- , Test "{a}" ["a" =: s "~"] "~"- , Test "{a}" ["a" =: s "%"] "%25"- , Test "{a}" ["a" =: s "?"] "%3F"- , Test "{a}" ["a" =: s "&"] "%26"- , Test "{a}" ["a" =: s "\xa0"] "%C2%A0"- , Test "{a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"- , Test "{a}" ["a" =: s "\x10000"] "%F0%90%80%80"- , Test "{a}" ["a" =: l ["A"]] "A"- , Test "{a}" ["a" =: l ["A", "B"]] "A,B"- , Test "{a}" ["a" =: l ["%"]] "%25"- , Test "{a}" ["a" =: l ["\xa0"]] "%C2%A0"- , Test "{a}" ["a" =: d ["A" =: "1"]] "A,1"- , Test "{a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"- , Test "{a}" ["a" =: d ["A" =: "%"]] "A,%25"- , Test "{a}" ["a" =: d ["A" =: "\xa0"]] "A,%C2%A0"- , Test "{a}" ["a" =: d ["%" =: "1"]] "%25,1"- , Test "{a*}" [] ""- , Test "{a*}" ["a" =: s ""] ""- , Test "{a*}" ["a" =: s "A"] "A"- , Test "{a*}" ["a" =: l []] ""- , Test "{a*}" ["a" =: l ["A"]] "A"- , Test "{a*}" ["a" =: l ["A", "B"]] "A,B"- , Test "{a*}" ["a" =: d []] ""- , Test "{a*}" ["a" =: d ["A" =: "1"]] "A=1"- , Test "{a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"- , Test "{a:1}" [] ""- , Test "{a:1}" ["a" =: s ""] ""- , Test "{a:1}" ["a" =: s "A"] "A"- , Test "{a:1}" ["a" =: s "AB"] "A"- , Test "{a:1}" ["a" =: s "%B"] "%25"- , Test "{a:1}" ["a" =: s "\xa0\&B"] "%C2%A0"- , Test "{a:1}" ["a" =: s "\xd7ff\&B"] "%ED%9F%BF"- , Test "{a:1}" ["a" =: s "\x10000\&B"] "%F0%90%80%80"- , Test "{a:1}" ["a" =: l []] ""- , Test "{a:1}" ["a" =: l ["AB"]] "AB"- , Test "{a:1}" ["a" =: l ["AB", "CD"]] "AB,CD"- , Test "{a:1}" ["a" =: d []] ""- , Test "{a:1}" ["a" =: d ["AB" =: "12"]] "AB,12"- , Test "{a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34"- , Test "{a,a}" [] ""- , Test "{a,a}" ["a" =: l []] ""- , Test "{a,a}" ["a" =: d []] ""- , Test "{a,a}" ["a" =: s ""] ","- , Test "{a,b}" ["a" =: s ""] ""- , Test "{a,b}" ["b" =: s ""] ""- , Test "{%aa}" ["%aa" =: s "A"] "A"- , Test "{%aa}" ["%aa" =: l ["A", "B"]] "A,B"- , Test "{%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"- , Test "{%aa*}" ["%aa" =: s "A"] "A"- , Test "{%aa*}" ["%aa" =: l ["A", "B"]] "A,B"- , Test "{%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"- , Test "{+a}" [] ""- , Test "{+a}" ["a" =: l []] ""- , Test "{+a}" ["a" =: d []] ""- , Test "{+a}" ["a" =: s ""] ""- , Test "{+a}" ["a" =: s "A"] "A"- , Test "{+a}" ["a" =: s "~"] "~"- , Test "{+a}" ["a" =: s "%"] "%25"- , Test "{+a}" ["a" =: s "?"] "?"- , Test "{+a}" ["a" =: s "&"] "&"- , Test "{+a}" ["a" =: s "\xa0"] "%C2%A0"- , Test "{+a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"- , Test "{+a}" ["a" =: s "\x10000"] "%F0%90%80%80"- , Test "{+a}" ["a" =: l ["A"]] "A"- , Test "{+a}" ["a" =: l ["A", "B"]] "A,B"- , Test "{+a}" ["a" =: l ["%"]] "%25"- , Test "{+a}" ["a" =: l ["\xa0"]] "%C2%A0"- , Test "{+a}" ["a" =: d ["A" =: "1"]] "A,1"- , Test "{+a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"- , Test "{+a}" ["a" =: d ["A" =: "%"]] "A,%25"- , Test "{+a}" ["a" =: d ["A" =: "\xa0"]] "A,%C2%A0"- , Test "{+a}" ["a" =: d ["%" =: "1"]] "%25,1"- , Test "{+a*}" [] ""- , Test "{+a*}" ["a" =: s ""] ""- , Test "{+a*}" ["a" =: s "A"] "A"- , Test "{+a*}" ["a" =: l []] ""- , Test "{+a*}" ["a" =: l ["A"]] "A"- , Test "{+a*}" ["a" =: l ["A", "B"]] "A,B"- , Test "{+a*}" ["a" =: d []] ""- , Test "{+a*}" ["a" =: d ["A" =: "1"]] "A=1"- , Test "{+a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"- , Test "{+a:1}" [] ""- , Test "{+a:1}" ["a" =: s ""] ""- , Test "{+a:1}" ["a" =: s "A"] "A"- , Test "{+a:1}" ["a" =: s "AB"] "A"- , Test "{+a:1}" ["a" =: s "%B"] "%25"- , Test "{+a:1}" ["a" =: s "\xa0\&B"] "%C2%A0"- , Test "{+a:1}" ["a" =: s "\xd7ff\&B"] "%ED%9F%BF"- , Test "{+a:1}" ["a" =: s "\x10000\&B"] "%F0%90%80%80"- , Test "{+a:1}" ["a" =: l []] ""- , Test "{+a:1}" ["a" =: l ["AB"]] "AB"- , Test "{+a:1}" ["a" =: l ["AB", "CD"]] "AB,CD"- , Test "{+a:1}" ["a" =: d []] ""- , Test "{+a:1}" ["a" =: d ["AB" =: "12"]] "AB,12"- , Test "{+a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34"- , Test "{+a,a}" [] ""- , Test "{+a,a}" ["a" =: l []] ""- , Test "{+a,a}" ["a" =: d []] ""- , Test "{+a,a}" ["a" =: s ""] ","- , Test "{+a,b}" ["a" =: s ""] ""- , Test "{+a,b}" ["b" =: s ""] ""- , Test "{+%aa}" ["%aa" =: s "A"] "A"- , Test "{+%aa}" ["%aa" =: l ["A", "B"]] "A,B"- , Test "{+%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"- , Test "{+%aa*}" ["%aa" =: s "A"] "A"- , Test "{+%aa*}" ["%aa" =: l ["A", "B"]] "A,B"- , Test "{+%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"- , Test "{#a}" [] ""- , Test "{#a}" ["a" =: l []] ""- , Test "{#a}" ["a" =: d []] ""- , Test "{#a}" ["a" =: s ""] "#"- , Test "{#a}" ["a" =: s "A"] "#A"- , Test "{#a}" ["a" =: s "~"] "#~"- , Test "{#a}" ["a" =: s "%"] "#%25"- , Test "{#a}" ["a" =: s "?"] "#?"- , Test "{#a}" ["a" =: s "&"] "#&"- , Test "{#a}" ["a" =: s "\xa0"] "#%C2%A0"- , Test "{#a}" ["a" =: s "\xd7ff"] "#%ED%9F%BF"- , Test "{#a}" ["a" =: s "\x10000"] "#%F0%90%80%80"- , Test "{#a}" ["a" =: l ["A"]] "#A"- , Test "{#a}" ["a" =: l ["A", "B"]] "#A,B"- , Test "{#a}" ["a" =: l ["%"]] "#%25"- , Test "{#a}" ["a" =: l ["\xa0"]] "#%C2%A0"- , Test "{#a}" ["a" =: d ["A" =: "1"]] "#A,1"- , Test "{#a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "#A,1,B,2"- , Test "{#a}" ["a" =: d ["A" =: "%"]] "#A,%25"- , Test "{#a}" ["a" =: d ["A" =: "\xa0"]] "#A,%C2%A0"- , Test "{#a}" ["a" =: d ["%" =: "1"]] "#%25,1"- , Test "{#a*}" [] ""- , Test "{#a*}" ["a" =: s ""] "#"- , Test "{#a*}" ["a" =: s "A"] "#A"- , Test "{#a*}" ["a" =: l []] ""- , Test "{#a*}" ["a" =: l ["A"]] "#A"- , Test "{#a*}" ["a" =: l ["A", "B"]] "#A,B"- , Test "{#a*}" ["a" =: d []] ""- , Test "{#a*}" ["a" =: d ["A" =: "1"]] "#A=1"- , Test "{#a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "#A=1,B=2"- , Test "{#a:1}" [] ""- , Test "{#a:1}" ["a" =: s ""] "#"- , Test "{#a:1}" ["a" =: s "A"] "#A"- , Test "{#a:1}" ["a" =: s "AB"] "#A"- , Test "{#a:1}" ["a" =: s "%B"] "#%25"- , Test "{#a:1}" ["a" =: s "\xa0\&B"] "#%C2%A0"- , Test "{#a:1}" ["a" =: s "\xd7ff\&B"] "#%ED%9F%BF"- , Test "{#a:1}" ["a" =: s "\x10000\&B"] "#%F0%90%80%80"- , Test "{#a:1}" ["a" =: l []] ""- , Test "{#a:1}" ["a" =: l ["AB"]] "#AB"- , Test "{#a:1}" ["a" =: l ["AB", "CD"]] "#AB,CD"- , Test "{#a:1}" ["a" =: d []] ""- , Test "{#a:1}" ["a" =: d ["AB" =: "12"]] "#AB,12"- , Test "{#a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "#AB,12,CD,34"- , Test "{#a,a}" [] ""- , Test "{#a,a}" ["a" =: l []] ""- , Test "{#a,a}" ["a" =: d []] ""- , Test "{#a,a}" ["a" =: s ""] "#,"- , Test "{#a,b}" ["a" =: s ""] "#"- , Test "{#a,b}" ["b" =: s ""] "#"- , Test "{#%aa}" ["%aa" =: s "A"] "#A"- , Test "{#%aa}" ["%aa" =: l ["A", "B"]] "#A,B"- , Test "{#%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "#A,1,B,2"- , Test "{#%aa*}" ["%aa" =: s "A"] "#A"- , Test "{#%aa*}" ["%aa" =: l ["A", "B"]] "#A,B"- , Test "{#%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "#A=1,B=2"- , Test "{.a}" [] ""- , Test "{.a}" ["a" =: l []] ""- , Test "{.a}" ["a" =: d []] ""- , Test "{.a}" ["a" =: s ""] "."- , Test "{.a}" ["a" =: s "A"] ".A"- , Test "{.a}" ["a" =: s "~"] ".~"- , Test "{.a}" ["a" =: s "%"] ".%25"- , Test "{.a}" ["a" =: s "?"] ".%3F"- , Test "{.a}" ["a" =: s "&"] ".%26"- , Test "{.a}" ["a" =: s "\xa0"] ".%C2%A0"- , Test "{.a}" ["a" =: s "\xd7ff"] ".%ED%9F%BF"- , Test "{.a}" ["a" =: s "\x10000"] ".%F0%90%80%80"- , Test "{.a}" ["a" =: l ["A"]] ".A"- , Test "{.a}" ["a" =: l ["A", "B"]] ".A,B"- , Test "{.a}" ["a" =: l ["%"]] ".%25"- , Test "{.a}" ["a" =: l ["\xa0"]] ".%C2%A0"- , Test "{.a}" ["a" =: d ["A" =: "1"]] ".A,1"- , Test "{.a}" ["a" =: d ["A" =: "1", "B" =: "2"]] ".A,1,B,2"- , Test "{.a}" ["a" =: d ["A" =: "%"]] ".A,%25"- , Test "{.a}" ["a" =: d ["A" =: "\xa0"]] ".A,%C2%A0"- , Test "{.a}" ["a" =: d ["%" =: "1"]] ".%25,1"- , Test "{.a*}" [] ""- , Test "{.a*}" ["a" =: s ""] "."- , Test "{.a*}" ["a" =: s "A"] ".A"- , Test "{.a*}" ["a" =: l []] ""- , Test "{.a*}" ["a" =: l ["A"]] ".A"- , Test "{.a*}" ["a" =: l ["A", "B"]] ".A.B"- , Test "{.a*}" ["a" =: d []] ""- , Test "{.a*}" ["a" =: d ["A" =: "1"]] ".A=1"- , Test "{.a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] ".A=1.B=2"- , Test "{.a:1}" [] ""- , Test "{.a:1}" ["a" =: s ""] "."- , Test "{.a:1}" ["a" =: s "A"] ".A"- , Test "{.a:1}" ["a" =: s "AB"] ".A"- , Test "{.a:1}" ["a" =: s "%B"] ".%25"- , Test "{.a:1}" ["a" =: s "\xa0\&B"] ".%C2%A0"- , Test "{.a:1}" ["a" =: s "\xd7ff\&B"] ".%ED%9F%BF"- , Test "{.a:1}" ["a" =: s "\x10000\&B"] ".%F0%90%80%80"- , Test "{.a:1}" ["a" =: l []] ""- , Test "{.a:1}" ["a" =: l ["AB"]] ".AB"- , Test "{.a:1}" ["a" =: l ["AB", "CD"]] ".AB,CD"- , Test "{.a:1}" ["a" =: d []] ""- , Test "{.a:1}" ["a" =: d ["AB" =: "12"]] ".AB,12"- , Test "{.a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] ".AB,12,CD,34"- , Test "{.a,a}" [] ""- , Test "{.a,a}" ["a" =: l []] ""- , Test "{.a,a}" ["a" =: d []] ""- , Test "{.a,a}" ["a" =: s ""] ".."- , Test "{.a,b}" ["a" =: s ""] "."- , Test "{.a,b}" ["b" =: s ""] "."- , Test "{.%aa}" ["%aa" =: s "A"] ".A"- , Test "{.%aa}" ["%aa" =: l ["A", "B"]] ".A,B"- , Test "{.%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ".A,1,B,2"- , Test "{.%aa*}" ["%aa" =: s "A"] ".A"- , Test "{.%aa*}" ["%aa" =: l ["A", "B"]] ".A.B"- , Test "{.%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ".A=1.B=2"- , Test "{/a}" [] ""- , Test "{/a}" ["a" =: l []] ""- , Test "{/a}" ["a" =: d []] ""- , Test "{/a}" ["a" =: s ""] "/"- , Test "{/a}" ["a" =: s "A"] "/A"- , Test "{/a}" ["a" =: s "~"] "/~"- , Test "{/a}" ["a" =: s "%"] "/%25"- , Test "{/a}" ["a" =: s "?"] "/%3F"- , Test "{/a}" ["a" =: s "&"] "/%26"- , Test "{/a}" ["a" =: s "\xa0"] "/%C2%A0"- , Test "{/a}" ["a" =: s "\xd7ff"] "/%ED%9F%BF"- , Test "{/a}" ["a" =: s "\x10000"] "/%F0%90%80%80"- , Test "{/a}" ["a" =: l ["A"]] "/A"- , Test "{/a}" ["a" =: l ["A", "B"]] "/A,B"- , Test "{/a}" ["a" =: l ["%"]] "/%25"- , Test "{/a}" ["a" =: l ["\xa0"]] "/%C2%A0"- , Test "{/a}" ["a" =: d ["A" =: "1"]] "/A,1"- , Test "{/a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "/A,1,B,2"- , Test "{/a}" ["a" =: d ["A" =: "%"]] "/A,%25"- , Test "{/a}" ["a" =: d ["A" =: "\xa0"]] "/A,%C2%A0"- , Test "{/a}" ["a" =: d ["%" =: "1"]] "/%25,1"- , Test "{/a*}" [] ""- , Test "{/a*}" ["a" =: s ""] "/"- , Test "{/a*}" ["a" =: s "A"] "/A"- , Test "{/a*}" ["a" =: l []] ""- , Test "{/a*}" ["a" =: l ["A"]] "/A"- , Test "{/a*}" ["a" =: l ["A", "B"]] "/A/B"- , Test "{/a*}" ["a" =: d []] ""- , Test "{/a*}" ["a" =: d ["A" =: "1"]] "/A=1"- , Test "{/a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "/A=1/B=2"- , Test "{/a:1}" [] ""- , Test "{/a:1}" ["a" =: s ""] "/"- , Test "{/a:1}" ["a" =: s "A"] "/A"- , Test "{/a:1}" ["a" =: s "AB"] "/A"- , Test "{/a:1}" ["a" =: s "%B"] "/%25"- , Test "{/a:1}" ["a" =: s "\xa0\&B"] "/%C2%A0"- , Test "{/a:1}" ["a" =: s "\xd7ff\&B"] "/%ED%9F%BF"- , Test "{/a:1}" ["a" =: s "\x10000\&B"] "/%F0%90%80%80"- , Test "{/a:1}" ["a" =: l []] ""- , Test "{/a:1}" ["a" =: l ["AB"]] "/AB"- , Test "{/a:1}" ["a" =: l ["AB", "CD"]] "/AB,CD"- , Test "{/a:1}" ["a" =: d []] ""- , Test "{/a:1}" ["a" =: d ["AB" =: "12"]] "/AB,12"- , Test "{/a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "/AB,12,CD,34"- , Test "{/a,a}" [] ""- , Test "{/a,a}" ["a" =: l []] ""- , Test "{/a,a}" ["a" =: d []] ""- , Test "{/a,a}" ["a" =: s ""] "//"- , Test "{/a,b}" ["a" =: s ""] "/"- , Test "{/a,b}" ["b" =: s ""] "/"- , Test "{/%aa}" ["%aa" =: s "A"] "/A"- , Test "{/%aa}" ["%aa" =: l ["A", "B"]] "/A,B"- , Test "{/%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "/A,1,B,2"- , Test "{/%aa*}" ["%aa" =: s "A"] "/A"- , Test "{/%aa*}" ["%aa" =: l ["A", "B"]] "/A/B"- , Test "{/%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "/A=1/B=2"- , Test "{;a}" [] ""- , Test "{;a}" ["a" =: l []] ""- , Test "{;a}" ["a" =: d []] ""- , Test "{;a}" ["a" =: s ""] ";a"- , Test "{;a}" ["a" =: s "A"] ";a=A"- , Test "{;a}" ["a" =: s "~"] ";a=~"- , Test "{;a}" ["a" =: s "%"] ";a=%25"- , Test "{;a}" ["a" =: s "?"] ";a=%3F"- , Test "{;a}" ["a" =: s "&"] ";a=%26"- , Test "{;a}" ["a" =: s "\xa0"] ";a=%C2%A0"- , Test "{;a}" ["a" =: s "\xd7ff"] ";a=%ED%9F%BF"- , Test "{;a}" ["a" =: s "\x10000"] ";a=%F0%90%80%80"- , Test "{;a}" ["a" =: l ["A"]] ";a=A"- , Test "{;a}" ["a" =: l ["A", "B"]] ";a=A,B"- , Test "{;a}" ["a" =: l ["%"]] ";a=%25"- , Test "{;a}" ["a" =: l ["\xa0"]] ";a=%C2%A0"- , Test "{;a}" ["a" =: d ["A" =: "1"]] ";a=A,1"- , Test "{;a}" ["a" =: d ["A" =: "1", "B" =: "2"]] ";a=A,1,B,2"- , Test "{;a}" ["a" =: d ["A" =: "%"]] ";a=A,%25"- , Test "{;a}" ["a" =: d ["A" =: "\xa0"]] ";a=A,%C2%A0"- , Test "{;a}" ["a" =: d ["%" =: "1"]] ";a=%25,1"- , Test "{;a*}" [] ""- , Test "{;a*}" ["a" =: s ""] ";a"- , Test "{;a*}" ["a" =: s "A"] ";a=A"- , Test "{;a*}" ["a" =: l []] ""- , Test "{;a*}" ["a" =: l ["A"]] ";a=A"- , Test "{;a*}" ["a" =: l ["A", "B"]] ";a=A;a=B"- , Test "{;a*}" ["a" =: d []] ""- , Test "{;a*}" ["a" =: d ["A" =: "1"]] ";A=1"- , Test "{;a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] ";A=1;B=2"- , Test "{;a:1}" [] ""- , Test "{;a:1}" ["a" =: s ""] ";a"- , Test "{;a:1}" ["a" =: s "A"] ";a=A"- , Test "{;a:1}" ["a" =: s "AB"] ";a=A"- , Test "{;a:1}" ["a" =: s "%B"] ";a=%25"- , Test "{;a:1}" ["a" =: s "\xa0\&B"] ";a=%C2%A0"- , Test "{;a:1}" ["a" =: s "\xd7ff\&B"] ";a=%ED%9F%BF"- , Test "{;a:1}" ["a" =: s "\x10000\&B"] ";a=%F0%90%80%80"- , Test "{;a:1}" ["a" =: l []] ""- , Test "{;a:1}" ["a" =: l ["AB"]] ";a=AB"- , Test "{;a:1}" ["a" =: l ["AB", "CD"]] ";a=AB,CD"- , Test "{;a:1}" ["a" =: d []] ""- , Test "{;a:1}" ["a" =: d ["AB" =: "12"]] ";a=AB,12"- , Test "{;a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] ";a=AB,12,CD,34"- , Test "{;a,a}" [] ""- , Test "{;a,a}" ["a" =: l []] ""- , Test "{;a,a}" ["a" =: d []] ""- , Test "{;a,a}" ["a" =: s ""] ";a;a"- , Test "{;a,b}" ["a" =: s ""] ";a"- , Test "{;a,b}" ["b" =: s ""] ";b"- , Test "{;%aa}" ["%aa" =: s "A"] ";%aa=A"- , Test "{;%aa}" ["%aa" =: l ["A", "B"]] ";%aa=A,B"- , Test "{;%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ";%aa=A,1,B,2"- , Test "{;%aa*}" ["%aa" =: s "A"] ";%aa=A"- , Test "{;%aa*}" ["%aa" =: l ["A", "B"]] ";%aa=A;%aa=B"- , Test "{;%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ";A=1;B=2"- , Test "{?a}" [] ""- , Test "{?a}" ["a" =: l []] ""- , Test "{?a}" ["a" =: d []] ""- , Test "{?a}" ["a" =: s ""] "?a="- , Test "{?a}" ["a" =: s "A"] "?a=A"- , Test "{?a}" ["a" =: s "~"] "?a=~"- , Test "{?a}" ["a" =: s "%"] "?a=%25"- , Test "{?a}" ["a" =: s "?"] "?a=%3F"- , Test "{?a}" ["a" =: s "&"] "?a=%26"- , Test "{?a}" ["a" =: s "\xa0"] "?a=%C2%A0"- , Test "{?a}" ["a" =: s "\xd7ff"] "?a=%ED%9F%BF"- , Test "{?a}" ["a" =: s "\x10000"] "?a=%F0%90%80%80"- , Test "{?a}" ["a" =: l ["A"]] "?a=A"- , Test "{?a}" ["a" =: l ["A", "B"]] "?a=A,B"- , Test "{?a}" ["a" =: l ["%"]] "?a=%25"- , Test "{?a}" ["a" =: l ["\xa0"]] "?a=%C2%A0"- , Test "{?a}" ["a" =: d ["A" =: "1"]] "?a=A,1"- , Test "{?a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "?a=A,1,B,2"- , Test "{?a}" ["a" =: d ["A" =: "%"]] "?a=A,%25"- , Test "{?a}" ["a" =: d ["A" =: "\xa0"]] "?a=A,%C2%A0"- , Test "{?a}" ["a" =: d ["%" =: "1"]] "?a=%25,1"- , Test "{?a*}" [] ""- , Test "{?a*}" ["a" =: s ""] "?a="- , Test "{?a*}" ["a" =: s "A"] "?a=A"- , Test "{?a*}" ["a" =: l []] ""- , Test "{?a*}" ["a" =: l ["A"]] "?a=A"- , Test "{?a*}" ["a" =: l ["A", "B"]] "?a=A&a=B"- , Test "{?a*}" ["a" =: d []] ""- , Test "{?a*}" ["a" =: d ["A" =: "1"]] "?A=1"- , Test "{?a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "?A=1&B=2"- , Test "{?a:1}" [] ""- , Test "{?a:1}" ["a" =: s ""] "?a="- , Test "{?a:1}" ["a" =: s "A"] "?a=A"- , Test "{?a:1}" ["a" =: s "AB"] "?a=A"- , Test "{?a:1}" ["a" =: s "%B"] "?a=%25"- , Test "{?a:1}" ["a" =: s "\xa0\&B"] "?a=%C2%A0"- , Test "{?a:1}" ["a" =: s "\xd7ff\&B"] "?a=%ED%9F%BF"- , Test "{?a:1}" ["a" =: s "\x10000\&B"] "?a=%F0%90%80%80"- , Test "{?a:1}" ["a" =: l []] ""- , Test "{?a:1}" ["a" =: l ["AB"]] "?a=AB"- , Test "{?a:1}" ["a" =: l ["AB", "CD"]] "?a=AB,CD"- , Test "{?a:1}" ["a" =: d []] ""- , Test "{?a:1}" ["a" =: d ["AB" =: "12"]] "?a=AB,12"- , Test "{?a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "?a=AB,12,CD,34"- , Test "{?a,a}" [] ""- , Test "{?a,a}" ["a" =: l []] ""- , Test "{?a,a}" ["a" =: d []] ""- , Test "{?a,a}" ["a" =: s ""] "?a=&a="- , Test "{?a,b}" ["a" =: s ""] "?a="- , Test "{?a,b}" ["b" =: s ""] "?b="- , Test "{?%aa}" ["%aa" =: s "A"] "?%aa=A"- , Test "{?%aa}" ["%aa" =: l ["A", "B"]] "?%aa=A,B"- , Test "{?%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "?%aa=A,1,B,2"- , Test "{?%aa*}" ["%aa" =: s "A"] "?%aa=A"- , Test "{?%aa*}" ["%aa" =: l ["A", "B"]] "?%aa=A&%aa=B"- , Test "{?%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "?A=1&B=2"- , Test "{&a}" [] ""- , Test "{&a}" ["a" =: l []] ""- , Test "{&a}" ["a" =: d []] ""- , Test "{&a}" ["a" =: s ""] "&a="- , Test "{&a}" ["a" =: s "A"] "&a=A"- , Test "{&a}" ["a" =: s "~"] "&a=~"- , Test "{&a}" ["a" =: s "%"] "&a=%25"- , Test "{&a}" ["a" =: s "?"] "&a=%3F"- , Test "{&a}" ["a" =: s "&"] "&a=%26"- , Test "{&a}" ["a" =: s "\xa0"] "&a=%C2%A0"- , Test "{&a}" ["a" =: s "\xd7ff"] "&a=%ED%9F%BF"- , Test "{&a}" ["a" =: s "\x10000"] "&a=%F0%90%80%80"- , Test "{&a}" ["a" =: l ["A"]] "&a=A"- , Test "{&a}" ["a" =: l ["A", "B"]] "&a=A,B"- , Test "{&a}" ["a" =: l ["%"]] "&a=%25"- , Test "{&a}" ["a" =: l ["\xa0"]] "&a=%C2%A0"- , Test "{&a}" ["a" =: d ["A" =: "1"]] "&a=A,1"- , Test "{&a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "&a=A,1,B,2"- , Test "{&a}" ["a" =: d ["A" =: "%"]] "&a=A,%25"- , Test "{&a}" ["a" =: d ["A" =: "\xa0"]] "&a=A,%C2%A0"- , Test "{&a}" ["a" =: d ["%" =: "1"]] "&a=%25,1"- , Test "{&a*}" [] ""- , Test "{&a*}" ["a" =: s ""] "&a="- , Test "{&a*}" ["a" =: s "A"] "&a=A"- , Test "{&a*}" ["a" =: l []] ""- , Test "{&a*}" ["a" =: l ["A"]] "&a=A"- , Test "{&a*}" ["a" =: l ["A", "B"]] "&a=A&a=B"- , Test "{&a*}" ["a" =: d []] ""- , Test "{&a*}" ["a" =: d ["A" =: "1"]] "&A=1"- , Test "{&a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "&A=1&B=2"- , Test "{&a:1}" [] ""- , Test "{&a:1}" ["a" =: s ""] "&a="- , Test "{&a:1}" ["a" =: s "A"] "&a=A"- , Test "{&a:1}" ["a" =: s "AB"] "&a=A"- , Test "{&a:1}" ["a" =: s "%B"] "&a=%25"- , Test "{&a:1}" ["a" =: s "\xa0\&B"] "&a=%C2%A0"- , Test "{&a:1}" ["a" =: s "\xd7ff\&B"] "&a=%ED%9F%BF"- , Test "{&a:1}" ["a" =: s "\x10000\&B"] "&a=%F0%90%80%80"- , Test "{&a:1}" ["a" =: l []] ""- , Test "{&a:1}" ["a" =: l ["AB"]] "&a=AB"- , Test "{&a:1}" ["a" =: l ["AB", "CD"]] "&a=AB,CD"- , Test "{&a:1}" ["a" =: d []] ""- , Test "{&a:1}" ["a" =: d ["AB" =: "12"]] "&a=AB,12"- , Test "{&a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "&a=AB,12,CD,34"- , Test "{&a,a}" [] ""- , Test "{&a,a}" ["a" =: l []] ""- , Test "{&a,a}" ["a" =: d []] ""- , Test "{&a,a}" ["a" =: s ""] "&a=&a="- , Test "{&a,b}" ["a" =: s ""] "&a="- , Test "{&a,b}" ["b" =: s ""] "&b="- , Test "{&%aa}" ["%aa" =: s "A"] "&%aa=A"- , Test "{&%aa}" ["%aa" =: l ["A", "B"]] "&%aa=A,B"- , Test "{&%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "&%aa=A,1,B,2"- , Test "{&%aa*}" ["%aa" =: s "A"] "&%aa=A"- , Test "{&%aa*}" ["%aa" =: l ["A", "B"]] "&%aa=A&%aa=B"- , Test "{&%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "&A=1&B=2"- ]- , let values = ["%AA" =: s "1", "%Aa" =: s "2", "%aA" =: s "3", "%aa" =: s "4"]- in- [ Test "{%AA}" values "1"- , Test "{%Aa}" values "2"- , Test "{%aA}" values "3"- , Test "{%aa}" values "4"- -- This comment forces Brittany to use a multi-line layout.- ]- , let values = ["a" =: s "abcdefghijklmnopqrstuvwxyz"]- in- [ Test "{a:1}" values "a"- , Test "{a:5}" values "abcde"- , Test "{a:10}" values "abcdefghij"- , Test "{a:15}" values "abcdefghijklmno"- , Test "{a:20}" values "abcdefghijklmnopqrst"- , Test "{a:25}" values "abcdefghijklmnopqrstuvwxy"- , Test "{a:26}" values "abcdefghijklmnopqrstuvwxyz"- , Test "{a:30}" values "abcdefghijklmnopqrstuvwxyz"- ]- , let values = ["a" =: l []]- in- [ Test "{a}" values ""- , Test "{+a}" values ""- , Test "{#a}" values ""- , Test "{.a}" values ""- , Test "{/a}" values ""- , Test "{;a}" values ""- , Test "{?a}" values ""- , Test "{&a}" values ""- ]- , let values = ["a" =: d []]- in- [ Test "{a}" values ""- , Test "{+a}" values ""- , Test "{#a}" values ""- , Test "{.a}" values ""- , Test "{/a}" values ""- , Test "{;a}" values ""- , Test "{?a}" values ""- , Test "{&a}" values ""- ]- , let values = ["a" =: s ""]- in- [ Test "{a}" values ""- , Test "{+a}" values ""- , Test "{#a}" values "#"- , Test "{.a}" values "."- , Test "{/a}" values "/"- , Test "{;a}" values ";a"- , Test "{?a}" values "?a="- , Test "{&a}" values "&a="- ]- , let values = ["a" =: s "A"]- in- [ Test "{a}" values "A"- , Test "{+a}" values "A"- , Test "{#a}" values "#A"- , Test "{.a}" values ".A"- , Test "{/a}" values "/A"- , Test "{;a}" values ";a=A"- , Test "{?a}" values "?a=A"- , Test "{&a}" values "&a=A"- ]- , let values = ["b" =: s "B"]- in- [ Test "{a,b}" values "B"- , Test "{+a,b}" values "B"- , Test "{#a,b}" values "#B"- , Test "{.a,b}" values ".B"- , Test "{/a,b}" values "/B"- , Test "{;a,b}" values ";b=B"- , Test "{?a,b}" values "?b=B"- , Test "{&a,b}" values "&b=B"- ]- , let values = ["a" =: s ""]- in- [ Test "{a,a}" values ","- , Test "{+a,a}" values ","- , Test "{#a,a}" values "#,"- , Test "{.a,a}" values ".."- , Test "{/a,a}" values "//"- , Test "{;a,a}" values ";a;a"- , Test "{?a,a}" values "?a=&a="- , Test "{&a,a}" values "&a=&a="- ]- , let values = ["a" =: s "A", "b" =: s "B"]- in- [ Test "{a,b}" values "A,B"- , Test "{+a,b}" values "A,B"- , Test "{#a,b}" values "#A,B"- , Test "{.a,b}" values ".A.B"- , Test "{/a,b}" values "/A/B"- , Test "{;a,b}" values ";a=A;b=B"- , Test "{?a,b}" values "?a=A&b=B"- , Test "{&a,b}" values "&a=A&b=B"- ]- , let values = ["a" =: d ["K! \xa0\xd7ff\x10000" =: "V! \xa0\xd7ff\x10000"]]- in- [ Test "{a*}" values "K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{+a*}" values "K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{#a*}" values "#K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{.a*}" values ".K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{/a*}" values "/K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{;a*}" values ";K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{?a*}" values "?K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"- , Test "{&a*}" values "&K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"- ]- , let- values =- [ "empty" =: s ""- , "hello" =: s "Hello World!"- , "keys" =: d ["semi" =: ";", "dot" =: ".", "comma" =: ","]- , "list" =: l ["red", "green", "blue"]- , "path" =: s "/foo/bar"- , "var" =: s "value"- , "x" =: s "1024"- , "y" =: s "768"- ]- in- [ Test "{var}" values "value"- , Test "{hello}" values "Hello%20World%21"- , Test "{+var}" values "value"- , Test "{+hello}" values "Hello%20World!"- , Test "{+path}/here" values "/foo/bar/here"- , Test "here?ref={+path}" values "here?ref=/foo/bar"- , Test "X{#var}" values "X#value"- , Test "X{#hello}" values "X#Hello%20World!"- , Test "map?{x,y}" values "map?1024,768"- , Test "{x,hello,y}" values "1024,Hello%20World%21,768"- , Test "{+x,hello,y}" values "1024,Hello%20World!,768"- , Test "{+path,x}/here" values "/foo/bar,1024/here"- , Test "{#x,hello,y}" values "#1024,Hello%20World!,768"- , Test "{#path,x}/here" values "#/foo/bar,1024/here"- , Test "X{.var}" values "X.value"- , Test "X{.x,y}" values "X.1024.768"- , Test "{/var}" values "/value"- , Test "{/var,x}/here" values "/value/1024/here"- , Test "{;x,y}" values ";x=1024;y=768"- , Test "{;x,y,empty}" values ";x=1024;y=768;empty"- , Test "{?x,y}" values "?x=1024&y=768"- , Test "{?x,y,empty}" values "?x=1024&y=768&empty="- , Test "?fixed=yes{&x}" values "?fixed=yes&x=1024"- , Test "{&x,y,empty}" values "&x=1024&y=768&empty="- , Test "{var:3}" values "val"- , Test "{var:30}" values "value"- , Test "{list}" values "red,green,blue"- , Test "{list*}" values "red,green,blue"- , Test "{keys}" values "comma,%2C,dot,.,semi,%3B"- , Test "{keys*}" values "comma=%2C,dot=.,semi=%3B"- , Test "{+path:6}/here" values "/foo/b/here"- , Test "{+list}" values "red,green,blue"- , Test "{+list*}" values "red,green,blue"- , Test "{+keys}" values "comma,,,dot,.,semi,;"- , Test "{+keys*}" values "comma=,,dot=.,semi=;"- , Test "{#path:6}/here" values "#/foo/b/here"- , Test "{#list}" values "#red,green,blue"- , Test "{#list*}" values "#red,green,blue"- , Test "{#keys}" values "#comma,,,dot,.,semi,;"- , Test "{#keys*}" values "#comma=,,dot=.,semi=;"- , Test "X{.var:3}" values "X.val"- , Test "X{.list}" values "X.red,green,blue"- , Test "X{.list*}" values "X.red.green.blue"- , Test "X{.keys}" values "X.comma,%2C,dot,.,semi,%3B"- , Test "X{.keys*}" values "X.comma=%2C.dot=..semi=%3B"- , Test "{/var:1,var}" values "/v/value"- , Test "{/list}" values "/red,green,blue"- , Test "{/list*}" values "/red/green/blue"- , Test "{/list*,path:4}" values "/red/green/blue/%2Ffoo"- , Test "{/keys}" values "/comma,%2C,dot,.,semi,%3B"- , Test "{/keys*}" values "/comma=%2C/dot=./semi=%3B"- , Test "{;hello:5}" values ";hello=Hello"- , Test "{;list}" values ";list=red,green,blue"- , Test "{;list*}" values ";list=red;list=green;list=blue"- , Test "{;keys}" values ";keys=comma,%2C,dot,.,semi,%3B"- , Test "{;keys*}" values ";comma=%2C;dot=.;semi=%3B"- , Test "{?var:3}" values "?var=val"- , Test "{?list}" values "?list=red,green,blue"- , Test "{?list*}" values "?list=red&list=green&list=blue"- , Test "{?keys}" values "?keys=comma,%2C,dot,.,semi,%3B"- , Test "{?keys*}" values "?comma=%2C&dot=.&semi=%3B"- , Test "{&var:3}" values "&var=val"- , Test "{&list}" values "&list=red,green,blue"- , Test "{&list*}" values "&list=red&list=green&list=blue"- , Test "{&keys}" values "&keys=comma,%2C,dot,.,semi,%3B"- , Test "{&keys*}" values "&comma=%2C&dot=.&semi=%3B"- ]- , let values = ["var" =: s "value", "semi" =: s ";"]- in- [ Test "{var}" values "value"- , Test "{var:20}" values "value"- , Test "{var:3}" values "val"- , Test "{semi}" values "%3B"- , Test "{semi:2}" values "%3B"- -- This comment forces Brittany to use a multi-line layout.- ]- , let values = ["year" =: l ["1965", "2000", "2012"], "dom" =: l ["example", "com"]]- in- [ Test "find{?year*}" values "find?year=1965&year=2000&year=2012"- , Test "www{.dom*}" values "www.example.com"- -- This comment forces Brittany to use a multi-line layout.- ]- , let- values =- [ "base" =: s "http://example.com/home/"- , "count" =: l ["one", "two", "three"]- , "dom" =: l ["example", "com"]- , "dub" =: s "me/too"- , "empty_keys" =: d []- , "empty" =: s ""- , "half" =: s "50%"- , "hello" =: s "Hello World!"- , "keys" =: d ["semi" =: ";", "dot" =: ".", "comma" =: ","]- , "list" =: l ["red", "green", "blue"]- , "path" =: s "/foo/bar"- , "v" =: s "6"- , "var" =: s "value"- , "who" =: s "fred"- , "x" =: s "1024"- , "y" =: s "768"- ]- in- [ Test "{count}" values "one,two,three"- , Test "{count*}" values "one,two,three"- , Test "{/count}" values "/one,two,three"- , Test "{/count*}" values "/one/two/three"- , Test "{;count}" values ";count=one,two,three"- , Test "{;count*}" values ";count=one;count=two;count=three"- , Test "{?count}" values "?count=one,two,three"- , Test "{?count*}" values "?count=one&count=two&count=three"- , Test "{&count*}" values "&count=one&count=two&count=three"- , Test "{var}" values "value"- , Test "{hello}" values "Hello%20World%21"- , Test "{half}" values "50%25"- , Test "O{empty}X" values "OX"- , Test "O{undef}X" values "OX"- , Test "{x,y}" values "1024,768"- , Test "{x,hello,y}" values "1024,Hello%20World%21,768"- , Test "?{x,empty}" values "?1024,"- , Test "?{x,undef}" values "?1024"- , Test "?{undef,y}" values "?768"- , Test "{var:3}" values "val"- , Test "{var:30}" values "value"- , Test "{list}" values "red,green,blue"- , Test "{list*}" values "red,green,blue"- , Test "{keys}" values "comma,%2C,dot,.,semi,%3B"- , Test "{keys*}" values "comma=%2C,dot=.,semi=%3B"- , Test "{+var}" values "value"- , Test "{+hello}" values "Hello%20World!"- , Test "{+half}" values "50%25"- , Test "{base}index" values "http%3A%2F%2Fexample.com%2Fhome%2Findex"- , Test "{+base}index" values "http://example.com/home/index"- , Test "O{+empty}X" values "OX"- , Test "O{+undef}X" values "OX"- , Test "{+path}/here" values "/foo/bar/here"- , Test "here?ref={+path}" values "here?ref=/foo/bar"- , Test "up{+path}{var}/here" values "up/foo/barvalue/here"- , Test "{+x,hello,y}" values "1024,Hello%20World!,768"- , Test "{+path,x}/here" values "/foo/bar,1024/here"- , Test "{+path:6}/here" values "/foo/b/here"- , Test "{+list}" values "red,green,blue"- , Test "{+list*}" values "red,green,blue"- , Test "{+keys}" values "comma,,,dot,.,semi,;"- , Test "{+keys*}" values "comma=,,dot=.,semi=;"- , Test "{#var}" values "#value"- , Test "{#hello}" values "#Hello%20World!"- , Test "{#half}" values "#50%25"- , Test "foo{#empty}" values "foo#"- , Test "foo{#undef}" values "foo"- , Test "{#x,hello,y}" values "#1024,Hello%20World!,768"- , Test "{#path,x}/here" values "#/foo/bar,1024/here"- , Test "{#path:6}/here" values "#/foo/b/here"- , Test "{#list}" values "#red,green,blue"- , Test "{#list*}" values "#red,green,blue"- , Test "{#keys}" values "#comma,,,dot,.,semi,;"- , Test "{#keys*}" values "#comma=,,dot=.,semi=;"- , Test "{.who}" values ".fred"- , Test "{.who,who}" values ".fred.fred"- , Test "{.half,who}" values ".50%25.fred"- , Test "www{.dom*}" values "www.example.com"- , Test "X{.var}" values "X.value"- , Test "X{.empty}" values "X."- , Test "X{.undef}" values "X"- , Test "X{.var:3}" values "X.val"- , Test "X{.list}" values "X.red,green,blue"- , Test "X{.list*}" values "X.red.green.blue"- , Test "X{.keys}" values "X.comma,%2C,dot,.,semi,%3B"- , Test "X{.keys*}" values "X.comma=%2C.dot=..semi=%3B"- , Test "X{.empty_keys}" values "X"- , Test "X{.empty_keys*}" values "X"- , Test "{/who}" values "/fred"- , Test "{/who,who}" values "/fred/fred"- , Test "{/half,who}" values "/50%25/fred"- , Test "{/who,dub}" values "/fred/me%2Ftoo"- , Test "{/var}" values "/value"- , Test "{/var,empty}" values "/value/"- , Test "{/var,undef}" values "/value"- , Test "{/var,x}/here" values "/value/1024/here"- , Test "{/var:1,var}" values "/v/value"- , Test "{/list}" values "/red,green,blue"- , Test "{/list*}" values "/red/green/blue"- , Test "{/list*,path:4}" values "/red/green/blue/%2Ffoo"- , Test "{/keys}" values "/comma,%2C,dot,.,semi,%3B"- , Test "{/keys*}" values "/comma=%2C/dot=./semi=%3B"- , Test "{;who}" values ";who=fred"- , Test "{;half}" values ";half=50%25"- , Test "{;empty}" values ";empty"- , Test "{;v,empty,who}" values ";v=6;empty;who=fred"- , Test "{;v,bar,who}" values ";v=6;who=fred"- , Test "{;x,y}" values ";x=1024;y=768"- , Test "{;x,y,empty}" values ";x=1024;y=768;empty"- , Test "{;x,y,undef}" values ";x=1024;y=768"- , Test "{;hello:5}" values ";hello=Hello"- , Test "{;list}" values ";list=red,green,blue"- , Test "{;list*}" values ";list=red;list=green;list=blue"- , Test "{;keys}" values ";keys=comma,%2C,dot,.,semi,%3B"- , Test "{;keys*}" values ";comma=%2C;dot=.;semi=%3B"- , Test "{?who}" values "?who=fred"- , Test "{?half}" values "?half=50%25"- , Test "{?x,y}" values "?x=1024&y=768"- , Test "{?x,y,empty}" values "?x=1024&y=768&empty="- , Test "{?x,y,undef}" values "?x=1024&y=768"- , Test "{?var:3}" values "?var=val"- , Test "{?list}" values "?list=red,green,blue"- , Test "{?list*}" values "?list=red&list=green&list=blue"- , Test "{?keys}" values "?keys=comma,%2C,dot,.,semi,%3B"- , Test "{?keys*}" values "?comma=%2C&dot=.&semi=%3B"- , Test "{&who}" values "&who=fred"- , Test "{&half}" values "&half=50%25"- , Test "?fixed=yes{&x}" values "?fixed=yes&x=1024"- , Test "{&x,y,empty}" values "&x=1024&y=768&empty="- , Test "{&x,y,undef}" values "&x=1024&y=768"- , Test "{&var:3}" values "&var=val"- , Test "{&list}" values "&list=red,green,blue"- , Test "{&list*}" values "&list=red&list=green&list=blue"- , Test "{&keys}" values "&keys=comma,%2C,dot,.,semi,%3B"- , Test "{&keys*}" values "&comma=%2C&dot=.&semi=%3B"- ]- ]--data Test = Test- { testInput :: String- , testValues :: [(String, Burrito.Value)]- , testOutput :: Output- } deriving (Eq, Show)--runTest :: Test -> Hspec.Expectation-runTest test =- case (Burrito.parse $ testInput test, unwrapOutput $ testOutput test) of- (Nothing, Nothing) -> pure ()- (Nothing, Just _) -> Hspec.expectationFailure "should have parsed"- (Just _, Nothing) -> Hspec.expectationFailure "should not have parsed"- (Just template, Just expected) -> do- let- values = testValues test- actual = Burrito.expand values template- actual `Hspec.shouldBe` expected- Burrito.parse (show template) `Hspec.shouldBe` Just template- let- relevant =- List.sort $ keepRelevant (templateVariables template) values- Monad.when (isMatchable template relevant) $ do- let matches = List.sort <$> Burrito.match expected template- matches `Hspec.shouldSatisfy` elem relevant--isMatchable :: Template.Template -> [(String, Burrito.Value)] -> Bool-isMatchable template values =- (not . any (isAsterisk . Variable.modifier) $ templateVariables template)- && all (isString . snd) values--isString :: Burrito.Value -> Bool-isString value = case value of- Value.String _ -> True- _ -> False--isAsterisk :: Modifier.Modifier -> Bool-isAsterisk modifier = case modifier of- Modifier.Asterisk -> True- _ -> False--keepRelevant- :: Set.Set Variable.Variable- -> [(String, Burrito.Value)]- -> [(String, Burrito.Value)]-keepRelevant variables =- let- vs =- Map.fromListWith- (\mx my -> case (mx, my) of- (Just x, Just y) -> Just $ max x y- _ -> Nothing- )- . fmap- (\v ->- ( Render.builderToString . Render.name $ Variable.name v- , case Variable.modifier v of- Modifier.Colon n -> Just $ MaxLength.count n- _ -> Nothing- )- )- $ Set.toList variables- in- Maybe.mapMaybe $ \(k, v) -> do- m <- Map.lookup k vs- pure . (,) k $ case m of- Nothing -> v- Just n -> case v of- Value.String t -> Value.String $ Text.take n t- _ -> v--templateVariables :: Template.Template -> Set.Set Variable.Variable-templateVariables =- Set.fromList- . concatMap- (\token -> case token of- Token.Expression expression ->- NonEmpty.toList $ Expression.variables expression- Token.Literal _ -> []- )- . Template.tokens--newtype Output = Output- { unwrapOutput :: Maybe String- } deriving (Eq, Show)--instance String.IsString Output where- fromString = Output . Just--noParse :: Output-noParse = Output Nothing--(=:) :: a -> b -> (a, b)-(=:) = (,)--s :: String -> Burrito.Value-s = Burrito.stringValue--l :: [String] -> Burrito.Value-l = Burrito.listValue--d :: [(String, String)] -> Burrito.Value-d = Burrito.dictionaryValue--newtype Template = Template- { unwrapTemplate :: Template.Template- } deriving (Eq)--instance Show Template where- show (Template template) =- unwords [show $ show template, "{-", show (Template.tokens template), "-}"]--instance QC.Arbitrary Template where- arbitrary = Template <$> arbitraryTemplate- shrink = fmap Template . shrinkTemplate . unwrapTemplate--type Shrink a = a -> [a]--arbitraryTemplate :: QC.Gen Template.Template-arbitraryTemplate = Template.Template . simplify <$> QC.listOf arbitraryToken--shrinkTemplate :: Shrink Template.Template-shrinkTemplate =- fmap (Template.Template . simplify)- . QC.shrinkList shrinkToken- . Template.tokens--simplify :: [Token.Token] -> [Token.Token]-simplify tokens = case tokens of- t1 : t2 : ts -> case (t1, t2) of- (Token.Literal l1, Token.Literal l2) ->- simplify $ Token.Literal (appendLiteral l1 l2) : ts- _ -> t1 : simplify (t2 : ts)- _ -> tokens--appendLiteral :: Literal.Literal -> Literal.Literal -> Literal.Literal-appendLiteral x y =- Literal.Literal $ Literal.characters x <> Literal.characters y--arbitraryToken :: QC.Gen Token.Token-arbitraryToken = QC.oneof- [ Token.Expression <$> arbitraryExpression- , Token.Literal <$> arbitraryLiteral- ]--shrinkToken :: Shrink Token.Token-shrinkToken x = case x of- Token.Expression y -> Token.Expression <$> shrinkExpression y- Token.Literal y -> Token.Literal <$> shrinkLiteral y--arbitraryExpression :: QC.Gen Expression.Expression-arbitraryExpression =- Expression.Expression- <$> arbitraryOperator- <*> arbitraryNonEmpty arbitraryVariable--shrinkExpression :: Shrink Expression.Expression-shrinkExpression x = uncurry Expression.Expression <$> shrinkTuple- shrinkOperator- (shrinkNonEmpty shrinkVariable)- (Expression.operator x, Expression.variables x)--arbitraryOperator :: QC.Gen Operator.Operator-arbitraryOperator = QC.elements- [ Operator.Ampersand- , Operator.FullStop- , Operator.None- , Operator.NumberSign- , Operator.PlusSign- , Operator.QuestionMark- , Operator.Semicolon- , Operator.Solidus- ]--shrinkOperator :: Shrink Operator.Operator-shrinkOperator x = case x of- Operator.None -> []- _ -> [Operator.None]--arbitraryNonEmpty :: QC.Gen a -> QC.Gen (NonEmpty.NonEmpty a)-arbitraryNonEmpty g = (NonEmpty.:|) <$> g <*> QC.listOf g--shrinkNonEmpty :: Shrink a -> Shrink (NonEmpty.NonEmpty a)-shrinkNonEmpty f x = uncurry (NonEmpty.:|)- <$> shrinkTuple f (QC.shrinkList f) (NonEmpty.head x, NonEmpty.tail x)--arbitraryVariable :: QC.Gen Variable.Variable-arbitraryVariable = Variable.Variable <$> arbitraryName <*> arbitraryModifier--shrinkVariable :: Shrink Variable.Variable-shrinkVariable variable = uncurry Variable.Variable <$> shrinkTuple- shrinkName- shrinkModifier- (Variable.name variable, Variable.modifier variable)--shrinkTuple :: Shrink a -> Shrink b -> Shrink (a, b)-shrinkTuple f g (x, y) =- fmap (\a -> (a, y)) (f x) <> fmap (\b -> (x, b)) (g y)--arbitraryName :: QC.Gen Name.Name-arbitraryName = Name.Name <$> arbitraryNonEmpty arbitraryField--shrinkName :: Shrink Name.Name-shrinkName = fmap Name.Name . shrinkNonEmpty shrinkField . Name.fields--arbitraryField :: QC.Gen Field.Field-arbitraryField = Field.Field <$> arbitraryNonEmpty arbitraryFieldCharacter--shrinkField :: Shrink Field.Field-shrinkField =- fmap Field.Field . shrinkNonEmpty shrinkFieldCharacter . Field.characters--arbitraryFieldCharacter :: QC.Gen (Character.Character Field.Field)-arbitraryFieldCharacter = QC.oneof- [ Character.Encoded <$> arbitraryDigit <*> arbitraryDigit- , Character.Unencoded <$> QC.suchThat QC.arbitrary Parse.isFieldCharacter- ]--shrinkFieldCharacter :: Shrink (Character.Character Field.Field)-shrinkFieldCharacter x = case x of- Character.Encoded y z ->- uncurry Character.Encoded <$> shrinkTuple shrinkDigit shrinkDigit (y, z)- Character.Unencoded y ->- fmap Character.Unencoded . filter Parse.isFieldCharacter $ QC.shrink y--arbitraryDigit :: QC.Gen Digit.Digit-arbitraryDigit = QC.oneof- [ pure Digit.Ox0- , pure Digit.Ox1- , pure Digit.Ox2- , pure Digit.Ox3- , pure Digit.Ox4- , pure Digit.Ox5- , pure Digit.Ox6- , pure Digit.Ox7- , pure Digit.Ox8- , pure Digit.Ox9- , Digit.OxA <$> arbitraryCase- , Digit.OxB <$> arbitraryCase- , Digit.OxC <$> arbitraryCase- , Digit.OxD <$> arbitraryCase- , Digit.OxE <$> arbitraryCase- , Digit.OxF <$> arbitraryCase- ]--shrinkDigit :: Shrink Digit.Digit-shrinkDigit x = case x of- Digit.Ox0 -> []- Digit.Ox1 -> [Digit.Ox0]- Digit.Ox2 -> [Digit.Ox0]- Digit.Ox3 -> [Digit.Ox0]- Digit.Ox4 -> [Digit.Ox0]- Digit.Ox5 -> [Digit.Ox0]- Digit.Ox6 -> [Digit.Ox0]- Digit.Ox7 -> [Digit.Ox0]- Digit.Ox8 -> [Digit.Ox0]- Digit.Ox9 -> [Digit.Ox0]- Digit.OxA y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)- Digit.OxB y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)- Digit.OxC y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)- Digit.OxD y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)- Digit.OxE y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)- Digit.OxF y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)--arbitraryCase :: QC.Gen Case.Case-arbitraryCase = QC.elements [Case.Lower, Case.Upper]--shrinkCase :: Shrink Case.Case-shrinkCase x = case x of- Case.Lower -> []- Case.Upper -> [Case.Lower]--arbitraryModifier :: QC.Gen Modifier.Modifier-arbitraryModifier = QC.oneof- [ pure Modifier.Asterisk- , Modifier.Colon <$> arbitraryMaxLength- , pure Modifier.None- ]--shrinkModifier :: Shrink Modifier.Modifier-shrinkModifier x = case x of- Modifier.Asterisk -> [Modifier.None]- Modifier.Colon y -> Modifier.None : fmap Modifier.Colon (shrinkMaxLength y)- Modifier.None -> []--arbitraryMaxLength :: QC.Gen MaxLength.MaxLength-arbitraryMaxLength =- MaxLength.MaxLength <$> QC.suchThat QC.arbitrary Parse.isMaxLength--shrinkMaxLength :: Shrink MaxLength.MaxLength-shrinkMaxLength =- fmap MaxLength.MaxLength- . filter Parse.isMaxLength- . QC.shrink- . MaxLength.count--arbitraryLiteral :: QC.Gen Literal.Literal-arbitraryLiteral =- Literal.Literal <$> arbitraryNonEmpty arbitraryLiteralCharacter--shrinkLiteral :: Shrink Literal.Literal-shrinkLiteral =- fmap Literal.Literal- . shrinkNonEmpty shrinkLiteralCharacter- . Literal.characters--arbitraryLiteralCharacter :: QC.Gen (Character.Character Literal.Literal)-arbitraryLiteralCharacter = QC.oneof- [ Character.Encoded <$> arbitraryDigit <*> arbitraryDigit- , Character.Unencoded <$> QC.suchThat QC.arbitrary Parse.isLiteralCharacter- ]--shrinkLiteralCharacter :: Shrink (Character.Character Literal.Literal)-shrinkLiteralCharacter x = case x of- Character.Encoded y z ->- uncurry Character.Encoded <$> shrinkTuple shrinkDigit shrinkDigit (y, z)- Character.Unencoded y ->- fmap Character.Unencoded . filter Parse.isLiteralCharacter $ QC.shrink y