miss-lemon 0.2.0__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 (35) hide show
  1. miss_lemon-0.2.0/LICENCE.txt +21 -0
  2. miss_lemon-0.2.0/MANIFEST.in +9 -0
  3. miss_lemon-0.2.0/PKG-INFO +76 -0
  4. miss_lemon-0.2.0/README.rst +33 -0
  5. miss_lemon-0.2.0/copy_tests/__init__.py +0 -0
  6. miss_lemon-0.2.0/copy_tests/conftest.py +97 -0
  7. miss_lemon-0.2.0/miss_lemon/__init__.py +6 -0
  8. miss_lemon-0.2.0/miss_lemon/cli/__init__.py +0 -0
  9. miss_lemon-0.2.0/miss_lemon/cli/check.py +126 -0
  10. miss_lemon-0.2.0/miss_lemon/cli/entrypoint.py +70 -0
  11. miss_lemon-0.2.0/miss_lemon/cli/rename.py +86 -0
  12. miss_lemon-0.2.0/miss_lemon/cli/version.py +15 -0
  13. miss_lemon-0.2.0/miss_lemon/diff.py +214 -0
  14. miss_lemon-0.2.0/miss_lemon/exceptions.py +20 -0
  15. miss_lemon-0.2.0/miss_lemon/logger.py +40 -0
  16. miss_lemon-0.2.0/miss_lemon/models/__init__.py +0 -0
  17. miss_lemon-0.2.0/miss_lemon/models/lists.py +38 -0
  18. miss_lemon-0.2.0/miss_lemon/models/mixins/__init__.py +0 -0
  19. miss_lemon-0.2.0/miss_lemon/models/mixins/export.py +92 -0
  20. miss_lemon-0.2.0/miss_lemon/models/resource.py +207 -0
  21. miss_lemon-0.2.0/miss_lemon/renamer.py +91 -0
  22. miss_lemon-0.2.0/miss_lemon/utils/__init__.py +0 -0
  23. miss_lemon-0.2.0/miss_lemon/utils/jsons.py +40 -0
  24. miss_lemon-0.2.0/miss_lemon/utils/tests.py +19 -0
  25. miss_lemon-0.2.0/miss_lemon/walker.py +224 -0
  26. miss_lemon-0.2.0/miss_lemon.egg-info/PKG-INFO +76 -0
  27. miss_lemon-0.2.0/miss_lemon.egg-info/SOURCES.txt +34 -0
  28. miss_lemon-0.2.0/miss_lemon.egg-info/dependency_links.txt +1 -0
  29. miss_lemon-0.2.0/miss_lemon.egg-info/entry_points.txt +2 -0
  30. miss_lemon-0.2.0/miss_lemon.egg-info/requires.txt +24 -0
  31. miss_lemon-0.2.0/miss_lemon.egg-info/top_level.txt +2 -0
  32. miss_lemon-0.2.0/miss_lemon.egg-info/zip-safe +1 -0
  33. miss_lemon-0.2.0/pyproject.toml +3 -0
  34. miss_lemon-0.2.0/setup.cfg +96 -0
  35. miss_lemon-0.2.0/setup.py +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026, David Thenon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include MANIFEST.in
