Create ipasphinx package for Sphinx plugins

Sphinx is extensible with plugins that can add new syntax, roles,
directives, domains, and more.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Christian Heimes 2020-04-08 11:54:28 +02:00
parent 87408ee755
commit e00dc40fea
11 changed files with 248 additions and 15 deletions

View File

@ -3,7 +3,7 @@ NULL =
ACLOCAL_AMFLAGS = -I m4
if ENABLE_SERVER
IPASERVER_SUBDIRS = ipaserver
IPASERVER_SUBDIRS = ipaserver ipasphinx
SERVER_SUBDIRS = daemons init install
endif

View File

@ -598,6 +598,7 @@ AC_CONFIG_FILES([
ipalib/Makefile
ipaplatform/Makefile
ipapython/Makefile
ipasphinx/Makefile
ipaserver/Makefile
ipatests/Makefile
ipatests/man/Makefile

View File

@ -10,9 +10,10 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
import sys
# insert parent directory with ipalib and ipasphinx
sys.path.insert(0, os.path.abspath('..'))
# -- Project information -----------------------------------------------------
@ -35,10 +36,10 @@ master_doc = 'index'
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'ipasphinx.ipabase',
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'm2r',
]

View File

@ -1,6 +1,28 @@
# m2r is not compatible with Sphinx 3.x
## m2r is not compatible with Sphinx 3.x yet
sphinx < 3.0
# convert markdown to rest
## convert markdown to rest
m2r
## ipa dependencies
dnspython
jwcrypto
netaddr
qrcode
six
pyasn1
pyasn1-modules
requests
## C libraries with binary wheels
cffi
cryptography
lxml
## C libraries without binaries wheels
# gssapi
# dbus-python
# python-ldap
## No sufficient PyPI packages
# dogtag-pki

View File

@ -819,6 +819,9 @@ make %{?_smp_mflags} check VERBOSE=yes LIBDIR=%{_libdir}
%make_install
# don't package ipasphinx for now
rm -rf %{buildroot}%{python3_sitelib}/ipasphinx*
%if 0%{?with_ipatests}
mv %{buildroot}%{_bindir}/ipa-run-tests %{buildroot}%{_bindir}/ipa-run-tests-%{python3_version}
mv %{buildroot}%{_bindir}/ipa-test-config %{buildroot}%{_bindir}/ipa-test-config-%{python3_version}

View File

@ -510,14 +510,17 @@ class API(ReadOnly):
level = logging.INFO
if self.env.debug: # pylint: disable=using-constant-test
level = logging.DEBUG
try:
handler = logging.FileHandler(self.env.log)
except IOError as e:
logger.error('Cannot open log file %r: %s', self.env.log, e)
return
handler.setLevel(level)
handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_FILE))
root_logger.addHandler(handler)
if self.env.log is not None:
try:
handler = logging.FileHandler(self.env.log)
except IOError as e:
logger.error('Cannot open log file %r: %s', self.env.log, e)
else:
handler.setLevel(level)
handler.setFormatter(
ipa_log_manager.Formatter(LOGGING_FORMAT_FILE)
)
root_logger.addHandler(handler)
def build_global_parser(self, parser=None, context=None):
"""

View File

@ -304,6 +304,10 @@ class Gettext(LazyText):
def format(self, *args, **kwargs):
return unicode(self).format(*args, **kwargs)
def expandtabs(self, tabsize=8):
"""Compatibility for sphinx prepare_docstring()"""
return str(self).expandtabs(tabsize)
@six.python_2_unicode_compatible
class FixMe(Gettext):
@ -524,6 +528,10 @@ class ConcatenatedLazyText:
else:
return ConcatenatedLazyText(*[other] + self.components)
def expandtabs(self, tabsize=8):
"""Compatibility for sphinx prepare_docstring()"""
return str(self).expandtabs(tabsize)
class GettextFactory:
"""

1
ipasphinx/Makefile.am Normal file
View File

@ -0,0 +1 @@
include $(top_srcdir)/Makefile.python.am

0
ipasphinx/__init__.py Normal file
View File

173
ipasphinx/ipabase.py Normal file
View File

