Source code for vermouth.truncating_formatter

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2018 University of Groningen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Provides a string formatter that can not only pad strings to a specified
length if they're too short, but also truncate them if they're too long.
"""


import string
import re
from collections import namedtuple

FormatSpec = namedtuple('FormatSpec', 'fill align sign alt zero_padding width comma decimal precision type')


[docs] class TruncFormatter(string.Formatter): """ Adds the 't' option to the format specification mini-language at the end of the format string. If provided, the produced formatted string will be truncated to the specified length. """ # https://stackoverflow.com/questions/44551535/access-the-cpython-string-format-specification-mini-language-parser format_spec_re = r'(([\s\S])?([<>=\^]))?([\+\- ])?(#)?(0)?(\d*)?(,)?((\.)(\d*))?([sbcdoxXneEfFgGn%])?' format_spec_re = re.compile(format_spec_re)
[docs] def format_field(self, value, format_spec): """ Implements the 't' option to truncate strings that are too long to the required width. Parameters ---------- value The object to format. format_spec: str The format_spec describing how `value` should be formatted Returns str `value` formatted as per `format_spec` """ if format_spec.endswith('t'): truncate = True format_spec = format_spec[:-1] else: truncate = False result = super().format_field(value, format_spec) # From here on we know the format spec is valid spec = FormatSpec(*self.format_spec_re.fullmatch(format_spec).group(2, 3, 4, 5, 6, 7, 8, 10, 11, 12)) if spec.width: spec = spec._replace(width=int(spec.width)) else: spec = spec._replace(width=0) if not truncate or spec.width == 0 or len(result) <= spec.width: return result # skip groups not interested in if not spec.type: if isinstance(value, str): spec = spec._replace(type='s') elif isinstance(value, int): spec = spec._replace(type='d') elif isinstance(value, float): spec = spec._replace(type='g') if not spec.align: if spec.type in 's': spec = spec._replace(align='<') elif spec.type in 'bcdoxXn' or spec.type in 'eEfFgGn%': spec = spec._replace(align='>') # We know len(result) > width. So there's no fill characters. # We also have at least width, type and align at this point. # We should probably do something special when it's a number with a # magic formatting prefix (0b, 0o, 0x) or if it has a sign. Idem for # exponent notation. Maybe, for numerical types we should round instead # of truncate the string. overflow = len(result) - spec.width if spec.align == '<': # left chars most significant. e.g. str result = result[:-overflow] elif spec.align == '>': # right characters most significant. e.g. int result = result[overflow:] elif spec.align == '=': # padding between sign and digits +0000120 # Note that this is the default for fill character 0 raise NotImplementedError elif spec.align == '^': # centered result = result[overflow//2:-overflow//2] return result
# if __name__ == '__main__': # formatter = TruncFormatter() # # str_data = 'abcde' # # print(formatter.format('{}', str_data)) # # print(formatter.format('"{:4.4}"', str_data)) # print(formatter.format('"{:>4.4}"', str_data)) # print(formatter.format('"{:4t}"', str_data)) # print(formatter.format('"{:>4t}"', str_data)) # print(formatter.format('"{:5t}"', str_data)) # print(formatter.format('"{:6t}"', str_data)) # # int_data = 123456789 # # print(formatter.format('"{:4}"', int_data)) # print(formatter.format('"{:>4}"', int_data)) # print(formatter.format('"{:4t}"', int_data)) # print(formatter.format('"{:>4t}"', int_data)) # print(formatter.format('"{:<4t}"', int_data)) # print(formatter.format('"{:^4t}"', int_data)) # print(formatter.format('"{:5t}"', int_data)) # print(formatter.format('"{:6t}"', int_data)) # # print(formatter.format('"{:11t}"', int_data)) # print(formatter.format('"{:<11t}"', int_data)) # print(formatter.format('"{:^11t}"', int_data))