__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
"""
    babel.messages.pofile
    ~~~~~~~~~~~~~~~~~~~~~

    Reading and writing of files in the ``gettext`` PO (portable object)
    format.

    :copyright: (c) 2013-2025 by the Babel Team.
    :license: BSD, see LICENSE for more details.
"""
from __future__ import annotations

import os
import re
from collections.abc import Iterable
from typing import TYPE_CHECKING, Literal

from babel.core import Locale
from babel.messages.catalog import Catalog, Message
from babel.util import TextWrapper, _cmp

if TYPE_CHECKING:
    from typing import IO, AnyStr

    from _typeshed import SupportsWrite


def unescape(string: str) -> str:
    r"""Reverse `escape` the given string.

    >>> print(unescape('"Say:\\n  \\"hello, world!\\"\\n"'))
    Say:
      "hello, world!"
    <BLANKLINE>

    :param string: the string to unescape
    """
    def replace_escapes(match):
        m = match.group(1)
        if m == 'n':
            return '\n'
        elif m == 't':
            return '\t'
        elif m == 'r':
            return '\r'
        # m is \ or "
        return m
    return re.compile(r'\\([\\trn"])').sub(replace_escapes, string[1:-1])


def denormalize(string: str) -> str:
    r"""Reverse the normalization done by the `normalize` function.

    >>> print(denormalize(r'''""
    ... "Say:\n"
    ... "  \"hello, world!\"\n"'''))
    Say:
      "hello, world!"
    <BLANKLINE>

    >>> print(denormalize(r'''""
    ... "Say:\n"
    ... "  \"Lorem ipsum dolor sit "
    ... "amet, consectetur adipisicing"
    ... " elit, \"\n"'''))
    Say:
      "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
    <BLANKLINE>

    :param string: the string to denormalize
    """
    if '\n' in string:
        escaped_lines = string.splitlines()
        if string.startswith('""'):
            escaped_lines = escaped_lines[1:]
        lines = map(unescape, escaped_lines)
        return ''.join(lines)
    else:
        return unescape(string)


def _extract_locations(line: str) -> list[str]:
    """Extract locations from location comments.

    Locations are extracted while properly handling First Strong
    Isolate (U+2068) and Pop Directional Isolate (U+2069), used by
    gettext to enclose filenames with spaces and tabs in their names.
    """
    if "\u2068" not in line and "\u2069" not in line:
        return line.lstrip().split()

    locations = []
    location = ""
    in_filename = False
    for c in line:
        if c == "\u2068":
            if in_filename:
                raise ValueError("location comment contains more First Strong Isolate "
                                 "characters, than Pop Directional Isolate characters")
            in_filename = True
            continue
        elif c == "\u2069":
            if not in_filename:
                raise ValueError("location comment contains more Pop Directional Isolate "
                                 "characters, than First Strong Isolate characters")
            in_filename = False
            continue
        elif c == " ":
            if in_filename:
                location += c
            elif location:
                locations.append(location)
                location = ""
        else:
            location += c
    else:
        if location:
            if in_filename:
                raise ValueError("location comment contains more First Strong Isolate "
                                 "characters, than Pop Directional Isolate characters")
            locations.append(location)

    return locations


class PoFileError(Exception):
    """Exception thrown by PoParser when an invalid po file is encountered."""

    def __init__(self, message: str, catalog: Catalog, line: str, lineno: int) -> None:
        super().__init__(f'{message} on {lineno}')
        self.catalog = catalog
        self.line = line
        self.lineno = lineno


