GnuCash  5.6-150-g038405b370+
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 
33 import gnucash
34 from gnucash import function_class
35 
36 # Default values for encoding of strings in GnuCashs Database
37 DEFAULT_ENCODING = "UTF-8"
38 DEFAULT_ERROR = "ignore"
39 
40 def 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 
48 def 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 
53 def 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 
58 def 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 
76 def 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 
168 def 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 
180 def 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
199 def __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.
249 infect(gnucash.Split,__split__str__,"__str__")
250 gnucash.Split.register_optionflag("PRINT_TRANSACTION")
251 gnucash.Split.setflag("PRINT_TRANSACTION",True)
252 
253 def __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 
281 gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
282 gnucash.Transaction.add_method("__transaction__str__","__str__")
283 
284 def __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 
319 from gnucash.gnucash_business import Invoice
320 
321 gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
322 gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
323 
324 def __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 
347 from gnucash.gnucash_business import Entry
348 
349 gnucash.gnucash_core_c.__entry__str__=__entry__str__
350 gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")