pythonic-fp.splitends 2.0.2__tar.gz → 2.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. pythonic_fp_splitends-2.0.3/.github/workflows/static.yml +64 -0
  2. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/.gitignore +2 -0
  3. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/CHANGELOG.rst +11 -0
  4. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/PKG-INFO +14 -9
  5. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/README.rst +9 -4
  6. pythonic_fp_splitends-2.0.3/docs/Makefile +78 -0
  7. pythonic_fp_splitends-2.0.3/docs/gen_conf.py +110 -0
  8. pythonic_fp_splitends-2.0.3/docs/requirements.txt +2 -0
  9. pythonic_fp_splitends-2.0.3/docs/source/_static/custom.css +3 -0
  10. pythonic_fp_splitends-2.0.3/docs/source/api/index.rst +15 -0
  11. pythonic_fp_splitends-2.0.3/docs/source/api/splitend.rst +8 -0
  12. pythonic_fp_splitends-2.0.3/docs/source/api/splitend_node.rst +8 -0
  13. pythonic_fp_splitends-2.0.3/docs/source/changelog.rst +7 -0
  14. pythonic_fp_splitends-2.0.3/docs/source/description.rst +7 -0
  15. pythonic_fp_splitends-2.0.3/docs/source/index.rst +30 -0
  16. pythonic_fp_splitends-2.0.3/docs/source/releases.rst +24 -0
  17. pythonic_fp_splitends-2.0.3/docs/source/usage.rst +21 -0
  18. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/pyproject.toml +25 -17
  19. pythonic_fp_splitends-2.0.3/src/pythonic_fp/splitends/__init__.py +41 -0
  20. pythonic_fp_splitends-2.0.3/src/pythonic_fp/splitends/__init__.pyi +0 -0
  21. pythonic_fp_splitends-2.0.3/src/pythonic_fp/splitends/py.typed +0 -0
  22. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/src/pythonic_fp/splitends/splitend.py +144 -52
  23. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/src/pythonic_fp/splitends/splitend_node.py +62 -27
  24. pythonic_fp_splitends-2.0.2/src/pythonic_fp/splitends/__init__.py +0 -38
  25. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/LICENSE +0 -0
  26. /pythonic_fp_splitends-2.0.2/src/pythonic_fp/splitends/__init__.pyi → /pythonic_fp_splitends-2.0.3/docs/source/_static/.gitkeep +0 -0
  27. /pythonic_fp_splitends-2.0.2/src/pythonic_fp/splitends/py.typed → /pythonic_fp_splitends-2.0.3/docs/source/_templates/.gitkeep +0 -0
  28. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/src/pythonic_fp/splitends/splitend.pyi +0 -0
  29. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/src/pythonic_fp/splitends/splitend_node.pyi +0 -0
  30. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/tests/test_splitend.py +0 -0
  31. {pythonic_fp_splitends-2.0.2 → pythonic_fp_splitends-2.0.3}/tests/test_splitend_node.py +0 -0
@@ -0,0 +1,64 @@
1
+ name: Build and Deploy Furo Themed Sphinx Documentation
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ workflow_dispatch:
7
+
8
+ env:
9
+ RELEASE: '2.0.3'
10
+ DEVEL: '2.0.4'
11
+ PYTHON: '3.14'
12
+
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ concurrency:
19
+ group: "pages"
20
+ cancel-in-progress: false
21
+
22
+ jobs:
23
+ deploy:
24
+ runs-on: ubuntu-latest
25
+ environment:
26
+ name: github-pages
27
+ url: ${{ steps.deployment.outputs.page_url }}
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v6
31
+
32
+ - name: Select Python
33
+ uses: actions/setup-python@v6
34
+ with:
35
+ python-version: '3.14'
36
+
37
+ - name: Install Sphinx Dependencies
38
+ run: |
39
+ pip install -r docs/requirements.txt
40
+ sudo apt-get install -y graphviz
41
+
42
+ - name: Build Release Docs
43
+ run: |
44
+ pip install pythonic-fp-splitends
45
+ docs/gen_conf.py release ${{ env.RELEASE }} Splitends splitends > docs/source/conf.py
46
+ sphinx-build -M html docs/source docs/build/release
47
+
48
+ - name: Build Development Docs
49
+ run: |
50
+ pip install -e .
51
+ docs/gen_conf.py devel ${{ env.DEVEL }} Splitends splitends > docs/source/conf.py
52
+ sphinx-build -M html docs/source docs/build/development
53
+
54
+ - name: Setup Pages
55
+ uses: actions/configure-pages@v6
56
+
57
+ - name: Upload Artifact
58
+ uses: actions/upload-pages-artifact@v5
59
+ with:
60
+ path: docs/build
61
+
62
+ - name: Deploy to GitHub Pages
63
+ uses: actions/deploy-pages@v5
64
+ id: deployment
@@ -1,6 +1,8 @@
1
1
  # Minimal version - only add to when necessary
2
2
  **/__pycache__/
3
3
  dist/
4
+ docs/build/
5
+ docs/source/conf.py
4
6
  **/.dmypy.json
5
7
  **/.mypy_cache/
6
8
  **/.pytest_cache/
@@ -17,6 +17,17 @@ See `Semantic Versioning 2.0.0 <https://semver.org>`_.
17
17
  Releases and Important Milestones
18
18
  ---------------------------------
19
19
 
20
+ PyPI 2.0.3 - 2026-05-13
21
+ ~~~~~~~~~~~~~~~~~~~~~~~
22
+
23
+ - Documentation now in maintenance mode.
24
+
25
+ Development Status Reappraisal - 2026-05-05
26
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27
+
28
+ Maintainer appraised the Development Status for
29
+ pythonic-fp-splitends to be ``"3 - Alpha"``.
30
+
20
31
  PyPI 2.0.2 - 2026-01-22
