File: term.py

package info (click to toggle)
watcher 15.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,216 kB
  • sloc: python: 52,260; xml: 323; sh: 299; makefile: 78
file content (181 lines) | stat: -rw-r--r-- 5,949 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
173
174
175
176
177
178
179
180
181
# Copyright (c) 2015 b<>com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import importlib
import inspect

from docutils import nodes
from docutils.parsers import rst
from docutils import statemachine

from watcher.version import version_string


class BaseWatcherDirective(rst.Directive):

    def __init__(self, name, arguments, options, content, lineno,
                 content_offset, block_text, state, state_machine):
        super(BaseWatcherDirective, self).__init__(
            name, arguments, options, content, lineno,
            content_offset, block_text, state, state_machine)
        self.result = statemachine.ViewList()

    def run(self):
        raise NotImplementedError('Must override run() is subclass.')

    def add_line(self, line, *lineno):
        """Append one line of generated reST to the output."""
        # Provide a proper source string to avoid issues with newer docutils
        # Try to get the actual source file, fallback to class name
        source = getattr(self.state.document, 'current_source', None)
        if source is None:
            source = f'<watcher-{self.__class__.__name__.lower()}>'

        # Handle lineno properly - use the first arg from *lineno if provided, otherwise use self.lineno
        actual_lineno = lineno[0] if lineno else getattr(self, "lineno", 0)

        # For newer docutils, ensure source is always a string
        self.result.append(line, source, actual_lineno)

    def add_textblock(self, textblock):
        base_lineno = getattr(self, "lineno", 0)
        for i, line in enumerate(textblock.splitlines()):
            self.add_line(line, base_lineno + i)

    def add_object_docstring(self, obj):
        obj_raw_docstring = obj.__doc__ or ""

        # Maybe it's within the __init__
        if not obj_raw_docstring and hasattr(obj, "__init__"):
            if obj.__init__.__doc__:
                obj_raw_docstring = obj.__init__.__doc__

        if not obj_raw_docstring:
            # Raise a warning to make the tests fail with doc8
            raise self.error("No docstring available for %s!" % obj)

        obj_docstring = inspect.cleandoc(obj_raw_docstring)
        self.add_textblock(obj_docstring)


class WatcherTerm(BaseWatcherDirective):
    """Directive to import an RST formatted docstring into the Watcher glossary

    **How to use it**

    # inside your .py file
    class DocumentedObject(object):
        '''My *.rst* docstring'''


    # Inside your .rst file
    .. watcher-term:: import.path.to.your.DocumentedObject

    This directive will then import the docstring and then interpret it.
    """

    # You need to put an import path as an argument for this directive to work
    required_arguments = 1

    def run(self):
        cls_path = self.arguments[0]

        try:
            try:
                cls = importlib.import_module(cls_path)
            except ImportError:
                module_name, cls_name = cls_path.rsplit('.', 1)
                mod = importlib.import_module(module_name)
                cls = getattr(mod, cls_name)
        except Exception as exc:
            raise self.error(exc)

        self.add_object_docstring(cls)

        node = nodes.paragraph()
        node.document = self.state.document
        self.state.nested_parse(self.result, 0, node)
        return node.children


class WatcherFunc(BaseWatcherDirective):
    """Directive to import a value returned by a func into the Watcher doc

    **How to use it**

    # inside your .py file
    class Bar(object):

        def foo(object):
            return foo_string


    # Inside your .rst file
    .. watcher-func:: import.path.to.your.Bar.foo node_classname

    node_classname is decumented here:
    http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html

    This directive will then import the value and then interpret it.
    """

    # You need to put an import path as an argument for this directive to work
    # required_arguments = 1
    # optional_arguments = 1

    option_spec = {'format': rst.directives.unchanged}
    has_content = True

    def run(self):
        if not self.content:
            error = self.state_machine.reporter.error(
                'The "%s" directive is empty; content required.' % self.name,
                nodes.literal_block(self.block_text, self.block_text),
                line=self.lineno)
            return [error]

        func_path = self.content[0]
        try:
            cls_path, func_name = func_path.rsplit('.', 1)
            module_name, cls_name = cls_path.rsplit('.', 1)
            mod = importlib.import_module(module_name)
            cls = getattr(mod, cls_name)
        except Exception as exc:
            raise self.error(exc)

        cls_obj = cls()
        func = getattr(cls_obj, func_name)
        textblock = func()
        if not isinstance(textblock, str):
            textblock = str(textblock)

        self.add_textblock(textblock)

        try:
            node_class = getattr(nodes,
                                 self.options.get('format', 'paragraph'))
        except Exception as exc:
            raise self.error(exc)

        node = node_class()
        node.document = self.state.document
        self.state.nested_parse(self.result, 0, node)
        return [node]


def setup(app):
    app.add_directive('watcher-term', WatcherTerm)
    app.add_directive('watcher-func', WatcherFunc)
    return {'version': version_string}