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 }