This file is indexed.

/usr/lib/python2.7/dist-packages/woo/document.py is in python-woo 1.0+dfsg1-1+b4.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# encoding: utf-8
import woo
import woo.core
import sys
import codecs
import re
from minieigen import *
import logging


sphinxOnlineDocPath='http://www.woodem.eu/doc/'
"Base URL for the documentation. Packaged versions should change to the local installation directory."

import os.path
# find if we have docs installed locally from package
sphinxLocalDocPath=woo.config.prefix+'/share/doc/woo'+woo.config.suffix+'/html/'
# FIXME: unfortunately file:// links don't seem to work with anchors, so just make this invalid
sphinxBuildDocPath=woo.config.sourceRoot+'/doc/sphinx2/build/html/REMOVE_THIS_TO_USE_LOCAL_DOCS'
# we prefer the packaged documentation for this version, if installed
if   os.path.exists(sphinxLocalDocPath+'/index.html'): sphinxPrefix='file://'+sphinxLocalDocPath
# otherwise look for documentation generated in the source tree
elif  os.path.exists(sphinxBuildDocPath+'/index.html'): sphinxPrefix='file://'+sphinxBuildDocPath
# fallback to online docs
else: sphinxPrefix=sphinxOnlineDocPath


def fixDocstring(s):
	s=s.replace(':ref:',':obj:')
	s=re.sub(r'(?<!\\)\$([^\$]+)(?<!\\)\$',r'\ :math:`\1`\ ',s)
	s=re.sub(r'\\\$',r'$',s)
	return s

allWooClasses,allWooMods=set(),set()

def _ensureInitialized():
	'Fill allWooClasses and allWooMods, called automatically as needed'
	global allWooClasses,allWooMods
	if allWooClasses: return  # do nothing if already filled
	def subImport(pkg,exclude=None):
		'Import recursively all subpackages'
		import pkgutil
		for importer, modname, ispkg in pkgutil.iter_modules(pkg.__path__):
			fqmodname=pkg.__name__+'.'+modname
			print fqmodname
			if exclude and re.match(exclude,fqmodname):
				print 'Skipping  '+fqmodname
				continue
			if fqmodname not in sys.modules:
				sys.stdout.write('Importing '+fqmodname+'... ')
				try:
					__import__(fqmodname,pkg.__name__)
					print 'ok'
				except (ImportError, NameError): print '(error, ignoring)'
			if fqmodname in sys.modules and ispkg:
				print 'Import submodules from ',fqmodname
				subImport(sys.modules[fqmodname])
				
	modExcludeRegex='^woo\.(_cxxInternal.*)(\..*|$)'
	subImport(woo,exclude=modExcludeRegex)
	try:
		import wooExtra
		subImport(wooExtra)
	except ImportError:
		print 'No wooExtra modules imported.'

	for m in woo.master.compiledPyModules:
		if m not in sys.modules:
			print 'Importing',m
			__import__(m)

	# global allWooClasses, allWooMods
	cc=woo.system.childClasses(woo.core.Object,includeBase=True,recurse=True)
	for c in cc:
		if re.match(modExcludeRegex,c.__module__): continue
		if c.__doc__: c.__doc__=fixDocstring(c.__doc__)
		allWooClasses.add(c)

	allWooMods=set([sys.modules[m] for m in sys.modules if m.startswith('woo') and sys.modules[m] and sys.modules[m].__name__==m])
	

