[DOC-SIG] simple but flexible text document classes

Scott scott@chronis.icgroup.com
Fri, 7 Nov 1997 23:08:28 -0500


periodically I end up writing scripts or programs wchich have the need to 
automate the production of fairly simple text documents.  After some time
pondering how to make an easy to use interface to these documents while
separating the documents themselves from the control flow of the program, I
started to use the following library.

It's simple, flexible, and about as efficient as I can imagine it being
without being coded in C.

Just thought I'd post it here in case anyone would want to use it one day.


# -*-Python-*-
"""
this module defines two classes and one function.  The first class, ddoc, 
defines a document which can contain other documents.  each ddoc may be
treated like a dictionary to some degree.  When you assign a key that belongs
to any subdocuments, it will place the value where you intend it.  This
recursive structure allows for creating text documents with many different
parts put together in a structured manner.  The topmost document still allows
access and assignment to the variables in the inner documents.  The whole
thing is implemented using the format operator so it's pretty fast.

The listdoc class acts much the same way, but allows for repetition of a
single document with different values for the variables each time. With a
listdoc, you simply give the variables in the document (accessed as keys)
value of a list, and it does the rest of the work for you.

the load function facilitates storing the whole document in a separate file.
each document has a method 'save' which stores the document together with the
small amount of information needed to reload it.  the load function will load
docs that have been saved with save methods.  This doesn't use pickle or
Cpickle because part of the idea of saving the document in a separate file is
to be able to edit it without interfereing with the program(s) that use it.
"""


import string


class ddoc:
    
    error = "error"

    def __init__(self, text="", dict=None):
	self.text = text
	if not dict:
	    self.dict = {}
	else:
	    self.dict = dict

    def __repr__(self):
	return self.text % self
    
    def __setitem__(self, key, doc):
	#
	# if key is in top level values, set that value and return
	#
	for k in self.dict.keys():
	    if key == k:
		self.dict[key] = doc
		return	

	#
	# if key is in top level text, set it at top level
	#
	if string.find(self.text, '%(' + key + ')') >= 0:
	    self.dict[key] = doc

	#
	# if key in keys of each sub doc, set that sub-doc's key to val and return
	#
	for c in self.children():
	    if key in c.dict.keys():
		c[key] = doc
		return
	    if string.find(c.text, '%(' + key + ')') >= 0:
		c[key] = doc
		return
	#
	# key isn't anywhere in doc, so create it at top level
	#
	self.dict[key] = doc
    
    # 
    # return any subdoc or subsubdoc, etc  or just text as value
    #
    def __getitem__(self, key):
	try:
	    return self.dict[key]
	except KeyError:
	    if string.find(self.text, "%(" + key + ")") >= 0:
		return ''
	    for c in self.children():
		try:
		    return c[key]
		except KeyError:
		    continue
	    raise KeyError, key

		    

    def __delitem__(self, item):
	del self.dict[item]

    # 
    # totally makes object as if it were a newly created document
    #
    def clear(self):
	self.text = ""
	self.dict.clear()
    
    #
    # return all keys of self + all keys of subdocs, etc
    # because of setitem constraints, this should always be a list of unqique elements
    #
    def keys(self):
	return  self.leaves() + self.namechildren()

	
    #
    # return all subdocs, even if subsubdocs, etc
    #
    def children(self):
	d = self.dict
	c = []
	for k in d.keys():
	    if type(d[k]) is type(self):
		c.insert(0, d[k])
		c = c + d[k].children()
	return c

    def namechildren(self):
	l = []
	for k in self.dict.keys():
	    if type(self.dict[k]) is type(self):
		l.insert(0, k)
		l = l + self.dict[k].namechildren()
	return l

    #
    # return only keys that are not documents
    #
    def leaves(self):
	d = self.dict
	l = []
	for k in d.keys():
	    if type(d[k]) is type(''):
		l.insert(0,k)
	    else:
		l = l + d[k].leaves()
	return l

    def insert(self, index, text):
	start = self.text[0:index]
	finish = self.text[index:]
	self.text = "%s%s%s" % (start, text, finish)

    def set_default(self, default, *keys):
	for k in keys:
	    self[k] = default

    def has_children(self):
	return len(self.namechildren()) == 0

    def raw_texts(self):
	tlist = [("text", '', self.text)]
	for cn in self.namechildren():
	    tlist.append((cn, string.split(`self[cn].__class__`)[1], self[cn].text))
	    if self[cn].has_children():
		tlist = tlist + self[cn].raw_texts()[1:]
	return tlist

    def save(self, filename):
	fp = open(filename, "w")
	rtl = self.raw_texts()
	fp.write("\"\"\"" + rtl[0][2] + "\"\"\"\n\n")
	d = [("thisdoc", string.split(`self.__class__`)[1])]
	for n,c, rt in rtl[1:]:
	    fp.write("%s=\"\"\"%s\"\"\"\n\n\n" % (n,rt))
	    d.append((n,c))
	fp.write("\n#\n# document types -- do not edit if you don't know how\n#\n_dtypes=\\\n\t%s" % (`d`))
	fp.close()



class listdoc(ddoc):
    
    def __init__(self, text="", dict=None):
	ddoc.__init__(self, text, dict)
	self.dict['__iterator'] = 1
	self.__reg_vals = {}

    def __setitem__(self, item, val):
	if type(val) is type([]):
	    self.__reg_vals[item] = val
	    return
	ddoc.__setitem__(self, item, val)

    def __getitem__(self, item):
	if type(item) is type(0):
	    for k in self.__reg_vals.keys():
		self.dict[k] = self.__reg_vals[k][item]
	    return ddoc.__repr__(self)
	return ddoc.__getitem__(self, item)

    def __len__(self):
	max = 1
	for l in self.__reg_vals.values():
	    if len(l) > max:
		max = len(l)
	return max
	    
    def __repr__(self):
	res = ""
	for x in range(len(self)):
	    for k in self.__reg_vals.keys():
		self.dict[k] = self.__reg_vals[k][x]
	    res = "%s%s" % (res, ddoc.__repr__(self))
	return res


    def clear(self):
	ddoc.clear(self)
	self.dict['__iterator'] = 1
	self.__reg_vals.clear()


def load(filename):
    import sys
    pl = string.split(filename, "/")
    if len(pl) > 1:
	dir = string.join(pl[:-1], "/")
	sys.path.insert(0, dir)
	f = pl[-1][:-3]
    else:
	if filename[-3:] == ".py":
	    f = filename[:-3]
	else:
	    f = filename
    exec("import %s" % (f))
    exec("dtl = %s._dtypes" % (f))
    exec("thisdoc = %s()" % (dtl[0][1]))
    exec("thisdoc.text = %s.__doc__" % (f))
    for dn, dt in dtl[1:]:
	exec("%s = %s(); %s.text = %s.%s; thisdoc['%s'] = %s" % (dn,dt,dn,f,dn,dn,dn))
    return thisdoc












_______________
DOC-SIG  - SIG for the Python Documentation Project

send messages to: doc-sig@python.org
administrivia to: doc-sig-request@python.org
_______________