class _NormalizedString:

    def __init__(self, *args: str) -> None:
        self._strs: list[str] = []
        for arg in args:
            self.append(arg)

    def append(self, s: str) -> None:
        self._strs.append(s.strip())

    def denormalize(self) -> str:
        return ''.join(map(unescape, self._strs))

    def __bool__(self) -> bool:
        return bool(self._strs)

    def __repr__(self) -> str:
        return os.linesep.join(self._strs)

    def __cmp__(self, other: object) -> int:
        if not other:
            return 1

        return _cmp(str(self), str(other))

    def __gt__(self, other: object) -> bool:
        return self.__cmp__(other) > 0

    def __lt__(self, other: object) -> bool:
        return self.__cmp__(other) < 0

    def __ge__(self, other: object) -> bool:
        return self.__cmp__(other) >= 0

    def __le__(self, other: object) -> bool:
        return self.__cmp__(other) <= 0

    def __eq__(self, other: object) -> bool:
        return self.__cmp__(other) == 0

    def __ne__(self, other: object) -> bool:
        return self.__cmp__(other) != 0


class PoFileParser:
    """Support class to  read messages from a ``gettext`` PO (portable object) file
    and add them to a `Catalog`

    See `read_po` for simple cases.
    """

    _keywords = [
        'msgid',
        'msgstr',
        'msgctxt',
        'msgid_plural',
    ]

    def __init__(self, catalog: Catalog, ignore_obsolete: bool = False, abort_invalid: bool = False) -> None:
        self.catalog = catalog
        self.ignore_obsolete = ignore_obsolete
        self.counter = 0
        self.offset = 0
        self.abort_invalid = abort_invalid
        self._reset_message_state()

    def _reset_message_state(self) -> None:
        self.messages = []
        self.translations = []
        self.locations = []
        self.flags = []
        self.user_comments = []
        self.auto_comments = []
        self.context = None
        self.obsolete = False
        self.in_msgid = False
        self.in_msgstr = False
        self.in_msgctxt = False

    def _add_message(self) -> None:
        """
        Add a message to the catalog based on the current parser state and
        clear the state ready to process the next message.
        """
        self.translations.sort()
        if len(self.messages) > 1:
            msgid = tuple(m.denormalize() for m in self.messages)
        else:
            msgid = self.messages[0].denormalize()
        if isinstance(msgid, (list, tuple)):
            string = ['' for _ in range(self.catalog.num_plurals)]
            for idx, translation in self.translations:
                if idx >= self.catalog.num_plurals:
                    self._invalid_pofile("", self.offset, "msg has more translations than num_plurals of catalog")
                    continue
                string[idx] = translation.denormalize()
            string = tuple(string)
        else:
            string = self.translations[0][1].denormalize()
        msgctxt = self.context.denormalize() if self.context else None
        message = Message(msgid, string, list(self.locations), set(self.flags),
                          self.auto_comments, self.user_comments, lineno=self.offset + 1,
                          context=msgctxt)
        if self.obsolete:
            if not self.ignore_obsolete:
                self.catalog.obsolete[self.catalog._key_for(msgid, msgctxt)] = message
        else:
            self.catalog[msgid] = message
        self.counter += 1
        self._reset_message_state()

    def _finish_current_message(self) -> None:
        if self.messages:
            if not self.translations:
                self._invalid_pofile("", self.offset, f"missing msgstr for msgid '{self.messages[0].denormalize()}'")
                self.translations.append([0, _NormalizedString("")])
            self._add_message()

    def _process_message_line(self, lineno, line, obsolete=False) -> None:
        if line.startswith('"'):
            self._process_string_continuation_line(line, lineno)
        else:
            self._process_keyword_line(lineno, line, obsolete)

    def _process_keyword_line(self, lineno, line, obsolete=False) -> None:

        for keyword in self._keywords:
            try:
                if line.startswith(keyword) and line[len(keyword)] in [' ', '[']:
                    arg = line[len(keyword):]
                    break
            except IndexError:
                self._invalid_pofile(line, lineno, "Keyword must be followed by a string")
        else:
            self._invalid_pofile(line, lineno, "Start of line didn't match any expected keyword.")
            return

        if keyword in ['msgid', 'msgctxt']:
            self._finish_current_message()

        self.obsolete = obsolete

        # The line that has the msgid is stored as the offset of the msg
        # should this be the msgctxt if it has one?
        if keyword == 'msgid':
            self.offset = lineno

        if keyword in ['msgid', 'msgid_plural']:
            self.in_msgctxt = False
            self.in_msgid = True
            self.messages.append(_NormalizedString(arg))

        elif keyword == 'msgstr':
            self.in_msgid = False
            self.in_msgstr = True
            if arg.startswith('['):
                idx, msg = arg[1:].split(']', 1)
                self.translations.append([int(idx), _NormalizedString(msg)])
            else:
                self.translations.append([0, _NormalizedString(arg)])

        elif keyword == 'msgctxt':
            self.in_msgctxt = True
            self.context = _NormalizedString(arg)

    def _process_string_continuation_line(self, line, lineno) -> None:
        if self.in_msgid:
            s = self.messages[-1]
        elif self.in_msgstr:
            s = self.translations[-1][1]
        elif self.in_msgctxt:
            s = self.context
        else:
            self._invalid_pofile(line, lineno, "Got line starting with \" but not in msgid, msgstr or msgctxt")
            return
        s.append(line)

    def _process_comment(self, line) -> None:

        self._finish_current_message()

        if line[1:].startswith(':'):
            for location in _extract_locations(line[2:]):
                pos = location.rfind(':')
                if pos >= 0:
                    try:
                        lineno = int(location[pos + 1:])
                    except ValueError:
                        continue
                    self.locations.append((location[:pos], lineno))
                else:
                    self.locations.append((location, None))
        elif line[1:].startswith(','):
            for flag in line[2:].lstrip().split(','):
                self.flags.append(flag.strip())
        elif line[1:].startswith('.'):
            # These are called auto-comments
            comment = line[2:].strip()
            if comment:  # Just check that we're not adding empty comments
                self.auto_comments.append(comment)
        else:
            # These are called user comments
            self.user_comments.append(line[1:].strip())

    def parse(self, fileobj: IO[AnyStr] | Iterable[AnyStr]) -> None:
        """
        Reads from the file-like object `fileobj` and adds any po file
        units found in it to the `Catalog` supplied to the constructor.
        """

        for lineno, line in enumerate(fileobj):
            line = line.strip()
            if not isinstance(line, str):
                line = line.decode(self.catalog.charset)
            if not line:
                continue
            if line.startswith('#'):
                if line[1:].startswith('~'):
                    self._process_message_line(lineno, line[2:].lstrip(), obsolete=True)
                else:
                    try:
                        self._process_comment(line)
                    except ValueError as exc:
                        self._invalid_pofile(line, lineno, str(exc))
            else:
                self._process_message_line(lineno, line)

        self._finish_current_message()

        # No actual messages found, but there was some info in comments, from which
        # we'll construct an empty header message
        if not self.counter and (self.flags or self.user_comments or self.auto_comments):
            self.messages.append(_NormalizedString('""'))
            self.translations.append([0, _NormalizedString('""')])
            self._add_message()

    def _invalid_pofile(self, line, lineno, msg) -> None:
        assert isinstance(line, str)
        if self.abort_invalid:
            raise PoFileError(msg, self.catalog, line, lineno)
        print("WARNING:", msg)
        print(f"WARNING: Problem on line {lineno + 1}: {line!r}")