def allWooPackages(outDir='/tmp',skip='^(woo|wooExtra(|\..*))$'):
	'''Generate documentation of packages in the Restructured Text format. Each package is written to file called *out*/.`woo.[package].rst` and list of files created is returned.'''

	global allWooClasses,allWooMods
	_ensureInitialized()
	
	modsElsewhere=set()
	for m in allWooMods: modsElsewhere|=set(m._docInlineModules if hasattr(m,'_docInlineModules') else [])
	# woo.foo.* modules go insides woo.foo
	toplevMods=set([m for m in allWooMods if (m not in modsElsewhere) and len(m.__name__.split('.'))<3])
	rsts=[]
	print 'TOPLEVEL MODULES',[m.__name__ for m in toplevMods]
	print 'MODULES DOCUMENTED ELSEWHERE',[m.__name__ for m in modsElsewhere]

	for mod in toplevMods:
		if re.match(skip,mod.__name__):
			print '[SKIPPING %s]'%(mod.__name__)
			continue
		outFile=outDir+'/%s.rst'%(mod.__name__)
		print 'WRITING',outFile,mod.__name__
		rsts.append(outFile)
		out=codecs.open(outFile,'w','utf-8')
		oneModuleWithSubmodules(mod,out)

	return rsts

def guessInstanceTypeFromCxxType(klass,trait,noneOnFail=False):
	import logging
	'Return type object guessed from cxxType'
	wHead=klass.__module__+'.'+klass.__name__+'.'+trait.name
	m=re.match(r'^\s*(weak_ptr\s*<|shared_ptr\s*<)?([A-Za-z0-9_:]+)(\s*>)?\s*',trait.cxxType)
	if m:
		cT=m.group(2)
		logging.debug('%s: got c++ base type: %s -> %s'%(wHead,trait.cxxType,cT))
		klasses=[c for c in woo.system.childClasses(woo.core.Object,includeBase=True) if c.__name__==cT]
		if len(klasses)==0:
			if noneOnFail: return None
			logging.warn('%s: no Python type object with name %s found (cxxType=%s)'%(wHead,cT,trait.cxxType))
		elif len(klasses)>1: logging.warn('%s: multiple Python types with name %s found (cxxType=%s): %s'%(wHead,cT,trait.cxxType,', '.join([c.__module__+'.'+c.__name__ for c in klasses])))
		else: return klasses[0]
	if noneOnFail: return None
	logging.warn('%s: no c++ base type found for %s'%(wHead,trait.cxxType))
	logging.warn('%s: using woo.core.Object as type'%(wHead))
	return woo.core.Object

def guessListTypeFromCxxType(klass,trait,warnFail=False):
	"Guess type of array from parsing trait.cxxType. Ugly but works."
	# head for warnings
	wHead=klass.__module__+'.'+klass.__name__+'.'+trait.name
	def vecTest(T,cxxT):
		regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(shared_ptr\s*<\s*)?\s*(std\s*::)?\s*('+T+r')(\s*>)?\s*>\s*$'
		m=re.match(regexp,cxxT)
		return m
	def vecGuess(T):
		regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(shared_ptr\s*<\s*)?\s*(std\s*::)?\s*(?P<elemT>[a-zA-Z_][a-zA-Z0-9_]+)(\s*>)?\s*>\s*$'
		m=re.match(regexp,T)
		return m
	from woo import dem
	if 'opengl' in woo.config.features: from woo import gl
	from woo import core
	vecMap={
		'bool':bool,'int':int,'long':int,'Particle::id_t':int,'size_t':int,
		'Real':float,'float':float,'double':float,
		'Vector6r':Vector6,'Vector6i':Vector6i,'Vector3i':Vector3i,'Vector2r':Vector2,'Vector2i':Vector2i,
		'Vector3r':Vector3,'Matrix3r':Matrix3,'Quaternionr':Quaternion,
		'VectorXr':VectorX,'MatrixXr':MatrixX,
		'AlignedBox2r':AlignedBox2,'AlignedBox3r':AlignedBox3,
		'string':str
	}
	cxxT=trait.cxxType
	if not cxxT:
		logging.error("Trait for %s does not define cxxType"%(trait.name))
		return None
	for T,ret in vecMap.items():
		if vecTest(T,cxxT):
			logging.debug("Got type %s from cxx type %s"%(repr(ret),cxxT))
			return (ret,)
	#print 'No luck with ',T
	m=vecGuess(cxxT)
	if m:
		# print 'guessed literal type',m.group('elemT')
		elemT=m.group('elemT')
		klasses=[c for c in woo.system.childClasses(woo.core.Object,includeBase=True) if c.__name__==elemT]
		if len(klasses)==0: logging.warn('%s: no Python type object with name %s found (cxxType=%s)'%(wHead,elemT,trait.cxxType))
		elif len(klasses)>1: logging.warn('%s: multiple Python types with name %s found (cxxType=%s): %s'%(wHead,elemT,trait.cxxType,', '.join([c.__module__+'.'+c.__name__ for c in klasses])))
		else: return (klasses[0],) # return tuple to signify sequence
	if warnFail: logging.error("Unable to guess python type from cxx type '%s'"%cxxT)
	return None


