1 module osc.address; 2 3 import std..string; 4 import std.array; 5 import std.algorithm; 6 import std.regex; 7 8 9 10 /// A parsed OSC address, splitted in multiple parts. 11 /// 12 struct Address 13 { 14 15 private { 16 bool _isContainer; 17 string[] _parts; 18 } 19 20 21 22 /// Constructs a new address from a string. 23 /// 24 this( string addr ) 25 in 26 { 27 assert( addr.length > 0 ); 28 } 29 do 30 { 31 _parts = addr 32 .split( "/" ) 33 .filter!( p => !p.empty ) 34 .array; 35 36 _isContainer = addr[ $-1 ] == '/'; 37 } 38 39 /// Constructs a new address from another one. 40 /// 41 this( const(Address) copy ) 42 { 43 _parts = copy._parts.dup; 44 } 45 46 /// Constructs a new address from a container list. 47 /// 48 this( const(string)[] parts, bool endsWithMethod = true ) 49 in 50 { 51 if( endsWithMethod ) 52 assert( parts.length > 0 ); 53 } 54 do 55 { 56 _parts = parts.dup; 57 _isContainer = !endsWithMethod; 58 } 59 60 61 62 /// Checks if the given address match the current one. 63 /// 64 bool match( const(Address) addr ) const 65 { 66 if( length != addr.length ) 67 return false; 68 69 if( isContainer != addr.isContainer ) 70 return false; 71 72 for( int i; i < length; i++ ) 73 { 74 string r = "^" ~ _parts[ i ] ~ "$"; 75 r = r.replace( "]*", "]#"); 76 r = r.replace( "*", "[a-zA-Z0-9%-]*" ); 77 r = r.replace( "[!", "[^" ); 78 r = r.replace( "{", "(" ); 79 r = r.replace( "}", ")" ); 80 r = r.replace( ",", "|" ); 81 r = r.replace( "]#", "]*" ); 82 83 84 if( !matchFirst( addr[i], r ) ) 85 return false; 86 } 87 88 return true; 89 } 90 91 92 93 /// Is the current address "/" ? 94 /// 95 @property bool isRoot( ) const 96 { 97 return _parts.length == 0; 98 } 99 100 /// Is the current address a container ? 101 /// 102 @property bool isContainer( ) const 103 { 104 return _isContainer; 105 } 106 107 /// Is the current address a method ? 108 /// 109 @property bool isMethod( ) const 110 { 111 return !_isContainer; 112 } 113 114 115 116 /// Returns the address of the container. 117 /// 118 /// If the current address is a container, then the function return 119 /// a copy of the current address. 120 /// 121 /// If the current address is a method, then the function return the 122 /// container of the method. 123 /// 124 @property Address container( ) const 125 { 126 if( isContainer ) 127 return Address( this ); 128 else 129 return Address( _parts[ 0 .. $-1 ], false ); 130 } 131 132 /// Returns the method's name of the address. 133 /// 134 @property string method( ) const 135 in 136 { 137 assert( isMethod ); 138 } 139 do 140 { 141 return _parts[ $-1 ]; 142 } 143 144 /// Sets the method's name of the address. 145 /// 146 /// See also `.setMethod`. 147 /// 148 @property void method( string m ) 149 { 150 setMethod( m ); 151 } 152 153 154 155 /// Converts the address to a string. 156 /// 157 string toString( ) const 158 { 159 if( isRoot ) 160 return "/"; 161 162 string res = ""; 163 164 foreach( part; _parts ) 165 res ~= "/" ~ part; 166 167 if( isContainer ) 168 res ~= "/"; 169 170 return res; 171 } 172 173 174 175 //======= Input Range 176 177 178 179 /// Checks if the container range is empty. 180 /// 181 /// Note: This function can't return `true` just after the constructor call. 182 /// 183 @property bool empty( ) const 184 { 185 return _parts.length == 0; 186 } 187 188 /// Gets the first container of the address. 189 /// 190 @property string front( ) const 191 in 192 { 193 assert( !empty ); 194 } 195 do 196 { 197 return _parts[ 0 ]; 198 } 199 200 /// Removes the first container of the address. 201 /// 202 void popFront( ) 203 in 204 { 205 assert( !empty ); 206 } 207 do 208 { 209 _parts = _parts[ 1 .. $ ]; 210 } 211 212 213 214 //======= Forward Range 215 216 217 218 /// Copies the container range. 219 /// 220 Address save( ) const 221 { 222 return Address( this ); 223 } 224 225 226 227 //======= Bidirectional Range 228 229 230 231 /// Gets the last container (or the method) of the address. 232 /// 233 @property string back( ) const 234 in 235 { 236 assert( !empty ); 237 } 238 do 239 { 240 return _parts[ $-1 ]; 241 } 242 243 /// Removes the last container (or the method) of the address. 244 /// 245 /// Calling this method on a method address change it to a container 246 /// address (`.isMethod == false` after the call). 247 /// 248 void popBack( ) 249 in 250 { 251 assert( !empty ); 252 } 253 do 254 { 255 _parts = _parts[ 0 .. $-1 ]; 256 257 if( isMethod ) 258 _isContainer = true; 259 } 260 261 262 263 //======= Random Access Range 264 265 266 267 /// Access to a container in the address. 268 /// 269 string opIndex( size_t idx ) const 270 in 271 { 272 assert( idx < _parts.length ); 273 } 274 do 275 { 276 return _parts[ idx ]; 277 } 278 279 /// Returns the number of containers (including the method) in the 280 /// address. 281 @property size_t length( ) const 282 { 283 return _parts.length; 284 } 285 286 287 288 //====== Modifying the address 289 290 291 292 /// Adds a container to the start of the address. 293 /// 294 Address* pushFront( string cont ) 295 { 296 _parts = [cont] ~ _parts; 297 return &this; 298 } 299 300 /// Adds a container to the end of the address. 301 /// 302 /// When called on a method address, it becomes a container address. 303 /// 304 Address* pushBack( string cont ) 305 { 306 _parts = _parts ~ [cont]; 307 308 if( isMethod ) 309 _isContainer = true; 310 311 return &this; 312 } 313 314 315 316 /// Sets the method of the address. 317 /// 318 /// If the address is a container address, then it becomes a method address 319 /// and the method is added to the end of the address. 320 /// 321 /// If the address is already a method address, then the method's name 322 /// is changed. 323 /// 324 Address* setMethod( string m ) 325 { 326 if( isMethod ) 327 _parts[ $-1 ] = m; 328 else 329 { 330 _isContainer = false; 331 _parts ~= m; 332 } 333 334 return &this; 335 } 336 337 338 //====== Operators 339 340 341 /// Adds a container at the end of the address. 342 /// 343 Address opBinary( const(string) op )( string rhs ) const if( op == "+" ) 344 { 345 Address res = Address( this ); 346 res.pushBack( rhs ); 347 return res; 348 } 349 350 /// Checks if the object is the same as the current instance. 351 /// 352 bool opEquals()( auto ref const Address addr ) const 353 { 354 if( addr.length != length || addr.isContainer != isContainer ) 355 return false; 356 357 for( int i; i < length; i++ ) 358 if( addr[i] != _parts[i] ) 359 return false; 360 361 return true; 362 } 363 364 365 366 unittest 367 { 368 Address addr = Address( "/" ); 369 370 assert( addr.isRoot ); 371 assert( addr.isContainer ); 372 assert( addr.toString == "/" ); 373 assert( addr.length == 0 ); 374 assert( addr.empty == true ); 375 376 addr.pushBack( "added" ); 377 378 assert( !addr.isRoot ); 379 assert( addr.isContainer ); 380 assert( addr.toString == "/added/" ); 381 assert( addr.length == 1 ); 382 assert( addr.empty == false ); 383 assert( addr.front == "added" ); 384 } 385 386 unittest 387 { 388 Address addr = Address( "/container/" ); 389 390 assert( !addr.isRoot ); 391 assert( addr.isContainer ); 392 assert( addr.toString == "/container/" ); 393 assert( addr.length == 1 ); 394 assert( addr.front == "container" ); 395 396 addr.pushFront( "added" ); 397 398 assert( !addr.isRoot ); 399 assert( addr.isContainer ); 400 assert( addr.toString == "/added/container/" ); 401 assert( addr.length == 2 ); 402 assert( addr.empty == false ); 403 assert( addr.front == "added" ); 404 assert( addr.back == "container" ); 405 } 406 407 unittest 408 { 409 Address addr = Address( "/container/method" ); 410 411 assert( !addr.isRoot ); 412 assert( addr.isMethod ); 413 assert( addr.toString == "/container/method" ); 414 assert( addr.length == 2 ); 415 assert( addr.front == "container" ); 416 assert( addr.back == "method" ); 417 assert( addr.method == "method" ); 418 419 addr.pushFront( "added-front" ); 420 421 assert( !addr.isRoot ); 422 assert( addr.isMethod ); 423 assert( addr.toString == "/added-front/container/method" ); 424 assert( addr.length == 3 ); 425 assert( addr.front == "added-front" ); 426 assert( addr.back == "method" ); 427 assert( addr.method == "method" ); 428 429 addr.pushBack( "added-back" ); 430 431 assert( !addr.isRoot ); 432 assert( addr.isContainer ); 433 assert( addr.toString == "/added-front/container/method/added-back/" ); 434 assert( addr.length == 4 ); 435 assert( addr.front == "added-front" ); 436 assert( addr.back == "added-back" ); 437 438 addr.method = "new-method"; 439 440 assert( addr.isMethod ); 441 assert( addr.length == 5 ); 442 assert( addr.back == "new-method" ); 443 assert( addr.method == "new-method" ); 444 445 addr.method = "other-method"; 446 447 assert( addr.isMethod ); 448 assert( addr.length == 5 ); 449 assert( addr.back == "other-method" ); 450 assert( addr.method == "other-method" ); 451 } 452 453 unittest 454 { 455 const Address addr = Address( "/container/" ) + "added"; 456 457 assert( addr.isContainer ); 458 assert( addr.length == 2 ); 459 assert( addr.back == "added" ); 460 } 461 462 unittest 463 { 464 Address pattern = Address( "/container/" ); 465 466 assert( pattern.match( Address( "/container/" ) ) ); 467 assert( !pattern.match( Address( "/container" ) ) ); 468 assert( !pattern.match( Address( "/container/method" ) ) ); 469 assert( !pattern.match( Address( "/container/container/" ) ) ); 470 } 471 472 unittest 473 { 474 Address pattern = Address( "/method" ); 475 476 assert( pattern.match( Address( "/method" ) ) ); 477 assert( !pattern.match( Address( "/method/" ) ) ); 478 assert( !pattern.match( Address( "/method/container/" ) ) ); 479 assert( !pattern.match( Address( "/method/method" ) ) ); 480 } 481 482 unittest 483 { 484 Address pattern = Address( "/cont*/" ); 485 486 assert( pattern.match( Address( "/container/" ) ) ); 487 assert( !pattern.match( Address( "/cantainer/" ) ) ); 488 } 489 490 unittest 491 { 492 Address pattern = Address( "/cont?ainer/" ); 493 494 assert( pattern.match( Address( "/container/") ) ); 495 assert( !pattern.match( Address( "/conteiner/" ) ) ); 496 } 497 498 unittest 499 { 500 Address pattern = Address( "/[a-z]*/cont" ); 501 502 assert( pattern.match( Address( "/container/cont" ) ) ); 503 assert( !pattern.match( Address( "/container/cont2") ) ); 504 assert( !pattern.match( Address( "/cont-ainer/cont") ) ); 505 } 506 507 unittest 508 { 509 Address pattern = Address( "/[!0-9]*/test!" ); 510 511 assert( pattern.match( Address( "/container/test!" ) ) ); 512 assert( !pattern.match( Address( "/container2/test!" ) ) ); 513 } 514 515 unittest 516 { 517 Address pattern = Address( "/[aA]bc/" ); 518 519 assert( pattern.match( Address( "/abc/" ) ) ); 520 assert( pattern.match( Address( "/Abc/" ) ) ); 521 assert( !pattern.match( Address( "/xyz/" ) ) ); 522 } 523 524 unittest 525 { 526 Address pattern = Address( "/{cont1,cont2}/" ); 527 528 assert( pattern.match( Address( "/cont1/" ) ) ); 529 assert( pattern.match( Address( "/cont2/" ) ) ); 530 assert( !pattern.match( Address( "/cont3/" ) ) ); 531 } 532 533 unittest 534 { 535 const Address cont = Address( "/container/one/" ); 536 const Address meth = Address( "/container/method" ); 537 538 assert( cont == Address( "/container/one/" ) ); 539 assert( cont != Address( "/container/one" ) ); 540 541 assert( meth == Address( "/container/method" ) ); 542 assert( meth != Address( "/container/method/" ) ); 543 544 assert( cont != meth ); 545 } 546 547 }