Skip to content

Fast and almost spec-compliant URL parser library written in pure Lua

License

Notifications You must be signed in to change notification settings

Pika-Software/gm_url

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gm_url

Fast and almost (97%) spec-compliant with whatwg-url URL parser library written in pure Lua!

P.S. By saying "pure lua" I mean LuaJIT :P
P.S.S. I hate punycode
P.S.S.S. see a part about spec complience below

Features

  • URL class API that is similiar to Javascript's URL class
  • Fully featured URLSearchParams class
  • Fully covers specification but not the IDNA
  • Fast even without any 🦀 Rust code 🚀
  • Basic API parsing functions so you build your own URL class 💪
  • Easily integratable, just drop the file, require it and get everything you want!
  • Absolute overkill that beats everyone else's implementations 😈

And now about bad things:

  • Many fields may be nil, while in Javascript they are always string
  • port fields is a number instead of a string
  • Does not implement IDNA by spec, but believe me it won't bother you

And as far I remember that's all!
Talking about this library like it is some spaceship, anyway I am proud about this library :P

Wait! Where is .lua file???

Hey, I know you are confused, but it is fine. The library was written in Yuescript that itself is a somewhat successor to Moonscript, language that is transpiled to Lua.

But you don't need to transpile anything, just grab url.lua from here, or extract it from the latest release

You also may want to ask why not just in Lua. Well, plain Lua sucks, and Yuescript brings joy into coding because of syntax-sugar it provides. Definetely try it out!

Oh, and if you are Yuescript/Moonscript enthusiast in Garry's Mod community, then you should try out gm_moonloader ❤️

Example

local URL = require("url").URL

local baseUrl = "https://developer.mozilla.org"

local A = URL("/", baseURL)
-- => 'https://developer.mozilla.org/'

local B = URL(baseURL)
-- => 'https://developer.mozilla.org/'

URL("en-US/docs", B)
-- => 'https://developer.mozilla.org/en-US/docs'

local D = URL("/en-US/docs", B)
-- => 'https://developer.mozilla.org/en-US/docs'

URL("/en-US/docs", D)
-- => 'https://developer.mozilla.org/en-US/docs'

URL("/en-US/docs", A)
-- => 'https://developer.mozilla.org/en-US/docs'

URL("/en-US/docs", "https://developer.mozilla.org/fr-FR/toto")
-- => 'https://developer.mozilla.org/en-US/docs'

API

class URL
    -- Creates a new URL object and parses given url with given base
    -- Note: it is URL(url, base)
    new(url: string, base?: string)
    
    -- also calling tostring(...) with URL given will result in getting full url (aka .href)

    href: string -- full url
    origin: string? -- *readonly* origin of the url ¯\_(ツ)_/¯
    scheme: string? -- just a plain scheme
    protocol: string? -- a scheme with ':' appended at the end
    username: string?
    password: string?
    hostname: string?
    host: string -- hostname + port
    port: number?
    pathname: string?
    query: string?
    search: string -- a query with '?' prepended
    searchParams: URLSearchParams -- *readonly*
    fragment: string?
    hash: string? -- a fragment with '#' prepended

    -- Just a table with raw values that are used in the parser
    state: URLState

-- Same as calling URL(url, base)
URL URL.parse(url: string, base?: string)
-- Returns true if given string with base can be parsed
bool URL.canParse(url: string, base?: string)

-- Returns true if given obj is URL
bool IsURL(obj: any)

class URLSearchParams
    -- if `init` is table, then it must be a list that consists of tables 
    -- that have two value, name and value
    -- e.g. { {"name", "value"}, {"foo", "bar"}, {"good"} }
    -- Note: it is URLSearchParams(init, url)
    new(init: string/table/nil, url: URL?)

    -- also calling tostring(...) with URLSearchParams given will result in getting serialized query
    -- also '#' can be used to get a total count of parameters (e.g. #searchParams)

    -- appends name and value to the end
    append(name: string, value: string?)
    -- searches all parameters with given name, and deletes them
    -- if `value` is given, then searches for exactly given name AND value
    delete(name: string, value: string?)
    -- finds first value associated with given name
    string/nil get(name: string)
    -- finds all values associated with given name and returns them as list
    table getAll(name: string)
    -- returns true if parameters with given name exists
    -- and value if given
    bool has(name: string, value: string?)
    -- sets first name to a given value (or appends [name, value])
    -- and deletes other parameters with the same name
    set(name: string, value: string?)
    -- sorts parameters inside URLSearchParams
    sort() 

    -- returns iterator that can be used in for loops
    -- for example:
    --
    -- for k, v in searchParams:iterator()
    --     ...
    --
    iterator iterator() -- key, value
    iterator keys() -- key
    iterator values() -- value

-- Returns true if given obj is URLSearchParams
bool IsURLSearchParams(obj: any)

-- Percent-encode/decode methods 
string encodeURI(uri: string) -- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
string encodeURIComponent(uri: string) -- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
string decodeURI(uri: string) -- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
string decodeURIComponent(uri: string) -- see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent

-- Basic URL parsing/serializing functions that are used in URL class
URLState parse(obj: URLState, url: string, base: string?) -- returns the same obj that it was given
string serialize(obj: URLState, excludeFragment: bool = false) -- same as URL.href

-- Just a table that may have fields defined below
interface URLState
    scheme: string?
    username: string?
    password: string?
    hostname: string/table/number/nil
    port: number?
    path: string/table/nil
    query: string/URLSearchParams/nil
    fragment: string?

About spec-compliancy

The only part of this library that is not spec-compliant is IDNA processing because I am too lazy to spend time on this.

Here is rate how much tests pass / fail

And here is what tests exactly failed. As you can see everything is related to IDNA/Punycode. Since it is only 3% of all tests I don't even bother to fix it unless REALLY needed.

urltestdata.json was taken from web-platform-tests