diff --git a/CHANGELOG b/CHANGELOG index 6f837f9d06..545e0128d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add firstline tool * Add support for Python 3.9 * Skip access check on ModelStorage instances * Eager load Function field with same multiple getter diff --git a/doc/ref/tools/misc.rst b/doc/ref/tools/misc.rst index 488db583fc..002cae545c 100644 --- a/doc/ref/tools/misc.rst +++ b/doc/ref/tools/misc.rst @@ -12,3 +12,7 @@ Resolve a dotted name to a global object. .. method:: sql_pairing(x, y) Return an SQL expression that pairs SQL integers x and y. + +.. method:: firstline(text) + +Return first non-empty line of a text field. diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py index 7a25c64e9f..a2423e2776 100644 --- a/trytond/ir/attachment.py +++ b/trytond/ir/attachment.py @@ -7,19 +7,13 @@ from trytond.config import config from trytond.i18n import lazy_gettext from trytond.model import ModelView, ModelSQL, fields from trytond.pyson import Eval +from trytond.tools import firstline from trytond.transaction import Transaction from .resource import ResourceMixin, resource_copy __all__ = ['AttachmentCopyMixin'] -def firstline(description): - try: - return next((x for x in description.splitlines() if x.strip())) - except StopIteration: - return '' - - if config.getboolean('attachment', 'filestore', default=True): file_id = 'file_id' store_prefix = config.get('attachment', 'store_prefix', default=None) diff --git a/trytond/tests/test_tools.py b/trytond/tests/test_tools.py index 90b3a31146..58a32a89f7 100644 --- a/trytond/tests/test_tools.py +++ b/trytond/tests/test_tools.py @@ -13,7 +13,7 @@ import sql.operators from trytond.tools import ( reduce_ids, reduce_domain, decimal_, is_instance_method, file_open, strip_wildcard, lstrip_wildcard, rstrip_wildcard, slugify, sortable_values, - escape_wildcard) + escape_wildcard, firstline) from trytond.tools.string_ import StringPartitioned, LazyString from trytond.tools.domain_inversion import ( domain_inversion, parse, simplify, merge, concat, unique_value, @@ -757,6 +757,18 @@ class DomainInversionTestCase(unittest.TestCase): (('a', 1), ('b', None)), ]) + def test_firstline(self): + "Test firstline" + for text, result in [ + ("", ""), + ("first line\nsecond line", "first line"), + ("\nsecond line", "second line"), + ("\n\nthird line", "third line"), + (" \nsecond line", "second line"), + ]: + with self.subTest(text=text, result=result): + self.assertEqual(firstline(text), result) + def suite(): func = unittest.TestLoader().loadTestsFromTestCase diff --git a/trytond/tools/__init__.py b/trytond/tools/__init__.py index e8afa93657..e1f769fae0 100644 --- a/trytond/tools/__init__.py +++ b/trytond/tools/__init__.py @@ -9,14 +9,14 @@ from .misc import ( file_open, get_smtp_server, reduce_ids, reduce_domain, grouped_slice, is_instance_method, resolve, strip_wildcard, lstrip_wildcard, rstrip_wildcard, slugify, sortable_values, - escape_wildcard, sql_pairing) + escape_wildcard, sql_pairing, firstline) from .decimal_ import decistmt __all__ = ['file_open', 'get_smtp_server', 'reduce_ids', 'reduce_domain', 'grouped_slice', 'is_instance_method', 'resolve', 'strip_wildcard', 'lstrip_wildcard', 'rstrip_wildcard', 'slugify', 'decistmt', 'ClassProperty', 'cursor_dict', 'cached_property', - 'sortable_values', 'escape_wildcard', 'sql_pairing'] + 'sortable_values', 'escape_wildcard', 'sql_pairing', 'firstline'] class ClassProperty(property): diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py index c8d078b690..5377c9ed38 100644 --- a/trytond/tools/misc.py +++ b/trytond/tools/misc.py @@ -250,3 +250,11 @@ def sql_pairing(x, y): return Case( (x < y, (y * y) + x), else_=(x * x) + x + y) + + +def firstline(text): + "Returns first non-empty line" + try: + return next((x for x in text.splitlines() if x.strip())) + except StopIteration: + return ''