Commit 19b7893f authored by Trevor Cappallo's avatar Trevor Cappallo
Browse files

add shell args

parent 85499efb
......@@ -7,36 +7,46 @@ import os
import logging
import re
import OperationalLogger as OpLog
import OperationalMail as OpMail
OpLog.setup()
log = logging.getLogger(__name__)
log = None
class FileCheck(object):
class OperationalCheck(object):
"""Check files against specifications."""
PARAMS = ('size', 'files', 'count')
SUFFIX = {None: 1, 'k': 10**3, 'm': 10**6, 'g': 10**9, 'K': 10**3, 'M': 10**6, 'G': 10**9}
OPER = {
'=': lambda b: lambda a: a == b,
'=': lambda b: lambda a: a == b,
'==': lambda b: lambda a: a == b,
'!=': lambda b: lambda a: a != b,
'>=': lambda b: lambda a: a >= b,
'<=': lambda b: lambda a: a <= b,
'>': lambda b: lambda a: a > b,
'<': lambda b: lambda a: a < b,
'has': lambda b: lambda a: b in a,
'>': lambda b: lambda a: a > b,
'<': lambda b: lambda a: a < b,
}
def __init__(self, filename, path=os.getcwd()):
def __init__(self, filename=None, path=None, err_on_fail=True, verbose=False):
OpLog.setup(console_level=(logging.DEBUG if verbose else None))
global log
log = logging.getLogger(__name__)
if path is None:
path = os.getcwd()
if filename is None:
filename = os.path.join(path, "opcheck.txt")
if not self.load_rules(filename):
raise RuntimeError("no rules detected")
if self.check_rules(path):
log.error("failed to meet all file rules")
failed = self.check_rules(path)
if failed:
if err_on_fail:
log.error("failed to meet %d of %d rules:\n - %s", len(failed), len(self.rules), '\n - '.join(failed))
else:
log.warn("failed to meet %d of %d rules:\n - %s", len(failed), len(self.rules), '\n - '.join(failed))
else:
log.info("all file rules passed")
log.info("all %d rules passed", len(self.rules))
def load_rules(self, filename):
try:
......@@ -58,19 +68,33 @@ class FileCheck(object):
pattern = entry.split(' ')[0]
matched = False
for param in self.PARAMS:
match = re.search(r"\s%s\s?(%s)\s?(\d+)([kmg])?\s*(.+)?" % (param, '|'.join(self.OPER.keys())), entry, re.IGNORECASE)
match = re.search(r"\b((\d+)([kmg])?\s*({ops}))?\s*{param}\s*({ops})\s*(\d+)([kmg])?(\s*(.+))?".format(
param=param, ops='|'.join(self.OPER.keys())), entry, re.IGNORECASE)
if match:
matched = True
op = match.group(1)
num = int(match.group(2)) * self.SUFFIX[match.group(3)]
num_str = match.group(2) + (match.group(3).lower() if match.group(3) else '')
text = match.group(4)
op = match.group(5)
num = int(match.group(6)) * self.SUFFIX[match.group(7)]
num_str = match.group(6) + (match.group(7).lower() if match.group(7) else '')
text = match.group(9)
self.rules += [Rule(
pattern=pattern, param=param, num=num, text=text,
pattern=pattern, param=param.lower(), num=num, text=text,
cond_func=self.OPER[op](num),
cond_str="%s %s" % (op, num_str),
raw_str=entry,
)]
op = match.group(4)
if op:
if op[0] in '<>':
op = '>' if op[0] == '<' else '<' + op[1:]
num = int(match.group(2)) * self.SUFFIX[match.group(3)]
num_str = match.group(2) + (match.group(3).lower() if match.group(3) else '')
self.rules += [Rule(
pattern=pattern, param=param.lower(), num=num, text=text,
cond_func=self.OPER[op](num),
cond_str="%s %s" % (op, num_str),
raw_str=entry,
)]
# if no explicit conditions, assume 1 or more matching files
if not matched:
......@@ -115,11 +139,11 @@ class FileCheck(object):
log.info("'%s' FAILED count condition: %d %s '%s'", f, count, rule.cond_str, rule.text)
rule.okay = False
return not all([x.okay for x in self.rules])
return [x.raw_str for x in self.rules if not x.okay]
class Rule(object):
def __init__(self, pattern, param='files', num=0, text=None,
cond_func=FileCheck.OPER['>'](0), cond_str="> 0", raw_str=''):
cond_func=OperationalCheck.OPER['>'](0), cond_str="> 0", raw_str=''):
self.pattern = pattern
self.param = param
self.num = num
......@@ -131,4 +155,18 @@ class Rule(object):
self.okay = None
if __name__ == '__main__':
FileCheck('test.fc')
import argparse
parser = argparse.ArgumentParser(description="Check file structure against a rule list.")
parser.add_argument("path", help="base path to check rules against")
parser.add_argument("-f", "--filename", help="text file containing rule list (default: path/opcheck.txt)")
parser.add_argument("-w", "--warn", action='store_false', help="if set, will log warning instead of error any rules failed")
parser.add_argument("-v", "--verbose", action='count', help="verbose console output; shows all rules")
options = parser.parse_args()
OperationalCheck(
path=options.path,
filename=options.filename,
err_on_fail=options.warn,
verbose=options.verbose,
)
# test filecheck manifest file
# one rule/file-pattern per line
# by default, a check throws an error at the end iff any tests fail; can be toggled to a warning
# format: globpattern [exp 1] ... [exp N],
# where an expression is: param comparator value [regex]
# params:
# 'files' returns number of files/directories matching glob
# 'size' checks filesize of every file matching glob
# 'count' returns number of occurrences of 'regex' for every file matching glob
# (regex text is parsed as all text following count expression)
# comparators: <=, <, >=, >, ==, !=, = (same as ==)
# value: non-negative integer to check against
# regex: python-style regular expression, only used with 'count'
# hashmarks designate explicit comments, but:
./opcheck.* for the most part... files = 1 ...unrecognized text is ignored # this will still parse the files = 1
# if no expressions are given, rule defaults to checking for one or more file/dir matches for glob
README # succeeds if README in root dir, else fails; most whitespace ignored
opcheck.t?t size <= 10k # ? and * allowed in globs;
# k, m, and g may be used after value as shorthand
lib/*.py sIzE != 123M FiLeS>2 # params are case insensitive; glob and regex are not
lib/*estC*py files >= 2 0 <= size < 1GB # <---- ranges can be formed like so
lib/OperationalCheck.py count == 2 class # uses 'class' as regex
lib/OperationalCheck.py 0<files=1, size>=1, count<=1k def \S+\(self.*\): # python-style regex (does not span multilines)
lib/OperationalCheck.py 5 >= count > 0 rule.cond_func( # flawed regexes treated as plaintext
* <--- only fails on empty directory
......@@ -15,7 +15,7 @@ TESTS_REQUIRE = [
setup(
name='OperationalLogging',
version='0.5.3',
version='0.6.0',
description='Logging and email notification for operational projects',
author='Verisk Climate',
author_email='tcappallo@veriskclimate.com',
......@@ -25,5 +25,5 @@ setup(
package_dir={'': 'lib'},
#package_dir={'OperationalLogging': 'lib'},
#packages=['OperationalLogging'] #, 'OperationalLogging.Tests'],
py_modules=['OperationalLogger', 'OperationalMail', 'TestAll'],
py_modules=['OperationalLogger', 'OperationalMail', 'OperationalCheck', 'TestAll'],
)
# test filecheck manifest file
README # hash designates comment
## whitespace ignored
test.fc size < 10
test.f? size <= 10k
./test.* files = 1
lib/*.py sIzE== 1290 FiLeS>2
lib/*est*py files >2 size < 1GB
lib/FileCheck.py count=2 class
lib/FileCheck.py count<=1k def \S+\(self.*\):
lib/FileCheck.py count>0 rule.cond_func(
* # if no other parameters, rule passes if at least one file/dir matches
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment