GnuCash 2.4.99
str_methods.py
00001 #!/usr/bin/env python
00002 ## @file @brief Add __str__ and __unicode__ methods to financial objects so that @code print object @endcode leads to human readable results
00003 """ @package str_methods.py -- Add __str__ and __unicode__ methods to financial objects
00004 
00005    Import this module and str(Object) and unicode(Object) where Object is Transaction, Split,Invoice
00006    or Entry leads to human readable results. That is handy when using @code print object @endcode
00007 
00008    I chose to put these functions/methods in a seperate file to develop them like this and maybe if
00009    they prove to be useful they can be put in gnucash_core.py.
00010 
00011    I am searching to find the best way to serialize these complex objects. Ideally this serialization
00012    would be configurable.
00013 
00014    If someone has suggestions how to beautify, purify or improve this code in any way please feel
00015    free to do so.
00016 
00017    This is written as a first approach to a shell-environment using ipython to interactively manipulate
00018    GnuCashs Data."""
00019 
00020 #   @author Christoph Holtermann, c.holtermann@gmx.de
00021 #   @ingroup python_bindings_examples
00022 #   @date May 2011
00023 #   
00024 #   ToDo :
00025 #
00026 #   * Testing for SWIGtypes
00027 #   * dealing the cutting format in a bit more elegant way
00028 #   * having setflag as a classmethod makes it probably impossible to have flags on instance level. Would changing that be useful ?
00029 #   * It seems useful to have an object for each modification. That is because there is some Initialisation to be done.
00030 #
00031 
00032 import gnucash, function_class
00033 
00034 # Default values for encoding of strings in GnuCashs Database
00035 DEFAULT_ENCODING = "UTF-8"
00036 DEFAULT_ERROR = "ignore"
00037 
00038 def setflag(self, name, value):
00039     if not(name in self.OPTIONFLAGS_BY_NAME):
00040       self.register_optionflag(name)
00041     if value == True:
00042       self.optionflags |= self.OPTIONFLAGS_BY_NAME[name]
00043     else:
00044       self.optionflags &= ~self.OPTIONFLAGS_BY_NAME[name]
00045 
00046 def getflag(self, name):
00047     if not(name in self.OPTIONFLAGS_BY_NAME):
00048       raise KeyError(str(name)+" is not a registered key.")
00049     return ((self.optionflags & self.OPTIONFLAGS_BY_NAME[name]) != 0)
00050 
00051 def register_optionflag(self,name):
00052     """Taken from doctest.py"""
00053     # Create a new flag unless `name` is already known.
00054     return self.OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(self.OPTIONFLAGS_BY_NAME))
00055 
00056 def ya_add_method(_class, function, method_name=None, clsmethod=False, noinstance=False):
00057     """Calls add_method from function_methods.py but makes it
00058     possible to use functions in this module. Also keeps the
00059     docstring"""
00060 
00061     if method_name == None:
00062       method_name = function.__name__
00063     
00064     setattr(gnucash.gnucash_core_c,function.__name__,function)
00065     if clsmethod:
00066       mf=_class.ya_add_classmethod(function.__name__,method_name)
00067     elif noinstance: 
00068       mf=_class.add_method(function.__name__,method_name)
00069     else:
00070       mf=_class.ya_add_method(function.__name__,method_name)
00071     if function.__doc__ != None:
00072       setattr(mf, "__doc__", function.__doc__)
00073 
00074 def infect(_class, function, method_name):
00075     if not getattr(_class, "OPTIONFLAGS_BY_NAME", None):    
00076       _class.OPTIONFLAGS_BY_NAME={}
00077       _class.optionflags=0
00078       ya_add_method(_class,register_optionflag,clsmethod=True)
00079       ya_add_method(_class,setflag,clsmethod=True)
00080       ya_add_method(_class,getflag,clsmethod=True)
00081     ya_add_method(_class, function, method_name)
00082 
00083 class ClassWithCutting__format__():
00084     """This class provides a __format__ method which cuts values to a certain width.
00085     
00086     If width is too big '...' will be put at the end of the resulting string."""
00087 
00088     def __init__(self,value):
00089       self.value = value
00090 
00091     def __format__(self, fmt):
00092         def get_width(fmt_spec):
00093             """Parse fmt_spec to obtain width"""
00094 
00095             def remove_alignment(fmt_spec):
00096                 if fmt_spec[1] in ["<","^",">"]:
00097                     fmt_spec=fmt_spec[2:len(fmt_spec)]
00098                 return fmt_spec
00099 
00100             def remove_sign(fmt_spec):
00101                 if fmt_spec[0] in ["-","+"," "]:
00102                     fmt_spec=fmt_spec[1:len(fmt_spec)]
00103                 return fmt_spec
00104 
00105             def remove_cross(fmt_spec):
00106                 if fmt_spec[0] in ["#"]:
00107                     fmt_spec=fmt_spec[1:len(fmt_spec)]
00108                 return fmt_spec
00109 
00110             def do_width(fmt_spec):
00111                 n=""
00112                 
00113                 while len(fmt_spec)>0:
00114                     if fmt_spec[0].isdigit():
00115                       n+=fmt_spec[0]
00116                       fmt_spec=fmt_spec[1:len(fmt_spec)]
00117                     else:
00118                         break
00119                 if n:
00120                     return int(n)
00121                 else:
00122                     return None
00123 
00124             if len(fmt_spec)>=2:
00125                 fmt_spec=remove_alignment(fmt_spec)
00126             if len(fmt_spec)>=1:
00127                fmt_spec=remove_sign(fmt_spec)
00128             if len(fmt_spec)>=1:
00129                 fmt_spec=remove_cross(fmt_spec)
00130             width=do_width(fmt_spec)
00131             # Stop parsing here for we only need width
00132 
00133             return width
00134 
00135         def cut(s, width, replace_string="..."):
00136             """Cuts s to width and puts replace_string at it's end."""
00137             
00138             #s=s.decode('UTF-8', "replace")
00139             
00140             if len(s)>width:
00141                 if len(replace_string)>width:
00142                     replace_string=replace_string[0:width]
00143                 s=s[0:width-len(replace_string)]
00144                 s=s+replace_string
00145             
00146             return s
00147      
00148         value=self.value
00149 
00150         # Replace Tabs and linebreaks
00151         import types
00152         if type(value) in [types.StringType, types.UnicodeType]:
00153             value=value.replace("\t","|")
00154             value=value.replace("\n","|")
00155         
00156         # Do regular formatting of object 
00157         value=value.__format__(fmt)
00158 
00159         # Cut resulting value if longer than specified by width
00160         width=get_width(fmt)
00161         if width:
00162             value=cut(value, width, "...")
00163 
00164         return value
00165 
00166 def all_as_classwithcutting__format__(*args):
00167     """Converts every argument to instance of ClassWithCutting__format__"""
00168 
00169     import types
00170     l=[]
00171     for a in args:
00172         if type(a) in [types.StringType, types.UnicodeType]:
00173           a=a.decode("UTF-8")
00174         l.append(ClassWithCutting__format__(a))
00175 
00176     return l
00177 
00178 def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
00179     """Converts every argument to instance of ClassWithCutting__format__"""
00180 
00181     import types
00182     d={}
00183     if encoding==None:
00184       encoding=DEFAULT_ENCODING
00185     if error==None:
00186       error=DEFAULT_ERROR
00187     for a in keys:
00188         if type(keys[a]) in [types.StringType, types.UnicodeType]:
00189           keys[a]=keys[a].decode(encoding,error)
00190         d[a]=ClassWithCutting__format__(keys[a])
00191 
00192     return d
00193 
00194 
00195 
00196 # Split
00197 def __split__unicode__(self, encoding=None, error=None):
00198     """__unicode__(self, encoding=None, error=None) -> object
00199     
00200     Serialize the Split object and return as a new Unicode object.
00201     
00202     Keyword arguments:
00203     encoding -- defaults to str_methods.default_encoding
00204     error -- defaults to str_methods.default_error
00205     See help(unicode) for more details or http://docs.python.org/howto/unicode.html.
00206 
00207     """
00208 
00209     from gnucash import Split
00210     import time
00211     #self=Split(instance=self)
00212 
00213     lot=self.GetLot()
00214     if lot:
00215         if type(lot).__name__ == 'SwigPyObject':  
00216           lot=gnucash.GncLot(instance=lot)
00217         lot_str=lot.get_title()
00218     else:
00219         lot_str='---'
00220 
00221     transaction=self.GetParent()
00222    
00223     # This dict and the return statement can be changed according to individual needs 
00224     fmt_dict={
00225         "account":self.GetAccount().name,
00226         "value":self.GetValue(),
00227         "memo":self.GetMemo(),
00228         "lot":lot_str}
00229         
00230     fmt_str= (u"Account: {account:20} "+
00231             u"Value: {value:>10} "+
00232             u"Memo: {memo:30} ")
00233     
00234     if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
00235         fmt_t_dict={      
00236             "transaction_time":time.ctime(transaction.GetDate()),
00237             "transaction2":transaction.GetDescription()}
00238         fmt_t_str=(
00239             u"Transaction: {transaction_time:30} "+
00240             u"- {transaction2:30} "+
00241             u"Lot: {lot:10}")
00242         fmt_dict.update(fmt_t_dict)
00243         fmt_str += fmt_t_str 
00244                 
00245     return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
00246 
00247 def __split__str__(self):
00248     """Returns a bytestring representation of self.__unicode__"""
00249     
00250     from gnucash import Split
00251     #self=Split(instance=self)
00252 
00253     return unicode(self).encode('utf-8')
00254 
00255 # This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
00256 # thereafter mutates to have better capabilities.
00257 infect(gnucash.Split,__split__str__,"__str__")
00258 infect(gnucash.Split,__split__unicode__,"__unicode__")
00259 gnucash.Split.register_optionflag("PRINT_TRANSACTION")
00260 gnucash.Split.setflag("PRINT_TRANSACTION",True)
00261 
00262 def __transaction__unicode__(self):
00263     """__unicode__ method for Transaction class"""
00264     from gnucash import Transaction
00265     import time
00266     self=Transaction(instance=self)
00267 
00268     fmt_tuple=('Date:',time.ctime(self.GetDate()),
00269           'Description:',self.GetDescription(),
00270           'Notes:',self.GetNotes())
00271 
00272     transaction_str = u"{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
00273           *all_as_classwithcutting__format__(*fmt_tuple))
00274     transaction_str += "\n"
00275 
00276     splits_str=""
00277     for n,split in enumerate(self.GetSplitList()):
00278         if not (type(split)==gnucash.Split):
00279             split=gnucash.Split(instance=split)
00280 
00281         transaction_flag = split.getflag("PRINT_TRANSACTION")
00282         split.setflag("PRINT_TRANSACTION",False)
00283         splits_str += u"[{0:>2}] ".format(unicode(n))
00284         splits_str += unicode(split)
00285         splits_str += "\n"
00286         split.setflag("PRINT_TRANSACTION",transaction_flag)
00287 
00288     return transaction_str + splits_str
00289 
00290 def __transaction__str__(self):
00291     """__str__ method for Transaction class"""
00292     from gnucash import Transaction
00293 
00294     self=Transaction(instance=self)
00295     return unicode(self).encode('utf-8')
00296 
00297 # These lines add transaction_str as method __str__ to Transaction object
00298 gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
00299 gnucash.Transaction.add_method("__transaction__str__","__str__")
00300 
00301 gnucash.gnucash_core_c.__transaction__unicode__=__transaction__unicode__
00302 gnucash.Transaction.add_method("__transaction__unicode__","__unicode__")
00303 
00304 def __invoice__unicode__(self):
00305     """__unicode__ method for Invoice"""
00306 
00307     from gnucash.gnucash_business import Invoice
00308     self=Invoice(instance=self)
00309 
00310    
00311     # This dict and the return statement can be changed according to individual needs 
00312     fmt_dict={
00313         "id_name":"ID:",
00314         "id_value":self.GetID(),
00315         "notes_name":"Notes:",
00316         "notes_value":self.GetNotes(),
00317         "active_name":"Active:",
00318         "active_value":str(self.GetActive()),
00319         "owner_name":"Owner Name:",
00320         "owner_value":self.GetOwner().GetName(),
00321         "total_name":"Total:",
00322         "total_value":str(self.GetTotal()),
00323         "currency_mnemonic":self.GetCurrency().get_mnemonic()}
00324 
00325     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}"+
00326                   u"{total_name:8}{total_value:10}{currency_mnemonic:3}").\
00327                     format(**all_as_classwithcutting__format__keys(**fmt_dict))
00328 
00329     ret_entries=u""
00330     entry_list = self.GetEntries()
00331     for entry in entry_list: # Type of entry has to be checked
00332       if not(type(entry)==Entry):
00333         entry=Entry(instance=entry)
00334       ret_entries += "  "+unicode(entry)+"\n"
00335     
00336     return ret_invoice+"\n"+ret_entries
00337   
00338 def __invoice__str__(self):
00339     """__str__ method for invoice class"""
00340     
00341     from gnucash.gnucash_business import Invoice
00342     self=Invoice(instance=self)
00343 
00344     return unicode(self).encode('utf-8')
00345 
00346 from gnucash.gnucash_business import Invoice
00347 
00348 gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
00349 gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
00350 
00351 gnucash.gnucash_core_c.__invoice__unicode__=__invoice__unicode__
00352 gnucash.gnucash_business.Invoice.add_method("__invoice__unicode__","__unicode__")
00353 
00354 def __entry__unicode__(self):
00355     """__unicode__ method for Entry"""
00356 
00357     from gnucash.gnucash_business import Entry
00358     self=Entry(instance=self)
00359 
00360     # This dict and the return statement can be changed according to individual needs 
00361     fmt_dict={
00362         "date_name":"Date:",
00363         "date_value":unicode(self.GetDate()),
00364         "description_name":"Description:",
00365         "description_value":self.GetDescription(),
00366         "notes_name":"Notes:",
00367         "notes_value":self.GetNotes(),
00368         "quant_name":"Quantity:",
00369         "quant_value":unicode(gnucash.GncNumeric(instance=self.GetQuantity())),
00370         "invprice_name":"InvPrice:",
00371         "invprice_value":unicode(gnucash.GncNumeric(instance=self.GetInvPrice()))}
00372 
00373     return (u"{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
00374             u"{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
00375                 format(**all_as_classwithcutting__format__keys(**fmt_dict))
00376 
00377 def __entry__str__(self):
00378     """__str__ method for Entry class"""
00379     
00380     from gnucash.gnucash_business import Entry
00381     self=Entry(instance=self)
00382 
00383     return unicode(self).encode('utf-8')
00384 
00385 from gnucash.gnucash_business import Entry
00386 
00387 gnucash.gnucash_core_c.__entry__str__=__entry__str__
00388 gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")
00389 
00390 gnucash.gnucash_core_c.__entry__unicode__=__entry__unicode__
00391 gnucash.gnucash_business.Entry.add_method("__entry__unicode__","__unicode__")
00392 
00393 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines