GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
str_methods.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 ## @file
3 # @brief Add __str__ and __unicode__ methods to financial objects so that @code print object @endcode leads to human readable results
4 """ @package str_methods.py -- Add __str__ and __unicode__ methods to financial objects
5 
6  Import this module and str(Object) and unicode(Object) where Object is Transaction, Split,Invoice
7  or Entry leads to human readable results. That is handy when using @code print object @endcode
8 
9  I chose to put these functions/methods in a seperate 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, [email protected]
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 
33 import gnucash, function_class
34 
35 # Default values for encoding of strings in GnuCashs Database
36 DEFAULT_ENCODING = "UTF-8"
37 DEFAULT_ERROR = "ignore"
38 
39 def setflag(self, name, value):
40  if not(name in self.OPTIONFLAGS_BY_NAME):
41  self.register_optionflag(name)
42  if value == True:
43  self.optionflags |= self.OPTIONFLAGS_BY_NAME[name]
44  else:
45  self.optionflags &= ~self.OPTIONFLAGS_BY_NAME[name]
46 
47 def getflag(self, name):
48  if not(name in self.OPTIONFLAGS_BY_NAME):
49  raise KeyError(str(name)+" is not a registered key.")
50  return ((self.optionflags & self.OPTIONFLAGS_BY_NAME[name]) != 0)
51 
52 def register_optionflag(self,name):
53  """Taken from doctest.py"""
54  # Create a new flag unless `name` is already known.
55  return self.OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(self.OPTIONFLAGS_BY_NAME))
56 
57 def ya_add_method(_class, function, method_name=None, clsmethod=False, noinstance=False):
58  """Calls add_method from function_methods.py but makes it
59  possible to use functions in this module. Also keeps the
60  docstring"""
61 
62  if method_name == None:
63  method_name = function.__name__
64 
65  setattr(gnucash.gnucash_core_c,function.__name__,function)
66  if clsmethod:
67  mf=_class.ya_add_classmethod(function.__name__,method_name)
68  elif noinstance:
69  mf=_class.add_method(function.__name__,method_name)
70  else:
71  mf=_class.ya_add_method(function.__name__,method_name)
72  if function.__doc__ != None:
73  setattr(mf, "__doc__", function.__doc__)
74 
75 def infect(_class, function, method_name):
76  if not getattr(_class, "OPTIONFLAGS_BY_NAME", None):
77  _class.OPTIONFLAGS_BY_NAME={}
78  _class.optionflags=0
79  ya_add_method(_class,register_optionflag,clsmethod=True)
80  ya_add_method(_class,setflag,clsmethod=True)
81  ya_add_method(_class,getflag,clsmethod=True)
82  ya_add_method(_class, function, method_name)
83 
85  """This class provides a __format__ method which cuts values to a certain width.
86 
87  If width is too big '...' will be put at the end of the resulting string."""
88 
89  def __init__(self,value):
90  self.value = value
91 
92  def __format__(self, fmt):
93  def get_width(fmt_spec):
94  """Parse fmt_spec to obtain width"""
95 
96  def remove_alignment(fmt_spec):
97  if fmt_spec[1] in ["<","^",">"]:
98  fmt_spec=fmt_spec[2:len(fmt_spec)]
99  return fmt_spec
100 
101  def remove_sign(fmt_spec):
102  if fmt_spec[0] in ["-","+"," "]:
103  fmt_spec=fmt_spec[1:len(fmt_spec)]
104  return fmt_spec
105 
106  def remove_cross(fmt_spec):
107  if fmt_spec[0] in ["#"]:
108  fmt_spec=fmt_spec[1:len(fmt_spec)]
109  return fmt_spec
110 
111  def do_width(fmt_spec):
112  n=""
113 
114  while len(fmt_spec)>0:
115  if fmt_spec[0].isdigit():
116  n+=fmt_spec[0]
117  fmt_spec=fmt_spec[1:len(fmt_spec)]
118  else:
119  break
120  if n:
121  return int(n)
122  else:
123  return None
124 
125  if len(fmt_spec)>=2:
126  fmt_spec=remove_alignment(fmt_spec)
127  if len(fmt_spec)>=1:
128  fmt_spec=remove_sign(fmt_spec)
129  if len(fmt_spec)>=1:
130  fmt_spec=remove_cross(fmt_spec)
131  width=do_width(fmt_spec)
132  # Stop parsing here for we only need width
133 
134  return width
135 
136  def cut(s, width, replace_string="..."):
137  """Cuts s to width and puts replace_string at it's end."""
138 
139  #s=s.decode('UTF-8', "replace")
140 
141  if len(s)>width:
142  if len(replace_string)>width:
143  replace_string=replace_string[0:width]
144  s=s[0:width-len(replace_string)]
145  s=s+replace_string
146 
147  return s
148 
149  value=self.value
150 
151  # Replace Tabs and linebreaks
152  import types
153  if type(value) in [types.StringType, types.UnicodeType]:
154  value=value.replace("\t","|")
155  value=value.replace("\n","|")
156 
157  # Do regular formatting of object
158  value=value.__format__(fmt)
159 
160  # Cut resulting value if longer than specified by width
161  width=get_width(fmt)
162  if width:
163  value=cut(value, width, "...")
164 
165  return value
166 
167 def all_as_classwithcutting__format__(*args):
168  """Converts every argument to instance of ClassWithCutting__format__"""
169 
170  import types
171  l=[]
172  for a in args:
173  if type(a) in [types.StringType, types.UnicodeType]:
174  a=a.decode("UTF-8")
175  l.append(ClassWithCutting__format__(a))
176 
177  return l
178 
179 def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
180  """Converts every argument to instance of ClassWithCutting__format__"""
181 
182  import types
183  d={}
184  if encoding==None:
185  encoding=DEFAULT_ENCODING
186  if error==None:
187  error=DEFAULT_ERROR
188  for a in keys:
189  if type(keys[a]) in [types.StringType, types.UnicodeType]:
190  keys[a]=keys[a].decode(encoding,error)
191  d[a]=ClassWithCutting__format__(keys[a])
192 
193  return d
194 
195 
196 
197 # Split
198 def __split__unicode__(self, encoding=None, error=None):
199  """__unicode__(self, encoding=None, error=None) -> object
200 
201  Serialize the Split object and return as a new Unicode object.
202 
203  Keyword arguments:
204  encoding -- defaults to str_methods.default_encoding
205  error -- defaults to str_methods.default_error
206  See help(unicode) for more details or http://docs.python.org/howto/unicode.html.
207 
208  """
209 
210  from gnucash import Split
211  import time
212  #self=Split(instance=self)
213 
214  lot=self.GetLot()
215  if lot:
216  if type(lot).__name__ == 'SwigPyObject':
217  lot=gnucash.GncLot(instance=lot)
218  lot_str=lot.get_title()
219  else:
220  lot_str='---'
221 
222  transaction=self.GetParent()
223 
224  # This dict and the return statement can be changed according to individual needs
225  fmt_dict={
226  "account":self.GetAccount().name,
227  "value":self.GetValue(),
228  "memo":self.GetMemo(),
229  "lot":lot_str}
230 
231  fmt_str= (u"Account: {account:20} "+
232  u"Value: {value:>10} "+
233  u"Memo: {memo:30} ")
234 
235  if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
236  fmt_t_dict={
237  "transaction_time":time.ctime(transaction.GetDate()),
238  "transaction2":transaction.GetDescription()}
239  fmt_t_str=(
240  u"Transaction: {transaction_time:30} "+
241  u"- {transaction2:30} "+
242  u"Lot: {lot:10}")
243  fmt_dict.update(fmt_t_dict)
244  fmt_str += fmt_t_str
245 
246  return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
247 
248 def __split__str__(self):
249  """Returns a bytestring representation of self.__unicode__"""
250 
251  from gnucash import Split
252  #self=Split(instance=self)
253 
254  return unicode(self).encode('utf-8')
255 
256 # This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
257 # thereafter mutates to have better capabilities.
258 infect(gnucash.Split,__split__str__,"__str__")
259 infect(gnucash.Split,__split__unicode__,"__unicode__")
260 gnucash.Split.register_optionflag("PRINT_TRANSACTION")
261 gnucash.Split.setflag("PRINT_TRANSACTION",True)
262 
263 def __transaction__unicode__(self):
264  """__unicode__ method for Transaction class"""
265  from gnucash import Transaction
266  import time
267  self=Transaction(instance=self)
268 
269  fmt_tuple=('Date:',time.ctime(self.GetDate()),
270  'Description:',self.GetDescription(),
271  'Notes:',self.GetNotes())
272 
273  transaction_str = u"{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
274  *all_as_classwithcutting__format__(*fmt_tuple))
275  transaction_str += "\n"
276 
277  splits_str=""
278  for n,split in enumerate(self.GetSplitList()):
279  if not (type(split)==gnucash.Split):
280  split=gnucash.Split(instance=split)
281 
282  transaction_flag = split.getflag("PRINT_TRANSACTION")
283  split.setflag("PRINT_TRANSACTION",False)
284  splits_str += u"[{0:>2}] ".format(unicode(n))
285  splits_str += unicode(split)
286  splits_str += "\n"
287  split.setflag("PRINT_TRANSACTION",transaction_flag)
288 
289  return transaction_str + splits_str
290 
291 def __transaction__str__(self):
292  """__str__ method for Transaction class"""
293  from gnucash import Transaction
294 
295  self=Transaction(instance=self)
296  return unicode(self).encode('utf-8')
297 
298 # These lines add transaction_str as method __str__ to Transaction object
299 gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
300 gnucash.Transaction.add_method("__transaction__str__","__str__")
301 
302 gnucash.gnucash_core_c.__transaction__unicode__=__transaction__unicode__
303 gnucash.Transaction.add_method("__transaction__unicode__","__unicode__")
304 
305 def __invoice__unicode__(self):
306  """__unicode__ method for Invoice"""
307 
308  from gnucash.gnucash_business import Invoice
309  self=Invoice(instance=self)
310 
311 
312  # This dict and the return statement can be changed according to individual needs
313  fmt_dict={
314  "id_name":"ID:",
315  "id_value":self.GetID(),
316  "notes_name":"Notes:",
317  "notes_value":self.GetNotes(),
318  "active_name":"Active:",
319  "active_value":str(self.GetActive()),
320  "owner_name":"Owner Name:",
321  "owner_value":self.GetOwner().GetName(),
322  "total_name":"Total:",
323  "total_value":str(self.GetTotal()),
324  "currency_mnemonic":self.GetCurrency().get_mnemonic()}
325 
326  ret_invoice= (u"{id_name:4}{id_value:10} {notes_name:7}{notes_value:20} {active_name:8}{active_value:7} {owner_name:12}{owner_value:20}"+
327  u"{total_name:8}{total_value:10}{currency_mnemonic:3}").\
328  format(**all_as_classwithcutting__format__keys(**fmt_dict))
329 
330  ret_entries=u""
331  entry_list = self.GetEntries()
332  for entry in entry_list: # Type of entry has to be checked
333  if not(type(entry)==Entry):
334  entry=Entry(instance=entry)
335  ret_entries += " "+unicode(entry)+"\n"
336 
337  return ret_invoice+"\n"+ret_entries
338 
339 def __invoice__str__(self):
340  """__str__ method for invoice class"""
341 
342  from gnucash.gnucash_business import Invoice
343  self=Invoice(instance=self)
344 
345  return unicode(self).encode('utf-8')
346 
347 from gnucash.gnucash_business import Invoice
348 
349 gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
350 gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
351 
352 gnucash.gnucash_core_c.__invoice__unicode__=__invoice__unicode__
353 gnucash.gnucash_business.Invoice.add_method("__invoice__unicode__","__unicode__")
354 
355 def __entry__unicode__(self):
356  """__unicode__ method for Entry"""
357 
358  from gnucash.gnucash_business import Entry
359  self=Entry(instance=self)
360 
361  # This dict and the return statement can be changed according to individual needs
362  fmt_dict={
363  "date_name":"Date:",
364  "date_value":unicode(self.GetDate()),
365  "description_name":"Description:",
366  "description_value":self.GetDescription(),
367  "notes_name":"Notes:",
368  "notes_value":self.GetNotes(),
369  "quant_name":"Quantity:",
370  "quant_value":unicode(self.GetQuantity()),
371  "invprice_name":"InvPrice:",
372  "invprice_value":unicode(self.GetInvPrice())}
373 
374  return (u"{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
375  u"{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
376  format(**all_as_classwithcutting__format__keys(**fmt_dict))
377 
378 def __entry__str__(self):
379  """__str__ method for Entry class"""
380 
381  from gnucash.gnucash_business import Entry
382  self=Entry(instance=self)
383 
384  return unicode(self).encode('utf-8')
385 
386 from gnucash.gnucash_business import Entry
387 
388 gnucash.gnucash_core_c.__entry__str__=__entry__str__
389 gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")
390 
391 gnucash.gnucash_core_c.__entry__unicode__=__entry__unicode__
392 gnucash.gnucash_business.Entry.add_method("__entry__unicode__","__unicode__")
393 
394