├── .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 |
5 |
6 |
7 |
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 |
--------------------------------------------------------------------------------