Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-roifile for openSUSE:Factory checked in at 2024-01-21 23:10:09 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-roifile (Old) and /work/SRC/openSUSE:Factory/.python-roifile.new.16006 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-roifile" Sun Jan 21 23:10:09 2024 rev:5 rq:1140267 version:2024.1.10 Changes: -------- --- /work/SRC/openSUSE:Factory/python-roifile/python-roifile.changes 2023-12-13 18:36:34.641984292 +0100 +++ /work/SRC/openSUSE:Factory/.python-roifile.new.16006/python-roifile.changes 2024-01-21 23:10:33.650327026 +0100 @@ -1,0 +2,9 @@ +Sun Jan 21 10:53:56 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 2024.1.10: + * Support text rotation. + * Improve text rendering. + * Avoid array copies. + * Limit size read from files. + +------------------------------------------------------------------- Old: ---- roifile-2023.8.30.tar.gz New: ---- roifile-2024.1.10.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-roifile.spec ++++++ --- /var/tmp/diff_new_pack.HrJnnY/_old 2024-01-21 23:10:34.366353127 +0100 +++ /var/tmp/diff_new_pack.HrJnnY/_new 2024-01-21 23:10:34.366353127 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-roifile # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -21,7 +21,7 @@ %define packagename roifile %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-roifile -Version: 2023.8.30 +Version: 2024.1.10 Release: 0 Summary: Read and write ImageJ ROI format License: BSD-3-Clause ++++++ roifile-2023.8.30.tar.gz -> roifile-2024.1.10.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/roifile-2023.8.30/CHANGES.rst new/roifile-2024.1.10/CHANGES.rst --- old/roifile-2023.8.30/CHANGES.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/roifile-2024.1.10/CHANGES.rst 2024-01-11 03:05:22.000000000 +0100 @@ -0,0 +1,73 @@ +Revisions +--------- + +2024.1.10 + +- Support text rotation. +- Improve text rendering. +- Avoid array copies. +- Limit size read from files. + +2023.8.30 + +- Fix linting issues. +- Add py.typed marker. + +2023.5.12 + +- Improve object repr and type hints. +- Drop support for Python 3.8 and numpy < 1.21 (NEP29). + +2023.2.12 + +- Delay import of zipfile. +- Verify shape of coordinates on write. + +2022.9.19 + +- Fix integer coordinates to -5000..60536 conforming with ImageJ (breaking). +- Add subpixel_coordinates in frompoints for out-of-range integer coordinates. + +2022.7.29 + +- Update metadata. + +2022.3.18 + +- Fix creating ROIs from float coordinates exceeding int16 range (#7). +- Fix bottom-right bounds in ImagejRoi.frompoints. + +2022.2.2 + +- Add type hints. +- Change ImagejRoi to dataclass. +- Drop support for Python 3.7 and numpy < 1.19 (NEP29). + +2021.6.6 + +- Add enums for point types and sizes. + +2020.11.28 + +- Support group attribute. +- Add roiread and roiwrite functions (#3). +- Use UUID as default name of ROI in ImagejRoi.frompoints (#2). + +2020.8.13 + +- Support writing to ZIP file. +- Support os.PathLike file names. + +2020.5.28 + +- Fix int32 to hex color conversion. +- Fix coordinates of closing path. +- Fix reading TIFF files with no overlays. + +2020.5.1 + +- Split positions from counters. + +2020.2.12 + +- Initial release. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/roifile-2023.8.30/LICENSE new/roifile-2024.1.10/LICENSE --- old/roifile-2023.8.30/LICENSE 2023-08-31 02:23:04.000000000 +0200 +++ new/roifile-2024.1.10/LICENSE 2024-01-11 03:05:22.000000000 +0100 @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020-2023, Christoph Gohlke +Copyright (c) 2020-2024, Christoph Gohlke All rights reserved. Redistribution and use in source and binary forms, with or without diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/roifile-2023.8.30/MANIFEST.in new/roifile-2024.1.10/MANIFEST.in --- old/roifile-2023.8.30/MANIFEST.in 2023-08-31 02:23:04.000000000 +0200 +++ new/roifile-2024.1.10/MANIFEST.in 2024-01-11 03:05:22.000000000 +0100 @@ -1,5 +1,6 @@ include LICENSE include README.rst +include CHANGES.rst include roifile_demo.py include roifile/py.typed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/roifile-2023.8.30/README.rst new/roifile-2024.1.10/README.rst --- old/roifile-2023.8.30/README.rst 2023-08-31 02:23:04.000000000 +0200 +++ new/roifile-2024.1.10/README.rst 2024-01-11 03:05:22.000000000 +0100 @@ -9,7 +9,7 @@ :Author: `Christoph Gohlke <https://www.cgohlke.com>`_ :License: BSD 3-Clause -:Version: 2023.8.30 +:Version: 2024.1.10 :DOI: `10.5281/zenodo.6941603 <https://doi.org/10.5281/zenodo.6941603>`_ Quickstart @@ -35,14 +35,21 @@ This revision was tested with the following requirements and dependencies (other versions may work): -- `CPython <https://www.python.org>`_ 3.9.13, 3.10.11, 3.11.5, 3.12rc -- `Numpy <https://pypi.org/project/numpy/>`_ 1.25.2 -- `Tifffile <https://pypi.org/project/tifffile/>`_ 2023.8.30 (optional) -- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.7.2 (optional) +- `CPython <https://www.python.org>`_ 3.9.13, 3.10.11, 3.11.4, 3.12.1 +- `Numpy <https://pypi.org/project/numpy/>`_ 1.26.3 +- `Tifffile <https://pypi.org/project/tifffile/>`_ 2023.12.9 (optional) +- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.8.2 (optional) Revisions --------- +2024.1.10 + +- Support text rotation. +- Improve text rendering. +- Avoid array copies. +- Limit size read from files. + 2023.8.30 - Fix linting issues. @@ -80,32 +87,9 @@ 2021.6.6 -- Add enums for point types and sizes. - -2020.11.28 - -- Support group attribute. -- Add roiread and roiwrite functions (#3). -- Use UUID as default name of ROI in ImagejRoi.frompoints (#2). - -2020.8.13 - -- Support writing to ZIP file. -- Support os.PathLike file names. - -2020.5.28 - -- Fix int32 to hex color conversion. -- Fix coordinates of closing path. -- Fix reading TIFF files with no overlays. - -2020.5.1 - -- Split positions from counters. - -2020.2.12 +- ⦠-- Initial release. +Refer to the CHANGES file for older revisions. Notes ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/roifile-2023.8.30/roifile/roifile.py new/roifile-2024.1.10/roifile/roifile.py --- old/roifile-2023.8.30/roifile/roifile.py 2023-08-31 02:23:04.000000000 +0200 +++ new/roifile-2024.1.10/roifile/roifile.py 2024-01-11 03:05:22.000000000 +0100 @@ -1,6 +1,6 @@ # roifile.py -# Copyright (c) 2020-2023, Christoph Gohlke +# Copyright (c) 2020-2024, Christoph Gohlke # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ :Author: `Christoph Gohlke <https://www.cgohlke.com>`_ :License: BSD 3-Clause -:Version: 2023.8.30 +:Version: 2024.1.10 :DOI: `10.5281/zenodo.6941603 <https://doi.org/10.5281/zenodo.6941603>`_ Quickstart @@ -65,14 +65,21 @@ This revision was tested with the following requirements and dependencies (other versions may work): -- `CPython <https://www.python.org>`_ 3.9.13, 3.10.11, 3.11.5, 3.12rc -- `Numpy <https://pypi.org/project/numpy/>`_ 1.25.2 -- `Tifffile <https://pypi.org/project/tifffile/>`_ 2023.8.30 (optional) -- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.7.2 (optional) +- `CPython <https://www.python.org>`_ 3.9.13, 3.10.11, 3.11.4, 3.12.1 +- `Numpy <https://pypi.org/project/numpy/>`_ 1.26.3 +- `Tifffile <https://pypi.org/project/tifffile/>`_ 2023.12.9 (optional) +- `Matplotlib <https://pypi.org/project/matplotlib/>`_ 3.8.2 (optional) Revisions --------- +2024.1.10 + +- Support text rotation. +- Improve text rendering. +- Avoid array copies. +- Limit size read from files. + 2023.8.30 - Fix linting issues. @@ -110,32 +117,9 @@ 2021.6.6 -- Add enums for point types and sizes. - -2020.11.28 - -- Support group attribute. -- Add roiread and roiwrite functions (#3). -- Use UUID as default name of ROI in ImagejRoi.frompoints (#2). - -2020.8.13 - -- Support writing to ZIP file. -- Support os.PathLike file names. - -2020.5.28 - -- Fix int32 to hex color conversion. -- Fix coordinates of closing path. -- Fix reading TIFF files with no overlays. - -2020.5.1 +- ⦠-- Split positions from counters. - -2020.2.12 - -- Initial release. +Refer to the CHANGES file for older revisions. Notes ----- @@ -197,7 +181,7 @@ from __future__ import annotations -__version__ = '2023.8.30' +__version__ = '2024.1.10' __all__ = [ 'roiread', @@ -213,31 +197,37 @@ import dataclasses import enum +import logging import os import struct import sys +import uuid from typing import TYPE_CHECKING import numpy if TYPE_CHECKING: from collections.abc import Iterable - from typing import Any, Literal, Union + from typing import Any, Literal from numpy.typing import ArrayLike, NDArray - ZipFileMode = Union[Literal['r'], Literal['w'], Literal['x'], Literal['a']] - def roiread( - filename: os.PathLike[Any] | str, /, *, min_int_coord: int | None = None + filename: os.PathLike[Any] | str, + /, + *, + min_int_coord: int | None = None, + maxsize: int = 268435456, # 256 MB ) -> ImagejRoi | list[ImagejRoi]: """Return ImagejRoi instance(s) from ROI, ZIP, or TIFF file. For ZIP or TIFF files, return a list of ImagejRoi. """ - return ImagejRoi.fromfile(filename, min_int_coord=min_int_coord) + return ImagejRoi.fromfile( + filename, min_int_coord=min_int_coord, maxsize=maxsize + ) def roiwrite( @@ -246,7 +236,7 @@ /, *, name: str | Iterable[str] | None = None, - mode: ZipFileMode | None = None, + mode: Literal['r', 'w', 'x', 'a'] | None = None, ) -> None: """Write ImagejRoi instance(s) to ROI or ZIP file. @@ -351,7 +341,7 @@ class ImagejRoi: """Read and write ImageJ ROI format.""" - byteorder: Literal['>'] | Literal['<'] = '>' + byteorder: Literal['>', '<'] = '>' roitype: ROI_TYPE = ROI_TYPE.POLYGON subtype: ROI_SUBTYPE = ROI_SUBTYPE.UNDEFINED options: ROI_OPTIONS = ROI_OPTIONS(0) @@ -391,6 +381,7 @@ text_size: int = 0 text_style: int = 0 text_justification: int = 0 + text_angle: float = 0.0 text_name: str = '' text: str = '' counters: NDArray[numpy.uint8] | None = None @@ -440,8 +431,6 @@ self.t_position = t + 1 if name is None: if index is None: - import uuid - name = str(uuid.uuid1()) else: name = f'F{self.t_position:02}-C{index}' @@ -452,11 +441,11 @@ numpy.any(coords > 60000) or numpy.any(coords < -5000) ): self.options |= ROI_OPTIONS.SUB_PIXEL_RESOLUTION - self.subpixel_coordinates = coords.astype('f4', copy=True) + self.subpixel_coordinates = coords.astype(numpy.float32, copy=True) if coords.dtype.kind == 'f': coords = numpy.round(coords) - coords = numpy.array(coords, dtype='i4') + coords = numpy.array(coords, dtype=numpy.int32) left_top = coords.min(axis=0) right_bottom = coords.max(axis=0) @@ -479,6 +468,7 @@ /, *, min_int_coord: int | None = None, + maxsize: int = 268435456, # 256 MB ) -> ImagejRoi | list[ImagejRoi]: """Return ImagejRoi instance from ROI, ZIP, or TIFF file. @@ -516,13 +506,14 @@ with zipfile.ZipFile(filename) as zf: return [ cls.frombytes( - zf.open(name).read(), min_int_coord=min_int_coord + zf.open(name).read(maxsize), + min_int_coord=min_int_coord, ) for name in zf.namelist() ] with open(filename, 'rb') as fh: - data = fh.read() + data = fh.read(maxsize) return cls.frombytes(data, min_int_coord=min_int_coord) @classmethod @@ -535,7 +526,7 @@ ) -> ImagejRoi: """Return ImagejRoi instance from bytes.""" if data[:4] != b'Iout': - raise ValueError('not an ImageJ ROI') + raise ValueError(f'not an ImageJ ROI {data[:4]!r}') self = cls() @@ -638,8 +629,10 @@ buffer=data, offset=counters_offset, ) - self.counters = (counters & 0xFF).astype('u1') - self.counter_positions = (counters >> 8).astype('u4') + self.counters = (counters & 0xFF).astype(numpy.uint8) + self.counter_positions = (counters >> 8).astype( + numpy.uint32, copy=False + ) if self.version >= 218 and self.subtype == ROI_SUBTYPE.TEXT: ( @@ -656,6 +649,11 @@ ) off += name_length * 2 self.text = data[off : off + text_length * 2].decode(self.utf16) + if self.version >= 225: + off += text_length * 2 + self.text_angle = struct.unpack( + self.byteorder + 'f', data[off : off + 4] + )[0] elif self.roitype in ( ROI_TYPE.POLYGON, @@ -672,7 +670,7 @@ buffer=data, offset=64, order='F', - ).astype('i4') + ).astype(numpy.int32) select = self.integer_coordinates < min_int_coord self.integer_coordinates[select] += 65536 @@ -695,7 +693,7 @@ ).copy() elif self.roitype not in (ROI_TYPE.RECT, ROI_TYPE.LINE, ROI_TYPE.OVAL): - log_warning(f'cannot handle ImagejRoi type {self.roitype!r}') + logger().warning(f'cannot handle ImagejRoi type {self.roitype!r}') return self @@ -703,7 +701,7 @@ self, filename: os.PathLike[Any] | str, name: str | None = None, - mode: ZipFileMode | None = None, + mode: Literal['r', 'w', 'x', 'a'] | None = None, ) -> None: """Write ImagejRoi to ROI or ZIP file. @@ -736,10 +734,10 @@ self.byteorder + 'hBxhhhhH', self.version, self.roitype.value, - numpy.array(self.top).astype('i2'), - numpy.array(self.left).astype('i2'), - numpy.array(self.bottom).astype('i2'), - numpy.array(self.right).astype('i2'), + numpy.array(self.top).astype(numpy.int16), + numpy.array(self.left).astype(numpy.int16), + numpy.array(self.bottom).astype(numpy.int16), + numpy.array(self.right).astype(numpy.int16), self.n_coordinates if self.n_coordinates < 2**16 else 0, ) ) @@ -801,7 +799,7 @@ ) extradata += self.text_name.encode(self.utf16) extradata += self.text.encode(self.utf16) - extradata += b'\x00' * 4 # ? + extradata += struct.pack(self.byteorder + 'f', self.text_angle) elif self.roitype in ( ROI_TYPE.POLYGON, @@ -819,7 +817,9 @@ f'{self.integer_coordinates.shape} ' f'!= ({self.n_coordinates}, 2)' ) - coord = self.integer_coordinates.astype(self.byteorder + 'i2') + coord = self.integer_coordinates.astype( + self.byteorder + 'i2', copy=False + ) extradata = coord.tobytes(order='F') if self.subpixel_coordinates is not None: if self.subpixel_coordinates.shape != (self.n_coordinates, 2): @@ -828,12 +828,16 @@ f'{self.subpixel_coordinates.shape} ' f'!= ({self.n_coordinates}, 2)' ) - coord = self.subpixel_coordinates.astype(self.byteorder + 'f4') + coord = self.subpixel_coordinates.astype( + self.byteorder + 'f4', copy=False + ) extradata += coord.tobytes(order='F') elif self.composite and self.roitype == ROI_TYPE.RECT: assert self.multi_coordinates is not None - coord = self.multi_coordinates.astype(self.byteorder + 'f4') + coord = self.multi_coordinates.astype( + self.byteorder + 'f4', copy=False + ) extradata += coord.tobytes() header2_offset = 64 + len(extradata) @@ -882,12 +886,12 @@ self.counter_positions, dtype=self.byteorder + 'u4' ) counters = counters & 0xFF | indices << 8 - counters = counters.astype(self.byteorder + 'u4') + counters = counters.astype(self.byteorder + 'u4', copy=False) result.append(counters.tobytes()) return b''.join(result) - def plot( # type: ignore + def plot( self, ax: Any | None = None, *, @@ -897,7 +901,7 @@ invert_yaxis: bool | None = None, **kwargs, ) -> None: - """Plot coordinates using matplotlib.""" + """Plot a draft of coordinates using matplotlib.""" roitype = self.roitype subtype = self.subtype @@ -967,10 +971,25 @@ x, y = line[1] ax.arrow(x, y, -dx, -dy, **kwargs) elif roitype == ROI_TYPE.RECT and subtype == ROI_SUBTYPE.TEXT: - point = self.coordinates()[0] + coords = self.coordinates(True)[0] if 'fontsize' not in kwargs and self.text_size > 0: kwargs['fontsize'] = self.text_size - ax.text(point[0], point[1], self.text, **kwargs) + text = ax.text( + *coords[1], + self.text, + va='center_baseline', + rotation=self.text_angle, + rotation_mode='anchor', + **kwargs, + ) + scale_text(text, width=abs(coords[2, 0] - coords[0, 0])) + # ax.plot( + # coords[:, 0], + # coords[:, 1], + # linewidth=1, + # color=kwargs.get('color', 0.9), + # ls=':', + # ) else: for coords in self.coordinates(multi=True): ax.plot(coords[:, 0], coords[:, 1], **kwargs) @@ -1013,7 +1032,7 @@ return coordslist elif self.roitype == ROI_TYPE.LINE: coords = numpy.array( - [[self.x1, self.y1], [self.x2, self.y2]], 'f4' + [[self.x1, self.y1], [self.x2, self.y2]], numpy.float32 ) elif self.roitype == ROI_TYPE.OVAL: coords = oval([[self.left, self.top], [self.right, self.bottom]]) @@ -1026,7 +1045,7 @@ [self.right, self.top], [self.left, self.top], ], - 'f4', + numpy.float32, ) else: coords = numpy.empty((0, 2), dtype=self.byteorder + 'i4') @@ -1055,7 +1074,9 @@ if op == 0: # MOVETO if n > 0: - coordinates.append(numpy.array(points, dtype='f4')) + coordinates.append( + numpy.array(points, dtype=numpy.float32) + ) points = [] points.append([path[n + 1], path[n + 2]]) m = len(points) - 1 @@ -1076,7 +1097,7 @@ else: raise RuntimeError(f'invalid PathIterator command {op!r}') - coordinates.append(numpy.array(points, dtype='f4')) + coordinates.append(numpy.array(points, dtype=numpy.float32)) return coordinates @staticmethod @@ -1091,7 +1112,7 @@ return -5000 if -32768 <= value <= 0: return int(value) - raise ValueError('min_int_coord out of range') + raise ValueError(f'{value=} out of range') @property def composite(self) -> bool: @@ -1160,6 +1181,29 @@ return indent(*info, end='\n)') +def scale_text(text: Any, width: float) -> None: + """Scale matplotlib text to width in data coordinates.""" + from matplotlib.patheffects import AbstractPathEffect + from matplotlib.transforms import Bbox + + class TextScaler(AbstractPathEffect): + def __init__(self, text, width): + self._text = text + self._width = width + + def draw_path(self, renderer, gc, tpath, affine, rgbFace=None): + ax = self._text.axes + renderer = ax.get_figure().canvas.get_renderer() + bbox = text.get_window_extent(renderer=renderer) + bbox = Bbox(ax.transData.inverted().transform(bbox)) + if self._width > 0 and bbox.width > 0: + scale = self._width / bbox.width + affine = affine.from_values(scale, 0, 0, scale, 0, 0) + affine + renderer.draw_path(gc, tpath, affine, rgbFace) + + text.set_path_effects([TextScaler(text, width)]) + + def oval(rect: ArrayLike, /, points: int = 33) -> NDArray[numpy.float32]: """Return coordinates of oval from rectangle corners.""" arr = numpy.array(rect, dtype=numpy.float32) @@ -1205,11 +1249,9 @@ return s -def log_warning(msg: object, *args: object, **kwargs: Any) -> None: - """Log message with level WARNING.""" - import logging - - logging.getLogger(__name__).warning(msg, *args, **kwargs) +def logger() -> logging.Logger: + """Return logging.getLogger('roifile').""" + return logging.getLogger(__name__.replace('roifile.roifile', 'roifile')) def test(verbose: bool = False) -> None: @@ -1250,6 +1292,9 @@ assert coords[-1][-1][-1] == 587.0 assert roi.multi_coordinates is not None assert roi.multi_coordinates[0] == 0.0 + with open('tests/box_combined.roi', 'rb') as fh: + expected = fh.read() + assert roi.tobytes() == expected str(roi) roi = ImagejRoi.frompoints([[1, 2], [3, 4], [5, 6]]) @@ -1274,7 +1319,9 @@ assert roi.bottom == 65535, roi.bottom # issue #7 - roi = ImagejRoi.frompoints(numpy.load('tests/issue7.npy').astype('f4')) + roi = ImagejRoi.frompoints( + numpy.load('tests/issue7.npy').astype(numpy.float32) + ) assert roi == ImagejRoi.frombytes(roi.tobytes()) assert roi.left == 28357, roi.left assert roi.top == 42200, roi.top # not -23336 @@ -1288,6 +1335,28 @@ assert roi.subpixel_coordinates[0, 0] == 28357.0 assert roi.subpixel_coordinates[0, 1] == 42215.0 + # rotated text + rois = roiread('tests/text_rotated.roi') + assert isinstance(rois, ImagejRoi) + roi = rois + if verbose: + print(roi) + assert roi == ImagejRoi.frombytes(roi.tobytes()) + assert roi.name == 'Rotated' + assert roi.roitype == ROI_TYPE.RECT + assert roi.subtype == ROI_SUBTYPE.TEXT + assert roi.version == 228 + assert (roi.top, roi.left, roi.bottom, roi.right) == (252, 333, 280, 438) + assert roi.stroke_color == b'\xff\x00\x00\xff' + assert roi.text_size == 20 + assert roi.text_justification == 1 + assert roi.text_name == 'SansSerif' + assert roi.text == 'Enter text...\n' + with open('tests/text_rotated.roi', 'rb') as fh: + expected = fh.read() + assert roi.tobytes() == expected + str(roi) + # read a ROI from a TIFF file rois = roiread('tests/IJMetadata.tif') assert isinstance(rois, list) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/roifile-2023.8.30/setup.py new/roifile-2024.1.10/setup.py --- old/roifile-2023.8.30/setup.py 2023-08-31 02:23:04.000000000 +0200 +++ new/roifile-2024.1.10/setup.py 2024-01-11 03:05:22.000000000 +0100 @@ -17,7 +17,7 @@ with open('roifile/roifile.py', encoding='utf-8') as fh: - code = fh.read() + code = fh.read().replace('\r\n', '\n').replace('\r', '\n') version = search(r"__version__ = '(.*?)'", code).replace('.x.x', '.dev0') @@ -28,25 +28,40 @@ code, re.MULTILINE | re.DOTALL, ) - readme = '\n'.join( [description, '=' * len(description)] + readme.splitlines()[1:] ) -license = search( - r'(# Copyright.*?(?:\r\n|\r|\n))(?:\r\n|\r|\n)+""', - code, - re.MULTILINE | re.DOTALL, -) +if 'sdist' in sys.argv: + # update README, LICENSE, and CHANGES files + + with open('README.rst', 'w', encoding='utf-8') as fh: + fh.write(readme) -license = license.replace('# ', '').replace('#', '') + license = search( + r'(# Copyright.*?(?:\r\n|\r|\n))(?:\r\n|\r|\n)+""', + code, + re.MULTILINE | re.DOTALL, + ) + license = license.replace('# ', '').replace('#', '') -if 'sdist' in sys.argv: with open('LICENSE', 'w', encoding='utf-8') as fh: fh.write('BSD 3-Clause License\n\n') fh.write(license) - with open('README.rst', 'w', encoding='utf-8') as fh: - fh.write(readme) + + revisions = search( + r'(?:\r\n|\r|\n){2}(Revisions.*)- â¦', + readme, + re.MULTILINE | re.DOTALL, + ).strip() + + with open('CHANGES.rst', encoding='utf-8') as fh: + old = fh.read() + + old = old.split(revisions.splitlines()[-1])[-1] + with open('CHANGES.rst', 'w', encoding='utf-8') as fh: + fh.write(revisions.strip()) + fh.write(old) setup( name='roifile',