Commit 8bc2842d authored by Elisabetta Giani's avatar Elisabetta Giani
Browse files

Merge branch 'revert-docs' into 'master'

revert doc generation

See merge request ska-telescope/csp-lmc!5
parents 1e79f1c8 50df9fc7
Loading
Loading
Loading
Loading
Loading

.readthedocs.yml

deleted100644 → 0
+0 −2
Original line number Diff line number Diff line
conda:
    file: docs/src/environment.yml
+2 −272
Original line number Diff line number Diff line
@@ -19,18 +19,9 @@ import sys
sys.path.append(os.path.abspath('../../csp-lmc-common/'))
sys.path.append(os.path.abspath('../../csp-lmc-mid/'))

sys.path.append(os.path.abspath('./'))
# Import tango
try:
    import tango
except ImportError:
    from mock_tango_extension import tango
from tango import Release
print("Building documentation for PyTango {0}".format(Release.version_long))
print("Using PyTango from: {0}".format(os.path.dirname(tango.__file__)))

#import tango
#import skabase
autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command',
autodoc_mock_imports = ['PyTango', 'tango', 'tango-server','run', 'DeviceMeta', 'command',
                 'future', 'future.utils', 'logging', 'logging.handlers', 'ska',
                 'ska.base', 'SKAMaster', 'SKASubarray','numpy'
                 ]
@@ -226,264 +217,3 @@ intersphinx_mapping = {'https://docs.python.org/': None}

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

def copy_spaces(origin):
    r = ''
    for x in range(len(origin)):
        if origin[x] in (' ', '\t'):
            r += origin[x]
        else:
            return r
    return r

def type_to_link(tipus):
        if tipus[:9] == 'sequence<' and tipus[-1:] == '>':
            return 'sequence<' + type_to_link(tipus[9:-1]) + '>'
        #elif tipus in dir(PyTango):
        else:
            return ':class:`' + tipus + "`"
        #else:
        #    return tipus

def type_to_pytango_link(tipus):
        if tipus[:9] == 'sequence<' and tipus[-1:] == '>':
            return 'sequence<' + type_to_link(tipus[9:-1]) + '>'
        elif tipus in dir(tango):
            return ':class:`' + tipus + "`"
        else:
           return tipus

def possible_type_to_link(text):
    if len(text) and text[0] == '(' and text[-1] == ')':
        return '(' + type_to_link(text[1:-1]) +')'
    return text

def parse_typed_line(line):
    spacesSplit = line.strip().split(' ')
    first = spacesSplit[0].strip()
    return possible_type_to_link(first) + ' ' + ' '.join(spacesSplit[1:])

def parse_parameters(line):
    spaces = copy_spaces(line)
    miniLine = line.strip()

    if miniLine[:2] != '- ':
        return line

    spl = miniLine[2:].split(':', 1)

    assert(len(spl) == 2)

    return spaces + ':' + spl[0].strip() + ': ' + parse_typed_line(spl[1])


def parse_bullet_with_type(line):
    spaces = copy_spaces(line)
    miniLine = line.strip()

    if miniLine[:2] not in ['- ', '* ']:
        return line

    spl = miniLine.split(':', 1)

    if len(spl) != 2:
        return line

    return spaces + spl[0] + ': ' + parse_typed_line(spl[1])


def parse_throws(line):
    words = re.split('(\W+)', line)
    assert(line == ''.join(words))
    return ''.join(map(type_to_pytango_link, words))


# http://codedump.tumblr.com/post/94712647/handling-python-docstring-indentation
def docstring_to_lines(docstring):
    if not docstring:
        return []
    lines = docstring.expandtabs().splitlines()

    # Determine minimum indentation (first line doesn't count):
    indent = sys.maxint
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))

    # Remove indentation (first line is special):
    trimmed = [lines[0].strip()]
    if indent < sys.maxint:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())

    # Strip off trailing and leading blank lines:
    while trimmed and not trimmed[-1]:
        trimmed.pop()
    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    return trimmed

def search_ONLY_signature(name, text):
    lines = docstring_to_lines(text)

    # There should be ONE signature and must be the FIRST text
    # Signature is the ONLY starting at position 0

    signatureLine = None

    for ln in range(len(lines)):
        line = lines[ln]

        if len(line.strip()) and line[0] != ' ':
            parentesis = line.split('(', 1)
            fname = parentesis[0].strip()
            if len(parentesis)==2 and fname == name.rsplit('.',1)[1]:
                if signatureLine is not None: # More than one signature!
                    return None
                signatureLine = ln
            else:
                return None # There's a text as FIRST text that's NOT the signature!

    if signatureLine is None:
        return None

    return lines[signatureLine]