def makeTraitInfo(obj,klass,trait):
	def maybe_decode(a): return (a if isinstance(a,unicode) else unicode(a,'utf-8'))

	hasVal=True # true if the value is accessible from python
	try: val=getattr(obj,trait.name)
	except: hasVal=False

	ret=[]
	if trait.static: ret.append('static')
	if hasattr(trait,'pyType'):
		if isinstance(trait.pyType,(list,tuple)): ret.append('type: ['+trait.pyType[0].__name__+", …]")
		else: ret.append('type: '+trait.pyType.__name__)
	else:
		tt=None
		if hasVal and not isinstance(val,woo.core.Object) and val!=None:
			if val.__class__.__module__=='minieigen': tt=':obj:`%s <minieigen:minieigen.%s>`'%(val.__class__.__name__,val.__class__.__name__)
			else: tt=trait.cxxType
		if not tt:
			if trait.cxxType in ('int','string','bool','Real','long','size_t','ContainerT','PendingContact','std::vector<PendingContact','list<id_t>','shared_ptr<SpherePack>','list<id_t>','boost_multi_array_real_5','py::object'): tt=trait.cxxType
		if not tt:
			l=guessListTypeFromCxxType(klass,trait,warnFail=False)
			if l and l[0].__module__!='__builtin__': tt=trait.cxxType.replace(l[0].__name__,':obj:`%s <%s.%s>`'%(l[0].__name__,l[0].__module__,l[0].__name__))
		if not tt:
			t=guessInstanceTypeFromCxxType(klass,trait,noneOnFail=True)
			if t and t.__module__!='__builtin__': tt=trait.cxxType.replace(t.__name__,':obj:`%s <%s.%s>`'%(t.__name__,t.__module__,t.__name__))
		if not tt: tt=trait.cxxType
		ret.append('type: '+tt)
	if trait.unit:
		if len(trait.unit)==1: ret.append('unit: '+maybe_decode(trait.unit[0]))
		else: ret.append(u'units: ['+u','.join(maybe_decode(u) for u in trait.unit)+u']')
	if trait.prefUnit and trait.unit[0]!=trait.prefUnit[0][0]:
		if len(trait.unit)==1: ret.append('preferred unit: '+maybe_decode(trait.prefUnit[0][0]))
		else: ret.append('preferred units: ['+u','.join(maybe_decode(u[0]) for u in trait.prefUnit)+']')
	if trait.range: ret.append(u'range: %g%g'%(trait.range[0],trait.range[1]))
	if trait.noGui: ret.append('not shown in the UI')
	if trait.noDump: ret.append('not dumped')
	if trait.noSave: ret.append('not saved')
	if trait.hidden: ret.append('not accessible from python')
	if trait.readonly: ret.append('read-only in python')
	if trait.choice and not trait.namedEnum: # named enums formatted differently below
		if isinstance(trait.choice[0],tuple): ret.append('choices: '+', '.join('%d = %s'%(c[0],c[1] if '|' not in c[1] else '``'+c[1]+'``') for c in trait.choice))
		else: ret.append('choices: '+', '.join(str(c) for c in trait.choice))
	if trait.namedEnum: ret.append('named enum, possible values are: '+trait.namedEnum_validValues(pre0="**'",post0="'**",pre="*'",post="'*"))
	if trait.filename: ret.append('filename')
	if trait.existingFilename: ret.append('existing filename')
	if trait.dirname: ret.append('directory name')
	if trait.bits: ret.append('bit accessors: '+', '.join(['**'+b+'**' for  b in trait.bits]))

	return ', '.join(ret)
	
