sema-url
0.1.0URL parsing, building, resolving, and query-string manipulation
sema-url
URL parsing, building, resolving, and query-string manipulation.
Install
sema pkg add sema-url
Quick start
(import "sema-url")
(define parsed (url/parse "https://api.example.com:8080/users?page=1&limit=10#results"))
(get parsed :scheme) ; => "https"
(get parsed :host) ; => "api.example.com"
(get parsed :port) ; => 8080
(get parsed :path) ; => "/users"
(url/query-parse (get parsed :query))
; => {"limit" "10" "page" "1"}
(url/build {:scheme "https" :host "example.com" :path "/search"
:query (url/query-build {"q" "sema lang" "page" "1"})})
; => "https://example.com/search?page=1&q=sema%20lang"
(url/join "https://example.com/docs/intro" "../api/reference")
; => "https://example.com/api/reference"
API
| Function | Description |
|---|---|
(url/parse str) |
Parse a URL into {:scheme :userinfo :host :port :path :query :fragment} |
(url/build parts) |
Build a URL string from a parts map |
(url/join base ref) |
Resolve a reference against a base URL, RFC 3986 style |
(url/absolute? str) |
#t when the string starts with scheme:// |
(url/with-query url params) |
Replace a URL's query string from a params map |
(url/query-parse str) |
Parse "a=1&b=2" into {"a" "1" "b" "2"} |
(url/query-build map) |
Build an encoded query string from a map |
(url/encode str) |
Percent-encode a string (UTF-8 aware) |
(url/decode str) |
Percent-decode a string (UTF-8 aware) |
url/parse
(url/parse "https://user:pw@example.com:8080/a/b?x=1#top")
; => {:scheme "https" :userinfo "user:pw" :host "example.com" :port 8080
; :path "/a/b" :query "x=1" :fragment "top"}
Absent parts are nil; :path defaults to "/". :query and :fragment
are returned raw (still encoded) — feed :query to url/query-parse to get a
map. :port is an integer; a non-numeric port raises an error. URLs without a
scheme parse too ((url/parse "localhost:3000/api") → host "localhost",
port 3000).
Limitations: only hierarchical URLs are supported — non-// schemes like
mailto: and IPv6 host literals ([::1]) are not.
url/build
(url/build {:scheme "https" :host "example.com" :port 8080 :path "/x" :query "a=1"})
; => "https://example.com:8080/x?a=1"
nil parts are omitted. :query and :fragment are emitted verbatim, so
encode them first (e.g. with url/query-build). (url/build (url/parse u))
roundtrips.
url/join
(url/join "https://example.com/a/b" "c") ; => "https://example.com/a/c"
(url/join "https://example.com/a/b/" "c") ; => "https://example.com/a/b/c"
(url/join "https://example.com/a/b/" "../c") ; => "https://example.com/a/c"
(url/join "https://example.com/a/b" "/c/d") ; => "https://example.com/c/d"
(url/join "https://example.com/a" "https://o.io/") ; => "https://o.io/"
(url/join "https://example.com/a" "//cdn.io/x.js") ; => "https://cdn.io/x.js"
RFC 3986-style resolution: an absolute ref replaces the base entirely; an
absolute path replaces the base's path; a relative path resolves against the
directory of the base's path, normalizing . and .. segments. The base's
query and fragment are dropped; the ref's are kept. An empty ref returns the
base unchanged.
url/absolute?
(url/absolute? "https://example.com") ; => #t
(url/absolute? "/a/b") ; => #f
(url/absolute? "example.com/a") ; => #f
url/with-query
(url/with-query "https://example.com/s?old=1" {"q" "sema" "page" "2"})
; => "https://example.com/s?page=2&q=sema"
(url/with-query "https://example.com/s?old=1" {})
; => "https://example.com/s"
Replaces (never merges) the query; an empty map removes it. The fragment is preserved.
url/query-parse
(url/query-parse "q=sema+lang&page=1") ; => {"page" "1" "q" "sema lang"}
(url/query-parse "debug") ; => {"debug" ""}
(url/query-parse "") ; => {}
Keys and values are percent-decoded (+ becomes a space). A key without =
gets the value "". Repeated keys keep the last value — if you need
multi-value params, split the query string yourself.
url/query-build
(url/query-build {"q" "sema lang" "page" 1}) ; => "page=1&q=sema%20lang"
Keys and values are percent-encoded; non-string values go through str.
Keys are emitted in sorted order, so output is deterministic.
url/encode
(url/encode "a b/c") ; => "a%20b%2Fc"
(url/encode "café") ; => "caf%C3%A9"
Percent-encodes everything outside RFC 3986 unreserved characters
(A-Z a-z 0-9 - . _ ~). Non-ASCII characters are encoded as their UTF-8
bytes.
url/decode
(url/decode "caf%C3%A9") ; => "café"
(url/decode "a+b") ; => "a b"
(url/decode "100%") ; => "100%"
Decodes %XX sequences as UTF-8 (upper- or lowercase hex) and + as a
space. A % not followed by two hex digits is kept literally.
Testing
sema pkg add sema-test # once
sema tests.sema
License
MIT
| Version | Size | Published |
|---|---|---|
| 0.1.0 | 6 KB | 2026-07-07 21:28:20 |