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}
|