rpatricia 0.05 → 0.06
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changes +16 -0
- data/README +12 -5
- data/ext/rpatricia/extconf.rb +2 -1
- data/ext/rpatricia/patricia.c +7 -1
- data/ext/rpatricia/rpatricia.c +172 -47
- data/rpatricia.gemspec +1 -1
- metadata +16 -5
data/Changes
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
0.06 2010/09/14
|
|
2
|
+
- rely on Ruby GC for allocations/free
|
|
3
|
+
no need to explicitly destroy objects anymore
|
|
4
|
+
- Ruby 1.9.2 C API compatibility
|
|
5
|
+
- allow creation of subclasses of Patricia class
|
|
6
|
+
- non-String objects may be stored as node-data
|
|
7
|
+
- Patricia::Node objects are returned when match succeeds
|
|
8
|
+
It's no longer possible to call tree methods on node
|
|
9
|
+
objects and cause segfaults.
|
|
10
|
+
- Patricia#show_nodes may be given a custom IO-like object
|
|
11
|
+
- sync docs with remove, match_best and match_exact behavior
|
|
12
|
+
- ArgumentError is raised for invalid addresses
|
|
13
|
+
no more assertion failures for bad addresses
|
|
14
|
+
- Patricia#show_nodes and Patricia#num_nodes no longer
|
|
15
|
+
segfaults on empty trees
|
|
16
|
+
|
|
1
17
|
0.05 2010/03/12
|
|
2
18
|
- reorganized directory layout
|
|
3
19
|
no chance of accidentally having 'extconf' in the require path
|
data/README
CHANGED
|
@@ -60,10 +60,9 @@ new:
|
|
|
60
60
|
|
|
61
61
|
pt = Patricia.new
|
|
62
62
|
|
|
63
|
-
This is the class' constructor - it returns a Patricia object
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
address and mask values as keys.
|
|
63
|
+
This is the class' constructor - it returns a Patricia object. For
|
|
64
|
+
now, the constructor takes no arguments, and defaults to creating a
|
|
65
|
+
tree which uses AF_INET IPv4 address and mask values as keys.
|
|
67
66
|
|
|
68
67
|
The Patricia object will be destroyed automatically when there are
|
|
69
68
|
no longer any references to it.
|
|
@@ -124,6 +123,14 @@ search_exact:
|
|
|
124
123
|
|
|
125
124
|
match_exact: An alias of search_exact.
|
|
126
125
|
|
|
126
|
+
include?:
|
|
127
|
+
|
|
128
|
+
pt.include?(key_string);
|
|
129
|
+
|
|
130
|
+
This method behaves like match_best, but returns true on success
|
|
131
|
+
and false on failure. This method is more efficient than match_best
|
|
132
|
+
as it does not allocate a new object.
|
|
133
|
+
|
|
127
134
|
remove:
|
|
128
135
|
|
|
129
136
|
pt.remove(key_string);
|
|
@@ -132,7 +139,7 @@ remove:
|
|
|
132
139
|
and mask specified from the Patricia Trie.
|
|
133
140
|
|
|
134
141
|
If the matching node is found in the Patricia Trie, it is removed,
|
|
135
|
-
and this method returns the true. This method returns
|
|
142
|
+
and this method returns the true. This method returns false on
|
|
136
143
|
failure.
|
|
137
144
|
|
|
138
145
|
remove_node: An alias of remove
|
data/ext/rpatricia/extconf.rb
CHANGED
data/ext/rpatricia/patricia.c
CHANGED
|
@@ -31,7 +31,13 @@ static char copyright[] =
|
|
|
31
31
|
|
|
32
32
|
#include "patricia.h"
|
|
33
33
|
|
|
34
|
-
#
|
|
34
|
+
#if defined(HAVE_RUBY_XCALLOC)
|
|
35
|
+
# include <ruby.h>
|
|
36
|
+
# define calloc xcalloc
|
|
37
|
+
# define Delete xfree
|
|
38
|
+
#else
|
|
39
|
+
# define Delete free
|
|
40
|
+
#endif
|
|
35
41
|
|
|
36
42
|
/* { from prefix.c */
|
|
37
43
|
|
data/ext/rpatricia/rpatricia.c
CHANGED
|
@@ -4,28 +4,57 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include "ruby.h"
|
|
7
|
-
#include <stdio.h> /* printf */
|
|
8
7
|
#include <stdlib.h>
|
|
9
8
|
#include "patricia.h"
|
|
9
|
+
#include <assert.h>
|
|
10
10
|
|
|
11
|
-
VALUE cPatricia;
|
|
11
|
+
static VALUE cPatricia, cNode;
|
|
12
12
|
|
|
13
|
-
void dummy(void) {}
|
|
13
|
+
static void dummy(void) {}
|
|
14
14
|
|
|
15
15
|
static VALUE
|
|
16
16
|
p_destroy (VALUE self)
|
|
17
17
|
{
|
|
18
18
|
patricia_tree_t *tree;
|
|
19
|
+
|
|
19
20
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
20
|
-
|
|
21
|
+
Clear_Patricia(tree, dummy);
|
|
22
|
+
tree->head = NULL; /* Clear_Patricia() should do this, actually */
|
|
23
|
+
|
|
21
24
|
return Qtrue;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
static void
|
|
28
|
+
p_node_mark (void *ptr)
|
|
29
|
+
{
|
|
30
|
+
patricia_node_t *node = ptr;
|
|
31
|
+
|
|
32
|
+
rb_gc_mark((VALUE)node->data);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static VALUE
|
|
36
|
+
wrap_node(patricia_node_t *node)
|
|
37
|
+
{
|
|
38
|
+
/* node will be freed when parent is freed */
|
|
39
|
+
return Data_Wrap_Struct(cNode, p_node_mark, 0, node);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static prefix_t *
|
|
43
|
+
my_ascii2prefix(int family, VALUE str)
|
|
44
|
+
{
|
|
45
|
+
char *cstr = StringValuePtr(str);
|
|
46
|
+
prefix_t *prefix = ascii2prefix(family, cstr);
|
|
47
|
+
|
|
48
|
+
if (!prefix)
|
|
49
|
+
rb_raise(rb_eArgError, "invalid prefix: %s", cstr);
|
|
50
|
+
|
|
51
|
+
return prefix;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static VALUE
|
|
25
55
|
p_add (int argc, VALUE *argv, VALUE self)
|
|
26
56
|
{
|
|
27
|
-
|
|
28
|
-
char *user_data;
|
|
57
|
+
VALUE user_data;
|
|
29
58
|
patricia_tree_t *tree;
|
|
30
59
|
patricia_node_t *node;
|
|
31
60
|
prefix_t *prefix;
|
|
@@ -34,33 +63,34 @@ p_add (int argc, VALUE *argv, VALUE self)
|
|
|
34
63
|
return Qnil;
|
|
35
64
|
|
|
36
65
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
37
|
-
prefix =
|
|
66
|
+
prefix = my_ascii2prefix(AF_INET, argv[0]);
|
|
38
67
|
node = patricia_lookup(tree, prefix);
|
|
39
68
|
Deref_Prefix(prefix);
|
|
40
69
|
|
|
41
70
|
if (argc == 2) {
|
|
42
|
-
user_data =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
user_data = argv[1];
|
|
72
|
+
|
|
73
|
+
/* for backwards compatibility, we always dup and return new strings */
|
|
74
|
+
if (TYPE(user_data) == T_STRING)
|
|
75
|
+
user_data = rb_str_dup(user_data);
|
|
46
76
|
} else {
|
|
47
|
-
|
|
48
|
-
sprintf((char *)node->data, "");
|
|
77
|
+
user_data = rb_str_new(NULL, 0);
|
|
49
78
|
}
|
|
50
|
-
|
|
79
|
+
PATRICIA_DATA_SET(node, user_data);
|
|
80
|
+
|
|
81
|
+
/* node will be freed when parent is freed */
|
|
82
|
+
return wrap_node(node);
|
|
51
83
|
}
|
|
52
84
|
|
|
53
85
|
static VALUE
|
|
54
86
|
p_remove (VALUE self, VALUE r_key)
|
|
55
87
|
{
|
|
56
|
-
char *c_key;
|
|
57
88
|
patricia_tree_t *tree;
|
|
58
89
|
patricia_node_t *node;
|
|
59
90
|
prefix_t *prefix;
|
|
60
91
|
|
|
61
92
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
62
|
-
|
|
63
|
-
prefix = ascii2prefix (AF_INET, c_key);
|
|
93
|
+
prefix = my_ascii2prefix (AF_INET, r_key);
|
|
64
94
|
node = patricia_search_exact(tree, prefix);
|
|
65
95
|
Deref_Prefix (prefix);
|
|
66
96
|
|
|
@@ -80,15 +110,26 @@ p_match (VALUE self, VALUE r_key)
|
|
|
80
110
|
prefix_t *prefix;
|
|
81
111
|
|
|
82
112
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
83
|
-
prefix =
|
|
113
|
+
prefix = my_ascii2prefix (AF_INET, r_key);
|
|
84
114
|
node = patricia_search_best(tree, prefix);
|
|
85
115
|
Deref_Prefix (prefix);
|
|
86
116
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
return node ? wrap_node(node) : Qnil;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static VALUE
|
|
121
|
+
p_include (VALUE self, VALUE r_key)
|
|
122
|
+
{
|
|
123
|
+
patricia_tree_t *tree;
|
|
124
|
+
patricia_node_t *node;
|
|
125
|
+
prefix_t *prefix;
|
|
126
|
+
|
|
127
|
+
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
128
|
+
prefix = my_ascii2prefix (AF_INET, r_key);
|
|
129
|
+
node = patricia_search_best(tree, prefix);
|
|
130
|
+
Deref_Prefix (prefix);
|
|
91
131
|
|
|
132
|
+
return node ? Qtrue : Qfalse;
|
|
92
133
|
}
|
|
93
134
|
|
|
94
135
|
static VALUE
|
|
@@ -99,14 +140,11 @@ p_match_exact (VALUE self, VALUE r_key)
|
|
|
99
140
|
prefix_t *prefix;
|
|
100
141
|
|
|
101
142
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
102
|
-
prefix =
|
|
143
|
+
prefix = my_ascii2prefix (AF_INET, r_key);
|
|
103
144
|
node = patricia_search_exact(tree, prefix);
|
|
104
145
|
Deref_Prefix (prefix);
|
|
105
146
|
|
|
106
|
-
|
|
107
|
-
return Data_Wrap_Struct(cPatricia, 0, 0, node);
|
|
108
|
-
else
|
|
109
|
-
return Qfalse;
|
|
147
|
+
return node ? wrap_node(node) : Qnil;
|
|
110
148
|
}
|
|
111
149
|
|
|
112
150
|
static VALUE
|
|
@@ -116,33 +154,63 @@ p_num_nodes (VALUE self)
|
|
|
116
154
|
patricia_tree_t *tree;
|
|
117
155
|
|
|
118
156
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
119
|
-
n = patricia_walk_inorder(tree->head, dummy);
|
|
157
|
+
n = tree->head ? patricia_walk_inorder(tree->head, dummy) : 0;
|
|
120
158
|
|
|
121
159
|
return INT2NUM(n);
|
|
122
160
|
}
|
|
123
161
|
|
|
162
|
+
/* needed for Ruby 1.8.6, in 1.8.7 and later */
|
|
163
|
+
#ifndef HAVE_RB_STR_SET_LEN
|
|
164
|
+
static void
|
|
165
|
+
rb_str_set_len(VALUE str, long len)
|
|
166
|
+
{
|
|
167
|
+
RSTRING(str)->len = len;
|
|
168
|
+
RSTRING(str)->ptr[len] = '\0';
|
|
169
|
+
}
|
|
170
|
+
#endif
|
|
171
|
+
|
|
124
172
|
static VALUE
|
|
125
|
-
p_print_nodes (VALUE self)
|
|
173
|
+
p_print_nodes (int argc, VALUE *argv, VALUE self)
|
|
126
174
|
{
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
ID id_printf = rb_intern("printf");
|
|
176
|
+
VALUE fmt = rb_str_new2("node: %s\n");
|
|
177
|
+
VALUE buf = rb_str_buf_new(128);
|
|
178
|
+
char *cbuf;
|
|
129
179
|
patricia_tree_t *tree;
|
|
130
180
|
patricia_node_t *node;
|
|
181
|
+
VALUE out;
|
|
131
182
|
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
132
183
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
184
|
+
rb_scan_args(argc, argv, "01", &out);
|
|
185
|
+
if (NIL_P(out))
|
|
186
|
+
out = rb_stdout;
|
|
187
|
+
|
|
188
|
+
if (tree->head) {
|
|
189
|
+
PATRICIA_WALK(tree->head, node) {
|
|
190
|
+
rb_str_resize(buf, 128);
|
|
191
|
+
cbuf = RSTRING_PTR(buf);
|
|
192
|
+
prefix_toa2x(node->prefix, cbuf, 1);
|
|
193
|
+
rb_str_set_len(buf, strlen(cbuf));
|
|
194
|
+
rb_funcall(out, id_printf, 2, fmt, buf);
|
|
195
|
+
} PATRICIA_WALK_END;
|
|
196
|
+
}
|
|
137
197
|
return Qtrue;
|
|
138
198
|
}
|
|
139
199
|
|
|
140
200
|
static VALUE
|
|
141
201
|
p_data (VALUE self)
|
|
142
202
|
{
|
|
203
|
+
VALUE user_data;
|
|
143
204
|
patricia_node_t *node;
|
|
144
205
|
Data_Get_Struct(self, patricia_node_t, node);
|
|
145
|
-
|
|
206
|
+
|
|
207
|
+
user_data = (VALUE)node->data;
|
|
208
|
+
|
|
209
|
+
/* for backwards compatibility, we always dup and return new strings */
|
|
210
|
+
if (TYPE(user_data) == T_STRING)
|
|
211
|
+
user_data = rb_str_dup(user_data);
|
|
212
|
+
|
|
213
|
+
return user_data;
|
|
146
214
|
}
|
|
147
215
|
|
|
148
216
|
static VALUE
|
|
@@ -173,21 +241,75 @@ p_prefixlen (VALUE self)
|
|
|
173
241
|
return INT2NUM(node->prefix->bitlen);
|
|
174
242
|
}
|
|
175
243
|
|
|
244
|
+
/* called during GC for each node->data attached to a Patricia tree */
|
|
245
|
+
static void
|
|
246
|
+
p_tree_mark_each(prefix_t *prefix, void *data)
|
|
247
|
+
{
|
|
248
|
+
VALUE user_data = (VALUE)data;
|
|
249
|
+
|
|
250
|
+
rb_gc_mark(user_data);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static void
|
|
254
|
+
p_tree_mark (void *ptr)
|
|
255
|
+
{
|
|
256
|
+
patricia_tree_t *tree = ptr;
|
|
257
|
+
|
|
258
|
+
patricia_process(tree, p_tree_mark_each);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static void
|
|
262
|
+
p_tree_free (void *ptr)
|
|
263
|
+
{
|
|
264
|
+
patricia_tree_t *tree = ptr;
|
|
265
|
+
|
|
266
|
+
/* no need to explicitly free each node->data, GC will do it for us */
|
|
267
|
+
Destroy_Patricia(tree, NULL);
|
|
268
|
+
}
|
|
269
|
+
|
|
176
270
|
static VALUE
|
|
177
|
-
|
|
271
|
+
p_alloc(VALUE klass)
|
|
178
272
|
{
|
|
179
273
|
patricia_tree_t *tree;
|
|
180
274
|
tree = New_Patricia(32); /* assuming only IPv4 */
|
|
181
|
-
|
|
275
|
+
|
|
276
|
+
return Data_Wrap_Struct(klass, p_tree_mark, p_tree_free, tree);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static VALUE
|
|
280
|
+
p_init_copy(VALUE self, VALUE orig)
|
|
281
|
+
{
|
|
282
|
+
patricia_tree_t *orig_tree;
|
|
283
|
+
|
|
284
|
+
Data_Get_Struct(orig, patricia_tree_t, orig_tree);
|
|
285
|
+
if (orig_tree->head) {
|
|
286
|
+
patricia_tree_t *tree;
|
|
287
|
+
patricia_node_t *orig_node, *node;
|
|
288
|
+
prefix_t prefix;
|
|
289
|
+
VALUE user_data;
|
|
290
|
+
|
|
291
|
+
Data_Get_Struct(self, patricia_tree_t, tree);
|
|
292
|
+
PATRICIA_WALK(orig_tree->head, orig_node) {
|
|
293
|
+
node = patricia_lookup(tree, orig_node->prefix);
|
|
294
|
+
assert(node->prefix == orig_node->prefix);
|
|
295
|
+
|
|
296
|
+
user_data = (VALUE)(orig_node->data);
|
|
297
|
+
if (T_STRING == TYPE(user_data))
|
|
298
|
+
user_data = rb_str_dup(user_data);
|
|
299
|
+
PATRICIA_DATA_SET(node, user_data);
|
|
300
|
+
} PATRICIA_WALK_END;
|
|
301
|
+
}
|
|
182
302
|
}
|
|
183
303
|
|
|
184
304
|
void
|
|
185
305
|
Init_rpatricia (void)
|
|
186
306
|
{
|
|
187
307
|
cPatricia = rb_define_class("Patricia", rb_cObject);
|
|
308
|
+
cNode = rb_define_class_under(cPatricia, "Node", rb_cObject);
|
|
188
309
|
|
|
189
|
-
/*
|
|
190
|
-
|
|
310
|
+
/* allocate new Patricia object, called before initialize */
|
|
311
|
+
rb_define_alloc_func(cPatricia, p_alloc);
|
|
312
|
+
rb_define_method(cPatricia, "initialize_copy", p_init_copy, 1);
|
|
191
313
|
|
|
192
314
|
/*---------- methods to tree ----------*/
|
|
193
315
|
/* add string */
|
|
@@ -202,24 +324,27 @@ Init_rpatricia (void)
|
|
|
202
324
|
rb_define_method(cPatricia, "match_exact", p_match_exact, 1);
|
|
203
325
|
rb_define_method(cPatricia, "search_exact", p_match_exact, 1);
|
|
204
326
|
|
|
327
|
+
/* check existence */
|
|
328
|
+
rb_define_method(cPatricia, "include?", p_include, 1);
|
|
329
|
+
|
|
205
330
|
/* removal */
|
|
206
331
|
rb_define_method(cPatricia, "remove", p_remove, 1);
|
|
207
332
|
rb_define_method(cPatricia, "remove_node", p_remove, 1);
|
|
208
333
|
|
|
209
334
|
/* derivatives of climb */
|
|
210
335
|
rb_define_method(cPatricia, "num_nodes", p_num_nodes, 0);
|
|
211
|
-
rb_define_method(cPatricia, "show_nodes", p_print_nodes,
|
|
336
|
+
rb_define_method(cPatricia, "show_nodes", p_print_nodes, -1);
|
|
212
337
|
|
|
213
338
|
/* destroy tree */
|
|
214
339
|
rb_define_method(cPatricia, "destroy", p_destroy, 0);
|
|
215
340
|
rb_define_method(cPatricia, "clear", p_destroy, 0);
|
|
216
341
|
|
|
217
342
|
/*---------- methods to node ----------*/
|
|
218
|
-
rb_define_method(
|
|
219
|
-
rb_define_method(
|
|
220
|
-
rb_define_method(
|
|
221
|
-
rb_define_method(
|
|
222
|
-
rb_define_method(
|
|
343
|
+
rb_define_method(cNode, "data", p_data, 0);
|
|
344
|
+
rb_define_method(cNode, "show_data", p_data, 0);
|
|
345
|
+
rb_define_method(cNode, "network", p_network, 0);
|
|
346
|
+
rb_define_method(cNode, "prefix", p_prefix, 0);
|
|
347
|
+
rb_define_method(cNode, "prefixlen", p_prefixlen, 0);
|
|
223
348
|
// rb_define_method(cPatricia, "family", p_family, 0);
|
|
224
349
|
|
|
225
350
|
}
|
data/rpatricia.gemspec
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = %q{rpatricia}
|
|
6
|
-
s.version = %q{0.
|
|
6
|
+
s.version = %q{0.06} # remember to update Changes if this is changed
|
|
7
7
|
|
|
8
8
|
s.homepage = "http://www.goto.info.waseda.ac.jp/~tatsuya/rpatricia/"
|
|
9
9
|
|
metadata
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rpatricia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
4
|
+
hash: 7
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 6
|
|
9
|
+
version: "0.06"
|
|
5
10
|
platform: ruby
|
|
6
11
|
authors:
|
|
7
12
|
- Tatsuya Mori
|
|
@@ -10,7 +15,7 @@ autorequire:
|
|
|
10
15
|
bindir: bin
|
|
11
16
|
cert_chain: []
|
|
12
17
|
|
|
13
|
-
date: 2010-
|
|
18
|
+
date: 2010-09-15 00:00:00 +00:00
|
|
14
19
|
default_executable:
|
|
15
20
|
dependencies: []
|
|
16
21
|
|
|
@@ -51,21 +56,27 @@ rdoc_options: []
|
|
|
51
56
|
require_paths:
|
|
52
57
|
- lib
|
|
53
58
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
none: false
|
|
54
60
|
requirements:
|
|
55
61
|
- - ">="
|
|
56
62
|
- !ruby/object:Gem::Version
|
|
63
|
+
hash: 3
|
|
64
|
+
segments:
|
|
65
|
+
- 0
|
|
57
66
|
version: "0"
|
|
58
|
-
version:
|
|
59
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
|
+
none: false
|
|
60
69
|
requirements:
|
|
61
70
|
- - ">="
|
|
62
71
|
- !ruby/object:Gem::Version
|
|
72
|
+
hash: 3
|
|
73
|
+
segments:
|
|
74
|
+
- 0
|
|
63
75
|
version: "0"
|
|
64
|
-
version:
|
|
65
76
|
requirements: []
|
|
66
77
|
|
|
67
78
|
rubyforge_project:
|
|
68
|
-
rubygems_version: 1.3.
|
|
79
|
+
rubygems_version: 1.3.7
|
|
69
80
|
signing_key:
|
|
70
81
|
specification_version: 3
|
|
71
82
|
summary: module for fast IP address/prefix lookups
|