def classSrcHyperlink(klass):
	'Return ReST-formatted line with hyperlinks to class headers (and implementation, if the corresponding .cpp file exists).'
	def findFirstLine(ff,pattern,regex=True):
		if regex: pat=re.compile(pattern)
		if not os.path.exists(ff): return -1
		numLines=[i for i,l in enumerate(file(ff).readlines()) if (pat.match(l) if regex else (l==pattern))]
		return (numLines[0] if numLines else -1)

	import woo.config, inspect, os, hashlib
	pysrc=inspect.getsourcefile(klass)
	if pysrc: # python class
		lineNo=inspect.getsourcelines(klass)[1]
		pysrc1=os.path.basename(pysrc)
		# locate the file in the source tree (as a quick hack, just use filename match)
		matches=[]
		for root,dirnames,filenames in os.walk(woo.config.sourceRoot):
			relRoot=root[len(woo.config.sourceRoot)+1:]
			if relRoot.startswith('build') or relRoot.startswith('debian'): continue # garbage
			matches+=[relRoot+'/'+f for f in filenames if f==pysrc1]
		if len(matches)>1:
			#print 'WARN: multiple files named %s, using the first one: %s'%(pysrc1,str(matches))
			md0=hashlib.md5(open(pysrc).read()).hexdigest()
			matches=[m for m in matches if hashlib.md5(open(woo.config.sourceRoot+'/'+m).read()).hexdigest()==md0]
			if len(matches)==0:
				print 'WARN: no file named %s with matching md5 digest found in source tree?'%pysrc1
				return None
			elif len(matches)>1:
				print 'WARN: multiple files named %s with the same md5 found in the source tree, using the one with shortest path.'%pysrc1
				matches.sort(key=len)
				match=matches[0]
			else: match=matches[0]
		elif len(matches)==1: match=matches[0]
		else:
			print 'WARN: no python source %s found.'%(pysrc1)
			return None
		return '[:woosrc:`%s <%s#L%s>`]'%(match,match,lineNo)


		
	if not klass._classTrait: return None
	f=klass._classTrait.file
	if f.startswith(woo.config.sourceRoot): commonRoot=woo.config.sourceRoot
	elif f.startswith(woo.config.buildRoot): commonRoot=woo.config.buildRoot
	elif not f.startswith('/'): commonRoot=''
	else:
		print 'File where class is defined (%s) does not start with source root (%s) or build root (%s)'%(f,woo.config.sourceRoot,woo.config.buildRoot)
		return None
	# +1 removes underscore in woo/...
	f2=f[len(commonRoot)+(1 if commonRoot else 0):] 
	# if this header was copied into include/, get rid of that now
	m=re.match('include/woo/(.*)',f2)
	if m: f2=m.group(1)
	if f2.endswith('.hpp'): hpp,cpp,lineIs=f2,f2[:-4]+'.cpp','hpp'
	elif f2.endswith('.cpp'): hpp,cpp,lineIs=f2[:-4]+'.hpp',f2,'cpp'
	else: return None # should not happen, anyway

	hppLine=findFirstLine(woo.config.sourceRoot+'/'+hpp,r'^(.*\s)?(struct|class)\s+%s\s*({|:).*$'%klass.__name__)
	cppLine=findFirstLine(woo.config.sourceRoot+'/'+cpp,r'^(.*\s)?%s::.*$'%klass.__name__)
	ret=[]
	if hppLine>0: ret+=[':woosrc:`%s <%s#L%d>`'%(hpp,hpp,hppLine+1)]
	else:
		if f2.endswith('.hpp'): ret+=[':woosrc:`%s <%s#L%d>`'%(hpp,hpp,klass._classTrait.line)]
		else: print 'WARN: No header line found for %s in %s'%(klass.__name__,hpp)
	if cppLine>0: ret+=[':woosrc:`%s <%s#L%d>`'%(cpp,cpp,cppLine+1)]
	else: pass # print 'No impl. line found for %s in %s'%(klass.__name__,cpp)
	#ret=[':woosrc:`header <%s#L%d>`'%(f2,klass._classTrait.line)]
	#if f2.endswith('.hpp'):
	#	cpp=woo.config.sourceRoot+'/'+f2[:-4]+'.cpp'
	#	# print 'Trying ',cpp
	#	if os.path.exists(cpp):
	#		# find first line in the cpp file which contains KlassName:: -- that's most likely where the implementation begins
	#		numLines=[i for i,l in enumerate(file(cpp).readlines()) if klass.__name__+'::' in l]
	#		if numLines: ret.append(':woosrc:`implementation <%s#L%d>`'%(f2[:-4]+'.cpp',numLines[0]+1))
	#		# return just the cpp file if the implementation is probably not there at all?
	#		# don't do it for now
	#		# else: ret.append(':woosrc:`implementation <%s#L>`'%(f2[:-4]+'.cpp'))
	if ret: return '[ '+' , '.join(ret)+' ]'
	else: return None

