API BREAKING:
Node opEquals(), opIndex(), opIndexAssign(), remove(), removeAt() no longer automatically convert to string. This was changed to prevent the API from getting too non-obvious, and to remove the need for too many special cases in the code. The API documentation was changed accordingly. opApply() still automatically converts to string. Added a contains() method to Node. Clarified YAML null values in the documentation. Improved unittests. Fixed a bug that caused opIndex() and opIndexAssign() to only work with exactly the same type as stored in the node (e.g. real, but not double, for floating-point values) Fixed some potential bugs. Minor documentation fixes.
This commit is contained in:
parent
7673281ae4
commit
ebc6e64c2b
|
@ -124,13 +124,13 @@ final class Constructor
|
||||||
/**
|
/**
|
||||||
* Add a constructor function from scalar.
|
* Add a constructor function from scalar.
|
||||||
*
|
*
|
||||||
* The function must take a reference to Node to construct from.
|
* The function must take a reference to $(D Node) to construct from.
|
||||||
* The node contains a string for scalars, Node[] for sequences and
|
* The node contains a string for scalars, $(Node[]) for sequences and
|
||||||
* Node.Pair[] for mappings.
|
* $(Node.Pair[]) for mappings.
|
||||||
*
|
*
|
||||||
* Any exception thrown by this function will be caught by D:YAML and
|
* Any exception thrown by this function will be caught by D:YAML and
|
||||||
* its message will be added to a YAMLException that will also tell the
|
* its message will be added to a $(YAMLException) that will also tell
|
||||||
* user which type failed to construct, and position in the file.
|
* the user which type failed to construct, and position in the file.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* The value returned by this function will be stored in the resulting node.
|
* The value returned by this function will be stored in the resulting node.
|
||||||
|
|
249
dyaml/node.d
249
dyaml/node.d
|
@ -204,9 +204,10 @@ struct Node
|
||||||
/**
|
/**
|
||||||
* Construct a Node from a value.
|
* Construct a Node from a value.
|
||||||
*
|
*
|
||||||
* Any type except of Node can be stored in a Node, but default YAML
|
* Any type except for Node can be stored in a Node, but default YAML
|
||||||
* types (integers, floats, strings, timestamps, etc.) will be stored
|
* types (integers, floats, strings, timestamps, etc.) will be stored
|
||||||
* more efficiently.
|
* more efficiently. To create a node representing a null value,
|
||||||
|
* construct it from YAMLNull.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Note that to emit any non-default types you store
|
* Note that to emit any non-default types you store
|
||||||
|
@ -464,14 +465,17 @@ struct Node
|
||||||
* If T is Node, recursively compare all subnodes.
|
* If T is Node, recursively compare all subnodes.
|
||||||
* This might be quite expensive if testing entire documents.
|
* This might be quite expensive if testing entire documents.
|
||||||
*
|
*
|
||||||
* If T is not Node, convert the node to T and test equality with that.
|
* If T is not Node, get a value if type T from the node and test
|
||||||
|
* equality with that.
|
||||||
|
*
|
||||||
|
* To test equality with a null YAML value, use YAMLNull.
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* --------------------
|
* --------------------
|
||||||
* auto node = Node(42);
|
* auto node = Node(42);
|
||||||
*
|
*
|
||||||
* assert(node == 42);
|
* assert(node == 42);
|
||||||
* assert(node == "42");
|
* assert(node != "42");
|
||||||
* assert(node != "43");
|
* assert(node != "43");
|
||||||
* --------------------
|
* --------------------
|
||||||
*
|
*
|
||||||
|
@ -479,10 +483,21 @@ struct Node
|
||||||
*
|
*
|
||||||
* Returns: true if equal, false otherwise.
|
* Returns: true if equal, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool opEquals(T)(const ref T rhs) const
|
bool opEquals(T)(const auto ref T rhs) const
|
||||||
{
|
{
|
||||||
return equals!true(rhs);
|
return equals!true(rhs);
|
||||||
}
|
}
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto node = Node(42);
|
||||||
|
|
||||||
|
assert(node == 42);
|
||||||
|
assert(node != "42");
|
||||||
|
assert(node != "43");
|
||||||
|
|
||||||
|
auto node2 = Node(YAMLNull());
|
||||||
|
assert(node2 == YAMLNull());
|
||||||
|
}
|
||||||
|
|
||||||
///Shortcut for get().
|
///Shortcut for get().
|
||||||
alias get as;
|
alias get as;
|
||||||
|
@ -491,7 +506,9 @@ struct Node
|
||||||
* Get the value of the node as specified type.
|
* Get the value of the node as specified type.
|
||||||
*
|
*
|
||||||
* If the specifed type does not match type in the node,
|
* If the specifed type does not match type in the node,
|
||||||
* conversion is attempted.
|
* conversion is attempted. The stringConversion template
|
||||||
|
* parameter can be used to disable conversion from non-string
|
||||||
|
* types to strings.
|
||||||
*
|
*
|
||||||
* Numeric values are range checked, throwing if out of range of
|
* Numeric values are range checked, throwing if out of range of
|
||||||
* requested type.
|
* requested type.
|
||||||
|
@ -499,6 +516,9 @@ struct Node
|
||||||
* Timestamps are stored as std.datetime.SysTime.
|
* Timestamps are stored as std.datetime.SysTime.
|
||||||
* Binary values are decoded and stored as ubyte[].
|
* Binary values are decoded and stored as ubyte[].
|
||||||
*
|
*
|
||||||
|
* To get a null value, use get!YAMLNull . This is to
|
||||||
|
* prevent getting null values for types such as strings or classes.
|
||||||
|
*
|
||||||
* $(BR)$(B Mapping default values:)
|
* $(BR)$(B Mapping default values:)
|
||||||
*
|
*
|
||||||
* $(PBR
|
* $(PBR
|
||||||
|
@ -526,7 +546,8 @@ struct Node
|
||||||
* Throws: NodeException if unable to convert to specified type, or if
|
* Throws: NodeException if unable to convert to specified type, or if
|
||||||
* the value is out of range of requested type.
|
* the value is out of range of requested type.
|
||||||
*/
|
*/
|
||||||
@property T get(T)() if(!is(T == const))
|
@property T get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)()
|
||||||
|
if(!is(T == const))
|
||||||
{
|
{
|
||||||
if(isType!T){return value_.get!T;}
|
if(isType!T){return value_.get!T;}
|
||||||
|
|
||||||
|
@ -544,18 +565,27 @@ struct Node
|
||||||
|
|
||||||
//If we're getting from a mapping and we're not getting Node.Pair[],
|
//If we're getting from a mapping and we're not getting Node.Pair[],
|
||||||
//we're getting the default value.
|
//we're getting the default value.
|
||||||
if(isMapping){return this["="].as!T;}
|
if(isMapping){return this["="].as!(T, stringConversion);}
|
||||||
|
|
||||||
static if(isSomeString!T)
|
static if(isSomeString!T)
|
||||||
{
|
{
|
||||||
//Try to convert to string.
|
static if(!stringConversion)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
return value_.coerce!T();
|
if(isString){return to!T(value_.get!string);}
|
||||||
|
throw new Error("Node has unexpected type: " ~ type.toString ~
|
||||||
|
". Expected: " ~ typeid(T).toString, startMark_);
|
||||||
}
|
}
|
||||||
catch(VariantException e)
|
else
|
||||||
{
|
{
|
||||||
throw new Error("Unable to convert node value to string", startMark_);
|
//Try to convert to string.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return value_.coerce!T();
|
||||||
|
}
|
||||||
|
catch(VariantException e)
|
||||||
|
{
|
||||||
|
throw new Error("Unable to convert node value to string", startMark_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -577,10 +607,17 @@ struct Node
|
||||||
throw new Error("Node has unexpected type: " ~ type.toString ~
|
throw new Error("Node has unexpected type: " ~ type.toString ~
|
||||||
". Expected: " ~ typeid(T).toString, startMark_);
|
". Expected: " ~ typeid(T).toString, startMark_);
|
||||||
}
|
}
|
||||||
|
assert(false, "This code should never be reached");
|
||||||
|
}
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assertThrown!NodeException(Node("42").get!int);
|
||||||
|
Node(YAMLNull()).get!YAMLNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Const version of get.
|
///Ditto.
|
||||||
@property T get(T)() const if(is(T == const))
|
@property T get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() const
|
||||||
|
if(is(T == const))
|
||||||
{
|
{
|
||||||
if(isType!(Unqual!T)){return value_.get!T;}
|
if(isType!(Unqual!T)){return value_.get!T;}
|
||||||
|
|
||||||
|
@ -598,19 +635,28 @@ struct Node
|
||||||
|
|
||||||
//If we're getting from a mapping and we're not getting Node.Pair[],
|
//If we're getting from a mapping and we're not getting Node.Pair[],
|
||||||
//we're getting the default value.
|
//we're getting the default value.
|
||||||
if(isMapping){return indexConst("=").as!T;}
|
if(isMapping){return indexConst("=").as!( T, stringConversion);}
|
||||||
|
|
||||||
static if(isSomeString!T)
|
static if(isSomeString!T)
|
||||||
{
|
{
|
||||||
//Try to convert to string.
|
static if(!stringConversion)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
//NOTE: We are casting away const here
|
if(isString){return to!T(value_.get!(const string));}
|
||||||
return (cast(Value)value_).coerce!T();
|
throw new Error("Node has unexpected type: " ~ type.toString ~
|
||||||
|
". Expected: " ~ typeid(T).toString, startMark_);
|
||||||
}
|
}
|
||||||
catch(VariantException e)
|
else
|
||||||
{
|
{
|
||||||
throw new Error("Unable to convert node value to string", startMark_);
|
//Try to convert to string.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//NOTE: We are casting away const here
|
||||||
|
return (cast(Value)value_).coerce!T();
|
||||||
|
}
|
||||||
|
catch(VariantException e)
|
||||||
|
{
|
||||||
|
throw new Error("Unable to convert node value to string", startMark_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -657,8 +703,9 @@ struct Node
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* If the node is a mapping, return the value corresponding to the first
|
* If the node is a mapping, return the value corresponding to the first
|
||||||
* key equal to index, even after conversion. I.e; node["12"] will
|
* key equal to index.
|
||||||
* return value of the first key that equals "12", even if it's an integer.
|
*
|
||||||
|
* To get element at a null index, use YAMLNull for index.
|
||||||
*
|
*
|
||||||
* Params: index = Index to use.
|
* Params: index = Index to use.
|
||||||
*
|
*
|
||||||
|
@ -709,16 +756,91 @@ struct Node
|
||||||
Node k4 = Node("14");
|
Node k4 = Node("14");
|
||||||
|
|
||||||
Node narray = Node([n1, n2, n3, n4]);
|
Node narray = Node([n1, n2, n3, n4]);
|
||||||
Node nmap = Node([Pair(k1, n1),
|
Node nmap = Node([k1, k2, k3, k4],
|
||||||
Pair(k2, n2),
|
[n1, n2, n3, n4]);
|
||||||
Pair(k3, n3),
|
|
||||||
Pair(k4, n4)]);
|
|
||||||
|
|
||||||
assert(narray[0].as!int == 11);
|
assert(narray[0].as!int == 11);
|
||||||
assert(null !is collectException(narray[42]));
|
assert(null !is collectException(narray[42]));
|
||||||
assert(nmap["11"].as!int == 11);
|
assert(nmap["11"].as!int == 11);
|
||||||
assert(nmap["14"].as!int == 14);
|
assert(nmap["14"].as!int == 14);
|
||||||
assert(null !is collectException(nmap["42"]));
|
assert(null !is collectException(nmap["42"]));
|
||||||
|
|
||||||
|
narray.add(YAMLNull());
|
||||||
|
nmap.add(YAMLNull(), "Nothing");
|
||||||
|
assert(narray[4].as!YAMLNull == YAMLNull());
|
||||||
|
assert(nmap[YAMLNull()].as!string == "Nothing");
|
||||||
|
|
||||||
|
assertThrown!NodeException(nmap[11]);
|
||||||
|
assertThrown!NodeException(nmap[14]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a collection contains specified item.
|
||||||
|
*
|
||||||
|
* If the node is a sequence, check if it contains the specified item.
|
||||||
|
* If it's a mapping, check if it has a value that matches specified item.
|
||||||
|
*
|
||||||
|
* To check for a null item, use YAMLNull for rhs.
|
||||||
|
*
|
||||||
|
* Params: rhs = Item to look for.
|
||||||
|
*
|
||||||
|
* Returns: true if rhs can was found, false otherwise.
|
||||||
|
*
|
||||||
|
* Throws: NodeException if the node is not a collection.
|
||||||
|
*/
|
||||||
|
bool contains(T)(T rhs) const
|
||||||
|
{
|
||||||
|
if(isSequence)
|
||||||
|
{
|
||||||
|
foreach(ref node; value_.get!(const Node[]))
|
||||||
|
{
|
||||||
|
if(node == rhs){return true;}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(isMapping)
|
||||||
|
{
|
||||||
|
return findPair!(T, true)(rhs) >= 0;
|
||||||
|
}
|
||||||
|
throw new Error("Trying to use the in operator on a node that is not a collection",
|
||||||
|
startMark_);
|
||||||
|
}
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto seq = Node([1, 2, 3, 4, 5]);
|
||||||
|
assert(seq.contains(3));
|
||||||
|
assert(seq.contains(5));
|
||||||
|
assert(!seq.contains("5"));
|
||||||
|
assert(!seq.contains(6));
|
||||||
|
assert(!seq.contains(float.nan));
|
||||||
|
|
||||||
|
auto seq2 = Node(["1", "2"]);
|
||||||
|
assert(seq2.contains("1"));
|
||||||
|
assert(!seq2.contains(1));
|
||||||
|
|
||||||
|
auto map = Node(["1", "2", "3", "4"], [1, 2, 3, 4]);
|
||||||
|
assert(map.contains(1));
|
||||||
|
assert(!map.contains("1"));
|
||||||
|
assert(!map.contains(5));
|
||||||
|
assert(!map.contains(float.nan));
|
||||||
|
|
||||||
|
assert(!seq.contains(YAMLNull()));
|
||||||
|
assert(!map.contains(YAMLNull()));
|
||||||
|
seq.add(YAMLNull());
|
||||||
|
map.add("Nothing", YAMLNull());
|
||||||
|
assert(seq.contains(YAMLNull()));
|
||||||
|
assert(map.contains(YAMLNull()));
|
||||||
|
|
||||||
|
auto map2 = Node([1, 2, 3, 4], [1, 2, 3, 4]);
|
||||||
|
assert(!map2.contains("1"));
|
||||||
|
assert(map2.contains(1));
|
||||||
|
|
||||||
|
//scalar
|
||||||
|
assertThrown!NodeException(Node(1).contains(4));
|
||||||
|
|
||||||
|
auto mapNan = Node([1.0, 2], [1, double.nan]);
|
||||||
|
|
||||||
|
assert(mapNan.contains(double.nan));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -736,6 +858,8 @@ struct Node
|
||||||
* range. This ensures behavior siilar to D arrays and associative
|
* range. This ensures behavior siilar to D arrays and associative
|
||||||
* arrays.
|
* arrays.
|
||||||
*
|
*
|
||||||
|
* To set element at a null index, use YAMLNull for index.
|
||||||
|
*
|
||||||
* Params: index = Index of the value to set.
|
* Params: index = Index of the value to set.
|
||||||
*
|
*
|
||||||
* Throws: NodeException if the node is not a collection, index is out
|
* Throws: NodeException if the node is not a collection, index is out
|
||||||
|
@ -782,6 +906,9 @@ struct Node
|
||||||
opIndexAssign(42, 3);
|
opIndexAssign(42, 3);
|
||||||
assert(length == 5);
|
assert(length == 5);
|
||||||
assert(opIndex(3).as!int == 42);
|
assert(opIndex(3).as!int == 42);
|
||||||
|
|
||||||
|
opIndexAssign(YAMLNull(), 0);
|
||||||
|
assert(opIndex(0) == YAMLNull());
|
||||||
}
|
}
|
||||||
with(Node(["1", "2", "3"], [4, 5, 6]))
|
with(Node(["1", "2", "3"], [4, 5, 6]))
|
||||||
{
|
{
|
||||||
|
@ -790,6 +917,15 @@ struct Node
|
||||||
assert(length == 4);
|
assert(length == 4);
|
||||||
assert(opIndex("3").as!int == 42);
|
assert(opIndex("3").as!int == 42);
|
||||||
assert(opIndex(456).as!int == 123);
|
assert(opIndex(456).as!int == 123);
|
||||||
|
|
||||||
|
opIndexAssign(43, 3);
|
||||||
|
//3 and "3" should be different
|
||||||
|
assert(length == 5);
|
||||||
|
assert(opIndex("3").as!int == 42);
|
||||||
|
assert(opIndex(3).as!int == 43);
|
||||||
|
|
||||||
|
opIndexAssign(YAMLNull(), "2");
|
||||||
|
assert(opIndex("2") == YAMLNull());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1021,8 +1157,7 @@ struct Node
|
||||||
*
|
*
|
||||||
* This method can only be called on collection nodes.
|
* This method can only be called on collection nodes.
|
||||||
*
|
*
|
||||||
* If the node is a sequence, the first node matching value (including
|
* If the node is a sequence, the first node matching value is removed.
|
||||||
* conversion, so e.g. "42" matches 42) is removed.
|
|
||||||
* If the node is a mapping, the first key-value pair where _value
|
* If the node is a mapping, the first key-value pair where _value
|
||||||
* matches specified value is removed.
|
* matches specified value is removed.
|
||||||
*
|
*
|
||||||
|
@ -1036,7 +1171,8 @@ struct Node
|
||||||
{
|
{
|
||||||
foreach(idx, ref elem; get!(Node[]))
|
foreach(idx, ref elem; get!(Node[]))
|
||||||
{
|
{
|
||||||
if(elem.convertsTo!T && elem.as!T == value)
|
if(elem.convertsTo!T &&
|
||||||
|
elem.as!(T, No.stringConversion) == value)
|
||||||
{
|
{
|
||||||
removeAt(idx);
|
removeAt(idx);
|
||||||
return;
|
return;
|
||||||
|
@ -1067,11 +1203,20 @@ struct Node
|
||||||
assert(length == 4);
|
assert(length == 4);
|
||||||
assert(opIndex(2).as!int == 4);
|
assert(opIndex(2).as!int == 4);
|
||||||
assert(opIndex(3).as!int == 3);
|
assert(opIndex(3).as!int == 3);
|
||||||
|
|
||||||
|
add(YAMLNull());
|
||||||
|
assert(length == 5);
|
||||||
|
remove(YAMLNull());
|
||||||
|
assert(length == 4);
|
||||||
}
|
}
|
||||||
with(Node(["1", "2", "3"], [4, 5, 6]))
|
with(Node(["1", "2", "3"], [4, 5, 6]))
|
||||||
{
|
{
|
||||||
remove(4);
|
remove(4);
|
||||||
assert(length == 2);
|
assert(length == 2);
|
||||||
|
add("nullkey", YAMLNull());
|
||||||
|
assert(length == 3);
|
||||||
|
remove(YAMLNull());
|
||||||
|
assert(length == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1083,7 +1228,7 @@ struct Node
|
||||||
* If the node is a sequence, index must be integral.
|
* If the node is a sequence, index must be integral.
|
||||||
*
|
*
|
||||||
* If the node is a mapping, remove the first key-value pair where
|
* If the node is a mapping, remove the first key-value pair where
|
||||||
* key matches index (including conversion, so e.g. "42" matches 42).
|
* key matches index.
|
||||||
*
|
*
|
||||||
* If the node is a mapping and no key matches index, nothing is removed
|
* If the node is a mapping and no key matches index, nothing is removed
|
||||||
* and no exception is thrown. This ensures behavior siilar to D arrays
|
* and no exception is thrown. This ensures behavior siilar to D arrays
|
||||||
|
@ -1130,13 +1275,21 @@ struct Node
|
||||||
with(Node([1, 2, 3, 4, 3]))
|
with(Node([1, 2, 3, 4, 3]))
|
||||||
{
|
{
|
||||||
removeAt(3);
|
removeAt(3);
|
||||||
|
assertThrown!NodeException(removeAt("3"));
|
||||||
assert(length == 4);
|
assert(length == 4);
|
||||||
assert(opIndex(3).as!int == 3);
|
assert(opIndex(3).as!int == 3);
|
||||||
}
|
}
|
||||||
with(Node(["1", "2", "3"], [4, 5, 6]))
|
with(Node(["1", "2", "3"], [4, 5, 6]))
|
||||||
{
|
{
|
||||||
|
//no integer 2 key, so don't remove anything
|
||||||
|
removeAt(2);
|
||||||
|
assert(length == 3);
|
||||||
removeAt("2");
|
removeAt("2");
|
||||||
assert(length == 2);
|
assert(length == 2);
|
||||||
|
add(YAMLNull(), "nullval");
|
||||||
|
assert(length == 3);
|
||||||
|
removeAt(YAMLNull());
|
||||||
|
assert(length == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1201,9 +1354,11 @@ struct Node
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
static if(is(T == const))
|
auto stored = get!(const(Unqual!T), No.stringConversion);
|
||||||
|
//Need to handle NaNs separately.
|
||||||
|
static if(isFloatingPoint!T)
|
||||||
{
|
{
|
||||||
return rhs == get!T;
|
return rhs == stored || (isNaN(rhs) && isNaN(stored));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1429,26 +1584,14 @@ struct Node
|
||||||
static if(value){node = &pair.value;}
|
static if(value){node = &pair.value;}
|
||||||
else{node = &pair.key;}
|
else{node = &pair.key;}
|
||||||
|
|
||||||
static if(is(Unqual!T == Node))
|
|
||||||
|
bool typeMatch = (isFloatingPoint!T && (node.isInt || node.isFloat)) ||
|
||||||
|
(isIntegral!T && node.isInt) ||
|
||||||
|
(isSomeString!T && node.isString) ||
|
||||||
|
(node.isType!T);
|
||||||
|
if(typeMatch && *node == index)
|
||||||
{
|
{
|
||||||
if(*node == index){return idx;}
|
return idx;
|
||||||
}
|
|
||||||
else static if(isFloatingPoint!T)
|
|
||||||
{
|
|
||||||
//Need to handle NaNs separately.
|
|
||||||
if((node.as!T == index) ||
|
|
||||||
(isFloat && isNaN(index) && isNaN(node.as!real)))
|
|
||||||
{
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try if(node.as!(const T) == index){return idx;}
|
|
||||||
catch(NodeException e)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
Loading…
Reference in a new issue