def read_po(
    fileobj: IO[AnyStr] | Iterable[AnyStr],
    locale: Locale | str | None = None,
    domain: str | None = None,
    ignore_obsolete: bool = False,
    charset: str | None = None,
    abort_invalid: bool = False,
) -> Catalog:
    """Read messages from a ``gettext`` PO (portable object) file from the given
    file-like object (or an iterable of lines) and return a `Catalog`.

    >>> from datetime import datetime
    >>> from io import StringIO
    >>> buf = StringIO('''
    ... #: main.py:1
    ... #, fuzzy, python-format
    ... msgid "foo %(name)s"
    ... msgstr "quux %(name)s"
    ...
    ... # A user comment
    ... #. An auto comment
    ... #: main.py:3
    ... msgid "bar"
    ... msgid_plural "baz"
    ... msgstr[0] "bar"
    ... msgstr[1] "baaz"
    ... ''')
    >>> catalog = read_po(buf)
    >>> catalog.revision_date = datetime(2007, 4, 1)

    >>> for message in catalog:
    ...     if message.id:
    ...         print((message.id, message.string))
    ...         print(' ', (message.locations, sorted(list(message.flags))))
    ...         print(' ', (message.user_comments, message.auto_comments))
    (u'foo %(name)s', u'quux %(name)s')
      ([(u'main.py', 1)], [u'fuzzy', u'python-format'])
      ([], [])
    ((u'bar', u'baz'), (u'bar', u'baaz'))
      ([(u'main.py', 3)], [])
      ([u'A user comment'], [u'An auto comment'])

    .. versionadded:: 1.0
       Added support for explicit charset argument.

    :param fileobj: the file-like object (or iterable of lines) to read the PO file from
    :param locale: the locale identifier or `Locale` object, or `None`
                   if the catalog is not bound to a locale (which basically
                   means it's a template)
    :param domain: the message domain
    :param ignore_obsolete: whether to ignore obsolete messages in the input
    :param charset: the character set of the catalog.
    :param abort_invalid: abort read if po file is invalid
    """
    catalog = Catalog(locale=locale, domain=domain, charset=charset)
    parser = PoFileParser(catalog, ignore_obsolete, abort_invalid=abort_invalid)
    parser.parse(fileobj)
    return catalog


