Skip to content

Commit

Permalink
Make schema2html an entry point and test it.
Browse files Browse the repository at this point in the history
Port to Python 3.

Note that this doesn't do much validation of whether the output makes
any sense (in multkey defaults, it doesn't), just that it runs. I'll
work on making sense in other PRs if I keep going down the direction
of using this to produce a sphinx extension.
  • Loading branch information
jamadden committed Feb 22, 2017
1 parent 1cc197a commit 4c91df8
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 139 deletions.
133 changes: 133 additions & 0 deletions ZConfig/schema2html.py
@@ -0,0 +1,133 @@
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from __future__ import print_function

import argparse
import sys
import cgi

import ZConfig.loader

from ZConfig.info import SectionType
from ZConfig.info import SectionInfo
from ZConfig.info import ValueInfo

def esc(x):
return cgi.escape(str(x))

def dt(x):
tn = type(x).__name__

if isinstance(x, type):
return '%s %s' % (tn, x.__module__ + '.' + x.__name__)

return '%s %s' % (tn, x.__name__)

class explain(object):
done = []

def __call__(self, st, file=None):
if st.name in self.done: # pragma: no cover
return

self.done.append(st.name)

out = file or sys.stdout

if st.description:
print(st.description, file=out)
for sub in st.getsubtypenames():
print('<dl>', file=out)
printContents(None, st.getsubtype(sub), file=file)
print('</dl>', file=out)

explain = explain()

def printSchema(schema, out):
print('<dl>', file=out)
for child in schema:
printContents(*child, file=out)
print('</dl>', file=out)

def printContents(name, info, file=None):
out = file or sys.stdout

if isinstance(info, SectionType):
print('<dt><b><i>', info.name, '</i></b> (%s)</dt>' % dt(info.datatype), file=out)
print('<dd>', file=out)
if info.description:
print(info.description, file=out)
print('<dl>', file=out)
for sub in info:
printContents(*sub, file=out) # pragma: no cover
print('</dl></dd>', file=out)
elif isinstance(info, SectionInfo):
st = info.sectiontype
if st.isabstract():
print('<dt><b><i>', st.name, '</i>', info.name, '</b></dt>', file=out)
print('<dd>', file=out)
if info.description:
print(info.description, file=out)
explain(st, file=out)
print('</dd>', file=out)
else:
print('<dt><b>', info.attribute, info.name, '</b>', file=out)
print('(%s)</dt>' % dt(info.datatype), file=out)
print('<dd><dl>', file=out)
for sub in info.sectiontype:
printContents(*sub, file=out)
print('</dl></dd>', file=out)
else:
print('<dt><b>',info.name, '</b>', '(%s)' % dt(info.datatype), file=out)
default = info.getdefault()
if isinstance(default, ValueInfo):
print('(default: %r)' % esc(default.value), file=out)
elif default is not None:
print('(default: %r)' % esc(default), file=out)
if info.metadefault:
print('(metadefault: %s)' % info.metadefault, file=out)
print('</dt>', file=out)
if info.description:
print('<dd>',info.description,'</dd>', file=out)

def main(argv=None):
argv = argv or sys.argv[1:]

argparser = argparse.ArgumentParser(
description="Print an HTML version of a schema")
argparser.add_argument(
"schema",
help="The schema to print",
default="-",
type=argparse.FileType('r'))
argparser.add_argument(
"--out", "-o",
help="Write the schema to this file; if not given, write to stdout",
type=argparse.FileType('w'))

args = argparser.parse_args(argv)

out = args.out or sys.stdout

schema = ZConfig.loader.loadSchemaFile(args.schema)

print('''<html><body>
<style>
dl {margin: 0 0 1em 0;}
</style>
''', file=out)
printSchema(schema, out)
print('</body></html>', file=out)

return 0
32 changes: 31 additions & 1 deletion ZConfig/tests/input/simplesections.xml
Expand Up @@ -26,5 +26,35 @@
<key name="var-3" />
<key name="var-4" />
<key name="var-5" />
<key name="var-6" />
<key name="var-6" default="1">
<description>Description</description>
<metadefault>For humans</metadefault>
</key>

<abstracttype name="abstype" abstract="true">
<description>Description</description>
</abstracttype>
<sectiontype name="implabs" implements="abstype">
<description>Description</description>
</sectiontype>
<sectiontype name="extabs" extends="implabs">
<description>Description</description>
</sectiontype>
<sectiontype name="extabs2" extends="implabs">
<description>Description</description>
</sectiontype>
<sectiontype name="extabs3" extends="extabs2">
<description>Description</description>
</sectiontype>
<section type="abstype" name="absinfo">
<description>Description</description>
</section>
<section type="extabs" name="extabs" />
<section type="extabs" name="extabs2" />
<section type="implabs" name="implabs" />
<section type="extabs3" name="implabs2" />
<multikey name="mkey">
<default>1</default>
<default>2</default>
</multikey>
</schema>
23 changes: 23 additions & 0 deletions ZConfig/tests/support.py
Expand Up @@ -14,7 +14,9 @@

"""Support code shared among the tests."""

import contextlib
import os
import sys

import ZConfig

Expand All @@ -30,6 +32,27 @@
def input_file(fname):
return os.path.abspath(os.path.join(INPUT_DIR, fname))

def with_stdin_from_input_file(fname):
input_fname = input_file(fname)
@contextlib.contextmanager
def stdin_replaced():
old_stdin = sys.stdin
sys.stdin = open(input_fname)
try:
yield
finally:
sys.stdin.close()
sys.stdin = old_stdin

def make_wrapper(f):
def f2(self):
with stdin_replaced():
f(self)
return f2

return make_wrapper


class TestHelper(object):
"""Utility methods which can be used with the schema support."""

Expand Down
87 changes: 87 additions & 0 deletions ZConfig/tests/test_schema2html.py
@@ -0,0 +1,87 @@
##############################################################################
#
# Copyright (c) 2017 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from __future__ import absolute_import

import contextlib
import sys
import unittest

try:
# Note that we're purposely using the old
# StringIO object on Python 2 because it auto-converts
# Unicode to str, which io.BytesIO and io.StringIO don't
# but which normal printing to default sys.stdout *does*
from cStringIO import StringIO
except ImportError:
from ZConfig._compat import NStringIO as StringIO


from ZConfig import schema2html

from .support import input_file
from .support import with_stdin_from_input_file


@contextlib.contextmanager
def stdout_replaced(buf):
old_stdout = sys.stdout
sys.stdout = buf
try:
yield
finally:
sys.stdout = old_stdout


def run_transform(*args):
if '--out' not in args and '-o' not in args:
buf = StringIO()
with stdout_replaced(buf):
schema2html.main(args)
return buf
return schema2html.main(args)



class TestSchema2HTML(unittest.TestCase):

def test_no_schema(self):
self.assertRaises(SystemExit,
run_transform)

def test_schema_only(self):
res = run_transform(input_file('simple.xml'))
self.assertIn('</html>', res.getvalue())

@with_stdin_from_input_file('simple.xml')
def test_schema_only_redirect(self):
res = run_transform("-")
self.assertIn('</html>', res.getvalue())

def test_cover_all_schemas(self):
for name in ('base-datatype1.xml',
'base-datatype2.xml',
'base-keytype1.xml',
'base-keytype2.xml',
'base.xml',
'library.xml',
'simplesections.xml',):
res = run_transform(input_file(name))
self.assertIn('</html>', res.getvalue())


def test_suite():
return unittest.makeSuite(TestSchema2HTML)

if __name__ == "__main__":
unittest.main(defaultTest="test_suite")
22 changes: 1 addition & 21 deletions ZConfig/tests/test_validator.py
Expand Up @@ -20,31 +20,11 @@
from ZConfig import validator

from .support import input_file
from .support import with_stdin_from_input_file

def run_validator(*args):
return validator.main(args)

def with_stdin_from_input_file(fname):
input_fname = input_file(fname)
@contextlib.contextmanager
def stdin_replaced():
old_stdin = sys.stdin
sys.stdin = open(input_fname)
try:
yield
finally:
sys.stdin = old_stdin

def make_wrapper(f):
def f2(self):
with stdin_replaced():
f(self)
return f2

return make_wrapper



class TestValidator(unittest.TestCase):

def test_no_schema(self):
Expand Down
8 changes: 8 additions & 0 deletions doc/tools.rst
Expand Up @@ -14,3 +14,11 @@ that can validate both schemas and configurations written against
those schemas:

.. program-output:: zconfig --help

Documenting Schemas
===================

ZConfig also installs a tool called ``zconfig_schema2html`` that can
print schemas in a simple HTML format:

.. program-output:: zconfig_schema2html --help

0 comments on commit 4c91df8

Please sign in to comment.