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