WORD_SEP = re.compile('('
                      r'\s+|'                                 # any whitespace
                      r'[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|'  # hyphenated words
                      r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w)'   # em-dash
                      ')')


def escape(string: str) -> str:
    r"""Escape the given string so that it can be included in double-quoted
    strings in ``PO`` files.

    >>> escape('''Say:
    ...   "hello, world!"
    ... ''')
    '"Say:\\n  \\"hello, world!\\"\\n"'

    :param string: the string to escape
    """
    return '"%s"' % string.replace('\\', '\\\\') \
                          .replace('\t', '\\t') \
                          .replace('\r', '\\r') \
                          .replace('\n', '\\n') \
                          .replace('\"', '\\"')


def normalize(string: str, prefix: str = '', width: int = 76) -> str:
    r"""Convert a string into a format that is appropriate for .po files.

    >>> print(normalize('''Say:
    ...   "hello, world!"
    ... ''', width=None))
    ""
    "Say:\n"
    "  \"hello, world!\"\n"

    >>> print(normalize('''Say:
    ...   "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
    ... ''', width=32))
    ""
    "Say:\n"
    "  \"Lorem ipsum dolor sit "
    "amet, consectetur adipisicing"
    " elit, \"\n"

    :param string: the string to normalize
    :param prefix: a string that should be prepended to every line
    :param width: the maximum line width; use `None`, 0, or a negative number
                  to completely disable line wrapping
    """
    if width and width > 0:
        prefixlen = len(prefix)
        lines = []
        for line in string.splitlines(True):
            if len(escape(line)) + prefixlen > width:
                chunks = WORD_SEP.split(line)
                chunks.reverse()
                while chunks:
                    buf = []
                    size = 2
                    while chunks:
                        length = len(escape(chunks[-1])) - 2 + prefixlen
                        if size + length < width:
                            buf.append(chunks.pop())
                            size += length
                        else:
                            if not buf:
                                # handle long chunks by putting them on a
                                # separate line
                                buf.append(chunks.pop())
                            break
                    lines.append(''.join(buf))
            else:
                lines.append(line)
    else:
        lines = string.splitlines(True)

    if len(lines) <= 1:
        return escape(string)

    # Remove empty trailing line
    if lines and not lines[-1]:
        del lines[-1]
        lines[-1] += '\n'
    return '""\n' + '\n'.join([(prefix + escape(line)) for line in lines])


