# -*- coding: utf-8 -*-
# Description: DocumentTree macro
# Detail: Generates a hierarchical link-tree of the document-tree.
import fnmatch
import re
from Remark.Macro_Registry import registerMacro
from Remark.FileSystem import markdownRegion, globToRegex, combineRegex
class DocumentTree_Macro(object):
def name(self):
return 'DocumentTree'
def expand(self, parameter, remark):
# Precomputation
self.remark = remark
self.document = remark.document
self.documentTree = remark.documentTree
scope = remark.scopeStack.top()
# Variables
self.minDepth = scope.getInteger('DocumentTree.min_depth', 1)
self.maxDepth = scope.getInteger('DocumentTree.max_depth', 10)
self.rootName = scope.getString('DocumentTree.root_document', self.document.fileName)
self.className = scope.getString('DocumentTree.class_name', 'DocumentTree')
self.includeGlob = scope.get('DocumentTree.include', ['file_name *'])
self.includeRegex = scope.get('DocumentTree.include_regex')
self.excludeGlob = scope.get('DocumentTree.exclude')
self.excludeRegex = scope.get('DocumentTree.exclude_regex')
self.compact = scope.getInteger('DocumentTree.compact', 1)
self.includeMap = {}
self._parse(self.includeGlob, self.includeMap, globToRegex)
self._parse(self.includeRegex, self.includeMap, lambda x: x + r'\Z')
self.includeFilter = {}
for tagName, regex in self.includeMap.items():
self.includeFilter[tagName] = re.compile(combineRegex(regex))
self.excludeMap = {}
self._parse(self.excludeGlob, self.excludeMap, globToRegex)
self._parse(self.excludeRegex, self.excludeMap, lambda x: x + r'\Z')
self.excludeFilter = {}
for tagName, regex in self.excludeMap.items():
self.excludeFilter[tagName] = re.compile(combineRegex(regex))
rootDocument, unique = self.documentTree.findDocument(self.rootName,
self.document.relativeDirectory)
if rootDocument == None:
self.remark.reporter.reportMissingDocument(self.rootName)
return []
if not unique:
self.remark.reporter.reportAmbiguousDocument(self.rootName)
# Start reporting the document-tree using the
# given root document.
self.visitedSet = set()
text = ['']
self._workDocument(rootDocument, text, 0)
if text == ['']:
return []
return markdownRegion(
remark.convert(text),
{'class' : self.className})
def expandOutput(self):
return False
def htmlHead(self, remark):
return []
def postConversion(self, remark):
None
def _parse(self, globSet, map, transform):
for line in globSet:
pairSet = line.split()
if len(pairSet) == 0:
continue
if len(pairSet) == 1:
self.remark.reportWarning(
line +
' missing either tag-name or tag-value. Ignoring it.',
'invalid-input')
continue;
if len(pairSet) > 2:
self.remark.reportWarning(
line + ' has too many parameters. Ignoring it.',
'invalid-input')
continue
tagName = pairSet[0].strip()
tagValue = pairSet[1].strip()
regex = transform(tagValue)
if not tagName in map:
map[tagName] = [regex]
else:
map[tagName].append(regex)
def _workDocument(self, document, text, depth):
# Limit the reporting to given maximum depth.
if depth > self.maxDepth:
return False, False
# Protect against self-recursion.
selfRecursive = document in self.visitedSet
if not selfRecursive:
self.visitedSet.add(document)
# Sort the children by link-description.
childSet = list(document.childSet.values())
childSet.sort(key = lambda x: x.linkDescription())
# Recurse to output the children, but only
# if we have not visited this document before.
localText = []
childMatches = 0
usefulBranches = 0
if not selfRecursive:
for child in childSet:
match, useful = self._workDocument(child, localText, depth + 1)
if match:
childMatches += 1
if useful:
usefulBranches += 1
# Filter by exclusion.
exclude = False
for tagName, tagRegex in self.excludeFilter.items():
excludeValue = document.tagString(tagName).strip()
if tagRegex.match(excludeValue) != None:
exclude = True
break
# Filter by inclusion.
include = False
for tagName, tagRegex in self.includeFilter.items():
includeValue = document.tagString(tagName).strip()
if tagRegex.match(includeValue) != None:
include = True
break
match = (not exclude) and include
# In the non-compacting mode,
# we will report a document if and only if
#
# * it has the proper depth, and
# * it matches or it has matching descendants.
#
# This retains the parent-child relation.
report = ((match or usefulBranches > 0) and
depth >= self.minDepth)
if self.compact != 0:
# In the compacting mode, we will report
# a document if and only if
#
# * it has the proper depth, and
# * it matches, or its child matches, or it
# has at least two children which have
# matching descendants.
#
# This retains the ancestor-descendant
# relation, but not the parent-child relation.
report = ((match or
childMatches > 0 or
usefulBranches > 1) and
depth >= self.minDepth)
if report:
# Add this document to the list of links.
linkText = self.remark.remarkLink(
document.linkDescription(),
self.document, document)
text.append(' 1. ' + linkText)
#text.append('')
for line in localText:
text.append('\t' + line)
else:
text += localText
return match, (usefulBranches > 0 or match)
registerMacro('DocumentTree', DocumentTree_Macro())