From 54525d2e213ea62d89cb08ae29358a62056aa616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Sat, 25 Jun 2022 16:45:34 +0200 Subject: [PATCH] [formatter] implement slice operator as format specifier this allows using a slice operator alongside other (special) format specifiers like J, to first join list elements to a string and then trimming that with a slice. {tags:J, /[:50]} --- docs/formatting.md | 6 ++++++ gallery_dl/formatter.py | 29 +++++++++++++++++++++++------ test/test_formatter.py | 20 +++++++++++++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/docs/formatting.md b/docs/formatting.md index f01e4193..b9c4355e 100644 --- a/docs/formatting.md +++ b/docs/formatting.md @@ -139,6 +139,12 @@ Format specifiers can be used for advanced formatting by using the options provi {empty:?[/]/} + + [<start>:<stop>] + Applies a Slicing operation to the current value, similar to Field Names + {foo:[1:-1]} + oo Ba + L<maxlen>/<repl>/ Replaces the entire output with <repl> if its length exceeds <maxlen> diff --git a/gallery_dl/formatter.py b/gallery_dl/formatter.py index f2c2d331..107c8ed6 100644 --- a/gallery_dl/formatter.py +++ b/gallery_dl/formatter.py @@ -232,12 +232,7 @@ def parse_field_name(field_name): func = operator.itemgetter try: if ":" in key: - start, _, stop = key.partition(":") - stop, _, step = stop.partition(":") - start = int(start) if start else None - stop = int(stop) if stop else None - step = int(step) if step else None - key = slice(start, stop, step) + key = _slice(key) except TypeError: pass # key is an integer @@ -246,6 +241,16 @@ def parse_field_name(field_name): return first, funcs +def _slice(indices): + start, _, stop = indices.partition(":") + stop, _, step = stop.partition(":") + return slice( + int(start) if start else None, + int(stop) if stop else None, + int(step) if step else None, + ) + + def parse_format_spec(format_spec, conversion): fmt = build_format_func(format_spec) if not conversion: @@ -283,6 +288,8 @@ def build_format_func(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": @@ -305,6 +312,16 @@ def _parse_optional(format_spec): return optional +def _parse_slice(format_spec): + indices, _, format_spec = format_spec.partition("]") + slice = _slice(indices[1:]) + fmt = build_format_func(format_spec) + + def apply_slice(obj): + return fmt(obj[slice]) + return apply_slice + + def _parse_maxlen(format_spec): maxlen, replacement, format_spec = format_spec.split("/", 2) maxlen = text.parse_int(maxlen[1:]) diff --git a/test/test_formatter.py b/test/test_formatter.py index dc818b00..5b8ca0ab 100644 --- a/test/test_formatter.py +++ b/test/test_formatter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2021 Mike Fährmann +# Copyright 2021-2022 Mike Fährmann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as @@ -135,6 +135,21 @@ class TestFormatter(unittest.TestCase): self._run_test("{a[:50:2]}", v[:50:2]) self._run_test("{a[::]}" , v) + self._run_test("{a:[1:10]}" , v[1:10]) + self._run_test("{a:[-10:-1]}", v[-10:-1]) + self._run_test("{a:[5:]}" , v[5:]) + self._run_test("{a:[50:]}", v[50:]) + self._run_test("{a:[:5]}" , v[:5]) + self._run_test("{a:[:50]}", v[:50]) + self._run_test("{a:[:]}" , v) + self._run_test("{a:[1:10:2]}" , v[1:10:2]) + self._run_test("{a:[-10:-1:2]}", v[-10:-1:2]) + self._run_test("{a:[5::2]}" , v[5::2]) + self._run_test("{a:[50::2]}", v[50::2]) + self._run_test("{a:[:5:2]}" , v[:5:2]) + self._run_test("{a:[:50:2]}", v[:50:2]) + self._run_test("{a:[::]}" , v) + def test_maxlen(self): v = self.kwdict["a"] self._run_test("{a:L5/foo/}" , "foo") @@ -177,6 +192,9 @@ class TestFormatter(unittest.TestCase): # join-and-replace self._run_test("{l:J-/Rb/E/}", "a-E-c") + # join and slice + self._run_test("{l:J-/[1:-1]}", "-b-") + # optional-and-maxlen self._run_test("{d[a]:?/L1/too long/}", "") self._run_test("{d[c]:?/L5/too long/}", "")