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