udat 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/example.udat +23 -5
  2. data/lib/udat.rb +403 -198
  3. metadata +2 -2
@@ -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 without square or angle brackets or the tilde symbol is
84
- considered to be a scalar:
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
- Any string which includes either square or angle brackets or a tilde
113
- symbol is considered to be a collection. Each entry in a collection
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 in angle brackets and being followed by the value in
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:
@@ -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 any Object to a Udat::Node,
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#encode_document.
24
+ # Udat::Node#encode.
22
25
  #
23
- # UDAT documents can be parsed by calling String#parse_udat_document.
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
- # Error raised by Udat::Collection#fetch,
27
- # when a fetched value doesn't have the expected tag (i.e. type).
28
- class UdatTagMismatch < StandardError; end
29
- # Error raised by Udat::Collection#fetch,
30
- # when a fetched value is a collection instead of a scalar or vice versa.
31
- class UdatTypeMismatch < StandardError; end
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 passed to Udat::Node#to_udat, if the tag is to be
36
- # kept.
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#encode_document, the Node and its
115
- # children are encoded in a both easily human readable and easily machine
116
- # readable format. The output can be later parsed by
117
- # String#parse_udat_document or IO#read_udat.
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
- kind_of? Scalar
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
- kind_of? Collection
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 data (including it's tag) encoded in the UDAT format. The
153
- # result is not enclosed by square brackets, so not intended to be
154
- # used externally. This method is public for backwards compatiblity
155
- # only, and will be declared protected in future.
156
- # Use Udat::Node#encode_document instead.
157
- def encode_part
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}|#{encoded_content}"
165
+ return "#{Udat::escape_string tag}|#{encode_content}"
161
166
  else
162
- return encoded_content
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. This is the preferred form of encoding,
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
- encode_part
180
+ "[#{encode_without_brackets}]"
181
181
  end
182
- # Here the method does the same as Udat::Node#encode_part, but this
183
- # method is overwritten in Udat::Scalar! It is also planned to change
184
- # the behaviour to Udat::Node#encode_document in future.
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
- encode_part
189
+ encode
187
190
  end
188
- # Does the same as Udat::Node#encode_document, but preceds the result
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.encode_document}"
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 = KeepTag)
197
- if tag == KeepTag
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
- # Deprecated. Parses a string encoded by Udat::Node#encode_part. This
383
- # method might be removed in future versions. Use
384
- # Udat::Node#encode_document in combination with
385
- # Udat::Node.parse_document instead.
386
- def self.parse_part(input)
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
- return (
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
- # Parses a given UDAT document string and returns a structure of
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).first or raise ParseError, "No UDAT object found."
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 Udat::Node encoded by Udat::Node#encode_document from a stream.
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
- # Encodes an object by calling Object#to_udat followed by
426
- # Udat::Node#encode_document, and writes it to a stream. Returns self.
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.encode_document
427
+ io << self.to_udat.encode
429
428
  return self
430
429
  end
431
430
 
432
- end
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 error, if no value was
797
- # found. If an additional second argument is passed, an error will also
798
- # be raised if the tag (i.e. type) of the value is not equal to the
799
- # second argument.
800
- def fetch(*args)
801
- if args.length == 1
802
- value = self[args[0]]
803
- unless value
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::Collection.
819
- def fetch_collection(*args)
820
- value = fetch(*args)
821
- if value.scalar?
822
- raise UdatTypeMismatch,
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::Scalar.
829
- def fetch_scalar(*args)
830
- value = fetch(*args)
831
- if value.collection?
832
- raise UdatTypeMismatch,
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
- self[0]
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
- self[-1]
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 false, if the order of the contents of this collection is to
955
- # be ignored for comparisons.
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. This method will be
1047
- # declared protected in future versions.
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
- # Deprecated. Calls Udat::Node.parse_part(self).
1088
- # Use String#parse_udat_document instead.
1299
+ public
1300
+ # Calls Udat::Node.parse(self).
1089
1301
  def parse_udat
1090
- Udat::Node.parse_part(self)
1302
+ Udat::Node.parse(self)
1091
1303
  end
1092
- # Calls Udat::Node.parse_document(self).
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.3.0
7
- date: 2007-05-28 00:00:00 +00:00
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/