GnuCash c935c2f+
Loading...
Searching...
No Matches
str_methods.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
4""" @package str_methods.py -- Add __str__ methods to financial objects
5
6 Import this module and str(Object) where Object is Transaction, Split, Invoice or Entry leads to
7 human readable results. That is handy when using @code print object @endcode
8
9 I chose to put these functions/methods in a separate file to develop them like this and maybe if
10 they prove to be useful they can be put in gnucash_core.py.
11
12 I am searching to find the best way to serialize these complex objects. Ideally this serialization
13 would be configurable.
14
15 If someone has suggestions how to beautify, purify or improve this code in any way please feel
16 free to do so.
17
18 This is written as a first approach to a shell-environment using ipython to interactively manipulate
19 GnuCashs Data."""
20
21# @author Christoph Holtermann, c.holtermann@gmx.de
22# @ingroup python_bindings_examples
23# @date May 2011
24#
25# ToDo :
26#
27# * Testing for SWIGtypes
28# * dealing the cutting format in a bit more elegant way
29# * having setflag as a classmethod makes it probably impossible to have flags on instance level. Would changing that be useful ?
30# * It seems useful to have an object for each modification. That is because there is some Initialisation to be done.
31#
32
33import gnucash
34from gnucash import function_class
35
36# Default values for encoding of strings in GnuCashs Database
37DEFAULT_ENCODING = "UTF-8"
38DEFAULT_ERROR = "ignore"
39
40def setflag(self, name, value):
41 if not(name in self.OPTIONFLAGS_BY_NAME):
42 self.register_optionflag(name)
43 if value == True:
44 self.optionflags |= self.OPTIONFLAGS_BY_NAME[name]
45 else:
46 self.optionflags &= ~self.OPTIONFLAGS_BY_NAME[name]
47
48def getflag(self, name):
49 if not(name in self.OPTIONFLAGS_BY_NAME):
50 raise KeyError(str(name)+" is not a registered key.")
51 return ((self.optionflags & self.OPTIONFLAGS_BY_NAME[name]) != 0)
52
53def register_optionflag(self,name):
54 """Taken from doctest.py"""
55 # Create a new flag unless `name` is already known.
56 return self.OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(self.OPTIONFLAGS_BY_NAME))
57
58def ya_add_method(_class, function, method_name=None, clsmethod=False, noinstance=False):
59 """Calls add_method from function_methods.py but makes it
60 possible to use functions in this module. Also keeps the
61 docstring"""
62
63 if method_name == None:
64 method_name = function.__name__
65
66 setattr(gnucash.gnucash_core_c,function.__name__,function)
67 if clsmethod:
68 mf=_class.ya_add_classmethod(function.__name__,method_name)
69 elif noinstance:
70 mf=_class.add_method(function.__name__,method_name)
71 else:
72 mf=_class.ya_add_method(function.__name__,method_name)
73 if function.__doc__ != None:
74 setattr(mf, "__doc__", function.__doc__)
75
76def infect(_class, function, method_name):
77 if not getattr(_class, "OPTIONFLAGS_BY_NAME", None):
78 _class.OPTIONFLAGS_BY_NAME={}
79 _class.optionflags=0
80 ya_add_method(_class,register_optionflag,clsmethod=True)
81 ya_add_method(_class,setflag,clsmethod=True)
82 ya_add_method(_class,getflag,clsmethod=True)
83 ya_add_method(_class, function, method_name)
84
86 """This class provides a __format__ method which cuts values to a certain width.
87
88 If width is too big '...' will be put at the end of the resulting string."""
89
90 def __init__(self,value):
91 self.value = value
92
93 def __format__(self, fmt):
94 def get_width(fmt_spec):
95 """Parse fmt_spec to obtain width"""
96
97 def remove_alignment(fmt_spec):
98 if fmt_spec[1] in ["<","^",">"]:
99 fmt_spec=fmt_spec[2:len(fmt_spec)]
100 return fmt_spec
101
102 def remove_sign(fmt_spec):
103 if fmt_spec[0] in ["-","+"," "]:
104 fmt_spec=fmt_spec[1:len(fmt_spec)]
105 return fmt_spec
106
107 def remove_cross(fmt_spec):
108 if fmt_spec[0] in ["#"]:
109 fmt_spec=fmt_spec[1:len(fmt_spec)]
110 return fmt_spec
111
112 def do_width(fmt_spec):
113 n=""
114
115 while len(fmt_spec)>0:
116 if fmt_spec[0].isdigit():
117 n+=fmt_spec[0]
118 fmt_spec=fmt_spec[1:len(fmt_spec)]
119 else:
120 break
121 if n:
122 return int(n)
123 else:
124 return None
125
126 if len(fmt_spec)>=2:
127 fmt_spec=remove_alignment(fmt_spec)
128 if len(fmt_spec)>=1:
129 fmt_spec=remove_sign(fmt_spec)
130 if len(fmt_spec)>=1:
131 fmt_spec=remove_cross(fmt_spec)
132 width=do_width(fmt_spec)
133 # Stop parsing here for we only need width
134
135 return width
136
137 def cut(s, width, replace_string="..."):
138 """Cuts s to width and puts replace_string at it's end."""
139
140 #s=s.decode('UTF-8', "replace")
141
142 if len(s)>width:
143 if len(replace_string)>width:
144 replace_string=replace_string[0:width]
145 s=s[0:width-len(replace_string)]
146 s=s+replace_string
147
148 return s
149
150 value=self.value
151
152 # Replace Tabs and linebreaks
153 #import types
154 if isinstance(value, str):
155 value = value.replace("\t","|")
156 value = value.replace("\n","|")
157
158 # Do regular formatting of object
159 value = value.__format__(fmt)
160
161 # Cut resulting value if longer than specified by width
162 width = get_width(fmt)
163 if width:
164 value = cut(value, width, "...")
165
166 return value
167
168def all_as_classwithcutting__format__(*args):
169 """Converts every argument to instance of ClassWithCutting__format__"""
170
171 #import types
172 l=[]
173 for a in args:
174 #if type(a) in [types.StringType, types.UnicodeType]:
175 # a=a.decode("UTF-8")
176 l.append(ClassWithCutting__format__(a))
177
178 return l
179
180def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
181 """Converts every argument to instance of ClassWithCutting__format__"""
182
183 #import types
184 d={}
185 if encoding==None:
186 encoding=DEFAULT_ENCODING
187 if error==None:
188 error=DEFAULT_ERROR
189 for a in keys:
190 #if isinstance(keys[a], str):
191 # keys[a]=keys[a].decode(encoding,error)
192 d[a]=ClassWithCutting__format__(keys[a])
193
194 return d
195
196
197
198# Split
199def __split__str__(self, encoding=None, error=None):
200 """__str__(self, encoding=None, error=None) -> object
201
202 Serialize the Split object and return as a new Unicode object.
203
204 Keyword arguments:
205 encoding -- defaults to str_methods.default_encoding
206 error -- defaults to str_methods.default_error
207 See help(unicode) for more details or http://docs.python.org/howto/unicode.html.
208
209 """
210
211 from gnucash import Split
212 import time
213 #self=Split(instance=self)
214
215 lot=self.GetLot()
216 if lot:
217 lot_str=lot.get_title()
218 else:
219 lot_str='---'
220
221 transaction=self.GetParent()
222
223 # This dict and the return statement can be changed according to individual needs
224 fmt_dict={
225 "account":self.GetAccount().name,
226 "value":self.GetValue(),
227 "memo":self.GetMemo(),
228 "lot":lot_str}
229
230 fmt_str= ("Account: {account:20} "+
231 "Value: {value:>10} "+
232 "Memo: {memo:30} ")
233
234 if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
235 fmt_t_dict={
236 "transaction_time":time.ctime(transaction.GetDate()),
237 "transaction2":transaction.GetDescription()}
238 fmt_t_str=(
239 "Transaction: {transaction_time:30} "+
240 "- {transaction2:30} "+
241 "Lot: {lot:10}")
242 fmt_dict.update(fmt_t_dict)
243 fmt_str += fmt_t_str
244
245 return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
246
247# This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
248# thereafter mutates to have better capabilities.
249infect(gnucash.Split,__split__str__,"__str__")
250gnucash.Split.register_optionflag("PRINT_TRANSACTION")
251gnucash.Split.setflag("PRINT_TRANSACTION",True)
252
253def __transaction__str__(self):
254 """__str__ method for Transaction class"""
255 from gnucash import Transaction
256 import time
257 self=Transaction(instance=self)
258
259 fmt_tuple=('Date:',time.ctime(self.GetDate()),
260 'Description:',self.GetDescription(),
261 'Notes:',self.GetNotes())
262
263 transaction_str = "{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
264 *all_as_classwithcutting__format__(*fmt_tuple))
265 transaction_str += "\n"
266
267 splits_str=""
268 for n,split in enumerate(self.GetSplitList()):
269 if not (type(split)==gnucash.Split):
270 split=gnucash.Split(instance=split)
271
272 transaction_flag = split.getflag("PRINT_TRANSACTION")
273 split.setflag("PRINT_TRANSACTION",False)
274 splits_str += "[{0:>2}] ".format(str(n))
275 splits_str += str(split)
276 splits_str += "\n"
277 split.setflag("PRINT_TRANSACTION",transaction_flag)
278
279 return transaction_str + splits_str
280
281gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
282gnucash.Transaction.add_method("__transaction__str__","__str__")
283
284def __invoice__str__(self):
285 """__str__ method for Invoice"""
286
287 from gnucash.gnucash_business import Invoice
288 self=Invoice(instance=self)
289
290
291 # This dict and the return statement can be changed according to individual needs
292 fmt_dict={
293 "id_name":"ID:",
294 "id_value":self.GetID(),
295 "notes_name":"Notes:",
296 "notes_value":self.GetNotes(),
297 "active_name":"Active:",
298 "active_value":str(self.GetActive()),
299 "owner_name":"Owner Name:",
300 "owner_value":self.GetOwner().GetName(),
301 "total_name":"Total:",
302 "total_value":str(self.GetTotal()),
303 "currency_mnemonic":self.GetCurrency().get_mnemonic()}
304
305 ret_invoice= ("{id_name:4}{id_value:10} {notes_name:7}{notes_value:20} {active_name:8}{active_value:7} {owner_name:12}{owner_value:20}"+
306 "{total_name:8}{total_value:10}{currency_mnemonic:3}").\
307 format(**all_as_classwithcutting__format__keys(**fmt_dict))
308
309 ret_entries=""
310 entry_list = self.GetEntries()
311 for entry in entry_list: # Type of entry has to be checked
312 if not(type(entry)==Entry):
313 entry=Entry(instance=entry)
314 ret_entries += " "+str(entry)+"\n"
315
316 return ret_invoice+"\n"+ret_entries
317
318
319from gnucash.gnucash_business import Invoice
320
321gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
322gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
323
324def __entry__str__(self):
325 """__str__ method for Entry"""
326
327 from gnucash.gnucash_business import Entry
328 self=Entry(instance=self)
329
330 # This dict and the return statement can be changed according to individual needs
331 fmt_dict={
332 "date_name":"Date:",
333 "date_value":str(self.GetDate()),
334 "description_name":"Description:",
335 "description_value":self.GetDescription(),
336 "notes_name":"Notes:",
337 "notes_value":self.GetNotes(),
338 "quant_name":"Quantity:",
339 "quant_value":str(self.GetQuantity()),
340 "invprice_name":"InvPrice:",
341 "invprice_value":str(self.GetInvPrice())}
342
343 return ("{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
344 "{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
345 format(**all_as_classwithcutting__format__keys(**fmt_dict))
346
347from gnucash.gnucash_business import Entry
348
349gnucash.gnucash_core_c.__entry__str__=__entry__str__
350gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")