1
0
mirror of https://github.com/mikf/gallery-dl.git synced 2024-11-22 10:42:34 +01:00

add custom string formatter class

This commit is contained in:
Mike Fährmann 2017-09-27 21:18:34 +02:00
parent 85a2b2ae59
commit c1f0afe4c6
No known key found for this signature in database
GPG Key ID: 5680CA389D365A88
2 changed files with 136 additions and 2 deletions

View File

@ -15,6 +15,7 @@ import time
import base64
import random
import string
import _string
import hashlib
import urllib.parse
from . import text, exception
@ -218,6 +219,90 @@ class ChainPredicate():
return True
class Formatter():
"""Custom, trimmed-down version of string.Formatter
This string formatter implementation is a mostly performance-optimized
variant of the original string.Formatter class. Unnecessary features have
been removed (positional arguments, unused argument check) and new
formatting options have been added.
Extra Conversions:
- "l": calls str.lower on the target value
- "u": calls str.upper
- "c": calls str.capitalize
- "C": calls string.capwords
- Example: {f!l} -> "example"; {f!u} -> "EXAMPLE"
Extra Format Specifiers:
- "?<before>/<after>/":
Adds <before> and <after> to the actual value if it evaluates to True.
Otherwise the whole replacement field beomes an empty string.
Example: {f:?-+/+-/} -> "-+Example+-" (if "f" contains "Example")
-> "" (if "f" is None, 0, "")
"""
conversions = {
"l": str.lower,
"u": str.upper,
"c": str.capitalize,
"C": string.capwords,
"s": str,
"r": repr,
"a": ascii,
}
def __init__(self, format_string):
self.formatter_rules = tuple(_string.formatter_parser(format_string))
def format_map(self, kwargs):
"""Apply 'kwargs' to the initial format_string and return its result"""
result = []
append = result.append
for literal_text, field_name, format_spec, conversion in \
self.formatter_rules:
if literal_text:
append(literal_text)
if field_name:
obj = self.get_field(field_name, kwargs)
if conversion:
obj = self.conversions[conversion](obj)
if format_spec:
format_spec = format_spec.format_map(kwargs)
obj = self.format_field(obj, format_spec)
else:
obj = str(obj)
append(obj)
return "".join(result)
@staticmethod
def format_field(value, format_spec):
"""Format 'value' according to 'format_spec'"""
if format_spec[0] == "?":
if not value:
return ""
before, after, format_spec = format_spec.split("/", 2)
return before[1:] + format(value, format_spec) + after
return format(value, format_spec)
@staticmethod
def get_field(field_name, kwargs):
"""Return value called 'field_name' from 'kwargs'"""
first, rest = _string.formatter_field_name_split(field_name)
obj = kwargs[first]
for is_attr, i in rest:
if is_attr:
obj = getattr(obj, i)
else:
obj = obj[i]
return obj
class PathFormat():
def __init__(self, extractor):
@ -225,6 +310,7 @@ class PathFormat():
"filename", extractor.filename_fmt)
self.directory_fmt = extractor.config(
"directory", extractor.directory_fmt)
self.filename_formatter = Formatter(self.filename_fmt)
self.has_extension = False
self.keywords = {}
self.directory = self.realdirectory = ""
@ -257,7 +343,8 @@ class PathFormat():
"""Build directory path and create it if necessary"""
try:
segments = [
text.clean_path(segment.format_map(keywords).strip())
text.clean_path(
Formatter(segment).format_map(keywords).strip())
for segment in self.directory_fmt
]
except Exception as exc:
@ -287,7 +374,7 @@ class PathFormat():
"""Use filename-keywords and directory to build a full path"""
try:
filename = text.clean_path(
self.filename_fmt.format_map(self.keywords))
self.filename_formatter.format_map(self.keywords))
except Exception as exc:
raise exception.FormatError(exc, "filename")

View File

@ -134,6 +134,53 @@ class TestISO639_1(unittest.TestCase):
self.assertEqual(func(*args), result)
class TestFormatter(unittest.TestCase):
kwdict = {
"a": "hElLo wOrLd",
"b": "äöü",
"name": "Name",
"title1": "Title",
"title2": "",
"title3": None,
"title4": 0,
}
def test_conversions(self):
self._run_test("{a!l}", "hello world")
self._run_test("{a!u}", "HELLO WORLD")
self._run_test("{a!c}", "Hello world")
self._run_test("{a!C}", "Hello World")
self._run_test("{a!s}", self.kwdict["a"])
self._run_test("{a!r}", "'" + self.kwdict["a"] + "'")
self._run_test("{a!a}", "'" + self.kwdict["a"] + "'")
self._run_test("{b!a}", "'\\xe4\\xf6\\xfc'")
with self.assertRaises(KeyError):
self._run_test("{a!q}", "hello world")
def test_optional(self):
self._run_test("{name}{title1}", "NameTitle")
self._run_test("{name}{title1:?//}", "NameTitle")
self._run_test("{name}{title1:? **/''/}", "Name **Title''")
self._run_test("{name}{title2}", "Name")
self._run_test("{name}{title2:?//}", "Name")
self._run_test("{name}{title2:? **/''/}", "Name")
self._run_test("{name}{title3}", "NameNone")
self._run_test("{name}{title3:?//}", "Name")
self._run_test("{name}{title3:? **/''/}", "Name")
self._run_test("{name}{title4}", "Name0")
self._run_test("{name}{title4:?//}", "Name")
self._run_test("{name}{title4:? **/''/}", "Name")
def _run_test(self, format_string, result):
formatter = util.Formatter(format_string)
output = formatter.format_map(self.kwdict)
self.assertEqual(output, result, format_string)
class TestOther(unittest.TestCase):
def test_bdecode(self):