diff --git a/source/ddbus/simple.d b/source/ddbus/simple.d index 96cd6aa..d58ac12 100644 --- a/source/ddbus/simple.d +++ b/source/ddbus/simple.d @@ -8,6 +8,10 @@ import std.string; import std.traits; class PathIface { + this(Connection conn, string dest, ObjectPath path, string iface) { + this(conn, dest, path.value, iface); + } + this(Connection conn, string dest, string path, string iface) { this.conn = conn; this.dest = dest.toStringz(); diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index 31ca7c5..2a78a2f 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -18,8 +18,8 @@ public import ddbus.exception : wrapErrors, DBusException; struct ObjectPath { private string _value; - this(string objPath) { - enforce(_isValid(objPath)); + this(string objPath) pure @safe { + enforce(isValid(objPath)); _value = objPath; } @@ -27,17 +27,73 @@ struct ObjectPath { return _value; } - size_t toHash() const pure nothrow @trusted { + /++ + Returns the string representation of this ObjectPath. + +/ + string value() const pure @nogc nothrow @safe { + return _value; + } + + size_t toHash() const pure @nogc nothrow @trusted { return hashOf(_value); } - bool opEquals(ref const typeof(this) b) const pure nothrow @safe { + bool opEquals(ref const typeof(this) b) const pure @nogc nothrow @safe { return _value == b._value; } - private static bool _isValid(string objPath) { - import std.regex : matchFirst, ctRegex; - return cast(bool) objPath.matchFirst(ctRegex!("^((/[0-9A-Za-z_]+)+|/)$")); + ObjectPath opBinary(string op : "~")(string rhs) const pure @safe { + if (!rhs.startsWith("/")) + return opBinary!"~"(ObjectPath("/" ~ rhs)); + else + return opBinary!"~"(ObjectPath(rhs)); + } + + ObjectPath opBinary(string op : "~")(ObjectPath rhs) const pure @safe + in { + assert(ObjectPath.isValid(_value) && ObjectPath.isValid(rhs._value)); + } out (v) { + assert(ObjectPath.isValid(v._value)); + } body { + ObjectPath ret; + + if (_value == "/") + ret._value = rhs._value; + else + ret._value = _value ~ rhs._value; + + return ret; + } + + void opOpAssign(string op : "~")(string rhs) pure @safe { + _value = opBinary!"~"(rhs)._value; + } + + void opOpAssign(string op : "~")(ObjectPath rhs) pure @safe { + _value = opBinary!"~"(rhs)._value; + } + + /++ + Returns: `false` for empty strings or strings that don't match the + pattern `(/[0-9A-Za-z_]+)+|/`. + +/ + static bool isValid(string objPath) pure @nogc nothrow @safe { + import std.ascii : isAlphaNum; + + if (!objPath.length) + return false; + if (objPath == "/") + return true; + if (objPath[0] != '/' || objPath[$ - 1] == '/') + return false; + // .representation to avoid unicode exceptions -> @nogc & nothrow + return objPath.representation.splitter('/').drop(1) + .all!(a => + a.length && + a.all!(c => + c.isAlphaNum || c == '_' + ) + ); } } @@ -46,12 +102,26 @@ unittest { ObjectPath("some.invalid/object_path").assertThrow(); ObjectPath("/path/with/TrailingSlash/").assertThrow(); + ObjectPath("/path/without/TrailingSlash").assertNotThrown(); string path = "/org/freedesktop/DBus"; auto obj = ObjectPath(path); - obj.toString().assertEqual(path); + obj.value.assertEqual(path); obj.toHash().assertEqual(path.hashOf); } +unittest { + import dunit.toolkit; + + ObjectPath a = ObjectPath("/org/freedesktop"); + a.assertEqual(ObjectPath("/org/freedesktop")); + a ~= ObjectPath("/UPower"); + a.assertEqual(ObjectPath("/org/freedesktop/UPower")); + a ~= "Device"; + a.assertEqual(ObjectPath("/org/freedesktop/UPower/Device")); + (a ~ "0").assertEqual(ObjectPath("/org/freedesktop/UPower/Device/0")); + a.assertEqual(ObjectPath("/org/freedesktop/UPower/Device")); +} + /// Structure allowing typeless parameters struct DBusAny { /// DBus type of the value (never 'v'), see typeSig!T