def _enclose_filename_if_necessary(filename: str) -> str:
    """Enclose filenames which include white spaces or tabs.

    Do the same as gettext and enclose filenames which contain white
    spaces or tabs with First Strong Isolate (U+2068) and Pop
    Directional Isolate (U+2069).
    """
    if " " not in filename and "\t" not in filename:
        return filename

    if not filename.startswith("\u2068"):
        filename = "\u2068" + filename
    if not filename.endswith("\u2069"):
        filename += "\u2069"
    return filename


def write_po(
    fileobj: SupportsWrite[bytes],
    catalog: Catalog,
    width: int = 76,
    no_location: bool = False,
    omit_header: bool = False,
    sort_output: bool = False,
    sort_by_file: bool = False,
    ignore_obsolete: bool = False,
    include_previous: bool = False,
    include_lineno: bool = True,
) -> None:
    r"""Write a ``gettext`` PO (portable object) template file for a given
    message catalog to the provided file-like object.

    >>> catalog = Catalog()
    >>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
    ...             flags=('fuzzy',))
    <Message...>
    >>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
    <Message...>
    >>> from io import BytesIO
    >>> buf = BytesIO()
    >>> write_po(buf, catalog, omit_header=True)
    >>> print(buf.getvalue().decode("utf8"))
    #: main.py:1
    #, fuzzy, python-format
    msgid "foo %(name)s"
    msgstr ""
    <BLANKLINE>
    #: main.py:3
    msgid "bar"
    msgid_plural "baz"
    msgstr[0] ""
    msgstr[1] ""
    <BLANKLINE>
    <BLANKLINE>

    :param fileobj: the file-like object to write to
    :param catalog: the `Catalog` instance
    :param width: the maximum line width for the generated output; use `None`,
                  0, or a negative number to completely disable line wrapping
    :param no_location: do not emit a location comment for every message
    :param omit_header: do not include the ``msgid ""`` entry at the top of the
                        output
    :param sort_output: whether to sort the messages in the output by msgid
    :param sort_by_file: whether to sort the messages in the output by their
                         locations
    :param ignore_obsolete: whether to ignore obsolete messages and not include
                            them in the output; by default they are included as
                            comments
    :param include_previous: include the old msgid as a comment when
                             updating the catalog
    :param include_lineno: include line number in the location comment
    """

    sort_by = None
    if sort_output:
        sort_by = "message"
    elif sort_by_file:
        sort_by = "location"

    for line in generate_po(
        catalog,
        ignore_obsolete=ignore_obsolete,
        include_lineno=include_lineno,
        include_previous=include_previous,
        no_location=no_location,
        omit_header=omit_header,
        sort_by=sort_by,
        width=width,
    ):
        if isinstance(line, str):
            line = line.encode(catalog.charset, 'backslashreplace')
        fileobj.write(line)


