]> git.phdru.name Git - m_lib.git/blob - m_lib/flad/flad.py
Remove import string for Py3 compatibility
[m_lib.git] / m_lib / flad / flad.py
1 """
2    Flat ASCII Database.
3    This module implements a very simple database on the flat ASCII files.
4 """
5
6
7 # Flad restriction error
8 checking_error = "flad.checking_error"
9
10 # Default key/value separator
11 def_keysep = ": "
12
13
14 class Flad(list):
15    """
16       Class to represent memory database.
17       FLAD database is a list of records,
18       where every record is a dictionary.
19    """
20
21    # Field and record separators are ALWAYS newline. It cannot be changed
22    #    without extensive rewriting of this module
23    # field_sep = rec_sep = '\n'
24
25    def __init__(self, check_record_func = None, key_sep = def_keysep):
26       list.__init__(self)
27       self.check_record_func = check_record_func
28       self.key_sep = key_sep
29
30       self.comment = ''
31       self.wait_comment = 1
32
33
34    def check_record(self, record): # Method can be overriden in subclasses
35       if self.check_record_func:
36          if callable(self.check_record_func):
37             return self.check_record_func(self, record)
38          else:
39             raise TypeError("non-callable restriction function")
40       else:
41          return 1
42
43
44    def checking_error(self): # Method can be overriden in subclasses
45       raise checking_error
46
47
48    def __setitem__(self, i, item):
49       if not self.check_record(item):
50          self.checking_error()
51       else:
52          list.__setitem__(self, i, item)
53
54
55    def __setslice__(self, i, j, v_list):
56       if v_list:
57          copy_list = v_list[:]
58          for item in v_list:
59             if not self.check_record(item):
60                self.checking_error()
61                del copy_list[copy_list.index(item)]
62          list.__setslice__(self, i, j, copy_list)
63
64
65    def append(self, item):
66       if not self.check_record(item):
67          self.checking_error()
68       else:
69          list.append(self, item)
70
71
72    def insert(self, i, item):
73       if not self.check_record(item):
74          self.checking_error()
75       else:
76          list.insert(self, i, item)
77
78
79    def split_key(self, line):
80       """
81          Split input line to key/value pair and add the pair to dictionary
82       """
83       ###line = line.lstrip() # Do not rstrip - if empty value, this will remove space from key
84       if line[-1] == '\n':
85          line = line[:-1] # Chop
86
87       l = line.split(self.key_sep, 1) # Just split to key and reminder
88       return tuple(l)
89
90
91    def __parse_line(self, record, line): # Internal function
92       if line == '\n': # Empty line is record separator (but there cannot be more than 1 empty lines)
93          if record: # This helps to skip all empty lines
94             self.append(record) # Check and add the record to list
95             return 1 # Signal to stop filling the record and start a new one
96
97       else:
98          key, value = self.split_key(line)
99          if key in record.keys(): # This have to be done with check_record
100                                   # but the record is not complete now,
101                                   # so it is not ready to be checked :(
102                                   # And, of course, two keys with the same name
103                                   # cannot be added to dictionary
104             raise KeyError("field key \"" + key + "\" already in record")
105
106          record[key] = value
107
108       return 0
109
110
111    def create_new_record(self): # Method can be overriden in subclasses
112       return {}
113
114
115    def feed(self, record, line): # Method can be overriden in subclasses
116       if line:
117          if self.wait_comment:
118             if line.strip() == '':
119                self.comment = self.comment + '\n'
120                self.wait_string = 0
121                return 0
122
123             elif line.lstrip()[0] == '#':
124                self.comment = self.comment + line
125                return 0
126
127             else:
128                self.wait_comment = 0
129                # Fallback to parsing
130
131          return self.__parse_line(record, line)
132
133       else:
134          self.append(record)
135
136       return 0
137
138
139    def load_file(self, f):
140       """
141          Load a database from file as a list of records.
142          Every record is a dictionary of key/value pairs.
143          The file is reading as whole - this is much faster, but require
144          more memory.
145       """
146
147       if type(f) == type(''): # If f is string - use it as file's name
148          infile = open(f, 'r')
149       else:
150          infile = f           # else assume it is opened file (fileobject) or
151                               # "compatible" object (must has readline() methods)
152
153       try:
154          lines = infile.readlines()
155
156       finally:
157          if type(f) == type(''): # If f was opened - close it
158             infile.close()
159
160       record = self.create_new_record()
161
162       for line in lines:
163          if self.feed(record, line):
164             record = self.create_new_record()
165
166       # Close record on EOF without empty line
167       if record:
168          self.feed(record, None)
169
170
171    def load_from_file(self, f):
172       """
173          Load a database from file as a list of records.
174          Every record is a dictionary of key/value pairs.
175          The file is reading line by line - this is much slower, but do not
176          require so much memory. (On systems with limited virtual memory,
177          like DOS, it is even faster - for big files)
178       """
179
180       if type(f) == type(''): # If f is string - use it as file's name
181          infile = open(f, 'r')
182       else:
183          infile = f           # else assume it is opened file (fileobject) or
184                               # "compatible" object (must has readline() method)
185
186       record = self.create_new_record()
187
188       try:
189          line = infile.readline()
190
191          while line:
192             if self.feed(record, line):
193                record = self.create_new_record()
194
195             line = infile.readline()
196
197       finally:
198          if type(f) == type(''): # If f was opened - close it
199             infile.close()
200
201       # Close record on EOF without empty line
202       if record:
203          self.feed(record, None)
204
205
206    def store_to_file(self, f):
207       if type(f) == type(''): # If f is string - use it as file's name
208          outfile = open(f, 'w')
209       else:
210          outfile = f          # else assume it is opened file (fileobject) or
211                               # "compatible" object (must has write() method)
212
213       flush_record = 0 # Do not close record on 1st loop
214
215       if self.comment != '':
216          outfile.write(self.comment)
217
218       for record in self:
219          copy_rec = record.copy() # Make a copy to delete keys
220
221          if flush_record:
222             outfile.write('\n') # Close record
223          else:
224             flush_record = 1    # Set flag for all but 1st record
225
226          if copy_rec:
227             for key in copy_rec.keys():
228                outfile.write(key + self.key_sep + copy_rec[key] + '\n')
229                del copy_rec[key]
230
231       if type(f) == type(''): # If f was opened - close it
232          outfile.close()
233
234
235 def load_file(f, check_record_func = None):
236    """
237       Create a database object and load it from file
238    """
239
240    db = Flad(check_record_func)
241    db.load_file(f)
242
243    return db
244
245
246 def load_from_file(f, check_record_func = None):
247    """
248       Create a database object and load it from file
249    """
250
251    db = Flad(check_record_func)
252    db.load_from_file(f)
253
254    return db