# Copyright (C) 2018-present MongoDB, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the Server Side Public License, version 1, # as published by MongoDB, Inc. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Server Side Public License for more details. # # You should have received a copy of the Server Side Public License # along with this program. If not, see # . # # As a special exception, the copyright holders give permission to link the # code of portions of this program with the OpenSSL library under certain # conditions as described in each individual source file and distribute # linked combinations including the program with the OpenSSL library. You # must comply with the Server Side Public License in all respects for # all of the code used other than as permitted herein. If you modify file(s) # with this exception, you may extend this exception to your version of the # file(s), but you are not obligated to do so. If you do not wish to do so, # delete this exception statement from your version. If you delete this # exception statement from all source files in the program, then also delete # it in the license file. # """Text Writing Utilites.""" import io import string from typing import List, Mapping, Union from . import common # Number of spaces to indent code _INDENT_SPACE_COUNT = 4 def _fill_spaces(count): # type: (int) -> str """Fill a string full of spaces.""" fill = '' for _ in range(count * _INDENT_SPACE_COUNT): fill += ' ' return fill def _indent_text(count, unindented_text): # type: (int, str) -> str """Indent each line of a multi-line string.""" lines = unindented_text.splitlines() fill = _fill_spaces(count) return '\n'.join(fill + line for line in lines) def is_function(name): # type: (str) -> bool """ Return True if a serializer/deserializer is function. A function is prefixed with '::' so that the IDL generated code calls it as a function instead of as a class method. """ return name.startswith("::") def get_method_name(name): # type: (str) -> str """Get a method name from a fully qualified method name.""" pos = name.rfind('::') if pos == -1: return name return name[pos + 2:] def get_method_name_from_qualified_method_name(name): # type: (str) -> str # pylint: disable=invalid-name """Get a method name from a fully qualified method name.""" # TODO: in the future, we may want to support full-qualified calls to static methods # Strip the global prefix from enum functions if name.startswith("::"): name = name[2:] prefix = 'mongo::' pos = name.find(prefix) if pos == -1: return name return name[len(prefix):] class IndentedTextWriter(object): """ A simple class to manage writing indented lines of text. Supports both writing indented lines, and unindented lines. Use write_empty_line() instead of write_line('') to avoid lines full of blank spaces. """ def __init__(self, stream): # type: (io.StringIO) -> None """Create an indented text writer.""" self._stream = stream self._indent = 0 self._template_context = None # type: Mapping[str, str] def write_unindented_line(self, msg): # type: (str) -> None """Write an unindented line to the stream, no template formattin applied.""" self._stream.write(msg) self._stream.write("\n") def indent(self): # type: () -> None """Indent the text by one level.""" self._indent += 1 def unindent(self): # type: () -> None """Unindent the text by one level.""" assert self._indent > 0 self._indent -= 1 def write_line(self, msg): # type: (str) -> None """Write a line to the stream, no template formattin applied.""" self._stream.write(_indent_text(self._indent, msg)) self._stream.write("\n") def set_template_mapping(self, template_params): # type: (Mapping[str,str]) -> None """Set the current template mapping parameters for string.Template formatting.""" assert not self._template_context self._template_context = template_params def clear_template_mapping(self): # type: () -> None """Clear the current template mapping parameters for string.Template formatting.""" assert self._template_context self._template_context = None def write_template(self, template): # type: (str) -> None """Write a template to the stream.""" msg = common.template_format(template, self._template_context) self._stream.write(_indent_text(self._indent, msg)) self._stream.write("\n") def write_empty_line(self): # type: () -> None """Write a line to the stream.""" self._stream.write("\n") class TemplateContext(object): """Set the template context for the writer.""" def __init__(self, writer, template_params): # type: (IndentedTextWriter, Mapping[str,str]) -> None """Create a template context.""" self._writer = writer self._template_context = template_params def __enter__(self): # type: () -> None """Set the template mapping for the writer.""" self._writer.set_template_mapping(self._template_context) def __exit__(self, *args): # type: (*str) -> None """Clear the template mapping for the writer.""" self._writer.clear_template_mapping() class WriterBlock(object): """Interface for block types below.""" def __enter__(self): # type: () -> None """Open a block.""" pass def __exit__(self, *args): # type: (*str) -> None """Close the block.""" pass class EmptyBlock(WriterBlock): """Do not generate an indented block.""" def __init__(self): # type: () -> None """Create an empty block.""" pass def __enter__(self): # type: () -> None """Do nothing.""" pass def __exit__(self, *args): # type: (*str) -> None """Do nothing.""" pass class IndentedScopedBlock(WriterBlock): """Generate a block, template the parameters, and indent the contents.""" def __init__(self, writer, opening, closing): # type: (IndentedTextWriter, str, str) -> None """Create a block.""" self._writer = writer self._opening = opening self._closing = closing def __enter__(self): # type: () -> None """Write the beginning of the block and then indent.""" self._writer.write_template(self._opening) self._writer.indent() def __exit__(self, *args): # type: (*str) -> None """Unindent the block and print the ending.""" self._writer.unindent() self._writer.write_template(self._closing) class NamespaceScopeBlock(WriterBlock): """Generate an unindented blocks for a list of namespaces, and do not indent the contents.""" def __init__(self, indented_writer, namespaces): # type: (IndentedTextWriter, List[str]) -> None """Create a block.""" self._writer = indented_writer self._namespaces = namespaces def __enter__(self): # type: () -> None """Write the beginning of the block and do not indent.""" for namespace in self._namespaces: self._writer.write_unindented_line('namespace %s {' % (namespace)) def __exit__(self, *args): # type: (*str) -> None """Write the end of the block and do not change indentation.""" self._namespaces.reverse() for namespace in self._namespaces: self._writer.write_unindented_line('} // namespace %s' % (namespace)) class UnindentedBlock(WriterBlock): """Generate a block without indentation.""" def __init__(self, writer, opening, closing): # type: (IndentedTextWriter, str, str) -> None """Create a block.""" self._writer = writer self._opening = opening self._closing = closing def __enter__(self): # type: () -> None """Write the beginning of the block.""" self._writer.write_unindented_line(self._opening) def __exit__(self, *args): # type: (*str) -> None """Write the ending of the block.""" self._writer.write_unindented_line(self._closing) class MultiBlock(WriterBlock): """Proxy container for a list of WriterBlocks.""" def __init__(self, blocks): # type: (MultiBlock, List[WriterBlock]) -> None """Create a multi-block.""" self._blocks = blocks def __enter__(self): # type: () -> None """Enter each block forwards.""" for i in self._blocks: i.__enter__() def __exit__(self, *args): # type: (*str) -> None """And leave each block in reverse.""" for i in reversed(self._blocks): i.__exit__(*args)