def split_signature(text):
    if text is None:
        return None

    # split "fname(params)", "returntype"
    ops = text.split('->')
    if len(ops) != 2:
        return None

    # get rid of "fname"
    params = ops[0].strip()
    ret_type = ops[1].strip()
    p = params.find('(')
    if p < 0:
        return None
    params = params[p:]
    return params, ret_type



_with_only_one_signature_methods = {}

def __reformat_lines(app, what, name, obj, options, lines):
    global _with_only_one_signature_methods
    if what != 'method':
        for ln in range(len(lines)):
            lines[ln] = parse_bullet_with_type(lines[ln])
        return

    toinsert = []
    parsingParameters = False
    parsingThrows = False

    toinsert.append((0, ""))

    for ln in range(len(lines)):
        line = lines[ln]

        if len(line) and line[0] != ' ':
            if name in _with_only_one_signature_methods:
                # This method has one and only one signature. So it will
                # be displayed by sphinx, there's no need for us to fake
                # it here...
                lines[ln] = ""
            else:
                parentesis = line.split('(', 1)
                fname = parentesis[0].strip()
                if len(parentesis)==2 and fname == name.rsplit('.',1)[1]:
                    sg = split_signature(line)
                    if sg is not None:
                        # Main lines are like small titles (**bold**):
                        lines[ln] = '**' + fname +'** *' + sg[0] + '* **->** ' + type_to_link(sg[1])
                        # Add an ENTER after the title, to make a different
                        # paragraph. So if I have 2 signatures, there's no problem
                        # with it...
                        toinsert.append((ln+1, ""))

                    ## Main lines are like small titles (**bold**):
                    #lines[ln]='**' + line.strip() + '**'
                    ## Add an ENTER after the title, to make a different
                    ## paragraph. So if I have 2 signatures, there's no problem
                    ## with it...
                    #toinsert.append((ln+1, ""))


        # Mark the "New in this version" lines...
        if line.strip()[:14] == "New in PyTango":
            lines[ln] = copy_spaces(lines[ln]) + "*" + line.strip() + "*"
            parsingParameters = False
            parsingThrows = False

        # Look for special control_words
        # To replace the actual syntax: "Return   : something"
        # with the one understood by reStructuredText ":Return: something"
        spl = line.strip().split(':', 1)
        control_word = spl[0].strip()

        if ((len(spl) != 2)
            or (control_word not in ["Parameters", "Return", "Throws", "Example", "See Also" ]) ):
                if parsingParameters:
                    lines[ln] = parse_parameters(line)
                elif parsingThrows:
                    lines[ln] = parse_throws(line)
                continue

        parsingParameters = False
        parsingThrows = False
        spaces = copy_spaces(line)

        # The Example control word is even more special. I will put
        # the contents from the following line into a code tag (::)
        if control_word == 'Example':
            lines[ln] = spaces + ":" + control_word + ": " + spl[1]
            toinsert.append((ln+1, ""))
            toinsert.append((ln+1, spaces + ' ::'))
            toinsert.append((ln+1, ""))
        elif control_word == 'Parameters':
            lines[ln] = spaces + ":Parameters:" + parse_parameters(spl[1])
            parsingParameters = True
        elif control_word == 'Return':
            lines[ln] = spaces + ":Return: " + parse_typed_line(spl[1])
        elif control_word == "Throws":
            lines[ln] = spaces + ":Throws:" + parse_throws(spl[1])
            parsingThrows = True
        else:
            lines[ln] = spaces + ":" + control_word + ": " + spl[1]

    for x in range(len(toinsert)-1, -1, -1):
        pos, txt = toinsert[x]
        lines.insert(pos, txt)


def __process_signature(app, what, name, obj, options, signature, return_annotation):
    global _with_only_one_signature_methods
    if what != 'method':
        return
    sg = split_signature(search_ONLY_signature(name, obj.__doc__))
    if sg is not None:
        _with_only_one_signature_methods[name] = True
        return sg
    return (signature, return_annotation)

def setup(app):
    # sphinx will call these methods when he finds an object to document.
    # I want to edit the docstring to adapt its format to something more
    # beautiful.
    # I also want to edit the signature because boost methods have no
    # signature. I will read the signature from the docstring.
    # The order sphinx will call it is __process_signature, __reformat_lines.
    # And it is important because I keep some information between the two
    # processes
    # Problem is __process_signature works great with python methods...
    # but is not even called for methods defined by boost. So, as it is,
    # is useless now.

    #app.connect('autodoc-process-signature', __process_signature)
    app.connect('autodoc-process-docstring', __reformat_lines)

docs/src/environment.yml

