udat 1.3.0 → 1.4.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/example.udat +23 -5
- data/lib/udat.rb +403 -198
- metadata +2 -2
data/example.udat
CHANGED
@@ -63,6 +63,8 @@ Sample configuration file:
|
|
63
63
|
|
64
64
|
General Information:
|
65
65
|
|
66
|
+
UDAT documents are composed of UDAT nodes.
|
67
|
+
|
66
68
|
Every UDAT node can either be a scalar or a collection. Scalars have
|
67
69
|
content represented by a single string, while collections contain other
|
68
70
|
UDAT nodes.
|
@@ -70,6 +72,9 @@ General Information:
|
|
70
72
|
Every UDAT node may be tagged, that means a meta-information in form of
|
71
73
|
a string is added. This information can be seen as type information.
|
72
74
|
|
75
|
+
UDAT nodes are normally enclosed in square brackets, but if being used as
|
76
|
+
keys in key/value pairs, they are enclosed in angle brackets.
|
77
|
+
|
73
78
|
Comments may appear anywhere in the document except in tags or scalars.
|
74
79
|
|
75
80
|
The following 7 characters are to be escaped with a backslash, unless
|
@@ -80,11 +85,14 @@ General Information:
|
|
80
85
|
|
81
86
|
Scalar values:
|
82
87
|
|
83
|
-
Any string
|
84
|
-
considered to be a
|
88
|
+
Any string enclosed in square or angle brackets, which doesn't contain
|
89
|
+
square or angle brackets or the tilde symbol inside is considered to be a
|
90
|
+
scalar:
|
85
91
|
|
86
92
|
[Hello World!]
|
87
93
|
|
94
|
+
However, angle brackets may only be used for keys in key/value lists.
|
95
|
+
|
88
96
|
You may add an additional tag, by preceding the string with the tag and
|
89
97
|
the pipe symbol:
|
90
98
|
|
@@ -109,10 +117,10 @@ Scalar values:
|
|
109
117
|
|
110
118
|
Collections:
|
111
119
|
|
112
|
-
|
113
|
-
|
120
|
+
If the contents contain either square or angle brackets or a tilde symbol
|
121
|
+
the node is considered to be a collection. Each entry in a collection
|
114
122
|
consists of a value and optionally a key associated with that value.
|
115
|
-
The key is written
|
123
|
+
The key is written with angle brackets and being followed by the value in
|
116
124
|
square brackets. If the value has no key, the angle brackets are not
|
117
125
|
written.
|
118
126
|
|
@@ -178,6 +186,16 @@ Collections:
|
|
178
186
|
< <red>[off] <yellow>[on] <green>[off] > [prepare to stop] ]
|
179
187
|
|
180
188
|
|
189
|
+
Tags:
|
190
|
+
|
191
|
+
There are no standard tags defined, each application or protocol can use
|
192
|
+
their own tags. It is recommended to not use an at-symbol (@) in the tag,
|
193
|
+
unless wanting to create a globally unique tag in the form of
|
194
|
+
"localpart@domain", e.g.:
|
195
|
+
|
196
|
+
[example@udat.org|Scalar having a unique tag.]
|
197
|
+
|
198
|
+
|
181
199
|
Structured meta information:
|
182
200
|
|
183
201
|
Don't abuse tags to store complicated meta information like this:
|
data/lib/udat.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'monitor'
|
4
|
+
require 'resolv'
|
5
|
+
require 'socket'
|
6
|
+
require 'timeout'
|
4
7
|
|
5
8
|
# Copyright (c) 2007 FlexiGuided GmbH, Berlin
|
6
9
|
#
|
@@ -16,90 +19,41 @@ require 'monitor'
|
|
16
19
|
# The format is comparable to formats like XML or YAML, but due to its
|
17
20
|
# simplicity is much more easy to parse.
|
18
21
|
#
|
19
|
-
# UDAT documents can be generated by converting
|
22
|
+
# UDAT documents can be generated by converting an Object to a Udat::Node,
|
20
23
|
# by calling Object#to_udat, and then encoding them by calling
|
21
|
-
# Udat::Node#
|
24
|
+
# Udat::Node#encode.
|
22
25
|
#
|
23
|
-
# UDAT documents can be parsed by calling String#
|
26
|
+
# UDAT documents can be parsed by calling String#parse_udat.
|
27
|
+
#
|
28
|
+
# There are methods providing useful IO functions for UDAT documents:
|
29
|
+
# IO#read_udat, IO#write_udat, Udat::Node#rpc and Udat.run_rpc_server.
|
24
30
|
module Udat
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
+
module_function
|
33
|
+
|
34
|
+
# Error raised, when a fetched Udat::Node doesn't have expected
|
35
|
+
# properties.
|
36
|
+
class UdatPropertyMismatch < StandardError; end
|
37
|
+
# Error raised, when a fetched Udat::Node doesn't have the expected tag
|
38
|
+
# (i.e. type).
|
39
|
+
class UdatTagMismatch < UdatPropertyMismatch; end
|
40
|
+
# Error raised, when a fetched Udat::Node is a collection instead of a
|
41
|
+
# scalar or vice versa.
|
42
|
+
class UdatTypeMismatch < UdatPropertyMismatch; end
|
43
|
+
# Error raised, when an Udat::Node could not be found by
|
44
|
+
# Udat::Collection#fetch.
|
45
|
+
class UdatIndexError < IndexError; end
|
32
46
|
# UDAT parsing error
|
33
47
|
class ParseError < StandardError; end
|
34
48
|
|
35
|
-
# Special argument
|
36
|
-
|
37
|
-
KeepTag = Object.new
|
38
|
-
|
39
|
-
# A string containing an UDAT example document.
|
40
|
-
ExampleDocument = <<-'END_OF_EXAMPLE'
|
41
|
-
[sample config v1.0|
|
42
|
-
|
43
|
-
<network> [
|
44
|
-
<max connections> [256]
|
45
|
-
<reverse lookups> [yes]
|
46
|
-
]
|
47
|
-
|
48
|
-
<locale> [
|
49
|
-
<language> [de] german language
|
50
|
-
<timezone> [CET] and CET timezone
|
51
|
-
Comments can appear almost anywhere.
|
52
|
-
]
|
53
|
-
|
54
|
-
<access control> [
|
55
|
-
<allowed> [
|
56
|
-
[user|martin]
|
57
|
-
[user|max]
|
58
|
-
[group|admins]
|
59
|
-
[ip4network|
|
60
|
-
<address> [192.168.0.0]
|
61
|
-
<netmask> [255.255.255.0]
|
62
|
-
]
|
63
|
-
]
|
64
|
-
<blocked> [~] The tilde symbol denotes an empty collection.
|
65
|
-
]
|
66
|
-
|
67
|
-
<misc> [
|
68
|
-
<symbol for homedir> [\~] Not an empty collection but the scalar
|
69
|
-
value "~".
|
70
|
-
]
|
71
|
-
|
72
|
-
<address mappings> [
|
73
|
-
|
74
|
-
<email|
|
75
|
-
<user> [jan.behrens]
|
76
|
-
<domain> [flexiguided.de]
|
77
|
-
>
|
78
|
-
[mbox|jan]
|
79
|
-
|
80
|
-
<email|
|
81
|
-
<user> [glob|*]
|
82
|
-
<domain> [flexiguided.de]
|
83
|
-
>
|
84
|
-
[mbox|catchall]
|
85
|
-
|
86
|
-
]
|
87
|
-
|
88
|
-
<logging> [
|
89
|
-
<verbosity> [high]
|
90
|
-
\## <verbosity> [debug] ## Uncomment this for debug output.
|
91
|
-
<destination> [file|/var/log/sample.log]
|
92
|
-
]
|
93
|
-
|
94
|
-
]
|
95
|
-
END_OF_EXAMPLE
|
49
|
+
# Special argument used as an argument default, do not use it directly.
|
50
|
+
AnyTag = Object.new
|
96
51
|
|
97
52
|
# Returns an escaped version of a string, where backslashes are
|
98
53
|
# preceding certain reserved characters.
|
99
54
|
def escape_string(string)
|
100
55
|
string.to_s.gsub /([<>\[\]|~\\])/, "\\\\\\1"
|
101
56
|
end
|
102
|
-
module_function :escape_string
|
103
57
|
|
104
58
|
# The abstract class to represent any UDAT data is a Udat::Node.
|
105
59
|
# Udat::Node's basically consist of a tag and the content. The tag can be
|
@@ -111,12 +65,14 @@ module Udat
|
|
111
65
|
# Udat::Node's can most easily be constructed from other ruby objects by
|
112
66
|
# using the Object#to_udat method.
|
113
67
|
#
|
114
|
-
# By calling the method Udat::Node#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
68
|
+
# By calling the method Udat::Node#encode, the Node and its children are
|
69
|
+
# encoded in a both easily human readable and easily machine readable
|
70
|
+
# format. The output can be later parsed by String#parse_udat or
|
71
|
+
# IO#read_udat.
|
118
72
|
class Node
|
119
73
|
|
74
|
+
public
|
75
|
+
|
120
76
|
include MonitorMixin
|
121
77
|
|
122
78
|
private_class_method :new
|
@@ -139,62 +95,109 @@ module Udat
|
|
139
95
|
end
|
140
96
|
|
141
97
|
# Returns true, if the Udat::Node represents a scalar value.
|
142
|
-
# Shortcut for Udat::Node#kind_of? Udat::Scalar.
|
143
98
|
def scalar?
|
144
|
-
|
99
|
+
false
|
145
100
|
end
|
146
101
|
# Returns true, if the Udat::Node represents a collection.
|
147
|
-
# Shortcut for Udat::Node#kind_of? Udat::Collection.
|
148
102
|
def collection?
|
149
|
-
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns self, if the tag is matching the argument,
|
107
|
+
# otherwise an Udat::UdatTagMismatch exception is raised.
|
108
|
+
def require_tag(tag)
|
109
|
+
synchronize do
|
110
|
+
if self.tag == tag.to_s
|
111
|
+
return self
|
112
|
+
else
|
113
|
+
raise UdatTagMismatch, "UDAT tag mismatch."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# Returns self, if the Udat::Node represents a scalar,
|
118
|
+
# otherwise an Udat::UdatTypeMismatch exception is raised.
|
119
|
+
# If an optional tag is given as an argument,
|
120
|
+
# it is checked by calling Udat::Node#require_tag.
|
121
|
+
def require_scalar(tag = AnyTag)
|
122
|
+
if scalar?
|
123
|
+
if tag == AnyTag
|
124
|
+
return self
|
125
|
+
else
|
126
|
+
return require_tag(tag)
|
127
|
+
end
|
128
|
+
elsif collection?
|
129
|
+
raise UdatTypeMismatch,
|
130
|
+
"UDAT collection found where a scalar was expected."
|
131
|
+
else
|
132
|
+
raise "Internal error in UDAT library."
|
133
|
+
end
|
134
|
+
end
|
135
|
+
# Returns self, if the Udat::Node represents a collection,
|
136
|
+
# otherwise an Udat::UdatTypeMismatch exception is raised.
|
137
|
+
# If an optional tag is given as an argument,
|
138
|
+
# it is checked by calling Udat::Node#require_tag.
|
139
|
+
def require_collection(tag = AnyTag)
|
140
|
+
if collection?
|
141
|
+
if tag == AnyTag
|
142
|
+
return self
|
143
|
+
else
|
144
|
+
return require_tag(tag)
|
145
|
+
end
|
146
|
+
elsif scalar?
|
147
|
+
raise UdatTypeMismatch,
|
148
|
+
"UDAT scalar found where a collection was expected."
|
149
|
+
else
|
150
|
+
raise "Internal error in UDAT library."
|
151
|
+
end
|
150
152
|
end
|
151
153
|
|
152
|
-
# Returns the
|
153
|
-
#
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
154
|
+
# Returns the encoded form of the content as a string.
|
155
|
+
# This method is used by Udat::Node#encode_without_brackets.
|
156
|
+
def encode_content
|
157
|
+
end
|
158
|
+
protected :encode_content
|
159
|
+
# Returns the data (including it's tag) encoded in the UDAT format.
|
160
|
+
# The result is not enclosed by square brackets, so not intended to be
|
161
|
+
# used directly externally.
|
162
|
+
def encode_without_brackets
|
158
163
|
synchronize do
|
159
164
|
if tag
|
160
|
-
return "#{Udat::escape_string tag}|#{
|
165
|
+
return "#{Udat::escape_string tag}|#{encode_content}"
|
161
166
|
else
|
162
|
-
return
|
167
|
+
return encode_content
|
163
168
|
end
|
164
169
|
end
|
165
170
|
end
|
171
|
+
protected :encode_without_brackets
|
166
172
|
# Returns the data (including it's tag) encoded in the UDAT format and
|
167
|
-
# enclosed in square brackets.
|
168
|
-
# when storing the data in files or over streams.
|
173
|
+
# enclosed in square brackets.
|
169
174
|
#
|
170
175
|
# Note: The UDAT format doesn't contain information, whether contained
|
171
176
|
# collections are ordered or unordered. This information is lost during
|
172
177
|
# the encoding process, and has to be restored in an application
|
173
178
|
# specific way, if neccessary.
|
174
|
-
def encode_document
|
175
|
-
"[#{encode_part}]"
|
176
|
-
end
|
177
|
-
# Deprecated. This method is an alias for Udat::Node#encode_part, but
|
178
|
-
# it is planned to be changed to Udat::Node#encode_document.
|
179
179
|
def encode
|
180
|
-
|
180
|
+
"[#{encode_without_brackets}]"
|
181
181
|
end
|
182
|
-
#
|
183
|
-
|
184
|
-
|
182
|
+
# Alias for Udat::Node#encode, will be removed in future versions.
|
183
|
+
def encode_document
|
184
|
+
encode
|
185
|
+
end
|
186
|
+
# Here the method does the same as Udat::Node#encode,
|
187
|
+
# but this method is overwritten in Udat::Scalar!
|
185
188
|
def to_s
|
186
|
-
|
189
|
+
encode
|
187
190
|
end
|
188
|
-
# Does the same as Udat::Node#
|
189
|
-
# with "udat".
|
191
|
+
# Does the same as Udat::Node#encode,
|
192
|
+
# but preceds the result with "udat".
|
190
193
|
def inspect
|
191
|
-
"udat#{self.
|
194
|
+
"udat#{self.encode}"
|
192
195
|
end
|
193
196
|
|
194
197
|
# Returns self, or a duplicate of self with a different tag set, if an
|
195
198
|
# argument is passed.
|
196
|
-
def to_udat(tag =
|
197
|
-
if tag ==
|
199
|
+
def to_udat(tag = AnyTag)
|
200
|
+
if tag == AnyTag
|
198
201
|
return self
|
199
202
|
else
|
200
203
|
obj = nil
|
@@ -379,41 +382,37 @@ module Udat
|
|
379
382
|
end
|
380
383
|
private_class_method :parse_intern
|
381
384
|
|
382
|
-
#
|
383
|
-
#
|
384
|
-
#
|
385
|
-
#
|
386
|
-
|
385
|
+
# Parses a given UDAT document string and returns a structure of
|
386
|
+
# Udat::Node's.
|
387
|
+
#
|
388
|
+
# Note: When parsing UDAT data, no information is gained, whether
|
389
|
+
# collections are ordered or unordered. After parsing, all collections
|
390
|
+
# will be marked as unordered, unless changed later by the application.
|
391
|
+
def self.parse(input)
|
387
392
|
input = input.to_s
|
388
393
|
pos = 0
|
389
394
|
begin
|
390
|
-
|
395
|
+
collection = (
|
391
396
|
parse_intern(:normal, nil) do
|
392
397
|
char = input[pos, 1]
|
393
398
|
pos += 1
|
394
399
|
next char.empty? ? nil : char
|
395
400
|
end
|
396
401
|
)
|
402
|
+
unless collection.collection? and not collection.empty?
|
403
|
+
raise ParseError, "No valid UDAT object found."
|
404
|
+
end
|
405
|
+
return collection.first
|
397
406
|
rescue EOFError
|
398
407
|
raise ParseError, $!.message
|
399
408
|
end
|
400
409
|
end
|
401
|
-
#
|
402
|
-
# Udat::Node's. It does the same as Udat::Node.parse_part(input).first.
|
403
|
-
#
|
404
|
-
# Note: When parsing UDAT data, no information is gained, whether
|
405
|
-
# collections are ordered or unordered. After parsing, all collections
|
406
|
-
# will be marked as unordered, unless changed later by the application.
|
410
|
+
# Alias for Udat::Node.parse, will be removed in future versions.
|
407
411
|
def self.parse_document(input)
|
408
|
-
parse(input)
|
409
|
-
end
|
410
|
-
# Deprecated. This method is an alias for Udat::Node.parse_part, but
|
411
|
-
# in future it might be changed to Udat::Node.parse_document.
|
412
|
-
def self.parse(input)
|
413
|
-
parse_part(input)
|
412
|
+
parse(input)
|
414
413
|
end
|
415
414
|
|
416
|
-
# Reads
|
415
|
+
# Reads an encoded Udat::Node from a stream.
|
417
416
|
def self.read_from_stream(io)
|
418
417
|
return (
|
419
418
|
parse_intern(:one_value, nil) do
|
@@ -421,16 +420,52 @@ module Udat
|
|
421
420
|
end
|
422
421
|
)
|
423
422
|
end
|
424
|
-
|
425
|
-
#
|
426
|
-
#
|
423
|
+
# Encodes an object in the UDAT format by calling Object#to_udat
|
424
|
+
# followed by Udat::Node#encode, and writes it to a stream.
|
425
|
+
# Returns self.
|
427
426
|
def write_to_stream(io)
|
428
|
-
io << self.to_udat.
|
427
|
+
io << self.to_udat.encode
|
429
428
|
return self
|
430
429
|
end
|
431
430
|
|
432
|
-
|
431
|
+
# Sends the object in encoded form through a TCP connection to a given
|
432
|
+
# host and port, or if no port is supplied to the host and port
|
433
|
+
# specified in a DNS SRV record "_udat-rpc._tcp.<domain>". Returns the
|
434
|
+
# UDAT object received as a reply. As this method can block for an
|
435
|
+
# unlimited amount of time, it might be useful to enclose the call in a
|
436
|
+
# Timeout.timeout call.
|
437
|
+
def rpc(domain, port = nil)
|
438
|
+
domain = domain.to_str
|
439
|
+
port = port.to_int if port
|
440
|
+
socket_args = nil
|
441
|
+
result = nil
|
442
|
+
Resolv::DNS.open do |dns|
|
443
|
+
if port.nil?
|
444
|
+
srv_rr = dns.getresource(
|
445
|
+
"_udat-rpc._tcp.#{domain}",
|
446
|
+
Resolv::DNS::Resource::IN::SRV
|
447
|
+
)
|
448
|
+
socket_args = [srv_rr.target.to_s, srv_rr.port.to_i]
|
449
|
+
else
|
450
|
+
socket_args = [domain, port]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
socket = nil
|
454
|
+
begin
|
455
|
+
socket = TCPSocket.new(*socket_args)
|
456
|
+
socket.write_udat(self)
|
457
|
+
socket.close_write
|
458
|
+
result = socket.read_udat
|
459
|
+
ensure
|
460
|
+
socket.close if socket
|
461
|
+
end
|
462
|
+
unless result
|
463
|
+
raise EOFError, "End of file before reading UDAT RPC result."
|
464
|
+
end
|
465
|
+
return result
|
466
|
+
end
|
433
467
|
|
468
|
+
end
|
434
469
|
|
435
470
|
# Class of Udat::Node's holding an ordered or unordered collection of
|
436
471
|
# values (where each value can optionally have a key associated with it).
|
@@ -442,6 +477,8 @@ module Udat
|
|
442
477
|
# methods of this class to Udat::Node's using Object#to_udat.
|
443
478
|
class Collection < Node
|
444
479
|
|
480
|
+
public
|
481
|
+
|
445
482
|
# Internally used wrapper class for Udat::Node's, to lookup
|
446
483
|
# Udat::Collection's in Hash'es, while ignoring the order of their
|
447
484
|
# content.
|
@@ -509,6 +546,11 @@ module Udat
|
|
509
546
|
content.to_ary.each { |entry| self << entry }
|
510
547
|
end
|
511
548
|
|
549
|
+
# Returns true.
|
550
|
+
def collection?
|
551
|
+
true
|
552
|
+
end
|
553
|
+
|
512
554
|
# Deletes the internal index hashes.
|
513
555
|
# They are automatically reconstructed once a lookup is done.
|
514
556
|
# This method should be called by the user, if contained Udat objects
|
@@ -793,54 +835,41 @@ module Udat
|
|
793
835
|
return value_by_key(key)
|
794
836
|
end
|
795
837
|
end
|
796
|
-
# Same as Udat::Collection#[], but raises an
|
797
|
-
# found. If an additional second argument is passed, an
|
798
|
-
# be raised if the tag (i.e. type) of the value is not
|
799
|
-
# second argument.
|
800
|
-
def fetch(
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
raise IndexError, "Value for the given key or index not found."
|
805
|
-
end
|
806
|
-
return value
|
807
|
-
elsif args.length == 2
|
808
|
-
value = fetch(args[0])
|
809
|
-
unless value.tag == args[1]
|
810
|
-
raise UdatTagMismatch, "UDAT tag mismatch."
|
811
|
-
end
|
812
|
-
return value
|
813
|
-
else
|
814
|
-
raise ArgumentError, "Wrong number of arguments supplied."
|
838
|
+
# Same as Udat::Collection#[], but raises an Udat::UdatIndexError, if
|
839
|
+
# no value was found. If an additional second argument is passed, an
|
840
|
+
# error will also be raised if the tag (i.e. type) of the value is not
|
841
|
+
# equal to the second argument.
|
842
|
+
def fetch(key, tag = AnyTag)
|
843
|
+
value = self[key]
|
844
|
+
unless value
|
845
|
+
raise UdatIndexError, "UDAT node not found."
|
815
846
|
end
|
847
|
+
value.require_tag(tag) unless tag == AnyTag
|
848
|
+
return value
|
816
849
|
end
|
817
850
|
# Same as Udat::Collection#fetch, but raises an error, if the value is
|
818
|
-
# not an Udat::
|
819
|
-
def
|
820
|
-
value = fetch(
|
821
|
-
|
822
|
-
|
823
|
-
"Scalar value found, where a collection was expected."
|
824
|
-
end
|
851
|
+
# not an Udat::Scalar.
|
852
|
+
def fetch_scalar(key, tag = AnyTag)
|
853
|
+
value = fetch(key)
|
854
|
+
value.require_scalar
|
855
|
+
value.require_tag(tag) unless tag == AnyTag
|
825
856
|
return value
|
826
857
|
end
|
827
858
|
# Same as Udat::Collection#fetch, but raises an error, if the value is
|
828
|
-
# not an Udat::
|
829
|
-
def
|
830
|
-
value = fetch(
|
831
|
-
|
832
|
-
|
833
|
-
"Collection found, where a scalar value was expected."
|
834
|
-
end
|
859
|
+
# not an Udat::Collection.
|
860
|
+
def fetch_collection(key, tag = AnyTag)
|
861
|
+
value = fetch(key)
|
862
|
+
value.require_collection
|
863
|
+
value.require_tag(tag) unless tag == AnyTag
|
835
864
|
return value
|
836
865
|
end
|
837
866
|
# Returns the first value of the collection, or nil if empty.
|
838
867
|
def first
|
839
|
-
|
868
|
+
value_by_index(0)
|
840
869
|
end
|
841
870
|
# Returns the last value of the collection, or nil if empty.
|
842
871
|
def last
|
843
|
-
|
872
|
+
value_by_index(-1)
|
844
873
|
end
|
845
874
|
|
846
875
|
# Returns the number of values in the collection.
|
@@ -927,32 +956,14 @@ module Udat
|
|
927
956
|
return self
|
928
957
|
end
|
929
958
|
|
930
|
-
# Returns the encoded form of the content as a string.
|
931
|
-
# This method is used by Udat::Node#encode_part and will be declared
|
932
|
-
# protected in future.
|
933
|
-
def encoded_content
|
934
|
-
synchronize do
|
935
|
-
return (
|
936
|
-
@entries.empty? ? "~" :
|
937
|
-
@entries.collect do |key, value|
|
938
|
-
if key
|
939
|
-
"<#{key.encode_part}>[#{value.encode_part}]"
|
940
|
-
else
|
941
|
-
"[#{value.encode_part}]"
|
942
|
-
end
|
943
|
-
end.join
|
944
|
-
)
|
945
|
-
end
|
946
|
-
end
|
947
|
-
|
948
959
|
# Same as Udat::Collection#ordered?.
|
949
960
|
def ordered
|
950
961
|
synchronize do
|
951
962
|
return @ordered
|
952
963
|
end
|
953
964
|
end
|
954
|
-
# Returns
|
955
|
-
#
|
965
|
+
# Returns true, if the order of the contents of this collection is
|
966
|
+
# significant for comparisons.
|
956
967
|
def ordered?
|
957
968
|
self.ordered
|
958
969
|
end
|
@@ -983,6 +994,23 @@ module Udat
|
|
983
994
|
return self
|
984
995
|
end
|
985
996
|
|
997
|
+
# Returns the encoded form of the content as a string.
|
998
|
+
# This method is used by Udat::Node#encode_without_brackets.
|
999
|
+
def encode_content
|
1000
|
+
synchronize do
|
1001
|
+
return (
|
1002
|
+
@entries.empty? ? "~" :
|
1003
|
+
@entries.collect do |key, value|
|
1004
|
+
if key
|
1005
|
+
"<#{key.encode_without_brackets}>" <<
|
1006
|
+
"[#{value.encode_without_brackets}]"
|
1007
|
+
else
|
1008
|
+
"[#{value.encode_without_brackets}]"
|
1009
|
+
end
|
1010
|
+
end.join
|
1011
|
+
)
|
1012
|
+
end
|
1013
|
+
end
|
986
1014
|
# Same as Udat::Node#inspect, but in case of unordered collections it
|
987
1015
|
# prepends "udat-unordered" instead of just "udat".
|
988
1016
|
def inspect
|
@@ -1012,6 +1040,8 @@ module Udat
|
|
1012
1040
|
# Class of Udat::Node's holding scalar values (stored as a String).
|
1013
1041
|
class Scalar < Node
|
1014
1042
|
|
1043
|
+
public
|
1044
|
+
|
1015
1045
|
public_class_method :new
|
1016
1046
|
# Creates a new Udat::Scalar with a given tag (which may be nil) and a
|
1017
1047
|
# given content, which is transformed to a string (via Object#to_s).
|
@@ -1021,6 +1051,11 @@ module Udat
|
|
1021
1051
|
self.content = content
|
1022
1052
|
end
|
1023
1053
|
|
1054
|
+
# Returns true.
|
1055
|
+
def scalar?
|
1056
|
+
true
|
1057
|
+
end
|
1058
|
+
|
1024
1059
|
# Content of the scalar (a String).
|
1025
1060
|
attr_reader :content
|
1026
1061
|
# Sets the content. The content is casted to a string.
|
@@ -1036,6 +1071,10 @@ module Udat
|
|
1036
1071
|
def to_i
|
1037
1072
|
content.to_i
|
1038
1073
|
end
|
1074
|
+
# Returns the content casted to a Float.
|
1075
|
+
def to_f
|
1076
|
+
content.to_f
|
1077
|
+
end
|
1039
1078
|
|
1040
1079
|
# Returns true, if the (String representation of the) content is empty.
|
1041
1080
|
def empty?
|
@@ -1043,18 +1082,189 @@ module Udat
|
|
1043
1082
|
end
|
1044
1083
|
|
1045
1084
|
# Returns the encoded form of the content as a string. In this case
|
1046
|
-
# this is simply an escaped version of the String.
|
1047
|
-
|
1048
|
-
def encoded_content
|
1085
|
+
# this is simply an escaped version of the String.
|
1086
|
+
def encode_content
|
1049
1087
|
Udat::escape_string(self.to_s)
|
1050
1088
|
end
|
1051
1089
|
|
1052
1090
|
end
|
1053
1091
|
|
1092
|
+
# Waits in an endless loop for incoming TCP connections on the given port
|
1093
|
+
# and runs a new thread for each incoming connection, reads and parses
|
1094
|
+
# UDAT objects, passes them each to a given block (by calling yield) and
|
1095
|
+
# writes the result of the called block in form of an encoded UDAT object
|
1096
|
+
# back to the TCP socket. A timeout can be specified, to determine how
|
1097
|
+
# long it may take to read an object.
|
1098
|
+
def run_rpc_server(port, timeout = 180)
|
1099
|
+
server = nil
|
1100
|
+
begin
|
1101
|
+
server = TCPServer.new(port)
|
1102
|
+
while true
|
1103
|
+
Thread.new(server.accept) do |socket|
|
1104
|
+
begin
|
1105
|
+
while true
|
1106
|
+
query = nil
|
1107
|
+
begin
|
1108
|
+
Timeout.timeout(timeout) do
|
1109
|
+
query = socket.read_udat
|
1110
|
+
end
|
1111
|
+
rescue Timeout::Error
|
1112
|
+
end
|
1113
|
+
break unless query
|
1114
|
+
socket.write_udat(yield(query))
|
1115
|
+
end
|
1116
|
+
ensure
|
1117
|
+
socket.close
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
ensure
|
1122
|
+
server.close if server
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
# Module containing some demonstration functions.
|
1127
|
+
module Demo
|
1128
|
+
|
1129
|
+
module_function
|
1130
|
+
|
1131
|
+
# A string containing an UDAT example document.
|
1132
|
+
ExampleDocument = <<-'END_OF_EXAMPLE'
|
1133
|
+
[sample config v1.0|
|
1134
|
+
|
1135
|
+
<network> [
|
1136
|
+
<max connections> [256]
|
1137
|
+
<reverse lookups> [yes]
|
1138
|
+
]
|
1139
|
+
|
1140
|
+
<locale> [
|
1141
|
+
<language> [de] german language
|
1142
|
+
<timezone> [CET] and CET timezone
|
1143
|
+
Comments can appear almost anywhere.
|
1144
|
+
]
|
1145
|
+
|
1146
|
+
<access control> [
|
1147
|
+
<allowed> [
|
1148
|
+
[user|martin]
|
1149
|
+
[user|max]
|
1150
|
+
[group|admins]
|
1151
|
+
[ip4network|
|
1152
|
+
<address> [192.168.0.0]
|
1153
|
+
<netmask> [255.255.255.0]
|
1154
|
+
]
|
1155
|
+
]
|
1156
|
+
<blocked> [~] The tilde symbol denotes an empty collection.
|
1157
|
+
]
|
1158
|
+
|
1159
|
+
<misc> [
|
1160
|
+
<symbol for homedir> [\~] Not an empty collection but the scalar
|
1161
|
+
value "~".
|
1162
|
+
]
|
1163
|
+
|
1164
|
+
<address mappings> [
|
1165
|
+
|
1166
|
+
<email|
|
1167
|
+
<user> [jan.behrens]
|
1168
|
+
<domain> [flexiguided.de]
|
1169
|
+
>
|
1170
|
+
[mbox|jan]
|
1171
|
+
|
1172
|
+
<email|
|
1173
|
+
<user> [glob|*]
|
1174
|
+
<domain> [flexiguided.de]
|
1175
|
+
>
|
1176
|
+
[mbox|catchall]
|
1177
|
+
|
1178
|
+
]
|
1179
|
+
|
1180
|
+
<logging> [
|
1181
|
+
<verbosity> [high]
|
1182
|
+
\## <verbosity> [debug] ## Uncomment this for debug output.
|
1183
|
+
<destination> [file|/var/log/sample.log]
|
1184
|
+
]
|
1185
|
+
|
1186
|
+
]
|
1187
|
+
END_OF_EXAMPLE
|
1188
|
+
|
1189
|
+
# Runs a demo server, not for production use.
|
1190
|
+
def run_rpc_demo_server(port, timeout = 180)
|
1191
|
+
Udat::run_rpc_server(port, timeout) do |query|
|
1192
|
+
begin
|
1193
|
+
case query.tag
|
1194
|
+
when "echo request"
|
1195
|
+
query.tag = "echo reply"
|
1196
|
+
next query
|
1197
|
+
when "calculation request"
|
1198
|
+
begin
|
1199
|
+
query.require_collection
|
1200
|
+
operands = query.fetch_collection("operands")
|
1201
|
+
operand1 = operands.fetch_scalar(0).to_f
|
1202
|
+
operand2 = operands.fetch_scalar(1).to_f
|
1203
|
+
end
|
1204
|
+
operand1 = operands[0].to_f
|
1205
|
+
operand2 = operands[1].to_f
|
1206
|
+
operator = query.fetch_scalar("operator")
|
1207
|
+
case operator
|
1208
|
+
when "+".to_udat
|
1209
|
+
next (operand1 + operand2).to_udat("calculation result")
|
1210
|
+
when "-".to_udat
|
1211
|
+
next (operand1 - operand2).to_udat("calculation result")
|
1212
|
+
when "*".to_udat
|
1213
|
+
next (operand1 * operand2).to_udat("calculation result")
|
1214
|
+
when "/".to_udat
|
1215
|
+
next (operand1.quo operand2).to_udat("calculation result")
|
1216
|
+
else
|
1217
|
+
next([["message", "Unknown operator."]].to_udat("error")
|
1218
|
+
)
|
1219
|
+
end
|
1220
|
+
when "time request"
|
1221
|
+
next Time.now.strftime("%Y-%m-%d %H:%M:%S %Z").to_udat('time')
|
1222
|
+
else
|
1223
|
+
raise Udat::UdatTagMismatch, "Unknown UDAT tag."
|
1224
|
+
end
|
1225
|
+
rescue Udat::UdatPropertyMismatch, UdatIndexError
|
1226
|
+
next [["message", $!.message]].to_udat("error")
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
# Method to test the RPC mechanism, not for production use.
|
1232
|
+
def rpc_selftest(port = 10330)
|
1233
|
+
a = rand(100) + 1
|
1234
|
+
b = rand(100) + 1
|
1235
|
+
demo_server_thread = nil
|
1236
|
+
begin
|
1237
|
+
demo_server_thread = Thread.new do
|
1238
|
+
Udat::Demo::run_rpc_demo_server(port)
|
1239
|
+
end
|
1240
|
+
sleep 0.5
|
1241
|
+
udat_result = nil
|
1242
|
+
Timeout.timeout(15) do
|
1243
|
+
udat_result = {'operands' => [a, b], 'operator' => '+'}.
|
1244
|
+
to_udat("calculation request").
|
1245
|
+
rpc('localhost', port)
|
1246
|
+
end
|
1247
|
+
if udat_result.tag == "error"
|
1248
|
+
raise "RPC call returned with an error: " <<
|
1249
|
+
udat_result.fetch_scalar("message").to_s
|
1250
|
+
end
|
1251
|
+
result = udat_result.require_scalar("calculation result").to_f
|
1252
|
+
ensure
|
1253
|
+
demo_server_thread.kill
|
1254
|
+
end
|
1255
|
+
unless result == a + b
|
1256
|
+
raise "Calculation error during UDAT RPC selftest."
|
1257
|
+
end
|
1258
|
+
return "Test done: #{a} + #{b} == #{result}"
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
end
|
1262
|
+
|
1054
1263
|
end
|
1055
1264
|
|
1056
1265
|
|
1057
1266
|
class Object
|
1267
|
+
public
|
1058
1268
|
# Casts the Object to an Udat::Scalar object, optionally with a given
|
1059
1269
|
# tag. Unless overwritten by sub classes, only the string representation
|
1060
1270
|
# (as returned by Object#to_s) will be stored as content in the
|
@@ -1065,6 +1275,7 @@ class Object
|
|
1065
1275
|
end
|
1066
1276
|
|
1067
1277
|
class Array
|
1278
|
+
public
|
1068
1279
|
# Casts the Array to an Udat::Collection object, optionally with a given
|
1069
1280
|
# tag. The Udat::Collection object is marked to be ordered. Each element
|
1070
1281
|
# of the array may be an Array of size 2, in which case the 2 entries are
|
@@ -1076,6 +1287,7 @@ class Array
|
|
1076
1287
|
end
|
1077
1288
|
|
1078
1289
|
class Hash
|
1290
|
+
public
|
1079
1291
|
# Casts the Hash to an Udat::Collection object, optionally with a given
|
1080
1292
|
# tag. The Udat::Collection object is marked to be unordered.
|
1081
1293
|
def to_udat(tag = nil)
|
@@ -1084,18 +1296,19 @@ class Hash
|
|
1084
1296
|
end
|
1085
1297
|
|
1086
1298
|
class String
|
1087
|
-
|
1088
|
-
#
|
1299
|
+
public
|
1300
|
+
# Calls Udat::Node.parse(self).
|
1089
1301
|
def parse_udat
|
1090
|
-
Udat::Node.
|
1302
|
+
Udat::Node.parse(self)
|
1091
1303
|
end
|
1092
|
-
#
|
1304
|
+
# Alias for Udat::Node.parse, will be removed in future versions.
|
1093
1305
|
def parse_udat_document
|
1094
1306
|
Udat::Node.parse_document(self)
|
1095
1307
|
end
|
1096
1308
|
end
|
1097
1309
|
|
1098
1310
|
class IO
|
1311
|
+
public
|
1099
1312
|
# Calls Udat::Node.read_from_stream(self).
|
1100
1313
|
def read_udat
|
1101
1314
|
Udat::Node.read_from_stream(self)
|
@@ -1108,11 +1321,3 @@ class IO
|
|
1108
1321
|
end
|
1109
1322
|
end
|
1110
1323
|
|
1111
|
-
# Deprecated constant for backwards compatibility.
|
1112
|
-
UdatCollection = Udat::Collection
|
1113
|
-
|
1114
|
-
# Deprecated constant for backwards compatibility.
|
1115
|
-
UdatScalar = Udat::Scalar
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: udat
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-05-
|
6
|
+
version: 1.4.0
|
7
|
+
date: 2007-05-30 00:00:00 +00:00
|
8
8
|
summary: Parser and generator for UDAT documents, a generic data format similar to XML or YAML.
|
9
9
|
require_paths:
|
10
10
|
- lib/
|