def generate_po(
    catalog: Catalog,
    *,
    ignore_obsolete: bool = False,
    include_lineno: bool = True,
    include_previous: bool = False,
    no_location: bool = False,
    omit_header: bool = False,
    sort_by: Literal["message", "location"] | None = None,
    width: int = 76,
) -> Iterable[str]:
    r"""Yield text strings representing a ``gettext`` PO (portable object) file.

    See `write_po()` for a more detailed description.
    """
    # xgettext always wraps comments even if --no-wrap is passed;
    # provide the same behaviour
    comment_width = width if width and width > 0 else 76

    comment_wrapper = TextWrapper(width=comment_width, break_long_words=False)
    header_wrapper = TextWrapper(width=width, subsequent_indent="# ", break_long_words=False)

    def _format_comment(comment, prefix=''):
        for line in comment_wrapper.wrap(comment):
            yield f"#{prefix} {line.strip()}\n"

    def _format_message(message, prefix=''):
        if isinstance(message.id, (list, tuple)):
            if message.context:
                yield f"{prefix}msgctxt {normalize(message.context, prefix=prefix, width=width)}\n"
            yield f"{prefix}msgid {normalize(message.id[0], prefix=prefix, width=width)}\n"
            yield f"{prefix}msgid_plural {normalize(message.id[1], prefix=prefix, width=width)}\n"

            for idx in range(catalog.num_plurals):
                try:
                    string = message.string[idx]
                except IndexError:
                    string = ''
                yield f"{prefix}msgstr[{idx:d}] {normalize(string, prefix=prefix, width=width)}\n"
        else:
            if message.context:
                yield f"{prefix}msgctxt {normalize(message.context, prefix=prefix, width=width)}\n"
            yield f"{prefix}msgid {normalize(message.id, prefix=prefix, width=width)}\n"
            yield f"{prefix}msgstr {normalize(message.string or '', prefix=prefix, width=width)}\n"

    for message in _sort_messages(catalog, sort_by=sort_by):
        if not message.id:  # This is the header "message"
            if omit_header:
                continue
            comment_header = catalog.header_comment
            if width and width > 0:
                lines = []
                for line in comment_header.splitlines():
                    lines += header_wrapper.wrap(line)
                comment_header = '\n'.join(lines)
            yield f"{comment_header}\n"

        for comment in message.user_comments:
            yield from _format_comment(comment)
        for comment in message.auto_comments:
            yield from _format_comment(comment, prefix='.')

        if not no_location:
            locs = []

            # sort locations by filename and lineno.
            # if there's no <int> as lineno, use `-1`.
            # if no sorting possible, leave unsorted.
            # (see issue #606)
            try:
                locations = sorted(message.locations,
                                   key=lambda x: (x[0], isinstance(x[1], int) and x[1] or -1))
            except TypeError:  # e.g. "TypeError: unorderable types: NoneType() < int()"
                locations = message.locations

            for filename, lineno in locations:
                location = filename.replace(os.sep, '/')
                location = _enclose_filename_if_necessary(location)
                if lineno and include_lineno:
                    location = f"{location}:{lineno:d}"
                if location not in locs:
                    locs.append(location)
            yield from _format_comment(' '.join(locs), prefix=':')
        if message.flags:
            yield f"#{', '.join(['', *sorted(message.flags)])}\n"

        if message.previous_id and include_previous:
            yield from _format_comment(
                f'msgid {normalize(message.previous_id[0], width=width)}',
                prefix='|',
            )
            if len(message.previous_id) > 1:
                norm_previous_id = normalize(message.previous_id[1], width=width)
                yield from _format_comment(f'msgid_plural {norm_previous_id}', prefix='|')

        yield from _format_message(message)
        yield '\n'

    if not ignore_obsolete:
        for message in _sort_messages(
            catalog.obsolete.values(),
            sort_by=sort_by,
        ):
            for comment in message.user_comments:
                yield from _format_comment(comment)
            yield from _format_message(message, prefix='#~ ')
            yield '\n'


def _sort_messages(messages: Iterable[Message], sort_by: Literal["message", "location"] | None) -> list[Message]:
    """
    Sort the given message iterable by the given criteria.

    Always returns a list.

    :param messages: An iterable of Messages.
    :param sort_by: Sort by which criteria? Options are `message` and `location`.
    :return: list[Message]
    """
    messages = list(messages)
    if sort_by == "message":
        messages.sort()
    elif sort_by == "location":
        messages.sort(key=lambda m: m.locations)
    return messages

Filemanager

Name Type Size Permission Actions
__pycache__ Folder 0755
__init__.py File 349 B 0644
_compat.py File 1.14 KB 0644
catalog.py File 36.92 KB 0644
checkers.py File 6.07 KB 0644
extract.py File 33.47 KB 0644
frontend.py File 44.34 KB 0644
jslexer.py File 7.04 KB 0644
mofile.py File 7.09 KB 0644
plurals.py File 7.32 KB 0644
pofile.py File 25.35 KB 0644
setuptools_frontend.py File 3.4 KB 0644
Filemanager