def classDocHierarchy_topsAndDict(mod):
	'Return tuple containing list of top-level class objects, and dictionary which maps all module-contained class objects to classes which should be documented under it (derived classes, and doc-related classes specified via ClassTrait.section'
	global allWooClasses,allWooMods
	_ensureInitialized()
	# kk=woo.system.childClasses(woo.core.Object,includeBase=True) # all woo classes
	dd={}
	for k in allWooClasses:
		if k==woo.core.Object: continue # obejct itself is not documented this way
		t=k._classTrait
		if not t: continue
		if k.__module__!=mod.__name__: continue
		dd[k]=[]
		for a in t.docOther:
			if '.' in a:
				raise NotImplementedError("Cross-module documentation not yet supported.")
				dd[k].append(eval(a))
			else: dd[k].append(eval(k.__module__+'.'+a))
			# [eval('%s.%s'%(k.__module__,a)) for a in t.docOther] # perhaps not in the same module? fix here!!
		#print t.name,t.docOther,t.intro,t.title

	for k in dd:
		# if the base is documented, put it under the base class (prepend)
		b=k.__bases__[0]
		if b in dd: dd[b]=[k]+dd[b]
	tops=[]
	for k in dd:
		refs=[]
		for kk,vv in dd.items():
			if k in vv: refs+=[kk]
		if len(refs)==0: tops.append(k)
		elif len(refs)>1: raise RuntimeError('Class %s listed under multiple classes: '%k.__name__+', '.join(['%s'%(r.__name__) for r in refs]))
		# if the class has one single reference, it is not a top class
	return (tops,dd)


