Files
gnucash/bindings/python/example_scripts/str_methods.py
Christoph Holtermann 1184e92687 2to3
2019-04-04 11:52:13 +02:00

353 lines
12 KiB
Python

#!/usr/bin/env python3
## @file
# @brief Add __str__ methods to financial objects so that @code print object @endcode leads to human readable results
""" @package str_methods.py -- Add __str__ methods to financial objects
Import this module and str(Object) where Object is Transaction, Split, Invoice or Entry leads to
human readable results. That is handy when using @code print object @endcode
I chose to put these functions/methods in a separate file to develop them like this and maybe if
they prove to be useful they can be put in gnucash_core.py.
I am searching to find the best way to serialize these complex objects. Ideally this serialization
would be configurable.
If someone has suggestions how to beautify, purify or improve this code in any way please feel
free to do so.
This is written as a first approach to a shell-environment using ipython to interactively manipulate
GnuCashs Data."""
# @author Christoph Holtermann, c.holtermann@gmx.de
# @ingroup python_bindings_examples
# @date May 2011
#
# ToDo :
#
# * Testing for SWIGtypes
# * dealing the cutting format in a bit more elegant way
# * having setflag as a classmethod makes it probably impossible to have flags on instance level. Would changing that be useful ?
# * It seems useful to have an object for each modification. That is because there is some Initialisation to be done.
#
import gnucash
from gnucash import function_class
# Default values for encoding of strings in GnuCashs Database
DEFAULT_ENCODING = "UTF-8"
DEFAULT_ERROR = "ignore"
def setflag(self, name, value):
if not(name in self.OPTIONFLAGS_BY_NAME):
self.register_optionflag(name)
if value == True:
self.optionflags |= self.OPTIONFLAGS_BY_NAME[name]
else:
self.optionflags &= ~self.OPTIONFLAGS_BY_NAME[name]
def getflag(self, name):
if not(name in self.OPTIONFLAGS_BY_NAME):
raise KeyError(str(name)+" is not a registered key.")
return ((self.optionflags & self.OPTIONFLAGS_BY_NAME[name]) != 0)
def register_optionflag(self,name):
"""Taken from doctest.py"""
# Create a new flag unless `name` is already known.
return self.OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(self.OPTIONFLAGS_BY_NAME))
def ya_add_method(_class, function, method_name=None, clsmethod=False, noinstance=False):
"""Calls add_method from function_methods.py but makes it
possible to use functions in this module. Also keeps the
docstring"""
if method_name == None:
method_name = function.__name__
setattr(gnucash.gnucash_core_c,function.__name__,function)
if clsmethod:
mf=_class.ya_add_classmethod(function.__name__,method_name)
elif noinstance:
mf=_class.add_method(function.__name__,method_name)
else:
mf=_class.ya_add_method(function.__name__,method_name)
if function.__doc__ != None:
setattr(mf, "__doc__", function.__doc__)
def infect(_class, function, method_name):
if not getattr(_class, "OPTIONFLAGS_BY_NAME", None):
_class.OPTIONFLAGS_BY_NAME={}
_class.optionflags=0
ya_add_method(_class,register_optionflag,clsmethod=True)
ya_add_method(_class,setflag,clsmethod=True)
ya_add_method(_class,getflag,clsmethod=True)
ya_add_method(_class, function, method_name)
class ClassWithCutting__format__():
"""This class provides a __format__ method which cuts values to a certain width.
If width is too big '...' will be put at the end of the resulting string."""
def __init__(self,value):
self.value = value
def __format__(self, fmt):
def get_width(fmt_spec):
"""Parse fmt_spec to obtain width"""
def remove_alignment(fmt_spec):
if fmt_spec[1] in ["<","^",">"]:
fmt_spec=fmt_spec[2:len(fmt_spec)]
return fmt_spec
def remove_sign(fmt_spec):
if fmt_spec[0] in ["-","+"," "]:
fmt_spec=fmt_spec[1:len(fmt_spec)]
return fmt_spec
def remove_cross(fmt_spec):
if fmt_spec[0] in ["#"]:
fmt_spec=fmt_spec[1:len(fmt_spec)]
return fmt_spec
def do_width(fmt_spec):
n=""
while len(fmt_spec)>0:
if fmt_spec[0].isdigit():
n+=fmt_spec[0]
fmt_spec=fmt_spec[1:len(fmt_spec)]
else:
break
if n:
return int(n)
else:
return None
if len(fmt_spec)>=2:
fmt_spec=remove_alignment(fmt_spec)
if len(fmt_spec)>=1:
fmt_spec=remove_sign(fmt_spec)
if len(fmt_spec)>=1:
fmt_spec=remove_cross(fmt_spec)
width=do_width(fmt_spec)
# Stop parsing here for we only need width
return width
def cut(s, width, replace_string="..."):
"""Cuts s to width and puts replace_string at it's end."""
#s=s.decode('UTF-8', "replace")
if len(s)>width:
if len(replace_string)>width:
replace_string=replace_string[0:width]
s=s[0:width-len(replace_string)]
s=s+replace_string
return s
value=self.value
# Replace Tabs and linebreaks
#import types
if isinstance(value, str):
value = value.replace("\t","|")
value = value.replace("\n","|")
# Do regular formatting of object
value = value.__format__(fmt)
# Cut resulting value if longer than specified by width
width = get_width(fmt)
if width:
value = cut(value, width, "...")
return value
def all_as_classwithcutting__format__(*args):
"""Converts every argument to instance of ClassWithCutting__format__"""
#import types
l=[]
for a in args:
#if type(a) in [types.StringType, types.UnicodeType]:
# a=a.decode("UTF-8")
l.append(ClassWithCutting__format__(a))
return l
def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
"""Converts every argument to instance of ClassWithCutting__format__"""
#import types
d={}
if encoding==None:
encoding=DEFAULT_ENCODING
if error==None:
error=DEFAULT_ERROR
for a in keys:
#if isinstance(keys[a], str):
# keys[a]=keys[a].decode(encoding,error)
d[a]=ClassWithCutting__format__(keys[a])
return d
# Split
def __split__str__(self, encoding=None, error=None):
"""__str__(self, encoding=None, error=None) -> object
Serialize the Split object and return as a new Unicode object.
Keyword arguments:
encoding -- defaults to str_methods.default_encoding
error -- defaults to str_methods.default_error
See help(unicode) for more details or http://docs.python.org/howto/unicode.html.
"""
from gnucash import Split
import time
#self=Split(instance=self)
lot=self.GetLot()
if lot:
if type(lot).__name__ == 'SwigPyObject':
lot=gnucash.GncLot(instance=lot)
lot_str=lot.get_title()
else:
lot_str='---'
transaction=self.GetParent()
# This dict and the return statement can be changed according to individual needs
fmt_dict={
"account":self.GetAccount().name,
"value":self.GetValue(),
"memo":self.GetMemo(),
"lot":lot_str}
fmt_str= ("Account: {account:20} "+
"Value: {value:>10} "+
"Memo: {memo:30} ")
if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
fmt_t_dict={
"transaction_time":time.ctime(transaction.GetDate()),
"transaction2":transaction.GetDescription()}
fmt_t_str=(
"Transaction: {transaction_time:30} "+
"- {transaction2:30} "+
"Lot: {lot:10}")
fmt_dict.update(fmt_t_dict)
fmt_str += fmt_t_str
return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
# This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
# thereafter mutates to have better capabilities.
infect(gnucash.Split,__split__str__,"__str__")
gnucash.Split.register_optionflag("PRINT_TRANSACTION")
gnucash.Split.setflag("PRINT_TRANSACTION",True)
def __transaction__str__(self):
"""__str__ method for Transaction class"""
from gnucash import Transaction
import time
self=Transaction(instance=self)
fmt_tuple=('Date:',time.ctime(self.GetDate()),
'Description:',self.GetDescription(),
'Notes:',self.GetNotes())
transaction_str = "{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
*all_as_classwithcutting__format__(*fmt_tuple))
transaction_str += "\n"
splits_str=""
for n,split in enumerate(self.GetSplitList()):
if not (type(split)==gnucash.Split):
split=gnucash.Split(instance=split)
transaction_flag = split.getflag("PRINT_TRANSACTION")
split.setflag("PRINT_TRANSACTION",False)
splits_str += "[{0:>2}] ".format(str(n))
splits_str += str(split)
splits_str += "\n"
split.setflag("PRINT_TRANSACTION",transaction_flag)
return transaction_str + splits_str
gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
gnucash.Transaction.add_method("__transaction__str__","__str__")
def __invoice__str__(self):
"""__str__ method for Invoice"""
from gnucash.gnucash_business import Invoice
self=Invoice(instance=self)
# This dict and the return statement can be changed according to individual needs
fmt_dict={
"id_name":"ID:",
"id_value":self.GetID(),
"notes_name":"Notes:",
"notes_value":self.GetNotes(),
"active_name":"Active:",
"active_value":str(self.GetActive()),
"owner_name":"Owner Name:",
"owner_value":self.GetOwner().GetName(),
"total_name":"Total:",
"total_value":str(self.GetTotal()),
"currency_mnemonic":self.GetCurrency().get_mnemonic()}
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}"+
"{total_name:8}{total_value:10}{currency_mnemonic:3}").\
format(**all_as_classwithcutting__format__keys(**fmt_dict))
ret_entries=""
entry_list = self.GetEntries()
for entry in entry_list: # Type of entry has to be checked
if not(type(entry)==Entry):
entry=Entry(instance=entry)
ret_entries += " "+str(entry)+"\n"
return ret_invoice+"\n"+ret_entries
from gnucash.gnucash_business import Invoice
gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
def __entry__str__(self):
"""__str__ method for Entry"""
from gnucash.gnucash_business import Entry
self=Entry(instance=self)
# This dict and the return statement can be changed according to individual needs
fmt_dict={
"date_name":"Date:",
"date_value":str(self.GetDate()),
"description_name":"Description:",
"description_value":self.GetDescription(),
"notes_name":"Notes:",
"notes_value":self.GetNotes(),
"quant_name":"Quantity:",
"quant_value":str(self.GetQuantity()),
"invprice_name":"InvPrice:",
"invprice_value":str(self.GetInvPrice())}
return ("{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
"{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
format(**all_as_classwithcutting__format__keys(**fmt_dict))
from gnucash.gnucash_business import Entry
gnucash.gnucash_core_c.__entry__str__=__entry__str__
gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")