Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nvn-nil committed Apr 24, 2021
0 parents commit 6e33799
Show file tree
Hide file tree
Showing 17 changed files with 699 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[flake8]
max-line-length = 88
ignore = E501, E203, W503
per-file-ignores =
__init__.py:F401
tests/console/commands/debug/test_resolve.py:W291
exclude =
.git
__pycache__
setup.py
build
dist
releases
.venv
.tox
.mypy_cache
.pytest_cache
.vscode
.github
poetry/utils/_compat.py
poetry/utils/env_scripts/tags.py
tests/fixtures/
tests/repositories/fixtures/
tests/utils/fixtures/
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
*.pyc

# Packages
*.egg
!/tests/**/*.egg
/*.egg-info
/dist/*
build
_build
.cache
*.so

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox
.pytest_cache

.DS_Store
.idea/*
.python-version
.vscode/*

/test.py
/test_*.*

.mypy_cache

.venv
/releases/*
pip-wheel-metadata
33 changes: 33 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
exclude: '.git|((?:[^/]*/)*)(.svg)|((?:[^/]*/)*)(.xml)'
default_stages: [commit]
fail_fast: true
default_language_version:
python: python3 # force all unspecified python hooks to run python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files

- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
args: ['--line-length', '120']

- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.1
hooks:
- id: flake8
args: ['--config=setup.cfg']
language_version: python3

- repo: https://github.com/timothycrosley/isort
rev: 5.0.9
hooks:
- id: isort
require_serial: true
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Naveen Anil, All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 4 additions & 0 deletions blackwatch/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .blackwatch import application


application.run()
17 changes: 17 additions & 0 deletions blackwatch/blackwatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import logging
from cleo import Application

from .commands import WatchCommand


logger = logging.getLogger(__name__)
console = logging.StreamHandler()
logger.addHandler(console)


class BlackWatchApplication(Application):
_version = "0.1.0"


application = BlackWatchApplication()
application.add(WatchCommand())
1 change: 1 addition & 0 deletions blackwatch/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .watch import WatchCommand # noqa:F401
59 changes: 59 additions & 0 deletions blackwatch/commands/watch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from cleo import Command

from ..handlers import FolderWatcher


class WatchCommand(Command):
"""
Watch a folder
watch
{folder : Which folder do you want to watch?}
{command? : What command to run?}
{--d|debug : Debug mode?}
{--k|kind=any : Whether to watch for folders or files? allowed: any,files,folders}
{--i|interval=2 : How long to wait after an event for another one?}
{--e|event=* : What file system even to watch for? allowed: any,create,delete,modified,moved}
"""

ALLOWED_KIND = ["any", "folders", "files"]
ALLOWED_EVENTS = ["any", "create", "delete", "modify", "move"]

def handle(self):
folder = self.argument("folder")
command = self.argument("command")

event = self.option("event")
kind = self.option("kind")
interval = self.option("interval")
debug = self.option("debug")

try:
interval = int(interval)
except Exception:
raise Exception("Interval cannot be converted to int, interval must be an integer")

if kind not in self.ALLOWED_KIND:
raise Exception("Kind must be one of 'any', 'folders' or 'files'. Default is 'any'")

if "any" in event or len(event) < 1:
event = ["any"]
elif not all(one_event in self.ALLOWED_EVENTS for one_event in event):
raise Exception("Invalid event, allowed events are", ",".join(self.ALLOWED_EVENTS))

if debug:
print("folder", folder)
print("command", command)
print("event", event)
print("kind", kind)
print("interval", interval)
else:
if command:
watcher = FolderWatcher(folder, command, interval=interval, kind=kind, event=event)
else:
watcher = FolderWatcher(folder, interval=interval, kind=kind, event=event)

self.line(
f"Watching folder: {folder} for {event} events in {kind} with interval {interval}s and running command: {command}"
)
watcher.run()
1 change: 1 addition & 0 deletions blackwatch/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .fsevents import FolderWatcher # noqa:F401
104 changes: 104 additions & 0 deletions blackwatch/handlers/fsevents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import logging
import shlex
import time
from datetime import datetime, timedelta
from subprocess import run
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer


logger = logging.getLogger()
console = logging.StreamHandler()
logger.addHandler(console)


class MyHandler(FileSystemEventHandler):
def __init__(self, folder, command_to_run, interval, kind, event):
self._folder = folder
self._command = command_to_run
self._interval = interval
self._kind = kind
self._last_modified = datetime.now()
self._event = event

def should_process_event(self, event):
process = False

if self._kind == "any":
process = True
elif event.is_directory and self._kind == "folders":
process = True
elif self._kind == "files" and not event.is_directory:
process = True

return process

def process_event(self, event):
if self.should_process_event(event):
if self._interval and (datetime.now() - self._last_modified) < timedelta(seconds=self._interval):
print("skip this event, interval:", self._interval)
return
else:
self._last_modified = datetime.now()
print(f"Event type: {event.event_type} path : {event.src_path}")
if self._command:
try:
run(shlex.split(self._command), shell=True, check=True)
except Exception:
raise

def on_created(self, event):
if "any" in self._event or "create" in self._event:
self.process_event(event)

def on_deleted(self, event):
if "any" in self._event or "delete" in self._event:
self.process_event(event)

def on_modified(self, event):
if "any" in self._event or "modify" in self._event:
self.process_event(event)

def on_moved(self, event):
if "any" in self._event or "move" in self._event:
self.process_event(event)


class FolderWatcher:
"""The class which abstracts the watching and handling of file system events
:param src_path: The folder to watch
:type src_path: string
:param command_to_run: the command to run when a file modification is detected
:type command_to_run: string
"""

DEFAULT_INTERVAL = 2

def __init__(self, src_path, command_to_run=None, interval=None, kind="any", event=["any"]):
self.__src_path = src_path
if isinstance(interval, (int)):
self._interval = interval
else:
self._interval = self.DEFAULT_INTERVAL
self.__event_handler = MyHandler(src_path, command_to_run, self._interval, kind, event)
self.__event_observer = Observer()

def run(self):
self.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
self.stop()

def start(self):
self.__schedule()
self.__event_observer.start()

def stop(self):
self.__event_observer.stop()
self.__event_observer.join()

def __schedule(self):
self.__event_observer.schedule(self.__event_handler, self.__src_path, recursive=True)
Loading

0 comments on commit 6e33799

Please sign in to comment.