~gpanders/pushbroom

f36b82a79e05427ad05cf34bdf041ea730f6ab79 — Greg Anders 1 year, 5 months ago bb33aba
Start conversion of janitor into a python package
8 files changed, 263 insertions(+), 41 deletions(-)

M .gitignore
A MANIFEST.in
D bin/janitor
A janitor.conf
A setup.py
A src/janitor/__init__.py
A src/janitor/console.py
A src/janitor/sweep.py
M .gitignore => .gitignore +129 -1
@@ 1,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

A MANIFEST.in => MANIFEST.in +4 -0
@@ 0,0 1,4 @@
global-exclude .gitignore .git
include setup.py
include README.md
include janitor.conf

D bin/janitor => bin/janitor +0 -40
@@ 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

A janitor.conf => janitor.conf +3 -0
@@ 0,0 1,3 @@
[~/Downloads]
Trash = ~/.Trash
NumDays = 30

A setup.py => setup.py +24 -0
@@ 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",
    ],
)

A src/janitor/__init__.py => src/janitor/__init__.py +1 -0
@@ 0,0 1,1 @@
from .sweep import sweep

A src/janitor/console.py => src/janitor/console.py +62 -0
@@ 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)

A src/janitor/sweep.py => src/janitor/sweep.py +40 -0
@@ 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)