def oneModuleWithSubmodules(mod,out,exclude=None,level=0,importedInto=None):
	global allWooClasses,allWooMods
	_ensureInitialized()
	if exclude==None: exclude=set() # avoid referenced empty set to be modified
	if level>=0: out.write(':mod:`%s`\n%s\n\n'%(mod.__name__,(20+len(mod.__name__))*('=-^"'[level])))
	if importedInto:
		out.write('.. note:: This module is imported into the :obj:`%s` module automatically; refer to its objects through :obj:`%s`.\n\n'%(importedInto.__name__,importedInto.__name__))

	def _docOneClass(k):
		if k in exclude: return
		exclude.add(k) # already-documented should not be documented again
		obj=k()

		kOut.write('.. autoclass:: %s\n'%k.__name__)
		#kOut.write('   :members: %s\n'%(','.join([m for m in dir(k) if (not m.startswith('_') and m not in set(trait.name for trait in k._attrTraits))])))
		kOut.write('   :members:\n')
		ex=[t.name for t in k._attrTraits]
		if ex: kOut.write('   :exclude-members: %s\n\n'%(', '.join(ex)))

		srcXref=classSrcHyperlink(k)
		if srcXref: kOut.write('\n   '+srcXref+'\n\n')
		for trait in k._attrTraits:
			try:
				iniStr=' (= %s)'%(repr(trait.ini))
			except TypeError: # no converter found
				iniStr=''
			if trait.startGroup:
				kOut.write(u'   .. rubric:: ► %s\n\n'%(trait.startGroup))
			kOut.write('   .. attribute:: %s%s\n\n'%(trait.name,iniStr))
			for l in fixDocstring(trait.doc.decode('utf-8')).split('\n'): kOut.write('      '+l+'\n')
			traitInfo=makeTraitInfo(obj,k,trait)
			if traitInfo: kOut.write(u'\n      ['+traitInfo+']\n')
			kOut.write('\n')

	klasses=[c for c in allWooClasses if (c.__module__==mod.__name__ or (hasattr(mod,'_docInlineModules') and sys.modules[c.__module__] in mod._docInlineModules))]
	klasses.sort(key=lambda x: x.__name__)
	#
	tops,klassesUnder=classDocHierarchy_topsAndDict(mod)

	#global prevLevel
	#prevLevel=0

	def _inheritanceDiagram(k,currmod):
		def nodeName(kk): return ('' if kk.__module__==currmod else kk.__module__+'.')+kk.__name__
		def mkNode(kk,style='solid',fillcolor=None): return '\t\t"%s" [shape="box",fontsize=8,style="setlinewidth(0.5),%s",%sheight=0.2,URL="%s.html#%s.%s"];\n'%(nodeName(kk),style,'fillcolor=%s,'%fillcolor if fillcolor else '','.'.join(kk.__module__.split('.')[:2]),kk.__module__,k.__name__)
		ret=".. graphviz::\n\n\tdigraph %s {\n\t\trankdir=LR;\n\t\tmargin=.2;\n"%k.__name__
		ret+=mkNode(k)
		cc=woo.system.childClasses(k,includeBase=False)
		for c in cc:
			if c.__module__.startswith('wooExtra.'): continue
			# this class will have its own diagram for inheritance
			# if hasattr(c,'_classTrait') and c._classTrait.title and c._classTrait.klassesUnder:
			if c.__module__==currmod: ret+=mkNode(c)
			else: ret+=mkNode(c,style='filled',fillcolor='grey')
			ret+='\t\t"%s" -> "%s" [arrowsize=0.5,style="setlinewidth(0.5)"]'%(nodeName(c.__bases__[0]),nodeName(c))
		return ret+'\n\t}\n\n'


	def _docOneClassWithSectioning(k,level=0):
		# print level*'\t',k.__name__
		if not hasattr(k,'_classTrait'): return # will be documented later
		t=k._classTrait
		nextLevel=level
		#global prevLevel
		#if not t.title:
		#	# decrasing level without section -- write dividing line
		#	if prevLevel>level: out.write('\n\n-----\n\n\n') #
		#prevLevel=level
		#if t.title: # write section title
		nextLevel=level+1
		tt=t.title if t.title else k.__name__
		if level<3:
			kOut.write('\n.. rst-class:: html-toggle\n\n')
			kOut.write('\n.. rst-class:: emphasized\n\n')
		kOut.write(tt+'\n'+len(tt)*'-+"%\'^_'[level]+'\n\n')
		#else:
		#	nextLevel=level+1
		#	kOut.write(k.__name__+'\n'+len(k.__name__)*('-"\'^+_%'[level])+'\n\n')
		if t.intro:
			kOut.write(t.intro+'\n\n')
			if not t.title: warnings.warn('Class %s.%s has intro but no title in class trait.'%(k.__module__,k.__name__))
		# if there was a title different from the class title and there are classes under us, repeat the class name here on the subordinate level
		if t.title:
			if nextLevel<3:
				kOut.write('\n.. rst-class:: html-toggle\n\n')
				kOut.write('\n.. rst-class:: emphasized\n\n')
			kOut.write(k.__name__+'\n'+len(k.__name__)*'-+"%\'^_'[nextLevel]+'\n\n')
		# print inheritance
		if k!=woo.core.Object:
			bb=[k]
			while True:
				bb+=[bb[-1].__bases__[0]]
				if bb[-1]==woo.core.Object: break
			kOut.write(u'\n'+u' → '.join([u':obj:`~%s.%s`'%(b.__module__,b.__name__) for b in reversed(bb)])+'\n\n')
		# print derived classes
		if klassesUnder and woo.system.childClasses(k):
			kOut.write(_inheritanceDiagram(k,mod.__name__))
		_docOneClass(k)
		for kk in klassesUnder[k]:
			assert k!=kk
			# print kk.__name__
			_docOneClassWithSectioning(kk,nextLevel)
		#if not t.title and t.intro:

	# document any c++ classes in a special way
	# defer writing that to out though so that automodule can exclude classes which are documented manually
	import StringIO
	kOut=StringIO.StringIO()
	for top in tops: _docOneClassWithSectioning(top,level)

	# document all remaining classes linearly here, if any
	for k in klasses: _docOneClass(k)

	# control whether autodocs are at the bottom or at the top
	def writeAutoMod(mm,skip=None):
		out.write('.. automodule:: %s\n   :members:\n   :undoc-members:\n'%(mm.__name__))
		if skip: out.write('   :exclude-members: %s\n'%(',  '.join([e.__name__ for e in skip])))
		out.write('\n')
	autoAtBottom=False
	if autoAtBottom:
		# HACK: we want module's docstring to appear at the top, but autodocumented stuff at the bottom
		# dump __doc__ straight to the output here, and reset it, so that automodule does not pick it up
		# autodoc however automatically creates index entry for the module;
		# that's why we end up with bunch of SEVERE: Duplicate ID: "module-..." msgs
		out.write('.. module:: %s\n\n'%mod.__name__)
		if mod.__doc__:
			out.write(mod.__doc__+'\n\n')
			mod.__doc__=None
		# if there are no classes, avoid warning from sphinx
		if klasses: out.write('.. inheritance-diagram:: %s\n\n'%mod.__name__) 
		# insert documentation of classes
		out.write(kOut.getvalue())
		# document the rest of the module here (don't recurse)
		writeAutoMod(mod,skip=exclude)
	else:
		# automodule first, including inheritance tree (are excluded classes excluded from the tree as well?)
		# exlude classes which are are then documented manually
		# if there are no classes, avoid warning from sphinx
		if klasses: out.write('.. inheritance-diagram:: %s\n   :parts: %s\n\n'%(mod.__name__,len(mod.__name__.split('.')) if mod.__name__.startswith('woo.') else 0)) # without showing module name in the inheritance diagram
		writeAutoMod(mod,skip=exclude)
		out.write(kOut.getvalue())
	# imported modules
	if hasattr(mod,'_docInlineModules'):
		# negative level will skip heading
		for m in mod._docInlineModules: oneModuleWithSubmodules(m,out,exclude=exclude,level=level+1,importedInto=mod)
	# nested modules
	# with nested heading
	for m in [m for m in allWooMods if m.__name__.startswith(mod.__name__+'.') and len(mod.__name__.split('.'))+1==len(m.__name__.split('.'))]: oneModuleWithSubmodules(m,out,exclude=exclude,level=level+1)



