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  if type(lot).__name__ == 'SwigPyObject':
218  lot=gnucash.GncLot(instance=lot)
219  lot_str=lot.get_title()
220  else:
221  lot_str='---'
222 
223  transaction=self.GetParent()
224 
225  # This dict and the return statement can be changed according to individual needs
226  fmt_dict={
227  "account":self.GetAccount().name,
228  "value":self.GetValue(),
229  "memo":self.GetMemo(),
230  "lot":lot_str}
231 
232  fmt_str= ("Account: {account:20} "+
233  "Value: {value:>10} "+
234  "Memo: {memo:30} ")
235 
236  if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
237  fmt_t_dict={
238  "transaction_time":time.ctime(transaction.GetDate()),
239  "transaction2":transaction.GetDescription()}
240  fmt_t_str=(
241  "Transaction: {transaction_time:30} "+
242  "- {transaction2:30} "+
243  "Lot: {lot:10}")
244  fmt_dict.update(fmt_t_dict)
245  fmt_str += fmt_t_str
246 
247  return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
248 
249 # This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
250 # thereafter mutates to have better capabilities.
251 infect(gnucash.Split,__split__str__,"__str__")
252 gnucash.Split.register_optionflag("PRINT_TRANSACTION")
253 gnucash.Split.setflag("PRINT_TRANSACTION",True)
254 
255 def __transaction__str__(self):
256  """__str__ method for Transaction class"""
257  from gnucash import Transaction
258  import time
259  self=Transaction(instance=self)
260 
261  fmt_tuple=('Date:',time.ctime(self.GetDate()),
262  'Description:',self.GetDescription(),
263  'Notes:',self.GetNotes())
264 
265  transaction_str = "{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
266  *all_as_classwithcutting__format__(*fmt_tuple))
267  transaction_str += "\n"
268 
269  splits_str=""
270  for n,split in enumerate(self.GetSplitList()):
271  if not (type(split)==gnucash.Split):
272  split=gnucash.Split(instance=split)
273 
274  transaction_flag = split.getflag("PRINT_TRANSACTION")
275  split.setflag("PRINT_TRANSACTION",False)
276  splits_str += "[{0:>2}] ".format(str(n))
277  splits_str += str(split)
278  splits_str += "\n"
279  split.setflag("PRINT_TRANSACTION",transaction_flag)
280 
281  return transaction_str + splits_str
282 
283 gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
284 gnucash.Transaction.add_method("__transaction__str__","__str__")
285 
286 def __invoice__str__(self):
287  """__str__ method for Invoice"""
288 
289  from gnucash.gnucash_business import Invoice
290  self=Invoice(instance=self)
291 
292 
293  # This dict and the return statement can be changed according to individual needs
294  fmt_dict={
295  "id_name":"ID:",
296  "id_value":self.GetID(),
297  "notes_name":"Notes:",
298  "notes_value":self.GetNotes(),
299  "active_name":"Active:",
300  "active_value":str(self.GetActive()),
301  "owner_name":"Owner Name:",
302  "owner_value":self.GetOwner().GetName(),
303  "total_name":"Total:",
304  "total_value":str(self.GetTotal()),
305  "currency_mnemonic":self.GetCurrency().get_mnemonic()}
306 
307  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}"+
308  "{total_name:8}{total_value:10}{currency_mnemonic:3}").\
309  format(**all_as_classwithcutting__format__keys(**fmt_dict))
310 
311  ret_entries=""
312  entry_list = self.GetEntries()
313  for entry in entry_list: # Type of entry has to be checked
314  if not(type(entry)==Entry):
315  entry=Entry(instance=entry)
316  ret_entries += " "+str(entry)+"\n"
317 
318  return ret_invoice+"\n"+ret_entries
319 
320 
321 from gnucash.gnucash_business import Invoice
322 
323 gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
324 gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
325 
326 def __entry__str__(self):
327  """__str__ method for Entry"""
328 
329  from gnucash.gnucash_business import Entry
330  self=Entry(instance=self)
331 
332  # This dict and the return statement can be changed according to individual needs
333  fmt_dict={
334  "date_name":"Date:",
335  "date_value":str(self.GetDate()),
336  "description_name":"Description:",
337  "description_value":self.GetDescription(),
338  "notes_name":"Notes:",
339  "notes_value":self.GetNotes(),
340  "quant_name":"Quantity:",
341  "quant_value":str(self.GetQuantity()),
342  "invprice_name":"InvPrice:",
343  "invprice_value":str(self.GetInvPrice())}
344 
345  return ("{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
346  "{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
347  format(**all_as_classwithcutting__format__keys(**fmt_dict))
348 
349 from gnucash.gnucash_business import Entry
350 
351 gnucash.gnucash_core_c.__entry__str__=__entry__str__
352 gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")