udat 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/
|