def makeSphinxHtml(k):
	'Given a class, try to guess name of the HTML page where it is documented by Sphinx'
	if not k.__module__.startswith('woo.'): return k.__module__
	mod=k.__module__.split('.')[1] # sphinx does not make the hierarchy any deeper than 2
	for start,repl in [('_pack','pack'),('_qt','qt'),('_utils','utils')]:
		if mod.startswith(start): return 'woo.'+repl
	return 'woo.'+mod

def makeClassAttrDocUrl(klass,attr=None):
	'''Return URL to documentation of Woo class or its attribute in http://woodem.eu/doc.
	:param klass: class object
	:param attr:  attribute to link to. If given, must exist directly in given *klass* (not its parent); if not given or empty, link to the class itself is created and *attr* is ignored.
	:return: URL as text
	'''
	dotAttr=(('.'+attr) if attr else '')
	if klass.__module__.startswith('wooExtra.'):
		KEY=sys.modules['.'.join(klass.__module__.split('.')[:2])].KEY
		return 'http://www.woodem.eu/private/{KEY}/doc/index.html#{module}.{klass}{dotAttr}'.format(KEY=KEY,module=klass.__module__,klass=klass.__name__,dotAttr=dotAttr)
	return '{sphinxPrefix}/{sphinxHtml}.html#{module}.{klass}{dotAttr}'.format(sphinxPrefix=sphinxPrefix,sphinxHtml=makeSphinxHtml(klass),module=klass.__module__,klass=klass.__name__,dotAttr=dotAttr)

