|
@@ -17,6 +17,7 @@ import io
|
|
|
import json
|
|
|
import locale
|
|
|
import math
|
|
|
+import operator
|
|
|
import os
|
|
|
import pipes
|
|
|
import platform
|
|
@@ -1678,3 +1679,79 @@ def render_table(header_row, data):
|
|
|
max_lens = [max(len(compat_str(v)) for v in col) for col in zip(*table)]
|
|
|
format_str = ' '.join('%-' + compat_str(ml + 1) + 's' for ml in max_lens[:-1]) + '%s'
|
|
|
return '\n'.join(format_str % tuple(row) for row in table)
|
|
|
+
|
|
|
+
|
|
|
+def _match_one(filter_part, dct):
|
|
|
+ COMPARISON_OPERATORS = {
|
|
|
+ '<': operator.lt,
|
|
|
+ '<=': operator.le,
|
|
|
+ '>': operator.gt,
|
|
|
+ '>=': operator.ge,
|
|
|
+ '=': operator.eq,
|
|
|
+ '!=': operator.ne,
|
|
|
+ }
|
|
|
+ operator_rex = re.compile(r'''(?x)\s*
|
|
|
+ (?P<key>[a-z_]+)
|
|
|
+ \s*(?P<op>%s)(?P<none_inclusive>\s*\?)?\s*
|
|
|
+ (?:
|
|
|
+ (?P<intval>[0-9.]+(?:[kKmMgGtTpPeEzZyY]i?[Bb]?)?)|
|
|
|
+ (?P<strval>(?![0-9.])[a-z0-9A-Z]*)
|
|
|
+ )
|
|
|
+ \s*$
|
|
|
+ ''' % '|'.join(map(re.escape, COMPARISON_OPERATORS.keys())))
|
|
|
+ m = operator_rex.search(filter_part)
|
|
|
+ if m:
|
|
|
+ op = COMPARISON_OPERATORS[m.group('op')]
|
|
|
+ if m.group('strval') is not None:
|
|
|
+ if m.group('op') not in ('=', '!='):
|
|
|
+ raise ValueError(
|
|
|
+ 'Operator %s does not support string values!' % m.group('op'))
|
|
|
+ comparison_value = m.group('strval')
|
|
|
+ else:
|
|
|
+ try:
|
|
|
+ comparison_value = int(m.group('intval'))
|
|
|
+ except ValueError:
|
|
|
+ comparison_value = parse_filesize(m.group('intval'))
|
|
|
+ if comparison_value is None:
|
|
|
+ comparison_value = parse_filesize(m.group('intval') + 'B')
|
|
|
+ if comparison_value is None:
|
|
|
+ raise ValueError(
|
|
|
+ 'Invalid integer value %r in filter part %r' % (
|
|
|
+ m.group('intval'), filter_part))
|
|
|
+ actual_value = dct.get(m.group('key'))
|
|
|
+ if actual_value is None:
|
|
|
+ return m.group('none_inclusive')
|
|
|
+ return op(actual_value, comparison_value)
|
|
|
+
|
|
|
+ UNARY_OPERATORS = {
|
|
|
+ '': lambda v: v is not None,
|
|
|
+ '!': lambda v: v is None,
|
|
|
+ }
|
|
|
+ operator_rex = re.compile(r'''(?x)\s*
|
|
|
+ (?P<op>%s)\s*(?P<key>[a-z_]+)
|
|
|
+ \s*$
|
|
|
+ ''' % '|'.join(map(re.escape, UNARY_OPERATORS.keys())))
|
|
|
+ m = operator_rex.search(filter_part)
|
|
|
+ if m:
|
|
|
+ op = UNARY_OPERATORS[m.group('op')]
|
|
|
+ actual_value = dct.get(m.group('key'))
|
|
|
+ return op(actual_value)
|
|
|
+
|
|
|
+ raise ValueError('Invalid filter part %r' % filter_part)
|
|
|
+
|
|
|
+
|
|
|
+def match_str(filter_str, dct):
|
|
|
+ """ Filter a dictionary with a simple string syntax. Returns True (=passes filter) or false """
|
|
|
+
|
|
|
+ return all(
|
|
|
+ _match_one(filter_part, dct) for filter_part in filter_str.split('&'))
|
|
|
+
|
|
|
+
|
|
|
+def match_filter_func(filter_str):
|
|
|
+ def _match_func(info_dict):
|
|
|
+ if match_str(filter_str, info_dict):
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ video_title = info_dict.get('title', info_dict.get('id', 'video'))
|
|
|
+ return '%s does not pass filter %s, skipping ..' % (video_title, filter_str)
|
|
|
+ return _match_func
|