Coverage for sources/mimeogram/formatters.py: 100%
31 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-05 19:15 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-05 19:15 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
4#============================================================================#
5# #
6# Licensed under the Apache License, Version 2.0 (the "License"); #
7# you may not use this file except in compliance with the License. #
8# You may obtain a copy of the License at #
9# #
10# http://www.apache.org/licenses/LICENSE-2.0 #
11# #
12# Unless required by applicable law or agreed to in writing, software #
13# distributed under the License is distributed on an "AS IS" BASIS, #
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15# See the License for the specific language governing permissions and #
16# limitations under the License. #
17# #
18#============================================================================#
21''' Formatting of mimeogram bundles. '''
24from . import __
25from . import parts as _parts
28def format_mimeogram(
29 parts: __.cabc.Sequence[ _parts.Part ],
30 message: __.typx.Optional[ str ] = None,
31 deterministic_boundary: bool = False,
32) -> str:
33 ''' Formats parts into mimeogram. '''
34 if not parts and message is None:
35 from .exceptions import MimeogramFormatEmpty
36 raise MimeogramFormatEmpty( )
37 if deterministic_boundary:
38 content_hash = _compute_content_hash( parts, message )
39 boundary = f"====MIMEOGRAM_{content_hash}===="
40 else:
41 boundary = "====MIMEOGRAM_{uuid}====".format( uuid = __.uuid4( ).hex )
42 lines: list[ str ] = [ ]
43 if message:
44 message_part = _parts.Part(
45 location = 'mimeogram://message',
46 mimetype = 'text/plain', # TODO? Markdown
47 charset = 'utf-8',
48 linesep = _parts.LineSeparators.LF,
49 content = message )
50 lines.append( format_part( message_part, boundary ) )
51 for part in parts:
52 lines.append( format_part( part, boundary ) ) # noqa: PERF401
53 lines.append( f"--{boundary}--" )
54 return '\n'.join( lines )
57def format_part( part: _parts.Part, boundary: str ) -> str:
58 ''' Formats part with boundary marker and headers. '''
59 return '\n'.join( (
60 f"--{boundary}",
61 f"Content-Location: {part.location}",
62 f"Content-Type: {part.mimetype}; "
63 f"charset={part.charset}; "
64 f"linesep={part.linesep.name}",
65 '',
66 part.content ) )
69def _compute_content_hash(
70 parts: __.cabc.Sequence[ _parts.Part ],
71 message: __.typx.Optional[ str ] = None,
72) -> str:
73 ''' Computes deterministic hash for mimeogram content. '''
74 hasher = __.hashlib.sha256( )
75 if message is not None:
76 hasher.update( message.encode( 'utf-8' ) )
77 for part in parts:
78 hasher.update( str( part.location ).encode( 'utf-8' ) )
79 hasher.update( str( part.mimetype ).encode( 'utf-8' ) )
80 hasher.update( str( part.charset ).encode( 'utf-8' ) )
81 hasher.update( str( part.linesep.name ).encode( 'utf-8' ) )
82 hasher.update( str( part.content ).encode( 'utf-8' ) )
83 return hasher.hexdigest( )