aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Pilfold <louis@lpil.uk>2021-09-15 21:21:55 +0100
committerLouis Pilfold <louis@lpil.uk>2021-09-15 21:21:55 +0100
commit103b76f91d4e6c4ef82a51c39895f0d025234e22 (patch)
tree7a5aaaee895a7a31b2ddafb7b9249f2efdb520ce
parentbc0768008492566626b8570766e0acaffcf3ed5d (diff)
downloadgleam_stdlib-103b76f91d4e6c4ef82a51c39895f0d025234e22.tar.gz
gleam_stdlib-103b76f91d4e6c4ef82a51c39895f0d025234e22.zip
Use Erlang uri parser
-rw-r--r--src/gleam/uri.gleam237
-rw-r--r--src/gleam_stdlib.erl38
-rw-r--r--test/gleam/uri_test.gleam25
3 files changed, 158 insertions, 142 deletions
diff --git a/src/gleam/uri.gleam b/src/gleam/uri.gleam
index caa02f9..86e0c3a 100644
--- a/src/gleam/uri.gleam
+++ b/src/gleam/uri.gleam
@@ -50,144 +50,135 @@ pub fn parse(uri_string: String) -> Result(Uri, Nil) {
do_parse(uri_string)
}
-fn do_parse(uri_string: String) -> Result(Uri, Nil) {
- // From https://tools.ietf.org/html/rfc3986#appendix-B
- let pattern =
- // 12 3 4 5 6 7 8
- "^(([a-z][a-z0-9\\+\\-\\.]*):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#.*)?"
- let matches =
- pattern
- |> regex_submatches(uri_string)
- |> pad_list(8)
-
- let #(scheme, authority, path, query, fragment) = case matches {
- [
- _scheme_with_colon,
- scheme,
- authority_with_slashes,
- _authority,
- path,
- query_with_question_mark,
- _query,
- fragment,
- ] -> #(
- scheme,
- authority_with_slashes,
- path,
- query_with_question_mark,
- fragment,
- )
- _ -> #(None, None, None, None, None)
- }
-
- let scheme = noneify_empty_string(scheme)
- let path = option.unwrap(path, "")
- let query = noneify_query(query)
- let #(userinfo, host, port) = split_authority(authority)
- let fragment =
- fragment
- |> option.to_result(Nil)
- |> result.then(string.pop_grapheme)
- |> result.map(pair.second)
- |> option.from_result
- let port = case port {
- None -> default_port(scheme)
- _ -> port
- }
- let scheme =
- scheme
- |> noneify_empty_string
- |> option.map(string.lowercase)
- Ok(Uri(
- scheme: scheme,
- userinfo: userinfo,
- host: host,
- port: port,
- path: path,
- query: query,
- fragment: fragment,
- ))
+if erlang {
+ external fn do_parse(String) -> Result(Uri, Nil) =
+ "gleam_stdlib" "uri_parse"
}
-fn default_port(scheme: Option(String)) -> Option(Int) {
- case scheme {
- Some("ftp") -> Some(21)
- Some("sftp") -> Some(22)
- Some("tftp") -> Some(69)
- Some("http") -> Some(80)
- Some("https") -> Some(443)
- Some("ldap") -> Some(389)
- _ -> None
+if javascript {
+ fn do_parse(uri_string: String) -> Result(Uri, Nil) {
+ // From https://tools.ietf.org/html/rfc3986#appendix-B
+ let pattern =
+ // 12 3 4 5 6 7 8
+ "^(([a-z][a-z0-9\\+\\-\\.]*):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#.*)?"
+ let matches =
+ pattern
+ |> regex_submatches(uri_string)
+ |> pad_list(8)
+
+ let #(scheme, authority, path, query, fragment) = case matches {
+ [
+ _scheme_with_colon,
+ scheme,
+ authority_with_slashes,
+ _authority,
+ path,
+ query_with_question_mark,
+ _query,
+ fragment,
+ ] -> #(
+ scheme,
+ authority_with_slashes,
+ path,
+ query_with_question_mark,
+ fragment,
+ )
+ _ -> #(None, None, None, None, None)
+ }
+
+ let scheme = noneify_empty_string(scheme)
+ let path = option.unwrap(path, "")
+ let query = noneify_query(query)
+ let #(userinfo, host, port) = split_authority(authority)
+ let fragment =
+ fragment
+ |> option.to_result(Nil)
+ |> result.then(string.pop_grapheme)
+ |> result.map(pair.second)
+ |> option.from_result
+ let scheme =
+ scheme
+ |> noneify_empty_string
+ |> option.map(string.lowercase)
+ Ok(Uri(
+ scheme: scheme,
+ userinfo: userinfo,
+ host: host,
+ port: port,
+ path: path,
+ query: query,
+ fragment: fragment,
+ ))
}
-}
-fn regex_submatches(pattern: String, string: String) -> List(Option(String)) {
- pattern
- |> regex.compile(regex.Options(case_insensitive: True, multi_line: False))
- |> result.nil_error
- |> result.map(regex.scan(_, string))
- |> result.then(list.head)
- |> result.map(fn(m: regex.Match) { m.submatches })
- |> result.unwrap([])
-}
+ fn regex_submatches(pattern: String, string: String) -> List(Option(String)) {
+ pattern
+ |> regex.compile(regex.Options(case_insensitive: True, multi_line: False))
+ |> result.nil_error
+ |> result.map(regex.scan(_, string))
+ |> result.then(list.head)
+ |> result.map(fn(m: regex.Match) { m.submatches })
+ |> result.unwrap([])
+ }
-fn noneify_query(x: Option(String)) -> Option(String) {
- case x {
- None -> None
- Some(x) ->
- case string.pop_grapheme(x) {
- Ok(#("?", query)) -> Some(query)
- _ -> None
- }
+ fn noneify_query(x: Option(String)) -> Option(String) {
+ case x {
+ None -> None
+ Some(x) ->
+ case string.pop_grapheme(x) {
+ Ok(#("?", query)) -> Some(query)
+ _ -> None
+ }
+ }
}
-}
-fn noneify_empty_string(x: Option(String)) -> Option(String) {
- case x {
- Some("") | None -> None
- Some(_) -> x
+ fn noneify_empty_string(x: Option(String)) -> Option(String) {
+ case x {
+ Some("") | None -> None
+ Some(_) -> x
+ }
}
-}
-// Split an authority into its userinfo, host and port parts.
-fn split_authority(
- authority: Option(String),
-) -> #(Option(String), Option(String), Option(Int)) {
- case option.unwrap(authority, "") {
- "" -> #(None, None, None)
- "//" -> #(None, Some(""), None)
- authority -> {
- let matches =
- "^(//)?((.*)@)?(\\[[a-zA-Z0-9:.]*\\]|[^:]*)(:(\\d*))?"
- |> regex_submatches(authority)
- |> pad_list(6)
- case matches {
- [_, _, userinfo, host, _, port] -> {
- let userinfo = noneify_empty_string(userinfo)
- let host = noneify_empty_string(host)
- let port =
- port
- |> option.unwrap("")
- |> int.parse
- |> option.from_result
- #(userinfo, host, port)
+ // Split an authority into its userinfo, host and port parts.
+ fn split_authority(
+ authority: Option(String),
+ ) -> #(Option(String), Option(String), Option(Int)) {
+ case option.unwrap(authority, "") {
+ "" -> #(None, None, None)
+ "//" -> #(None, Some(""), None)
+ authority -> {
+ let matches =
+ "^(//)?((.*)@)?(\\[[a-zA-Z0-9:.]*\\]|[^:]*)(:(\\d*))?"
+ |> regex_submatches(authority)
+ |> pad_list(6)
+ case matches {
+ [_, _, userinfo, host, _, port] -> {
+ let userinfo = noneify_empty_string(userinfo)
+ let host = noneify_empty_string(host)
+ let port =
+ port
+ |> option.unwrap("")
+ |> int.parse
+ |> option.from_result
+ #(userinfo, host, port)
+ }
+ _ -> #(None, None, None)
}
- _ -> #(None, None, None)
}
}
}
-}
-fn pad_list(list: List(Option(a)), size: Int) -> List(Option(a)) {
- list
- |> list.append(list.repeat(None, extra_required(list, size)))
-}
+ fn pad_list(list: List(Option(a)), size: Int) -> List(Option(a)) {
+ list
+ |> list.append(list.repeat(None, extra_required(list, size)))
+ }
-fn extra_required(list: List(a), remaining: Int) -> Int {
- case list {
- _ if remaining == 0 -> 0
- [] -> remaining
- [_, ..xs] -> extra_required(xs, remaining - 1)
+ fn extra_required(list: List(a), remaining: Int) -> Int {
+ case list {
+ _ if remaining == 0 -> 0
+ [] -> remaining
+ [_, ..xs] -> extra_required(xs, remaining - 1)
+ }
}
}
diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl
index f607497..9a27c18 100644
--- a/src/gleam_stdlib.erl
+++ b/src/gleam_stdlib.erl
@@ -6,7 +6,7 @@
decode_float/1, decode_thunk/1, decode_list/1, decode_option/2,
decode_field/2, parse_int/1, parse_float/1, less_than/2,
string_pop_grapheme/1, string_starts_with/2, wrap_list/1,
- string_ends_with/2, string_pad/4, decode_map/1,
+ string_ends_with/2, string_pad/4, decode_map/1, uri_parse/1,
bit_string_int_to_u32/1, bit_string_int_from_u32/1, decode_result/1,
bit_string_slice/3, decode_bit_string/1, compile_regex/2, regex_scan/2,
percent_encode/1, percent_decode/1, regex_check/2, regex_split/2,
@@ -298,3 +298,39 @@ check_utf8(Cs) ->
{error, _, _} -> {error, nil};
_ -> {ok, Cs}
end.
+
+uri_parse(String) ->
+ case uri_string:parse(String) of
+ {error, _, _} -> {error, nil};
+ Uri ->
+ % #{
+ % host := Host, path := Path, port := Port, query := Query,
+ % scheme := Scheme, userinfo := Userinfo
+ % } ->
+ % scheme: Option(String),
+ % userinfo: Option(String),
+ % host: Option(String),
+ % port: Option(Int),
+ % path: String,
+ % query: Option(String),
+ % fragment: Option(String),
+ {ok, {uri,
+ maps_get_optional(Uri, scheme),
+ maps_get_optional(Uri, userinfo),
+ maps_get_optional(Uri, host),
+ maps_get_optional(Uri, port),
+ maps_get_or(Uri, path, <<>>),
+ maps_get_optional(Uri, query),
+ maps_get_optional(Uri, fragment)
+ }}
+ end.
+
+maps_get_optional(Map, Key) ->
+ try {some, maps:get(Key, Map)}
+ catch _:_ -> none
+ end.
+
+maps_get_or(Map, Key, Default) ->
+ try maps:get(Key, Map)
+ catch _:_ -> Default
+ end.
diff --git a/test/gleam/uri_test.gleam b/test/gleam/uri_test.gleam
index 78038c1..bcf0d18 100644
--- a/test/gleam/uri_test.gleam
+++ b/test/gleam/uri_test.gleam
@@ -41,17 +41,6 @@ pub fn parse_only_host_test() {
should.equal(parsed.fragment, None)
}
-pub fn colon_uri_test() {
- assert Ok(parsed) = uri.parse("::")
- should.equal(parsed.scheme, None)
- should.equal(parsed.userinfo, None)
- should.equal(parsed.host, None)
- should.equal(parsed.port, None)
- should.equal(parsed.path, "::")
- should.equal(parsed.query, None)
- should.equal(parsed.fragment, None)
-}
-
pub fn parse_scheme_test() {
uri.parse("http://one.com/path/to/something?one=two&two=one#fragment")
|> should.equal(Ok(uri.Uri(
@@ -60,7 +49,7 @@ pub fn parse_scheme_test() {
path: "/path/to/something",
query: Some("one=two&two=one"),
fragment: Some("fragment"),
- port: Some(80),
+ port: None,
userinfo: None,
)))
}
@@ -73,7 +62,7 @@ pub fn parse_https_scheme_test() {
path: "",
query: None,
fragment: None,
- port: Some(443),
+ port: None,
userinfo: None,
)))
}
@@ -101,7 +90,7 @@ pub fn parse_ftp_scheme_test() {
path: "/my_directory/my_file.txt",
query: None,
fragment: None,
- port: Some(21),
+ port: None,
)))
}
@@ -115,7 +104,7 @@ pub fn parse_sftp_scheme_test() {
path: "/my_directory/my_file.txt",
query: None,
fragment: None,
- port: Some(22),
+ port: None,
)))
}
@@ -129,7 +118,7 @@ pub fn parse_tftp_scheme_test() {
path: "/my_directory/my_file.txt",
query: None,
fragment: None,
- port: Some(69),
+ port: None,
)))
}
@@ -143,7 +132,7 @@ pub fn parse_ldap_scheme_test() {
path: "/dc=example,dc=com",
query: Some("?sub?(givenName=John)"),
fragment: None,
- port: Some(389),
+ port: None,
)))
}
@@ -157,7 +146,7 @@ pub fn parse_ldap_2_scheme_test() {
path: "/cn=John%20Doe,dc=foo,dc=com",
query: None,
fragment: None,
- port: Some(389),
+ port: None,
)))
}