summaryrefslogtreecommitdiffstats
path: root/gallery_dl/formatter.py
diff options
context:
space:
mode:
Diffstat (limited to 'gallery_dl/formatter.py')
-rw-r--r--gallery_dl/formatter.py184
1 files changed, 99 insertions, 85 deletions
diff --git a/gallery_dl/formatter.py b/gallery_dl/formatter.py
index 107c8ed..bc4d837 100644
--- a/gallery_dl/formatter.py
+++ b/gallery_dl/formatter.py
@@ -10,6 +10,7 @@
import os
import json
+import time
import string
import _string
import datetime
@@ -17,17 +18,9 @@ import operator
import functools
from . import text, util
-_CACHE = {}
-_CONVERSIONS = None
-_GLOBALS = {
- "_env": lambda: os.environ,
- "_lit": lambda: _literal,
- "_now": datetime.datetime.now,
-}
-
-def parse(format_string, default=None):
- key = format_string, default
+def parse(format_string, default=None, fmt=format):
+ key = format_string, default, fmt
try:
return _CACHE[key]
@@ -48,7 +41,7 @@ def parse(format_string, default=None):
elif kind == "F":
cls = FStringFormatter
- formatter = _CACHE[key] = cls(format_string, default)
+ formatter = _CACHE[key] = cls(format_string, default, fmt)
return formatter
@@ -95,8 +88,9 @@ class StringFormatter():
Example: {f:R /_/} -> "f_o_o_b_a_r" (if "f" is "f o o b a r")
"""
- def __init__(self, format_string, default=None):
+ def __init__(self, format_string, default=None, fmt=format):
self.default = default
+ self.format = fmt
self.result = []
self.fields = []
@@ -126,7 +120,7 @@ class StringFormatter():
return "".join(result)
def _field_access(self, field_name, format_spec, conversion):
- fmt = parse_format_spec(format_spec, conversion)
+ fmt = self._parse_format_spec(format_spec, conversion)
if "|" in field_name:
return self._apply_list([
@@ -184,27 +178,38 @@ class StringFormatter():
return fmt(obj)
return wrap
+ def _parse_format_spec(self, format_spec, conversion):
+ fmt = _build_format_func(format_spec, self.format)
+ if not conversion:
+ return fmt
+
+ conversion = _CONVERSIONS[conversion]
+ if fmt is self.format:
+ return conversion
+ else:
+ return lambda obj: fmt(conversion(obj))
+
class TemplateFormatter(StringFormatter):
"""Read format_string from file"""
- def __init__(self, path, default=None):
+ def __init__(self, path, default=None, fmt=format):
with open(util.expand_path(path)) as fp:
format_string = fp.read()
- StringFormatter.__init__(self, format_string, default)
+ StringFormatter.__init__(self, format_string, default, fmt)
class ExpressionFormatter():
"""Generate text by evaluating a Python expression"""
- def __init__(self, expression, default=None):
+ def __init__(self, expression, default=None, fmt=None):
self.format_map = util.compile_expression(expression)
class ModuleFormatter():
"""Generate text by calling an external function"""
- def __init__(self, function_spec, default=None):
+ def __init__(self, function_spec, default=None, fmt=None):
module_name, _, function_name = function_spec.partition(":")
module = __import__(module_name)
self.format_map = getattr(module, function_name)
@@ -213,7 +218,7 @@ class ModuleFormatter():
class FStringFormatter():
"""Generate text by evaluaring an f-string literal"""
- def __init__(self, fstring, default=None):
+ def __init__(self, fstring, default=None, fmt=None):
self.format_map = util.compile_expression("f'''" + fstring + "'''")
@@ -251,81 +256,37 @@ def _slice(indices):
)
-def parse_format_spec(format_spec, conversion):
- fmt = build_format_func(format_spec)
- if not conversion:
- return fmt
-
- global _CONVERSIONS
- if _CONVERSIONS is None:
- _CONVERSIONS = {
- "l": str.lower,
- "u": str.upper,
- "c": str.capitalize,
- "C": string.capwords,
- "j": functools.partial(json.dumps, default=str),
- "t": str.strip,
- "T": util.datetime_to_timestamp_string,
- "d": text.parse_timestamp,
- "U": text.unescape,
- "S": util.to_string,
- "s": str,
- "r": repr,
- "a": ascii,
- }
-
- conversion = _CONVERSIONS[conversion]
- if fmt is format:
- return conversion
- else:
- def chain(obj):
- return fmt(conversion(obj))
- return chain
+def _build_format_func(format_spec, default):
+ if format_spec:
+ return _FORMAT_SPECIFIERS.get(
+ format_spec[0], _default_format)(format_spec, default)
+ return default
-def build_format_func(format_spec):
- if format_spec:
- fmt = format_spec[0]
- if fmt == "?":
- return _parse_optional(format_spec)
- if fmt == "[":
- return _parse_slice(format_spec)
- if fmt == "L":
- return _parse_maxlen(format_spec)
- if fmt == "J":
- return _parse_join(format_spec)
- if fmt == "R":
- return _parse_replace(format_spec)
- if fmt == "D":
- return _parse_datetime(format_spec)
- return _default_format(format_spec)
- return format
-
-
-def _parse_optional(format_spec):
- before, after, format_spec = format_spec.split("/", 2)
+def _parse_optional(format_spec, default):
+ before, after, format_spec = format_spec.split(_SEPARATOR, 2)
before = before[1:]
- fmt = build_format_func(format_spec)
+ fmt = _build_format_func(format_spec, default)
def optional(obj):
return before + fmt(obj) + after if obj else ""
return optional
-def _parse_slice(format_spec):
+def _parse_slice(format_spec, default):
indices, _, format_spec = format_spec.partition("]")
slice = _slice(indices[1:])
- fmt = build_format_func(format_spec)
+ fmt = _build_format_func(format_spec, default)
def apply_slice(obj):
return fmt(obj[slice])
return apply_slice
-def _parse_maxlen(format_spec):
- maxlen, replacement, format_spec = format_spec.split("/", 2)
+def _parse_maxlen(format_spec, default):
+ maxlen, replacement, format_spec = format_spec.split(_SEPARATOR, 2)
maxlen = text.parse_int(maxlen[1:])
- fmt = build_format_func(format_spec)
+ fmt = _build_format_func(format_spec, default)
def mlen(obj):
obj = fmt(obj)
@@ -333,37 +294,58 @@ def _parse_maxlen(format_spec):
return mlen
-def _parse_join(format_spec):
- separator, _, format_spec = format_spec.partition("/")
+def _parse_join(format_spec, default):
+ separator, _, format_spec = format_spec.partition(_SEPARATOR)
separator = separator[1:]
- fmt = build_format_func(format_spec)
+ fmt = _build_format_func(format_spec, default)
def join(obj):
return fmt(separator.join(obj))
return join
-def _parse_replace(format_spec):
- old, new, format_spec = format_spec.split("/", 2)
+def _parse_replace(format_spec, default):
+ old, new, format_spec = format_spec.split(_SEPARATOR, 2)
old = old[1:]
- fmt = build_format_func(format_spec)
+ fmt = _build_format_func(format_spec, default)
def replace(obj):
return fmt(obj.replace(old, new))
return replace
-def _parse_datetime(format_spec):
- dt_format, _, format_spec = format_spec.partition("/")
+def _parse_datetime(format_spec, default):
+ dt_format, _, format_spec = format_spec.partition(_SEPARATOR)
dt_format = dt_format[1:]
- fmt = build_format_func(format_spec)
+ fmt = _build_format_func(format_spec, default)
def dt(obj):
return fmt(text.parse_datetime(obj, dt_format))
return dt
-def _default_format(format_spec):
+def _parse_offset(format_spec, default):
+ offset, _, format_spec = format_spec.partition(_SEPARATOR)
+ offset = offset[1:]
+ fmt = _build_format_func(format_spec, default)
+
+ if not offset or offset == "local":
+ is_dst = time.daylight and time.localtime().tm_isdst > 0
+ offset = -(time.altzone if is_dst else time.timezone)
+ else:
+ hours, _, minutes = offset.partition(":")
+ offset = 3600 * int(hours)
+ if minutes:
+ offset += 60 * (int(minutes) if offset > 0 else -int(minutes))
+
+ offset = datetime.timedelta(seconds=offset)
+
+ def off(obj):
+ return fmt(obj + offset)
+ return off
+
+
+def _default_format(format_spec, default):
def wrap(obj):
return format(obj, format_spec)
return wrap
@@ -379,3 +361,35 @@ class Literal():
_literal = Literal()
+
+_CACHE = {}
+_SEPARATOR = "/"
+_GLOBALS = {
+ "_env": lambda: os.environ,
+ "_lit": lambda: _literal,
+ "_now": datetime.datetime.now,
+}
+_CONVERSIONS = {
+ "l": str.lower,
+ "u": str.upper,
+ "c": str.capitalize,
+ "C": string.capwords,
+ "j": functools.partial(json.dumps, default=str),
+ "t": str.strip,
+ "T": util.datetime_to_timestamp_string,
+ "d": text.parse_timestamp,
+ "U": text.unescape,
+ "S": util.to_string,
+ "s": str,
+ "r": repr,
+ "a": ascii,
+}
+_FORMAT_SPECIFIERS = {
+ "?": _parse_optional,
+ "[": _parse_slice,
+ "D": _parse_datetime,
+ "L": _parse_maxlen,
+ "J": _parse_join,
+ "O": _parse_offset,
+ "R": _parse_replace,
+}