-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28 from vodik/cleaner
Add sippfmt utility
- Loading branch information
Showing
4 changed files
with
237 additions
and
1 deletion.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import functools | ||
from xml.dom import minidom | ||
from xml.dom.minidom import getDOMImplementation, parse, Node # noqa | ||
|
||
|
||
@functools.total_ordering | ||
class AttributeSorter(object): | ||
"""Special attribute sorter. | ||
Sort elements in alphabetical order, but let special important | ||
attributes bubble to the front. | ||
""" | ||
|
||
__special__ = ('request', 'response') | ||
|
||
def __init__(self, obj): | ||
self.obj = obj | ||
|
||
def __lt__(self, other): | ||
if self.obj in self.__special__: | ||
return True | ||
elif other.obj in self.__special__: | ||
return False | ||
return self.obj < other.obj | ||
|
||
|
||
class Newline(minidom.CharacterData): | ||
"""Minidom node which represents a newline.""" | ||
|
||
__slots__ = () | ||
|
||
nodeType = Node.TEXT_NODE | ||
nodeName = "#text" | ||
attributes = None | ||
|
||
def writexml(self, writer, indent="", addindent="", newl=""): | ||
"""Emit a newline.""" | ||
writer.write(newl) | ||
|
||
|
||
def createSeparator(self): | ||
"""Create a document element which represents a empty line.""" | ||
c = Newline() | ||
c.ownerDocument = self | ||
return c | ||
|
||
|
||
minidom.Document.createSeparator = createSeparator | ||
|
||
|
||
def monkeypatch_scenario_xml(self, writer, indent="", addindent="", newl=""): | ||
"""Ensure there's a newline before the scenario tag. | ||
It needs to be there or SIPp otherwise seems to have trouble | ||
parsing the document. | ||
""" | ||
writer.write('\n') | ||
minidom.Element.writexml(self, writer, indent, addindent, newl) | ||
|
||
|
||
def monkeypatch_element_xml(self, writer, indent="", addindent="", newl=""): | ||
"""Format scenario step elements. | ||
Ensures a stable and predictable order to the attributes of these | ||
elements with the most important information always coming first, | ||
then let all other elements follow in alphabetical order. | ||
""" | ||
writer.write("{}<{}".format(indent, self.tagName)) | ||
|
||
attrs = self._get_attributes() | ||
a_names = sorted(attrs.keys(), key=AttributeSorter) | ||
|
||
for a_name in a_names: | ||
writer.write(" {}=\"".format(a_name)) | ||
minidom._write_data(writer, attrs[a_name].value) | ||
writer.write("\"") | ||
if self.childNodes: | ||
writer.write(">") | ||
if (len(self.childNodes) == 1 and | ||
self.childNodes[0].nodeType == Node.TEXT_NODE): | ||
self.childNodes[0].writexml(writer, '', '', '') | ||
else: | ||
writer.write(newl) | ||
for node in self.childNodes: | ||
node.writexml(writer, indent+addindent, addindent, newl) | ||
writer.write(indent) | ||
writer.write("</{}>{}".format(self.tagName, newl)) | ||
else: | ||
writer.write("/>{}".format(newl)) | ||
|
||
|
||
def monkeypatch_sipp_cdata_xml(self, writer, indent="", addindent="", newl=""): | ||
"""Format CDATA. | ||
Ensure that CDATA blocks are indented as expected, for visual | ||
clarity. | ||
""" | ||
if self.data.find("]]>") >= 0: | ||
raise ValueError("']]>' not allowed in a CDATA section") | ||
|
||
writer.write("{}<![CDATA[{}\n".format(indent, newl)) | ||
|
||
for line in self.data.splitlines(): | ||
x = "{}{}{}".format(indent, addindent, line.strip()).rstrip() | ||
writer.write("{}\n".format(x)) | ||
|
||
writer.write("{}{}]]>{}".format(newl, indent, newl)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
from __future__ import print_function | ||
|
||
import argparse | ||
import types | ||
|
||
from . import minidom | ||
|
||
|
||
def copy_tree(doc, node): | ||
"""Duplicate a minidom element.""" | ||
new_node = doc.createElement(node.tagName) | ||
for k, v in node.attributes.items(): | ||
new_node.setAttribute(k, v) | ||
|
||
for child in node.childNodes: | ||
if child.nodeType == minidom.Node.COMMENT_NODE: | ||
new_node.appendChild(child) | ||
elif child.nodeType == minidom.Node.ELEMENT_NODE: | ||
new_node.appendChild(copy_tree(doc, child)) | ||
|
||
return new_node | ||
|
||
|
||
def monkeypatch_element(node): | ||
"""Alter the representation of scenario elements. | ||
Monkey patch the `writexml` so that when pretty print these | ||
elements, we always put the response/request attributes first, and | ||
then all remaining attributes in alphabetical order. | ||
""" | ||
node.writexml = types.MethodType(minidom.monkeypatch_element_xml, node) | ||
|
||
|
||
def monkeypatch_cdata(node): | ||
"""Alter the representation of CDATA elements. | ||
Monkey patch the `writexml` so that when pretty print these | ||
elements, the appropriate amount of whitespace is embedded into | ||
the CDATA block for visual consistency. | ||
""" | ||
node.writexml = types.MethodType(minidom.monkeypatch_sipp_cdata_xml, node) | ||
|
||
|
||
def process_element(doc, elem): | ||
"""Process individual sections of a sipp scenario. | ||
Copy and format the various steps inside the sipp script, such as | ||
the recv and send elements. Make sure that CDATA chunks are | ||
preserved and well formatted. | ||
""" | ||
new_node = doc.createElement(elem.tagName) | ||
monkeypatch_element(new_node) | ||
|
||
# copy attributes | ||
for k, v in elem.attributes.items(): | ||
new_node.setAttribute(k, v) | ||
|
||
for child in elem.childNodes: | ||
if child.nodeType == minidom.Node.CDATA_SECTION_NODE: | ||
data = doc.createCDATASection(child.data.strip()) | ||
monkeypatch_cdata(data) | ||
new_node.appendChild(data) | ||
elif child.nodeType == minidom.Node.COMMENT_NODE: | ||
new_node.appendChild(child) | ||
elif child.nodeType == minidom.Node.ELEMENT_NODE: | ||
new_node.appendChild(copy_tree(doc, child)) | ||
|
||
return new_node | ||
|
||
|
||
def process_document(filepath): | ||
"""Process an XML document. | ||
Process the document with minidom, process it for consistency, and | ||
emit a new document. Minidom is used since we need to preserve the | ||
structure of the XML document rather than its content. | ||
""" | ||
dom = minidom.parse(filepath) | ||
scenario = next(elem for elem in dom.childNodes | ||
if getattr(elem, 'tagName', None) == 'scenario') | ||
|
||
imp = minidom.getDOMImplementation('') | ||
dt = imp.createDocumentType('scenario', None, 'sipp.dtd') | ||
doc = imp.createDocument(None, 'scenario', dt) | ||
|
||
new_scen = doc.childNodes[-1] | ||
new_scen.writexml = types.MethodType(minidom.monkeypatch_scenario_xml, | ||
new_scen) | ||
|
||
for elem in scenario.childNodes: | ||
if elem.nodeType == minidom.Node.TEXT_NODE: | ||
continue | ||
elif elem.nodeType == minidom.Node.CDATA_SECTION_NODE: | ||
continue | ||
elif elem.nodeType == minidom.Node.ELEMENT_NODE: | ||
new_node = process_element(doc, elem) | ||
if new_node: | ||
new_scen.appendChild(new_node) | ||
new_scen.appendChild(doc.createSeparator()) | ||
else: | ||
new_scen.appendChild(elem) | ||
|
||
# delete the last separator | ||
if (new_scen.childNodes | ||
and isinstance(new_scen.childNodes[-1], minidom.Newline)): | ||
del new_scen.childNodes[-1] | ||
|
||
doc.appendChild(new_scen) | ||
return doc | ||
|
||
|
||
def main(): | ||
"""Format sipp scripts.""" | ||
parser = argparse.ArgumentParser(description='Format sipp scripts') | ||
parser.add_argument('filename') | ||
args = parser.parse_args() | ||
|
||
doc = process_document(args.filename) | ||
xml = doc.toprettyxml(indent=' ', encoding='ISO-8859-1') | ||
print(xml, end='') | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,9 +35,14 @@ | |
author_email='[email protected]', | ||
url='https://github.com/SIPp/pysipp', | ||
platforms=['linux'], | ||
packages=['pysipp'], | ||
packages=['pysipp', 'pysipp.cli'], | ||
install_requires=['pluggy==0.3.1'], | ||
tests_require=['pytest'], | ||
entry_points={ | ||
'console_scripts': [ | ||
'sippfmt=pysipp.cli.sippfmt:main' | ||
], | ||
}, | ||
classifiers=[ | ||
'Development Status :: 3 - Alpha', | ||
'Intended Audience :: Developers', | ||
|