1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
fileprivate enum SnippetVisibility {
case shown
case hidden
}
fileprivate extension StringProtocol {
/// If the string is a line comment, attempt to parse
/// a ``SnippetVisibility`` with `mark: show` or `mark: hide`.
var parsedVisibilityMark: SnippetVisibility? {
guard var comment = parsedLineCommentText else {
return nil
}
comment = comment.drop { $0.isWhitespace }
if comment.lowercased().starts(with: "mark: show") {
return SnippetVisibility.shown
} else if comment.lowercased().starts(with: "mark: hide") {
return SnippetVisibility.hidden
} else {
return nil
}
}
/// If the string is a line comment starting with `"//"`, return the
/// contents with the comment marker stripped.
var parsedLineCommentText: Self.SubSequence? {
var trimmed = self.drop { $0.isWhitespace }
guard trimmed.starts(with: "//") else {
return nil
}
trimmed.removeFirst(2)
return trimmed
}
var isEmptyOrWhiteSpace: Bool {
return self.isEmpty || self.allSatisfy { $0.isWhitespace }
}
}
fileprivate extension String {
mutating func removeLeadingAndTrailingNewlines() {
while self.starts(with: "\n") {
self.removeFirst(1)
}
while self.suffix(1) == "\n" {
self.removeLast(1)
}
}
/// Returns a re-indented string with the most indentation removed
/// without changing the relative indentation between lines. This is
/// useful for re-indenting some inner part of a block of nested code.
mutating func trimExtraIndentation() {
var lines = self.split(separator: "\n", maxSplits: Int.max,
omittingEmptySubsequences: false)
lines = Array(lines
.drop(while: { $0.isEmptyOrWhiteSpace })
.reversed()
.drop(while: { $0.isEmptyOrWhiteSpace })
.reversed())
let minimumIndentation = lines.map {
guard !$0.isEmpty else {
return Int.max
}
return $0.prefix { $0 == " " }.count
}.min() ?? 0
guard minimumIndentation > 0 else {
return
}
self = lines.map { $0.dropFirst(minimumIndentation) }
.joined(separator: "\n")
}
}
/// Extracts a ``Snippet`` structure from Swift source code.
///
/// - todo: In order to support different styles of comments, it might be
/// better to adopt SwiftSyntax if possible in the future.
struct PlainTextSnippetExtractor {
var source: String
var explanation = ""
var presentationCode = ""
private var currentVisibility = SnippetVisibility.shown
init(source: String) {
self.source = source
let lines = source.split(separator: "\n", omittingEmptySubsequences: false)
var lastExplanationLine = "..."
var lastPresentationCodeLine = "..."
for line in lines {
if let visibility = line.parsedVisibilityMark {
self.currentVisibility = visibility
continue
}
guard case .shown = currentVisibility else {
continue
}
if var comment = line.parsedLineCommentText,
comment.starts(with: "!") {
comment.removeFirst(1)
comment = comment.drop { $0.isWhitespace }
if lastExplanationLine.isEmptyOrWhiteSpace && comment.isEmptyOrWhiteSpace {
continue
}
print(comment, to: &explanation)
lastExplanationLine = String(comment)
} else {
if lastPresentationCodeLine.isEmptyOrWhiteSpace && line.isEmptyOrWhiteSpace {
continue
}
print(line, to: &presentationCode)
lastPresentationCodeLine = String(line)
}
}
self.explanation
.removeLeadingAndTrailingNewlines()
self.presentationCode
.removeLeadingAndTrailingNewlines()
self.presentationCode
.trimExtraIndentation()
}
}
|