def makeObjectUrl(obj,attr=None):
	"""Return HTML href to a *obj* optionally to the attribute *attr*.
	The class hierarchy is crawled upwards to find out in which parent class is *attr* defined,
	so that the href target is a valid link. In that case, only single inheritace is assumed and
	the first class from the top defining *attr* is used.

	:param obj: object of class deriving from :obj:`woo.core.Object`, or string; if string, *attr* must be empty.
	:param attr: name of the attribute to link to; if empty, linke to the class itself is created.

	:returns: HTML with the hyperref.
	"""
	if attr:
		klass=obj.__class__
		while attr in dir(klass.__bases__[0]): klass=klass.__bases__[0]
	else:
		klass=obj.__class__
	return makeClassAttrDocUrl(klass,attr)

def makeObjectHref(obj,attr=None,text=None):
	'''Create HTML hyperlink, wrapping :obj:`makeObjectUrl`. adding ``<a href="...">text</a>``.

	:param text: visible text of the hyperlink; if not given, either class name or attribute name without class name (when *attr* is given) is used.
	'''
	if not text:
		if attr: text=attr
		else: text=obj.__class__.__name__
	return '<a href="%s">%s</a>'%(makeObjectUrl(obj,attr),text)


def makeCGeomFunctorsMatrix():
	import woo, woo.dem, woo.system
	import prettytable
	ggg0=woo.system.childClasses(woo.dem.CGeomFunctor)
	ggg=set()
	for g in ggg0:
		# handle derived classes which don't really work as functors (Cg2_Any_Any_L6Geom__Base)
		try: 
			g().bases
			ggg.add(g)
		except: pass 

	ss=list(woo.system.childClasses(woo.dem.Shape))
	ss.sort(key=lambda s: s.__name__)
	ss=[s for s in ss if s.__name__ not in ('Membrane','Tet4')] # Membrane is useless here, as it is the same as Facet

	def type2sphinx(t,name=None):
		if name==None: return ':obj:`~%s.%s`'%(t.__module__,t.__name__)
		else: return ':obj:`%s <%s.%s>`'%(name,t.__module__,t.__name__)

	t=prettytable.PrettyTable(['']+[type2sphinx(s) for s in ss],border=True,header=True,hrules=prettytable.ALL)
	for s1 in ss:
		row=[type2sphinx(s1)] # header column
		for s2 in ss:
			gg=[g for g in ggg if sorted(g().bases)==sorted([s1.__name__,s2.__name__])]
			cell=[]
			for g in gg:
				if g.__name__.endswith('L6Geom'): cell+=[type2sphinx(g,'l6g')]
				elif g.__name__.endswith('G3Geom'): cell+=[type2sphinx(g,'g3g')]
				else: raise RuntimError('CGeomFunctor name does not end in L6Geom or G3Geom.')
			cell.sort(reverse=True) # so that l6g comes first
			if cell: row.append(', '.join(cell))
			else: row.append(u'×')
		t.add_row(row)
	tt=t.get_string().split('\n')
	tt=[(tt[i] if i!=2 else tt[i].replace('-','=')) for i in range(len(tt))]
	# return '.. tabularcolumns:: |l|'+'|'.join(len(ss)*['c'])+'\n\n'
	return '\n'.join(tt)+'\n\n'