123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- # Sitef is a simple text format for serializing data to
- # It's intent is to be human readable and easy to
- # use for multi line text.
- module Sitef
- # All Sitef data is stored in files with one or more records.
- # Records are separated by separated by at least 2 dashes on a line.
- # Records contain key/value fields. The key starts in the first column
- # with a : and is followed by a : the value starts after the second :
- # A multiline key / value needs a key that starts with . and ends with .
- # the end of the value is a pair of dots .. by itself
- # Keys may not be nested, however, you could use spaces or dots,
- # or array indexes to emulate nexted keys.
- # A # at the start optionally after whitespace is a comment
- #
- def self.parse_file(file)
- lineno = 0
- results = []
- errors = []
-
- record = {}
- key = nil
- value = nil
- until file.eof?
- lineno += 1
- line = file.gets(256)
- break if line.nil?
- next if line.empty?
- # new record
- if line[0,2] == '--'
- # Store last key used if any.
- if key
- record[key] = value.chomp
- key = nil
- end
- results << record
- record = {}
- elsif line[0] == '#'
- # Comments start with #
- elsif line[0] == ':'
- # a key/value pair
- key, value = line[1,line.size].split(':', 2)
- record[key] = value.chomp
- key = value = nil
- elsif line[0, 2] == '..'
- # end of multiline value
- record[key] = value.chomp
- key = value = nil
- elsif (line[0] == '.') && key.nil?
- # Multiline key/value starts here (but is ignored
- # until .. is encountered)
- key = line[1, line.size]
- key.chomp!
- value = ""
- # multiline value
- elsif key
- if line[0] == '\\'
- # remove any escapes
- line.slice!(0)
- end
- # continue the value
- value << line
- else
- # Not in a key, sntax error.
- errors << "#{lineno}: Don't know how to process line"
- end
- end
- # Store last key used if any.
- if key
- record[key] = value.chomp
- end
- # store last record
- results << record unless record.empty?
- return results, errors
- end
-
- def self.load_filename(filename)
- results, errors = nil, nil, nil;
- file = File.open(filename, 'rt')
- return nil, ["Could not open #{filename}"] unless file
- begin
- results, errors = parse_file(file)
- ensure
- file.close
- end
- return results, errors
- end
-
- # Loads a Sitef fileas obejcts. Uses the ruby_klass atribute to load the object
- # If that is missing, uses defklass
- def self.load_objects(filename, defklass=nil)
- results, errors = load_filename(filename)
- p filename, results, errors
- unless errors.nil? || errors.empty?
- return nil, errors
- end
-
- objres = []
- results.each do | result |
- klassname = result['ruby_class'] || defklass
- return nil unless klassname
- klass = klassname.split('::').inject(Kernel) { |klass, name| klass.const_get(name) rescue nil }
- return nil unless klass
- if klass.respond_to? :from_sitef
- objres << klass.from_sitef(result)
- else
- objres << klass.new(result)
- end
- end
- return objres, errors
- end
-
-
- # Saves a single field to a file in Sitef format.
- def self.save_field(file, key, value)
- if value.is_a? String
- sval = value.dup
- else
- sval = value.to_s
- end
- if sval["\n"]
- file.puts(".#{key}\n")
- # Escape everything that could be misinterpreted with a \\
- sval.gsub!(/\n([\.\-\:\#\\]+)/, "\n\\\\\\1")
- sval.gsub!(/\A([\.\-\:\#\\]+)/, "\\\\\\1")
- file.printf("%s", sval)
- file.printf("\n..\n")
- else
- file.printf(":#{key}:#{sval}\n")
- end
- end
-
- def self.save_object(file, object, *fields)
- save_field(file, :ruby_class, object.class.to_s)
- fields.each do | field |
- value = object.send(field.to_sym)
- save_field(file, field, value)
- end
- end
-
- def self.save_record(file, record, *fields)
- record.each do | key, value |
- next if fields && !fields.empty? && !fields.member?(key)
- save_field(file, key, value)
- end
- end
- def self.save_file(file, records, *fields)
- records.each do | record |
- if record.is_a? Hash
- save_record(file, record, *fields)
- else
- save_object(file, record, *fields)
- end
- file.puts("--\n")
- end
- end
-
- def self.save_filename(filename, records, *fields)
- results , errors = nil, nil
- file = File.open(filename, 'wt')
- return false, ["Could not open #{filename}"] unless file
- begin
- save_file(file, records, *fields)
- ensure
- file.close
- end
- return true, []
- end
-
- end
|