123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- package sitef
- import "os"
- import "io"
- import "strings"
- import "fmt"
- import "bytes"
- import "bufio"
- import "strconv"
- // Sitef format for serialization
- // 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.
- // It is quite similar to recfiles, though not compatible because
- // in sitef files the first character on the line determines the meaning,
- // and there is no backslash escaping.
- //
- // Sitef is a line based syntax where the first character on the line
- // determines the meaning of the line.
- // Several lines together form a record.
- // A line that starts with # is a comment. There may be no whitespace
- // in front of the comment.
- // A newline character by itself (that is, an empty line),
- // or a - ends a record.
- // A plus character, an escape on the previous line or a tab or
- // a space continues a value.
- // A Continues value gets a newline inserted only when a space or tab was used.
- // + supresses the newline.
- // Anything else signifies the beginning of the next key.
- // % is allowed for special keys for recfile compatibility.
- // However % directives are not implemented.
- // 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
- //
- type Record map[string]string
- func (me * Record) Put(key string, val string) {
- (*me)[key] = val
- }
- func (me * Record) Putf(key string, format string, values ...interface{}) {
- me.Put(key, fmt.Sprintf(format, values...))
- }
- func (me * Record) PutArray(key string, values []string) {
- for i, value := range values {
- realkey := fmt.Sprintf("%s[%d]", key, i)
- me.Put(realkey, value)
- }
- }
- func (me * Record) PutInt(key string, val int) {
- me.Putf(key, "%d", val)
- }
- func (me * Record) PutFloat64(key string, val float64) {
- me.Putf(key, "%lf", val)
- }
- func (me Record) Get(key string) (result string) {
- return me[key]
- }
- func (me Record) Getf(key string, format string,
- values ...interface{}) (amount int, ok bool) {
- val := me.Get(key)
- count, err := fmt.Sscanf(val, format, values...)
- if err != nil {
- return 0, false
- }
- return count, true
- }
- func (me Record) GetInt(key string) (val int, err error) {
- i, err := strconv.ParseInt(me.Get(key), 0, 0)
- return int(i), err
- }
- func (me Record) GetIntDefault(key string, def int) (val int) {
- i, err := strconv.ParseInt(me.Get(key), 0, 0)
- if err != nil {
- return def;
- }
- return int(i);
- }
- func (me Record) GetFloat(key string) (val float64, error error) {
- return strconv.ParseFloat(me.Get(key), 64)
- }
- type Error struct {
- error string
- lineno int
- }
- func (me Error) Error() string {
- return fmt.Sprintf("%d: %s", me.Lineno, me.error)
- }
- func (me Error) Lineno() int {
- return me.lineno
- }
- type ParserState int
- const (
- PARSER_STATE_INIT ParserState = iota
- PARSER_STATE_KEY
- PARSER_STATE_VALUE
- )
- type RecordList []Record
- func ParseReader(read io.Reader) (RecordList, error) {
- var records RecordList
- var record Record = make(Record)
- var err Error
- lineno := 0
- scanner := bufio.NewScanner(read)
- var key bytes.Buffer
- var value bytes.Buffer
-
-
- for scanner.Scan() {
- lineno++
- line := scanner.Text()
- // End of record?
- if (len(line) < 1) || line[0] == '-' {
- // save the record and make a new one
- records = append(records, record)
- record = make(Record)
- // comment?
- } else if line[0] == '#' {
- continue;
- // continue value?
- } else if line[0] == '\t' || line[0] == ' '|| line[0] == '+' {
-
- /* Add a newline unless + is used */
- if (line[0] != '+') {
- value.WriteRune('\n')
- }
-
- // continue the value, skipping the first character
- value.WriteString(line[1:])
- // new key
- } else if strings.ContainsRune(line, ':') {
- // save the previous key/value pair if needed
- if len(key.String()) > 0 {
- record[key.String()] = value.String()
- }
-
- key.Reset()
- value.Reset()
- parts := strings.SplitN(line, ":", 2)
-
- key.WriteString(parts[0])
- if len(parts) > 1 {
- value.WriteString(parts[1])
- }
- // Not a key. Be lenient and assume this is a continued value.
- } else {
- value.WriteString(line)
- }
- }
-
- // Append last record if needed.
- if len(key.String()) > 0 {
- record[key.String()] = value.String()
- }
-
- if (len(record) > 0) {
- records = append(records, record)
- }
-
- if serr := scanner.Err(); serr != nil {
- err.lineno = lineno
- err.error = serr.Error()
- return records, err
- }
-
- return records, nil
-
- }
- func ParseFilename(filename string) (RecordList, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- defer file.Close()
- return ParseReader(file)
- }
- func WriteField(writer io.Writer, key string, value string) {
- replacer := strings.NewReplacer("\n", "\n\t")
- writer.Write([]byte(key))
- writer.Write([]byte{':'})
- writer.Write([]byte(replacer.Replace(value)))
- writer.Write([]byte{'\n'})
- }
- func WriteRecord(writer io.Writer, record Record) {
- for key, value := range record {
- WriteField(writer, key, value);
- }
- writer.Write([]byte{'-', '-', '-', '-', '\n'})
- }
- func WriteRecordList(writer io.Writer, records RecordList) {
- for _, record := range records {
- WriteRecord(writer, record);
- }
- }
- func SaveRecord(filename string, record Record) (error) {
- file, err := os.Create(filename)
- if err != nil {
- return err
- }
- defer file.Close()
- WriteRecord(file, record)
- return nil
- }
- func SaveRecordList(filename string, records RecordList) (error) {
- file, err := os.Create(filename)
- if err != nil {
- return err
- }
- defer file.Close()
- WriteRecordList(file, records)
- return nil
- }
- /*
- func ParseFile(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
-
- func load_filename(filename)
- results, errors = nil, nil, nil;
- file = File.open(filename, 'rt') rescue nil
- 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
- func 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.
- func 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
-
- func 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
-
- func 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
- func 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
-
- func 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
- */
|