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