Commit 050be97e authored by Matthias Meschede's avatar Matthias Meschede Committed by Martin Owens

Add typehints for documentation with checks and ci build config.

parent cf9e2a63
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# 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, '/home/matto/Projects/inkscape/extensions/inkex')
# -- Project information -----------------------------------------------------
project = 'inkex'
copyright = '2020, Author'
author = 'Author'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx_autodoc_typehints',
'sphinx.ext.viewcode',
'sphinx.ext.todo',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Extension configuration -------------------------------------------------
always_document_param_types = True
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
......@@ -87,11 +87,25 @@ test:validate-inx:
- xmllint --noout --relaxng inkscape.extension.rng *.inx
mypy:
stage: test
when: always
script:
- source /root/pyenv-init
- pyenv shell 3.7.2
- pip install mypy
- mypy inkex --ignore-missing-imports
sphinx:
stage: test
when: always
script:
- source /root/pyenv-init
- pyenv shell 3.7.2
- pip3 install sphinx-autodoc-typehints
- pip3 install typed-ast
- sphinx-apidoc -F -o source inkex
- cp .gitlab-ci.sphinx-conf.py source/conf.py
- python3 setup.py build_sphinx -s source
- echo -e "\n\n"
"Documentation for inkex module successfully created; you can access the HTML version at\n"
......
......@@ -37,7 +37,7 @@ stdout = sys.stdout
if PY3:
unicode = str # pylint: disable=redefined-builtin,invalid-name
basestring = str # pylint: disable=redefined-builtin,invalid-name
stdout = sys.stdout.buffer
stdout = sys.stdout.buffer # type: ignore
class InkscapeExtension(object):
......
......@@ -338,7 +338,7 @@ def ensure_value(self, attr, value):
setattr(self, attr, value)
return getattr(self, attr)
argparse.Namespace.ensure_value = ensure_value
argparse.Namespace.ensure_value = ensure_value # type: ignore
@deprecate
def zSort(inNode, idList):
......
......@@ -30,18 +30,24 @@ from collections import defaultdict
from copy import deepcopy
from lxml import etree
from ..paths import Path
from ..paths import Path, BoundingBox
from ..styles import Style, AttrFallbackStyle, Classes
from ..transforms import Transform
from ..utils import PY3, NSS, addNS, removeNS, InitSubClassPy3, FragmentError
try:
from typing import overload, DefaultDict, Type, Any, List, Tuple, Union, Optional # pylint: disable=unused-import
except ImportError:
overload = lambda x: x
class NodeBasedLookup(etree.PythonElementClassLookup):
"""
We choose what kind of Elements we should return for each element, providing useful
SVG based API to our extensions system.
"""
# (ns,tag) -> list(cls) ; ascending priority
lookup_table = defaultdict(list)
lookup_table = defaultdict(list) # type: DefaultDict[str, List[Any]]
@classmethod
def register_class(cls, klass):
......@@ -111,7 +117,7 @@ class BaseElement(etree.ElementBase):
('transform', Transform),
('style', Style),
('classes', 'class', Classes),
)
) # type: Tuple[Tuple[Any, ...], ...]
# We do this because python2 and python3 have different ways
# of combining two dictionaries that are incompatible.
......@@ -384,7 +390,8 @@ class BaseElement(etree.ElementBase):
def label(self):
"""Returns the inkscape label"""
return self.get('inkscape:label', None)
label = label.setter(lambda self, value: self.set('inkscape:label', str(value)))
label = label.setter(lambda self, value: self.set('inkscape:label', str(value))) # type: ignore
class ShapeElement(BaseElement):
......@@ -441,7 +448,8 @@ class ShapeElement(BaseElement):
"""Without parent styles, what is the effective style is"""
return self.style
def bounding_box(self, transform=None): # type: () -> BoundingBox
def bounding_box(self, transform=None):
# type: (Optional[Transform]) -> Optional[BoundingBox]
"""BoundingBox calculation based on the ShapeElement rendered to a path."""
path = self.path.to_absolute()
if transform is True:
......
......@@ -33,6 +33,13 @@ from ..tween import interpcoord, interp
from ..styles import Style
from ._base import BaseElement
try:
from typing import overload, Iterable, List, Tuple, Union, Optional # pylint: disable=unused-import
except ImportError:
overload = lambda x: x
class Filter(BaseElement):
"""A filter (usually in defs)"""
tag_name = 'filter'
......@@ -100,6 +107,7 @@ class Stop(BaseElement):
@property
def offset(self):
# type: () -> float
return self.get('offset')
@offset.setter
......@@ -123,16 +131,18 @@ class Gradient(BaseElement):
"""A gradient instruction usually in the defs"""
WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (('gradientTransform', Transform),)
orientation_attributes = ()
orientation_attributes = () # type: Tuple[str, ...]
@property
def stops(self): # type: () -> List[Stop]
def stops(self):
"""Return an ordered list of own or linked stop nodes"""
gradcolor = self.href if isinstance(self.href, LinearGradient) else self
return sorted(gradcolor, key=lambda x: float(x.offset))
return sorted([child for child in gradcolor if isinstance(child, Stop)]
, key=lambda x: float(x.offset))
@property
def stop_offsets(self): # type: () -> List[float]
def stop_offsets(self):
# type: () -> List[float]
"""Return a list of own or linked stop offsets"""
return [child.offset for child in self.stops]
......@@ -168,8 +178,9 @@ class Gradient(BaseElement):
else:
# gradients might have different stops
newoffsets = sorted(self.stop_offsets + other.stop_offsets[1:-1])
sstops = interp(self.stop_offsets, self.stops, newoffsets)
ostops = interp(other.stop_offsets, other.stops, newoffsets)
func = lambda x,y,f: x.interpolate(y, f)
sstops = interp(self.stop_offsets, list(self.stops), newoffsets, func)
ostops = interp(other.stop_offsets, list(other.stops), newoffsets, func)
newstops = [s1.interpolate(s2, fraction) for s1, s2 in zip(sstops, ostops)]
newgrad.remove_all(Stop)
newgrad.add(*newstops)
......
......@@ -23,7 +23,9 @@
Interface for all group based elements such as Groups, Use, Markers etc.
"""
from ..paths import Path
from lxml import etree
from ..paths import Path, BoundingBox
from ..utils import addNS
from ..transforms import Transform
......@@ -42,7 +44,8 @@ class GroupBase(ShapeElement):
ret += child.path.transform(child.transform)
return ret
def bounding_box(self, transform=None): # type: (Transform) -> Optional[BoundingBox]
def bounding_box(self, transform=None):
# type: (Transform) -> Optional[BoundingBox]
bbox = None
transform = Transform(transform) * self.transform
......@@ -87,7 +90,8 @@ class Layer(Group):
self.set('inkscape:groupmode', 'layer')
@classmethod
def _is_class_element(cls, el): # type: (etree.Element) -> bool
def _is_class_element(cls, el):
# type: (etree.Element) -> bool
return el.attrib.get(addNS('inkscape:groupmode'), None) == "layer"
......
......@@ -168,7 +168,8 @@ class EllipseBase(ShapeElement):
self.set("cx", value.x)
self.set("cy", value.y)
def _rxry(self): # type: () -> Vector2d()
def _rxry(self):
# type: () -> Vector2d
"""Helper function """
raise NotImplementedError()
......
......@@ -27,7 +27,7 @@ try:
from inspect import isclass
from importlib import util
except ImportError:
util = None
util = None # type: ignore
from .base import InkscapeExtension
from .utils import Boolean
......
This diff is collapsed.
......@@ -196,7 +196,8 @@ class Style(OrderedDict):
val = a1
return val
def interpolate(self, other, fraction): # type: (Style, float, Optional[str], Optional[str]) -> Style
def interpolate(self, other, fraction):
# type: (Style, float) -> Style
"""Interpolate all properties."""
style = Style()
for prop, value in self.items():
......
......@@ -166,8 +166,8 @@ class ImmutableVector2d(object):
other = Vector2d(other)
return self.x * other.x + self.y * other.y
def is_close(self, other, rtol=1e-5, atol=1e-8
): # type: (Union[VectorLike,Tuple[float,float]], Optional[float], Optional[float]) -> float
def is_close(self, other, rtol=1e-5, atol=1e-8):
# type: (Union[VectorLike,Tuple[float,float]], float, float) -> float
other = Vector2d(other)
delta = (self - other).length
return delta < (atol + rtol * other.length)
......@@ -190,34 +190,35 @@ class Vector2d(ImmutableVector2d):
def y(self, value):
self._y = float(value)
def __iadd__(self, other): # type: (VectorLike) -> VectorLike
def __iadd__(self, other):
# type: (VectorLike) -> Vector2d
other = Vector2d(other)
self.x += other.x
self.y += other.y
return self
def __isub__(self, other): # type: (VectorLike) -> VectorLike
def __isub__(self, other): # type: (VectorLike) -> Vector2d
other = Vector2d(other)
self.x -= other.x
self.y -= other.y
return self
def __imul__(self, factor): # type: (float) -> VectorLike
def __imul__(self, factor): # type: (float) -> Vector2d
self.x *= factor
self.y *= factor
return self
def __idiv__(self, factor): # type: (float) -> VectorLike
def __idiv__(self, factor): # type: (float) -> Vector2d
self.x /= factor
self.y /= factor
return self
def __itruediv__(self, factor): # type: (float) -> VectorLike
def __itruediv__(self, factor): # type: (float) -> Vector2d
self.x /= factor
self.y /= factor
return self
def __ifloordiv__(self, factor): # type: (float) -> VectorLike
def __ifloordiv__(self, factor): # type: (float) -> Vector2d
self.x /= factor
self.y /= factor
return self
......
......@@ -24,18 +24,31 @@ from .utils import X, Y
from .units import convert_unit, parse_unit, render_unit
try:
from typing import Tuple, List, TypeVar, Callable
V = TypeVar('V')
from typing import Union, Tuple, List, TypeVar, Callable, overload
hasTypes = True
Value = TypeVar('Value')
Number = TypeVar('Number', int, float)
except ImportError:
pass
def interpcoord(coord_a, coord_b, time): # type: (float, float, float) -> float
def interpcoord(
coord_a, # type: Number
coord_b, # type: Number
time # type: float
):
# type: (...) -> float
"""Interpolate single coordinate by the amount of time"""
return coord_a + ((coord_b - coord_a) * time)
def interp(positions, values, newpositions, func=None): # type: (Callable[[V, V, float], V], List[float], List[V], List[float]) -> V
def interp(
positions, # type: List[float]
values, # type: List[Value]
newpositions, # type: List[float]
func # type: (Callable[[Value, Value, float], Value])
):
# type: (...) -> List[Value]
"""Interpolate list with arbitrary interpolation function."""
newvalues = []
positions = list(map(float, positions))
......@@ -46,24 +59,19 @@ def interp(positions, values, newpositions, func=None): # type: (Callable[[V, V,
fraction = (pos - positions[idxl]) / (positions[idxr] - positions[idxl])
vall = values[idxl]
valr = values[idxr]
if func is not None:
newval = func(vall, valr, fraction)
if isinstance(vall, (float, int)):
newval = interpcoord(vall, valr, fraction)
elif hasattr(vall, 'interpolate'):
newval = vall.interpolate(valr, fraction)
else:
raise Exception('Interpolated objects must be float/int or have an interpolate method if func is not passed as argument')
newval = func(vall, valr, fraction)
newvalues.append(newval)
return newvalues
def interppoints(point1, point2, time): # type: (Tuple[float, float], Tuple[float, float], float) -> Tuple[float, float]
def interppoints(point1, point2, time):
# type: (Tuple[float, float], Tuple[float, float], float) -> Tuple[float, float]
"""Interpolate coordinate points by amount of time"""
return (interpcoord(point1[X], point2[X], time), interpcoord(point1[Y], point2[Y], time))
def interpunit(start, end, fraction): # type: (SvgDocumentElement, str, str, str, float) -> str
def interpunit(start, end, fraction):
# type: (str, str, float) -> str
"""Interpolate float attributes with unit."""
# moved here so we can call 'unittouu'
sp, unit = parse_unit(start)
......
......@@ -22,6 +22,7 @@ import math
import inkex
from inkex.paths import Move, Line, Curve, ZoneClose, Arc, Path, Vert, Horz, TepidQuadratic, Quadratic, Smooth
from inkex.transforms import Vector2d
from inkex.bezier import beziertatslope, beziersplitatt
......@@ -54,7 +55,7 @@ class Motion(inkex.EffectExtension):
elem.path = inkex.Path([
Move(last[0], last[1]),
segment,
npt.to_line(),
npt.to_line(Vector2d()),
rev,
ZoneClose(),
])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment