Simplified wrapper around Node's native 'url' module
# url.coffee | |
# by Aseem Kishore ( https://github.com/aseemk ), under MIT license | |
# | |
# A simplified wrapper around the native 'url' module that returns "live" | |
# objects: updates to properties are reflected in related properties. E.g. | |
# updates to the `port` property will be reflected on the `host` property. | |
# | |
# The public API and behavior are pretty close to the native 'url' module's: | |
# http://nodejs.org/docs/latest/api/url.html Currently lacking / known issues: | |
# | |
# - No support for `parseQueryString`; `query` can only be a string. | |
# Similarly, no support for `slashesDenoteHost`. | |
# | |
# - Parsing of URLs beyond the initial parse (which uses the native 'url' | |
# module) isn't thorough or strict right now. It assumes plenty. | |
# | |
# This is mainly just a simple and easy way to mutate/tweak URLs (e.g. to | |
# change a URL's port). Feedback and patches welcome! Thanks. =) | |
NativeURL = require 'url' | |
class URL | |
# Private constructor: | |
constructor: (href) -> | |
@_reparse href | |
# Language helpers: | |
get = (props) => | |
@::__defineGetter__ name, getter for name, getter of props | |
set = (props) => | |
@::__defineSetter__ name, setter for name, setter of props | |
# Public properties: | |
get href: -> | |
# Postfix http/https/ftp/gopher/file protocols (trailing colon) with | |
# colon-slash-slash; all others just colon: | |
protocol = @protocol | |
protocol += '//' if protocol.replace(/:$/, '') in ['http', 'https', 'ftp', 'gopher', 'file'] | |
# Add auth via @ if needed: | |
auth = @auth | |
auth += '@' if auth | |
# Finally: | |
"#{protocol}#{auth}#{@host}#{@pathname}#{@search}#{@hash}" | |
set href: (val='') -> | |
@_reparse val | |
get protocol: -> @_protocol | |
set protocol: (val='') -> | |
# Lowercase, and ensure trailing colon: | |
@_protocol = val.toLowerCase().replace(/:$/, '') + ':' | |
# WARNING: The latest Node docs are inaccurate on host. They say host | |
# includes auth, but it doesn't in practice: | |
# https://github.com/joyent/node/issues/1626#issuecomment-5373917 | |
get host: -> | |
host = @hostname | |
host = [host, @port].join ':' if @port | |
host | |
set host: (val='') -> | |
[@hostname, @port] = val.split ':' | |
get auth: -> @_auth | |
set auth: (@_auth='') -> | |
get hostname: -> @_hostname | |
set hostname: (@_hostname='') -> | |
get port: -> @_port | |
set port: (@_port='') -> | |
get pathname: -> @_pathname | |
set pathname: (val='') -> | |
# Ensure trailing slash: | |
@_pathname = '/' + val.replace(/^\//, '') | |
get search: -> | |
# Leading question mark if there's a query string: | |
@query and "?#{@query}" | |
set search: (val='') -> | |
# Ensure leading question mark / strip it when assigning to @query: | |
@_query = val.replace(/^\?/, '') | |
get path: -> | |
"#{@pathname}#{@search}" | |
set path: (val='') -> | |
[@pathname, @query] = val.split '?' | |
get query: -> @_query | |
set query: (@_query='') -> | |
get hash: -> @_hash | |
set hash: (val='') -> | |
# Ensure leading anchor: | |
@_hash = '#' + val.replace(/^#/, '') | |
# Private methods: | |
# Reparses the given URL and updates this instance in-place: | |
_reparse: (href) -> | |
# Save all properties as private(-ish) properties so that we can | |
# validate/adjust updates to them, e.g. add trailing colons. | |
# NOTE: All properties will be present -- missing ones will be the | |
# empty string. This is consistent with browsers, but not w/ Node's | |
# native 'url' module. That's fine hopefully? | |
for prop, value of NativeURL.parse href | |
@["_#{prop}"] = value or '' | |
# Public methods: | |
equals: (other) -> | |
@href is other.href | |
toString: -> | |
@href | |
@parse: (str) -> | |
new URL str | |
@format: (url) -> | |
# Don't assume the given param will be an instance of this class: | |
NativeURL.format url | |
@resolve: (from, to) -> | |
NativeURL.resolve from, to | |
# Export static functions only: | |
for name, func of URL when typeof func is 'function' | |
exports[name] = func |
assert = require 'assert' | |
NativeURL = require 'url' | |
URL = require './url' | |
# Helper func for testing all getters: | |
test = (url, props) -> | |
# Ignore 'slashes' property; we don't implement that yet. | |
for prop, expected of props when prop isnt 'slashes' | |
assert.equal url[prop], expected, """ | |
url.#{prop}: | |
expected: #{JSON.stringify expected} | |
actual: #{JSON.stringify url[prop]} | |
""" | |
# Test case: a full URL, straight from the Node docs! | |
# http://nodejs.org/docs/latest/api/url.html | |
STR = 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' | |
exp = NativeURL.parse STR | |
# Test initial parse: | |
url = URL.parse STR | |
test url, exp | |
# Test protocol set w/out trailing colon: | |
url.protocol = 'https' | |
exp.protocol = 'https:' | |
exp.href = exp.href.replace 'http:', 'https:' | |
test url, exp | |
# Test auth set: | |
url.auth = 'usr:pwd' | |
exp.auth = 'usr:pwd' | |
exp.href = exp.href.replace 'user:pass', 'usr:pwd' | |
test url, exp | |
# Test host set, clearing port: | |
url.host = 'example.com' | |
exp.host = 'example.com' | |
exp.port = '' | |
exp.hostname = 'example.com' | |
exp.href = exp.href.replace 'host.com:8080', 'example.com' | |
test url, exp | |
# Test hostname set: | |
url.hostname = 'foobar.net' | |
exp.hostname = 'foobar.net' | |
exp.host = 'foobar.net' | |
exp.href = exp.href.replace 'example.com', 'foobar.net' | |
test url, exp | |
# Test port set: | |
url.port = '1234' | |
exp.port = '1234' | |
exp.host = 'foobar.net:1234' | |
exp.href = exp.href.replace 'foobar.net', 'foobar.net:1234' | |
test url, exp | |
# Test path set w/out leading slash, clearing query string: | |
url.path = 'hello/world' | |
exp.path = '/hello/world' | |
exp.pathname = '/hello/world' | |
exp.search = '' | |
exp.query = '' | |
exp.href = exp.href.replace '/p/a/t/h?query=string', '/hello/world' | |
test url, exp | |
# Test pathname set w/out leading slash: | |
url.pathname = 'foo/bar' | |
exp.pathname = '/foo/bar' | |
exp.path = exp.path.replace '/hello/world', '/foo/bar' | |
exp.href = exp.href.replace '/hello/world', '/foo/bar' | |
test url, exp | |
# Test search set w/out leading question mark: | |
url.search = 'a=b' | |
exp.search = '?a=b' | |
exp.query = 'a=b' | |
exp.path = exp.path + '?a=b' | |
exp.href = exp.href.replace '/foo/bar', exp.path | |
test url, exp | |
# Test query set -- w/ (double) question mark: | |
# TODO Is this actually legitimate/correct?? It works... | |
url.query = '?foo=bar' | |
exp.query = '?foo=bar' | |
exp.search = '??foo=bar' | |
exp.path = exp.path.replace '?a=b', '??foo=bar' | |
exp.href = exp.href.replace '?a=b', '??foo=bar' | |
test url, exp | |
# Test hash set w/out leading anchor: | |
url.hash = 'baz' | |
exp.hash = '#baz' | |
exp.href = exp.href.replace '#hash', '#baz' | |
test url, exp | |
# Test hash clear: | |
url.hash = '' | |
exp.hash = '#' | |
exp.href = exp.href.replace '#baz', '#' | |
test url, exp | |
# Test search clear: | |
# TODO Should we be testing query clear too? Should question mark remain then? | |
url.search = '' | |
exp.search = '' | |
exp.query = '' | |
exp.path = exp.path.replace '??foo=bar', '' | |
exp.href = exp.href.replace '??foo=bar', '' | |
test url, exp | |
# Test path clear: | |
url.path = '' | |
exp.path = '/' | |
exp.pathname = '/' | |
exp.href = exp.href.replace '/foo/bar', '/' | |
test url, exp | |
# Test port clear: | |
url.port = '' | |
exp.port = '' | |
exp.host = exp.host.replace ':1234', '' | |
exp.href = exp.href.replace ':1234', '' | |
test url, exp | |
# Test auth clear: | |
url.auth = '' | |
exp.auth = '' | |
exp.href = exp.href.replace 'usr:pwd@', '' | |
test url, exp | |
# Finally, test set href: | |
url.href = STR | |
exp = NativeURL.parse STR | |
test url, exp | |
console.log 'All tests passed!' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
this is a really clever hack for native looking getters/setters!