@@ -291,7 +291,7 @@ def _process_comment(self, line) -> None:
291
291
# These are called user comments
292
292
self .user_comments .append (line [1 :].strip ())
293
293
294
- def parse (self , fileobj : IO [AnyStr ]) -> None :
294
+ def parse (self , fileobj : IO [AnyStr ] | Iterable [ AnyStr ] ) -> None :
295
295
"""
296
296
Reads from the file-like object `fileobj` and adds any po file
297
297
units found in it to the `Catalog` supplied to the constructor.
@@ -329,15 +329,15 @@ def _invalid_pofile(self, line, lineno, msg) -> None:
329
329
330
330
331
331
def read_po (
332
- fileobj : IO [AnyStr ],
332
+ fileobj : IO [AnyStr ] | Iterable [ AnyStr ] ,
333
333
locale : str | Locale | None = None ,
334
334
domain : str | None = None ,
335
335
ignore_obsolete : bool = False ,
336
336
charset : str | None = None ,
337
337
abort_invalid : bool = False ,
338
338
) -> Catalog :
339
339
"""Read messages from a ``gettext`` PO (portable object) file from the given
340
- file-like object and return a `Catalog`.
340
+ file-like object (or an iterable of lines) and return a `Catalog`.
341
341
342
342
>>> from datetime import datetime
343
343
>>> from io import StringIO
@@ -373,7 +373,7 @@ def read_po(
373
373
.. versionadded:: 1.0
374
374
Added support for explicit charset argument.
375
375
376
- :param fileobj: the file-like object to read the PO file from
376
+ :param fileobj: the file-like object (or iterable of lines) to read the PO file from
377
377
:param locale: the locale identifier or `Locale` object, or `None`
378
378
if the catalog is not bound to a locale (which basically
379
379
means it's a template)
@@ -529,45 +529,69 @@ def write_po(
529
529
updating the catalog
530
530
:param include_lineno: include line number in the location comment
531
531
"""
532
- def _normalize (key , prefix = '' ):
533
- return normalize (key , prefix = prefix , width = width )
534
-
535
- def _write (text ):
536
- if isinstance (text , str ):
537
- text = text .encode (catalog .charset , 'backslashreplace' )
538
- fileobj .write (text )
539
-
540
- def _write_comment (comment , prefix = '' ):
541
- # xgettext always wraps comments even if --no-wrap is passed;
542
- # provide the same behaviour
543
- _width = width if width and width > 0 else 76
544
- for line in wraptext (comment , _width ):
545
- _write (f"#{ prefix } { line .strip ()} \n " )
546
-
547
- def _write_message (message , prefix = '' ):
532
+
533
+ sort_by = None
534
+ if sort_output :
535
+ sort_by = "message"
536
+ elif sort_by_file :
537
+ sort_by = "location"
538
+
539
+ for line in generate_po (
540
+ catalog ,
541
+ ignore_obsolete = ignore_obsolete ,
542
+ include_lineno = include_lineno ,
543
+ include_previous = include_previous ,
544
+ no_location = no_location ,
545
+ omit_header = omit_header ,
546
+ sort_by = sort_by ,
547
+ width = width ,
548
+ ):
549
+ if isinstance (line , str ):
550
+ line = line .encode (catalog .charset , 'backslashreplace' )
551
+ fileobj .write (line )
552
+
553
+
554
+ def generate_po (
555
+ catalog : Catalog ,
556
+ * ,
557
+ ignore_obsolete : bool = False ,
558
+ include_lineno : bool = True ,
559
+ include_previous : bool = False ,
560
+ no_location : bool = False ,
561
+ omit_header : bool = False ,
562
+ sort_by : Literal ["message" , "location" ] | None = None ,
563
+ width : int = 76 ,
564
+ ) -> Iterable [str ]:
565
+ r"""Yield text strings representing a ``gettext`` PO (portable object) file.
566
+
567
+ See `write_po()` for a more detailed description.
568
+ """
569
+ # xgettext always wraps comments even if --no-wrap is passed;
570
+ # provide the same behaviour
571
+ comment_width = width if width and width > 0 else 76
572
+
573
+ def _format_comment (comment , prefix = '' ):
574
+ for line in wraptext (comment , comment_width ):
575
+ yield f"#{ prefix } { line .strip ()} \n "
576
+
577
+ def _format_message (message , prefix = '' ):
548
578
if isinstance (message .id , (list , tuple )):
549
579
if message .context :
550
- _write ( f"{ prefix } msgctxt { _normalize (message .context , prefix )} \n " )
551
- _write ( f"{ prefix } msgid { _normalize (message .id [0 ], prefix )} \n " )
552
- _write ( f"{ prefix } msgid_plural { _normalize (message .id [1 ], prefix )} \n " )
580
+ yield f"{ prefix } msgctxt { normalize (message .context , prefix = prefix , width = width )} \n "
581
+ yield f"{ prefix } msgid { normalize (message .id [0 ], prefix = prefix , width = width )} \n "
582
+ yield f"{ prefix } msgid_plural { normalize (message .id [1 ], prefix = prefix , width = width )} \n "
553
583
554
584
for idx in range (catalog .num_plurals ):
555
585
try :
556
586
string = message .string [idx ]
557
587
except IndexError :
558
588
string = ''
559
- _write ( f"{ prefix } msgstr[{ idx :d} ] { _normalize (string , prefix )} \n " )
589
+ yield f"{ prefix } msgstr[{ idx :d} ] { normalize (string , prefix = prefix , width = width )} \n "
560
590
else :
561
591
if message .context :
562
- _write (f"{ prefix } msgctxt { _normalize (message .context , prefix )} \n " )
563
- _write (f"{ prefix } msgid { _normalize (message .id , prefix )} \n " )
564
- _write (f"{ prefix } msgstr { _normalize (message .string or '' , prefix )} \n " )
565
-
566
- sort_by = None
567
- if sort_output :
568
- sort_by = "message"
569
- elif sort_by_file :
570
- sort_by = "location"
592
+ yield f"{ prefix } msgctxt { normalize (message .context , prefix = prefix , width = width )} \n "
593
+ yield f"{ prefix } msgid { normalize (message .id , prefix = prefix , width = width )} \n "
594
+ yield f"{ prefix } msgstr { normalize (message .string or '' , prefix = prefix , width = width )} \n "
571
595
572
596
for message in _sort_messages (catalog , sort_by = sort_by ):
573
597
if not message .id : # This is the header "message"
@@ -580,12 +604,12 @@ def _write_message(message, prefix=''):
580
604
lines += wraptext (line , width = width ,
581
605
subsequent_indent = '# ' )
582
606
comment_header = '\n ' .join (lines )
583
- _write ( f"{ comment_header } \n " )
607
+ yield f"{ comment_header } \n "
584
608
585
609
for comment in message .user_comments :
586
- _write_comment (comment )
610
+ yield from _format_comment (comment )
587
611
for comment in message .auto_comments :
588
- _write_comment (comment , prefix = '.' )
612
+ yield from _format_comment (comment , prefix = '.' )
589
613
590
614
if not no_location :
591
615
locs = []
@@ -606,35 +630,34 @@ def _write_message(message, prefix=''):
606
630
location = f"{ location } :{ lineno :d} "
607
631
if location not in locs :
608
632
locs .append (location )
609
- _write_comment (' ' .join (locs ), prefix = ':' )
633
+ yield from _format_comment (' ' .join (locs ), prefix = ':' )
610
634
if message .flags :
611
- _write ( f"#{ ', ' .join (['' , * sorted (message .flags )])} \n " )
635
+ yield f"#{ ', ' .join (['' , * sorted (message .flags )])} \n "
612
636
613
637
if message .previous_id and include_previous :
614
- _write_comment (
615
- f'msgid { _normalize (message .previous_id [0 ])} ' ,
638
+ yield from _format_comment (
639
+ f'msgid { normalize (message .previous_id [0 ], width = width )} ' ,
616
640
prefix = '|' ,
617
641
)
618
642
if len (message .previous_id ) > 1 :
619
- _write_comment ('msgid_plural %s' % _normalize (
620
- message .previous_id [1 ],
621
- ), prefix = '|' )
643
+ norm_previous_id = normalize (message .previous_id [1 ], width = width )
644
+ yield from _format_comment (f'msgid_plural { norm_previous_id } ' , prefix = '|' )
622
645
623
- _write_message (message )
624
- _write ( '\n ' )
646
+ yield from _format_message (message )
647
+ yield '\n '
625
648
626
649
if not ignore_obsolete :
627
650
for message in _sort_messages (
628
651
catalog .obsolete .values (),
629
652
sort_by = sort_by ,
630
653
):
631
654
for comment in message .user_comments :
632
- _write_comment (comment )
633
- _write_message (message , prefix = '#~ ' )
634
- _write ( '\n ' )
655
+ yield from _format_comment (comment )
656
+ yield from _format_message (message , prefix = '#~ ' )
657
+ yield '\n '
635
658
636
659
637
- def _sort_messages (messages : Iterable [Message ], sort_by : Literal ["message" , "location" ]) -> list [Message ]:
660
+ def _sort_messages (messages : Iterable [Message ], sort_by : Literal ["message" , "location" ] | None ) -> list [Message ]:
638
661
"""
639
662
Sort the given message iterable by the given criteria.
640
663
0 commit comments