2
+ include LICENCE.txt
3
+ include README.rst
4
+
5
+ exclude sphinx_reload.py
6
+
7
+ recursive-exclude tests *
8
+ recursive-exclude * __pycache__
9
+ recursive-exclude * *.py[co]
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: miss-lemon
3
+ Version: 0.2.0
4
+ Summary: Rename a file structure with auto incremented prefix number
5
+ Home-page: https://github.com/sveetch/miss-lemon
6
+ Author: David Thenon
7
+ Author-email: sveetch@gmail.com
8
+ License: MIT
9
+ Project-URL: Source Code, https://github.com/sveetch/miss-lemon
10
+ Project-URL: Issue Tracker, https://github.com/sveetch/miss-lemon/issues
11
+ Project-URL: Changelog, https://miss-lemon.readthedocs.io/en/latest/history.html
12
+ Project-URL: Documentation, https://miss-lemon.readthedocs.io/
13
+ Keywords: Python
14
+ Classifier: Development Status :: 2 - Pre-Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: English
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/x-rst
23
+ License-File: LICENCE.txt
24
+ Requires-Dist: click>=8.0
25
+ Requires-Dist: colorlog
26
+ Requires-Dist: bigtree>=1.5.3
27
+ Provides-Extra: rich
28
+ Requires-Dist: rich-click<2.0.0,>=1.9.9; extra == "rich"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Provides-Extra: quality
32
+ Requires-Dist: flake8>=6.0.0; extra == "quality"
33
+ Requires-Dist: tox>=4.11.0; extra == "quality"
34
+ Provides-Extra: doc
35
+ Requires-Dist: sphinx>=8.0.2; extra == "doc"
36
+ Requires-Dist: furo>=2024.8.6; extra == "doc"
37
+ Requires-Dist: sphinx-copybutton>=0.5.2; extra == "doc"
38
+ Provides-Extra: doc-live
39
+ Requires-Dist: livereload>=2.6.0; extra == "doc-live"
40
+ Provides-Extra: release
41
+ Requires-Dist: twine>=5.0.0; extra == "release"
42
+ Dynamic: license-file
43
+
44
+ .. _Python: https://www.python.org/
45
+ .. _Click: https://click.palletsprojects.com
46
+ .. _Big Tree: https://bigtree.readthedocs.io/
47
+ .. _rich-click: https://ewels.github.io/rich-click/latest/
48
+
49
+
50
+ ==========
51
+ Miss Lemon
52
+ ==========
53
+
54
+ Miss Lemon is tool to rename resources (files and directories) with a prefix like
55
+ ``0010_stuff/`` or ``0011_foo.py``.
56
+
57
+ The purpose of numbering resources is to organize them in order.
58
+
59
+ This tool helps to do it automatically without to rename them each time you introduce
60
+ a new resource or shift some ones.
61
+
62
+
63
+ Dependencies
64
+ ************
65
+
66
+ * `Python`_>=3.8;
67
+ * `Click`_>=8.0;
68
+ * `Big Tree`_>=1.5.3;
69
+ * Optionnally `rich-click`_>=1.9.9, <2.0.0;
70
+
71
+ Links
72
+ *****
73
+
74
+ * Read the documentation on `Read the docs <https://miss-lemon.readthedocs.io/>`_;
75
+ * Download its `PyPi package <https://pypi.python.org/pypi/miss-lemon>`_;
76
+ * Clone it on its `Github repository <https://github.com/sveetch/miss-lemon>`_;
@@ -0,0 +1,33 @@
1
+ .. _Python: https://www.python.org/
2
+ .. _Click: https://click.palletsprojects.com
3
+ .. _Big Tree: https://bigtree.readthedocs.io/
4
+ .. _rich-click: https://ewels.github.io/rich-click/latest/
5
+
6
+
7
+ ==========
8
+ Miss Lemon
9
+ ==========
10
+
11
+ Miss Lemon is tool to rename resources (files and directories) with a prefix like
12
+ ``0010_stuff/`` or ``0011_foo.py``.
13
+
14
+ The purpose of numbering resources is to organize them in order.
15
+
16
+ This tool helps to do it automatically without to rename them each time you introduce
17
+ a new resource or shift some ones.
18
+
19
+
20
+ Dependencies
21
+ ************
22
+
23
+ * `Python`_>=3.8;
24
+ * `Click`_>=8.0;
25
+ * `Big Tree`_>=1.5.3;
26
+ * Optionnally `rich-click`_>=1.9.9, <2.0.0;
27
+
28
+ Links
29
+ *****
30
+
31
+ * Read the documentation on `Read the docs <https://miss-lemon.readthedocs.io/>`_;
32
+ * Download its `PyPi package <https://pypi.python.org/pypi/miss-lemon>`_;
33
+ * Clone it on its `Github repository <https://github.com/sveetch/miss-lemon>`_;
File without changes
@@ -0,0 +1,97 @@
1
+ """
2
+ Pytest fixtures
3
+ """
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ import miss_lemon
10
+
11
+
12
+ class FixturesSettingsTestMixin(object):
13
+ """
14
+ A mixin containing settings about application. This is almost only about useful
15
+ paths which may be used in tests.
16
+
17
+ Attributes:
18
+ application_path (pathlib.Path): Absolute path to the application directory.
19
+ package_path (pathlib.Path): Absolute path to the package directory.
20
+ tests_dir (pathlib.Path): Directory name which include tests.
21
+ tests_path (pathlib.Path): Absolute path to the tests directory.
22
+ datas_dir (pathlib.Path): Directory name which include tests datas.
23
+ datas_path (pathlib.Path): Absolute path to the tests datas.
24
+ """
25
+ def __init__(self):
26
+ self.application_path = Path(
27
+ miss_lemon.__file__
28
+ ).parents[0].resolve()
29
+
30
+ self.package_path = self.application_path.parent
31
+
32
+ self.tests_dir = "tests"
33
+ self.tests_path = self.package_path / self.tests_dir
34
+
35
+ self.datas_dir = "data_fixtures"
36
+ self.datas_path = self.tests_path / self.datas_dir
37
+
38
+ def format(self, content):
39
+ """
40
+ Format given string to include some values related to this application.
41
+
42
+ Arguments:
43
+ content (str): Content string to format with possible values.
44
+
45
+ Returns:
46
+ str: Given string formatted with possible values.
47
+ """
48
+ return content.format(
49
+ HOMEDIR=Path.home(),
50
+ PACKAGE=str(self.package_path),
51
+ APPLICATION=str(self.application_path),
52
+ TESTS=str(self.tests_path),
53
+ FIXTURES=str(self.datas_path),
54
+ VERSION=miss_lemon.__version__,
55
+ )
56
+
57
+
58
+ @pytest.fixture(scope="function")
59
+ def temp_builds_dir(tmp_path):
60
+ """
61
+ Prepare a temporary build directory.
62
+
63
+ NOTE: You should use directly the "tmp_path" fixture in your tests.
64
+ """
65
+ return tmp_path
66
+
67
+
68
+ @pytest.fixture(scope="module")
69
+ def settings():
70
+ """
71
+ Initialize and return settings for tests.
72
+
73
+ Example:
74
+ You may use it like: ::
75
+
76
+ def test_foo(settings):
77
+ print(settings.package_path)
78
+ print(settings.format("Application version: {VERSION}"))
79
+ """
80
+ return FixturesSettingsTestMixin()
81
+
82
+
83
+ @pytest.fixture(scope="function")
84
+ def basic_structure(tmp_path, settings):
85
+ """
86
+ Copy the "basic_suite" structure into a temporary directory.
87
+
88
+ Returns:
89
+ Path: The path to the copied structure in temp directory.
90
+ """
91
+ dirname = "basic_structure"
92
+ basic_sample_path = settings.datas_path / dirname
93
+ destination = tmp_path / dirname
94
+
95
+ shutil.copytree(basic_sample_path, destination)
96
+
97
+ return destination
@@ -0,0 +1,6 @@
1
+ """Rename a file structure with auto incremented prefix number"""
2
+ from importlib.metadata import version
3
+
4
+
5
+ __pkgname__ = "miss-lemon"
6
+ __version__ = version(__pkgname__)
File without changes
@@ -0,0 +1,126 @@
1
+ from pathlib import Path
2
+
3
+ from ..walker import DirWalker
4
+ from ..diff import DiffTree
5
+
6
+ # On default rich is not present in outputs and may become available further
7
+ AVAILABLE_OUTPUTS = ["json", "tree"]
8
+ # On default Big tree output is prefered
9
+ DEFAULT_OUTPUT = "tree"
10
+ # First try to get Rich stack, fallback to basic Click and BigTree
11
+ try:
12
+ import rich_click as click
13
+ except ImportError:
14
+ import click
15
+ else:
16
+ from rich import print as RichPrint
17
+ # Append Rich to output and make it the default one
18
+ AVAILABLE_OUTPUTS.append("rich")
19
+ DEFAULT_OUTPUT = "rich"
20
+
21
+
22
+ @click.command()
23
+ @click.argument(
24
+ "source",
25
+ nargs=1,
26
+ type=click.Path(
27
+ exists=True,
28
+ file_okay=False,
29
+ dir_okay=True,
30
+ path_type=Path,
31
+ resolve_path=True,
32
+ )
33
+ )
34
+ @click.option(
35
+ "--step",
36
+ metavar="INTEGER",
37
+ show_default=True,
38
+ type=click.INT,
39
+ default=100,
40
+ help=(
41
+ "The lowest number slot step for the leaf resources."
42
+ ),
43
+ )
44
+ @click.option(
45
+ "--output",
46
+ metavar="NAME",
47
+ show_default=True,
48
+ type=click.Choice(AVAILABLE_OUTPUTS),
49
+ help=(
50
+ "Report output format name. 'json' will print a dictionnary of computed "
51
+ "resources. 'tree' and 'rich' are similar but the latter one "
52
+ "is only allowed if the rich stack has been installed, it will provide colored "
53
+ "nodes (according to their status). Both will print a tree of differences "
54
+ "between original and renamed tree"
55
+ ),
56
+ default=DEFAULT_OUTPUT,
57
+ )
58
+ @click.option(
59
+ "--unprefixed",
60
+ is_flag=True,
61
+ help=(
62
+ "Allows to collect all resource even if they don't match the regex for "
63
+ "\"filename with prefix\" (eg: '0001_foo'). Default behavior when this option "
64
+ "is not enabled, is to ignore those files without prefix, they won't be "
65
+ "renamed. This is commonly used with '--excludes' to prevent some resources to "
66
+ "be renamed."
67
+ ),
68
+ )
69
+ @click.option(
70
+ "--excludes",
71
+ metavar="PATTERN",
72
+ show_default=True,
73
+ multiple=True,
74
+ help=(
75
+ "Define a 'Unix filename pattern'(compatible with Python module 'fnmatch') to "
76
+ "exclude resources from collect. This can be defined multiple times."
77
+ ),
78
+ )
79
+ @click.option(
80
+ "--from-original",
81
+ is_flag=True,
82
+ help=(
83
+ "If enabled, the tree will display the original name first then the renamed "
84
+ "name. On default the renamed name is printed first."
85
+ ),
86
+ )
87
+ @click.pass_context
88
+ def check_command(context, source, step, output, unprefixed, excludes, from_original):
89
+ """
90
+ Proceed to analyze and prefix computation on a path then output a report.
91
+
92
+ This does not write or rename anything.
93
+
94
+ The preview report can be either a JSON payload or a tree of the original structure
95
+ including a preview of renaming that would occurs with the command 'rename'.
96
+ """
97
+ logger = context.obj["logger"]
98
+
99
+ logger.debug("Working on: {}".format(source))
100
+ logger.debug("Select output: {}".format(output))
101
+ logger.debug("Collecting resource without prefix: {}".format(unprefixed))
102
+ if excludes:
103
+ logger.debug("Excludes: {}".format(", ".join(excludes)))
104
+
105
+ # Collect and compute with user options
106
+ new_walker = DirWalker(source, allow_unprefixed=unprefixed, excludes=excludes)
107
+ new_root = new_walker.compute(step=step)
108
+
109
+ # Just print output as simple JSON
110
+ if output == "json":
111
+ click.echo(new_root.as_json())
112
+ return
113
+
114
+ differ = DiffTree(source_first=from_original)
115
+
116
+ # Collect the full structure without any exclusions
117
+ original_walker = DirWalker(source, allow_unprefixed=True)
118
+ original_root = original_walker.collect()
119
+
120
+ # Print tree preview with differences report
121
+ if output == "tree":
122
+ click.echo("🎨 Big Tree preview")
123
+ click.echo(differ.build_bigtree(original_root, new_root).show(alias="label"))
124
+ elif output == "rich":
125
+ click.echo("🎨 Rich tree preview")
126
+ RichPrint(differ.build_richtree(original_root, new_root))
@@ -0,0 +1,70 @@
1
+ """
2
+ Main entrance to commandline actions
3
+ """
4
+ try:
5
+ import rich_click as click
6
+ except ImportError:
7
+ import click
8
+ else:
9
+ # This is rich-click option only to show arguments in help
10
+ click.rich_click.SHOW_ARGUMENTS = True
11
+
12
+ from miss_lemon.logger import init_logger
13
+
14
+ from miss_lemon import __pkgname__
15
+ from miss_lemon.cli.version import version_command
16
+ from miss_lemon.cli.check import check_command
17
+ from miss_lemon.cli.rename import rename_command
18
+
19
+
20
+ # Help alias on "-h" argument
21
+ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
22
+
23
+
24
+ # Default logger conf
25
+ APP_LOGGER_CONF = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", None)
26
+
27
+
28
+ @click.group(context_settings=CONTEXT_SETTINGS)
29
+ @click.option(
30
+ "-v", "--verbose",
31
+ type=click.IntRange(min=0, max=5),
32
+ default=4,
33
+ metavar="INTEGER",
34
+ help=(
35
+ "An integer between 0 and 5, where '0' make a totaly "
36
+ "silent output and '5' set level to DEBUG (the most verbose "
37
+ "level). Default to '4' (Info level)."
38
+ )
39
+ )
40
+ @click.pass_context
41
+ def cli_frontend(ctx, verbose):
42
+ """
43
+ Script for application commandline entrypoint.
44
+ """
45
+ printout = True
46
+ if verbose == 0:
47
+ verbose = 1
48
+ printout = False
49
+
50
+ # Verbosity is the inverse of logging levels
51
+ levels = [item for item in APP_LOGGER_CONF]
52
+ levels.reverse()
53
+ # Init the logger config
54
+ root_logger = init_logger(
55
+ __pkgname__,
56
+ levels[verbose],
57
+ printout=printout
58
+ )
59
+
60
+ # Init the default context that will be passed to commands
61
+ ctx.obj = {
62
+ "verbosity": verbose,
63
+ "logger": root_logger,
64
+ }
65
+
66
+
67
+ # Attach commands methods to the main grouper
68
+ cli_frontend.add_command(version_command, name="version")
69
+ cli_frontend.add_command(check_command, name="check")
70
+ cli_frontend.add_command(rename_command, name="rename")
@@ -0,0 +1,86 @@
1
+ from pathlib import Path
2
+
3
+ from ..walker import DirWalker
4
+ from ..renamer import Renamer
5
+
6
+ try:
7
+ import rich_click as click
8
+ except ImportError:
9
+ import click
10
+
11
+
12
+ @click.command()
13
+ @click.argument(
14
+ "source",
15
+ nargs=1,
16
+ type=click.Path(
17
+ exists=True,
18
+ file_okay=False,
19
+ dir_okay=True,
20
+ path_type=Path,
21
+ resolve_path=True,
22
+ )
23
+ )
24
+ @click.option(
25
+ "--step",
26
+ metavar="INTEGER",
27
+ show_default=True,
28
+ type=click.INT,
29
+ default=100,
30
+ help=(
31
+ "The lowest number slot step for the leaf resources."
32
+ ),
33
+ )
34
+ @click.option(
35
+ "--command",
36
+ metavar="NAME",
37
+ show_default=True,
38
+ type=click.Choice(["mv", "git-mv"]),
39
+ help="Command name. All resource renaming lines will use this command.",
40
+ default="mv",
41
+ )
42
+ @click.option(
43
+ "--unprefixed",
44
+ is_flag=True,
45
+ help=(
46
+ "Allows to collect all resource even if they don't match the regex for "
47
+ "\"filename with prefix\" (eg: '0001_foo'). Default behavior when this option "
48
+ "is not enabled, is to ignore those files without prefix, they won't be "
49
+ "renamed. This is commonly used with '--exclude' to prevent some resources to "
50
+ "be renamed."
51
+ ),
52
+ )
53
+ @click.option(
54
+ "--excludes",
55
+ metavar="PATTERN",
56
+ multiple=True,
57
+ help=(
58
+ "Define a 'Unix filename pattern'(compatible with Python module 'fnmatch') to "
59
+ "exclude resources from collect. This can be defined multiple times."
60
+ ),
61
+ )
62
+ @click.pass_context
63
+ def rename_command(context, source, step, command, unprefixed, excludes):
64
+ """
65
+ Rename resources (directories and files) from a path with a computed number prefix.
66
+ """
67
+ logger = context.obj["logger"]
68
+
69
+ logger.debug("Working on: {}".format(source))
70
+ logger.debug("Selected command: {}".format(command))
71
+ logger.debug("Collecting resource without prefix: {}".format(unprefixed))
72
+ if excludes:
73
+ logger.debug("Excludes: {}".format(", ".join(excludes)))
74
+
75
+ walker = DirWalker(source, allow_unprefixed=unprefixed, excludes=excludes)
76
+
77
+ root = walker.compute(step=step)
78
+
79
+ renamer = Renamer(root)
80
+
81
+ if command == "mv":
82
+ cmd_output = renamer.with_shell_mv()
83
+ elif command == "git-mv":
84
+ cmd_output = renamer.with_shell_gitmv()
85
+
86
+ click.echo(cmd_output)
@@ -0,0 +1,15 @@
1
+ try:
2
+ import rich_click as click
3
+ except ImportError:
4
+ import click
5
+
6
+ from miss_lemon import __version__
7
+
8
+
9
+ @click.command()
10
+ @click.pass_context
11
+ def version_command(context):
12
+ """
13
+ Print out version information.
14
+ """
15
+ click.echo("miss-lemon {}".format(__version__))