Added main method for command line usage
This commit is contained in:
parent
ecbefa231e
commit
fad3714ae0
5 changed files with 127 additions and 18 deletions
11
README.md
11
README.md
|
@ -14,15 +14,20 @@ This library was adapted from the [RIFE2 Web Application Framework](https://rife
|
||||||
A pure Java version can also be found at [https://github.com/gbevin/urlencoder](https://github.com/gbevin/urlencoder).
|
A pure Java version can also be found at [https://github.com/gbevin/urlencoder](https://github.com/gbevin/urlencoder).
|
||||||
|
|
||||||
|
|
||||||
For decades we've been using [java.net.URLEncoder](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URLEncoder.html) because of its improper naming. It is actually intended to encode HTML form parameters, not URLs.
|
For decades we've been using [java.net.URLEncoder](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URLEncoder.html) because of its improper naming. It is actually intended to encode HTML form parameters, not URLs, causing the wrong escape sequences to be used.
|
||||||
|
|
||||||
Android's [Uri.encode](https://developer.android.com/reference/android/net/Uri#encode(java.lang.String,%20java.lang.String)) also addresses this issue.
|
Additionally, `java.net.URLEncoder` allocates memory even when no encoding is necessary, significantly impacting performance. This library has a negligible performance impact when the specified string doesn't need to be encoded.
|
||||||
|
|
||||||
|
|
||||||
|
Android's [Uri.encode](https://developer.android.com/reference/android/net/Uri#encode(java.lang.String,%20java.lang.String)) also addresses these issues, but does not currently support [unicode surrogate pairs](https://learn.microsoft.com/en-us/globalization/encoding/surrogate-pairs).
|
||||||
## Examples (TL;DR)
|
## Examples (TL;DR)
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
UrlEncoder.encode("a test &") // -> a%20test%20%26
|
||||||
UrlEncoder.encode("%#okékÉȢ smile!😁") // -> %25%23ok%C3%A9k%C3%89%C8%A2%20smile%21%F0%9F%98%81
|
UrlEncoder.encode("%#okékÉȢ smile!😁") // -> %25%23ok%C3%A9k%C3%89%C8%A2%20smile%21%F0%9F%98%81
|
||||||
UrlEncoder.encode("?test=a test", '=', '?') // -> ?test=a%20test
|
UrlEncoder.encode("?test=a test", allow = "?=") // -> ?test=a%20test
|
||||||
|
|
||||||
|
UrlEncoder.decode("a%20test%20%26") // -> a test &
|
||||||
UrlEncoder.decode("%25%23ok%C3%A9k%C3%89%C8%A2%20smile%21%F0%9F%98%81") // -> %#okékÉȢ smile!😁
|
UrlEncoder.decode("%25%23ok%C3%A9k%C3%89%C8%A2%20smile%21%F0%9F%98%81") // -> %#okékÉȢ smile!😁
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import org.jetbrains.dokka.gradle.DokkaTask
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
id("application")
|
||||||
id("com.github.ben-manes.versions") version "0.44.0"
|
id("com.github.ben-manes.versions") version "0.44.0"
|
||||||
id("io.gitlab.arturbosch.detekt") version "1.22.0"
|
id("io.gitlab.arturbosch.detekt") version "1.22.0"
|
||||||
id("java")
|
|
||||||
id("java-library")
|
id("java-library")
|
||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
id("org.jetbrains.dokka") version "1.7.20"
|
id("org.jetbrains.dokka") version "1.7.20"
|
||||||
|
@ -16,10 +16,11 @@ plugins {
|
||||||
id("signing")
|
id("signing")
|
||||||
}
|
}
|
||||||
|
|
||||||
description = "Encode and decode URL parameters"
|
description = "A simple library to encode/decode URL parameters"
|
||||||
group = "net.thauvin.erik"
|
group = "net.thauvin.erik"
|
||||||
version = "0.9-SNAPSHOT"
|
version = "0.9-SNAPSHOT"
|
||||||
|
|
||||||
|
val mavenName = "UrlEncoder"
|
||||||
val deployDir = "deploy"
|
val deployDir = "deploy"
|
||||||
val gitHub = "ethauvin/${rootProject.name}"
|
val gitHub = "ethauvin/${rootProject.name}"
|
||||||
val mavenUrl = "https://github.com/$gitHub"
|
val mavenUrl = "https://github.com/$gitHub"
|
||||||
|
@ -34,12 +35,20 @@ dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base {
|
||||||
|
archivesName.set(rootProject.name)
|
||||||
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("$group.${rootProject.name}.$mavenName")
|
||||||
|
}
|
||||||
|
|
||||||
sonarqube {
|
sonarqube {
|
||||||
properties {
|
properties {
|
||||||
property("sonar.projectName", rootProject.name)
|
property("sonar.projectName", rootProject.name)
|
||||||
|
@ -58,6 +67,12 @@ val javadocJar by tasks.creating(Jar::class) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes["Main-Class"] = "$group.${rootProject.name}.$mavenName"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
withType<KotlinCompile>().configureEach {
|
withType<KotlinCompile>().configureEach {
|
||||||
kotlinOptions.jvmTarget = java.targetCompatibility.toString()
|
kotlinOptions.jvmTarget = java.targetCompatibility.toString()
|
||||||
}
|
}
|
||||||
|
@ -104,10 +119,6 @@ tasks {
|
||||||
mustRunAfter(clean)
|
mustRunAfter(clean)
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
|
||||||
archiveBaseName.set(rootProject.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
"sonar" {
|
"sonar" {
|
||||||
dependsOn(koverReport)
|
dependsOn(koverReport)
|
||||||
}
|
}
|
||||||
|
@ -120,7 +131,7 @@ publishing {
|
||||||
artifactId = rootProject.name
|
artifactId = rootProject.name
|
||||||
artifact(javadocJar)
|
artifact(javadocJar)
|
||||||
pom {
|
pom {
|
||||||
name.set(rootProject.name)
|
name.set(mavenName)
|
||||||
description.set(project.description)
|
description.set(project.description)
|
||||||
url.set(mavenUrl)
|
url.set(mavenUrl)
|
||||||
licenses {
|
licenses {
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
<groupId>net.thauvin.erik</groupId>
|
<groupId>net.thauvin.erik</groupId>
|
||||||
<artifactId>urlencoder</artifactId>
|
<artifactId>urlencoder</artifactId>
|
||||||
<version>0.9-SNAPSHOT</version>
|
<version>0.9-SNAPSHOT</version>
|
||||||
<name>urlencoder</name>
|
<name>UrlEncoder</name>
|
||||||
<description>Encode and decode URL parameters</description>
|
<description>A simple library to encode/decode URL parameters</description>
|
||||||
<url>https://github.com/ethauvin/urlencoder</url>
|
<url>https://github.com/ethauvin/urlencoder</url>
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
|
|
|
@ -19,15 +19,22 @@ package net.thauvin.erik.urlencoder
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.BitSet
|
import java.util.BitSet
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL parameters encoding and decoding.
|
* URL parameters encoding and decoding.
|
||||||
*
|
*
|
||||||
|
* - Rules determined by [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#page-13),
|
||||||
|
*
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
||||||
* @author Erik C. Thauvin (erik@thauvin.net)
|
* @author Erik C. Thauvin (erik@thauvin.net)
|
||||||
*/
|
*/
|
||||||
object UrlEncoder {
|
object UrlEncoder {
|
||||||
private val hexDigits = "0123456789ABCDEF".toCharArray()
|
private val hexDigits = "0123456789ABCDEF".toCharArray()
|
||||||
|
internal val usage = """Usage : kotlin -cp urlencoder-*.jar ${UrlEncoder::class.java.name} [-ed] text
|
||||||
|
Encode and decode URL parameters.
|
||||||
|
-e encode (default)
|
||||||
|
-d decode"""
|
||||||
|
|
||||||
// see https://www.rfc-editor.org/rfc/rfc3986#page-13
|
// see https://www.rfc-editor.org/rfc/rfc3986#page-13
|
||||||
private val unreservedChars = BitSet('~'.code + 1).apply {
|
private val unreservedChars = BitSet('~'.code + 1).apply {
|
||||||
|
@ -103,7 +110,7 @@ object UrlEncoder {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (bytesBuffer != null) {
|
if (bytesBuffer != null) {
|
||||||
out?.append(String(bytesBuffer, 0, bytesPos, StandardCharsets.UTF_8))
|
out!!.append(String(bytesBuffer, 0, bytesPos, StandardCharsets.UTF_8))
|
||||||
bytesBuffer = null
|
bytesBuffer = null
|
||||||
bytesPos = 0
|
bytesPos = 0
|
||||||
}
|
}
|
||||||
|
@ -121,7 +128,9 @@ object UrlEncoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a provided [String] object into a new string, containing only valid URL characters in the UTF-8
|
* Transforms a provided [String] object into a new string, containing only valid URL characters in the UTF-8
|
||||||
* encoding. Letters, numbers, unreserved (`_-!.~'()*`) and allowed characters are left intact.
|
* encoding.
|
||||||
|
*
|
||||||
|
* - Letters, numbers, unreserved (`_-!.~'()*`) and allowed characters are left intact.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun encode(source: String, allow: String): String {
|
fun encode(source: String, allow: String): String {
|
||||||
|
@ -167,10 +176,50 @@ object UrlEncoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a provided [String] object into a new string, containing only valid URL characters in the UTF-8
|
* Transforms a provided [String] object into a new string, containing only valid URL characters in the UTF-8
|
||||||
* encoding. Letters, numbers, unreserved (`_-!.~'()*`) and allowed characters are left intact.
|
* encoding.
|
||||||
|
*
|
||||||
|
* - Letters, numbers, unreserved (`_-!.~'()*`) and allowed characters are left intact.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun encode(source: String, vararg allow: Char): String {
|
fun encode(source: String, vararg allow: Char): String {
|
||||||
return encode(source, String(allow))
|
return encode(source, String(allow))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes and decodes URLs from the command line.
|
||||||
|
*
|
||||||
|
* - `kotlin -cp urlencoder-*.jar net.thauvin.erik.urlencoder.UrlEncoder`
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val result = processMain(args)
|
||||||
|
if (result.status == 1) {
|
||||||
|
System.err.println(result.output)
|
||||||
|
} else {
|
||||||
|
println(result.output)
|
||||||
|
}
|
||||||
|
exitProcess(result.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class MainResult(var output: String = usage, var status: Int = 1)
|
||||||
|
|
||||||
|
internal fun processMain(args: Array<String>): MainResult {
|
||||||
|
val result = MainResult()
|
||||||
|
if (args.isNotEmpty() && args[0].isNotBlank()) {
|
||||||
|
val hasDecode = args[0] == "-d"
|
||||||
|
val hasOption = hasDecode || args[0] == "-e"
|
||||||
|
if (!hasOption || args.size >= 2) {
|
||||||
|
val argsList = mutableListOf<String>()
|
||||||
|
argsList.addAll(args)
|
||||||
|
if (hasOption) argsList.removeAt(0)
|
||||||
|
if (hasDecode) {
|
||||||
|
result.output = decode(argsList.joinToString(" "))
|
||||||
|
} else {
|
||||||
|
result.output = encode(argsList.joinToString(" "))
|
||||||
|
}
|
||||||
|
result.status = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package net.thauvin.erik.urlencoder
|
||||||
|
|
||||||
import net.thauvin.erik.urlencoder.UrlEncoder.decode
|
import net.thauvin.erik.urlencoder.UrlEncoder.decode
|
||||||
import net.thauvin.erik.urlencoder.UrlEncoder.encode
|
import net.thauvin.erik.urlencoder.UrlEncoder.encode
|
||||||
|
import net.thauvin.erik.urlencoder.UrlEncoder.processMain
|
||||||
|
import net.thauvin.erik.urlencoder.UrlEncoder.usage
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
@ -44,9 +46,10 @@ class UrlEncoderTest {
|
||||||
assertEquals(it.key, decode(it.value))
|
assertEquals(it.key, decode(it.value))
|
||||||
}
|
}
|
||||||
invalid.forEach {
|
invalid.forEach {
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith<IllegalArgumentException>(
|
||||||
decode(it)
|
message = it,
|
||||||
}
|
block = { decode(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,4 +66,45 @@ class UrlEncoderTest {
|
||||||
assertEquals("?test=a%20test", encode("?test=a test", "=?"))
|
assertEquals("?test=a%20test", encode("?test=a test", "=?"))
|
||||||
assertEquals("aaa", encode("aaa", 'a'))
|
assertEquals("aaa", encode("aaa", 'a'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMainDecode() {
|
||||||
|
var result: UrlEncoder.MainResult
|
||||||
|
validMap.forEach {
|
||||||
|
result = processMain(arrayOf("-d", it.value))
|
||||||
|
assertEquals(result.output, it.key, it.key)
|
||||||
|
assertEquals(result.status, 0, it.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMainEncode() {
|
||||||
|
var result: UrlEncoder.MainResult
|
||||||
|
validMap.forEach {
|
||||||
|
result = processMain(arrayOf("-e", it.key))
|
||||||
|
assertEquals(it.value, result.output, "-e ${it.key}")
|
||||||
|
assertEquals(0, result.status, "-e ${it.key}")
|
||||||
|
|
||||||
|
result = processMain(arrayOf(it.key))
|
||||||
|
assertEquals(it.value, result.output, it.value)
|
||||||
|
assertEquals(0, result.status, it.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalid.forEach {
|
||||||
|
assertFailsWith<IllegalArgumentException>(
|
||||||
|
message = it,
|
||||||
|
block = { processMain(arrayOf("-d", it)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMainUsage() {
|
||||||
|
var result: UrlEncoder.MainResult
|
||||||
|
for (arg in arrayOf("", " ", "-d", "-e")) {
|
||||||
|
result = processMain(arrayOf(arg))
|
||||||
|
assertEquals(usage, result.output, "processMain('$arg')")
|
||||||
|
assertEquals(1, result.status, "processMain('$arg')")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue