tp-blather 0.8.2

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.
Files changed (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,52 @@
1
+ module Blather
2
+ class Stanza
3
+ class PubSubOwner
4
+
5
+ # # PubSubOwner Purge Stanza
6
+ #
7
+ # [XEP-0060 Section 8.5 - Purge All Node Items](http://xmpp.org/extensions/xep-0060.html#owner-purge)
8
+ #
9
+ # @handler :pubsub_purge
10
+ class Purge < PubSubOwner
11
+ register :pubsub_purge, :purge, self.registered_ns
12
+
13
+ # Create a new purge stanza
14
+ #
15
+ # @param [Blather::Stanza::Iq::VALID_TYPES] type the IQ stanza type
16
+ # @param [String] host the host to send the request to
17
+ # @param [String] node the name of the node to purge
18
+ def self.new(type = :set, host = nil, node = nil)
19
+ new_node = super(type, host)
20
+ new_node.node = node
21
+ new_node
22
+ end
23
+
24
+ # Get the name of the node to delete
25
+ #
26
+ # @return [String]
27
+ def node
28
+ purge_node[:node]
29
+ end
30
+
31
+ # Set the name of the node to delete
32
+ #
33
+ # @param [String] node
34
+ def node=(node)
35
+ purge_node[:node] = node
36
+ end
37
+
38
+ # Get or create the actual purge node on the stanza
39
+ #
40
+ # @return [Blather::XMPPNode]
41
+ def purge_node
42
+ unless purge_node = pubsub.find_first('ns:purge', :ns => self.class.registered_ns)
43
+ self.pubsub << (purge_node = XMPPNode.new('purge', self.document))
44
+ purge_node.namespace = self.pubsub.namespace
45
+ end
46
+ purge_node
47
+ end
48
+ end # Retract
49
+
50
+ end # PubSub
51
+ end # Stanza
52
+ end # Blather
@@ -0,0 +1,416 @@
1
+ module Blather
2
+ class Stanza
3
+ # # X Stanza
4
+ #
5
+ # [XEP-0004 Data Forms](http://xmpp.org/extensions/xep-0004.html)
6
+ #
7
+ # Data Form node that allows for semi-structured data exchange
8
+ #
9
+ # @handler :x
10
+ class X < XMPPNode
11
+ register :x, 'jabber:x:data'
12
+
13
+ # @private
14
+ VALID_TYPES = [:cancel, :form, :result, :submit].freeze
15
+
16
+ # Create a new X node
17
+ # @param [:cancel, :form, :result, :submit, nil] type the x:form type
18
+ # @param [Array<Array, X::Field>, nil] fields a list of fields.
19
+ # These are passed directly to X::Field.new
20
+ # @return [X] a new X stanza
21
+ def self.new(type = nil, fields = [])
22
+ new_node = super :x
23
+
24
+ case type
25
+ when Nokogiri::XML::Node
26
+ new_node.inherit type
27
+ when Hash
28
+ new_node.type = type[:type]
29
+ new_node.fields = type[:fields]
30
+ else
31
+ new_node.type = type
32
+ new_node.fields = fields
33
+ end
34
+ new_node
35
+ end
36
+
37
+ # Find the X node on the parent or create a new one
38
+ #
39
+ # @param [Blather::Stanza] parent the parent node to search under
40
+ # @return [Blather::Stanza::X]
41
+ def self.find_or_create(parent)
42
+ if found_x = parent.find_first('//ns:x', :ns => self.registered_ns)
43
+ x = self.new found_x
44
+ found_x.remove
45
+ else
46
+ x = self.new
47
+ end
48
+ parent << x
49
+ x
50
+ end
51
+
52
+ # The Form's type
53
+ # @return [Symbol]
54
+ def type
55
+ read_attr :type, :to_sym
56
+ end
57
+
58
+ # Set the Form's type
59
+ # @param [:cancel, :form, :result, :submit] type the new type for the form
60
+ def type=(type)
61
+ if type && !VALID_TYPES.include?(type.to_sym)
62
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
63
+ end
64
+ write_attr :type, type
65
+ end
66
+
67
+ # List of field objects
68
+ # @return [Blather::Stanza::X::Field]
69
+ def fields
70
+ self.find('ns:field', :ns => self.class.registered_ns).map do |field|
71
+ Field.new(field)
72
+ end
73
+ end
74
+
75
+ # Find a field by var
76
+ # @param var the var for the field you wish to find
77
+ def field(var)
78
+ fields.detect { |f| f.var == var }
79
+ end
80
+
81
+ # Add an array of fields to form
82
+ # @param fields the array of fields, passed directly to Field.new
83
+ def fields=(fields)
84
+ remove_children :field
85
+ [fields].flatten.each do |field|
86
+ self << (f = Field.new(field))
87
+ f.namespace = self.namespace
88
+ end
89
+ end
90
+
91
+ # Check if the x is of type :cancel
92
+ #
93
+ # @return [true, false]
94
+ def cancel?
95
+ self.type == :cancel
96
+ end
97
+
98
+ # Check if the x is of type :form
99
+ #
100
+ # @return [true, false]
101
+ def form?
102
+ self.type == :form
103
+ end
104
+
105
+ # Check if the x is of type :result
106
+ #
107
+ # @return [true, false]
108
+ def result?
109
+ self.type == :result
110
+ end
111
+
112
+ # Check if the x is of type :submit
113
+ #
114
+ # @return [true, false]
115
+ def submit?
116
+ self.type == :submit
117
+ end
118
+
119
+ # Retrieve the form's instructions
120
+ #
121
+ # @return [String]
122
+ def instructions
123
+ content_from 'ns:instructions', :ns => self.registered_ns
124
+ end
125
+
126
+ # Set the form's instructions
127
+ #
128
+ # @param [String] instructions the form's instructions
129
+ def instructions=(instructions)
130
+ self.remove_children :instructions
131
+ if instructions
132
+ self << (i = XMPPNode.new(:instructions, self.document))
133
+ i.namespace = self.namespace
134
+ i << instructions
135
+ end
136
+ end
137
+
138
+ # Retrieve the form's title
139
+ #
140
+ # @return [String]
141
+ def title
142
+ content_from 'ns:title', :ns => self.registered_ns
143
+ end
144
+
145
+ # Set the form's title
146
+ #
147
+ # @param [String] title the form's title
148
+ def title=(title)
149
+ self.remove_children :title
150
+ if title
151
+ self << (t = XMPPNode.new(:title))
152
+ t.namespace = self.namespace
153
+ t << title
154
+ end
155
+ end
156
+
157
+ # Field stanza fragment
158
+ class Field < XMPPNode
159
+ register :field, 'jabber:x:data'
160
+ # @private
161
+ VALID_TYPES = [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"].freeze
162
+
163
+ # Create a new X Field
164
+ # @overload new(node)
165
+ # Imports the XML::Node to create a Field object
166
+ # @param [XML::Node] node the node object to import
167
+ # @overload new(opts = {})
168
+ # Creates a new Field using a hash of options
169
+ # @param [Hash] opts a hash of options
170
+ # @option opts [String] :var the variable for the field
171
+ # @option opts [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"] :type the type of the field
172
+ # @option opts [String] :label the label for the field
173
+ # @option [String, nil] :value the value for the field
174
+ # @option [String, nil] :description the description for the field
175
+ # @option [true, false, nil] :required the required flag for the field
176
+ # @param [Array<Array, X::Field::Option>, nil] :options a list of field options.
177
+ # These are passed directly to X::Field::Option.new
178
+ # @overload new(type, var = nil, label = nil)
179
+ # Create a new Field by name
180
+ # @param [String, nil] var the variable for the field
181
+ # @param [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"] type the type of the field
182
+ # @param [String, nil] label the label for the field
183
+ # @param [String, nil] value the value for the field
184
+ # @param [String, nil] description the description for the field
185
+ # @param [true, false, nil] required the required flag for the field
186
+ # @param [Array<Array, X::Field::Option>, nil] options a list of field options.
187
+ # These are passed directly to X::Field::Option.new
188
+ def self.new(var, type = nil, label = nil, value = nil, description = nil, required = false, options = [])
189
+ new_node = super :field
190
+
191
+ case var
192
+ when Nokogiri::XML::Node
193
+ new_node.inherit var
194
+ when Hash
195
+ new_node.var = var[:var]
196
+ new_node.type = var[:type]
197
+ new_node.label = var[:label]
198
+ new_node.value = var[:value]
199
+ new_node.desc = var[:description]
200
+ new_node.required = var[:required]
201
+ new_node.options = var[:options]
202
+ else
203
+ new_node.var = var
204
+ new_node.type = type
205
+ new_node.label = label
206
+ new_node.value = value
207
+ new_node.desc = description
208
+ new_node.required = required
209
+ new_node.options = options
210
+ end
211
+ new_node
212
+ end
213
+
214
+ # The Field's type
215
+ # @return [String]
216
+ def type
217
+ read_attr :type
218
+ end
219
+
220
+ # Set the Field's type
221
+ # @param [#to_sym] type the new type for the field
222
+ def type=(type)
223
+ if type && !VALID_TYPES.include?(type.to_sym)
224
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
225
+ end
226
+ write_attr :type, type
227
+ end
228
+
229
+ # The Field's var
230
+ # @return [String]
231
+ def var
232
+ read_attr :var
233
+ end
234
+
235
+ # Set the Field's var
236
+ # @param [String] var the new var for the field
237
+ def var=(var)
238
+ write_attr :var, var
239
+ end
240
+
241
+ # The Field's label
242
+ # @return [String]
243
+ def label
244
+ read_attr :label
245
+ end
246
+
247
+ # Set the Field's label
248
+ # @param [String] label the new label for the field
249
+ def label=(label)
250
+ write_attr :label, label
251
+ end
252
+
253
+ # Get the field's value
254
+ #
255
+ # @param [String]
256
+ def value
257
+ if self.namespace
258
+ content_from 'ns:value', :ns => self.namespace.href
259
+ else
260
+ content_from :value
261
+ end
262
+ end
263
+
264
+ # Set the field's value
265
+ #
266
+ # @param [String] value the field's value
267
+ def value=(value)
268
+ self.remove_children :value
269
+ if value
270
+ self << (v = XMPPNode.new(:value))
271
+ v.namespace = self.namespace
272
+ v << value
273
+ end
274
+ end
275
+
276
+ # Get the field's description
277
+ #
278
+ # @param [String]
279
+ def desc
280
+ if self.namespace
281
+ content_from 'ns:desc', :ns => self.namespace.href
282
+ else
283
+ content_from :desc
284
+ end
285
+ end
286
+
287
+ # Set the field's description
288
+ #
289
+ # @param [String] description the field's description
290
+ def desc=(description)
291
+ self.remove_children :desc
292
+ if description
293
+ self << (d = XMPPNode.new(:desc))
294
+ d.namespace = self.namespace
295
+ d << description
296
+ end
297
+ end
298
+
299
+ # Get the field's required flag
300
+ #
301
+ # @param [true, false]
302
+ def required?
303
+ !!if self.namespace
304
+ self.find_first 'ns:required', :ns => self.namespace.href
305
+ else
306
+ self.find_first 'required'
307
+ end
308
+ end
309
+
310
+ # Set the field's required flag
311
+ #
312
+ # @param [true, false] required the field's required flag
313
+ def required=(required)
314
+ return self.remove_children(:required) unless required
315
+
316
+ self << (r = XMPPNode.new(:required))
317
+ r.namespace = self.namespace
318
+ end
319
+
320
+ # Extract list of option objects
321
+ #
322
+ # @return [Blather::Stanza::X::Field::Option]
323
+ def options
324
+ if self.namespace
325
+ self.find('ns:option', :ns => self.namespace.href)
326
+ else
327
+ self.find('option')
328
+ end.map { |f| Option.new(f) }
329
+ end
330
+
331
+ # Add an array of options to field
332
+ # @param options the array of options, passed directly to Option.new
333
+ def options=(options)
334
+ remove_children :option
335
+ if options
336
+ Array(options).each { |o| self << Option.new(o) }
337
+ end
338
+ end
339
+
340
+ # Compare two Field objects by type, var and label
341
+ # @param [X::Field] o the Field object to compare against
342
+ # @return [true, false]
343
+ def eql?(o, *fields)
344
+ super o, *(fields + [:type, :var, :label, :desc, :required?, :value])
345
+ end
346
+
347
+ # Option stanza fragment
348
+ class Option < XMPPNode
349
+ register :option, 'jabber:x:data'
350
+ # Create a new X Field Option
351
+ # @overload new(node)
352
+ # Imports the XML::Node to create a Field option object
353
+ # @param [XML::Node] node the node object to import
354
+ # @overload new(opts = {})
355
+ # Creates a new Field option using a hash of options
356
+ # @param [Hash] opts a hash of options
357
+ # @option opts [String] :value the value of the field option
358
+ # @option opts [String] :label the human readable label for the field option
359
+ # @overload new(value, label = nil)
360
+ # Create a new Field option by name
361
+ # @param [String] value the value of the field option
362
+ # @param [String, nil] label the human readable label for the field option
363
+ def self.new(value, label = nil)
364
+ new_node = super :option
365
+
366
+ case value
367
+ when Nokogiri::XML::Node
368
+ new_node.inherit value
369
+ when Hash
370
+ new_node.value = value[:value]
371
+ new_node.label = value[:label]
372
+ else
373
+ new_node.value = value
374
+ new_node.label = label
375
+ end
376
+ new_node
377
+ end
378
+
379
+ # The Field Option's value
380
+ # @return [String]
381
+ def value
382
+ if self.namespace
383
+ content_from 'ns:value', :ns => self.namespace.href
384
+ else
385
+ content_from :value
386
+ end
387
+ end
388
+
389
+ # Set the Field Option's value
390
+ # @param [String] value the new value for the field option
391
+ def value=(value)
392
+ self.remove_children :value
393
+ if value
394
+ self << (v = XMPPNode.new(:value))
395
+ v.namespace = self.namespace
396
+ v << value
397
+ end
398
+ end
399
+
400
+ # The Field Option's label
401
+ # @return [String]
402
+ def label
403
+ read_attr :label
404
+ end
405
+
406
+ # Set the Field Option's label
407
+ # @param [String] label the new label for the field option
408
+ def label=(label)
409
+ write_attr :label, label
410
+ end
411
+ end # Option
412
+ end # Field
413
+ end # X
414
+
415
+ end #Stanza
416
+ end