190 lines
6.3 KiB
Python
190 lines
6.3 KiB
Python
|
# coding: utf-8
|
||
|
#
|
||
|
# Postfix queue control python tool (pymailq)
|
||
|
#
|
||
|
# Copyright (C) 2014 Denis Pompilio (jawa) <denis.pompilio@gmail.com>
|
||
|
#
|
||
|
# This file is part of pymailq
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or
|
||
|
# modify it under the terms of the GNU General Public License
|
||
|
# as published by the Free Software Foundation; either version 2
|
||
|
# of the License, or (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
import re
|
||
|
from functools import wraps
|
||
|
from collections import Counter
|
||
|
|
||
|
|
||
|
FORMAT_PARSER = re.compile(r'\{[^{}]+\}')
|
||
|
FORMATS = {
|
||
|
'brief': "{date} {qid} [{status}] {sender} ({size}B)",
|
||
|
'long': ("{date} {qid} [{status}] {sender} ({size}B)\n"
|
||
|
" Rcpt: {recipients}\n"
|
||
|
" Err: {errors}")
|
||
|
}
|
||
|
|
||
|
|
||
|
def viewer(function):
|
||
|
"""Result viewer decorator
|
||
|
|
||
|
:param func function: Function to decorate
|
||
|
"""
|
||
|
def wrapper(*args, **kwargs):
|
||
|
args = list(args) # conversion need for arguments cleaning
|
||
|
limit = None
|
||
|
overhead = 0
|
||
|
try:
|
||
|
if "limit" in args:
|
||
|
limit_idx = args.index('limit')
|
||
|
args.pop(limit_idx) # pop option, next arg is value
|
||
|
limit = int(args.pop(limit_idx))
|
||
|
except (IndexError, TypeError, ValueError):
|
||
|
raise SyntaxError("limit modifier needs a valid number")
|
||
|
|
||
|
output = "brief"
|
||
|
for known in FORMATS:
|
||
|
if known in args:
|
||
|
output = args.pop(args.index(known))
|
||
|
break
|
||
|
out_format = FORMATS[output]
|
||
|
|
||
|
elements = function(*args, **kwargs)
|
||
|
|
||
|
total_elements = len(elements)
|
||
|
if not total_elements:
|
||
|
return ["No element to display"]
|
||
|
|
||
|
# Check for headers and increase limit accordingly
|
||
|
headers = 0
|
||
|
if total_elements > 1 and "========" in str(elements[1]):
|
||
|
headers = 2
|
||
|
|
||
|
if limit is not None:
|
||
|
if total_elements > (limit + headers):
|
||
|
overhead = total_elements - (limit + headers)
|
||
|
else:
|
||
|
limit = total_elements
|
||
|
else:
|
||
|
limit = total_elements
|
||
|
|
||
|
out_format_attrs = FORMAT_PARSER.findall(out_format)
|
||
|
formatted = []
|
||
|
for element in elements[:limit + headers]:
|
||
|
# if attr qid exists, assume this is a mail
|
||
|
if hasattr(element, "qid"):
|
||
|
attrs = {}
|
||
|
for att in out_format_attrs:
|
||
|
if att == "{recipients}":
|
||
|
rcpts = getattr(element, att[1:-1], ["-"])
|
||
|
attrs[att[1:-1]] = ", ".join(rcpts)
|
||
|
elif att == "{errors}":
|
||
|
errors = getattr(element, att[1:-1], ["-"])
|
||
|
attrs[att[1:-1]] = "\n".join(errors)
|
||
|
else:
|
||
|
attrs[att[1:-1]] = getattr(element, att[1:-1], "-")
|
||
|
formatted.append(out_format.format(**attrs))
|
||
|
else:
|
||
|
formatted.append(element)
|
||
|
|
||
|
if overhead > 0:
|
||
|
msg = "...Preview of first %d (%d more)..." % (limit, overhead)
|
||
|
formatted.append(msg)
|
||
|
|
||
|
return formatted
|
||
|
wrapper.__doc__ = function.__doc__
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def sorter(function):
|
||
|
"""Result sorter decorator.
|
||
|
|
||
|
This decorator inspect decorated function arguments and search for
|
||
|
known keyword to sort decorated function result.
|
||
|
"""
|
||
|
@wraps(function)
|
||
|
def wrapper(*args, **kwargs):
|
||
|
args = list(args) # conversion need for arguments cleaning
|
||
|
sortkey = "date" # default sort by date
|
||
|
reverse = True # default sorting is desc
|
||
|
if "sortby" in args:
|
||
|
sortby_idx = args.index('sortby')
|
||
|
args.pop(sortby_idx) # pop option, next arg is value
|
||
|
|
||
|
try:
|
||
|
sortkey = args.pop(sortby_idx)
|
||
|
except IndexError:
|
||
|
raise SyntaxError("sortby requires a field")
|
||
|
|
||
|
# third param may be asc or desc, ignore unknown values
|
||
|
try:
|
||
|
if "asc" == args[sortby_idx]:
|
||
|
args.pop(sortby_idx)
|
||
|
reverse = False
|
||
|
elif "desc" == args[sortby_idx]:
|
||
|
args.pop(sortby_idx)
|
||
|
except IndexError:
|
||
|
pass
|
||
|
|
||
|
elements = function(*args, **kwargs)
|
||
|
|
||
|
try:
|
||
|
sorted_elements = sorted(elements,
|
||
|
key=lambda x: getattr(x, sortkey),
|
||
|
reverse=reverse)
|
||
|
except AttributeError:
|
||
|
msg = "elements cannot be sorted by %s" % sortkey
|
||
|
raise SyntaxError(msg)
|
||
|
|
||
|
return sorted_elements
|
||
|
wrapper.__doc__ = function.__doc__
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def ranker(function):
|
||
|
"""Result ranker decorator
|
||
|
"""
|
||
|
@wraps(function)
|
||
|
def wrapper(*args, **kwargs):
|
||
|
args = list(args) # conversion need for arguments cleaning
|
||
|
rankkey = None
|
||
|
if "rankby" in args:
|
||
|
rankby_idx = args.index('rankby')
|
||
|
args.pop(rankby_idx) # pop option, next arg is value
|
||
|
|
||
|
try:
|
||
|
rankkey = args.pop(rankby_idx)
|
||
|
except IndexError:
|
||
|
raise SyntaxError("rankby requires a field")
|
||
|
|
||
|
elements = function(*args, **kwargs)
|
||
|
|
||
|
if rankkey is not None:
|
||
|
try:
|
||
|
rank = Counter()
|
||
|
for element in elements:
|
||
|
rank[getattr(element, rankkey)] += 1
|
||
|
|
||
|
# XXX: headers are taken in elements display limit :(
|
||
|
ranked_elements = ['%-40s count' % rankkey, '='*48]
|
||
|
for entry in rank.most_common():
|
||
|
key, value = entry
|
||
|
ranked_elements.append('%-40s %s' % (key, value))
|
||
|
return ranked_elements
|
||
|
|
||
|
except AttributeError:
|
||
|
msg = "elements cannot be ranked by %s" % rankkey
|
||
|
raise SyntaxError(msg)
|
||
|
|
||
|
return elements
|
||
|
wrapper.__doc__ = function.__doc__
|
||
|
return wrapper
|