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