CodePrinter.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Sources/SwiftProtobufPluginLibrary/CodePrinter.swift - Code output
  2. //
  3. // Copyright (c) 2014 - 2016 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. ///
  11. /// This provides some basic indentation management for emitting structured
  12. /// source code text.
  13. ///
  14. // -----------------------------------------------------------------------------
  15. /// Prints code with automatic indentation based on calls to `indent` and
  16. /// `outdent`.
  17. public struct CodePrinter {
  18. /// Reserve an initial buffer of 64KB scalars to eliminate some reallocations
  19. /// in smaller files.
  20. private static let initialBufferSize = 65536
  21. private static let kNewline: String.UnicodeScalarView.Element = "\n"
  22. /// The string content that was printed.
  23. public var content: String {
  24. String(contentScalars)
  25. }
  26. /// See if anything was printed.
  27. public var isEmpty: Bool { contentScalars.isEmpty }
  28. /// The Unicode scalar buffer used to build up the printed contents.
  29. private var contentScalars = String.UnicodeScalarView()
  30. /// The `UnicodeScalarView` representing a single indentation step.
  31. private let singleIndent: String.UnicodeScalarView
  32. /// The current indentation level (a collection of spaces).
  33. private var indentation = String.UnicodeScalarView()
  34. /// Keeps track of whether the printer is currently sitting at the beginning
  35. /// of a line.
  36. private var atLineStart = true
  37. /// Keeps track of if a newline should be added after each string to the
  38. /// print apis.
  39. private let newlines: Bool
  40. public init(indent: String.UnicodeScalarView = " ".unicodeScalars) {
  41. contentScalars.reserveCapacity(CodePrinter.initialBufferSize)
  42. singleIndent = indent
  43. newlines = false
  44. }
  45. /// Initialize the printer for use.
  46. ///
  47. /// - Parameters:
  48. /// - indent: A string (usually spaces) to use for the indentation amount.
  49. /// - newlines: A boolean indicating if every `print` and `printIndented`
  50. /// should automatically add newlines to the end of the strings.
  51. public init(
  52. indent: String.UnicodeScalarView = " ".unicodeScalars,
  53. addNewlines newlines: Bool
  54. ) {
  55. contentScalars.reserveCapacity(CodePrinter.initialBufferSize)
  56. singleIndent = indent
  57. self.newlines = newlines
  58. }
  59. /// Initialize a new printer using the existing state from another printer.
  60. ///
  61. /// This can be useful to use with generation subtasks, so see if they
  62. /// actually generate something (via `isEmpty`) to then optionally add it
  63. /// back into the parent with whatever surounding content.
  64. ///
  65. /// This is most useful to then use `append` to add the new content.
  66. ///
  67. /// - Parameter parent: The other printer to copy the configuration/state
  68. /// from.
  69. public init(_ parent: CodePrinter) {
  70. self.init(parent, addNewlines: parent.newlines)
  71. }
  72. /// Initialize a new printer using the existing state from another printer
  73. /// but with support to control the behavior of `addNewlines`.
  74. ///
  75. /// This can be useful to use with generation subtasks, so see if they
  76. /// actually generate something (via `isEmpty`) to then optionally add it
  77. /// back into the parent with whatever surounding content.
  78. ///
  79. /// This is most useful to then use `append` to add the new content.
  80. ///
  81. /// - Parameters:
  82. /// - parent: The other printer to copy the configuration/state
  83. /// from.
  84. /// - newlines: A boolean indicating if every `print` and `printIndented`
  85. /// should automatically add newlines to the end of the strings.
  86. public init(_ parent: CodePrinter, addNewlines newlines: Bool) {
  87. self.init(indent: parent.singleIndent, addNewlines: newlines)
  88. indentation = parent.indentation
  89. }
  90. /// Writes the given strings to the printer, adding a newline after each
  91. /// string.
  92. ///
  93. /// Newlines within the strings are honored and indentention is applied.
  94. ///
  95. /// The `addNewlines` value from initializing the printer controls if
  96. /// newlines are appended after each string.
  97. ///
  98. /// If called with no strings, a blank line is added to the printer
  99. /// (even is `addNewlines` was false at initialization of the printer.
  100. ///
  101. /// - Parameter text: A variable-length list of strings to be printed.
  102. public mutating func print(_ text: String...) {
  103. if text.isEmpty {
  104. contentScalars.append(CodePrinter.kNewline)
  105. atLineStart = true
  106. } else {
  107. for t in text {
  108. printInternal(t.unicodeScalars, addNewline: newlines)
  109. }
  110. }
  111. }
  112. /// Writes the given strings to the printer, optionally adding a newline
  113. /// after each string. If called with no strings, a blank line is added to
  114. /// the printer.
  115. ///
  116. /// Newlines within the strings are honored and indentention is applied.
  117. ///
  118. /// - Parameters
  119. /// - text: A variable-length list of strings to be printed.
  120. /// - newlines: Boolean to control adding newlines after each string. This
  121. /// is an explicit override of the `addNewlines` value using to
  122. /// initialize this `CodePrinter`.
  123. public mutating func print(_ text: String..., newlines: Bool) {
  124. if text.isEmpty {
  125. assert(
  126. newlines,
  127. "Disabling newlines with no strings doesn't make sense."
  128. )
  129. contentScalars.append(CodePrinter.kNewline)
  130. atLineStart = true
  131. } else {
  132. for t in text {
  133. printInternal(t.unicodeScalars, addNewline: newlines)
  134. }
  135. }
  136. }
  137. /// Indents, writes the given strings to the printer, and then outdents.
  138. ///
  139. /// Newlines within the strings are honored and indentention is applied.
  140. ///
  141. /// The `addNewlines` value from initializing the printer controls if
  142. /// newlines are appended after each string.
  143. ///
  144. /// - Parameter text: A variable-length list of strings to be printed.
  145. public mutating func printIndented(_ text: String...) {
  146. indent()
  147. for t in text {
  148. printInternal(t.unicodeScalars, addNewline: newlines)
  149. }
  150. outdent()
  151. }
  152. private mutating func printInternal(
  153. _ scalars: String.UnicodeScalarView,
  154. addNewline: Bool
  155. ) {
  156. for scalar in scalars {
  157. // Indent at the start of a new line, unless it's a blank line.
  158. if atLineStart && scalar != CodePrinter.kNewline {
  159. contentScalars.append(contentsOf: indentation)
  160. }
  161. contentScalars.append(scalar)
  162. atLineStart = (scalar == CodePrinter.kNewline)
  163. }
  164. if addNewline {
  165. contentScalars.append(CodePrinter.kNewline)
  166. atLineStart = true
  167. }
  168. }
  169. /// Appended the content of another `CodePrinter`to this one.
  170. ///
  171. /// - Parameters:
  172. /// - printer: The other `CodePrinter` to copy from.
  173. /// - indenting: Boolean, if the text being appended should be reindented
  174. /// to the current state of this printer. If the `printer` was
  175. /// initialized off of this printer, there isn't a need to reindent.
  176. public mutating func append(_ printer: CodePrinter, indenting: Bool = false) {
  177. if indenting {
  178. printInternal(printer.contentScalars, addNewline: false)
  179. } else {
  180. contentScalars.append(contentsOf: printer.contentScalars)
  181. atLineStart = printer.atLineStart
  182. }
  183. }
  184. /// Increases the printer's indentation level.
  185. public mutating func indent() {
  186. indentation.append(contentsOf: singleIndent)
  187. }
  188. /// Decreases the printer's indentation level.
  189. ///
  190. /// - Precondition: The printer must not have an indentation level.
  191. public mutating func outdent() {
  192. let indentCount = singleIndent.count
  193. precondition(indentation.count >= indentCount, "Cannot outdent past the left margin")
  194. indentation.removeLast(indentCount)
  195. }
  196. /// Indents, calls `body` to do other work relaying along the printer, and
  197. /// the outdents after wards.
  198. ///
  199. /// - Parameter body: A closure that is invoked after the indent is
  200. /// increasted.
  201. public mutating func withIndentation(body: (_ p: inout CodePrinter) -> Void) {
  202. indent()
  203. body(&self)
  204. outdent()
  205. }
  206. }