flatdir 0.0.6__py3-none-any.whl

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.
flatdir/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.6"
flatdir/__main__.py ADDED
@@ -0,0 +1,89 @@
1
+ """Command-line entrypoint for flatdir: print directory listing as JSON.
2
+
3
+ Usage: python -m flatdir [--limit N] [--depth N] [--output FILE] [--fields FILE] [path]
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from .listing import list_entries
13
+ from .plugins_loader import load_fields_file
14
+
15
+
16
+ def main(argv: list[str] | None = None) -> int:
17
+ argv = argv if argv is not None else sys.argv[1:]
18
+
19
+ # parse --limit flag if present
20
+ limit: int | None = None
21
+ if "--limit" in argv:
22
+ try:
23
+ idx = argv.index("--limit")
24
+ limit = int(argv[idx + 1])
25
+ argv = argv[:idx] + argv[idx + 2 :]
26
+ except (IndexError, ValueError):
27
+ print("error: --limit requires a valid integer argument", file=sys.stderr)
28
+ return 1
29
+
30
+ # parse --depth flag if present
31
+ depth: int | None = None
32
+ if "--depth" in argv:
33
+ try:
34
+ idx = argv.index("--depth")
35
+ depth = int(argv[idx + 1])
36
+ argv = argv[:idx] + argv[idx + 2 :]
37
+ except (IndexError, ValueError):
38
+ print("error: --depth requires a valid integer argument", file=sys.stderr)
39
+ return 1
40
+
41
+ # parse --output flag if present
42
+ output: str | None = None
43
+ if "--output" in argv:
44
+ try:
45
+ idx = argv.index("--output")
46
+ output = argv[idx + 1]
47
+ argv = argv[:idx] + argv[idx + 2 :]
48
+ except (IndexError, ValueError):
49
+ print("error: --output requires a file path argument", file=sys.stderr)
50
+ return 1
51
+
52
+ # parse --fields flag if present
53
+ fields = None
54
+ if "--fields" in argv:
55
+ try:
56
+ idx = argv.index("--fields")
57
+ fields_path = argv[idx + 1]
58
+ fields = load_fields_file(fields_path)
59
+ argv = argv[:idx] + argv[idx + 2 :]
60
+ except IndexError:
61
+ print("error: --fields requires a file path argument", file=sys.stderr)
62
+ return 1
63
+ except (FileNotFoundError, ImportError) as exc:
64
+ print(f"error: {exc}", file=sys.stderr)
65
+ return 1
66
+
67
+ path = Path(argv[0]) if argv else Path(".")
68
+
69
+ # error in case of missing path or path is not a directory
70
+ if not path.exists() or not path.is_dir():
71
+ print(f"path is not a directory: {path}", file=sys.stderr)
72
+ return 2
73
+
74
+ # generate the actual list of entries to be returned as JSON
75
+ entries = list_entries(path, limit=limit, depth=depth, fields=fields)
76
+
77
+ # write JSON to output file or stdout
78
+ if output is not None:
79
+ with open(output, "w", encoding="utf-8") as f:
80
+ json.dump(entries, f, ensure_ascii=False, indent=4)
81
+ f.write("\n")
82
+ else:
83
+ json.dump(entries, sys.stdout, ensure_ascii=False, indent=4)
84
+ _ = sys.stdout.write("\n")
85
+ return 0
86
+
87
+
88
+ if __name__ == "__main__":
89
+ raise SystemExit(main())
flatdir/listing.py ADDED
@@ -0,0 +1,66 @@
1
+ """List entries in a directory and return metadata as a list of dict.
2
+
3
+ Each entry is a dict with keys determined by field plugins (defaults: name, type, mtime, size).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from pathlib import Path
10
+
11
+ from .plugins import defaults as _defaults
12
+ from .plugins_loader import load_fields_file
13
+
14
+ # built-in default fields, loaded once
15
+ DEFAULT_FIELDS = load_fields_file(_defaults.__file__)
16
+
17
+
18
+ def list_entries(
19
+ root: Path,
20
+ limit: int | None = None,
21
+ depth: int | None = None,
22
+ fields: dict[str, object] | None = None,
23
+ ) -> list[dict[str, object]]:
24
+ entries: list[dict[str, object]] = []
25
+ root = root.resolve()
26
+
27
+ # merge default fields with custom fields (custom can override defaults)
28
+ all_fields = dict(DEFAULT_FIELDS)
29
+ if fields:
30
+ all_fields.update(fields)
31
+
32
+ for dirpath, dirnames, filenames in os.walk(root):
33
+ base = Path(dirpath)
34
+ # check depth constraint
35
+ current_depth = len(base.relative_to(root).parts)
36
+ if depth is not None and depth >= 0 and current_depth > depth:
37
+ continue
38
+
39
+ # list subdirectories at this level
40
+ for dirname in dirnames:
41
+ p = (base / dirname).resolve()
42
+ entry: dict[str, object] = {}
43
+ for field_name, func in all_fields.items():
44
+ value = func(p, root)
45
+ if value is not None:
46
+ entry[field_name] = value
47
+ entries.append(entry)
48
+
49
+ # list files at this level
50
+ for filename in filenames:
51
+ p = (base / filename).resolve()
52
+ entry = {}
53
+ for field_name, func in all_fields.items():
54
+ value = func(p, root)
55
+ if value is not None:
56
+ entry[field_name] = value
57
+ entries.append(entry)
58
+
59
+ # return a sorted list of entries
60
+ entries.sort(key=lambda e: str(e.get("name", "")))
61
+
62
+ # apply limit if provided
63
+ if limit is not None and limit >= 0:
64
+ entries = entries[:limit]
65
+
66
+ return entries
File without changes
@@ -0,0 +1,38 @@
1
+ """Default field plugins for flatdir.
2
+
3
+ These functions produce the built-in fields: name, type, mtime, size.
4
+ Each function receives the file path and the root directory path.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import time
11
+ from pathlib import Path
12
+
13
+
14
+ def name(path: Path, root: Path) -> str:
15
+ """Relative path from the listing root."""
16
+ try:
17
+ return str(path.relative_to(root))
18
+ except ValueError:
19
+ return str(os.path.relpath(str(path), str(root)))
20
+
21
+
22
+ def type(path: Path, root: Path) -> str:
23
+ """Entry type: 'file' or 'directory'."""
24
+ return "directory" if path.is_dir() else "file"
25
+
26
+
27
+ def mtime(path: Path, root: Path) -> str:
28
+ """Last modification time in HTTP-date format."""
29
+ st = path.stat()
30
+ t = time.gmtime(st.st_mtime)
31
+ return time.strftime("%a, %d %b %Y %H:%M:%S GMT", t)
32
+
33
+
34
+ def size(path: Path, root: Path) -> int | None:
35
+ """File size in bytes. Returns None for directories (omitted from output)."""
36
+ if path.is_dir():
37
+ return None
38
+ return int(path.stat().st_size)
@@ -0,0 +1,8 @@
1
+ """Example plugin: adds the length of the filename to each entry."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def filename_length(path: Path, root: Path) -> int:
7
+ """Return the number of characters in the filename (including extension)."""
8
+ return len(path.name)
@@ -0,0 +1,52 @@
1
+ """Load custom field functions from a user-provided Python file.
2
+
3
+ A fields file is a plain Python module where each **public** function
4
+ (i.e. whose name does not start with ``_``) is treated as a field provider.
5
+
6
+ Each function must accept a single :class:`pathlib.Path` argument (the file
7
+ being listed) and return a JSON-serialisable value. The function name becomes
8
+ the field key in the output.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ import inspect
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Callable
18
+
19
+ FieldFunc = Callable[[Path], object]
20
+
21
+
22
+ def load_fields_file(filepath: str) -> dict[str, FieldFunc]:
23
+ """Import *filepath* as a module and return its public callables.
24
+
25
+ Returns a ``{name: func}`` mapping for every public, non-class callable
26
+ defined in the file.
27
+ """
28
+ path = Path(filepath).resolve()
29
+ if not path.is_file():
30
+ raise FileNotFoundError(f"fields file not found: {filepath}")
31
+
32
+ # import the file as a temporary module
33
+ spec = importlib.util.spec_from_file_location("_flatdir_fields", str(path))
34
+ if spec is None or spec.loader is None:
35
+ raise ImportError(f"cannot load fields file: {filepath}")
36
+ module = importlib.util.module_from_spec(spec)
37
+ sys.modules["_flatdir_fields"] = module
38
+ spec.loader.exec_module(module)
39
+
40
+ fields: dict[str, FieldFunc] = {}
41
+ for name, obj in inspect.getmembers(module, callable):
42
+ # skip private names, classes, and imported builtins
43
+ if name.startswith("_"):
44
+ continue
45
+ if inspect.isclass(obj):
46
+ continue
47
+ # only keep functions actually defined in this file
48
+ if getattr(obj, "__module__", None) != "_flatdir_fields":
49
+ continue
50
+ fields[name] = obj
51
+
52
+ return fields
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: flatdir
3
+ Version: 0.0.6
4
+ Summary: A Python library to create a flat JSON index of files and directories.
5
+ Author: Your Name
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/romsson/flatdir
8
+ Project-URL: Repository, https://github.com/romsson/flatdir
9
+ Classifier: Natural Language :: English
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: <4,>=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Dynamic: license-file
21
+
22
+ # flatdir
23
+
24
+ `flatdir` scans a directory tree and generates a flat JSON file with metadata for each entry.
25
+
26
+ ## Installation
27
+
28
+ # flatdir
29
+
30
+ `flatdir` scans a directory tree and generates a flat JSON file with metadata for each entry.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install -e .
36
+ ```
37
+
38
+ Or from PyPI:
39
+
40
+ ```bash
41
+ pip install flatdir
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```bash
47
+ python -m flatdir .
48
+ ```
@@ -0,0 +1,12 @@
1
+ flatdir/__init__.py,sha256=QiiYsv0kcJaB8wCWyT-FnI2b6be87HA-CrrIUn8LQhg,22
2
+ flatdir/__main__.py,sha256=aEzAZ2EG_TiQwf8ZXeG-T8imTp2z1z92kzQwpmlVL1Y,2896
3
+ flatdir/listing.py,sha256=IANCsiryDUuuMfXjZOREbIQY_ePhwd44aT5Bsp_vOLg,2043
4
+ flatdir/plugins_loader.py,sha256=O8RI2au3oQFMM3olKF4JXMredTGVoQRmhxM4NvAfUhM,1778
5
+ flatdir/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ flatdir/plugins/defaults.py,sha256=SiOgZoBrD_qD0VxkqSthFAcvDnBWZwL1uArFQS22bh8,1045
7
+ flatdir/plugins/filename_length.py,sha256=g8DsDODnKxrhAghOpz7DyYERXQlXxHk7nENBAHbW6Yk,256
8
+ flatdir-0.0.6.dist-info/licenses/LICENSE,sha256=6naVCeRSIEXqwijKGxc74qCmX9xZr_sVWdmSAFSWIdI,1073
9
+ flatdir-0.0.6.dist-info/METADATA,sha256=E1Yaj9IvYfsddwjpm3xqpmSeRIJO5k2xy_vjUVD6Ki8,1140
10
+ flatdir-0.0.6.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
11
+ flatdir-0.0.6.dist-info/top_level.txt,sha256=_xeO7mmnryAGKUeKuTunqj9gy3duEVHLQrmpkU7i7nw,8
12
+ flatdir-0.0.6.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Romain Vuillemot
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 @@
1
+ flatdir