@ -0,0 +1,173 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""IPA API initialization for Sphinx
"""
import os
import re
import sys
from sphinx.util import progress_message
from sphinx.ext.autodoc import mock as autodoc_mock
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(HERE, os.pardir))
VERSION_M4 = os.path.abspath(os.path.join(ROOT, "VERSION.m4"))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
ipa_mock_imports = [
# no binary wheels available
"dbus",
"gssapi",
"ldap",
"ldif", # python-ldap
"ldapurl", # python-ldap
# dogtag-pki is client-only
"pki",
# PyPI packages not available
"pyhbac",
"pysss",
"pysss_murmur",
"pysss_nss_idmap",
"samba",
"SSSDConfig",
]
def parse_version_m4(filename=VERSION_M4):
"""Poor man's macro parser for VERSION.m4
"""
def_re = re.compile(r"^define\(([\w]+)+,\s*(.*)\)\s*$")
defs = {}
with open(filename) as f:
for line in f:
mo = def_re.match(line)
if mo is not None:
k, v = mo.groups()
try:
v = int(v)
except ValueError:
pass
defs[k] = v
defs["IPA_NUM_VERSION"] = (
"{IPA_VERSION_MAJOR:d}"
"{IPA_VERSION_MINOR:02d}"
"{IPA_VERSION_RELEASE:02d}"
).format(**defs)
defs["IPA_API_VERSION"] = (
"{IPA_API_VERSION_MAJOR}.{IPA_API_VERSION_MINOR}"
).format(**defs)
if defs["IPA_VERSION_IS_GIT_SNAPSHOT"] == "yes":
defs["IPA_GIT_VERSION"] = ".dev"
else:
defs["IPA_GIT_VERSION"] = ""
defs["IPA_VERSION"] = (
"{IPA_VERSION_MAJOR}."
"{IPA_VERSION_MINOR}."
"{IPA_VERSION_RELEASE}"
"{IPA_VERSION_PRE_RELEASE}"
"{IPA_GIT_VERSION}"
).format(**defs)
return defs
def fake_ipaython_version(defs):
"""Fake ipapython.version module
We don't want and cannot run autoconf on read the docs. Fake the auto-
generated ipapython.version module.
"""
class FakeIpapythonVersion:
__name__ = "ipapython.version"
VERSION = defs["IPA_VERSION"]
VENDOR_VERSION = defs["IPA_VERSION"]
NUM_VERSION = defs["IPA_NUM_VERSION"]
API_VERSION = defs["IPA_API_VERSION"]
DEFAULT_PLUGINS = frozenset()
fake = FakeIpapythonVersion()
sys.modules[fake.__name__] = fake
def init_api(
context="doc",
domain="ipa.example",
server="server.ipa.example",
in_server=True,
):
import ipalib
ipalib.api.bootstrap(
context=context,
in_server=in_server,
logdir=None,
log=None,
domain=domain,
realm=domain.upper(),
server=server,
)
ipalib.api.finalize()
return ipalib.api
def inject_mock_imports(app, config):
"""Add additional module mocks for ipaserver
"""
mock_imports = set(getattr(config, "autodoc_mock_imports", []))
mock_imports.update(ipa_mock_imports)
config.autodoc_mock_imports = list(mock_imports)
# ldap is a mocked package
# ensure that ipapython.dn still use ctypes wrappers for str2dn/dn2str
# otherwise api won't be able to initialize properly
import ipapython.dn
assert ipapython.dn.str2dn("cn=ipa") == [[("cn", "ipa", 1)]]
def init_ipalib_api(app, config):
"""Initialize ipalib.api
1. Parse VERSION.m4
2. Create fake ipapython.version module
3. Initialize the API with mocked imports
"""
defs = parse_version_m4()
fake_ipaython_version(defs)
with progress_message("initializing ipalib.api"):
with autodoc_mock(config.autodoc_mock_imports):
init_api(
context=config.ipa_context,
domain=config.ipa_domain,
server=config.ipa_server_fqdn,
in_server=config.ipa_in_server,
)
def setup(app):
app.setup_extension("sphinx.ext.autodoc")
app.add_config_value("ipa_context", "doc", "env")
app.add_config_value("ipa_domain", "ipa.example", "env")
app.add_config_value("ipa_server_fqdn", "server.ipa.example", "env")
app.add_config_value("ipa_in_server", True, "env")
app.connect("config-inited", inject_mock_imports)
app.connect("config-inited", init_ipalib_api)
return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}

21
ipasphinx/setup.py Normal file
View File

@ -0,0 +1,21 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""Sphinx documentation plugins for IPA
"""
from os.path import abspath, dirname
import sys
if __name__ == "__main__":
# include ../ for ipasetup.py
sys.path.append(dirname(dirname(abspath(__file__))))
from ipasetup import ipasetup # noqa: E402
ipasetup(
name="ipasphinx",
doc=__doc__,
package_dir={"ipasphinx": ""},
packages=["ipasphinx"],
# m2r is not compatible with Sphinx 3.x yet
install_requires=["ipaserver", "ipalib", "sphinx < 3.0", "m2r"],
)