deleted100644 → 0
+0 −10
Original line number Diff line number Diff line
name: py3
dependencies:
- python=3
- numpy
- gevent
- graphviz
- mock
- pillow
- sphinx
- sphinx_rtd_theme

docs/src/mock_tango_extension.py

deleted100644 → 0
+0 −133
Original line number Diff line number Diff line
"""Mock the tango._tango extension module.

This is useful to build the documentation without building the extension.
However this is a bit tricky since the python side relies on what the
extension exposes. Here is the list of the mocking aspects that require
special attention:

  - __doc__ should not contain the mock documentation
  - __mro__ is required for autodoc
  - __name__ attribute is required
  - Device_6Impl class should not be accessible
  - the __base__ attribute for Device_[X]Impl is required
  - it shoud be possible to set __init__, __getattr__ and __setattr__ methods
  - tango.base_types.__document_enum needs to be patched before it is called
  - tango.base_types.__document_method needs to be patched before it is called
  - the mocks should not have any public methods such as assert_[...]
  - _tango.constants.TgLibVers is required (e.g. '9.2.2')
  - _tango._get_tango_lib_release function is required (e.g. lambda: 922)
  - tango._tango AND tango.constants modules have to be patched
  - autodoc requires a proper inheritance for the device impl classes

Patching tango._tango using sys.modules does not seem to work for python
version older than 3.5 (failed with 2.7 and 3.4)
"""

# Imports
import sys
from unittest.mock import MagicMock

__all__ = ('tango',)


# Constants
TANGO_VERSION = '9.2.2'
TANGO_VERSION_INT = int(TANGO_VERSION[::2])


# Extension mock class
class ExtensionMock(MagicMock):

    # Remove the mock documentation
    __doc__ = None

    # The method resolution order is required for autodoc
    __mro__ = object,

    @property
    def __name__(self):
        # __name__ is used for some objects
        if self._mock_name is None:
            return ''
        return self._mock_name.split('.')[-1]

    def __getattr__(self, name):
        # Limit device class discovery
        if name == 'Device_6Impl':
            raise AttributeError
        # Regular mock behavior
        return MagicMock.__getattr__(self, name)

    def __setattr__(self, name, value):
        # Ignore unsupported magic methods
        if name in ["__init__", "__getattr__", "__setattr__",
                    "__str__", "__repr__"]:
            return
        # Hook as soon as possible and patch the documentation methods
        if name == 'side_effect' and self.__name__ == 'AccessControlType':
            import tango.utils
            import tango.base_types
            import tango.device_server
            import tango.connection
            tango.utils.__dict__['document_enum'] = document_enum
            tango.utils.__dict__['document_method'] = document_method
            tango.base_types.__dict__['__document_enum'] = document_enum
            tango.device_server.__dict__['__document_method'] = document_method
            tango.connection.__dict__['__document_method'] = document_method
            tango.connection.__dict__['__document_static_method'] = document_method
        MagicMock.__setattr__(self, name, value)


# Remove all public methods
for name in dir(ExtensionMock):
    if not name.startswith('_') and \
       callable(getattr(ExtensionMock, name)):
        setattr(ExtensionMock, name, None)


# Patched version of document_enum
def document_enum(klass, enum_name, desc, append=True):
    getattr(klass, enum_name).__doc__ = desc


# Patched version of document_enum
def document_method(klass, name, doc, add=True):
    method = lambda self: None
    method.__doc__ = doc
    method.__name__ = name
    setattr(klass, name, method)


# Use empty classes for device impl inheritance scheme
def set_device_implementations(module):
    attrs = {'__module__': module.__name__}
    module.DeviceImpl = type('DeviceImpl', (object,), attrs)
    module.Device_2Impl = type('Device_2Impl', (module.DeviceImpl,), attrs)
    module.Device_3Impl = type('Device_3Impl', (module.Device_2Impl,), attrs)
    module.Device_4Impl = type('Device_4Impl', (module.Device_3Impl,), attrs)
    module.Device_5Impl = type('Device_5Impl', (module.Device_4Impl,), attrs)


# Use empty classes for device proxy inheritance scheme
def set_device_proxy_implementations(module):
    attrs = {'__module__': module.__name__}
    module.Connection = type('Connection', (object,), attrs)
    module.DeviceProxy = type('DeviceProxy', (module.Connection,), attrs)


# Patch the extension module
_tango = ExtensionMock(name='_tango')
_tango.constants.TgLibVers = TANGO_VERSION
_tango._get_tango_lib_release.return_value = TANGO_VERSION_INT
set_device_implementations(_tango)
set_device_proxy_implementations(_tango)


# Patch modules
sys.modules['tango._tango'] = _tango
sys.modules['tango.constants'] = _tango.constants
print('Mocking tango._tango extension module')


# Try to import
import tango