sfcc 0.1.0
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/CHANGELOG.rdoc +4 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +28 -0
- data/README.rdoc +60 -0
- data/Rakefile +32 -0
- data/TODO.rdoc +28 -0
- data/ext/sfcc/cim_class.c +274 -0
- data/ext/sfcc/cim_class.h +12 -0
- data/ext/sfcc/cim_client.c +862 -0
- data/ext/sfcc/cim_client.h +12 -0
- data/ext/sfcc/cim_enumeration.c +65 -0
- data/ext/sfcc/cim_enumeration.h +12 -0
- data/ext/sfcc/cim_instance.c +357 -0
- data/ext/sfcc/cim_instance.h +12 -0
- data/ext/sfcc/cim_object_path.c +419 -0
- data/ext/sfcc/cim_object_path.h +12 -0
- data/ext/sfcc/cim_string.c +64 -0
- data/ext/sfcc/cim_string.h +12 -0
- data/ext/sfcc/extconf.rb +8 -0
- data/ext/sfcc/sfcc.c +344 -0
- data/ext/sfcc/sfcc.h +87 -0
- data/lib/sfcc.rb +174 -0
- data/test/helper.rb +32 -0
- data/test/test_sfcc.rb +69 -0
- data/test/test_sfcc_cim_class.rb +53 -0
- data/test/test_sfcc_cim_client.rb +151 -0
- data/test/test_sfcc_cim_enumeration.rb +24 -0
- data/test/test_sfcc_cim_instance.rb +89 -0
- data/test/test_sfcc_cim_object_path.rb +85 -0
- metadata +152 -0
data/CHANGELOG.rdoc
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2009 Novell Inc.
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
CHANGELOG.rdoc
|
2
|
+
MIT-LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
TODO.rdoc
|
7
|
+
ext/sfcc/cim_class.c
|
8
|
+
ext/sfcc/cim_class.h
|
9
|
+
ext/sfcc/cim_client.c
|
10
|
+
ext/sfcc/cim_client.h
|
11
|
+
ext/sfcc/cim_enumeration.c
|
12
|
+
ext/sfcc/cim_enumeration.h
|
13
|
+
ext/sfcc/cim_instance.c
|
14
|
+
ext/sfcc/cim_instance.h
|
15
|
+
ext/sfcc/cim_object_path.c
|
16
|
+
ext/sfcc/cim_object_path.h
|
17
|
+
ext/sfcc/cim_string.c
|
18
|
+
ext/sfcc/cim_string.h
|
19
|
+
ext/sfcc/extconf.rb
|
20
|
+
ext/sfcc/sfcc.c
|
21
|
+
ext/sfcc/sfcc.h
|
22
|
+
lib/sfcc.rb
|
23
|
+
test/helper.rb
|
24
|
+
test/test_sfcc.rb
|
25
|
+
test/test_sfcc_cim_class.rb
|
26
|
+
test/test_sfcc_cim_client.rb
|
27
|
+
test/test_sfcc_cim_instance.rb
|
28
|
+
test/test_sfcc_cim_object_path.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
= ruby-sfcc
|
3
|
+
|
4
|
+
* http://github.com/dmacvicar/ruby-sfcc
|
5
|
+
|
6
|
+
== INTRODUCTION
|
7
|
+
|
8
|
+
ruby-sfcc provides the SBLIM client API ( http://sblim.wiki.sourceforge.net/Sfcc ) for ruby.
|
9
|
+
|
10
|
+
You can use it to connect to any CIMOM talking CIM-XML, but it can take advantage of
|
11
|
+
sfcb CIMOM by connecting using a native interface.
|
12
|
+
|
13
|
+
== Quick Start
|
14
|
+
|
15
|
+
You can create a gem by running rake gem.
|
16
|
+
|
17
|
+
See the testsuite for more examples. Or run rake docs.
|
18
|
+
|
19
|
+
require 'sfcc'
|
20
|
+
|
21
|
+
client = Sfcc::Cim::Client.connect('http://root@localhost:5988')
|
22
|
+
op = Sfcc::Cim::ObjectPath.new("root/cimv2", "")
|
23
|
+
result = client.query(op, "select * from CIM_OperatingSystem", "wql")
|
24
|
+
result.each do |instance|
|
25
|
+
puts "query result: #{instance}"
|
26
|
+
end
|
27
|
+
|
28
|
+
== FAQ
|
29
|
+
|
30
|
+
* Why not using SWIG?
|
31
|
+
|
32
|
+
sfcc has a complex API based on function tables to simulate an object
|
33
|
+
oriented environment in C which would make wrapping it complicated.
|
34
|
+
|
35
|
+
* Why the API?
|
36
|
+
|
37
|
+
The motivation is to have the plain API available, and use it to access
|
38
|
+
the CIMOM, gaining the footprint of SBLIM plus features like the
|
39
|
+
local interface to talk with the CIMOM without overhead.
|
40
|
+
|
41
|
+
So the API is pretty "raw", and it is supposed to be consumed by
|
42
|
+
ActiveCim ( http://github.com/dmacvicar/activecim ) which is a more
|
43
|
+
ruby-like API to do CIM based management.
|
44
|
+
|
45
|
+
* How to run the testsuite?
|
46
|
+
|
47
|
+
You need a CIMOM with base providers (ie: Linux_OperatingSystem) running
|
48
|
+
on localhost 5988 with no authentication
|
49
|
+
|
50
|
+
== Known Issues
|
51
|
+
|
52
|
+
* If you call Instance#references without a filter class you may get an
|
53
|
+
error. It seems sfcb can't load a provider.
|
54
|
+
|
55
|
+
* ObjectPath qualifier methods crash
|
56
|
+
|
57
|
+
== Authors
|
58
|
+
|
59
|
+
* Duncan Mac-Vicar P. <dmacvicar@suse.de>
|
60
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "test")
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'hoe', '>= 2.1.0'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
task :default => [:compile, :docs, :test]
|
7
|
+
|
8
|
+
Hoe.plugin :yard
|
9
|
+
|
10
|
+
HOE = Hoe.spec 'sfcc' do
|
11
|
+
developer('Duncan Mac-Vicar P.', 'dmacvicar@suse.de')
|
12
|
+
self.summary = "WBEM client for ruby based on the sblim-sfcc library"
|
13
|
+
self.description = "ruby-sfcc allows to access a CIMOM either with the WBEM protocol or by using the SfcbLocal interface provided by the sblim-sfcb CIMOM implementation from the sblim project."
|
14
|
+
self.readme_file = ['README', ENV['HLANG'], 'rdoc'].compact.join('.')
|
15
|
+
self.history_file = ['CHANGELOG', ENV['HLANG'], 'rdoc'].compact.join('.')
|
16
|
+
self.extra_rdoc_files = FileList['*.rdoc']
|
17
|
+
self.clean_globs = [
|
18
|
+
'lib/sfcc/*.{o,so,bundle,a,log,dll}',
|
19
|
+
]
|
20
|
+
|
21
|
+
%w{ rake-compiler }.each do |dep|
|
22
|
+
self.extra_dev_deps << [dep, '>= 0']
|
23
|
+
end
|
24
|
+
self.extra_deps << ['shoulda', '>= 0']
|
25
|
+
self.extra_deps << ['yard', '>= 0']
|
26
|
+
self.spec_extras = { :extensions => ["ext/sfcc/extconf.rb"] }
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
gem 'rake-compiler', '>= 0.4.1'
|
31
|
+
require 'rake/extensiontask'
|
32
|
+
Rake::ExtensionTask.new('sfcc')
|
data/TODO.rdoc
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
== General:
|
3
|
+
|
4
|
+
* add convencience [] operator to Class and Instance to set and
|
5
|
+
retrieve properties
|
6
|
+
|
7
|
+
== Missing relevant APIs
|
8
|
+
|
9
|
+
Other APIs may be missing, but they may not be relevant in a ruby
|
10
|
+
style API.
|
11
|
+
|
12
|
+
=== CIMCClass
|
13
|
+
|
14
|
+
Only available in the v2 API (we use v1):
|
15
|
+
|
16
|
+
* getSuperClassName
|
17
|
+
* getKeyList
|
18
|
+
* toString
|
19
|
+
* relocate
|
20
|
+
* getCharClassName
|
21
|
+
* getCharSuperClassName
|
22
|
+
* isAssociation
|
23
|
+
* isAbstract
|
24
|
+
* isIndication
|
25
|
+
* getPropQualAt
|
26
|
+
|
27
|
+
|
28
|
+
|
@@ -0,0 +1,274 @@
|
|
1
|
+
|
2
|
+
#include "cim_class.h"
|
3
|
+
|
4
|
+
static void
|
5
|
+
dealloc(CMPIConstClass *cimclass)
|
6
|
+
{
|
7
|
+
SFCC_DEC_REFCOUNT(cimclass);
|
8
|
+
}
|
9
|
+
|
10
|
+
/**
|
11
|
+
* call-seq:
|
12
|
+
* name()
|
13
|
+
*
|
14
|
+
* gets the class name
|
15
|
+
*/
|
16
|
+
static VALUE class_name(VALUE self)
|
17
|
+
{
|
18
|
+
CMPIConstClass *cimclass = NULL;
|
19
|
+
CMPIString *classname;
|
20
|
+
Data_Get_Struct(self, CMPIConstClass, cimclass);
|
21
|
+
classname = cimclass->ft->getClassName(cimclass, NULL);
|
22
|
+
return rb_str_new2(classname->ft->getCharPtr(classname, NULL));
|
23
|
+
}
|
24
|
+
|
25
|
+
/**
|
26
|
+
* call-seq:
|
27
|
+
* property(name)
|
28
|
+
*
|
29
|
+
* gets a named property value
|
30
|
+
*/
|
31
|
+
static VALUE property(VALUE self, VALUE name)
|
32
|
+
{
|
33
|
+
CMPIConstClass *ptr = NULL;
|
34
|
+
CMPIStatus status;
|
35
|
+
CMPIData data;
|
36
|
+
memset(&status, 0, sizeof(CMPIStatus));
|
37
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
38
|
+
data = ptr->ft->getProperty(ptr, StringValuePtr(name), &status);
|
39
|
+
if ( !status.rc )
|
40
|
+
return sfcc_cimdata_to_value(data);
|
41
|
+
|
42
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve property '%s'", StringValuePtr(name));
|
43
|
+
return Qnil;
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* call-seq:
|
48
|
+
* cimclass.each_property do |name, value|
|
49
|
+
* ...
|
50
|
+
* end
|
51
|
+
*
|
52
|
+
* enumerates properties yielding the property name and
|
53
|
+
* its value
|
54
|
+
*
|
55
|
+
*/
|
56
|
+
static VALUE each_property(VALUE self)
|
57
|
+
{
|
58
|
+
CMPIConstClass *ptr = NULL;
|
59
|
+
CMPIStatus status;
|
60
|
+
int k=0;
|
61
|
+
int num_props=0;
|
62
|
+
CMPIString *property_name = NULL;
|
63
|
+
CMPIData data;
|
64
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
65
|
+
|
66
|
+
num_props = ptr->ft->getPropertyCount(ptr, &status);
|
67
|
+
if (!status.rc) {
|
68
|
+
for (; k < num_props; ++k) {
|
69
|
+
data = ptr->ft->getPropertyAt(ptr, k, &property_name, &status);
|
70
|
+
if (!status.rc) {
|
71
|
+
rb_yield_values(2, (property_name ? rb_str_intern(rb_str_new2(property_name->ft->getCharPtr(property_name, NULL))) : Qnil), sfcc_cimdata_to_value(data));
|
72
|
+
}
|
73
|
+
else {
|
74
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve property #%d", k);
|
75
|
+
}
|
76
|
+
if (property_name) CMRelease(property_name);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve property count");
|
81
|
+
}
|
82
|
+
return Qnil;
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* call-seq:
|
87
|
+
* property_count()
|
88
|
+
*
|
89
|
+
* Gets the number of properties contained in this class
|
90
|
+
*/
|
91
|
+
static VALUE property_count(VALUE self)
|
92
|
+
{
|
93
|
+
CMPIConstClass *ptr = NULL;
|
94
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
95
|
+
return UINT2NUM(ptr->ft->getPropertyCount(ptr, NULL));
|
96
|
+
}
|
97
|
+
|
98
|
+
/**
|
99
|
+
* call-seq:
|
100
|
+
* qualifier(name)
|
101
|
+
*
|
102
|
+
* gets a named qualifier value
|
103
|
+
*/
|
104
|
+
static VALUE qualifier(VALUE self, VALUE name)
|
105
|
+
{
|
106
|
+
CMPIConstClass *ptr = NULL;
|
107
|
+
CMPIStatus status;
|
108
|
+
CMPIData data;
|
109
|
+
memset(&status, 0, sizeof(CMPIStatus));
|
110
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
111
|
+
data = ptr->ft->getQualifier(ptr, StringValuePtr(name), &status);
|
112
|
+
if ( !status.rc )
|
113
|
+
return sfcc_cimdata_to_value(data);
|
114
|
+
|
115
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve qualifier '%s'", StringValuePtr(name));
|
116
|
+
return Qnil;
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* call-seq:
|
121
|
+
* cimclass.each_qualifier do |name, value|
|
122
|
+
* ...
|
123
|
+
* end
|
124
|
+
*
|
125
|
+
* enumerates properties yielding the qualifier name and
|
126
|
+
* its value
|
127
|
+
*
|
128
|
+
*/
|
129
|
+
static VALUE each_qualifier(VALUE self)
|
130
|
+
{
|
131
|
+
CMPIConstClass *ptr = NULL;
|
132
|
+
CMPIStatus status;
|
133
|
+
int k=0;
|
134
|
+
int num_props=0;
|
135
|
+
CMPIString *qualifier_name = NULL;
|
136
|
+
CMPIData data;
|
137
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
138
|
+
|
139
|
+
num_props = ptr->ft->getQualifierCount(ptr, &status);
|
140
|
+
if (!status.rc) {
|
141
|
+
for (; k < num_props; ++k) {
|
142
|
+
data = ptr->ft->getQualifierAt(ptr, k, &qualifier_name, &status);
|
143
|
+
if (!status.rc) {
|
144
|
+
rb_yield_values(2, (qualifier_name ? rb_str_intern(rb_str_new2(qualifier_name->ft->getCharPtr(qualifier_name, NULL))) : Qnil), sfcc_cimdata_to_value(data));
|
145
|
+
}
|
146
|
+
else {
|
147
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve qualifier #%d", k);
|
148
|
+
}
|
149
|
+
if (qualifier_name) CMRelease(qualifier_name);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
else {
|
153
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve qualifier count");
|
154
|
+
}
|
155
|
+
return Qnil;
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* call-seq:
|
160
|
+
* qualifier_count()
|
161
|
+
*
|
162
|
+
* Gets the number of qualifiers in this class
|
163
|
+
*/
|
164
|
+
static VALUE qualifier_count(VALUE self)
|
165
|
+
{
|
166
|
+
CMPIConstClass *ptr = NULL;
|
167
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
168
|
+
return UINT2NUM(ptr->ft->getQualifierCount(ptr, NULL));
|
169
|
+
}
|
170
|
+
|
171
|
+
/**
|
172
|
+
* call-seq:
|
173
|
+
* property_qualifier(property_name, qualifier_name)
|
174
|
+
*
|
175
|
+
* gets a named property qualifier value
|
176
|
+
*/
|
177
|
+
static VALUE property_qualifier(VALUE self, VALUE property_name, VALUE qualifier_name)
|
178
|
+
{
|
179
|
+
CMPIConstClass *ptr = NULL;
|
180
|
+
CMPIStatus status;
|
181
|
+
CMPIData data;
|
182
|
+
memset(&status, 0, sizeof(CMPIStatus));
|
183
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
184
|
+
data = ptr->ft->getPropertyQualifier(ptr, StringValuePtr(property_name),
|
185
|
+
StringValuePtr(qualifier_name), &status);
|
186
|
+
if ( !status.rc )
|
187
|
+
return sfcc_cimdata_to_value(data);
|
188
|
+
|
189
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve property_qualifier '%s'", StringValuePtr(qualifier_name));
|
190
|
+
return Qnil;
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* call-seq:
|
195
|
+
* cimclass.each_property_qualifier(property_name) do |name, value|
|
196
|
+
* ...
|
197
|
+
* end
|
198
|
+
*
|
199
|
+
* enumerates properties yielding the property qualifier name and
|
200
|
+
* its value
|
201
|
+
*
|
202
|
+
*/
|
203
|
+
static VALUE each_property_qualifier(VALUE self, VALUE property_name)
|
204
|
+
{
|
205
|
+
CMPIConstClass *ptr = NULL;
|
206
|
+
CMPIStatus status;
|
207
|
+
int k=0;
|
208
|
+
int num_props=0;
|
209
|
+
CMPIString *property_qualifier_name = NULL;
|
210
|
+
CMPIData data;
|
211
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
212
|
+
|
213
|
+
num_props = ptr->ft->getPropertyQualifierCount(ptr, StringValuePtr(property_name), &status);
|
214
|
+
if (!status.rc) {
|
215
|
+
for (; k < num_props; ++k) {
|
216
|
+
data = ptr->ft->getPropertyQualifierAt(ptr, StringValuePtr(property_name), k, &property_qualifier_name, &status);
|
217
|
+
if (!status.rc) {
|
218
|
+
rb_yield_values(2, (property_qualifier_name ? rb_str_intern(rb_str_new2(property_qualifier_name->ft->getCharPtr(property_qualifier_name, NULL))) : Qnil), sfcc_cimdata_to_value(data));
|
219
|
+
}
|
220
|
+
else {
|
221
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve property qualifier #%d", k);
|
222
|
+
}
|
223
|
+
if (property_qualifier_name) CMRelease(property_qualifier_name);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
else {
|
227
|
+
sfcc_rb_raise_if_error(status, "Can't retrieve property qualifier count");
|
228
|
+
}
|
229
|
+
return Qnil;
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* call-seq:
|
234
|
+
* property_qualifier_count(property_name)
|
235
|
+
*
|
236
|
+
* Gets the number of qualifiers contained in this property
|
237
|
+
*/
|
238
|
+
static VALUE property_qualifier_count(VALUE self, VALUE property_name)
|
239
|
+
{
|
240
|
+
CMPIConstClass *ptr = NULL;
|
241
|
+
Data_Get_Struct(self, CMPIConstClass, ptr);
|
242
|
+
return UINT2NUM(ptr->ft->getPropertyQualifierCount(ptr, StringValuePtr(property_name), NULL));
|
243
|
+
}
|
244
|
+
|
245
|
+
VALUE
|
246
|
+
Sfcc_wrap_cim_class(CMPIConstClass *cimclass)
|
247
|
+
{
|
248
|
+
SFCC_INC_REFCOUNT(cimclass);
|
249
|
+
return Data_Wrap_Struct(cSfccCimClass, NULL, dealloc, cimclass);
|
250
|
+
}
|
251
|
+
|
252
|
+
VALUE cSfccCimClass;
|
253
|
+
void init_cim_class()
|
254
|
+
{
|
255
|
+
VALUE sfcc = rb_define_module("Sfcc");
|
256
|
+
VALUE cimc = rb_define_module_under(sfcc, "Cim");
|
257
|
+
|
258
|
+
/**
|
259
|
+
* class from the CIM schema
|
260
|
+
*/
|
261
|
+
VALUE klass = rb_define_class_under(cimc, "Class", rb_cObject);
|
262
|
+
cSfccCimClass = klass;
|
263
|
+
|
264
|
+
rb_define_method(klass, "class_name", class_name, 0);
|
265
|
+
rb_define_method(klass, "property", property, 1);
|
266
|
+
rb_define_method(klass, "each_property", each_property, 0);
|
267
|
+
rb_define_method(klass, "property_count", property_count, 0);
|
268
|
+
rb_define_method(klass, "qualifier", qualifier, 1);
|
269
|
+
rb_define_method(klass, "each_qualifier", each_qualifier, 0);
|
270
|
+
rb_define_method(klass, "qualifier_count", qualifier_count, 0);
|
271
|
+
rb_define_method(klass, "property_qualifier", property_qualifier, 2);
|
272
|
+
rb_define_method(klass, "each_property_qualifier", each_property_qualifier, 1);
|
273
|
+
rb_define_method(klass, "property_qualifier_count", property_qualifier_count, 1);
|
274
|
+
}
|