├── .gitignore ├── LICENSE ├── README.md ├── elm-package.json └── src └── QueryString.elm /.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Bogdan Paul Popa 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-querystring [![Build Status](https://travis-ci.org/Bogdanp/elm-querystring.svg)](https://travis-ci.org/Bogdanp/elm-querystring) 2 | 3 | ``` shell 4 | elm package install Bogdanp/elm-querystring 5 | ``` 6 | 7 | This library defines a `QueryString` type and functions parsing, 8 | rendering and manipulating values of that type. 9 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "A querystring parsing library", 4 | "repository": "https://github.com/Bogdanp/elm-querystring.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "QueryString" 11 | ], 12 | "dependencies": { 13 | "Bogdanp/elm-combine": "3.0.0 <= v < 4.0.0", 14 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 15 | "elm-lang/http": "1.0.0 <= v < 2.0.0" 16 | }, 17 | "elm-version": "0.18.0 <= v < 0.19.0" 18 | } 19 | -------------------------------------------------------------------------------- /src/QueryString.elm: -------------------------------------------------------------------------------- 1 | module QueryString 2 | exposing 3 | ( QueryString 4 | , parse 5 | , empty 6 | , render 7 | , add 8 | , remove 9 | , filter 10 | , all 11 | , one 12 | , many 13 | , string 14 | , int 15 | ) 16 | 17 | {-| This module exposes functions for working with query strings. 18 | 19 | You can manipulate `QueryString`s: 20 | 21 | > empty 22 | | |> add "a" "hello" 23 | | |> add "a" "goodbye" 24 | | |> add "b" "1" 25 | | |> render 26 | "?a=hello&a=goodbye&b=1" : String 27 | 28 | And you can parse and extract their parameters: 29 | 30 | > let 31 | | qs = parse "?a=1&a=2&a=test&b=hello" 32 | | a = qs |> many int "a" 33 | | b = qs |> one string "b" |> Maybe.withDefault "goodbye" 34 | | in 35 | | (a, b) 36 | ([1, 2], "hello") 37 | 38 | ## Types 39 | @docs QueryString 40 | 41 | ## Constructing QueryStrings 42 | @docs parse, empty 43 | 44 | ## Manipulating parameters 45 | @docs render, add, remove, filter 46 | 47 | ## Extracting parameters 48 | @docs all, one, many 49 | 50 | ### Parsers 51 | @docs string, int 52 | 53 | -} 54 | 55 | import Combine exposing (..) 56 | import Combine.Num 57 | import Dict exposing (Dict) 58 | import Http exposing (decodeUri, encodeUri) 59 | import String 60 | 61 | 62 | {-| Represents a parsed query string. 63 | -} 64 | type QueryString 65 | = QueryString (Dict String (List String)) 66 | 67 | 68 | {-| Construct an empty QueryString. 69 | -} 70 | empty : QueryString 71 | empty = 72 | QueryString Dict.empty 73 | 74 | 75 | {-| Turn a String into a QueryString. The initial `?` is optional. 76 | 77 | > parse "" 78 | QueryString (Dict.fromList []) : QueryString 79 | 80 | > parse "?a=1&b=c&a=2" 81 | QueryString (Dict.fromList [("a",["1","2"]),("b",["c"])]) 82 | : QueryString 83 | 84 | > parse "a=1&b=c&a=2" 85 | QueryString (Dict.fromList [("a",["1","2"]),("b",["c"])]) 86 | : QueryString 87 | 88 | -} 89 | parse : String -> QueryString 90 | parse = 91 | Combine.parse query 92 | >> Result.toMaybe 93 | >> Maybe.map (\( _, _, d ) -> QueryString d) 94 | >> Maybe.withDefault empty 95 | 96 | 97 | {-| Retrieve all of the values for a given key. 98 | 99 | > parse "?a=1&a=2" 100 | | |> all "a" 101 | ["1","2"] : List String 102 | 103 | > parse "?a=1&a=2" 104 | | |> all "b" 105 | [] : List String 106 | 107 | -} 108 | all : String -> QueryString -> List String 109 | all k (QueryString qs) = 110 | Dict.get k qs 111 | |> Maybe.withDefault [] 112 | 113 | 114 | {-| Retrieve a single value for a given key. Values are funneled through 115 | the given parser before being returned. 116 | 117 | > parse "?a=1&a=2" 118 | | |> one string "a" 119 | Just "2" : Maybe.Maybe String 120 | 121 | > parse "?a=1&a=2" 122 | | |> one int "a" 123 | Just 2 : Maybe.Maybe Int 124 | 125 | > parse "?a=1&a=c" 126 | | |> one int "a" 127 | Just 1 : Maybe.Maybe Int 128 | 129 | -} 130 | one : Parser () a -> String -> QueryString -> Maybe a 131 | one p k = 132 | many p k >> List.head 133 | 134 | 135 | {-| Retrieve zero or more values for some key. Values are funneled 136 | through the given parser before being returned. 137 | 138 | > parse "?a=1&a=c&a=2" 139 | | |> many int "a" 140 | [1,2] : List Int 141 | 142 | -} 143 | many : Parser () a -> String -> QueryString -> List a 144 | many p k = 145 | all k >> List.filterMap (maybeParse p) 146 | 147 | 148 | {-| A Parser that accepts any string. 149 | -} 150 | string : Parser s String 151 | string = 152 | regex ".*" 153 | 154 | 155 | {-| A Parser that accepts any integer. 156 | -} 157 | int : Parser s Int 158 | int = 159 | Combine.Num.int 160 | 161 | 162 | {-| Render a QueryString to a String. 163 | 164 | > render (parse "?a=1&b=a&a=c") 165 | "?a=1&a=c&b=a" : String 166 | 167 | -} 168 | render : QueryString -> String 169 | render (QueryString qs) = 170 | let 171 | flatten ( k, xs ) = 172 | List.map (\x -> k ++ "=" ++ encodeUri x) xs 173 | in 174 | Dict.toList qs 175 | |> List.concatMap flatten 176 | |> String.join "&" 177 | |> (++) "?" 178 | 179 | 180 | {-| Add a value to a key. 181 | 182 | > parse "?a=1&b=a&a=c" 183 | | |> add "a" "2" 184 | | |> render 185 | "?a=2&a=1&a=c&b=a" : String 186 | 187 | > parse "?a=1&b=a&a=c" 188 | | |> add "d" "hello" 189 | | |> render 190 | "?a=1&a=c&b=a&d=hello" : String 191 | 192 | -} 193 | add : String -> String -> QueryString -> QueryString 194 | add k v (QueryString qs) = 195 | let 196 | prepend xs = 197 | case xs of 198 | Nothing -> 199 | Just [ v ] 200 | 201 | Just xs -> 202 | Just (v :: xs) 203 | in 204 | Dict.update k prepend qs 205 | |> QueryString 206 | 207 | 208 | {-| Remove a key. 209 | 210 | > parse "?a=1&b=a&a=c" 211 | | |> remove "a" 212 | | |> render 213 | "?b=a" : String 214 | 215 | > parse "?a=1&b=a&a=c" 216 | | |> remove "c" 217 | | |> render 218 | "?a=1&a=c&b=a" : String 219 | 220 | -} 221 | remove : String -> QueryString -> QueryString 222 | remove k (QueryString qs) = 223 | Dict.remove k qs 224 | |> QueryString 225 | 226 | 227 | {-| Filter a key's values. 228 | 229 | > parse "?a=1&b=a&a=c" 230 | | |> filter "a" ((==) "1") 231 | | |> render 232 | "?a=1&b=a" : String 233 | 234 | -} 235 | filter : String -> (String -> Bool) -> QueryString -> QueryString 236 | filter k f (QueryString qs) = 237 | let 238 | remove xs = 239 | Maybe.map (List.filter f) xs 240 | in 241 | Dict.update k remove qs 242 | |> QueryString 243 | 244 | 245 | parameter : Parser s ( String, String ) 246 | parameter = 247 | let 248 | key = 249 | regex "[^=]+" 250 | 251 | value = 252 | regex "[^&]*" 253 | 254 | param k v = 255 | ( k, decodeUri v |> Maybe.withDefault "" ) 256 | in 257 | param <$> (key <* Combine.string "=") <*> value 258 | 259 | 260 | parameters : Parser s (List ( String, String )) 261 | parameters = 262 | sepBy (Combine.string "&") parameter <* (skip (Combine.string "#") <|> end) 263 | 264 | 265 | query : Parser s (Dict String (List String)) 266 | query = 267 | let 268 | prepend y xs = 269 | case xs of 270 | Nothing -> 271 | Just [ y ] 272 | 273 | Just xs -> 274 | Just (y :: xs) 275 | 276 | collect ( k, x ) d = 277 | Dict.update k (prepend x) d 278 | in 279 | List.foldr collect Dict.empty <$> (maybe (Combine.string "?") *> parameters) 280 | 281 | 282 | maybeParse : Parser () a -> String -> Maybe a 283 | maybeParse p = 284 | Combine.parse p 285 | >> Result.toMaybe 286 | >> Maybe.map (\( _, _, x ) -> x) 287 | --------------------------------------------------------------------------------