Files
oai-swift/oAI/Utilities/Extensions/String+Extensions.swift
T
runeandClaude Sonnet 5 bd686873c4 Update commercial licensing contact URL to oai.pm
Consolidates the mac.oai.pm subdomain references (introduced in the
PolyForm Noncommercial relicense) to the root oai.pm domain, across
the LICENSE file, README, and all Swift source file headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:33:53 +02:00

111 lines
3.6 KiB
Swift

//
// String+Extensions.swift
// oAI
//
// String utility extensions
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling oAI or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://oai.pm>.
import Foundation
extension String {
// MARK: - Command Parsing
var isSlashCommand: Bool {
hasPrefix("/")
}
func parseCommand() -> (command: String, args: [String])? {
guard isSlashCommand else { return nil }
let parts = self.split(separator: " ", omittingEmptySubsequences: true)
.map(String.init)
guard let command = parts.first else { return nil }
let args = Array(parts.dropFirst())
return (command, args)
}
// MARK: - File Attachment Parsing
func parseFileAttachments() -> (cleanText: String, filePaths: [String]) {
var cleanText = self
var filePaths: [String] = []
// Pattern 1: @<filepath>
let anglePattern = #"@<([^>]+)>"#
if let regex = try? NSRegularExpression(pattern: anglePattern) {
let matches = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
for match in matches.reversed() {
if let range = Range(match.range(at: 1), in: self) {
let path = String(self[range])
filePaths.insert(path, at: 0)
}
if let fullRange = Range(match.range, in: self) {
cleanText.removeSubrange(fullRange)
}
}
}
// Pattern 2: @filepath (starting with /, ~, ., or drive letter)
let directPattern = #"@([~/.][\S]+|[A-Za-z]:[\\\/][\S]+)"#
if let regex = try? NSRegularExpression(pattern: directPattern) {
let matches = regex.matches(in: cleanText, range: NSRange(cleanText.startIndex..., in: cleanText))
for match in matches.reversed() {
if let range = Range(match.range(at: 1), in: cleanText) {
let path = String(cleanText[range])
if !filePaths.contains(path) {
filePaths.insert(path, at: 0)
}
}
if let fullRange = Range(match.range, in: cleanText) {
cleanText.removeSubrange(fullRange)
}
}
}
return (cleanText.trimmingCharacters(in: .whitespaces), filePaths)
}
// MARK: - Token Estimation
func estimateTokens() -> Int {
// Rough estimation: ~4 characters per token
// This is approximate; Phase 2 will use proper tokenizer
return max(1, count / 4)
}
// MARK: - Nil coalescing helpers
/// Returns nil if the string is empty, otherwise self.
var nonEmptyOrNil: String? {
isEmpty ? nil : self
}
// MARK: - Truncation
func truncated(to length: Int, trailing: String = "...") -> String {
if count <= length {
return self
}
let endIndex = index(startIndex, offsetBy: length - trailing.count)
return String(self[..<endIndex]) + trailing
}
}