├── .github └── FUNDING.yml ├── .gitignore ├── .spi.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Example └── ExampleSwiftUI.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── SwiftSummarize │ ├── Documentation.docc │ └── SwiftSummarize.md │ └── SwiftSummarize.swift ├── Tests └── SwiftSummarizeTests │ └── SwiftSummarizeTests.swift └── preview.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: StefKors 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [SwiftSummarize] 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/ExampleSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // 4 | // Created by Stef Kors on 17/10/2023. 5 | // 6 | 7 | import SwiftUI 8 | import CoreServices 9 | 10 | /// Wraps CoreServices SKSummary 11 | struct Summary: Codable, Equatable { 12 | let input: String 13 | let sentenceCount: Int 14 | let paragraphCount: Int 15 | 16 | let output: String 17 | let numSentencesInSummary: Int 18 | 19 | /// Create summary from input 20 | /// - Parameters: 21 | /// - input: Full lengh text 22 | /// - numberOfSentences: Size of summary by number of sentences 23 | init(_ input: String, numberOfSentences: Int) { 24 | self.input = input 25 | self.numSentencesInSummary = numberOfSentences 26 | let summary = SKSummaryCreateWithString(input as CFString).takeRetainedValue() 27 | self.sentenceCount = SKSummaryGetSentenceCount(summary) 28 | self.paragraphCount = SKSummaryGetParagraphCount(summary) 29 | 30 | // Check number of sentences in output to avoid crashing... 31 | if sentenceCount > 0 { 32 | self.output = SKSummaryCopySentenceSummaryString(summary, numSentencesInSummary).takeRetainedValue() as String 33 | } else { 34 | self.output = input 35 | } 36 | } 37 | 38 | /// Create summary from input 39 | /// - Parameters: 40 | /// - input: Full length text 41 | /// - percent: Size of the summary in percent of input text 42 | init(_ input: String, percent: CGFloat) { 43 | self.input = input 44 | let summary = SKSummaryCreateWithString(input as CFString).takeRetainedValue() 45 | self.sentenceCount = SKSummaryGetSentenceCount(summary) 46 | print(percent.description) 47 | self.numSentencesInSummary = Int(max(1, ((CGFloat(sentenceCount)/100)*percent))) 48 | self.paragraphCount = SKSummaryGetParagraphCount(summary) 49 | print(numSentencesInSummary.description) 50 | 51 | // Check number of sentences in output to avoid crashing... 52 | if sentenceCount > 0 { 53 | self.output = SKSummaryCopySentenceSummaryString(summary, numSentencesInSummary).takeRetainedValue() as String 54 | } else { 55 | self.output = input 56 | } 57 | } 58 | } 59 | 60 | extension String { 61 | /// Uses Summary type to generate summary from string 62 | /// - Parameter numberOfSentences: Size of summary by number of sentences 63 | /// - Returns: Summarized output string 64 | func summarize(numberOfSentences: Int) -> String { 65 | Summary(self, numberOfSentences: numberOfSentences).output 66 | } 67 | 68 | /// Uses Summary type to generate summary from string 69 | /// - Parameter percent: Size of the summary in percent of input text 70 | /// - Returns: Summarized output string 71 | func summarize(percent: CGFloat) -> String { 72 | Summary(self, percent: percent).output 73 | } 74 | } 75 | 76 | struct AfterView: View { 77 | let text: String 78 | 79 | @State private var result: String? 80 | @State private var percent: CGFloat = 1 81 | 82 | var summary: Summary { 83 | Summary(text, percent: percent) 84 | } 85 | 86 | var body: some View { 87 | VStack(alignment: .leading) { 88 | Text(text.summarize(percent: percent)) 89 | .textSelection(.enabled) 90 | Spacer() 91 | 92 | Text("\(summary.numSentencesInSummary.description) Sentences") 93 | .foregroundStyle(.secondary) 94 | .font(.caption) 95 | 96 | Slider( 97 | value: $percent, 98 | in: 1...100, 99 | step: 5, 100 | label: {}, 101 | minimumValueLabel: { 102 | Text("1%") 103 | }, maximumValueLabel: { 104 | Text("100%") 105 | }) 106 | } 107 | .padding() 108 | } 109 | 110 | } 111 | 112 | struct BeforeView: View { 113 | @Binding var text: String 114 | var body: some View { 115 | VStack { 116 | TextEditor(text: $text) 117 | .textEditorStyle(.plain) 118 | .font(.body) 119 | } 120 | .padding() 121 | } 122 | } 123 | 124 | struct ContentView: View { 125 | @State private var text: String = """ 126 | Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They push the human race forward. And while some may see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do. 127 | """ 128 | var body: some View { 129 | VStack { 130 | HStack(content: { 131 | BeforeView(text: $text) 132 | Divider() 133 | AfterView(text: text) 134 | }) 135 | } 136 | .padding() 137 | } 138 | } 139 | 140 | #Preview { 141 | ContentView() 142 | } 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Stef Kors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftSummarize", 8 | platforms: [.macOS(.v10_13)], 9 | products: [ 10 | .library( 11 | name: "SwiftSummarize", 12 | targets: ["SwiftSummarize"]), 13 | ], 14 | targets: [ 15 | .target( 16 | name: "SwiftSummarize", 17 | exclude: ["ExampleSwiftUI.swift"], 18 | resources: [.process("Documentation.docc")] 19 | ), 20 | .testTarget( 21 | name: "SwiftSummarizeTests", 22 | dependencies: ["SwiftSummarize"]), 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftSummarize 2 | 3 | SwiftSummarize is the easiest way to create a summary from a String. Internally it's a simple wrapper around CoreServices [SKSummary](https://developer.apple.com/documentation/coreservices/1446229-sksummarycreatewithstring) 4 | 5 | **Before** 6 | > Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They push the human race forward. And while some may see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do. 7 | 8 | **After** 9 | > Because the people who are crazy enough to think they can change the world, are the ones who do 10 | ## Install 11 | 12 | Add this url to your dependencies: 13 | 14 | ``` 15 | https://github.com/StefKors/SwiftSummarize 16 | ``` 17 | 18 | ## Example 19 | 20 | ```Swift 21 | let input = """ 22 | Here's to the crazy ones. The misfits. The rebels. The troublemakers. The 23 | round pegs in the square holes. The ones who see things differently. They're not 24 | fond of rules. And they have no respect for the status quo. You can quote them, 25 | disagree with them, glorify or vilify them. About the only thing you can't do is ignore 26 | them. Because they change things. They push the human race forward. And while some 27 | may see them as the crazy ones, we see genius. Because the people who are crazy 28 | enough to think they can change the world, are the ones who do. 29 | """ 30 | 31 | let summary = Summary(text, numberOfSentences: 1) 32 | 33 | print(summary.output) 34 | // Because the people who are crazy enough to think they can change the world, are the ones who do 35 | ``` 36 | Or use it directly on Strings with the extension 37 | ```Swift 38 | let input = """ 39 | Here's to the crazy ones. The misfits. The rebels. The troublemakers. The 40 | round pegs in the square holes. The ones who see things differently. They're not 41 | fond of rules. And they have no respect for the status quo. You can quote them, 42 | disagree with them, glorify or vilify them. About the only thing you can't do is ignore 43 | them. Because they change things. They push the human race forward. And while some 44 | may see them as the crazy ones, we see genius. Because the people who are crazy 45 | enough to think they can change the world, are the ones who do. 46 | """ 47 | 48 | let output = input.summarize(numberOfSentences: 1) 49 | 50 | print(output) 51 | // Because the people who are crazy enough to think they can change the world, are the ones who do 52 | ``` 53 | 54 | A full SwiftUI code example can be found at [/Example/ExampleSwiftUI.swift](https://github.com/StefKors/SwiftSummarize/blob/main/Example/ExampleSwiftUI.swift) 55 | 56 | ![preview](https://github.com/StefKors/SwiftSummarize/assets/11800807/c3000422-0dbe-41f4-9cc4-7b85afec087f) 57 | 58 | -------------------------------------------------------------------------------- /Sources/SwiftSummarize/Documentation.docc/SwiftSummarize.md: -------------------------------------------------------------------------------- 1 | # ``SwiftSummarize`` 2 | 3 | SwiftSummarize is the easiest way to create a summary from a String. Internally it's a simple wrapper around CoreServices [SKSummary](https://developer.apple.com/documentation/coreservices/1446229-sksummarycreatewithstring) 4 | 5 | ## Overview 6 | 7 | **Before** 8 | > Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They push the human race forward. And while some may see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do. 9 | 10 | **After** 11 | > Because the people who are crazy enough to think they can change the world, are the ones who do 12 | 13 | 14 | ## Topics 15 | 16 | ### Summary struct 17 | 18 | - ``Summary`` 19 | 20 | ### String Extensions 21 | 22 | - ``summarize(numberOfSentences: Int)`` 23 | - ``summarize(percent: CGFloat)`` 24 | -------------------------------------------------------------------------------- /Sources/SwiftSummarize/SwiftSummarize.swift: -------------------------------------------------------------------------------- 1 | import CoreServices 2 | import Foundation 3 | 4 | /// Wraps CoreServices SKSummary 5 | public struct Summary: Codable, Equatable { 6 | public let input: String 7 | public let sentenceCount: Int 8 | public let paragraphCount: Int 9 | 10 | public let output: String 11 | public let numSentencesInSummary: Int 12 | 13 | /// Create summary from input 14 | /// - Parameters: 15 | /// - input: Full lengh text 16 | /// - numberOfSentences: Size of summary by number of sentences 17 | public init(_ input: String, numberOfSentences: Int) { 18 | self.input = input 19 | self.numSentencesInSummary = numberOfSentences 20 | let summary = SKSummaryCreateWithString(input as CFString).takeRetainedValue() 21 | self.sentenceCount = SKSummaryGetSentenceCount(summary) 22 | self.paragraphCount = SKSummaryGetParagraphCount(summary) 23 | 24 | // Check number of sentences in output to avoid crashing... 25 | if sentenceCount > 0 { 26 | self.output = SKSummaryCopySentenceSummaryString(summary, numSentencesInSummary).takeRetainedValue() as String 27 | } else { 28 | self.output = input 29 | } 30 | } 31 | 32 | /// Create summary from input 33 | /// - Parameters: 34 | /// - input: Full length text 35 | /// - percent: Size of the summary in percent of input text 36 | public init(_ input: String, percent: CGFloat) { 37 | self.input = input 38 | let summary = SKSummaryCreateWithString(input as CFString).takeRetainedValue() 39 | self.sentenceCount = SKSummaryGetSentenceCount(summary) 40 | print(percent.description) 41 | self.numSentencesInSummary = Int(max(1, ((CGFloat(sentenceCount)/100)*percent))) 42 | self.paragraphCount = SKSummaryGetParagraphCount(summary) 43 | print(numSentencesInSummary.description) 44 | 45 | // Check number of sentences in output to avoid crashing... 46 | if sentenceCount > 0 { 47 | self.output = SKSummaryCopySentenceSummaryString(summary, numSentencesInSummary).takeRetainedValue() as String 48 | } else { 49 | self.output = input 50 | } 51 | } 52 | } 53 | 54 | public extension String { 55 | /// Uses Summary type to generate summary from string 56 | /// - Parameter numberOfSentences: Size of summary by number of sentences 57 | /// - Returns: Summarized output string 58 | func summarize(numberOfSentences: Int) -> String { 59 | Summary(self, numberOfSentences: numberOfSentences).output 60 | } 61 | 62 | /// Uses Summary type to generate summary from string 63 | /// - Parameter percent: Size of the summary in percent of input text 64 | /// - Returns: Summarized output string 65 | func summarize(percent: CGFloat) -> String { 66 | Summary(self, percent: percent).output 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/SwiftSummarizeTests/SwiftSummarizeTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftSummarize 3 | 4 | final class SwiftSummarizeTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefKors/SwiftSummarize/f01a2953c3f8ed6fe136890b26f5b301074c0c44/preview.png --------------------------------------------------------------------------------