feat: initial commit for uri parser
This commit is contained in:
		
							
								
								
									
										23
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					name: test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - master
 | 
				
			||||||
 | 
					      - main
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  test:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					      - uses: erlef/setup-beam@v1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          otp-version: "27.1.2"
 | 
				
			||||||
 | 
					          gleam-version: "1.12.0"
 | 
				
			||||||
 | 
					          rebar3-version: "3"
 | 
				
			||||||
 | 
					          # elixir-version: "1"
 | 
				
			||||||
 | 
					      - run: gleam deps download
 | 
				
			||||||
 | 
					      - run: gleam test
 | 
				
			||||||
 | 
					      - run: gleam format --check src test
 | 
				
			||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					*.beam
 | 
				
			||||||
 | 
					*.ez
 | 
				
			||||||
 | 
					/build
 | 
				
			||||||
 | 
					erl_crash.dump
 | 
				
			||||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								README.md
									
									
									
									
									
								
							@@ -1,2 +1,27 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# uri
 | 
					# uri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Uri (RFC 3986) library for Gleam
 | 
					Uri (RFC 3986) library for Gleam
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://hex.pm/packages/uri)
 | 
				
			||||||
 | 
					[](https://hexdocs.pm/uri/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					gleam add uri@1
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					```gleam
 | 
				
			||||||
 | 
					import uri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn main() -> Nil {
 | 
				
			||||||
 | 
					  // TODO: An example of the project in use
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Further documentation can be found at <https://hexdocs.pm/uri>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					gleam run   # Run the project
 | 
				
			||||||
 | 
					gleam test  # Run the tests
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								gleam.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								gleam.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					name = "uri"
 | 
				
			||||||
 | 
					version = "1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Fill out these fields if you intend to generate HTML documentation or publish
 | 
				
			||||||
 | 
					# your project to the Hex package manager.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# description = ""
 | 
				
			||||||
 | 
					# licences = ["Apache-2.0"]
 | 
				
			||||||
 | 
					# repository = { type = "github", user = "", repo = "" }
 | 
				
			||||||
 | 
					# links = [{ title = "Website", href = "" }]
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# For a full reference of all the available options, you can have a look at
 | 
				
			||||||
 | 
					# https://gleam.run/writing-gleam/gleam-toml/.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					gleam_stdlib = ">= 0.44.0 and < 2.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dev-dependencies]
 | 
				
			||||||
 | 
					gleeunit = ">= 1.0.0 and < 2.0.0"
 | 
				
			||||||
 | 
					startest = ">= 0.7.0 and < 1.0.0"
 | 
				
			||||||
							
								
								
									
										32
									
								
								manifest.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								manifest.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					# This file was generated by Gleam
 | 
				
			||||||
 | 
					# You typically do not need to edit this file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					packages = [
 | 
				
			||||||
 | 
					  { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
 | 
				
			||||||
 | 
					  { name = "bigben", version = "1.0.1", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "bigben", source = "hex", outer_checksum = "190E489610A80D76C48BACC75EB8314BD184FF0220AB0F251ABE760B993B91BB" },
 | 
				
			||||||
 | 
					  { name = "birl", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "2AC7BA26F998E3DFADDB657148BD5DDFE966958AD4D6D6957DD0D22E5B56C400" },
 | 
				
			||||||
 | 
					  { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" },
 | 
				
			||||||
 | 
					  { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },
 | 
				
			||||||
 | 
					  { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
 | 
				
			||||||
 | 
					  { name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" },
 | 
				
			||||||
 | 
					  { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
 | 
				
			||||||
 | 
					  { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" },
 | 
				
			||||||
 | 
					  { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" },
 | 
				
			||||||
 | 
					  { name = "gleam_otp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "7987CBEBC8060B88F14575DEF546253F3116EBE2A5DA6FD82F38243FCE97C54B" },
 | 
				
			||||||
 | 
					  { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" },
 | 
				
			||||||
 | 
					  { name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" },
 | 
				
			||||||
 | 
					  { name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" },
 | 
				
			||||||
 | 
					  { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
 | 
				
			||||||
 | 
					  { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
 | 
				
			||||||
 | 
					  { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" },
 | 
				
			||||||
 | 
					  { name = "ranger", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_yielder"], otp_app = "ranger", source = "hex", outer_checksum = "C8988E8F8CDBD3E7F4D8F2E663EF76490390899C2B2885A6432E942495B3E854" },
 | 
				
			||||||
 | 
					  { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" },
 | 
				
			||||||
 | 
					  { name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
 | 
				
			||||||
 | 
					  { name = "startest", version = "0.7.0", build_tools = ["gleam"], requirements = ["argv", "bigben", "birl", "exception", "gleam_community_ansi", "gleam_erlang", "gleam_javascript", "gleam_regexp", "gleam_stdlib", "glint", "simplifile", "tom"], otp_app = "startest", source = "hex", outer_checksum = "71B9CB82C4B8779A4BD54C7151DF7D0B0F778D0DDE805B782B44EFA7BA8F50DA" },
 | 
				
			||||||
 | 
					  { name = "tom", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "74D0C5A3761F7A7D06994755D4D5AD854122EF8E9F9F76A3E7547606D8C77091" },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[requirements]
 | 
				
			||||||
 | 
					gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
 | 
				
			||||||
 | 
					gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
 | 
				
			||||||
 | 
					startest = { version = ">= 0.7.0 and < 1.0.0" }
 | 
				
			||||||
							
								
								
									
										733
									
								
								src/uri.gleam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										733
									
								
								src/uri.gleam
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,733 @@
 | 
				
			|||||||
 | 
					import gleam/bool
 | 
				
			||||||
 | 
					import gleam/int
 | 
				
			||||||
 | 
					import gleam/list.{Continue, Stop}
 | 
				
			||||||
 | 
					import gleam/option.{type Option, None, Some}
 | 
				
			||||||
 | 
					import gleam/result
 | 
				
			||||||
 | 
					import gleam/string
 | 
				
			||||||
 | 
					import gleam/uri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type Uri {
 | 
				
			||||||
 | 
					  Uri(
 | 
				
			||||||
 | 
					    scheme: Option(String),
 | 
				
			||||||
 | 
					    userinfo: Option(String),
 | 
				
			||||||
 | 
					    host: Option(String),
 | 
				
			||||||
 | 
					    port: Option(Int),
 | 
				
			||||||
 | 
					    path: String,
 | 
				
			||||||
 | 
					    query: Option(String),
 | 
				
			||||||
 | 
					    fragment: Option(String),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const empty_uri = Uri(None, None, None, None, "", None, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn main() {
 | 
				
			||||||
 | 
					  // parse("test://asd@test!te%20ste/a/b/c?a=31#asd") |> echo
 | 
				
			||||||
 | 
					  // parse("test:/blah") |> echo
 | 
				
			||||||
 | 
					  parse("foo://user:password@localhost") |> echo
 | 
				
			||||||
 | 
					  uri.parse("foo://user:password@localhost") |> echo
 | 
				
			||||||
 | 
					  // uri.parse("../../") |> echo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn parse(uri: String) -> Result(Uri, Nil) {
 | 
				
			||||||
 | 
					  case parse_scheme(uri) {
 | 
				
			||||||
 | 
					    Ok(#(scheme, rest)) -> {
 | 
				
			||||||
 | 
					      use #(rel_part, rest) <- result.try(parse_hier_part(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      use #(query, rest) <- result.try(parse_query(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      use #(fragment, rest) <- result.try(parse_fragment(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case rest {
 | 
				
			||||||
 | 
					        "" -> Ok(combine_uris([scheme, rel_part, query, fragment]))
 | 
				
			||||||
 | 
					        _ -> Error(Nil)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Error(_) -> {
 | 
				
			||||||
 | 
					      use #(rel_part, rest) <- result.try(parse_relative_part(uri))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      use #(query, rest) <- result.try(parse_query(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      use #(fragment, rest) <- result.try(parse_fragment(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case rest {
 | 
				
			||||||
 | 
					        "" -> Ok(combine_uris([rel_part, query, fragment]))
 | 
				
			||||||
 | 
					        _ -> Error(Nil)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_query(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "?" <> rest -> {
 | 
				
			||||||
 | 
					      let #(query, rest) = get_multiple_optional(parse_query_fragment, rest)
 | 
				
			||||||
 | 
					      Ok(#(Uri(..empty_uri, query: Some(query)), rest))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Ok(#(empty_uri, str))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_fragment(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "#" <> rest -> {
 | 
				
			||||||
 | 
					      let #(fragment, rest) = get_multiple_optional(parse_query_fragment, rest)
 | 
				
			||||||
 | 
					      Ok(#(Uri(..empty_uri, fragment: Some(fragment)), rest))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Ok(#(empty_uri, str))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_hier_part(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [parse_authority, parse_absolute, parse_rootless, parse_empty],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_relative_part(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [parse_authority, parse_absolute, parse_noscheme, parse_empty],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_absolute(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "/" <> rest -> {
 | 
				
			||||||
 | 
					      use #(seg1, rest) <- result.try(do_parse_segment_nz(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let #(segs, rest) =
 | 
				
			||||||
 | 
					        get_multiple_optional(
 | 
				
			||||||
 | 
					          fn(str) {
 | 
				
			||||||
 | 
					            case str {
 | 
				
			||||||
 | 
					              "/" <> rest -> {
 | 
				
			||||||
 | 
					                do_parse_segment(rest, do_parse_pchar, "/")
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              _ -> Error(Nil)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          rest,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Ok(#(Uri(None, None, None, None, "/" <> seg1 <> segs, None, None), rest))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_rootless(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  use #(seg1, rest) <- result.try(do_parse_segment_nz(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let #(segs, rest) =
 | 
				
			||||||
 | 
					    get_multiple_optional(
 | 
				
			||||||
 | 
					      fn(str) {
 | 
				
			||||||
 | 
					        case str {
 | 
				
			||||||
 | 
					          "/" <> rest -> {
 | 
				
			||||||
 | 
					            do_parse_segment(rest, do_parse_pchar, "/")
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          _ -> Error(Nil)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      rest,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Ok(#(Uri(None, None, None, None, seg1 <> segs, None, None), rest))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_noscheme(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  use #(seg1, rest) <- result.try(do_parse_segment_nz_nc(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let #(segs, rest) =
 | 
				
			||||||
 | 
					    get_multiple_optional(
 | 
				
			||||||
 | 
					      fn(str) {
 | 
				
			||||||
 | 
					        case str {
 | 
				
			||||||
 | 
					          "/" <> rest -> {
 | 
				
			||||||
 | 
					            do_parse_segment(rest, do_parse_pchar, "/")
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          _ -> Error(Nil)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      rest,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Ok(#(Uri(None, None, None, None, seg1 <> segs, None, None), rest))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_multiple_optional(opt_fn, str: String) {
 | 
				
			||||||
 | 
					  case get_multiple(opt_fn, str) {
 | 
				
			||||||
 | 
					    Error(_) -> #("", str)
 | 
				
			||||||
 | 
					    Ok(r) -> r
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_empty(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  Ok(#(Uri(None, None, None, None, "", None, None), str))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_authority(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "//" <> rest -> {
 | 
				
			||||||
 | 
					      parse_authority_part(rest)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_authority_part(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  let #(ui, rest) = case parse_userinfo(str, "") {
 | 
				
			||||||
 | 
					    Ok(#(ui, rest)) -> #(Some(ui), rest)
 | 
				
			||||||
 | 
					    Error(_) -> #(None, str)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  use #(host, rest) <- result.try(parse_host(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let #(port, rest) = case parse_port(rest) {
 | 
				
			||||||
 | 
					    Ok(#("", rest)) -> #(None, rest)
 | 
				
			||||||
 | 
					    Error(_) -> #(None, rest)
 | 
				
			||||||
 | 
					    Ok(#(port, rest)) -> {
 | 
				
			||||||
 | 
					      let assert Ok(port) = int.parse(port)
 | 
				
			||||||
 | 
					      #(Some(port), rest)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let #(path, rest) = parse_abs_empty(rest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Ok(#(Uri(None, ui, Some(host), port, path, None, None), rest))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_port(str: String) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    ":" <> rest -> {
 | 
				
			||||||
 | 
					      Ok(parse_digits(rest, ""))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_digits(str: String, digits: String) {
 | 
				
			||||||
 | 
					  case parse_digit(str) {
 | 
				
			||||||
 | 
					    Ok(#(d, rest)) -> {
 | 
				
			||||||
 | 
					      parse_digits(rest, digits <> d)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Error(_) -> #(digits, str)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_host(str: String) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [parse_ip_literal, parse_ipv4, parse_reg_name],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_ip_literal(str: String) {
 | 
				
			||||||
 | 
					  Error(Nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_multiple(
 | 
				
			||||||
 | 
					  to_run: fn(String) -> Result(#(String, String), Nil),
 | 
				
			||||||
 | 
					  str: String,
 | 
				
			||||||
 | 
					) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case do_get_multiple(to_run, str, "") {
 | 
				
			||||||
 | 
					    Ok(#("", _)) | Error(Nil) -> Error(Nil)
 | 
				
			||||||
 | 
					    Ok(#(r, rest)) -> Ok(#(r, rest))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_get_multiple(
 | 
				
			||||||
 | 
					  to_run: fn(String) -> Result(#(String, String), Nil),
 | 
				
			||||||
 | 
					  str: String,
 | 
				
			||||||
 | 
					  ret: String,
 | 
				
			||||||
 | 
					) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "" -> Ok(#(ret, str))
 | 
				
			||||||
 | 
					    _ ->
 | 
				
			||||||
 | 
					      case to_run(str) {
 | 
				
			||||||
 | 
					        Ok(#(r, rest)) -> do_get_multiple(to_run, rest, ret <> r)
 | 
				
			||||||
 | 
					        Error(_) -> Ok(#(ret, str))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_query_fragment(str: String) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      do_parse_pchar,
 | 
				
			||||||
 | 
					      fn(str: String) {
 | 
				
			||||||
 | 
					        case str {
 | 
				
			||||||
 | 
					          "/" as l <> rest | "?" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					          _ -> Error(Nil)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_abs_empty(str: String) -> #(String, String) {
 | 
				
			||||||
 | 
					  get_multiple_optional(
 | 
				
			||||||
 | 
					    fn(str) {
 | 
				
			||||||
 | 
					      case str {
 | 
				
			||||||
 | 
					        "/" <> rest -> {
 | 
				
			||||||
 | 
					          do_parse_segment(rest, do_parse_pchar, "/")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        _ -> Error(Nil)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    str,
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_segment(
 | 
				
			||||||
 | 
					  str: String,
 | 
				
			||||||
 | 
					  char_fn,
 | 
				
			||||||
 | 
					  segment: String,
 | 
				
			||||||
 | 
					) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case char_fn(str) {
 | 
				
			||||||
 | 
					    Error(Nil) | Ok(#("", _)) -> Ok(#(segment, str))
 | 
				
			||||||
 | 
					    Ok(#(l, rest)) -> do_parse_segment(rest, char_fn, segment <> l)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_segment_nz(str: String) {
 | 
				
			||||||
 | 
					  use #(char1, rest) <- result.try(do_parse_pchar(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  use #(chars, rest) <- result.try(do_parse_segment(rest, do_parse_pchar, char1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Ok(#(chars, rest))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_segment_nz_nc(str: String) {
 | 
				
			||||||
 | 
					  use #(char1, rest) <- result.try(do_parse_pchar_nc(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  use #(chars, rest) <- result.try(do_parse_segment(
 | 
				
			||||||
 | 
					    rest,
 | 
				
			||||||
 | 
					    do_parse_pchar_nc,
 | 
				
			||||||
 | 
					    char1,
 | 
				
			||||||
 | 
					  ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Ok(#(chars, rest))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_pchar(str: String) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      parse_unreserved,
 | 
				
			||||||
 | 
					      parse_pct_encoded,
 | 
				
			||||||
 | 
					      parse_sub_delim,
 | 
				
			||||||
 | 
					      fn(str: String) {
 | 
				
			||||||
 | 
					        case str {
 | 
				
			||||||
 | 
					          ":" as l <> rest | "@" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					          _ -> Error(Nil)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_pchar_nc(str: String) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      parse_unreserved,
 | 
				
			||||||
 | 
					      parse_pct_encoded,
 | 
				
			||||||
 | 
					      parse_sub_delim,
 | 
				
			||||||
 | 
					      fn(str: String) {
 | 
				
			||||||
 | 
					        case str {
 | 
				
			||||||
 | 
					          "@" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					          _ -> Error(Nil)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_reg_name(str: String) {
 | 
				
			||||||
 | 
					  // can't error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  case do_parse_reg_name(str, "") {
 | 
				
			||||||
 | 
					    Error(Nil) -> Ok(#("", str))
 | 
				
			||||||
 | 
					    Ok(#(reg_name, rest)) -> Ok(#(reg_name, rest))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_reg_name(str: String, reg_name: String) {
 | 
				
			||||||
 | 
					  case
 | 
				
			||||||
 | 
					    list.fold_until(
 | 
				
			||||||
 | 
					      [parse_unreserved, parse_pct_encoded, parse_sub_delim],
 | 
				
			||||||
 | 
					      Error(Nil),
 | 
				
			||||||
 | 
					      get_parser_fn(str),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    Error(Nil) | Ok(#("", _)) -> Ok(#(reg_name, str))
 | 
				
			||||||
 | 
					    Ok(#(l, rest)) -> do_parse_reg_name(rest, reg_name <> l)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_pct_encoded(str: String) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "%" <> rest -> {
 | 
				
			||||||
 | 
					      use #(hex1, rest) <- result.try(parse_hex_digit(rest))
 | 
				
			||||||
 | 
					      use #(hex2, rest) <- result.try(parse_hex_digit(rest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Ok(#("%" <> hex1 <> hex2, rest))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_sub_delim(str: String) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "!" as l <> rest
 | 
				
			||||||
 | 
					    | "$" as l <> rest
 | 
				
			||||||
 | 
					    | "&" as l <> rest
 | 
				
			||||||
 | 
					    | "'" as l <> rest
 | 
				
			||||||
 | 
					    | "(" as l <> rest
 | 
				
			||||||
 | 
					    | ")" as l <> rest
 | 
				
			||||||
 | 
					    | "*" as l <> rest
 | 
				
			||||||
 | 
					    | "+" as l <> rest
 | 
				
			||||||
 | 
					    | "," as l <> rest
 | 
				
			||||||
 | 
					    | ";" as l <> rest
 | 
				
			||||||
 | 
					    | "=" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_ipv4(str: String) {
 | 
				
			||||||
 | 
					  use #(oct1, rest) <- result.try(parse_dec_octet(str))
 | 
				
			||||||
 | 
					  use rest <- result.try(case rest {
 | 
				
			||||||
 | 
					    "." <> rest -> Ok(rest)
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  use #(oct2, rest) <- result.try(parse_dec_octet(rest))
 | 
				
			||||||
 | 
					  use rest <- result.try(case rest {
 | 
				
			||||||
 | 
					    "." <> rest -> Ok(rest)
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  use #(oct3, rest) <- result.try(parse_dec_octet(rest))
 | 
				
			||||||
 | 
					  use rest <- result.try(case rest {
 | 
				
			||||||
 | 
					    "." <> rest -> Ok(rest)
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  use #(oct4, rest) <- result.try(parse_dec_octet(rest))
 | 
				
			||||||
 | 
					  Ok(#(oct1 <> "." <> oct2 <> "." <> oct3 <> "." <> oct4, rest))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_dec_octet(str: String) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  let matches = [
 | 
				
			||||||
 | 
					    ["2", "5", "012345"],
 | 
				
			||||||
 | 
					    ["2", "01234", "0123456789"],
 | 
				
			||||||
 | 
					    ["1", "0123456789", "0123456789"],
 | 
				
			||||||
 | 
					    ["123456789", "0123456789"],
 | 
				
			||||||
 | 
					    ["0123456789"],
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  list.fold_until(matches, Error(Nil), fn(_, chars) {
 | 
				
			||||||
 | 
					    case
 | 
				
			||||||
 | 
					      list.fold_until(chars, #("", str), fn(acc, charset) {
 | 
				
			||||||
 | 
					        let #(octet, str) = acc
 | 
				
			||||||
 | 
					        case string.pop_grapheme(str) {
 | 
				
			||||||
 | 
					          Error(_) -> Stop(#("", ""))
 | 
				
			||||||
 | 
					          Ok(#(char, rest)) -> {
 | 
				
			||||||
 | 
					            case string.contains(charset, char) {
 | 
				
			||||||
 | 
					              True -> Continue(#(octet <> char, rest))
 | 
				
			||||||
 | 
					              False -> Stop(#("", ""))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      #("", _) -> Continue(Error(Nil))
 | 
				
			||||||
 | 
					      #(octet, rest) -> Stop(Ok(#(octet, rest)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_userinfo(
 | 
				
			||||||
 | 
					  str: String,
 | 
				
			||||||
 | 
					  userinfo: String,
 | 
				
			||||||
 | 
					) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "@" <> rest -> Ok(#(userinfo, rest))
 | 
				
			||||||
 | 
					    "" -> Error(Nil)
 | 
				
			||||||
 | 
					    _ -> {
 | 
				
			||||||
 | 
					      use #(part, rest) <- result.try(list.fold_until(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          parse_unreserved,
 | 
				
			||||||
 | 
					          parse_pct_encoded,
 | 
				
			||||||
 | 
					          parse_sub_delim,
 | 
				
			||||||
 | 
					          fn(str: String) {
 | 
				
			||||||
 | 
					            case str {
 | 
				
			||||||
 | 
					              ":" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					              _ -> Error(Nil)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        Error(Nil),
 | 
				
			||||||
 | 
					        get_parser_fn(str),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      parse_userinfo(rest, userinfo <> part)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_scheme(str: String) -> Result(#(Uri, String), Nil) {
 | 
				
			||||||
 | 
					  case parse_alpha(str) {
 | 
				
			||||||
 | 
					    Ok(#(first, rest)) -> {
 | 
				
			||||||
 | 
					      case do_parse_scheme(rest, first) {
 | 
				
			||||||
 | 
					        Error(_) -> Error(Nil)
 | 
				
			||||||
 | 
					        Ok(#(scheme, rest)) ->
 | 
				
			||||||
 | 
					          Ok(#(Uri(Some(scheme), None, None, None, "", None, None), rest))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn do_parse_scheme(
 | 
				
			||||||
 | 
					  str: String,
 | 
				
			||||||
 | 
					  scheme: String,
 | 
				
			||||||
 | 
					) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    ":" <> rest -> Ok(#(scheme, rest))
 | 
				
			||||||
 | 
					    "" -> Error(Nil)
 | 
				
			||||||
 | 
					    _ -> {
 | 
				
			||||||
 | 
					      use #(part, rest) <- result.try(list.fold_until(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          parse_alpha,
 | 
				
			||||||
 | 
					          parse_digit,
 | 
				
			||||||
 | 
					          fn(str) {
 | 
				
			||||||
 | 
					            case str {
 | 
				
			||||||
 | 
					              "+" as l <> rest | "-" as l <> rest | "." as l <> rest ->
 | 
				
			||||||
 | 
					                Ok(#(l, rest))
 | 
				
			||||||
 | 
					              _ -> Error(Nil)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        Error(Nil),
 | 
				
			||||||
 | 
					        get_parser_fn(str),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      do_parse_scheme(rest, scheme <> part)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_parser_fn(
 | 
				
			||||||
 | 
					  str: String,
 | 
				
			||||||
 | 
					) -> fn(a, fn(String) -> Result(b, c)) -> list.ContinueOrStop(Result(b, Nil)) {
 | 
				
			||||||
 | 
					  fn(_, parse_fn) {
 | 
				
			||||||
 | 
					    case parse_fn(str) {
 | 
				
			||||||
 | 
					      Ok(r) -> Stop(Ok(r))
 | 
				
			||||||
 | 
					      Error(_) -> Continue(Error(Nil))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_hex_digits(str, min, max) {
 | 
				
			||||||
 | 
					  use <- bool.guard(when: min < 0 || max <= 0 || min > max, return: Error(Nil))
 | 
				
			||||||
 | 
					  case
 | 
				
			||||||
 | 
					    list.repeat("", max)
 | 
				
			||||||
 | 
					    |> list.fold_until(Ok(#("", str, 0)), fn(acc, _) {
 | 
				
			||||||
 | 
					      let assert Ok(#(hex, str, i)) = acc
 | 
				
			||||||
 | 
					      case parse_hex_digit(str) {
 | 
				
			||||||
 | 
					        Error(_) ->
 | 
				
			||||||
 | 
					          case i < min {
 | 
				
			||||||
 | 
					            True -> Stop(Error(Nil))
 | 
				
			||||||
 | 
					            False -> Stop(Ok(#(hex, str, i)))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        Ok(#(l, rest)) -> Continue(Ok(#(hex <> l, rest, i + 1)))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    Error(_) -> Error(Nil)
 | 
				
			||||||
 | 
					    Ok(#(hex, str, _)) -> Ok(#(hex, str))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_hex_digit(str) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "0" as l <> rest
 | 
				
			||||||
 | 
					    | "1" as l <> rest
 | 
				
			||||||
 | 
					    | "2" as l <> rest
 | 
				
			||||||
 | 
					    | "3" as l <> rest
 | 
				
			||||||
 | 
					    | "4" as l <> rest
 | 
				
			||||||
 | 
					    | "5" as l <> rest
 | 
				
			||||||
 | 
					    | "6" as l <> rest
 | 
				
			||||||
 | 
					    | "7" as l <> rest
 | 
				
			||||||
 | 
					    | "8" as l <> rest
 | 
				
			||||||
 | 
					    | "9" as l <> rest
 | 
				
			||||||
 | 
					    | "a" as l <> rest
 | 
				
			||||||
 | 
					    | "b" as l <> rest
 | 
				
			||||||
 | 
					    | "c" as l <> rest
 | 
				
			||||||
 | 
					    | "d" as l <> rest
 | 
				
			||||||
 | 
					    | "e" as l <> rest
 | 
				
			||||||
 | 
					    | "f" as l <> rest
 | 
				
			||||||
 | 
					    | "A" as l <> rest
 | 
				
			||||||
 | 
					    | "B" as l <> rest
 | 
				
			||||||
 | 
					    | "C" as l <> rest
 | 
				
			||||||
 | 
					    | "D" as l <> rest
 | 
				
			||||||
 | 
					    | "E" as l <> rest
 | 
				
			||||||
 | 
					    | "F" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_digit(str: String) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "0" as l <> rest
 | 
				
			||||||
 | 
					    | "1" as l <> rest
 | 
				
			||||||
 | 
					    | "2" as l <> rest
 | 
				
			||||||
 | 
					    | "3" as l <> rest
 | 
				
			||||||
 | 
					    | "4" as l <> rest
 | 
				
			||||||
 | 
					    | "5" as l <> rest
 | 
				
			||||||
 | 
					    | "6" as l <> rest
 | 
				
			||||||
 | 
					    | "7" as l <> rest
 | 
				
			||||||
 | 
					    | "8" as l <> rest
 | 
				
			||||||
 | 
					    | "9" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_alpha(str: String) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  case str {
 | 
				
			||||||
 | 
					    "a" as l <> rest
 | 
				
			||||||
 | 
					    | "b" as l <> rest
 | 
				
			||||||
 | 
					    | "c" as l <> rest
 | 
				
			||||||
 | 
					    | "d" as l <> rest
 | 
				
			||||||
 | 
					    | "e" as l <> rest
 | 
				
			||||||
 | 
					    | "f" as l <> rest
 | 
				
			||||||
 | 
					    | "g" as l <> rest
 | 
				
			||||||
 | 
					    | "h" as l <> rest
 | 
				
			||||||
 | 
					    | "i" as l <> rest
 | 
				
			||||||
 | 
					    | "j" as l <> rest
 | 
				
			||||||
 | 
					    | "k" as l <> rest
 | 
				
			||||||
 | 
					    | "l" as l <> rest
 | 
				
			||||||
 | 
					    | "m" as l <> rest
 | 
				
			||||||
 | 
					    | "n" as l <> rest
 | 
				
			||||||
 | 
					    | "o" as l <> rest
 | 
				
			||||||
 | 
					    | "p" as l <> rest
 | 
				
			||||||
 | 
					    | "q" as l <> rest
 | 
				
			||||||
 | 
					    | "r" as l <> rest
 | 
				
			||||||
 | 
					    | "s" as l <> rest
 | 
				
			||||||
 | 
					    | "t" as l <> rest
 | 
				
			||||||
 | 
					    | "u" as l <> rest
 | 
				
			||||||
 | 
					    | "v" as l <> rest
 | 
				
			||||||
 | 
					    | "w" as l <> rest
 | 
				
			||||||
 | 
					    | "x" as l <> rest
 | 
				
			||||||
 | 
					    | "y" as l <> rest
 | 
				
			||||||
 | 
					    | "z" as l <> rest
 | 
				
			||||||
 | 
					    | "A" as l <> rest
 | 
				
			||||||
 | 
					    | "B" as l <> rest
 | 
				
			||||||
 | 
					    | "C" as l <> rest
 | 
				
			||||||
 | 
					    | "D" as l <> rest
 | 
				
			||||||
 | 
					    | "E" as l <> rest
 | 
				
			||||||
 | 
					    | "F" as l <> rest
 | 
				
			||||||
 | 
					    | "G" as l <> rest
 | 
				
			||||||
 | 
					    | "H" as l <> rest
 | 
				
			||||||
 | 
					    | "I" as l <> rest
 | 
				
			||||||
 | 
					    | "J" as l <> rest
 | 
				
			||||||
 | 
					    | "K" as l <> rest
 | 
				
			||||||
 | 
					    | "L" as l <> rest
 | 
				
			||||||
 | 
					    | "M" as l <> rest
 | 
				
			||||||
 | 
					    | "N" as l <> rest
 | 
				
			||||||
 | 
					    | "O" as l <> rest
 | 
				
			||||||
 | 
					    | "P" as l <> rest
 | 
				
			||||||
 | 
					    | "Q" as l <> rest
 | 
				
			||||||
 | 
					    | "R" as l <> rest
 | 
				
			||||||
 | 
					    | "S" as l <> rest
 | 
				
			||||||
 | 
					    | "T" as l <> rest
 | 
				
			||||||
 | 
					    | "U" as l <> rest
 | 
				
			||||||
 | 
					    | "V" as l <> rest
 | 
				
			||||||
 | 
					    | "W" as l <> rest
 | 
				
			||||||
 | 
					    | "X" as l <> rest
 | 
				
			||||||
 | 
					    | "Y" as l <> rest
 | 
				
			||||||
 | 
					    | "Z" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					    _ -> Error(Nil)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_unreserved(str: String) -> Result(#(String, String), Nil) {
 | 
				
			||||||
 | 
					  list.fold_until(
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      parse_alpha,
 | 
				
			||||||
 | 
					      parse_digit,
 | 
				
			||||||
 | 
					      fn(str) {
 | 
				
			||||||
 | 
					        case str {
 | 
				
			||||||
 | 
					          "_" as l <> rest
 | 
				
			||||||
 | 
					          | "-" as l <> rest
 | 
				
			||||||
 | 
					          | "." as l <> rest
 | 
				
			||||||
 | 
					          | "~" as l <> rest -> Ok(#(l, rest))
 | 
				
			||||||
 | 
					          _ -> Error(Nil)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    Error(Nil),
 | 
				
			||||||
 | 
					    get_parser_fn(str),
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn combine_uris(uris: List(Uri)) -> Uri {
 | 
				
			||||||
 | 
					  list.fold(uris, Uri(None, None, None, None, "", None, None), fn(acc, uri) {
 | 
				
			||||||
 | 
					    let acc = case uri {
 | 
				
			||||||
 | 
					      Uri(Some(scheme), _, _, _, _, _, _) -> Uri(..acc, scheme: Some(scheme))
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let acc = case uri {
 | 
				
			||||||
 | 
					      Uri(_, Some(userinfo), _, _, _, _, _) ->
 | 
				
			||||||
 | 
					        Uri(..acc, userinfo: Some(userinfo))
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let acc = case uri {
 | 
				
			||||||
 | 
					      Uri(_, _, Some(host), _, _, _, _) -> Uri(..acc, host: Some(host))
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let acc = case uri {
 | 
				
			||||||
 | 
					      Uri(_, _, _, Some(port), _, _, _) -> Uri(..acc, port: Some(port))
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let acc = case uri {
 | 
				
			||||||
 | 
					      Uri(_, _, _, _, path, _, _) if path != "" -> Uri(..acc, path: path)
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let acc = case uri {
 | 
				
			||||||
 | 
					      Uri(_, _, _, _, _, Some(query), _) -> Uri(..acc, query: Some(query))
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case uri {
 | 
				
			||||||
 | 
					      Uri(_, _, _, _, _, _, Some(fragment)) ->
 | 
				
			||||||
 | 
					        Uri(..acc, fragment: Some(fragment))
 | 
				
			||||||
 | 
					      _ -> acc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn to_uri(uri: Uri) -> uri.Uri {
 | 
				
			||||||
 | 
					  uri.Uri(
 | 
				
			||||||
 | 
					    uri.scheme,
 | 
				
			||||||
 | 
					    uri.userinfo,
 | 
				
			||||||
 | 
					    uri.host,
 | 
				
			||||||
 | 
					    uri.port,
 | 
				
			||||||
 | 
					    uri.path,
 | 
				
			||||||
 | 
					    uri.query,
 | 
				
			||||||
 | 
					    uri.fragment,
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn from_uri(uri: uri.Uri) -> Uri {
 | 
				
			||||||
 | 
					  Uri(
 | 
				
			||||||
 | 
					    uri.scheme,
 | 
				
			||||||
 | 
					    uri.userinfo,
 | 
				
			||||||
 | 
					    uri.host,
 | 
				
			||||||
 | 
					    uri.port,
 | 
				
			||||||
 | 
					    uri.path,
 | 
				
			||||||
 | 
					    uri.query,
 | 
				
			||||||
 | 
					    uri.fragment,
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										103
									
								
								test/uri_test.gleam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								test/uri_test.gleam
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					import gleam/option.{Some}
 | 
				
			||||||
 | 
					import gleeunit/should
 | 
				
			||||||
 | 
					import startest.{describe, it}
 | 
				
			||||||
 | 
					import uri.{Uri}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn main() {
 | 
				
			||||||
 | 
					  startest.run(startest.default_config())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn parse_scheme_tests() {
 | 
				
			||||||
 | 
					  describe("scheme parsing", [
 | 
				
			||||||
 | 
					    it("should parse", fn() {
 | 
				
			||||||
 | 
					      uri.parse("") |> should.equal(Ok(uri.empty_uri))
 | 
				
			||||||
 | 
					      uri.parse("foo")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(Uri(..uri.empty_uri, path: "foo")))
 | 
				
			||||||
 | 
					      uri.parse("foo:")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(Uri(..uri.empty_uri, scheme: Some("foo"))))
 | 
				
			||||||
 | 
					      uri.parse("foo:bar:nisse")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, scheme: Some("foo"), path: "bar:nisse"),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("foo://")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, scheme: Some("foo"), host: Some("")),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("foo:///")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, scheme: Some("foo"), host: Some(""), path: "/"),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("foo:////")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, scheme: Some("foo"), host: Some(""), path: "//"),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  ])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn parse_userinfo_tests() {
 | 
				
			||||||
 | 
					  describe("userinfo parsing", [
 | 
				
			||||||
 | 
					    it("should parse", fn() {
 | 
				
			||||||
 | 
					      uri.parse("user:password@localhost")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, scheme: Some("user"), path: "password@localhost"),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("user@")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(Uri(..uri.empty_uri, path: "user@")))
 | 
				
			||||||
 | 
					      uri.parse("/user@")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(Uri(..uri.empty_uri, path: "/user@")))
 | 
				
			||||||
 | 
					      uri.parse("user@localhost")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(Uri(..uri.empty_uri, path: "user@localhost")))
 | 
				
			||||||
 | 
					      uri.parse("//user@localhost")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, userinfo: Some("user"), host: Some("localhost")),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("//user:password@localhost")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(
 | 
				
			||||||
 | 
					          ..uri.empty_uri,
 | 
				
			||||||
 | 
					          userinfo: Some("user:password"),
 | 
				
			||||||
 | 
					          host: Some("localhost"),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("foo:/user@")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(..uri.empty_uri, scheme: Some("foo"), path: "/user@"),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("foo://user@localhost")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(
 | 
				
			||||||
 | 
					          ..uri.empty_uri,
 | 
				
			||||||
 | 
					          scheme: Some("foo"),
 | 
				
			||||||
 | 
					          userinfo: Some("user"),
 | 
				
			||||||
 | 
					          host: Some("localhost"),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					      uri.parse("foo://user:password@localhost")
 | 
				
			||||||
 | 
					      |> should.equal(Ok(
 | 
				
			||||||
 | 
					        Uri(
 | 
				
			||||||
 | 
					          ..uri.empty_uri,
 | 
				
			||||||
 | 
					          scheme: Some("foo"),
 | 
				
			||||||
 | 
					          userinfo: Some("user:password"),
 | 
				
			||||||
 | 
					          host: Some("localhost"),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  ])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// gleeunit test functions end in `_test`
 | 
				
			||||||
 | 
					// pub fn uri_test() {
 | 
				
			||||||
 | 
					//   match("uri:")
 | 
				
			||||||
 | 
					//   match("//@")
 | 
				
			||||||
 | 
					//   match("//")
 | 
				
			||||||
 | 
					//   match("")
 | 
				
			||||||
 | 
					//   match("?")
 | 
				
			||||||
 | 
					//   match("#")
 | 
				
			||||||
 | 
					//   match("#\t")
 | 
				
			||||||
 | 
					//   match("//:")
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fn match(uri: String) {
 | 
				
			||||||
 | 
					//   assert uri.parse(uri)  |> uri.to_uri
 | 
				
			||||||
 | 
					//     == uri2.parse(uri)
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
		Reference in New Issue
	
	Block a user