Class: Udat

Inherits:
Object show all
Includes:
MonitorMixin
Defined in:
lib/udat.rb

Overview

Copyright © 2007 FlexiGuided GmbH, Berlin

Author: Jan Behrens

Website: www.flexiguided.de/publications.udat.en.html


Abstract class of an UDAT object. UDAT objects basically consist of a tag and the content. The tag can be a String or be nil. The content is either a scalar value stored as a String, or an ordered/unordered collection of values (where each value can optionally have a key associated with it). Keys and values of collections are always UDAT objects too.

UDAT objects can most easily be constructed from other ruby objects by using the Object#to_udat method.

By calling the method Udat#encode, the UDAT object is encoded in a both easily human readable and easily machine readable format. The output can be later parsed by String#parse_udat, or if it is enclosed in square brackets by IO#read_udat.

Direct Known Subclasses

UdatCollection, UdatScalar

Defined Under Namespace

Classes: UdatParseError, UdatTagMismatch, UdatTypeMismatch

Constant Summary collapse

KeepTag =

Special argument passed to Udat#to_udat, if the tag is to be kept.

Object.new
ExampleDocument =

A string containing an UDAT example document.

<<-'END_OF_EXAMPLE'
  This file contains several UDAT objects (each of them enclosed in
  square brackets), serving as an example for the UDAT format:


  Scalar values:

    [Hello World!]

    [UTF-8|Hello World!]  This object has a tag "UTF-8".
                          Intepretation of tags is task of the
                          application, there are no standard tags
                          defined.

    [23]

    [integer|23]

    [rational|2/3]

    The following 7 characters must be escaped with a backslash, when
    being part of a tag or being part of the content of a scalar:

    \< \> \[ \] \| \~ \\


  Arrays (or sets):

    [ [1] [2] [3] ]

    [ [1] [2] Comments are allowed here! (and alomost everywhere) [3] ]

    [ [person| <firstname>[Max]    <lastname>[Mustermann] ]
      [person| <firstname>[Martin] <lastname>[html|M&uuml;ller] ]
      [group| <name>[Sample group] <members> [
        [person| <firstname>[Max]    <lastname>[Mustermann] ]
        [person| <firstname>[Martin] <lastname>[html|M&uuml;ller] ]
      ] ]                                                            ]

    References like in YAML with &id and *id
    are not directly supported by UDAT.

    Empty arrays, sets or maps must contain the ~ character, to avoid
    ambiguity with scalars:

    [   ]                  scalar containing three spaces
    [ ~ ]                  empty array
    [list of numbers| ~ ]  empty array, tagged with "list of numbers"
    [ ~ comment ]          empty array with a comment
    [ comment ~ ]          another empty array with a comment
    [ ~~ ]                 empty array too (multiple ~ are allowed)

    It is also allowed to write ~ in non-empty arrays. In that case the ~
    symbol is ignored.

    [~ [1] [2] [3] ]       Array containing "1", "2" and "3"


  Ordered or unordered maps:

    [ <&>  [html|&amp;]
      <">  [html|&quot;]
      <\<> [html|&lt;]
      <\>> [html|&gt;]   ]

    [ <color combination|[red][blue]>    [nice]
      <color combination|[red][magenta]> [ugly] ]

    [  < <red>[on]  <yellow>[off] <green>[off] >  [stop]
       < <red>[on]  <yellow>[on]  <green>[off] >  [attention]
       < <red>[off] <yellow>[off] <green>[on]  >  [go]
       < <red>[off] <yellow>[on]  <green>[off] >  [prepare to stop]  ]


  Sample configuration file:

    [sample config v1.0|
      <locale> [
        <language> [de]
        <timezone> [CET]
      ]
      <logging> [
        <verbosity>   [high]
        <destination> [file|/var/log/sample.log]
      ]
      <network> [
        <max connections> [256]
        <reverse lookups> [yes]
      ]
      <access control> [
        <allowed> [ [user|martin] [user|max] [group|admins] ]
      ]
    ]


    Don't abuse tags to store complicated meta information like this:

    WRONG!  [string charset=ISO-8859-1 language=EN|Hello World!]  WRONG!


    If you need to store more complicated meta information, you should do
    that by using ordinary key/value pairs:

    [string with metainfo v1.0|
      <charset>  [ISO-8859-1]
      <language> [en]
      <content>  [Hello World!]
    ]


END_OF_EXAMPLE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tag) ⇒ Udat

Creates a new Udat object with a given tag (which may be nil). This method is private, Udat#initialize is only called through supercalls.



160
161
162
163
# File 'lib/udat.rb', line 160

def initialize(tag)
  super()
  self.tag = tag
end

Instance Attribute Details

