{"id":31241653,"url":"https://github.com/arraypress/swift-quicklink-parser","last_synced_at":"2025-09-23T00:13:32.606Z","repository":{"id":308924268,"uuid":"1034586424","full_name":"arraypress/swift-quicklink-parser","owner":"arraypress","description":"Parse and process dynamic URL templates with Raycast-compatible placeholder syntax","archived":false,"fork":false,"pushed_at":"2025-08-08T16:46:40.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-08T18:35:49.096Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arraypress.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-08T16:23:51.000Z","updated_at":"2025-08-08T16:46:43.000Z","dependencies_parsed_at":"2025-08-08T18:49:05.055Z","dependency_job_id":null,"html_url":"https://github.com/arraypress/swift-quicklink-parser","commit_stats":null,"previous_names":["arraypress/swift-quicklink-parser"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/arraypress/swift-quicklink-parser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arraypress%2Fswift-quicklink-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arraypress%2Fswift-quicklink-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arraypress%2Fswift-quicklink-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arraypress%2Fswift-quicklink-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arraypress","download_url":"https://codeload.github.com/arraypress/swift-quicklink-parser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arraypress%2Fswift-quicklink-parser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276493638,"owners_count":25652218,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-22T02:00:08.972Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-23T00:13:30.792Z","updated_at":"2025-09-23T00:13:32.579Z","avatar_url":"https://github.com/arraypress.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Swift QuickLink Parser\n\nA Swift package for parsing and processing dynamic URL templates with Raycast-compatible placeholder syntax.\n\n[![Swift 5.9+](https://img.shields.io/badge/Swift-5.9+-blue.svg)](https://swift.org)\n[![Platforms](https://img.shields.io/badge/Platforms-iOS%2013%20|%20macOS%2010.15%20|%20tvOS%2013%20|%20watchOS%206-lightgrey.svg)](https://swift.org)\n\n## Overview\n\nQuickLinkParser processes URL templates containing dynamic placeholders for user input, clipboard content, selected text, and date/time values. It supports Raycast's template syntax including modifier chains for transforming values.\n\n## Features\n\n- 🎯 **Full Raycast Compatibility** - Supports Raycast's template syntax\n- 📋 **System Integration** - Access clipboard and selected text (with permissions)\n- 📅 **Date/Time Support** - Custom formats and offsets\n- 🔧 **Modifier Chains** - Transform values with trim, encode, case conversion\n- 🏗️ **Template Analysis** - Extract requirements before processing\n- ✅ **Validation** - Check template syntax with detailed errors\n- 🚀 **Cross-Platform** - Works on iOS and macOS\n\n## Installation\n\n### Swift Package Manager\n\nAdd QuickLinkParser to your project in Xcode:\n\n1. **File → Add Package Dependencies**\n2. Enter the repository URL: `https://github.com/arraypress/swift-quicklink-parser`\n3. Choose the version and add to your target\n\nOr add it to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/arraypress/swift-quicklink-parser\", from: \"1.0.0\")\n]\n```\n\n## Quick Start\n\n```swift\nimport QuickLinkParser\n\n// Simple example\nlet template = \"https://google.com/search?q={selection | percent-encode}\"\nlet result = QuickLinkParser.process(\n    template,\n    selection: \"Swift programming\"\n)\n\nif result.success {\n    print(result.url) // \"https://google.com/search?q=Swift%20programming\"\n}\n\n// With system access (auto-detects clipboard/selection)\nlet result = QuickLinkParser.processWithSystemAccess(template)\n```\n\n## Supported Placeholder Syntax\n\n### User Arguments\n\n```swift\n{argument name=\"query\"}                      // Required argument\n{argument name=\"query\" default=\"search\"}     // Optional with default\n{argument name=\"lang\" options=\"en, es, fr\"}  // Simple dropdown options\n\n// Label|value syntax for user-friendly options\n{argument name=\"filter\" options=\"Videos|EgIQAQ%3D, Channels|EgIQAg%3D\"}\n```\n\nThe `options` attribute supports two formats:\n- **Simple**: `\"option1, option2, option3\"` - Label and value are the same\n- **Label|Value**: `\"Display Label|actual_value\"` - Different label and value\n\nThis is particularly useful for:\n- Encoded values (YouTube filters, API tokens)\n- Technical IDs with friendly names\n- URLs with descriptive labels\n\n### System Values\n\n```swift\n{clipboard}                                   // Current clipboard content\n{selection}                                   // Currently selected text (macOS only)\n```\n\n### Date/Time\n\n```swift\n{date}                                       // Current date (system format)\n{time}                                       // Current time\n{datetime}                                   // Date and time combined\n{date format=\"yyyy-MM-dd\"}                  // Custom format\n{date format=\"MMM d\" offset=\"+7d\"}          // 7 days from now\n{date offset=\"-1M\"}                         // 1 month ago\n```\n\n### Modifiers\n\n```swift\n{clipboard | percent-encode}                 // URL encode\n{selection | trim}                           // Remove whitespace\n{argument name=\"text\" | lowercase}          // Convert to lowercase\n{clipboard | trim | lowercase | percent-encode}  // Chain multiple\n```\n\nAvailable modifiers:\n- `percent-encode` - URL encoding for safe URLs\n- `trim` - Remove leading/trailing whitespace\n- `uppercase` - Convert to UPPERCASE\n- `lowercase` - Convert to lowercase\n- `json-stringify` - Escape for JSON strings\n\n## Core API\n\n### Processing Templates\n\n```swift\n// Manual values - you provide everything\nlet result = QuickLinkParser.process(\n    template,\n    arguments: [\"query\": \"test\", \"lang\": \"en\"],\n    clipboard: \"clipboard text\",\n    selection: \"selected text\",\n    date: Date()\n)\n\n// With system access - automatically gets clipboard/selection\nlet result = QuickLinkParser.processWithSystemAccess(\n    template,\n    arguments: [\"query\": \"test\"]\n)\n\n// Check the result\nif result.success {\n    // Use result.url\n    if let url = URL(string: result.url) {\n        UIApplication.shared.open(url)  // iOS\n        NSWorkspace.shared.open(url)    // macOS\n    }\n} else {\n    // Handle missing arguments or errors\n    print(\"Missing: \\(result.missingArguments)\")\n    print(\"Errors: \\(result.errors)\")\n}\n```\n\n### Analyzing Templates\n\n```swift\n// Get template requirements before processing\nlet info = QuickLinkParser.analyze(template)\n\n// Build your UI based on requirements\nfor arg in info.arguments {\n    print(\"Argument: \\(arg.name)\")\n    print(\"Required: \\(arg.required)\")\n    print(\"Default: \\(arg.defaultValue ?? \"none\")\")\n    print(\"Options: \\(arg.options ?? [])\")\n}\n\n// Check what system features are needed\nif info.usesSelection {\n    // May need accessibility permissions on macOS\n}\nif info.usesClipboard {\n    // Will need clipboard access\n}\n```\n\n### Validation\n\n```swift\n// Simple validation\nif QuickLinkParser.validate(template) {\n    // Template syntax is valid\n}\n\n// Detailed validation with error messages\nlet validation = QuickLinkParser.validateWithErrors(template)\nif !validation.isValid {\n    for error in validation.errors {\n        print(\"Syntax error: \\(error)\")\n    }\n}\n```\n\n### System Access Helpers\n\n```swift\n// Get clipboard content\nif let clipboard = QuickLinkParser.getClipboard() {\n    print(\"Clipboard: \\(clipboard)\")\n}\n\n// Set clipboard content\nSystemAccessHelper.setClipboard(\"New clipboard text\")\n\n// Check accessibility permissions (macOS only)\nif !QuickLinkParser.hasAccessibilityPermission() {\n    // Show explanation to user first\n    showPermissionExplanation()\n    \n    // Then request permission\n    SystemAccessHelper.requestAccessibilityPermission()\n    // Note: App may need restart after granting\n}\n\n// Get selected text (macOS only, requires permission)\nif let selection = QuickLinkParser.getSelectedText() {\n    print(\"Selected: \\(selection)\")\n}\n```\n\n## Platform-Specific Notes\n\n### macOS\n\n- ✅ Full clipboard support\n- ✅ Selected text with accessibility permissions\n- ⚠️ Selection requires user approval in System Settings\n\n**Important:** For selected text access, your app must:\n\n1. Add to `Info.plist`:\n```xml\n\u003ckey\u003eNSAccessibilityUsageDescription\u003c/key\u003e\n\u003cstring\u003eThis app needs accessibility access to read selected text for QuickLinks\u003c/string\u003e\n```\n\n2. Handle permissions in your app:\n```swift\n// Check and request if needed\nif !SystemAccessHelper.hasAccessibilityPermission() {\n    // Show your custom UI explaining why\n    showAccessibilityPermissionDialog()\n    \n    // Open System Settings\n    SystemAccessHelper.openAccessibilitySettings()\n    \n    // Note: App restart may be required\n}\n```\n\n### iOS\n\n- ✅ Full clipboard support\n- ❌ Selected text not available (iOS limitation)\n- ✅ No special permissions required\n\nOn iOS, `{selection}` placeholders will return `nil`. Design your templates accordingly or provide clipboard as fallback.\n\n## Real-World Examples\n\n### Google Search\n\n```swift\nlet template = \"https://google.com/search?q={selection | percent-encode}\"\nlet result = QuickLinkParser.process(template, selection: \"Swift tutorials\")\n// Result: \"https://google.com/search?q=Swift%20tutorials\"\n```\n\n### GitHub Repository\n\n```swift\nlet template = \"https://github.com/{argument name=\\\"owner\\\"}/{argument name=\\\"repo\\\"}\"\nlet result = QuickLinkParser.process(\n    template,\n    arguments: [\"owner\": \"apple\", \"repo\": \"swift\"]\n)\n// Result: \"https://github.com/apple/swift\"\n```\n\n### Google Translate\n\n```swift\nlet template = \"\"\"\nhttps://translate.google.com/\n?sl={argument name=\"from\" default=\"auto\"}\n\u0026tl={argument name=\"to\" default=\"en\"}\n\u0026text={selection | percent-encode}\n\"\"\"\n\nlet result = QuickLinkParser.process(\n    template,\n    arguments: [\"from\": \"es\", \"to\": \"en\"],\n    selection: \"Hola mundo\"\n)\n// Result: \"https://translate.google.com/?sl=es\u0026tl=en\u0026text=Hola%20mundo\"\n```\n\n### YouTube Advanced Search\n\n```swift\nlet template = \"\"\"\nhttps://youtube.com/results?search_query={argument name=\"query\" | percent-encode}\n\u0026sp={argument name=\"filter\" options=\"Any|, Videos|EgIQAQ%253D%253D, Channels|EgIQAg%253D%253D, \nPlaylists|EgIQAw%253D%253D, This Week|CAISBAgCEAE, This Month|CAISBAgDEAE\" default=\"\"}\n\"\"\"\n\nlet info = QuickLinkParser.analyze(template)\n// info.arguments[1].options contains:\n// - ArgumentOption(label: \"Any\", value: \"\")\n// - ArgumentOption(label: \"Videos\", value: \"EgIQAQ%253D%253D\")\n// - ArgumentOption(label: \"Channels\", value: \"EgIQAg%253D%253D\")\n// etc.\n\n// In your UI, show the labels:\nPicker(\"Filter\", selection: $selectedFilter) {\n    ForEach(info.arguments[1].options ?? [], id: \\.value) { option in\n        Text(option.label).tag(option.value)\n    }\n}\n\n// Process with the value:\nlet result = QuickLinkParser.process(\n    template,\n    arguments: [\"query\": \"Swift tutorials\", \"filter\": \"EgIQAQ%253D%253D\"]\n)\n// Result: \"https://youtube.com/results?search_query=Swift%20tutorials\u0026sp=EgIQAQ%253D%253D\"\n```\n\n```swift\nlet template = \"\"\"\nhttps://calendar.google.com/calendar/render\n?action=TEMPLATE\n\u0026text={argument name=\"title\" | percent-encode}\n\u0026dates={date format=\"yyyyMMdd'T'HHmmss\"}/{date format=\"yyyyMMdd'T'HHmmss\" offset=\"+1h\"}\n\"\"\"\n\nlet result = QuickLinkParser.process(\n    template,\n    arguments: [\"title\": \"Team Meeting\"]\n)\n// Creates a 1-hour calendar event starting now\n```\n\n### Amazon Product Search\n\n```swift\nlet template = \"https://amazon.com/s?k={clipboard | trim | percent-encode}\"\nlet result = QuickLinkParser.processWithSystemAccess(template)\n// Uses current clipboard content\n```\n\n## Building User Interfaces\n\nHere's how to build a dynamic UI based on template requirements:\n\n```swift\nimport SwiftUI\nimport QuickLinkParser\n\nstruct QuickLinkView: View {\n    let template: String\n    @State private var arguments: [String: String] = [:]\n    @State private var error: String?\n    \n    var templateInfo: TemplateInfo {\n        QuickLinkParser.analyze(template)\n    }\n    \n    var body: some View {\n        Form {\n            // Generate input fields for each argument\n            ForEach(templateInfo.arguments, id: \\.name) { arg in\n                Section(arg.name) {\n                    if let options = arg.options {\n                        // Dropdown for options\n                        Picker(arg.name, selection: binding(for: arg)) {\n                            ForEach(options, id: \\.self) { option in\n                                Text(option).tag(option)\n                            }\n                        }\n                    } else {\n                        // Text field for free input\n                        TextField(\n                            arg.defaultValue ?? \"Enter \\(arg.name)\",\n                            text: binding(for: arg)\n                        )\n                    }\n                    \n                    if arg.required {\n                        Text(\"Required\").font(.caption).foregroundColor(.red)\n                    }\n                }\n            }\n            \n            // System requirements\n            if templateInfo.usesClipboard {\n                Text(\"📋 Will use clipboard content\").font(.caption)\n            }\n            if templateInfo.usesSelection {\n                Text(\"✂️ Will use selected text\").font(.caption)\n            }\n            \n            Button(\"Open Link\") {\n                openQuickLink()\n            }\n            \n            if let error = error {\n                Text(error).foregroundColor(.red)\n            }\n        }\n    }\n    \n    func binding(for arg: ArgumentInfo) -\u003e Binding\u003cString\u003e {\n        Binding(\n            get: { arguments[arg.name] ?? arg.defaultValue ?? \"\" },\n            set: { arguments[arg.name] = $0 }\n        )\n    }\n    \n    func openQuickLink() {\n        let result = QuickLinkParser.processWithSystemAccess(\n            template,\n            arguments: arguments\n        )\n        \n        if result.success {\n            if let url = URL(string: result.url) {\n                #if os(iOS)\n                UIApplication.shared.open(url)\n                #elseif os(macOS)\n                NSWorkspace.shared.open(url)\n                #endif\n            }\n        } else {\n            error = \"Missing: \\(result.missingArguments.joined(separator: \", \"))\"\n        }\n    }\n}\n```\n\n## Data Types Reference\n\n### ProcessResult\n\n```swift\nstruct ProcessResult {\n    let url: String                  // Processed URL ready to use\n    let missingArguments: [String]   // Required args not provided\n    let success: Bool                 // true if all requirements met\n    let errors: [String]              // Any parsing errors\n}\n```\n\n### TemplateInfo\n\n```swift\nstruct TemplateInfo {\n    let arguments: [ArgumentInfo]    // All arguments found\n    let usesClipboard: Bool          // Uses {clipboard}\n    let usesSelection: Bool          // Uses {selection}\n    let usesDate: Bool               // Uses date/time placeholders\n    let dateFormats: [String]        // Custom date formats found\n    \n    var requiredArguments: [ArgumentInfo]  // Args without defaults\n    var optionalArguments: [ArgumentInfo]  // Args with defaults\n}\n```\n\n### ArgumentInfo\n\n```swift\nstruct ArgumentInfo {\n    let name: String                 // Argument identifier\n    let defaultValue: String?        // Default if specified\n    let options: [ArgumentOption]?   // Dropdown options if specified\n    let required: Bool               // true if no default value\n    \n    var hasOptions: Bool            // Has dropdown options\n    var hasDefault: Bool            // Has a default value\n}\n\nstruct ArgumentOption {\n    let label: String               // Display label (\"Videos\")\n    let value: String               // Actual value (\"EgIQAQ%253D%253D\")\n}\n```\n\n## Advanced Usage\n\n### Custom Date Formats\n\n```swift\n// ISO 8601\n{date format=\"yyyy-MM-dd'T'HH:mm:ssZ\"}\n\n// Human readable\n{date format=\"EEEE, MMMM d, yyyy 'at' h:mm a\"}\n\n// File naming\n{date format=\"yyyyMMdd_HHmmss\"}\n```\n\n### Chaining Multiple Modifiers\n\n```swift\n// Process in order: trim → lowercase → encode\n{clipboard | trim | lowercase | percent-encode}\n\n// Each modifier transforms the previous result\n\"  HELLO WORLD  \" → \"HELLO WORLD\" → \"hello world\" → \"hello%20world\"\n```\n\n### Fallback Patterns\n\n```swift\n// In your app logic\nlet result = QuickLinkParser.process(\n    template,\n    selection: getSelectedText(),\n    clipboard: getSelectedText() ?? getClipboard()  // Fallback\n)\n```\n\n## Performance\n\nQuickLinkParser is optimized for speed:\n\n- ⚡ ~0.1ms for simple templates\n- ⚡ ~0.5ms for complex templates with multiple placeholders\n- ⚡ Efficient regex-based parsing\n- ⚡ Minimal memory allocation\n\nSuitable for real-time processing in response to hotkeys or user actions.\n\n## Testing\n\nThe package includes comprehensive tests:\n\n```bash\n# Run all tests\nswift test\n\n# Run with coverage\nswift test --enable-code-coverage\n\n# Run specific tests\nswift test --filter QuickLinkParserTests.testGoogleSearchTemplate\n```\n\n## Project Structure\n\n```\nQuickLinkParser/\n├── Sources/\n│   └── QuickLinkParser/\n│       ├── QuickLinkParser.swift      # Main API\n│       ├── Models.swift               # Data types\n│       └── SystemAccessHelper.swift   # Platform-specific\n└── Tests/\n    └── QuickLinkParserTests/\n        └── QuickLinkParserTests.swift # Test suite\n```\n\n## Requirements\n\n- **Swift 5.9+**\n- **iOS 13.0+** / **macOS 10.15+** / **tvOS 13.0+** / **watchOS 6.0+**\n- **Xcode 14.0+** (for development)\n\n## Limitations\n\n- **iOS**: Selected text access not available (platform limitation)\n- **macOS**: Selection requires accessibility permissions\n- **Clipboard**: Only supports current clipboard (no history)\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## License\n\nQuickLinkParser is available under the MIT license. See LICENSE file for details.\n\n## Acknowledgments\n\n- Syntax compatible with [Raycast](https://raycast.com) QuickLinks\n- Inspired by URL template systems and text expansion tools\n\n---\n\nMade with ❤️ for the Swift community\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farraypress%2Fswift-quicklink-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farraypress%2Fswift-quicklink-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farraypress%2Fswift-quicklink-parser/lists"}