From a79a945494da6aa0bac07e0a000ad9ca60899c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Sun, 11 Jan 2026 17:49:55 +0100 Subject: [PATCH] [formatter] overload '.' operator implement generic access of * list items (L[1] -> L.1) * dict vslues (D[key] -> D.key) * object attributes (O.attr -> O.attr) in standard format strings --- gallery_dl/formatter.py | 20 ++++++++++++++++++-- test/test_formatter.py | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/gallery_dl/formatter.py b/gallery_dl/formatter.py index 0787464a..ca6eca91 100644 --- a/gallery_dl/formatter.py +++ b/gallery_dl/formatter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright 2021-2025 Mike Fährmann +# Copyright 2021-2026 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 @@ -298,7 +298,7 @@ def parse_field_name(field_name): for is_attr, key in rest: if is_attr: - func = operator.attrgetter + func = _attrgetter else: func = operator.itemgetter try: @@ -330,6 +330,22 @@ def _slice(indices): ) +def _attrgetter(key): + + if key.isdecimal() or key[0] == "-": + try: + return operator.itemgetter(int(key)) + except ValueError: + pass + + def apply_key(obj): + try: + return obj[key] + except (TypeError, KeyError): + return getattr(obj, key) + return apply_key + + def _bytesgetter(slice): def apply_slice_bytes(obj): diff --git a/test/test_formatter.py b/test/test_formatter.py index 67df2790..28654a3d 100644 --- a/test/test_formatter.py +++ b/test/test_formatter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2021-2025 Mike Fährmann +# Copyright 2021-2026 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 @@ -188,6 +188,27 @@ class TestFormatter(unittest.TestCase): self._run_test("{d['a']}", "foo") self._run_test('{d["a"]}', "foo") + def test_dot_index(self): + self._run_test("{l.1}" , "b") + self._run_test("{a.6}" , "w") + self._run_test("{a.99}" , "None") + self._run_test("{l.-1}" , "c") + self._run_test("{a.-7}" , "o") + self._run_test("{a.-0}" , "h") # same as a[0] + self._run_test("{a.-99}", "None") + + def test_dot_access_dict(self): + self._run_test("{d.a}", "foo") + self._run_test("{d.d}", "None") + self._run_test("{a.d}", "None") + self._run_test("{L.0.age}", "42") + self._run_test("{L.-1.name.2}", "x") + self._run_test("{L.1:I}", self.kwdict["L"][1]) + + def test_dot_access_attr(self): + self._run_test("{t.real}", "1262304000") + self._run_test("{dt.year}", "2010") + def test_slice_str(self): v = self.kwdict["a"] self._run_test("{a[1:10]}" , v[1:10])