Skip to content

Commit c3fb08d

Browse files
authored
Merge pull request #19 from MarkusZehner/create_meta_templates close #15 close #17
Feat: added meta template and create command
2 parents e9770ed + ad579f1 commit c3fb08d

File tree

3 files changed

+125
-4
lines changed

3 files changed

+125
-4
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ Pathtraits can be either of type string, int, or real.
1414
# install
1515
python -m pip install pathtraits
1616

17+
# optionally add dust to calculate size and number of files per meta.yml
18+
# https://github.com/bootandy/dust
19+
1720
# create some test data
18-
echo "test" > foo.txt
19-
echo "test: true" > foo.txt.yml
21+
pathtraits create .
2022

2123
# create database
2224
pathtraits batch .
@@ -29,7 +31,7 @@ pathtraits get foo.txt
2931

3032
### Database path
3133

32-
The databse is being created in your home directory by default at "~/.pathtraits.db".
34+
The database is being created in your home directory by default at "~/.pathtraits.db".
3335
One can change this by setting the environmental variable `PATHTRAITS_DB_PATH` to any other path with write permssions if needed.
3436

3537
## Developing

pathtraits/cli.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import os
77
import click
8-
from pathtraits import scan, access
8+
from pathtraits import scan, access, create as _create
99

1010
logger = logging.getLogger(__name__)
1111

@@ -85,5 +85,24 @@ def query(query_str, db_path):
8585
access.query(query_str, db_path)
8686

8787

88+
@main.command()
89+
@click.argument("path", required=True, type=click.Path(exists=True, file_okay=False, dir_okay=True))
90+
@click.option(
91+
"--needed-until", "-nu",
92+
default=None,
93+
help="Optional account termination date (YYYY-MM-DD)",
94+
)
95+
@click.option(
96+
"--overwrite", "-o",
97+
is_flag=True, default=False,
98+
help="Overwrite existing metadata.",
99+
)
100+
@click.option("-v", "--verbose", is_flag=True, default=False)
101+
def create(path, needed_until, overwrite, verbose):
102+
"""
103+
Update database once, searches for all directories recursively.
104+
"""
105+
_create.generate_metadata(path, needed_until, overwrite, verbose)
106+
88107
if __name__ == "__main__":
89108
main()

pathtraits/create.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
Module for creating and populating yaml files
3+
"""
4+
5+
import logging
6+
import os
7+
import platform
8+
import pathlib
9+
import subprocess
10+
from datetime import datetime
11+
from importlib.metadata import version
12+
if platform.system() != "Windows":
13+
import grp
14+
import pwd
15+
else:
16+
import getpass
17+
18+
19+
logger = logging.getLogger(__name__)
20+
21+
def get_dust_metrics(path):
22+
"""Calls dust to get size and file count."""
23+
# Check if dust is installed in the current environment
24+
if subprocess.run(["which", "dust"], capture_output=True, check=False).returncode != 0:
25+
return "N/A (dust not found)", "N/A"
26+
27+
size_cmd = ["dust", "-s", "-c", "-b", path]
28+
inode_cmd = ["dust", "-s", "-c", "-b", "-i", path]
29+
size_out = subprocess.check_output(size_cmd, text=True).split()[0]
30+
inode_out = subprocess.check_output(inode_cmd, text=True).split()[0]
31+
return size_out, inode_out
32+
33+
34+
def get_folder_context(path):
35+
"""Retrieves owner, group, leader, and folder creation/change time."""
36+
path_obj = pathlib.Path(path)
37+
stat_info = path_obj.stat()
38+
39+
# Folder Creation Time
40+
try:
41+
created_ts = stat_info.st_birthtime
42+
except AttributeError:
43+
created_ts = stat_info.st_ctime
44+
folder_origin = datetime.fromtimestamp(created_ts).strftime('%Y-%m-%d')
45+
46+
if platform.system() != "Windows":
47+
owner = pwd.getpwuid(stat_info.st_uid).pw_name
48+
process_owner = pwd.getpwuid(os.getuid()).pw_name
49+
group = grp.getgrgid(stat_info.st_gid).gr_name
50+
leader = "Manual Entry Required"
51+
else:
52+
owner = getpass.getuser()
53+
process_owner = owner
54+
group = os.environ.get("USERDOMAIN", "LocalGroup")
55+
leader = "N/A"
56+
57+
return owner, process_owner, group, leader, folder_origin
58+
59+
60+
def generate_metadata(path, needed_until:str=None, overwrite:bool = False, verbose: bool = False):
61+
"""
62+
Generates meta.yml.
63+
64+
:param needed_until: Until when this folder is expected to be needed,
65+
can be a string in format '%Y-%m-%d' (e.g., "2026-12-31" ) or None.
66+
:param overwrite: Overwrite if metadata already exists? Default False
67+
:param verbose: enable verbose logging
68+
"""
69+
if verbose:
70+
logging.basicConfig(level=logging.DEBUG)
71+
file_path = os.path.join(path, "meta.yml")
72+
if os.path.exists(file_path) and not overwrite:
73+
logger.debug("Skipping: '%s' already exists.", file_path)
74+
logger.debug("Use 'overwrite=True' if you want to replace it.")
75+
return
76+
77+
owner, process_owner, group, leader, folder_created = get_folder_context(path)
78+
size, inodes = get_dust_metrics(path)
79+
80+
template = f"""# identification:
81+
pathtraits_version: {version("pathtraits")}
82+
yml_created_date: {datetime.now().strftime('%Y-%m-%d')}
83+
yml_created_by: "{process_owner}"
84+
folder_created_date: {folder_created}
85+
folder_owner: "{owner}"
86+
folder_owner_group: "{group}"
87+
folder_owner_group_lead: “{leader}"
88+
folder_no.files: {inodes}
89+
folder_size: {size}
90+
folder_needed_until: "{needed_until}"
91+
92+
# --- OPTIONAL FIELDS ---
93+
# project: "..."
94+
# tags: []
95+
"""
96+
97+
with open(file_path, "w", encoding="utf-8") as f:
98+
f.write(template)
99+
100+
logger.debug("Successfully generated template at: %s", file_path)

0 commit comments

Comments
 (0)