├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── Tests ├── .swiftlint.yml └── AlfredWorkflowScriptFilterTests │ ├── XCTestCase.swift │ ├── XCTestManifests.swift │ ├── Helpers.swift │ ├── Unit │ ├── Item │ │ ├── TextsTests.swift │ │ ├── TypesTests.swift │ │ ├── VariablesTests.swift │ │ ├── IconsTests.swift │ │ ├── ModsTests.swift │ │ └── OtherFieldsTests.swift │ ├── IconTests.swift │ ├── ScriptFilter │ │ ├── FilteringTests.swift │ │ ├── BasicTests.swift │ │ ├── SortingTests.swift │ │ └── NotBasicLOLTests.swift │ └── ModTests.swift │ └── Feature │ ├── MoreBetterAPI │ ├── ForModTests.swift │ ├── ForItemTests.swift │ └── ForScriptFilterTests.swift │ └── FluentAPITests.swift ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── AlfredWorkflowScriptFiltah.xcscheme ├── Sources └── AlfredWorkflowScriptFilter │ ├── Protocols │ ├── HasArg.swift │ ├── HasIcon.swift │ ├── HasValidity.swift │ ├── HasSubtitle.swift │ └── HasVariables.swift │ └── Core │ ├── Variable.swift │ ├── Mod.swift │ ├── Icon.swift │ ├── Item.swift │ └── ScriptFilter.swift ├── Package.swift ├── LICENSE.md ├── .swiftlint.yml └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: godbout 2 | ko_fi: godbout 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Tests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - vertical_whitespace_opening_braces 3 | - vertical_whitespace_closing_braces 4 | - function_body_length 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/XCTestCase.swift: -------------------------------------------------------------------------------- 1 | @testable import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | extension XCTestCase { 5 | override open func tearDown() { 6 | ScriptFilter.reset() 7 | 8 | super.tearDown() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | [ 6 | testCase(AlfredWorkflowScriptFilterTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Protocols/HasArg.swift: -------------------------------------------------------------------------------- 1 | public protocol HasArg: AnyObject { 2 | var arg: String? { get set } 3 | } 4 | 5 | public extension HasArg { 6 | @discardableResult 7 | func arg(_ arg: String) -> Self { 8 | self.arg = arg 9 | 10 | return self 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Protocols/HasIcon.swift: -------------------------------------------------------------------------------- 1 | public protocol HasIcon: AnyObject { 2 | var icon: Icon? { get set } 3 | } 4 | 5 | public extension HasIcon { 6 | @discardableResult 7 | func icon(_ icon: Icon) -> Self { 8 | self.icon = icon 9 | 10 | return self 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Protocols/HasValidity.swift: -------------------------------------------------------------------------------- 1 | public protocol HasValidity: AnyObject { 2 | var valid: Bool? { get set } 3 | } 4 | 5 | public extension HasValidity { 6 | @discardableResult 7 | func valid(_ validity: Bool = true) -> Self { 8 | valid = validity 9 | 10 | return self 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Protocols/HasSubtitle.swift: -------------------------------------------------------------------------------- 1 | public protocol HasSubtitle: AnyObject { 2 | var subtitle: String? { get set } 3 | } 4 | 5 | public extension HasSubtitle { 6 | @discardableResult 7 | func subtitle(_ subtitle: String) -> Self { 8 | self.subtitle = subtitle 9 | 10 | return self 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Protocols/HasVariables.swift: -------------------------------------------------------------------------------- 1 | public protocol HasVariables: AnyObject { 2 | var variables: [String: String]? { get set } 3 | } 4 | 5 | // TODO: make protocol extension methods final. Swift? 6 | public extension HasVariables { 7 | @discardableResult 8 | func variables(_ variables: Variable...) -> Self { 9 | for variable in variables { 10 | _ = self.variable(variable) 11 | } 12 | 13 | return self 14 | } 15 | 16 | @discardableResult 17 | func variable(_ variable: Variable) -> Self { 18 | variables = variables ?? [:] 19 | variables?[variable.name ?? ""] = variable.value 20 | 21 | return self 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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: "AlfredWorkflowScriptFilter", 8 | platforms: [.macOS(.v10_10)], 9 | products: [ 10 | .library( 11 | name: "AlfredWorkflowScriptFilter", 12 | targets: ["AlfredWorkflowScriptFilter"] 13 | ), 14 | ], 15 | dependencies: [ 16 | ], 17 | targets: [ 18 | .target( 19 | name: "AlfredWorkflowScriptFilter", 20 | dependencies: [] 21 | ), 22 | .testTarget( 23 | name: "AlfredWorkflowScriptFilterTests", 24 | dependencies: ["AlfredWorkflowScriptFilter"] 25 | ), 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Core/Variable.swift: -------------------------------------------------------------------------------- 1 | public final class Variable { 2 | var name: String? 3 | var value: String? 4 | 5 | public init(name: String? = nil, value: String? = nil) { 6 | self.name = name 7 | self.value = value 8 | } 9 | 10 | @discardableResult 11 | public func name(_ name: String) -> Variable { 12 | self.name = name 13 | 14 | return self 15 | } 16 | 17 | @discardableResult 18 | public func value(_ value: String) -> Variable { 19 | self.value = value 20 | 21 | return self 22 | } 23 | } 24 | 25 | extension Variable: Codable {} 26 | 27 | extension Variable: Equatable { 28 | public static func == (lhs: Variable, rhs: Variable) -> Bool { 29 | lhs.name == rhs.name 30 | && lhs.value == rhs.value 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Core/Mod.swift: -------------------------------------------------------------------------------- 1 | // TODO: make the Mod class abstract. Swift? 2 | public class Mod: HasSubtitle, HasArg, HasIcon, HasVariables, HasValidity, Codable { 3 | public var subtitle: String? 4 | public var arg: String? 5 | public var valid: Bool? 6 | public var icon: Icon? 7 | public var variables: [String: String]? 8 | 9 | // TODO: check if a subtitle in mandatory in Mod ScriptFilter 10 | public init() {} 11 | } 12 | 13 | extension Mod: Equatable { 14 | public static func == (lhs: Mod, rhs: Mod) -> Bool { 15 | lhs.subtitle == rhs.subtitle 16 | && lhs.arg == rhs.arg 17 | && lhs.valid == rhs.valid 18 | && lhs.icon == rhs.icon 19 | && lhs.variables == rhs.variables 20 | } 21 | } 22 | 23 | public class Ctrl: Mod {} 24 | public class Cmd: Mod {} 25 | public class Alt: Mod {} 26 | public class Fn: Mod {} 27 | public class Shift: Mod {} 28 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Core/Icon.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum IconType: String, Codable { 4 | case fileicon 5 | case filetype 6 | } 7 | 8 | public final class Icon { 9 | private var path: String 10 | private var type: IconType? 11 | 12 | public init(path: String, type: IconType? = nil) { 13 | self.path = path 14 | self.type = type 15 | } 16 | 17 | @discardableResult 18 | public func path(_ path: String) -> Icon { 19 | self.path = path 20 | 21 | return self 22 | } 23 | 24 | @discardableResult 25 | public func type(_ type: IconType) -> Icon { 26 | self.type = type 27 | 28 | return self 29 | } 30 | } 31 | 32 | extension Icon: Codable {} 33 | 34 | extension Icon: Equatable { 35 | public static func == (lhs: Icon, rhs: Icon) -> Bool { 36 | lhs.path == rhs.path 37 | && lhs.type == rhs.type 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: tests and coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | swift: 13 | name: on Ventura 14 | runs-on: macos-13 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: build 19 | run: swift build -v 20 | - name: run tests with code coverage 21 | run: swift test -v --enable-code-coverage 22 | - name: convert coverage to lcov format 23 | run: xcrun llvm-cov export -format="lcov" .build/debug/${SWIFT_PACKAGE_LIBRARY}PackageTests.xctest/Contents/MacOS/${SWIFT_PACKAGE_LIBRARY}PackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov 24 | env: 25 | SWIFT_PACKAGE_LIBRARY: AlfredWorkflowScriptFilter 26 | - name: upload coverage to codecov 27 | run: bash <(curl https://codecov.io/bash) 28 | env: 29 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 30 | 31 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Helpers.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import Foundation 3 | 4 | struct JSONHelper { 5 | func iconObject(from json: String) throws -> Icon? { 6 | try object(ofType: Icon.self, from: json) 7 | } 8 | 9 | func scriptFilterObject(from json: String) throws -> ScriptFilter? { 10 | try object(ofType: ScriptFilter.self, from: json) 11 | } 12 | 13 | func itemObject(from json: String) throws -> Item? { 14 | try object(ofType: Item.self, from: json) 15 | } 16 | 17 | func modObject(from json: String) throws -> Mod? { 18 | try object(ofType: Mod.self, from: json) 19 | } 20 | 21 | func variableObject(from json: String) throws -> Variable? { 22 | try object(ofType: Variable.self, from: json) 23 | } 24 | 25 | private func object(ofType _: Object.Type, from json: String) throws -> Object? { 26 | try JSONDecoder().decode(Object.self, from: json.data(using: .utf8) ?? Data()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 Guillaume Leclerc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/Item/TextsTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ItemTextsTests: XCTestCase { 5 | private var item: Item? 6 | 7 | override func setUp() { 8 | super.setUp() 9 | 10 | item = Item(title: "text me xoxo") 11 | } 12 | } 13 | 14 | extension ItemTextsTests { 15 | func test_that_it_may_set_a_copy_value() throws { 16 | item?.text("paste", for: .copy) 17 | 18 | let expectedOutput = """ 19 | { 20 | "title": "text me xoxo", 21 | "text": { 22 | "copy": "paste" 23 | } 24 | } 25 | """ 26 | 27 | XCTAssertEqual( 28 | item, 29 | try JSONHelper().itemObject(from: expectedOutput) 30 | ) 31 | } 32 | 33 | func test_that_it_may_set_a_largetype_value() throws { 34 | item?.text("XXL", for: .largetype) 35 | 36 | let expectedOutput = """ 37 | { 38 | "title": "text me xoxo", 39 | "text": { 40 | "largetype": "XXL" 41 | } 42 | } 43 | """ 44 | 45 | XCTAssertEqual( 46 | item, 47 | try JSONHelper().itemObject(from: expectedOutput) 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Feature/MoreBetterAPI/ForModTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class MoreBetterAPIForModTests: XCTestCase { 5 | func test_that_there_is_a_MoreBetterAPI_to_add_only_one_variable() throws { 6 | let cmd = Cmd().variable(Variable(name: "language", value: "yes")) 7 | 8 | let expectedOutput = """ 9 | { 10 | "variables": { 11 | "language": "yes" 12 | } 13 | } 14 | """ 15 | 16 | XCTAssertEqual( 17 | cmd, 18 | try JSONHelper().modObject(from: expectedOutput) 19 | ) 20 | } 21 | 22 | func test_that_there_is_a_MoreBetterAPI_to_add_multiple_variables_in_one_shot() throws { 23 | let ctrl = Ctrl() 24 | .variables( 25 | Variable(name: "band", value: "Pink Floyd"), 26 | Variable(name: "book", value: "Sum") 27 | ) 28 | 29 | let expectedOutput = """ 30 | { 31 | "variables": { 32 | "band": "Pink Floyd", 33 | "book": "Sum" 34 | } 35 | } 36 | """ 37 | 38 | XCTAssertEqual( 39 | ctrl, 40 | try JSONHelper().modObject(from: expectedOutput) 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/IconTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class IconTests: XCTestCase { 5 | func test_that_it_may_not_contain_a_type() throws { 6 | let icon = Icon(path: "~/Desktop") 7 | 8 | let expectedOutput = """ 9 | { 10 | "path": "~/Desktop" 11 | } 12 | """ 13 | 14 | XCTAssertEqual( 15 | icon, 16 | try JSONHelper().iconObject(from: expectedOutput) 17 | ) 18 | } 19 | 20 | func test_that_it_may_be_a_fileicon_type() throws { 21 | let icon = Icon(path: "C:/Windows/LOL", type: .fileicon) 22 | 23 | let expectedOutput = """ 24 | { 25 | "path": "C:/Windows/LOL", 26 | "type": "fileicon" 27 | } 28 | """ 29 | 30 | XCTAssertEqual( 31 | icon, 32 | try JSONHelper().iconObject(from: expectedOutput) 33 | ) 34 | } 35 | 36 | func test_that_it_may_be_a_filetype_type() throws { 37 | let icon = Icon(path: "chemin", type: .filetype) 38 | 39 | let expectedOutput = """ 40 | { 41 | "path": "chemin", 42 | "type": "filetype" 43 | } 44 | """ 45 | 46 | XCTAssertEqual( 47 | icon, 48 | try JSONHelper().iconObject(from: expectedOutput) 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/Item/TypesTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ItemTypesTests: XCTestCase { 5 | private var item: Item? 6 | 7 | override func setUp() { 8 | super.setUp() 9 | 10 | item = Item(title: "you're not my type") 11 | } 12 | } 13 | 14 | extension ItemTypesTests { 15 | func test_that_it_may_be_of_type_default() throws { 16 | item?.type(.default) 17 | 18 | let expectedOutput = """ 19 | { 20 | "title": "you're not my type", 21 | "type": "default", 22 | } 23 | """ 24 | 25 | XCTAssertEqual( 26 | item, 27 | try JSONHelper().itemObject(from: expectedOutput) 28 | ) 29 | } 30 | 31 | func test_that_it_may_be_of_type_file() throws { 32 | item?.type(.file) 33 | 34 | let expectedOutput = """ 35 | { 36 | "title": "you're not my type", 37 | "type": "file", 38 | } 39 | """ 40 | 41 | XCTAssertEqual( 42 | item, 43 | try JSONHelper().itemObject(from: expectedOutput) 44 | ) 45 | } 46 | 47 | func test_that_it_may_be_of_type_fileSkipcheck() throws { 48 | item?.type(.fileSkipcheck) 49 | 50 | let expectedOutput = """ 51 | { 52 | "title": "you're not my type", 53 | "type": "file:skipcheck", 54 | } 55 | """ 56 | 57 | XCTAssertEqual( 58 | item, 59 | try JSONHelper().itemObject(from: expectedOutput) 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/Item/VariablesTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ItemVariablesTests: XCTestCase { 5 | private var item: Item? 6 | 7 | override func setUp() { 8 | super.setUp() 9 | 10 | item = Item(title: "keep moving") 11 | } 12 | } 13 | 14 | extension ItemVariablesTests { 15 | func test_that_there_may_be_no_variable() throws { 16 | let expectedOutput = """ 17 | { 18 | "title": "keep moving" 19 | } 20 | """ 21 | 22 | XCTAssertEqual( 23 | item, 24 | try JSONHelper().itemObject(from: expectedOutput) 25 | ) 26 | } 27 | 28 | func test_that_there_may_be_one_variable() throws { 29 | item?.variables( 30 | Variable(name: "direction", value: "left") 31 | ) 32 | 33 | let expectedOutput = """ 34 | { 35 | "title": "keep moving", 36 | "variables": { 37 | "direction": "left" 38 | } 39 | } 40 | """ 41 | 42 | XCTAssertEqual( 43 | item, 44 | try JSONHelper().itemObject(from: expectedOutput) 45 | ) 46 | } 47 | 48 | func test_that_there_may_be_multiple_variables() throws { 49 | item?.variables( 50 | Variable(name: "race", value: "hooman") 51 | ) 52 | item?.variables( 53 | Variable(name: "color", value: "absolutely")) 54 | 55 | let expectedOutput = """ 56 | { 57 | "title": "keep moving", 58 | "variables": { 59 | "race": "hooman", 60 | "color": "absolutely" 61 | } 62 | } 63 | """ 64 | 65 | XCTAssertEqual( 66 | item, 67 | try JSONHelper().itemObject(from: expectedOutput) 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/Item/IconsTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ItemIconsTests: XCTestCase { 5 | private var item: Item? 6 | 7 | override func setUp() { 8 | super.setUp() 9 | 10 | item = Item(title: "i'm an item with some icons LOL") 11 | } 12 | } 13 | 14 | extension ItemIconsTests { 15 | func test_that_it_may_have_an_icon() throws { 16 | item?.icon(Icon(path: "the path")) 17 | 18 | let expectedOutput = """ 19 | { 20 | "title": "i'm an item with some icons LOL", 21 | "icon": { 22 | "path": "the path" 23 | } 24 | } 25 | """ 26 | 27 | XCTAssertEqual( 28 | item, 29 | try JSONHelper().itemObject(from: expectedOutput) 30 | ) 31 | } 32 | 33 | func test_that_it_may_have_an_icon_of_type_fileicon() throws { 34 | item?.icon( 35 | Icon(path: "another path Bites the Dust") 36 | .type(.fileicon) 37 | ) 38 | 39 | let expectedOutput = """ 40 | { 41 | "title": "i'm an item with some icons LOL", 42 | "icon": { 43 | "path": "another path Bites the Dust", 44 | "type": "fileicon" 45 | } 46 | } 47 | """ 48 | 49 | XCTAssertEqual( 50 | item, 51 | try JSONHelper().itemObject(from: expectedOutput) 52 | ) 53 | } 54 | 55 | func test_that_it_may_have_an_icon_of_type_filetype() throws { 56 | item?.icon( 57 | Icon(path: "caminho", type: .filetype) 58 | ) 59 | 60 | let expectedOutput = """ 61 | { 62 | "title": "i'm an item with some icons LOL", 63 | "icon": { 64 | "path": "caminho", 65 | "type": "filetype" 66 | } 67 | } 68 | """ 69 | 70 | XCTAssertEqual( 71 | item, 72 | try JSONHelper().itemObject(from: expectedOutput) 73 | ) 74 | } 75 | 76 | func test_that_it_cannot_have_multiple_icons() throws { 77 | item?.icon(Icon(path: "path1")) 78 | item?.icon(Icon(path: "path2")) 79 | 80 | let expectedOutput = """ 81 | { 82 | "title": "i'm an item with some icons LOL", 83 | "icon": { 84 | "path": "path2", 85 | } 86 | } 87 | """ 88 | 89 | XCTAssertEqual( 90 | item, 91 | try JSONHelper().itemObject(from: expectedOutput) 92 | ) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Feature/MoreBetterAPI/ForItemTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class MoreBetterAPIForItemTests: XCTestCase { 5 | func test_that_there_is_a_MoreBetterAPI_to_add_only_one_variable() throws { 6 | let item = Item(title: "the title") 7 | .variable(Variable(name: "breakfast", value: "whisky")) 8 | 9 | let expectedOutput = """ 10 | { 11 | "title": "the title", 12 | "variables": { 13 | "breakfast": "whisky" 14 | } 15 | } 16 | """ 17 | 18 | XCTAssertEqual( 19 | item, 20 | try JSONHelper().itemObject(from: expectedOutput) 21 | ) 22 | } 23 | 24 | func test_that_there_is_a_MoreBetterAPI_to_add_multiple_variables_in_one_shot() throws { 25 | let item = Item(title: "i t'❤️") 26 | .variables( 27 | Variable(name: "fruit", value: "cucumber"), 28 | Variable(name: "vegetable", value: "rhubarb") 29 | ) 30 | 31 | let expectedOutput = """ 32 | { 33 | "title": "i t'❤️", 34 | "variables": { 35 | "fruit": "cucumber", 36 | "vegetable": "rhubarb" 37 | } 38 | } 39 | """ 40 | 41 | XCTAssertEqual( 42 | item, 43 | try JSONHelper().itemObject(from: expectedOutput) 44 | ) 45 | } 46 | 47 | func test_that_there_is_a_MoreBetterAPI_to_add_only_one_modifier() throws { 48 | let item = Item(title: "i t'aime") 49 | .mod(Alt().subtitle("je te aime")) 50 | 51 | let expectedOutput = """ 52 | { 53 | "title": "i t'aime", 54 | "mods": { 55 | "alt": { 56 | "subtitle": "je te aime" 57 | } 58 | } 59 | } 60 | """ 61 | 62 | XCTAssertEqual( 63 | item, 64 | try JSONHelper().itemObject(from: expectedOutput) 65 | ) 66 | } 67 | 68 | func test_that_there_is_a_MoreBetterAPI_to_add_multiple_modifiers_in_one_shot() throws { 69 | let item = Item(title: "hello you") 70 | .mods(Alt(), Cmd(), Ctrl()) 71 | 72 | let expectedOutput = """ 73 | { 74 | "title": "hello you", 75 | "mods": { 76 | "alt": {}, 77 | "cmd": {}, 78 | "ctrl": {} 79 | } 80 | } 81 | """ 82 | 83 | XCTAssertEqual( 84 | item, 85 | try JSONHelper().itemObject(from: expectedOutput) 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Carthage 3 | - Pods 4 | - DerivedData 5 | 6 | disabled_rules: 7 | - discarded_notification_center_observer 8 | - notification_center_detachment 9 | - orphaned_doc_comment 10 | - todo 11 | - unused_capture_list 12 | 13 | opt_in_rules: 14 | - array_init 15 | - attributes 16 | - closure_end_indentation 17 | - closure_spacing 18 | - collection_alignment 19 | - colon # promote to error 20 | - convenience_type 21 | - discouraged_object_literal 22 | - empty_collection_literal 23 | - empty_count 24 | - empty_string 25 | - enum_case_associated_values_count 26 | - fatal_error_message 27 | - first_where 28 | - force_unwrapping 29 | - implicitly_unwrapped_optional 30 | - indentation_width 31 | - last_where 32 | - legacy_random 33 | - literal_expression_end_indentation 34 | - multiline_arguments 35 | - multiline_function_chains 36 | - multiline_literal_brackets 37 | - multiline_parameters 38 | - multiline_parameters_brackets 39 | - operator_usage_whitespace 40 | - overridden_super_call 41 | - pattern_matching_keywords 42 | - prefer_self_type_over_type_of_self 43 | - redundant_nil_coalescing 44 | - redundant_type_annotation 45 | - strict_fileprivate 46 | - toggle_bool 47 | - trailing_closure 48 | - unneeded_parentheses_in_closure_argument 49 | - unused_import 50 | - vertical_whitespace_closing_braces 51 | - vertical_whitespace_opening_braces 52 | - yoda_condition 53 | 54 | 55 | custom_rules: 56 | array_constructor: 57 | name: "Array/Dictionary initializer" 58 | regex: '[let,var] .+ = (\[.+\]\(\))' 59 | capture_group: 1 60 | message: "Use explicit type annotation when initializing empty arrays and dictionaries" 61 | severity: warning 62 | 63 | 64 | attributes: 65 | always_on_same_line: 66 | - "@IBSegueAction" 67 | - "@IBAction" 68 | - "@NSManaged" 69 | - "@objc" 70 | 71 | force_cast: warning 72 | force_try: warning 73 | function_body_length: 74 | warning: 60 75 | 76 | legacy_hashing: error 77 | 78 | identifier_name: 79 | excluded: 80 | - i 81 | - id 82 | - x 83 | - y 84 | - z 85 | 86 | type_name: 87 | excluded: 88 | - Fn 89 | 90 | indentation_width: 91 | indentation_width: 4 92 | 93 | line_length: 94 | ignores_urls: true 95 | ignores_function_declarations: true 96 | ignores_comments: true 97 | 98 | multiline_arguments: 99 | first_argument_location: next_line 100 | only_enforce_after_first_closure_on_first_line: true 101 | 102 | private_over_fileprivate: 103 | validate_extensions: true 104 | 105 | trailing_comma: 106 | mandatory_comma: true 107 | 108 | trailing_whitespace: 109 | ignores_empty_lines: false 110 | ignores_comments: true 111 | 112 | vertical_whitespace: 113 | max_empty_lines: 2 114 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Feature/MoreBetterAPI/ForScriptFilterTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class MoreBetterAPIForScriptFilterTests: XCTestCase { 5 | func test_that_there_is_a_MoreBetterAPI_to_add_multiple_variables_in_one_shot() throws { 6 | ScriptFilter.variables( 7 | Variable(name: "firstname", value: "Guillaume"), 8 | Variable(name: "lastname", value: "Leclerc") 9 | ) 10 | 11 | let output = ScriptFilter.output() 12 | let expectedOutput = """ 13 | { 14 | "variables": { 15 | "firstname": "Guillaume", 16 | "lastname": "Leclerc" 17 | }, 18 | "items": [] 19 | } 20 | """ 21 | 22 | XCTAssertEqual( 23 | try JSONHelper().scriptFilterObject(from: output), 24 | try JSONHelper().scriptFilterObject(from: expectedOutput) 25 | ) 26 | } 27 | 28 | func test_that_there_is_a_MoreBetterAPI_to_add_multiple_items_in_one_shot() throws { 29 | ScriptFilter.items( 30 | Item(title: "1st item"), 31 | Item(title: "2nd item") 32 | ) 33 | 34 | let output = ScriptFilter.output() 35 | let expectedOutput = """ 36 | { 37 | "items": [ 38 | { 39 | "title": "1st item" 40 | }, 41 | { 42 | "title": "2nd item" 43 | } 44 | ] 45 | } 46 | """ 47 | 48 | XCTAssertEqual( 49 | try JSONHelper().scriptFilterObject(from: output), 50 | try JSONHelper().scriptFilterObject(from: expectedOutput) 51 | ) 52 | } 53 | 54 | func test_that_there_is_a_MoreBetterAPI_to_add_multiple_elements_in_one_shot() throws { 55 | ScriptFilter.add( 56 | Item(title: "another first item"), 57 | Item(title: "an other second item") 58 | ) 59 | 60 | ScriptFilter.add( 61 | Variable(name: "language", value: "xml"), 62 | Variable(name: "job", value: "xml designer") 63 | ) 64 | 65 | let output = ScriptFilter.output() 66 | let expectedOutput = """ 67 | { 68 | "variables": { 69 | "language": "xml", 70 | "job": "xml designer" 71 | }, 72 | "items": [ 73 | { 74 | "title": "another first item" 75 | }, 76 | { 77 | "title": "an other second item" 78 | } 79 | ] 80 | } 81 | """ 82 | 83 | XCTAssertEqual( 84 | try JSONHelper().scriptFilterObject(from: output), 85 | try JSONHelper().scriptFilterObject(from: expectedOutput) 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/Item/ModsTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ItemModsTests: XCTestCase { 5 | private var item: Item? 6 | 7 | override func setUp() { 8 | super.setUp() 9 | 10 | item = Item(title: "wat mod ru using") 11 | } 12 | } 13 | 14 | extension ItemModsTests { 15 | func test_that_it_may_have_one_modifier() throws { 16 | item?.mods( 17 | Ctrl() 18 | .subtitle("a nice sub") 19 | .arg("new argument") 20 | .valid(false) 21 | ) 22 | 23 | let expectedOutput = """ 24 | { 25 | "title": "wat mod ru using", 26 | "mods": { 27 | "ctrl": { 28 | "subtitle": "a nice sub", 29 | "arg": "new argument", 30 | "valid": false 31 | } 32 | } 33 | } 34 | """ 35 | 36 | XCTAssertEqual( 37 | item, 38 | try JSONHelper().itemObject(from: expectedOutput) 39 | ) 40 | } 41 | 42 | func test_that_it_may_have_multiple_modifiers() throws { 43 | item?.mods( 44 | Shift() 45 | .arg("argh") 46 | .valid(false) 47 | ) 48 | item?.mods(Ctrl().subtitle("undertitle")) 49 | 50 | let expectedOutput = """ 51 | { 52 | "title": "wat mod ru using", 53 | "mods": { 54 | "shift": { 55 | "arg": "argh", 56 | "valid": false 57 | }, 58 | "ctrl": { 59 | "subtitle": "undertitle" 60 | } 61 | } 62 | } 63 | """ 64 | 65 | XCTAssertEqual( 66 | item, 67 | try JSONHelper().itemObject(from: expectedOutput) 68 | ) 69 | } 70 | 71 | func test_that_it_may_have_all_modifiers_together() { 72 | item?.mods(Shift().arg("shift")) 73 | item?.mods(Fn().arg("fn")) 74 | item?.mods(Ctrl().arg("ctrl")) 75 | item?.mods(Alt().arg("alt")) 76 | item?.mods(Cmd().arg("cmd")) 77 | 78 | let expectedOutput = """ 79 | { 80 | "title": "wat mod ru using", 81 | "mods": { 82 | "shift": { 83 | "arg": "shift" 84 | }, 85 | "fn": { 86 | "arg": "fn" 87 | }, 88 | "ctrl": { 89 | "arg": "ctrl" 90 | }, 91 | "alt": { 92 | "arg": "alt" 93 | }, 94 | "cmd": { 95 | "arg": "cmd" 96 | } 97 | } 98 | } 99 | """ 100 | 101 | XCTAssertEqual( 102 | item, 103 | try JSONHelper().itemObject(from: expectedOutput) 104 | ) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Feature/FluentAPITests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class FluentAPITests: XCTestCase { 5 | func test_that_there_is_one_for_Icons() throws { 6 | let icon = Icon(path: "~/") 7 | .type(.fileicon) 8 | .path("i'm on a new path") 9 | 10 | let expectedOutput = """ 11 | { 12 | "path": "i'm on a new path", 13 | "type": "fileicon" 14 | } 15 | """ 16 | 17 | XCTAssertEqual( 18 | icon, 19 | try JSONHelper().iconObject(from: expectedOutput) 20 | ) 21 | } 22 | 23 | func test_that_there_is_one_for_Variables() throws { 24 | let variable = Variable() 25 | .name("color") 26 | .value("blue") 27 | .name("mood") 28 | 29 | let expectedOutput = """ 30 | { 31 | "name": "mood", 32 | "value": "blue" 33 | } 34 | """ 35 | 36 | XCTAssertEqual( 37 | variable, 38 | try JSONHelper().variableObject(from: expectedOutput) 39 | ) 40 | } 41 | 42 | func test_that_there_is_one_for_Mods() throws { 43 | let cmd = Cmd() 44 | .subtitle("sous-titre") 45 | .arg("is arg a fight?") 46 | .valid() 47 | .icon(Icon(path: "c://mf")) 48 | .variables(Variable(name: "food", value: "chicken feet")) 49 | 50 | let expectedOutput = """ 51 | { 52 | "subtitle": "sous-titre", 53 | "arg": "is arg a fight?", 54 | "valid": true, 55 | "icon": { 56 | "path": "c://mf" 57 | }, 58 | "variables": { 59 | "food": "chicken feet" 60 | } 61 | } 62 | """ 63 | 64 | XCTAssertEqual( 65 | cmd, 66 | try JSONHelper().modObject(from: expectedOutput) 67 | ) 68 | } 69 | 70 | func test_that_there_is_one_for_Items() throws { 71 | let item = Item(title: "idem") 72 | .arg("argggghhhhh") 73 | .autocomplete("complete auto") 74 | .icon(Icon(path: "bath", type: .none)) 75 | .match("tennis?") 76 | .mods(Cmd().valid(false)) 77 | .quicklookurl("pervert") 78 | .subtitle("i'm fluent") 79 | .text("XXL", for: .largetype) 80 | .uid("you i d...") 81 | .valid(false) 82 | .variables(Variable(name: "country", value: "macau")) 83 | 84 | let expectedOutput = """ 85 | { 86 | "title": "idem", 87 | "arg": "argggghhhhh", 88 | "autocomplete": "complete auto", 89 | "icon": { 90 | "path": "bath" 91 | }, 92 | "match": "tennis?", 93 | "mods": { 94 | "cmd": { 95 | "valid": false 96 | } 97 | }, 98 | "quicklookurl": "pervert", 99 | "subtitle": "i'm fluent", 100 | "text": { 101 | "largetype": "XXL" 102 | }, 103 | "uid": "you i d...", 104 | "valid": false, 105 | "variables": { 106 | "country": "macau" 107 | } 108 | } 109 | """ 110 | 111 | XCTAssertEqual( 112 | item, 113 | try JSONHelper().itemObject(from: expectedOutput) 114 | ) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Core/Item.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ItemTextType { 4 | case copy 5 | case largetype 6 | } 7 | 8 | public enum ItemType: String, Codable { 9 | case `default` 10 | case file 11 | case fileSkipcheck 12 | } 13 | 14 | public final class Item: HasSubtitle, HasArg, HasIcon, HasVariables, HasValidity { 15 | var title: String 16 | public var subtitle: String? 17 | public var arg: String? 18 | private var autocomplete: String? 19 | private var uid: String? 20 | var match: String? 21 | private var quicklookurl: String? 22 | private var type: String? 23 | public var valid: Bool? 24 | public var icon: Icon? 25 | private var text: [String: String]? 26 | public var variables: [String: String]? 27 | private var mods: [String: Mod]? 28 | 29 | public init(title: String) { 30 | self.title = title 31 | } 32 | 33 | @discardableResult 34 | public func autocomplete(_ autocomplete: String) -> Item { 35 | self.autocomplete = autocomplete 36 | 37 | return self 38 | } 39 | 40 | @discardableResult 41 | public func uid(_ uid: String) -> Item { 42 | self.uid = uid 43 | 44 | return self 45 | } 46 | 47 | @discardableResult 48 | public func match(_ match: String) -> Item { 49 | self.match = match 50 | 51 | return self 52 | } 53 | 54 | @discardableResult 55 | public func quicklookurl(_ quicklookurl: String) -> Item { 56 | self.quicklookurl = quicklookurl 57 | 58 | return self 59 | } 60 | 61 | @discardableResult 62 | public func type(_ type: ItemType) -> Item { 63 | switch type { 64 | case .file: 65 | self.type = "file" 66 | case .fileSkipcheck: 67 | self.type = "file:skipcheck" 68 | default: 69 | self.type = "default" 70 | } 71 | 72 | return self 73 | } 74 | 75 | @discardableResult 76 | public func text(_ text: String, for: ItemTextType) -> Item { 77 | if self.text == nil { 78 | self.text = [:] 79 | } 80 | 81 | switch `for` { 82 | case .copy: 83 | self.text?["copy"] = text 84 | case .largetype: 85 | self.text?["largetype"] = text 86 | } 87 | 88 | return self 89 | } 90 | 91 | @discardableResult 92 | public func mods(_ mods: Mod...) -> Item { 93 | for mod in mods { 94 | self.mod(mod) 95 | } 96 | 97 | return self 98 | } 99 | 100 | @discardableResult 101 | public func mod(_ mod: Mod) -> Item { 102 | mods = mods ?? [:] 103 | mods?[String(describing: Swift.type(of: mod)).lowercased()] = mod 104 | 105 | return self 106 | } 107 | } 108 | 109 | extension Item: Codable {} 110 | 111 | extension Item: Equatable { 112 | public static func == (lhs: Item, rhs: Item) -> Bool { 113 | lhs.title == rhs.title 114 | && lhs.subtitle == rhs.subtitle 115 | && lhs.arg == rhs.arg 116 | && lhs.autocomplete == rhs.autocomplete 117 | && lhs.uid == rhs.uid 118 | && lhs.match == rhs.match 119 | && lhs.quicklookurl == rhs.quicklookurl 120 | && lhs.type == rhs.type 121 | && lhs.valid == rhs.valid 122 | && lhs.icon == rhs.icon 123 | && lhs.text == rhs.text 124 | && lhs.variables == rhs.variables 125 | && lhs.mods == rhs.mods 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/ScriptFilter/FilteringTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | final class ScriptFilterFilteringTests: XCTestCase { 5 | func test_that_it_can_filter_items_by_title() throws { 6 | ScriptFilter.add( 7 | Item(title: "Olá"), 8 | Item(title: "Bonjour"), 9 | Item(title: "Hello") 10 | ) 11 | 12 | let expectedOutputNotFiltered = """ 13 | { 14 | "items": [ 15 | { 16 | "title": "Olá" 17 | }, 18 | { 19 | "title": "Bonjour" 20 | }, 21 | { 22 | "title": "Hello" 23 | } 24 | ] 25 | } 26 | """ 27 | 28 | XCTAssertEqual( 29 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 30 | try JSONHelper().scriptFilterObject(from: expectedOutputNotFiltered) 31 | ) 32 | 33 | ScriptFilter.filterItems(containing: "Bon") 34 | let expectedOutputFiltered = """ 35 | { 36 | "items": [ 37 | { 38 | "title": "Bonjour" 39 | } 40 | ] 41 | } 42 | """ 43 | 44 | XCTAssertEqual( 45 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 46 | try JSONHelper().scriptFilterObject(from: expectedOutputFiltered) 47 | ) 48 | } 49 | 50 | func test_that_it_can_filter_items_by_subtitle() throws { 51 | ScriptFilter.add( 52 | Item(title: "Delete").subtitle("Select to delete timer"), 53 | Item(title: "Choose").subtitle("Select to choose timer"), 54 | Item(title: "See").subtitle("Select to see timer"), 55 | Item(title: "Continue").subtitle("Select to continue timer") 56 | ) 57 | 58 | let expectedOutput = """ 59 | { 60 | "items": [ 61 | { 62 | "title": "See", 63 | "subtitle": "Select to see timer" 64 | } 65 | ] 66 | } 67 | """ 68 | 69 | ScriptFilter.filterItems(by: .subtitle, containing: "see") 70 | 71 | XCTAssertEqual( 72 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 73 | try JSONHelper().scriptFilterObject(from: expectedOutput) 74 | ) 75 | } 76 | 77 | func test_that_filtering_by_an_empty_string_does_not_filter_items() throws { 78 | ScriptFilter.add( 79 | Item(title: "bananas"), 80 | Item(title: "apricots"), 81 | Item(title: "tomatoes") 82 | ) 83 | 84 | let expectedOutput = """ 85 | { 86 | "items": [ 87 | { 88 | "title": "bananas" 89 | }, 90 | { 91 | "title": "apricots" 92 | }, 93 | { 94 | "title": "tomatoes" 95 | } 96 | ] 97 | } 98 | """ 99 | 100 | ScriptFilter.filterItems(containing: "") 101 | 102 | print("output") 103 | print(ScriptFilter.output()) 104 | 105 | XCTAssertEqual( 106 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 107 | try JSONHelper().scriptFilterObject(from: expectedOutput) 108 | ) 109 | } 110 | 111 | func test_that_filtering_items_is_case_insensitive() throws { 112 | ScriptFilter.add( 113 | Item(title: "Renault"), 114 | Item(title: "Peugeot"), 115 | Item(title: "Citroën") 116 | ) 117 | 118 | let expectedOutput = """ 119 | { 120 | "items": [ 121 | { 122 | "title": "Peugeot" 123 | } 124 | ] 125 | } 126 | """ 127 | 128 | ScriptFilter.filterItems(containing: "peu") 129 | 130 | XCTAssertEqual( 131 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 132 | try JSONHelper().scriptFilterObject(from: expectedOutput) 133 | ) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/ModTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ModTests: XCTestCase { 5 | func test_that_it_may_have_a_subtitle() throws { 6 | let mod = Ctrl() 7 | .subtitle("eng.srt") 8 | 9 | let expectedOutput = """ 10 | { 11 | "subtitle": "eng.srt" 12 | } 13 | """ 14 | 15 | XCTAssertEqual( 16 | mod, 17 | try JSONHelper().modObject(from: expectedOutput) 18 | ) 19 | } 20 | 21 | func test_that_it_may_have_an_arg() throws { 22 | let mod = Cmd() 23 | .arg("nice arg, babe.") 24 | 25 | let expectedOutput = """ 26 | { 27 | "arg": "nice arg, babe." 28 | } 29 | """ 30 | 31 | XCTAssertEqual( 32 | mod, 33 | try JSONHelper().modObject(from: expectedOutput) 34 | ) 35 | } 36 | 37 | func test_that_it_may_be_valid() throws { 38 | let mod = Alt() 39 | .valid(true) 40 | 41 | let expectedOutput = """ 42 | { 43 | "valid": true 44 | } 45 | """ 46 | 47 | XCTAssertEqual( 48 | mod, 49 | try JSONHelper().modObject(from: expectedOutput) 50 | ) 51 | } 52 | 53 | func test_that_it_may_not_be_valid() throws { 54 | let mod = Fn() 55 | .valid(false) 56 | 57 | let expectedOutput = """ 58 | { 59 | "valid": false 60 | } 61 | """ 62 | 63 | XCTAssertEqual( 64 | mod, 65 | try JSONHelper().modObject(from: expectedOutput) 66 | ) 67 | } 68 | 69 | func test_that_it_maybe_have_an_icon() throws { 70 | let mod = Shift() 71 | .icon(Icon(path: "~/Dev")) 72 | 73 | let expectedOutput = """ 74 | { 75 | "icon": { 76 | "path": "~/Dev" 77 | } 78 | } 79 | """ 80 | 81 | XCTAssertEqual( 82 | mod, 83 | try JSONHelper().modObject(from: expectedOutput) 84 | ) 85 | } 86 | 87 | func test_that_it_cannot_have_multiple_icons() throws { 88 | let mod = Shift() 89 | .icon(Icon(path: "i con")) 90 | mod.icon(Icon(path: "another").type(.fileicon)) 91 | 92 | let expectedOutput = """ 93 | { 94 | "icon": { 95 | "path": "another", 96 | "type": "fileicon" 97 | } 98 | } 99 | """ 100 | 101 | XCTAssertEqual( 102 | mod, 103 | try JSONHelper().modObject(from: expectedOutput) 104 | ) 105 | } 106 | 107 | func test_that_it_may_have_an_empty_variables_object() throws { 108 | let mod = Cmd() 109 | .variable(Variable()) 110 | 111 | let expectedOutput = """ 112 | { 113 | "variables": {} 114 | } 115 | """ 116 | 117 | XCTAssertEqual( 118 | mod, 119 | try JSONHelper().modObject(from: expectedOutput) 120 | ) 121 | } 122 | 123 | func test_that_it_may_have_one_variable() throws { 124 | let mod = Shift() 125 | .variable( 126 | Variable(name: "car", value: "Toyota") 127 | ) 128 | 129 | let expectedOutput = """ 130 | { 131 | "variables": { 132 | "car": "Toyota" 133 | } 134 | } 135 | """ 136 | 137 | XCTAssertEqual( 138 | mod, 139 | try JSONHelper().modObject(from: expectedOutput) 140 | ) 141 | } 142 | 143 | func test_that_it_may_have_multiple_variables() throws { 144 | let mod = Alt() 145 | mod.variables(Variable(name: "plane", value: "Airbus")) 146 | mod.variables(Variable(name: "fruit", value: "apple")) 147 | 148 | let expectedOutput = """ 149 | { 150 | "variables": { 151 | "plane": "Airbus", 152 | "fruit": "apple" 153 | } 154 | } 155 | """ 156 | 157 | XCTAssertEqual( 158 | mod, 159 | try JSONHelper().modObject(from: expectedOutput) 160 | ) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/Item/OtherFieldsTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | class ItemOtherFieldsTests: XCTestCase { 5 | private var item: Item? 6 | 7 | override func setUp() { 8 | super.setUp() 9 | 10 | item = Item(title: "ot'ahhhhh") 11 | } 12 | } 13 | 14 | extension ItemOtherFieldsTests { 15 | func test_that_it_may_contain_only_a_title() throws { 16 | let item = Item(title: "some title") 17 | let expectedOutput = """ 18 | { 19 | "title": "some title" 20 | } 21 | """ 22 | 23 | XCTAssertEqual( 24 | item, 25 | try JSONHelper().itemObject(from: expectedOutput) 26 | ) 27 | } 28 | 29 | func test_that_it_may_contain_a_subtitle() throws { 30 | let item = Item(title: "Sir") 31 | .subtitle("the subtitle") 32 | 33 | let expectedOutput = """ 34 | { 35 | "title": "Sir", 36 | "subtitle": "the subtitle" 37 | } 38 | """ 39 | 40 | XCTAssertEqual( 41 | item, 42 | try JSONHelper().itemObject(from: expectedOutput) 43 | ) 44 | } 45 | 46 | func test_that_it_may_contain_an_arg() throws { 47 | let item = Item(title: "A Saucerful of Secrets") 48 | .arg("crazy") 49 | 50 | let expectedOutput = """ 51 | { 52 | "title": "A Saucerful of Secrets", 53 | "arg": "crazy" 54 | } 55 | """ 56 | 57 | XCTAssertEqual( 58 | item, 59 | try JSONHelper().itemObject(from: expectedOutput) 60 | ) 61 | } 62 | 63 | func test_that_it_may_contain_an_autocomplete() throws { 64 | let item = Item(title: "titlee") 65 | .autocomplete("autotitlee") 66 | 67 | let expectedOutput = """ 68 | { 69 | "title": "titlee", 70 | "autocomplete": "autotitlee" 71 | } 72 | """ 73 | 74 | XCTAssertEqual( 75 | item, 76 | try JSONHelper().itemObject(from: expectedOutput) 77 | ) 78 | } 79 | 80 | func test_that_it_may_contain_a_uid() throws { 81 | let item = Item(title: "can't think of anything") 82 | .uid("cantthinkofanything") 83 | 84 | let expectedOutput = """ 85 | { 86 | "title": "can't think of anything", 87 | "uid": "cantthinkofanything" 88 | } 89 | """ 90 | 91 | XCTAssertEqual( 92 | item, 93 | try JSONHelper().itemObject(from: expectedOutput) 94 | ) 95 | } 96 | 97 | func test_that_it_may_contain_a_match() throws { 98 | let item = Item(title: "supermarché") 99 | .match("supermarche") 100 | 101 | let expectedOutput = """ 102 | { 103 | "title": "supermarché", 104 | "match": "supermarche", 105 | } 106 | """ 107 | 108 | XCTAssertEqual( 109 | item, 110 | try JSONHelper().itemObject(from: expectedOutput) 111 | ) 112 | } 113 | 114 | func test_that_it_may_contain_a_quicklookurl() throws { 115 | let item = Item(title: "Basic Chinese") 116 | .quicklookurl("https://sleeplessmind.info") 117 | 118 | let expectedOutput = """ 119 | { 120 | "title": "Basic Chinese", 121 | "quicklookurl": "https://sleeplessmind.info", 122 | } 123 | """ 124 | 125 | XCTAssertEqual( 126 | item, 127 | try JSONHelper().itemObject(from: expectedOutput) 128 | ) 129 | } 130 | 131 | func test_that_it_may_not_be_valid() throws { 132 | let item = Item(title: "hihihi") 133 | .valid(false) 134 | 135 | let expectedOutput = """ 136 | { 137 | "title": "hihihi", 138 | "valid": false, 139 | } 140 | """ 141 | 142 | XCTAssertEqual( 143 | item, 144 | try JSONHelper().itemObject(from: expectedOutput) 145 | ) 146 | } 147 | 148 | func test_that_it_may_be_voluntarily_valid() throws { 149 | let item = Item(title: "HA") 150 | .valid(true) 151 | 152 | let expectedOutput = """ 153 | { 154 | "title": "HA", 155 | "valid": true, 156 | } 157 | """ 158 | 159 | XCTAssertEqual( 160 | item, 161 | try JSONHelper().itemObject(from: expectedOutput) 162 | ) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/ScriptFilter/BasicTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | final class ScriptFilterBasicTests: XCTestCase { 5 | func test_that_it_may_contain_nothing() { 6 | XCTAssertEqual( 7 | ScriptFilter.output(), 8 | #"{"items":[]}"# 9 | ) 10 | } 11 | 12 | func test_that_it_may_be_rerun() throws { 13 | XCTAssertEqual( 14 | ScriptFilter.rerun(secondsToWait: 0.0).output(), 15 | #"{"items":[]}"# 16 | ) 17 | 18 | XCTAssertEqual( 19 | ScriptFilter.rerun(secondsToWait: 5.1).output(), 20 | #"{"items":[]}"# 21 | ) 22 | 23 | let output = ScriptFilter.rerun(secondsToWait: 1.3).output() 24 | let expectedOutput = """ 25 | { 26 | "rerun": 1.3, 27 | "items": [] 28 | } 29 | """ 30 | 31 | XCTAssertEqual( 32 | try JSONHelper().scriptFilterObject(from: output), 33 | try JSONHelper().scriptFilterObject(from: expectedOutput) 34 | ) 35 | } 36 | 37 | func test_that_it_may_contain_one_variable() throws { 38 | let variable = Variable(name: "fruit", value: "tomato") 39 | 40 | let output = ScriptFilter.variable(variable).output() 41 | let expectedOutput = """ 42 | { 43 | "variables": { 44 | "fruit": "tomato" 45 | }, 46 | "items": [] 47 | } 48 | """ 49 | 50 | XCTAssertEqual( 51 | try JSONHelper().scriptFilterObject(from: output), 52 | try JSONHelper().scriptFilterObject(from: expectedOutput) 53 | ) 54 | } 55 | 56 | func test_that_it_may_contain_multiple_variables() throws { 57 | let firstVariable = Variable(name: "fruit", value: "cucumber") 58 | let secondVariable = Variable(name: "vegetable", value: "rhubarb") 59 | 60 | ScriptFilter.variable(firstVariable) 61 | ScriptFilter.variable(secondVariable) 62 | 63 | let output = ScriptFilter.output() 64 | let expectedOutput = """ 65 | { 66 | "variables": { 67 | "fruit": "cucumber", 68 | "vegetable": "rhubarb" 69 | }, 70 | "items": [] 71 | } 72 | """ 73 | 74 | XCTAssertEqual( 75 | try JSONHelper().scriptFilterObject(from: output), 76 | try JSONHelper().scriptFilterObject(from: expectedOutput) 77 | ) 78 | } 79 | 80 | func test_that_adding_an_empty_variable_results_in_an_empty_JSON_variable_object() throws { 81 | let variable = Variable() 82 | 83 | ScriptFilter.variable(variable) 84 | 85 | let output = ScriptFilter.output() 86 | let expectedOutput = """ 87 | { 88 | "variables": {}, 89 | "items": [] 90 | } 91 | """ 92 | 93 | XCTAssertEqual( 94 | try JSONHelper().scriptFilterObject(from: output), 95 | try JSONHelper().scriptFilterObject(from: expectedOutput) 96 | ) 97 | } 98 | 99 | func test_that_it_may_contain_one_item() throws { 100 | let item = Item(title: "a nice title") 101 | 102 | ScriptFilter.item(item) 103 | 104 | let output = ScriptFilter.output() 105 | let expectedOutput = """ 106 | { 107 | "items": [ 108 | { 109 | "title": "a nice title", 110 | } 111 | ] 112 | } 113 | """ 114 | 115 | XCTAssertEqual( 116 | try JSONHelper().scriptFilterObject(from: output), 117 | try JSONHelper().scriptFilterObject(from: expectedOutput) 118 | ) 119 | } 120 | 121 | func test_that_it_may_contain_multiple_items() throws { 122 | let firstItem = Item(title: "good title") 123 | let secondItem = Item(title: "bad title") 124 | 125 | ScriptFilter.item(firstItem) 126 | ScriptFilter.item(secondItem) 127 | 128 | let output = ScriptFilter.output() 129 | let expectedOutput = """ 130 | { 131 | "items": [ 132 | { 133 | "title": "good title", 134 | }, 135 | { 136 | "title": "bad title", 137 | } 138 | ] 139 | } 140 | """ 141 | 142 | XCTAssertEqual( 143 | try JSONHelper().scriptFilterObject(from: output), 144 | try JSONHelper().scriptFilterObject(from: expectedOutput) 145 | ) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sources/AlfredWorkflowScriptFilter/Core/ScriptFilter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ItemFilteringProperty { 4 | case title 5 | case subtitle 6 | } 7 | 8 | public enum ItemSortingProperty { 9 | case title 10 | case subtitle 11 | case match 12 | } 13 | 14 | public enum ItemSortingOrder { 15 | case ascendingly 16 | case descendingly 17 | } 18 | 19 | public final class ScriptFilter: HasVariables { 20 | static let shared = ScriptFilter() 21 | 22 | private var rerun: Double? 23 | public var variables: [String: String]? 24 | private var items: [Item] = [] 25 | 26 | private init() {} 27 | 28 | @discardableResult 29 | public static func rerun(secondsToWait seconds: Double) -> ScriptFilter.Type { 30 | if seconds >= 0.1, seconds <= 5.0 { 31 | shared.rerun = seconds 32 | } 33 | 34 | return self 35 | } 36 | 37 | @discardableResult 38 | public static func variables(_ variables: Variable...) -> ScriptFilter.Type { 39 | for variable in variables { 40 | add(variable) 41 | } 42 | 43 | return self 44 | } 45 | 46 | @discardableResult 47 | public static func variable(_ variable: Variable) -> ScriptFilter.Type { 48 | add(variable) 49 | } 50 | 51 | @discardableResult 52 | public static func items(_ items: Item...) -> ScriptFilter.Type { 53 | for item in items { 54 | add(item) 55 | } 56 | 57 | return self 58 | } 59 | 60 | @discardableResult 61 | public static func item(_ item: Item) -> ScriptFilter.Type { 62 | add(item) 63 | } 64 | 65 | @discardableResult 66 | public static func add(_ variables: Variable...) -> ScriptFilter.Type { 67 | for variable in variables { 68 | shared.variable(variable) 69 | } 70 | 71 | return self 72 | } 73 | 74 | @discardableResult 75 | public static func add(_ items: Item...) -> ScriptFilter.Type { 76 | for item in items { 77 | shared.items.append(item) 78 | } 79 | 80 | return self 81 | } 82 | 83 | @discardableResult 84 | public static func filterItems(by property: ItemFilteringProperty = .title, containing term: String) -> ScriptFilter.Type { 85 | if !term.isEmpty { 86 | shared.items = shared.items.filter { items in 87 | switch property { 88 | case .title: 89 | return items.title.localizedCaseInsensitiveContains(term) 90 | case .subtitle: 91 | return items.subtitle?.localizedCaseInsensitiveContains(term) ?? false 92 | } 93 | } 94 | } 95 | 96 | return self 97 | } 98 | 99 | public static func sortItems(by property: ItemSortingProperty = .title, _ order: ItemSortingOrder = .ascendingly) { 100 | shared.items.sort { firstItem, secondItem in 101 | switch property { 102 | case .title: 103 | if order == .descendingly { 104 | return firstItem.title.lowercased() > secondItem.title.lowercased() 105 | } 106 | 107 | return firstItem.title.lowercased() < secondItem.title.lowercased() 108 | case .subtitle: 109 | if let firstSubtitle = firstItem.subtitle, let secondSubtitle = secondItem.subtitle { 110 | if order == .descendingly { 111 | return firstSubtitle.lowercased() > secondSubtitle.lowercased() 112 | } 113 | 114 | return firstSubtitle.lowercased() < secondSubtitle.lowercased() 115 | } 116 | 117 | return false 118 | case .match: 119 | if let firstMatch = firstItem.match, let secondMatch = secondItem.match { 120 | if order == .descendingly { 121 | return firstMatch.lowercased() > secondMatch.lowercased() 122 | } 123 | 124 | return firstMatch.lowercased() < secondMatch.lowercased() 125 | } 126 | 127 | return false 128 | } 129 | } 130 | } 131 | 132 | public static func output() -> String { 133 | let jsonEncoder = JSONEncoder() 134 | if let jsonData = try? jsonEncoder.encode(ScriptFilter.shared) { 135 | if let jsonString = String(data: jsonData, encoding: .utf8) { 136 | return jsonString 137 | } 138 | } 139 | 140 | return #"{"items":[]}"# 141 | } 142 | 143 | static func reset() { 144 | shared.rerun = nil 145 | shared.variables = nil 146 | shared.items = [] 147 | } 148 | } 149 | 150 | extension ScriptFilter: Codable {} 151 | 152 | extension ScriptFilter: Equatable { 153 | public static func == (lhs: ScriptFilter, rhs: ScriptFilter) -> Bool { 154 | lhs.rerun == rhs.rerun 155 | && lhs.variables == rhs.variables 156 | && lhs.items == rhs.items 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/AlfredWorkflowScriptFiltah.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 40 | 41 | 42 | 48 | 54 | 55 | 56 | 57 | 58 | 63 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 86 | 87 | 89 | 92 | 93 | 94 | 95 | 96 | 102 | 103 | 109 | 110 | 111 | 112 | 114 | 115 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/ScriptFilter/SortingTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | final class ScriptFilterSorteringTests: XCTestCase { 5 | func test_that_it_can_sort_items_ascendingly_by_title() throws { 6 | ScriptFilter.add( 7 | Item(title: "France"), 8 | Item(title: "Angola"), 9 | Item(title: "Portugal") 10 | ) 11 | 12 | let expectedOutput = """ 13 | { 14 | "items": [ 15 | { 16 | "title": "Angola" 17 | }, 18 | { 19 | "title": "France" 20 | }, 21 | { 22 | "title": "Portugal" 23 | } 24 | ] 25 | } 26 | """ 27 | 28 | ScriptFilter.sortItems() 29 | 30 | XCTAssertEqual( 31 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 32 | try JSONHelper().scriptFilterObject(from: expectedOutput) 33 | ) 34 | } 35 | 36 | func test_that_it_can_sort_items_descendingly_by_title() throws { 37 | ScriptFilter.add( 38 | Item(title: "France"), 39 | Item(title: "Angola"), 40 | Item(title: "Portugal") 41 | ) 42 | 43 | let expectedOutput = """ 44 | { 45 | "items": [ 46 | { 47 | "title": "Portugal" 48 | }, 49 | { 50 | "title": "France" 51 | }, 52 | { 53 | "title": "Angola" 54 | } 55 | ] 56 | } 57 | """ 58 | 59 | ScriptFilter.sortItems(by: .title, .descendingly) 60 | 61 | XCTAssertEqual( 62 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 63 | try JSONHelper().scriptFilterObject(from: expectedOutput) 64 | ) 65 | } 66 | 67 | func test_that_it_can_sort_items_ascendingly_by_subtitle() throws { 68 | ScriptFilter.add( 69 | Item(title: "Megane").subtitle("Renault"), 70 | Item(title: "206").subtitle("Peugeot"), 71 | Item(title: "Veyron").subtitle("Bugatti") 72 | ) 73 | 74 | let expectedOutput = """ 75 | { 76 | "items": [ 77 | { 78 | "title": "Veyron", 79 | "subtitle": "Bugatti" 80 | }, 81 | { 82 | "title": "206", 83 | "subtitle": "Peugeot" 84 | }, 85 | { 86 | "title": "Megane", 87 | "subtitle": "Renault" 88 | } 89 | ] 90 | } 91 | """ 92 | 93 | ScriptFilter.sortItems(by: .subtitle, .ascendingly) 94 | 95 | XCTAssertEqual( 96 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 97 | try JSONHelper().scriptFilterObject(from: expectedOutput) 98 | ) 99 | } 100 | 101 | func test_that_it_can_sort_items_descendingly_by_subtitle() throws { 102 | ScriptFilter.add( 103 | Item(title: "Megane").subtitle("Renault"), 104 | Item(title: "206").subtitle("Peugeot"), 105 | Item(title: "Veyron").subtitle("Bugatti") 106 | ) 107 | 108 | let expectedOutput = """ 109 | { 110 | "items": [ 111 | { 112 | "title": "Megane", 113 | "subtitle": "Renault" 114 | }, 115 | { 116 | "title": "206", 117 | "subtitle": "Peugeot" 118 | }, 119 | { 120 | "title": "Veyron", 121 | "subtitle": "Bugatti" 122 | } 123 | ] 124 | } 125 | """ 126 | 127 | ScriptFilter.sortItems(by: .subtitle, .descendingly) 128 | 129 | XCTAssertEqual( 130 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 131 | try JSONHelper().scriptFilterObject(from: expectedOutput) 132 | ) 133 | } 134 | 135 | func test_that_it_can_sort_items_ascendingly_by_match() throws { 136 | ScriptFilter.add( 137 | Item(title: "Ross").match("Rachel"), 138 | Item(title: "Chandler").match("Monica"), 139 | Item(title: "Joey").match("Phoebe") 140 | ) 141 | 142 | let expectedOutput = """ 143 | { 144 | "items": [ 145 | { 146 | "title": "Chandler", 147 | "match": "Monica" 148 | }, 149 | { 150 | "title": "Joey", 151 | "match": "Phoebe" 152 | }, 153 | { 154 | "title": "Ross", 155 | "match": "Rachel" 156 | } 157 | ] 158 | } 159 | """ 160 | 161 | ScriptFilter.sortItems(by: .match) 162 | 163 | XCTAssertEqual( 164 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 165 | try JSONHelper().scriptFilterObject(from: expectedOutput) 166 | ) 167 | } 168 | 169 | func test_that_it_can_sort_items_descendingly_by_match() throws { 170 | ScriptFilter.add( 171 | Item(title: "Ross").match("Rachel"), 172 | Item(title: "Chandler").match("Monica"), 173 | Item(title: "Joey").match("Phoebe") 174 | ) 175 | 176 | let expectedOutput = """ 177 | { 178 | "items": [ 179 | { 180 | "title": "Ross", 181 | "match": "Rachel" 182 | }, 183 | { 184 | "title": "Joey", 185 | "match": "Phoebe" 186 | }, 187 | { 188 | "title": "Chandler", 189 | "match": "Monica" 190 | } 191 | ] 192 | } 193 | """ 194 | 195 | ScriptFilter.sortItems(by: .match, .descendingly) 196 | 197 | XCTAssertEqual( 198 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 199 | try JSONHelper().scriptFilterObject(from: expectedOutput) 200 | ) 201 | } 202 | 203 | func test_that_sorting_items_is_case_insensitive() throws { 204 | ScriptFilter.add( 205 | Item(title: "Sleeplessmind Ltd."), 206 | Item(title: "glmb.today"), 207 | Item(title: "sleeplessmind.info"), 208 | Item(title: "I Was Just Thinking"), 209 | Item(title: "dailycuckoo.xyz") 210 | ) 211 | 212 | let expectedOutput = """ 213 | { 214 | "items": [ 215 | { 216 | "title": "dailycuckoo.xyz" 217 | }, 218 | { 219 | "title": "glmb.today" 220 | }, 221 | { 222 | "title": "I Was Just Thinking" 223 | }, 224 | { 225 | "title": "Sleeplessmind Ltd." 226 | }, 227 | { 228 | "title": "sleeplessmind.info" 229 | } 230 | ] 231 | } 232 | """ 233 | 234 | ScriptFilter.sortItems() 235 | 236 | XCTAssertEqual( 237 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 238 | try JSONHelper().scriptFilterObject(from: expectedOutput) 239 | ) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Alfred Workflow ScriptFilter

2 | 3 |

4 | GitHub Release 5 | Build Status 6 | Quality Score 7 | Code Coverage 8 |

9 | 10 | ___ 11 | 12 | # WHY 13 | 14 | ever tried generating JSON results for Alfred manually? LOL. also that library exists in [PHP](https://github.com/godbout/alfred-workflow-scriptfilter) but macOS is getting rid of interpreters so here you go Swift. 15 | 16 | # INSTALLATION 17 | 18 | no idea about CocoaPod or Carthage. for Swift Package Manager: 19 | 20 | ```swift 21 | let package = Package( 22 | ... 23 | dependencies: [ 24 | .package( 25 | name: "AlfredWorkflowScriptFilter", 26 | url: "https://github.com/godbout/AlfredWorkflowScriptFilter", 27 | from: "1.0.0" 28 | ), 29 | ... 30 | ``` 31 | 32 | then don't forget 33 | 34 | ```bash 35 | swift package update 36 | ``` 37 | 38 | # USAGE 39 | 40 | ## print shits 41 | 42 | ```swift 43 | import AlfredWorkflowScriptFilter 44 | 45 | print(ScriptFilter.output()) 46 | ``` 47 | 48 | will result in (hopefully): 49 | 50 | ```json 51 | {"items":[]} 52 | ``` 53 | 54 | ## add shits 55 | 56 | you can add items, variables, rerun automatically your script: 57 | 58 | ```swift 59 | ScriptFilter.add( 60 | Item(title: "titlee") 61 | .uid("uuid") 62 | .subtitle("subtitlee") 63 | .arg("argg") 64 | .icon( 65 | Icon(path: "icon path") 66 | ) 67 | .valid() 68 | .match("matchh") 69 | .autocomplete("autocompletee") 70 | .mod( 71 | Ctrl() 72 | .arg("ctrl arg") 73 | .subtitle("ctrl subtitle") 74 | .valid() 75 | ) 76 | .text("copyy", for: .copy) 77 | .text("largetypee", for: .largetype) 78 | .quicklookurl("quicklookurll") 79 | ) 80 | 81 | ScriptFilter.add( 82 | Variable(name: "food", value: "chocolate"), 83 | Variable(name: "dessert", value: "red beans") 84 | ) 85 | 86 | ScriptFilter.rerun(secondsToWait: 4.5) 87 | 88 | let anotherItem = Item(title: "Another Item in the Wall") 89 | .icon( 90 | Icon(path: "icon pathh", type: .fileicon) 91 | ) 92 | .mods( 93 | Shift() 94 | .subtitle("shift subtitle"), 95 | Fn() 96 | .arg("fn arg") 97 | .valid(true) 98 | ) 99 | 100 | let thirdItem = Item(title: "3rd") 101 | .variables( 102 | Variable(name: "guitar", value: "fender"), 103 | Variable(name: "amplifier", value: "orange") 104 | ) 105 | .mod( 106 | Alt() 107 | .icon( 108 | Icon(path: "alt icon path", type: .fileicon) 109 | ) 110 | .variables( 111 | Variable(name: "grade", value: "colonel"), 112 | Variable(name: "drug", value: "power") 113 | ) 114 | ) 115 | 116 | ScriptFilter.add( 117 | anotherItem, 118 | thirdItem 119 | ) 120 | 121 | print(ScriptFilter.output()) 122 | ``` 123 | 124 | will result in (but not that pretty): 125 | 126 | ```json 127 | { 128 | "rerun": 4.5, 129 | "variables": { 130 | "food": "chocolate", 131 | "dessert": "red beans" 132 | }, 133 | "items": [ 134 | { 135 | "uid": "uidd", 136 | "title": "titlee", 137 | "subtitle": "subtitlee", 138 | "arg": "argg", 139 | "icon": { 140 | "path": "icon path" 141 | }, 142 | "valid": true, 143 | "match": "matchh", 144 | "autocomplete": "autocompletee", 145 | "mods": { 146 | "ctrl": { 147 | "arg": "ctrl arg", 148 | "subtitle": "ctrl subtitle", 149 | "valid": true 150 | } 151 | }, 152 | "text": { 153 | "copy": "copyy", 154 | "largetype": "largetypee" 155 | }, 156 | "quicklookurl": "quicklookurll" 157 | }, 158 | { 159 | "title": "Another Item in the Wall", 160 | "icon": { 161 | "path": "icon pathh", 162 | "type": "fileicon" 163 | }, 164 | "mods": { 165 | "shift": { 166 | "subtitle": "shift subtitle" 167 | }, 168 | "fn": { 169 | "arg": "fn arg", 170 | "valid": true 171 | } 172 | } 173 | }, 174 | { 175 | "title": "3rd", 176 | "mods": { 177 | "alt": { 178 | "icon": { 179 | "path": "alt icon path", 180 | "type": "fileicon" 181 | }, 182 | "variables": { 183 | "grade": "colonel", 184 | "drug": "power" 185 | } 186 | } 187 | }, 188 | "variables": { 189 | "guitar": "fender", 190 | "amplifier": "orange" 191 | } 192 | } 193 | ] 194 | } 195 | ``` 196 | 197 | ## sort shits 198 | 199 | ascendingly or descendingly, by title, subtitle or match: 200 | 201 | ```swift 202 | /** 203 | * sort items ascendingly by titles 204 | */ 205 | ScriptFilter.add(...) 206 | ScriptFilter.sortItems() 207 | print(ScriptFilter.output()) 208 | 209 | /** 210 | * sort items descendingly by subtitles 211 | */ 212 | ScriptFilter.add(...) 213 | ScriptFilter.sortItems(by: .subtitle, .descendingly) 214 | print(ScriptFilter.output()) 215 | ``` 216 | 217 | ## filter shits 218 | 219 | you might want to do this based on the user input, by title or subtitle: 220 | 221 | ```swift 222 | /** 223 | * only items with a title that contains 224 | * 'big' will show up in the output. 225 | */ 226 | ScriptFilter.add(...) 227 | ScriptFilter.filterItems(by: .title, containing: "big") 228 | print(ScriptFilter.output()) 229 | 230 | /** 231 | * only items with a subtitle that contains 232 | * 'duck' will show up in the output. 233 | */ 234 | ScriptFilter.add(...) 235 | ScriptFilter.filterItems(by: .subtitle, containing: "duck") 236 | print(ScriptFilter.output()) 237 | ``` 238 | 239 | # MORE BETTER™ API 240 | 241 | the API respects word for word the [Alfred JSON ScriptFilter format](https://www.alfredapp.com/help/workflows/inputs/script-filter/json/), but usually offers some More Better™ ways to build your ScriptFilter results. 242 | 243 | e.g.: 244 | 245 | ```swift 246 | /** 247 | * when you want to add only one item, variable or mod 248 | * you can use the singular form rather than the plural 249 | */ 250 | blah.item(item) 251 | blah.variable(variable) 252 | blah.mod(Cmd()) 253 | 254 | /** 255 | * rather than using ScriptFilter.item or ScriptFilter.variable 256 | * you can just use ScriptFilter.add and throw your shits in there 257 | */ 258 | ScriptFilter.add(item1, item68) 259 | ScriptFilter.add(variable1, variable2) 260 | ``` 261 | 262 | best is to go have a look through the [Feature Tests](https://github.com/godbout/AlfredWorkflowScriptFilter/tree/master/Tests/AlfredWorkflowScriptFilterTests/Feature) to see the whole API. 263 | 264 | # SPECIAL DEDICATED THANKS TO 265 | 266 | ME. 267 | -------------------------------------------------------------------------------- /Tests/AlfredWorkflowScriptFilterTests/Unit/ScriptFilter/NotBasicLOLTests.swift: -------------------------------------------------------------------------------- 1 | import AlfredWorkflowScriptFilter 2 | import XCTest 3 | 4 | final class ScriptFilterNotBasicLOLTests: XCTestCase { 5 | func test_that_it_may_contain_all_the_available_properties_up_to_Alfred_3_5() throws { 6 | ScriptFilter.add( 7 | Item(title: "i am the title now") 8 | .uid("U I D") 9 | .subtitle("sub title") 10 | .arg("☠️") 11 | .icon( 12 | Icon(path: "C://MikeRoweSoft") 13 | ) 14 | .valid() 15 | .match("45 — 0") 16 | .autocomplete("tab key") 17 | .mods( 18 | Ctrl() 19 | .arg("control arg") 20 | .subtitle("control sub") 21 | .valid(true) 22 | ) 23 | .text("text copy", for: .copy) 24 | .text("text largetype", for: .largetype) 25 | .quicklookurl("peep") 26 | ) 27 | 28 | ScriptFilter.variable( 29 | Variable(name: "variator", value: "malossi") 30 | ) 31 | 32 | ScriptFilter.variable( 33 | Variable(name: "motorcycle", value: "Peugeot 103 SP") 34 | ) 35 | 36 | ScriptFilter.rerun(secondsToWait: 3.9) 37 | 38 | let anotherItem = Item(title: "another or an other?") 39 | .icon( 40 | Icon(path: "i wanna cry", type: .fileicon) 41 | ) 42 | .mods( 43 | Shift().subtitle("shift subtitle") 44 | ) 45 | anotherItem.mods( 46 | Fn() 47 | .arg("fn arg") 48 | .valid() 49 | ) 50 | 51 | let thirdItem = Item(title: "3rd ww") 52 | .variables(Variable(name: "country", value: "France LOL")) 53 | thirdItem.variables(Variable(name: "language", value: "complaints")) 54 | .mods(Alt() 55 | .icon(Icon(path: "alt icon path yes", type: .fileicon)) 56 | .variable(Variable(name: "sport", value: "climbing")) 57 | .variable(Variable(name: "type", value: "sport (climbing)")) 58 | ) 59 | 60 | ScriptFilter.item(anotherItem) 61 | ScriptFilter.item(thirdItem) 62 | 63 | let output = ScriptFilter.output() 64 | let expectedOutput = """ 65 | { 66 | "rerun": 3.9, 67 | "variables": { 68 | "variator": "malossi", 69 | "motorcycle": "Peugeot 103 SP" 70 | }, 71 | "items": [ 72 | { 73 | "uid": "U I D", 74 | "title": "i am the title now", 75 | "subtitle": "sub title", 76 | "arg": "☠️", 77 | "icon": { 78 | "path": "C://MikeRoweSoft" 79 | }, 80 | "valid": true, 81 | "match": "45 — 0", 82 | "autocomplete": "tab key", 83 | "mods": { 84 | "ctrl": { 85 | "arg": "control arg", 86 | "subtitle": "control sub", 87 | "valid": true 88 | } 89 | }, 90 | "text": { 91 | "copy": "text copy", 92 | "largetype": "text largetype" 93 | }, 94 | "quicklookurl": "peep" 95 | }, 96 | { 97 | "title": "another or an other?", 98 | "icon": { 99 | "path": "i wanna cry", 100 | "type": "fileicon" 101 | }, 102 | "mods": { 103 | "shift": { 104 | "subtitle": "shift subtitle" 105 | }, 106 | "fn": { 107 | "arg": "fn arg", 108 | "valid": true 109 | } 110 | } 111 | }, 112 | { 113 | "title": "3rd ww", 114 | "variables": { 115 | "country": "France LOL", 116 | "language": "complaints" 117 | }, 118 | "mods": { 119 | "alt": { 120 | "icon": { 121 | "path": "alt icon path yes", 122 | "type": "fileicon" 123 | }, 124 | "variables": { 125 | "sport": "climbing", 126 | "type": "sport (climbing)" 127 | } 128 | } 129 | } 130 | } 131 | ] 132 | } 133 | """ 134 | 135 | XCTAssertEqual( 136 | try JSONHelper().scriptFilterObject(from: output), 137 | try JSONHelper().scriptFilterObject(from: expectedOutput) 138 | ) 139 | } 140 | 141 | func test_that_what_i_describe_in_the_README_actually_works_dotdotdot() throws { 142 | ScriptFilter.add( 143 | Item(title: "titlee") 144 | .uid("uidd") 145 | .subtitle("subtitlee") 146 | .arg("argg") 147 | .icon( 148 | Icon(path: "icon path") 149 | ) 150 | .valid(true) 151 | .match("matchh") 152 | .autocomplete("autocompletee") 153 | .mod( 154 | Ctrl() 155 | .arg("ctrl arg") 156 | .subtitle("ctrl subtitle") 157 | .valid(true) 158 | ) 159 | .text("copyy", for: .copy) 160 | .text("largetypee", for: .largetype) 161 | .quicklookurl("quicklookurll") 162 | ) 163 | 164 | ScriptFilter.add( 165 | Variable(name: "food", value: "chocolate"), 166 | Variable(name: "dessert", value: "red beans") 167 | ) 168 | 169 | ScriptFilter.rerun(secondsToWait: 4.5) 170 | 171 | let anotherItem = Item(title: "Another Item in the Wall") 172 | .icon( 173 | Icon(path: "icon pathh", type: .fileicon) 174 | ) 175 | .mods( 176 | Shift() 177 | .subtitle("shift subtitle"), 178 | Fn() 179 | .arg("fn arg") 180 | .valid(true) 181 | ) 182 | 183 | let thirdItem = Item(title: "3rd") 184 | .variables( 185 | Variable(name: "guitar", value: "fender"), 186 | Variable(name: "amplifier", value: "orange") 187 | ) 188 | .mod( 189 | Alt() 190 | .icon( 191 | Icon(path: "alt icon path", type: .fileicon) 192 | ) 193 | .variables( 194 | Variable(name: "grade", value: "colonel"), 195 | Variable(name: "drug", value: "power") 196 | ) 197 | ) 198 | 199 | ScriptFilter.add( 200 | anotherItem, 201 | thirdItem 202 | ) 203 | 204 | let expectedOutput = """ 205 | { 206 | "rerun": 4.5, 207 | "variables": { 208 | "food": "chocolate", 209 | "dessert": "red beans" 210 | }, 211 | "items": [ 212 | { 213 | "uid": "uidd", 214 | "title": "titlee", 215 | "subtitle": "subtitlee", 216 | "arg": "argg", 217 | "icon": { 218 | "path": "icon path" 219 | }, 220 | "valid": true, 221 | "match": "matchh", 222 | "autocomplete": "autocompletee", 223 | "mods": { 224 | "ctrl": { 225 | "arg": "ctrl arg", 226 | "subtitle": "ctrl subtitle", 227 | "valid": true 228 | } 229 | }, 230 | "text": { 231 | "copy": "copyy", 232 | "largetype": "largetypee" 233 | }, 234 | "quicklookurl": "quicklookurll" 235 | }, 236 | { 237 | "title": "Another Item in the Wall", 238 | "icon": { 239 | "path": "icon pathh", 240 | "type": "fileicon" 241 | }, 242 | "mods": { 243 | "shift": { 244 | "subtitle": "shift subtitle" 245 | }, 246 | "fn": { 247 | "arg": "fn arg", 248 | "valid": true 249 | } 250 | } 251 | }, 252 | { 253 | "title": "3rd", 254 | "mods": { 255 | "alt": { 256 | "icon": { 257 | "path": "alt icon path", 258 | "type": "fileicon" 259 | }, 260 | "variables": { 261 | "grade": "colonel", 262 | "drug": "power" 263 | } 264 | } 265 | }, 266 | "variables": { 267 | "guitar": "fender", 268 | "amplifier": "orange" 269 | } 270 | } 271 | ] 272 | } 273 | """ 274 | 275 | XCTAssertEqual( 276 | try JSONHelper().scriptFilterObject(from: ScriptFilter.output()), 277 | try JSONHelper().scriptFilterObject(from: expectedOutput) 278 | ) 279 | } 280 | } 281 | --------------------------------------------------------------------------------