#tagObject

Tag (i.e. type of the content).



177
178
179
# File 'lib/udat.rb', line 177

def tag
  @tag
end

Class Method Details

.escape_string(string) ⇒ Object

Returns an escaped version of a string, where backslashes are preceding certain reserved characters.



167
168
169
# File 'lib/udat.rb', line 167

def self.escape_string(string)
  string.to_s.gsub /([<>\[\]|~\\])/, "\\\\\\1"
end

.parse(input) ⇒ Object

Parses a given string and returns a structure of Udat objects.

Note: When parsing UDAT data, no information is gained, whether collections are ordered or unordered. After parsing, all collections will be marked as unordered, unless changed later by the application.



333
334
335
336
337
338
339
340
# File 'lib/udat.rb', line 333

def self.parse(input)
  input = input.to_s
  result, pos = parse_intern(input)
  if pos < input.length
    raise UdatParseError, "Closing bracket without opening bracket."
  end
  return result
end

.read_from_stream(io) ⇒ Object

Reads an encoded UDAT object from a stream. The object must be enclosed in square brackets.



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/udat.rb', line 344

def self.read_from_stream(io)
  begin
    begin
      char = io.read(1)
      return nil if char.nil? or char.length < 1
      if char == "\\"
        char = io.read(1)
        return nil if char.nil? or char.length < 1
      end
    end while char != "[" and char != "<"
    buffer = (char == "[") ? "" : nil
    level = 1
    while true
      char = io.read(1)
      if char.nil? or char.length < 1
        return EOFError, "Unexpected end of file in UDAT stream."
      end
      if char == "\\"
        buffer << char if buffer
        char = io.read(1)
        if char.nil? or char.length < 1
          return EOFError, "Unexpected end of file in UDAT stream."
        end
      elsif char == "[" or char == "<"
        level += 1
      elsif char == "]" or char == ">"
        level -= 1
      end
      if level > 0
        buffer << char if buffer
      else
        break
      end
    end
  end while buffer.nil?
  return parse(buffer)
end

Instance Method Details

#==(other) ⇒ Object

Same as Udat#eql?, but behaves differently in UdatCollection.



248
249
250
# File 'lib/udat.rb', line 248

def ==(other)
  self.eql? other
end

#collection?Boolean

Returns true, if the UDAT object represents a collection.

Returns:

  • (Boolean)


191
192
193
# File 'lib/udat.rb', line 191

def collection?
  kind_of? UdatCollection
end

#encodeObject

Returns the data (including it’s tag) encoded in the UDAT format.

Note: The UDAT format doesn’t contain information, whether contained collections are ordered or unordered. This information is lost during the encoding process, and has to be restored in an application specific way, if neccessary.



201
202
203
204
205
206
207
208
209
# File 'lib/udat.rb', line 201

def encode
  synchronize do
    if tag
      return "#{escape_string tag}|#{encoded_content}"
    else
      return encoded_content
    end
  end
end

#eql?(other) ⇒ Boolean

Returns true, if class, tag and content (including it’s order in case of a UdatCollection) are matching another object.

Returns:

  • (Boolean)


242
243
244
245
246
# File 'lib/udat.rb', line 242

def eql?(other)
  self.class == other.class and
    self.tag == other.tag and
    self.to_s == other.to_s
end

#hashObject

Returns a hash key used by ruby’s Hash’es.



237
238
239
# File 'lib/udat.rb', line 237

def hash
  to_s.hash
end

#inspectObject

Does the same as Udat#encode, but encloses the results in curly brackets and preceds them with the string “udat”.



217
218
219
# File 'lib/udat.rb', line 217

def inspect
  "udat{#{self.encode}}"
end

#scalar?Boolean

Returns true, if the UDAT object represents a scalar value.

Returns:

  • (Boolean)


187
188
189
# File 'lib/udat.rb', line 187

def scalar?
  kind_of? UdatScalar
end

#to_sObject

Here the method does the same as Udat#encode, but this method is overwritten in UdatScalar!



212
213
214
# File 'lib/udat.rb', line 212

def to_s
  encode
end

#to_udat(tag = KeepTag) ⇒ Object

Returns self, or a duplicate of self with a different tag set, if an argument is passed.



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/udat.rb', line 223

def to_udat(tag = KeepTag)
  if tag == KeepTag
    return self
  else
    obj = nil
    synchronize do
      obj = self.dup
    end
    obj.tag = tag
    return obj
  end
end

#write_to_stream(io) ⇒ Object

Encodes an UDAT object and writes it enclosed by square brackets to a stream.



384
385
386
387
# File 'lib/udat.rb', line 384

def write_to_stream(io)
  io << "[#{self.to_udat}]"
  return self
end