metadoc.py 4.85 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#!/usr/bin/python

# Copyright (C) 2017 Belledonne Communications SARL
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

19 20
import abstractapi

21 22 23 24 25 26

class Nil:
	pass


class Reference:
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
	def __init__(self, cname):
		self.cname = cname
		self.relatedObject = None


class ClassReference(Reference):
	def resolve(self, api):
		try:
			self.relatedObject = api.classesIndex[self.cname]
		except KeyError:
			print('doc reference pointing on an unknown object ({0})'.format(self.cname))


class FunctionReference(Reference):
	def resolve(self, api):
		try:
			self.relatedObject = api.methodsIndex[self.cname]
		except KeyError:
			print('doc reference pointing on an unknown object ({0})'.format(self.cname))
46 47 48


class Paragraph:
49
	def __init__(self):
50 51
		self.parts = []
	
52 53 54 55
	def resolve_all_references(self, api):
		for part in self.parts:
			if isinstance(part, Reference):
				part.resolve(api)
56 57 58


class Description:
59
	def __init__(self):
60 61
		self.paragraphs = []
	
62 63 64 65 66 67 68 69
	def resolve_all_references(self, api):
		for paragraph in self.paragraphs:
			paragraph.resolve_all_references(api)


class Parser:
	def parse_description(self, node):
		desc = Description()
70
		for paraNode in node.findall('./para'):
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
			desc.paragraphs.append(self._parse_paragraph(paraNode))
		return desc
	
	def _parse_paragraph(self, node):
		paragraph = Paragraph()
		for partNode in node.iter():
			if partNode is node:
				text = partNode.text
				if text is not None:
					paragraph.parts.append(text)
			else:
				if partNode.tag == 'ref':
					ref = self._parse_reference(partNode)
					if ref is not None:
						paragraph.parts.append(ref)
				else:
					text = partNode.text
					if text is not None:
						paragraph.parts.append(text)
				
				tail = partNode.tail
				if tail is not None:
					paragraph.parts.append(tail)
		
		return paragraph
	
	def _parse_reference(self, node):
		if node.text.endswith('()'):
			return FunctionReference(node.text[0:-2])
		else:
			return ClassReference(node.text)
102 103 104


class Translator:
105 106 107
	def __init__(self):
		self.textWidth = 80
	
108
	def translate(self, description):
109 110 111
		if description is None:
			return None
		
112 113 114 115
		lines = []
		for para in description.paragraphs:
			if para is not description.paragraphs[0]:
				lines.append('')
116
			lines.append(self._translate_paragraph(para))
117 118
		
		self._tag_as_brief(lines)
119
		lines = self._crop_text(lines, self.textWidth)
120 121 122 123 124 125
		
		translatedDoc = {'lines': []}
		for line in lines:
			translatedDoc['lines'].append({'line': line})
			
		return translatedDoc
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
	
	def _translate_paragraph(self, para):
		strPara = ''
		for part in para.parts:
			if isinstance(part, str):
				strPara += part
			elif isinstance(part, Reference):
				try:
					strPara += self._translate_reference(part)
				except ReferenceTranslationError as e:
					print('could not translate one reference in docstrings ({0})'.format(e.args[0]))
					strPara += Translator._translate_reference(self, part)
			else:
				raise TypeError('untranslatable paragraph element ({0})'.format(part))
		
		return strPara
	
	def _translate_reference(self, ref):
		if isinstance(ref, FunctionReference):
			return ref.cname + '()'
		else:
			return ref.cname
	
	def _crop_text(self, inputLines, width):
		outputLines = []
		for line in inputLines:
			outputLines += self._split_line(line, width)
		return outputLines
	
	def _split_line(self, line, width):
		lines = []
		while len(line) > width:
			cutIndex = line.rfind(' ', 0, width)
			if cutIndex != -1:
				lines.append(line[0:cutIndex])
				line = line[cutIndex+1:]
			else:
				cutIndex = width
				lines.append(line[0:cutIndex])
				line = line[cutIndex:]
		
		lines.append(line)
		return lines


class ReferenceTranslationError(RuntimeError):
	pass
173 174 175 176 177 178


class DoxygenCppTranslator(Translator):
	def _tag_as_brief(self, lines):
		if len(lines) > 0:
			lines[0] = '@brief ' + lines[0]
179 180 181 182 183 184 185 186
	
	def _translate_reference(self, ref):
		if isinstance(ref.relatedObject, (abstractapi.Class, abstractapi.Enum)):
			return '#' + ref.relatedObject.name.to_c()
		elif isinstance(ref.relatedObject, abstractapi.Method):
			return ref.relatedObject.name.to_c() + '()'
		else:
			raise ReferenceTranslationError(ref.cname)
187 188 189 190 191


class SandcastleCSharpTranslator(Translator):
	def _tag_as_brief(self, lines):
		if len(lines) > 0:
192 193
			lines.insert(0, '<summary>')
			lines.append('</summary>')