Repository: unjs/ufo (ts)

Issue:
Feature request: withHttps, withHttp, withoutProtocol
I have often had use for adding and removing protocols.

Some type of implementation for something like `withHttps`, `withHttp`, `withoutProtocol` would be really nice.

Pull request description:
feat: withHttp, withHttps, withoutProtocol, withProtocol
Closes #47 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1b4aaf..a9c13b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+### [0.8.1](https://github.com/unjs/ufo/compare/v0.8.0...v0.8.1) (2022-03-16)
+
+
+### Bug Fixes
+
+* **parseURL:** don't fail if hostAndPath not matching ([d297e60](https://github.com/unjs/ufo/commit/d297e60d01c91c2367a20e0779d8a6705c996a3e))
+
 ## [0.8.0](https://github.com/unjs/ufo/compare/v0.7.11...v0.8.0) (2022-03-15)
 
 
diff --git a/README.md b/README.md
index 362eee6..2327806 100644
--- a/README.md
+++ b/README.md
@@ -158,6 +158,42 @@ Check if a path starts with `./` or `../`.
 isRelative('./foo')
 ```
 
+### `withHttp`
+
+Ensures url protocol is `http`
+
+```ts
+// Result: http://example.com
+withHttp('https://example.com')
+```
+
+### `withHttps`
+
+Ensures url protocol is `https`
+
+```ts
+// Result: https://example.com
+withHttps('http://example.com')
+```
+
+### `withProtocol`
+
+Changes url protocol passed as second argument
+
+```ts
+// Result: ftp://example.com
+withProtocol('http://example.com', 'ftp://')
+```
+
+### `withoutProtocol`
+
+Removes url protocol
+
+```ts
+// Result: example.com
+withoutProtocol('http://example.com')
+```
+
 ## License
 
 [MIT](./LICENSE)
diff --git a/package.json b/package.json
index 135ad7b..33d46eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ufo",
-  "version": "0.8.0",
+  "version": "0.8.1",
   "description": "URL utils for humans",
   "repository": "unjs/ufo",
   "license": "MIT",
diff --git a/src/parse.ts b/src/parse.ts
index c5bb710..8f27802 100644
--- a/src/parse.ts
+++ b/src/parse.ts
@@ -24,7 +24,7 @@ export function parseURL (input: string = '', defaultProto?: string): ParsedURL
     return defaultProto ? parseURL(defaultProto + input) : parsePath(input)
   }
 
-  const [protocol = '', auth, hostAndPath] = (input.replace(/\\/g, '/').match(/([^:/]+:)?\/\/([^/@]+@)?(.*)/) || []).splice(1)
+  const [protocol = '', auth, hostAndPath = ''] = (input.replace(/\\/g, '/').match(/([^:/]+:)?\/\/([^/@]+@)?(.*)/) || []).splice(1)
   const [host = '', path = ''] = (hostAndPath.match(/([^/?#]*)(.*)?/) || []).splice(1)
   const { pathname, search, hash } = parsePath(path)
 
diff --git a/src/utils.ts b/src/utils.ts
index 2fd9c25..1cd8656 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -109,6 +109,22 @@ export function joinURL (base: string, ...input: string[]): string {
   return url
 }
 
+export function withHttp (input: string): string {
+  return withProtocol(input, 'http://')
+}
+
+export function withHttps (input: string): string {
+  return withProtocol(input, 'https://')
+}
+
+export function withoutProtocol (input: string): string {
+  return withProtocol(input, '')
+}
+
+export function withProtocol (input: string, protocol: string): string {
+  return input.replace(/^\w+:(\/\/)?/, protocol)
+}
+
 // $URL based utils
 
 export function createURL (input: string): $URL {


Test patch:
diff --git a/test/utilities.test.ts b/test/utilities.test.ts
index e1f5f4d..e9e220b 100644
--- a/test/utilities.test.ts
+++ b/test/utilities.test.ts
@@ -1,5 +1,14 @@
 import { describe, expect, test } from 'vitest'
-import { hasProtocol, isRelative, parsePath, stringifyParsedURL } from '../src'
+import {
+  hasProtocol,
+  isRelative,
+  parsePath,
+  stringifyParsedURL,
+  withHttp,
+  withHttps,
+  withoutProtocol,
+  withProtocol
+} from '../src'
 
 describe('hasProtocol', () => {
   const tests = [
@@ -60,3 +69,71 @@ describe('stringifyParsedURL', () => {
     })
   }
 })
+
+describe('withHttp', () => {
+  const tests = [
+    { input: 'https://example.com', out: 'http://example.com' },
+    { input: 'ftp://example.com/test?foo', out: 'http://example.com/test?foo' },
+    { input: 'https://foo.com/test?query=123#hash', out: 'http://foo.com/test?query=123#hash' },
+    { input: 'file:///home/user', out: 'http:///home/user' }
+  ]
+
+  for (const t of tests) {
+    test(t.input.toString(), () => {
+      expect(withHttp(t.input)).toBe(t.out)
+    })
+  }
+})
+
+describe('withHttps', () => {
+  const tests = [
+    { input: 'http://example.com', out: 'https://example.com' },
+    { input: 'ftp://example.com/test?foo', out: 'https://example.com/test?foo' },
+    { input: 'http://foo.com/test?query=123#hash', out: 'https://foo.com/test?query=123#hash' },
+    { input: 'file:///home/user', out: 'https:///home/user' }
+  ]
+
+  for (const t of tests) {
+    test(t.input.toString(), () => {
+      expect(withHttps(t.input)).toBe(t.out)
+    })
+  }
+})
+
+describe('withProtocol', () => {
+  const tests = [
+    { input: 'http://example.com', protocol: 'https://', out: 'https://example.com' },
+    { input: 'https://example.com', protocol: 'http://', out: 'http://example.com' },
+    { input: 'ftp://example.com/test?foo', protocol: 'http://', out: 'http://example.com/test?foo' },
+    { input: 'http://foo.com/test?query=123#hash', protocol: 'ftp://', out: 'ftp://foo.com/test?query=123#hash' },
+    { input: 'file:///home/user', protocol: 'https://', out: 'https:///home/user' },
+    { input: 'tel:1234567890', protocol: 'skype:', out: 'skype:1234567890' },
+    { input: 'tel://+1234567890', protocol: 'callto://', out: 'callto://+1234567890' }
+  ]
+
+  for (const t of tests) {
+    test(t.input.toString(), () => {
+      expect(withProtocol(t.input, t.protocol)).toBe(t.out)
+    })
+  }
+})
+
+describe('withoutProtocol', () => {
+  const tests = [
+    { input: 'http://example.com', out: 'example.com' },
+    { input: 'https://example.com', out: 'example.com' },
+    { input: 'ftp://example.com/test?foo', out: 'example.com/test?foo' },
+    { input: 'http://foo.com/test?query=123#hash', out: 'foo.com/test?query=123#hash' },
+    { input: 'file:///home/user', out: '/home/user' },
+    { input: 'tel:1234567890', out: '1234567890' },
+    { input: 'mailto:support@example.com', out: 'support@example.com' },
+    { input: 'skype:1234567890', out: '1234567890' },
+    { input: 'callto://+1234567890', out: '+1234567890' }
+  ]
+
+  for (const t of tests) {
+    test(t.input.toString(), () => {
+      expect(withoutProtocol(t.input)).toBe(t.out)
+    })
+  }
+})