21
32
  ~~~~~~~~~~~~~~~~~~~~~~~
22
33
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-splitends
3
- Version: 2.0.2
4
- Summary: Mutable stack objects that can safely share data.
3
+ Version: 2.0.3
4
+ Summary: .. admonition:: Mutable stack objects that can safely share data
5
5
  Keywords: SplitEnd,lifo,stack
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
7
  Requires-Python: >=3.13
@@ -11,7 +11,7 @@ Classifier: Framework :: Pytest
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Typing :: Typed
16
16
  License-File: LICENSE
17
17
  Requires-Dist: pythonic-fp-fptools>=5.2.0
@@ -19,8 +19,8 @@ Requires-Dist: pythonic-fp-iterables>=5.1.3
19
19
  Requires-Dist: pythonic-fp-queues>=5.1.1
20
20
  Requires-Dist: pytest>=8.4.1 ; extra == "test"
21
21
  Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.rst
22
- Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/splitends/development/build/html/
23
- Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/build/html/
22
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/projects/splitends.html
23
+ Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
24
24
  Project-URL: Source, https://github.com/grscheller/pythonic-fp-splitends
25
25
  Provides-Extra: test
26
26
 
@@ -28,7 +28,8 @@ Pythonic FP - SplitEnds
28
28
  =======================
29
29
 
30
30
  PyPI project
31
- `pythonic-fp.splitends <https://pypi.org/project/pythonic-fp.splitends/>`_.
31
+ `pythonic-fp.splitends
32
+ <https://pypi.org/project/pythonic-fp-splitends/>`_.
32
33
 
33
34
  Python package Implementing a singularly linked LIFO queue called
34
35
  a ``SplitEnd``. These data structures can safely share data nodes
@@ -45,19 +46,23 @@ between themselves.
45
46
 
46
47
  Part of the
47
48
  `pythonic-fp
48
- <https://grscheller.github.io/pythonic-fp>`_
49
+ <https://grscheller.github.io/pythonic-fp/>`_
49
50
  PyPI projects.
50
51
 
51
52
  Documentation
52
53
  -------------
53
54
 
55
+ Documentation and other links for this project are hosted on
56
+ `GitHub Pages
57
+ <https://grscheller.github.io/pythonic-fp/projects/splitends.html>`_.
58
+
54
59
  Documentation for this project is hosted on
55
60
  `GitHub Pages
56
- <https://grscheller.github.io/pythonic-fp/splitends>`_.
61
+ <https://grscheller.github.io/pythonic-fp-splitends/development/html/>`_.
57
62
 
58
63
  Copyright and License
59
64
  ---------------------
60
65
 
61
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
66
+ Copyright (c) 2023-2026 Geoffrey R. Scheller. Licensed under the Apache
62
67
  License, Version 2.0. See the LICENSE file for details.
63
68
 
@@ -2,7 +2,8 @@ Pythonic FP - SplitEnds
2
2
  =======================
3
3
 
4
4
  PyPI project
5
- `pythonic-fp.splitends <https://pypi.org/project/pythonic-fp.splitends/>`_.
5
+ `pythonic-fp.splitends
6
+ <https://pypi.org/project/pythonic-fp-splitends/>`_.
6
7
 
7
8
  Python package Implementing a singularly linked LIFO queue called
8
9
  a ``SplitEnd``. These data structures can safely share data nodes
@@ -19,18 +20,22 @@ between themselves.
19
20
 
20
21
  Part of the
21
22
  `pythonic-fp
22
- <https://grscheller.github.io/pythonic-fp>`_
23
+ <https://grscheller.github.io/pythonic-fp/>`_
23
24
  PyPI projects.
24
25
 
25
26
  Documentation
26
27
  -------------
27
28
 
29
+ Documentation and other links for this project are hosted on
30
+ `GitHub Pages
31
+ <https://grscheller.github.io/pythonic-fp/projects/splitends.html>`_.
32
+
28
33
  Documentation for this project is hosted on
29
34
  `GitHub Pages
30
- <https://grscheller.github.io/pythonic-fp/splitends>`_.
35
+ <https://grscheller.github.io/pythonic-fp-splitends/development/html/>`_.
31
36
 
32
37
  Copyright and License
33
38
  ---------------------
34
39
 
35
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
40
+ Copyright (c) 2023-2026 Geoffrey R. Scheller. Licensed under the Apache
36
41
  License, Version 2.0. See the LICENSE file for details.
