From f36b82a79e05427ad05cf34bdf041ea730f6ab79 Mon Sep 17 00:00:00 2001 From: Greg Anders Date: Mon, 17 Jun 2019 18:22:40 -0600 Subject: [PATCH] Start conversion of janitor into a python package --- .gitignore | 130 +++++++++++++++++++++++++++++++++++++++- MANIFEST.in | 4 ++ bin/janitor | 40 ------------- janitor.conf | 3 + setup.py | 24 ++++++++ src/janitor/__init__.py | 1 + src/janitor/console.py | 62 +++++++++++++++++++ src/janitor/sweep.py | 40 +++++++++++++ 8 files changed, 263 insertions(+), 41 deletions(-) create mode 100644 MANIFEST.in delete mode 100755 bin/janitor create mode 100644 janitor.conf create mode 100644 setup.py create mode 100644 src/janitor/__init__.py create mode 100644 src/janitor/console.py create mode 100644 src/janitor/sweep.py diff --git a/.gitignore b/.gitignore index 333c1e9..cdf1bfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,129 @@ -logs/ +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..799cd7b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +global-exclude .gitignore .git +include setup.py +include README.md +include janitor.conf diff --git a/bin/janitor b/bin/janitor deleted file mode 100755 index 9dc597b..0000000 --- a/bin/janitor +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -# Define which folder to clean out (default: ~/Downloads) -export TARGET_DIR="$1" - -# How many days should a file be able to live in the folder? (default: 30) -export DAYS_TO_KEEP="$2" - -# Trash folder to move deleted files to (default: ~/.Trash) -export TRASH_DIR="$3" - -# Location of Janitor installation -export JANITOR_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd)" - -if [ -z "$TARGET_DIR" -o -z "$DAYS_TO_KEEP" ]; then - echo "Minimum 2 arguments required" - exit 1 -fi - -function sweep() { - if [ $# -eq 0 ]; then - exit 1 - fi - - if [ ! -z "$TRASH_DIR" ]; then - rsync -a "$1" "$TRASH_DIR" 2>/dev/null - fi - rm -rf "$1" 2>/dev/null - - LOG="$JANITOR_DIR/logs/janitor_$(date +"%Y%m%d").log" - - if [ ! -e "$LOG" ]; then - echo "Files removed from $TARGET_DIR on $(date +%m-%d-%Y):" > "$LOG" - fi - echo "${1##*/}" >> "$LOG" -} - -export -f sweep - -find "$TARGET_DIR" -mindepth 1 -maxdepth 1 -mtime +$DAYS_TO_KEEP -exec bash -c 'sweep "$0"' {} \; 2>/dev/null diff --git a/janitor.conf b/janitor.conf new file mode 100644 index 0000000..fc8b225 --- /dev/null +++ b/janitor.conf @@ -0,0 +1,3 @@ +[~/Downloads] +Trash = ~/.Trash +NumDays = 30 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c0e009b --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +from setuptools import setup, find_packages + +setup( + name="janitor", + version="1.0.0", + author="Greg Anders", + author_email="greg@gpanders.com", + description="Clean up filesystem paths", + long_description=open("README.md", "r").read(), + long_description_content_type="text/markdown", + url="https://github.com/gpanders/janitor", + package_dir={"": "src"}, + packages=find_packages("src"), + entry_points={ + "console_scripts": [ + "janitor = janitor.console:run", + ], + }, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +) diff --git a/src/janitor/__init__.py b/src/janitor/__init__.py new file mode 100644 index 0000000..2a67fde --- /dev/null +++ b/src/janitor/__init__.py @@ -0,0 +1 @@ +from .sweep import sweep diff --git a/src/janitor/console.py b/src/janitor/console.py new file mode 100644 index 0000000..433c9d7 --- /dev/null +++ b/src/janitor/console.py @@ -0,0 +1,62 @@ +import argparse +import configparser +import fnmatch +import logging +import os +import sys + +import janitor + +LOG_FILE = os.path.expanduser("~/.cache/janitor/janitor.log") +os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", + filename=LOG_FILE, +) + + +def run(): + """Run janitor""" + parser = argparse.ArgumentParser(description="Clean up your filesystem.") + parser.add_argument( + "-c", + "--config", + default=os.path.expanduser("~/.config/janitor/config"), + type=str, + help="path to config file", + ) + parser.add_argument( + "-n", + "--dry-run", + action="store_true", + help="show what would be done without actually doing anything", + ) + + args = parser.parse_args() + + config = configparser.ConfigParser() + try: + with open(args.config, "r") as f: + config.read_file(f) + except FileNotFoundError: + print("No configuration file found.") + sys.exit(1) + + for path in config.sections(): + fullpath = os.path.abspath(os.path.expanduser(path)) + if not os.path.isdir(fullpath): + logging.error("No such directory: %s", fullpath) + else: + num_days = config.getint(path, "numdays") + trash = config.get(path, "trash", fallback=None) + ignore = config.get(path, "ignore", fallback="").split(",") + ignored = r"|".join([fnmatch.translate(x) for x in ignore]) + + if trash: + trash = os.path.abspath(os.path.expanduser(trash)) + if not os.path.isdir(trash): + logging.error("No such directory %s", trash) + + janitor.sweep(fullpath, num_days, ignored, trash, args.dry_run) diff --git a/src/janitor/sweep.py b/src/janitor/sweep.py new file mode 100644 index 0000000..d5ed988 --- /dev/null +++ b/src/janitor/sweep.py @@ -0,0 +1,40 @@ +import logging +import os +import re +import time + +SECONDS_PER_DAY = 24 * 60 * 60 + + +def sweep(path, num_days, ignored, trash=None, dry_run=False): + """Remove old files from a directory + + :path: Path to remove files from + :num_days: Remove files older than this many days + :ignored: Regex pattern of paths to ignore + :trash: If set, move files to this directory instead of deleting them + + """ + now = time.time() + logging.info("Starting janitor") + num_seconds = num_days * SECONDS_PER_DAY + thresh = now - num_seconds + for root, dirs, files in os.walk(path): + dirs[:] = [d for d in dirs if not re.match(ignored, d)] + + files = [f for f in files if not re.match(ignored, f)] + for file in files: + fpath = os.path.join(root, file) + if os.stat(fpath).st_mtime < thresh: + if trash: + if dry_run: + print("Moving {} to {}".format(file, trash)) + else: + logging.info("Moving %s to %s", file, trash) + os.rename(fpath, os.path.join(trash, file)) + else: + if dry_run: + print("Deleting {}".format(file)) + else: + logging.info("Deleting %s", file) + os.remove(fpath) -- 2.26.2