builder.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. """Core of the fluent API used by **ConfigUpdater** to make editing configuration files
  2. easier.
  3. """
  4. from configparser import DuplicateOptionError, DuplicateSectionError
  5. from typing import TYPE_CHECKING, Optional, TypeVar, Union
  6. if TYPE_CHECKING:
  7. from .block import Block
  8. from .container import Container
  9. from .section import Section
  10. T = TypeVar("T", bound="BlockBuilder")
  11. class BlockBuilder:
  12. """Builder that injects blocks at a given index position."""
  13. def __init__(self, container: "Container", idx: int):
  14. self._container = container
  15. self._idx = idx
  16. def _insert(self: T, block: "Block") -> T:
  17. self._container.structure.insert(self._idx, block)
  18. self._idx += 1
  19. return self
  20. def comment(self: T, text: str, comment_prefix="#") -> T:
  21. """Creates a comment block
  22. Args:
  23. text (str): content of comment without #
  24. comment_prefix (str): character indicating start of comment
  25. Returns:
  26. self for chaining
  27. """
  28. from .block import Comment
  29. comment = Comment(self._container)
  30. if not text.startswith(comment_prefix):
  31. text = "{} {}".format(comment_prefix, text)
  32. if not text.endswith("\n"):
  33. text = "{}{}".format(text, "\n")
  34. return self._insert(comment.add_line(text))
  35. def section(self: T, section: Union[str, "Section"]) -> T:
  36. """Creates a section block
  37. Args:
  38. section (str or :class:`Section`): name of section or object
  39. Returns:
  40. self for chaining
  41. """
  42. from .document import Document
  43. from .section import Section
  44. container = self._container
  45. if not isinstance(container, Document):
  46. raise ValueError("Sections can only be added at section level!")
  47. if isinstance(section, str):
  48. # create a new section
  49. section = Section(section)
  50. elif not isinstance(section, Section):
  51. msg = "Parameter must be a string or Section type!"
  52. raise ValueError(msg, {"container": section})
  53. if container.has_section(section.name):
  54. raise DuplicateSectionError(section.name)
  55. section.attach(container)
  56. return self._insert(section)
  57. def space(self: T, newlines: int = 1) -> T:
  58. """Creates a vertical space of newlines
  59. Args:
  60. newlines (int): number of empty lines
  61. Returns:
  62. self for chaining
  63. """
  64. from .block import Space
  65. space = Space(container=self._container)
  66. for _ in range(newlines):
  67. space.add_line("\n")
  68. return self._insert(space)
  69. def option(self: T, key, value: Optional[str] = None, **kwargs) -> T:
  70. """Creates a new option inside a section
  71. Args:
  72. key (str): key of the option
  73. value (str or None): value of the option
  74. **kwargs: are passed to the constructor of :class:`Option`
  75. Returns:
  76. self for chaining
  77. """
  78. from .section import Section
  79. if not isinstance(self._container, Section):
  80. msg = "Options can only be added inside a section!"
  81. raise ValueError(msg, {"container": self._container})
  82. section = self._container
  83. option = section.create_option(key, value)
  84. if option.key in section.options():
  85. raise DuplicateOptionError(section.name, option.key)
  86. option.value = value
  87. return self._insert(option)