@@ -0,0 +1,78 @@
1
+ # Makefile for Sphinx documentation
2
+
3
+ # Last and future releases
4
+ # - former needs to agree with PyPI
5
+ # - later needs to agree with pyproject.toml
6
+ PROJECT_NAME = Splitends
7
+ PYPI_NAME = splitends
8
+ RELEASE_VERSION = 2.0.3
9
+ DEVEL_VERSION = 2.0.4
10
+ CUSTOM_VERSION = 0.0.0
11
+
12
+ SPHINXOPTS ?=
13
+ SPHINXBUILD ?= sphinx-build
14
+ SOURCEDIR = source
15
+ BUILDDIR = build
16
+
17
+ STATICDIR = source/_static
18
+ TEMPLATEDIR = source/_templates
19
+
20
+ # Put help first so that "make" without argument is like "make help".
21
+ help:
22
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
23
+
24
+ clean_source:
25
+ @rm -f "$(SOURCEDIR)"/conf.py
26
+
27
+ clean_devel:
28
+ @rm -rf "$(BUILDDIR)"/development
29
+
30
+ clean_release:
31
+ @rm -rf "$(BUILDDIR)"/release
32
+
33
+ clean_custom:
34
+ @rm -rf "$(BUILDDIR)"/custom
35
+
36
+ clean: clean_devel clean_release clean_custom clean_source
37
+ @rm -rf $(BUILDDIR)
38
+ @echo Removed all doc build artifacts.
39
+
40
+ setup_devel: clean_devel
41
+ @./gen_conf.py devel "$(DEVEL_VERSION)" "$(PROJECT_NAME)" "$(PYPI_NAME)" > "$(SOURCEDIR)"/conf.py
42
+
43
+ setup_release: clean_release
44
+ @./gen_conf.py release "$(RELEASE_VERSION)" "$(PROJECT_NAME)" "$(PYPI_NAME)" > "$(SOURCEDIR)"/conf.py
45
+
46
+ setup_custom: clean_custom
47
+ @./gen_conf.py custom "$(CUSTOM_VERSION)" "$(PROJECT_NAME)" "$(PYPI_NAME)" > "$(SOURCEDIR)"/conf.py
48
+
49
+ setup_redo: clean_custom
50
+ @echo Redoing custom build with a possibly reeditted conf.py
51
+ @./gen_conf.py redo "" "" ""
52
+
53
+ # Build development labeled html docs.
54
+ devel: Makefile setup_devel
55
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/development $(SPHINXOPTS)
56
+
57
+ # Build release labeled html docs.
58
+ release: Makefile setup_release
59
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/release $(SPHINXOPTS)
60
+
61
+ # Build custom labeled html docs.
62
+ custom: Makefile setup_custom
63
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/custom $(SPHINXOPTS)
64
+
65
+ # Build HTML docs with previous generated (possibly hand edited) source/conf.py,
66
+ # put results in $(BUILDDIR)/custom.
67
+ redo: Makefile setup_redo
68
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/custom $(SPHINXOPTS)
69
+
70
+ # Catch-all target: route all unknown targets to Sphinx and save to $(BUILDDIR)/custom.
71
+ # Reuse a previously generated source/conf.py.
72
+ %: Makefile setup_redo
73
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)"/custom $(SPHINXOPTS)
74
+
75
+ .PHONY: help Makefile
76
+ .PHONY: setup_devel setup_release setup_custom
77
+ .PHONY: devel release
78
+ .PHONY: clean_source clean_devel clean_release clean_custom clean
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python
2
+ # Generate conf.py Sphinx configuration executable
3
+
4
+ import sys
5
+ import os
6
+
7
+ project_prefix = 'Pythonic FP'
8
+ pypi_prefix = 'pythonic-fp'
9
+ author = 'Geoffrey R. Scheller'
10
+ copyright = f'2023-2026, {author}'
11
+
12
+ args = sys.argv[1:]
13
+ num_args = len(args)
14
+
15
+ if num_args == 4:
16
+ build_type, version, project_name, pypi_name = args
17
+ project = project_prefix + ' - ' + project_name
18
+ pypi_project_repo = pypi_prefix + '-' + pypi_name
19
+ project_url_rst = f'`{project} <https://pypi.org/project/{pypi_project_repo}/>`_'
20
+ homepage_url_rst = f'`{project_prefix} <https://grscheller.github.io/{pypi_prefix}/>`_'
21
+
22
+ else:
23
+ sys.exit('Error: gen_conf.py takes exactly 4 arguments')
24
+
25
+ match build_type:
26
+ case 'custom':
27
+ release = f'{version}'
28
+ release_string = f'**Custom PyPI Non-release version {release}**'
29
+ case 'devel':
30
+ release = f'{version}'
31
+ release_string = f'**Proposed PyPI release version {release}**'
32
+ case 'release':
33
+ release = f'{version}'
34
+ release_string = f'**PyPI release version {release}**'
35
+ case 'redo':
36
+ if not os.path.isfile('source/conf.py'):
37
+ sys.exit('Error: cannot redo without source/conf.py')
38
+ sys.exit()
39
+ case _:
40
+ sys.exit(f'Error: unknown built_type {build_type} given')
41
+
42
+ print(f'''# Generated conf.py Sphinx configuration executable
43
+
44
+ from typing import Any
45
+ from sphinx.application import Sphinx
46
+
47
+ project = '{project}'
48
+ author = '{author}'
49
+ copyright = '{copyright}'
50
+ release = '{release}'
51
+ release_string = '{release_string}'
52
+
53
+ def skip_abc_methods(
54
+ app: Any, what: str, name: str, obj: Any, skip: bool, options: Any
55
+ ) -> bool:
56
+ if name in [
57
+ '__init_subclass__',
58
+ '__subclasshook__',
59
+ '__class_getitem__',
60
+ '__weakref__',
61
+ ]:
62
+ return True # Skip these members
63
+ return skip
64
+
65
+
66
+ def setup(app: Sphinx) -> None:
67
+ app.connect('autodoc-skip-member', skip_abc_methods)
68
+
69
+
70
+ extensions = [
71
+ 'sphinx.ext.autodoc',
72
+ 'sphinx.ext.graphviz',
73
+ ]
74
+
75
+ autodoc_default_options = {{
76
+ 'members': True,
77
+ 'private-members': True,
78
+ 'special-members': True,
79
+ 'inherited-members': False,
80
+ 'show-inheritance': True,
81
+ }}
82
+ autodoc_member_order = 'bysource'
83
+ autoclass_content = 'class'
84
+ autodoc_class_signature = 'separated'
85
+ autodoc_typehints_format = 'short'
86
+ autodoc_use_type_comments = True
87
+ autodoc_docstring_signature = False
88
+ autodoc_preserve_defaults = True
89
+ autodoc_warningiserror = False
90
+
91
+ templates_path = ['_templates']
92
+ exclude_patterns: list[str] = []
93
+
94
+ html_theme_options = {{
95
+ 'light_css_variables': {{
96
+ 'color-link--visited': 'var(--color-link)',
97
+ }},
98
+ 'dark_css_variables': {{
99
+ 'color-link--visited': 'var(--color-link)',
100
+ }},
101
+ }}
102
+ html_theme = 'furo'
103
+ html_static_path = ['_static']
104
+ html_css_files = ['custom.css']
105
+
106
+ rst_epilog = """
107
+ .. |RELEASE_STRING| replace:: {release_string}
108
+
109
+ """
110
+ ''')
@@ -0,0 +1,2 @@
1
+ sphinx>=9.1
2
+ furo
@@ -0,0 +1,3 @@
1
+ dl.field-list > dt {
2
+ text-transform: none !important;
3
+ }
@@ -0,0 +1,15 @@
1
+ Splitends
2
+ =========
3
+
4
+ .. automodule:: pythonic_fp.splitends
5
+ :no-members:
6
+ :ignore-module-all:
7
+ :no-index:
8
+
9
+ .. toctree::
10
+ :caption: Splitends
11
+ :maxdepth: 1
12
+
13
+ splitend
14
+ splitend_node
15
+
@@ -0,0 +1,8 @@
1
+ module splitend
2
+ ---------------
3
+
4
+ **Mutable LIFO stacks safely share immutable data between themselves.**
5
+
6
+ .. automodule:: pythonic_fp.splitends.splitend
7
+ :members:
8
+ :special-members:
@@ -0,0 +1,8 @@
1
+ module splitend_node
2
+ --------------------
3
+
4
+ **Immutable data nodes used by splitend stacks.**
5
+
6
+ .. automodule:: pythonic_fp.splitends.splitend_node
7
+ :members:
8
+ :special-members:
@@ -0,0 +1,7 @@
1
+ changelog
2
+ ---------
3
+
4
+ Change log for the
5
+ `pythonic-fp-splitends
6
+ <https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.rst>`_
7
+ PyPI project.
@@ -0,0 +1,7 @@
1
+ Description
2
+ -----------
3
+
4
+ .. automodule:: pythonic_fp.splitends
5
+ :synopsis:
6
+ :noindex:
7
+
@@ -0,0 +1,30 @@
1
+ pythonic-fp-splitends
2
+ =====================
3
+
4
+ Project
5
+ `Pypythonic-fp-Splitends <https://pypi.org/project/pythonic-fp-splitends/>`_
6
+ one of the
7
+ `Pythonic FP <https://grscheller.github.io/pythonic-fp/>`_
8
+ PyPI projects.
9
+
10
+ |RELEASE_STRING|
11
+
12
+ .. toctree::
13
+ :caption: Overview
14
+ :hidden:
15
+
16
+ self
17
+
18
+ .. toctree::
19
+ :caption: User Documentation
20
+
21
+ description
22
+ usage
23
+ releases
24
+ changelog
25
+
26
+ .. toctree::
27
+ :caption: API Documentation
28
+ :maxdepth: 1
29
+
30
+ api/index
@@ -0,0 +1,24 @@
1
+ PyPI releases
2
+ =============
3
+
4
+ **pythonic-fp-splitends:** pythonic_fp.splitends
5
+
6
+ +---------------------------------------------------------------------------------------+--------------+
7
+ | PyPI Release | Release date |
8
+ +=======================================================================================+==============+
9
+ | `development <https://grscheller.github.io/pythonic-fp-splitends/development/html/>`_ | |
10
+ +---------------------------------------------------------------------------------------+--------------+
11
+ | `v2.0.3 <https://grscheller.github.io/pythonic-fp-splitends/release/html/>`_ | 2026-05-13 |
12
+ +---------------------------------------------------------------------------------------+--------------+
13
+ | v2.0.2 | 2026-01-22 |
14
+ +---------------------------------------------------------------------------------------+--------------+
15
+ | v2.0.0 | 2025-09-28 |
16
+ +---------------------------------------------------------------------------------------+--------------+
17
+ | v1.0.2 | 2025-09-09 |
18
+ +---------------------------------------------------------------------------------------+--------------+
19
+ | v1.0.1 | 2025-09-09 |
20
+ +---------------------------------------------------------------------------------------+--------------+
21
+ | v1.0.0 | 2025-08-02 |
22
+ +---------------------------------------------------------------------------------------+--------------+
23
+ | v0.30.0 | 2025-07-06 |
24
+ +---------------------------------------------------------------------------------------+--------------+
@@ -0,0 +1,21 @@
1
+ usage
2
+ =====
3
+
4
+ How to install the package
5
+ --------------------------
6
+
7
+ Install the project into your Python environment:
8
+
9
+ .. code:: console
10
+
11
+ $ pip install pythonic-fp.splitends
12
+
13
+ Importing the package
14
+ ---------------------
15
+
16
+ Import the Splitend class and its supporting infrastructure (TODO)
17
+ into your code.
18
+
19
+ .. code:: python
20
+
21
+ from pythonic_fp.splitends.splitend import Splitend
@@ -1,18 +1,27 @@
1
+ [build-system]
2
+ requires = ["flit_core>=3.12,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
1
5
  [project]
2
6
  name = "pythonic-fp-splitends"
3
- version = "2.0.2"
7
+ version = "2.0.3"
4
8
  readme = "README.rst"
5
9
  requires-python = ">=3.13"
6
- license = { file = "LICENSE" }
7
- authors = [{ name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" }]
8
- keywords = ["SplitEnd", "lifo", "stack"]
10
+ authors = [
11
+ { name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
12
+ ]
13
+ keywords = [
14
+ "SplitEnd",
15
+ "lifo",
16
+ "stack",
17
+ ]
9
18
  classifiers = [
10
19
  "Development Status :: 3 - Alpha",
11
20
  "Framework :: Pytest",
12
21
  "Intended Audience :: Developers",
13
22
  "License :: OSI Approved :: Apache Software License",
14
23
  "Operating System :: OS Independent",
15
- "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
16
25
  "Typing :: Typed",
17
26
  ]
18
27
  dependencies = [
@@ -24,8 +33,8 @@ dynamic = ["description"]
24
33
 
25
34
  [project.urls]
26
35
  Changelog = "https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.rst"
27
- Documentation = "https://grscheller.github.io/pythonic-fp/splitends/development/build/html/"
28
- Homepage = "https://grscheller.github.io/pythonic-fp/homepage/build/html/"
36
+ Documentation = "https://grscheller.github.io/pythonic-fp/projects/splitends.html"
37
+ Homepage = "https://grscheller.github.io/pythonic-fp/"
29
38
  Source = "https://github.com/grscheller/pythonic-fp-splitends"
30
39
 
31
40
  [project.optional-dependencies]
@@ -33,17 +42,11 @@ test = [
33
42
  "pytest>=8.4.1",
34
43
  ]
35
44
 
36
- [build-system]
37
- requires = ["flit_core>=3.12,<4"]
38
- build-backend = "flit_core.buildapi"
39
-
40
45
  [tool.flit.module]
41
46
  name = "pythonic_fp.splitends"
42
47
 
43
48
  [tool.mypy]
44
- enable_incomplete_feature = "NewGenericSyntax"
45
49
  explicit_package_bases = true
46
- implicit_reexport = false
47
50
  local_partial_types = true
48
51
  namespace_packages = true
49
52
  warn_redundant_casts = true
@@ -57,19 +60,24 @@ dmypy = false
57
60
  strict = true
58
61
  report_progress = true
59
62
 
60
- [tool.pytest.ini_options]
61
- addopts = "-ra"
63
+ [tool.pytest]
64
+ addopts = [
65
+ "-ra",
66
+ ]
62
67
  consider_namespace_packages = true
63
68
  testpaths = ["tests/"]
64
69
 
65
70
  [tool.ruff]
66
- target-version = "py313"
71
+ target-version = "py314"
67
72
 
68
73
  [tool.ruff.lint.flake8-quotes]
69
74
  docstring-quotes = "double"
70
75
 
71
76
  [tool.ruff.lint.per-file-ignores]
72
- "tests/**/*.py" = ["C901"]
77
+ "tests/**/*.py" = [
78
+ "E731",
79
+ "C901",
80
+ ]
73
81
  "**/*.py" = ["E741"]
74
82
 
75
83
  [tool.ruff.format]
@@ -0,0 +1,41 @@
1
+ # Copyright 2024-2026 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ .. admonition:: Mutable stack objects that can safely share data
17
+
18
+ Python package implementing a singularly linked stack of nodes called a ``SplitEnd``.
19
+ While mutable, these data structures can safely share immutable data nodes between
20
+ themselves. These nodes, of type ``SENode``, always contain data. A root node is
21
+ a node whose previous node is itself. Root nodes mark the bottom of the stack.
22
+
23
+ Like one of many "split ends" from shafts of hair, a ``SplitEnd`` can be "snipped"
24
+ shorter or "extended" further from its "end" node at the top of the stack. Its "root"
25
+ node is at the bottom of the stack and can not be "snipped" or "cut" off.
26
+
27
+ A ``SplitEnd`` can be duplicated onto a compatible root node. By compatible, the
28
+ new root must contain data that compares as equal to the old root node.
29
+
30
+ .. admonition:: Future directions
31
+
32
+ A ``scalp`` is a container of root nodes all of whose contained data must
33
+ be hashable. The ``scalp.add(d)`` method will return the unique root node with data
34
+ that compares as equal to ``d``, or create a new root node if such a contained node
35
+ does not exist.
36
+
37
+ """
38
+
39
+ __author__ = 'Geoffrey R. Scheller'
40
+ __copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
41
+ __license__ = 'Apache License 2.0'
@@ -19,16 +19,20 @@ Class SplitEnds
19
19
  .. admonition:: LIFO stacks safely sharing immutable data.
20
20
 
21
21
  - each ``SplitEnd`` is a very simple stateful (mutable) LIFO stack
22
- - data can be either "extended" to or "snipped" off the "end"
23
- - the "root" of a ``SplitEnd``
22
+ - data can be either "extended" to or "snipped" off the "end"
23
+ - "root" of a ``SplitEnd`` is embedded in a ``scalp``
24
24
 
25
- - it is fixed and cannot be removed from the ``SplitEnd``
25
+ - the root data is immutable
26
+ - it cannot be split off
26
27
 
27
28
  - different mutable split ends can safely share the same "tail"
28
29
  - each ``SplitEnd`` sees itself as a singularly linked list
29
30
  - bush-like datastructures can be formed using multiple ``SplitEnds``
30
31
  - the ``SplitEnd.split`` and ``len`` methods are O(1)
31
- - in boolean context returns true if the ``SplitEnd`` is not just its "root"
32
+ - in a boolean context
33
+
34
+ - falsy just a "root"
35
+ - truthy otherwise
32
36
 
33
37
  """
34
38
 
@@ -52,8 +56,10 @@ class SplitEnd[D]:
52
56
 
53
57
  def __init__(self, *ds: D, root: SENode[D] | _Sentinel = _sentinel) -> None:
54
58
  """
55
- :param root_data: Irremovable initial data at bottom of stack.
56
- :param data: Removable data to be pushed onto splitend stack.
59
+ .. admonition:: init
60
+
61
+ :param root_data: Irremovable initial data at bottom of stack.
62
+ :param ds: Removable data to be pushed onto splitend stack.
57
63
 
58
64
  """
59
65
  if root is _sentinel:
@@ -78,28 +84,57 @@ class SplitEnd[D]:
78
84
  self._end, self._root, self._count = end, root_node, count
79
85
 
80
86
  def __iter__(self) -> Iterator[D]:
87
+ """
88
+ .. admonition:: iter
89
+
90
+ Iterate from end to root.
91
+
92
+ :yields: data from end to root
93
+
94
+ """
81
95
  return iter(self._end)
82
96
 
83
97
  def __reversed__(self) -> Iterator[D]:
98
+ """
99
+ .. admonition:: reverse iter
100
+
101
+ Iterate from end to root.
102
+
103
+ :yields: data from end to root
104
+
105
+ """
84
106
  return reversed(list(self))
85
107
 
86
108
  def __bool__(self) -> bool:
87
109
  """
88
- :returns: ``True`` is ``SplitEnd`` is not just its root node.
110
+ .. admonition:: bool
111
+
112
+ :returns: ``True`` if ``SplitEnd`` is not just its root node.
89
113
 
90
114
  """
91
115
  return bool(self._end)
92
116
 
93
117
  def __len__(self) -> int:
94
- return self._count
118
+ """
119
+ .. admonition:: len
95
120
 
96
- def __repr__(self) -> str:
97
- return 'SplitEend(' + ', '.join(map(repr, reversed(self))) + ')'
121
+ :returns: The number of nodes on the ``Splitend``.
98
122
 
99
- def __str__(self) -> str:
100
- return '>< ' + ' -> '.join(map(str, self)) + ' ||'
123
+ """
124
+ return self._count
101
125
 
102
126
  def __eq__(self, other: object, /) -> bool:
127
+ """
128
+ .. admonition:: equality comparison
129
+
130
+ Efficiently compare two ``SplitEnds``.
131
+
132
+ :returns: ``True`` only if ``other`` is also a ``Splitend``
133
+ whose corresponding data to ``self`` compares as
134
+ equal and both share the same root node.
135
+ Otherwise ``False``.
136
+
137
+ """
103
138
  if not isinstance(other, type(self)):
104
139
  return False
105
140
 
@@ -120,11 +155,54 @@ class SplitEnd[D]:
120
155
  right = right.prev()
121
156
  return True
122
157
 
158
+ def __repr__(self) -> str:
159
+ """
160
+ .. admonition:: repr string
161
+
162
+ Construct string to reproduce the ``SplitEnd``.
163
+ TODO: Fix, does not include root node.
164
+
165
+ :returns: String to reproduce the ``SplitEnd``.
166
+
167
+ """
168
+ return 'SplitEend(' + ', '.join(map(repr, reversed(self))) + ')'
169
+
170
+ def __str__(self) -> str:
171
+ """
172
+ .. admonition:: user string
173
+
174
+ Construct string meaningful to an end user.
175
+ Does not give root node info.
176
+
177
+ :returns: String to reproduce the ``SplitEnd``.
178
+
179
+ """
180
+ return '>< ' + ' -> '.join(map(str, self)) + ' ||'
181
+
182
+ def snip(self) -> D:
183
+ """
184
+ .. admonition:: cut just tip
185
+
186
+ :returns: Data from snipped tip,
187
+ just return root data if at root.
188
+
189
+ """
190
+ if self._count > 1:
191
+ data, self._end, self._count = self._end.both() + (self._count - 1,)
192
+ else:
193
+ data = self._end.data()
194
+
195
+ return data
196
+
123
197
  def cut(self, num: int | None = None) -> tuple[D, ...]:
124
- """Cut data off end of ``SplitEnd``.
198
+ """
199
+ .. admonition:: Cut data off end of SplitEnd
200
+
201
+ Pop "cut" data off Split, just copy root data.
125
202
 
126
- :param num: Optional number of nodes to cut, default is entire stack.
127
- :returns: Tuple of data cut off from end.
203
+ :param num: Optional number of nodes to cut,
204
+ default is entire stack.
205
+ :returns: Tuple of data cut off from end towards root.
128
206
 
129
207
  """
130
208
  if num is None or num > self._count:
@@ -147,10 +225,13 @@ class SplitEnd[D]:
147
225
  return data
148
226
 
149
227
  def extend(self, *ds: D) -> None:
150
- """Add data onto the tip of the SplitEnd. Like adding a hair
151
- extension.
228
+ """
229
+ .. admonition:: extend SplitEnd with data
152
230
 
153
- :param ds: data to extend the splitend
231
+ Add data onto the tip of the SplitEnd. Like adding
232
+ a hair extension.
233
+
234
+ :param ds: data to extend the splitend
154
235
 
155
236
  """
156
237
  for d in ds:
@@ -158,37 +239,48 @@ class SplitEnd[D]:
158
239
  self._end, self._count = node, self._count + 1
159
240
 
160
241
  def peak(self) -> D:
161
- """Return the data at end (top) of SplitEnd without consuming it.
242
+ """
243
+ .. admonition:: peak at end
162
244
 
163
- :returns: The data at the end of the SplitEnd.
245
+ Return the data at end (top) of the SplitEnd
246
+ without consuming it.
247
+
248
+ :returns: The data at the end (tip) of the SplitEnd.
164
249
 
165
250
  """
166
251
  return self._end.data()
167
252
 
168
253
  def root(self) -> SENode[D]:
169
254
  """
170
- :returns: The root SENode node of the SplitEnd.
255
+ .. admonition:: get root
256
+
257
+ :returns: The root SENode node of the SplitEnd.
171
258
 
172
259
  """
173
260
  return self._root
174
261
 
175
262
  def reroot(self, root: SENode[D]) -> 'SplitEnd[D]':
176
- """Create a brand new SplitEnd with the same data but different root.
263
+ """
264
+ .. admonition:: re-root SplitEnd
177
265
 
178
- .. note::
266
+ Create a brand new SplitEnd with the same data but different root.
179
267
 
180
- Two nodes are compatible root nodes if and only if
268
+ :returns: New SplitEnd with the same data
269
+ and the new ``root``.
270
+ :raises ValueError: If new and original root nodes
271
+ are not compatible.
181
272
 
182
- - they are both actually root nodes
273
+ .. note::
183
274
 
184
- - which implies that their previous nodes are themselves
275
+ Two nodes are compatible root nodes if and only if
185
276
 
186
- - their data compare as equal
277
+ - they are both actually root nodes
187
278
 
188
- - comparing by identity is too strong for some use cases
279
+ - which implies that their previous nodes are themselves
189
280
 
190
- :returns: New SplitEnd with the same data and the new ``root``.
191
- :raises ValueError: If new and original root nodes are not compatible.
281
+ - their data compare as equal
282
+
283
+ - comparing by identity is too strong
192
284
 
193
285
  """
194
286
  if not root:
@@ -205,23 +297,14 @@ class SplitEnd[D]:
205
297
 
206
298
  return SplitEnd(*lifo, root = root)
207
299
 
208
- def snip(self) -> D:
209
- """Snip data off tip of SplitEnd. Just return data if tip is root.
210
-
211
- :returns: Data snipped off tip, just return root data if at root.
212
-
300
+ def split(self, *ds: D) -> 'SplitEnd[D]':
213
301
  """
214
- if self._count > 1:
215
- data, self._end, self._count = self._end.both() + (self._count - 1,)
216
- else:
217
- data = self._end.data()
302
+ .. admonition:: split
218
303
 
219
- return data
220
-
221
- def split(self, *ds: D) -> 'SplitEnd[D]':
222
- """Split the end and add more data.
304
+ Split the end and add more data.
223
305
 
224
- :returns: New instance, same data nodes plus additional ones on end.
306
+ :returns: New instance, same data nodes plus
307
+ additional ones on end. Same root node.
225
308
 
226
309
  """
227
310
  se: SplitEnd[D] = SplitEnd(self._root.data())
@@ -235,11 +318,16 @@ class SplitEnd[D]:
235
318
  def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
236
319
 
237
320
  def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
238
- """Reduce with a function, folding from tip to root.
321
+ """
322
+ .. admonition:: fold
323
+
324
+ Reduce with a function, folding from tip to root.
239
325
 
240
- :param f: Folding function, first argument is for the accumulator.
241
- :param init: Optional initial starting value for the fold.
242
- :returns: Reduced value folding from tip to root in natural LIFO order.
326
+ :param f: Folding function, first argument
327
+ is for the accumulator.
328
+ :param init: Optional initial starting value for the fold.
329
+ :returns: Reduced value folding from tip to root
330
+ in natural LIFO order.
243
331
 
244
332
  """
245
333
  if init is None:
@@ -252,11 +340,15 @@ class SplitEnd[D]:
252
340
  def rev_fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
253
341
 
254
342
  def rev_fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
255
- """Reduce with a function, fold from root to tip.
343
+ """
344
+ .. admonition:: reverse fold
345
+
346
+ Reduce with a function, fold from root to tip.
256
347
 
257
- :param f: Folding function, first argument is for the accumulator.
258
- :param init: Optional initial starting value for the fold.
259
- :returns: Reduced value folding from root to tip.
348
+ :param f: Folding function, first argument
349
+ is for the accumulator.
350
+ :param init: Optional initial starting value for the fold.
351
+ :returns: Reduced value folding from root to tip.
260
352
 
261
353
  """
262
354
  if init is None:
@@ -13,10 +13,9 @@
13
13
  # limitations under the License.
14
14
 
15
15
  """
16
- Class SENode
17
- ------------
16
+ .. admonition:: SplitEnd node
18
17
 
19
- .. admonition:: Used to make inwardly directed bush-like graphs
18
+ Used to make inwardly directed bush-like graphs.
20
19
 
21
20
  - designed so multiple splitends can safely share the same data
22
21
  - nodes always contain data
@@ -51,8 +50,12 @@ class SENode[D]:
51
50
 
52
51
  def __init__(self, data: D, prev: Self | _Sentinel = _sentinel) -> None:
53
52
  """
54
- :param data: Nodes always contain data of type ``D``.
55
- :param prev: Link to previous node. Points to ``self`` if a root node.
53
+ .. admonition:: init
54
+
55
+ :param data: Nodes always contain data of type ``D``.
56
+ :param prev: Link to previous node, points to ``self``
57
+ if a root node.
58
+
56
59
  """
57
60
  self._data = data
58
61
  if prev is not _sentinel:
@@ -62,11 +65,20 @@ class SENode[D]:
62
65
 
63
66
  def __bool__(self) -> bool:
64
67
  """
65
- :returns: ``True`` if ``SENode`` is not a root node.
68
+ .. admonition:: bool
69
+
70
+ :returns: ``True`` only if ``SENode`` is not a root node.
71
+
66
72
  """
67
73
  return self._prev is not self
68
74
 
69
75
  def __iter__(self) -> Iterator[D]:
76
+ """
77
+ .. admonition:: iter
78
+
79
+ :yields: Node date until and including root node.
80
+
81
+ """
70
82
  node = self
71
83
  while node:
72
84
  yield node._data
@@ -75,8 +87,18 @@ class SENode[D]:
75
87
 
76
88
  def __eq__(self, other: object) -> bool:
77
89
  """
78
- Two ``SENodes`` nodes are equal if their previous nodes are the
79
- same object and their data compare as equal.
90
+ .. admonition:: equality comparison
91
+
92
+ Two ``SENodes`` nodes are equal if their previous nodes
93
+ are the same object and their data compare as equal.
94
+
95
+ :param other:
96
+ :returns: ``True`` if ``other`` is a SplitEnd which compares
97
+ as equal to ``self`` as described above.
98
+
99
+ .. note::
100
+
101
+ Will be useful when consolidating SplitEnds.
80
102
 
81
103
  """
82
104
  if not isinstance(other, type(self)):
@@ -88,35 +110,43 @@ class SENode[D]:
88
110
  return True
89
111
  return False
90
112
 
91
- def both(self) -> tuple[D, Self]:
92
- """Peak at data and previous node, if a root then data and self.
93
-
94
- :returns: tuple of type tuple[D, SENode[D]]
95
-
96
- """
97
- return self._data, self._prev
98
-
99
113
  def data(self) -> D:
100
- """Peak at data.
114
+ """
115
+ .. admonition:: peak at data in SENode
101
116
 
102
- :returns: The data stored in the ``SENode``.
117
+ :returns: The data stored in the ``SENode``.
103
118
 
104
119
  """
105
120
  return self._data
106
121
 
107
122
  def prev(self) -> Self:
108
- """Peak at previous node.
123
+ """
124
+ .. admonition:: peak at previous node
109
125
 
110
- :returns: The previous node stored in the ``SENode``.
126
+ :returns: The previous node stored in the ``SENode``.
111
127
 
112
128
  """
113
129
  return self._prev
114
130
 
131
+ def both(self) -> tuple[D, Self]:
132
+ """
133
+ .. admonition:: both data and previous node
134
+
135
+ Peak at data and previous node,
136
+ if a root then data and ``self``.
137
+
138
+ :returns: Tuple of type ``tuple[D, SENode[D]]``
139
+
140
+ """
141
+ return self._data, self._prev
142
+
115
143
  def push(self, data: D) -> Self:
116
- """Create a new ``SENode[D]``.
144
+ """
145
+ .. admonition:: push date to create new ``SENode[D]``
117
146
 
118
- :param data: Data for new node to contain.
119
- :returns: New ``SENode`` whose previous node is the current node.
147
+ :param data: Data for new node to contain.
148
+ :returns: New ``SENode`` whose previous node is
149
+ the current node.
120
150
 
121
151
  """
122
152
  return cast(Self, SENode(data, self))
@@ -127,11 +157,16 @@ class SENode[D]:
127
157
  def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
128
158
 
129
159
  def fold[T](self, f: Callable[[T, D], T], init: T | _Sentinel = _sentinel) -> T:
130
- """Fold data across linked nodes with a function..
160
+ """
161
+ .. admonition:: fold
162
+
163
+ Fold data across linked nodes with a function.
131
164
 
132
- :param f: Folding function, first argument is for accumulated value.`
133
- :param init: Optional initial starting value for the fold.
134
- :returns: Reduced value folded from end to root in natural LIFO order.
165
+ :param f: Folding function, first argument
166
+ is for accumulated value.`
167
+ :param init: Optional initial starting value for the fold.
168
+ :returns: Reduced value folded from end to root
169
+ in natural LIFO order.
135
170
 
136
171
  """
137
172
  if init is not _sentinel:
@@ -1,38 +0,0 @@
1
- # Copyright 2024-2026 Geoffrey R. Scheller
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- """Mutable stack objects that can safely share data.
16
-
17
- Python package implementing a singularly linked stack of nodes called a ``SplitEnd``.
18
- While mutable, these data structures can safely share immutable data nodes between
19
- themselves. These nodes, of type ``SENode``, always contain data. A root node is
20
- a node whose previous node is itself. Root nodes mark the bottom of the stack.
21
-
22
- Like one of many "split ends" from shafts of hair, a ``SplitEnd`` can be "snipped"
23
- shorter or "extended" further from its "end" node at the top of the stack. Its "root"
24
- node is at the bottom of the stack and can not be "snipped" or "cut" off.
25
-
26
- A ``SplitEnd`` can be duplicated onto a compatible root node. By compatible, the
27
- new root must contain data that compares as equal to the old root node.
28
-
29
- **TODO:** A ``scalp`` is a container of root nodes all of whose contained data must
30
- be hashable. The ``scalp.add(d)`` method will return the unique root node with data
31
- that compares as equal to ``d``, or create a new root node if such a contained node
32
- does not exist.
33
-
34
- """
35
-
36
- __author__ = 'Geoffrey R. Scheller'
37
- __copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
38
- __license__ = 'Apache License 2.0'