Merge.
This commit is contained in:
commit
9887402b57
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<import file="jdb-build-1.10.xml"/>
|
<import file="jdb-build-1.10.xml"/>
|
||||||
|
|
||||||
<target name="package" depends="-build-modular">
|
<target name="package" depends="run-tests,-build-modular">
|
||||||
<copy
|
<copy
|
||||||
file="${build.dir}/${name}-${version}.${build.number}.jar"
|
file="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||||
tofile="${build.dir}/${name}-${version}.jar"/>
|
tofile="${build.dir}/${name}-${version}.jar"/>
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
#Sat, 11 Jan 2014 18:55:50 -0600
|
#Sat, 11 Jan 2014 18:55:50 -0600
|
||||||
name=jdb-util
|
name=jdb-util
|
||||||
version=2.3
|
version=2.3
|
||||||
|
=======
|
||||||
|
#Wed, 29 Oct 2014 19:31:44 -0500
|
||||||
|
name=jdb-util
|
||||||
|
version=3.1
|
||||||
|
>>>>>>> 14332b878ce29d57aee9bea47bc4e42ff4555612
|
||||||
lib.local=true
|
lib.local=true
|
||||||
|
|
||||||
build.number=0
|
build.number=0
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
package com.jdbernard.net
|
package com.jdbernard.net
|
||||||
|
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import javax.net.ssl.SSLSocket
|
||||||
|
import javax.net.ssl.SSLSocketFactory
|
||||||
|
import javax.xml.bind.DatatypeConverter
|
||||||
import groovy.json.JsonBuilder
|
import groovy.json.JsonBuilder
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
|
|
||||||
@ -15,19 +19,34 @@ public class HttpContext {
|
|||||||
public String host = 'vbs-test'
|
public String host = 'vbs-test'
|
||||||
|
|
||||||
/** The port number.*/
|
/** The port number.*/
|
||||||
public int port = 8000
|
public int port = 80
|
||||||
|
|
||||||
/** A cookie value to send in the request. This value will be automatically
|
/** A cookie value to send in the request. This value will be automatically
|
||||||
* set from any `Set-Cookie` header in the request response. */
|
* set from any `Set-Cookie` header in the request response. */
|
||||||
public String cookie
|
public String cookie
|
||||||
|
|
||||||
|
/** HTTP Basic Authentication information. If this is a string, it will be
|
||||||
|
* Base64 encoded as-is. Otherwise it may be an object with "username" and
|
||||||
|
* "password" properties. The username and password will be concatenated
|
||||||
|
* with a colon in between. The result will be Base64 encoded. */
|
||||||
|
public def basicAuth
|
||||||
|
|
||||||
|
/** Set this to `true` to use HTTPS. Otherwise, HTTP will be used. */
|
||||||
|
public boolean secure = false
|
||||||
|
|
||||||
|
/** The default Content-Type that we will send with our requests. This can
|
||||||
|
* be overridden using the different method overloads. */
|
||||||
|
public String defaultContentType = "application/json"
|
||||||
|
|
||||||
|
private SSLSocketFactory sslSocketFactory = null;
|
||||||
|
|
||||||
/** #### makeMessage
|
/** #### makeMessage
|
||||||
* Make and return an HTTP request for the given HTTP method and URL. If
|
* Make and return an HTTP request for the given HTTP method and URL. If
|
||||||
* `request` is not null, it is expected to be an object which will be
|
* `request` is not null, it is expected to be an object which will be
|
||||||
* converted to JSON and included as the request body. The `Host`,
|
* converted to JSON and included as the request body. The `Host`,
|
||||||
* `Cookie`, and `Content-Length` headers will be added based on the
|
* `Cookie`, and `Content-Length` headers will be added based on the
|
||||||
* `host` and `cookie` fields and the `request` object. */
|
* `host` and `cookie` fields and the `request` object. */
|
||||||
public String makeMessage(String method, String url, def request) {
|
public String makeMessage(String method, String url, String contentType, def request) {
|
||||||
StringBuilder message = new StringBuilder()
|
StringBuilder message = new StringBuilder()
|
||||||
message.append(method)
|
message.append(method)
|
||||||
message.append(" ")
|
message.append(" ")
|
||||||
@ -46,24 +65,60 @@ public class HttpContext {
|
|||||||
message.append(cookie)
|
message.append(cookie)
|
||||||
message.append("\r\n") }
|
message.append("\r\n") }
|
||||||
|
|
||||||
if (request) {
|
if (basicAuth) {
|
||||||
String requestBody = new JsonBuilder(request).toString()
|
message.append("Authorization: Basic ")
|
||||||
|
|
||||||
|
if (basicAuth instanceof String) message.append(
|
||||||
|
DatatypeConverter.printBase64Binary(
|
||||||
|
(basicAuth as String).bytes))
|
||||||
|
|
||||||
|
else message.append(
|
||||||
|
DatatypeConverter.printBase64Binary(
|
||||||
|
"${basicAuth.username ?: ''}:${basicAuth.password ?: ''}".bytes))
|
||||||
|
|
||||||
|
message.append("\r\n") }
|
||||||
|
|
||||||
|
if (!contentType) contentType = "application.json"
|
||||||
|
message.append("Content-Type: ")
|
||||||
|
message.append(contentType)
|
||||||
|
message.append("\r\n")
|
||||||
|
|
||||||
|
if (request) {
|
||||||
|
String requestBody
|
||||||
|
if (contentType.startsWith("application/json") &&
|
||||||
|
request instanceof Map) {
|
||||||
|
def jsonRequestBuilder = new JsonBuilder(request)
|
||||||
|
requestBody = jsonRequestBuilder.toString() }
|
||||||
|
|
||||||
|
else if (contentType.startsWith("application/x-www-form-urlencoded") &&
|
||||||
|
request instanceof Map)
|
||||||
|
requestBody = urlEncode(request)
|
||||||
|
|
||||||
|
else requestBody = request.toString()
|
||||||
|
|
||||||
message.append("Content-Type: application/json\r\n")
|
|
||||||
message.append("Content-Length: ")
|
message.append("Content-Length: ")
|
||||||
message.append(requestBody.length())
|
message.append(requestBody.length())
|
||||||
message.append("\r\n\r\n")
|
message.append("\r\n\r\n")
|
||||||
message.append(requestBody) }
|
message.append(requestBody)
|
||||||
|
|
||||||
|
message.append("\r\n") }
|
||||||
|
else message.append("\r\n")
|
||||||
|
|
||||||
message.append("\r\n")
|
|
||||||
return message.toString()
|
return message.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** #### send
|
||||||
|
* A wrapper around `makeMessage` and `send(String message)` to create a
|
||||||
|
* request, send it, and return the response. This version allows you to
|
||||||
|
* specify the request's Content-Type.*/
|
||||||
|
public def send(String method, String url, String contentType, def request) {
|
||||||
|
return send(makeMessage(method, url, contentType, request)) }
|
||||||
|
|
||||||
/** #### send
|
/** #### send
|
||||||
* A wrapper around `makeMessage` and `send(String message)` to create a
|
* A wrapper around `makeMessage` and `send(String message)` to create a
|
||||||
* request, send it, and return the response. */
|
* request, send it, and return the response. */
|
||||||
public def send(String method, String url, def request) {
|
public def send(String method, String url, def request) {
|
||||||
return send(makeMessage(method, url, request)) }
|
return send(makeMessage(method, url, defaultContentType, request)) }
|
||||||
|
|
||||||
/** #### send
|
/** #### send
|
||||||
* Send a message to the host specified by the object's `host` and `port`
|
* Send a message to the host specified by the object's `host` and `port`
|
||||||
@ -79,7 +134,18 @@ public class HttpContext {
|
|||||||
*/
|
*/
|
||||||
public def send(String message) {
|
public def send(String message) {
|
||||||
Map result = [headers:[], content: null]
|
Map result = [headers:[], content: null]
|
||||||
Socket sock = new Socket(host, port)
|
|
||||||
|
Socket sock
|
||||||
|
|
||||||
|
if (secure) {
|
||||||
|
if (!sslSocketFactory)
|
||||||
|
sslSocketFactory = SSLSocketFactory.getDefault()
|
||||||
|
|
||||||
|
sock = sslSocketFactory.createSocket(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
else sock = new Socket(host, port)
|
||||||
|
|
||||||
def startTime
|
def startTime
|
||||||
|
|
||||||
sock.withStreams { strin, out ->
|
sock.withStreams { strin, out ->
|
||||||
@ -91,7 +157,7 @@ public class HttpContext {
|
|||||||
writer.flush()
|
writer.flush()
|
||||||
|
|
||||||
def line = reader.readLine().trim()
|
def line = reader.readLine().trim()
|
||||||
result.status = line.split(/\s/)[1]
|
result.status = line.split(/\s/)[1] as int
|
||||||
line = reader.readLine().trim()
|
line = reader.readLine().trim()
|
||||||
|
|
||||||
boolean isChunked = false
|
boolean isChunked = false
|
||||||
@ -164,4 +230,18 @@ public class HttpContext {
|
|||||||
* `send(String method, String url, def request)` with `method =
|
* `send(String method, String url, def request)` with `method =
|
||||||
* "POST"`. */
|
* "POST"`. */
|
||||||
public def post(String url, def body) { return send('POST', url, body) }
|
public def post(String url, def body) { return send('POST', url, body) }
|
||||||
|
|
||||||
|
/** #### post
|
||||||
|
* A wrapper to perform a `POST` request. This calls
|
||||||
|
* `send(String method, String url, def request)` with `method =
|
||||||
|
* "POST"`. This version also allows you to set the request's
|
||||||
|
* Content-Type. */
|
||||||
|
public def post(String url, String contentType, def body) {
|
||||||
|
return send('POST', url, contentType, body) }
|
||||||
|
|
||||||
|
private String urlEncode(Map m) {
|
||||||
|
List<String> parts = m.collect { k, v ->
|
||||||
|
"$k=${URLEncoder.encode(v.toString(), 'UTF-8')}" }
|
||||||
|
return parts.join("&") }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,79 +9,112 @@ package com.jdbernard.util
|
|||||||
|
|
||||||
public class LightOptionParser {
|
public class LightOptionParser {
|
||||||
|
|
||||||
public static def parseOptions(def optionDefinitions, List args) {
|
public static def parseOptions(def optionDefinitions, String[] args) {
|
||||||
|
return parseOptions(optionDefinitions, args as List<String>) }
|
||||||
|
|
||||||
def returnOpts = [:]
|
public static def parseOptions(def optionDefinitions, List<String> args) {
|
||||||
def foundOpts = [:]
|
|
||||||
def optionArgIndices = []
|
|
||||||
|
|
||||||
/// Find all the options.
|
def returnOpts = [args:[]]
|
||||||
args.eachWithIndex { arg, idx ->
|
|
||||||
if (arg.startsWith('--')) foundOpts[arg.substring(2)] = [idx: idx]
|
|
||||||
else if (arg.startsWith('-')) foundOpts[arg.substring(1)] = [idx: idx] }
|
|
||||||
|
|
||||||
/// Look for option arguments.
|
/// Look through each of the arguments to see if it is an option.
|
||||||
foundOpts.each { foundName, optInfo ->
|
/// Note that we are manually advancing the index in the loop.
|
||||||
|
for (int i = 0; i < args.size();) {
|
||||||
|
|
||||||
def retVal
|
def retVal = false
|
||||||
|
def optName = false
|
||||||
|
|
||||||
|
if (args[i].startsWith('--')) optName = args[i].substring(2)
|
||||||
|
else if (args[i].startsWith('-')) optName = args[i].substring(1)
|
||||||
|
|
||||||
|
/// This was recognized as an option, try to find the definition
|
||||||
|
/// and read in any arguments.
|
||||||
|
if (optName) {
|
||||||
|
|
||||||
/// Find the definition for this option.
|
/// Find the definition for this option.
|
||||||
def optDef = optionDefinitions.find {
|
def optDef = optionDefinitions.find {
|
||||||
it.key == foundName || it.value.longName == foundName }
|
it.key == optName || it.value.longName == optName }
|
||||||
|
|
||||||
if (!optDef) throw new IllegalArgumentException(
|
if (!optDef) throw new IllegalArgumentException(
|
||||||
"Unrecognized option: '${args[optInfo.idx]}.")
|
"Unrecognized option: '${args[i]}'.")
|
||||||
|
|
||||||
def optName = optDef.key
|
optName = optDef.key
|
||||||
optDef = optDef.value
|
optDef = optDef.value
|
||||||
|
|
||||||
/// Remember the option index for later.
|
/// If there are no arguments, this is a flag. Set the value
|
||||||
optionArgIndices << optInfo.idx
|
/// and advance the index.
|
||||||
|
if ((optDef.arguments ?: 0) == 0) { retVal = true; i++ }
|
||||||
|
|
||||||
/// If there are no arguments, this is a flag.
|
/// If there are a pre-determined number of arguments, read them
|
||||||
if ((optDef.arguments ?: 0) == 0) retVal = true
|
/// in.
|
||||||
|
else if (optDef.arguments &&
|
||||||
|
optDef.arguments instanceof Number &&
|
||||||
|
optDef.arguments > 0) {
|
||||||
|
|
||||||
/// Otherwise, read in the arguments
|
retVal = []
|
||||||
if (optDef.arguments && optDef.arguments > 0) {
|
|
||||||
|
|
||||||
/// Not enough arguments left
|
/// Not enough arguments left
|
||||||
if ((optInfo.idx + optDef.arguments) >= args.size()) {
|
if ((i + optDef.arguments) >= args.size()) {
|
||||||
throw new Exception("Option '${args[optInfo.idx]}' " +
|
throw new Exception("Option '${args[i]}' " +
|
||||||
"expects ${optDef.arguments} arguments.") }
|
"expects ${optDef.arguments} arguments.") }
|
||||||
|
|
||||||
int firstArgIdx = optInfo.idx + 1
|
/// Advance past the option onto the first argument.
|
||||||
|
i++
|
||||||
|
|
||||||
/// Case of only one argument
|
/// Copy the arguments
|
||||||
if (optDef.arguments == 1)
|
retVal += args[i..<(i + optDef.arguments)]
|
||||||
retVal = args[firstArgIdx]
|
|
||||||
/// Case of multiple arguments
|
|
||||||
else retVal = args[firstArgIdx..<(firstArgIdx + optDef.arguments)]
|
|
||||||
|
|
||||||
/// Remember all the option argument indices for later.
|
/// Advance the index past end of the arguements
|
||||||
(firstArgIdx..<(firstArgIdx + optDef.arguments)).each {
|
i += optDef.arguments }
|
||||||
optionArgIndices << it }}
|
|
||||||
|
|
||||||
/// Store the value in the returnOpts map
|
/// If there are a variable number of arguments, treat all
|
||||||
|
/// arguments until the next argument or the end of options as
|
||||||
|
/// arguments for this option
|
||||||
|
else if (optDef.arguments == 'variable') {
|
||||||
|
|
||||||
|
retVal = []
|
||||||
|
|
||||||
|
/// Advance past the option to the first argument
|
||||||
|
i++
|
||||||
|
|
||||||
|
/// As long as we have not hit another option or the end of
|
||||||
|
/// arguments, keep adding arguments to the list for this
|
||||||
|
/// option.
|
||||||
|
for(;i < args.size() && !args[i].startsWith('-'); i++)
|
||||||
|
retVal << args[i] }
|
||||||
|
|
||||||
|
else {
|
||||||
|
throw new Exception("Invalid number of arguments " +
|
||||||
|
"defined for option ${optName}. The number of " +
|
||||||
|
"arguments must be either an integer or the value " +
|
||||||
|
"'variable'") }
|
||||||
|
|
||||||
|
/// Set the value on the option.
|
||||||
|
if (retVal instanceof Boolean) {
|
||||||
returnOpts[optName] = retVal
|
returnOpts[optName] = retVal
|
||||||
if (optDef.longName) returnOpts[optDef.longName] = retVal }
|
if (optDef.longName) returnOpts[optDef.longName] = retVal }
|
||||||
|
|
||||||
|
else {
|
||||||
|
if (!returnOpts.containsKey(optName))
|
||||||
|
returnOpts[optName] = []
|
||||||
|
returnOpts[optName] += retVal
|
||||||
|
|
||||||
|
if (optDef.longName) {
|
||||||
|
if (!returnOpts.containsKey(optDef.longName))
|
||||||
|
returnOpts[optDef.longName] = []
|
||||||
|
returnOpts[optDef.longName] += retVal } } }
|
||||||
|
|
||||||
|
/// This was not as option, it is an unclaomed argument.
|
||||||
|
else { returnOpts.args << args[i]; i++ } }
|
||||||
|
|
||||||
/// Check that all required options have been found.
|
/// Check that all required options have been found.
|
||||||
optionDefinitions.each { optName, optDef ->
|
optionDefinitions.each { optName, optDef ->
|
||||||
/// If this is a required field
|
/// If this is a required field
|
||||||
if (optDef.required &&
|
if (optDef.required &&
|
||||||
/// and it has not been found, by either it's short or long name.
|
/// and it has not been found, by either it's short or long name.
|
||||||
!(returnOpts[optName] ||
|
!(returnOpts[optName] ||
|
||||||
(optDef.longName && returnOpts[longName])))
|
(optDef.longName && returnOpts[optDef.longName])))
|
||||||
|
|
||||||
throw new Exception("Missing required option: '-${optName}'.") }
|
throw new Exception("Missing required option: '-${optName}'.") }
|
||||||
|
|
||||||
/// Remove all the option arguments from the args list and return just
|
|
||||||
/// the non-option arguments.
|
|
||||||
optionArgIndices.sort().reverse().each { args.remove(it) }
|
|
||||||
|
|
||||||
//optionArgIndices = optionArgIndices.collect { args[it] }
|
|
||||||
//args.removeAll(optionArgIndices)
|
|
||||||
|
|
||||||
returnOpts.args = args
|
|
||||||
return returnOpts }
|
return returnOpts }
|
||||||
}
|
}
|
||||||
|
65
src/test/com/jdbernard/util/LightOptionParserTests.groovy
Normal file
65
src/test/com/jdbernard/util/LightOptionParserTests.groovy
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package com.jdbernard.util
|
||||||
|
|
||||||
|
import groovy.util.GroovyTestCase
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static com.jdbernard.util.LightOptionParser.parseOptions
|
||||||
|
|
||||||
|
public class LightOptionParserTests extends GroovyTestCase {
|
||||||
|
|
||||||
|
def helpDef = ['h': [longName: 'help']]
|
||||||
|
def confDef = ['c': [longName: 'config', required: true, arguments: 1]]
|
||||||
|
def fullDef = [
|
||||||
|
h: [longName: 'help'],
|
||||||
|
c: [longName: 'config-file', required: true, arguments: 1],
|
||||||
|
i: [longName: 'input-file', arguments: 'variable'],
|
||||||
|
o: [longName: 'output-file2', arguments: 2]]
|
||||||
|
|
||||||
|
void testShortFlagPresent1() { assert parseOptions(helpDef, ["-h"]).h }
|
||||||
|
void testShortFlagPresent2() { assert parseOptions(helpDef, ["-h"]).help }
|
||||||
|
void testLongFlagPresent() { assert parseOptions(helpDef, ["--help"]).h}
|
||||||
|
void testShortFlagPresent() { assert parseOptions(helpDef, ["--help"]).help }
|
||||||
|
|
||||||
|
void testFlagAbsent1() { assert !parseOptions(helpDef, ["arg"]).h }
|
||||||
|
void testFlagAbsent2() { assert !parseOptions(helpDef, ["arg"]).help }
|
||||||
|
|
||||||
|
void testRequiredOptionMissing() {
|
||||||
|
try {
|
||||||
|
parseOptions(confDef, ["arg"])
|
||||||
|
assert false }
|
||||||
|
catch (Exception e) {} }
|
||||||
|
|
||||||
|
void testSingleArg1() {
|
||||||
|
assert parseOptions(confDef, ["-c", "confFile"]).c == ["confFile"] }
|
||||||
|
|
||||||
|
void testSingleArg2() {
|
||||||
|
assert parseOptions(confDef, ["-c", "confFile"]).config == ["confFile"] }
|
||||||
|
|
||||||
|
void testUnclaimedArgsAndFlag() {
|
||||||
|
def opts = parseOptions(helpDef, ["arg1", "-h", "arg2"])
|
||||||
|
assert opts.args == ["arg1", "arg2"] }
|
||||||
|
|
||||||
|
void testUnclaimedAndClaimedArgs() {
|
||||||
|
def opts = parseOptions(fullDef, ["-c", "confFile", "arg1"])
|
||||||
|
assert opts.args == ["arg1"]
|
||||||
|
assert opts.c == ["confFile"] }
|
||||||
|
|
||||||
|
/*void testMultipleArgs1() {
|
||||||
|
def opts = parseOptions(fullDef, ["-c", "confFile", ""])
|
||||||
|
assert .conf == ["confFile"] }*/
|
||||||
|
|
||||||
|
void testFull() {
|
||||||
|
def opts = parseOptions(fullDef,
|
||||||
|
["-c", "cfgFile", "arg1", "-i", "in1", "in2", "in3",
|
||||||
|
"-o", "out1", "out2", "arg2", "-h", "-i", "in4"])
|
||||||
|
|
||||||
|
assert opts.h
|
||||||
|
assert opts.c == ["cfgFile"]
|
||||||
|
assert opts['config-file'] == ["cfgFile"]
|
||||||
|
assert opts.args == ["arg1", "arg2"]
|
||||||
|
assert opts.i == ["in1", "in2", "in3", "in4"]
|
||||||
|
assert opts["input-file"] == ["in1", "in2", "in3", "in4"]
|
||||||
|
assert opts.o == ["out1", "out2"]
|
||||||
|
assert opts["output-file2"] == ["out1", "out2"] }
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user