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 }