Sylvester 😼

Carthage compatible Pod Version codebeat badge Swift Version GitHub license PRs Welcome

Header

A type-safe, XPC-available SourceKitten (SourceKit) interface with some sugar.


Looking for…

  • A Floating Action Button for macOS? Check out Fab. πŸ›οΈ.
  • An Expanding Bubble Text Field for macOS? Check out BubbleTextField πŸ’¬.
  • An integrated spotlight-based onboarding and help library for macOS? Check out Enlighten πŸ’‘.

Features

  • [x] Type-safe, no more dictionaries and SourceKitRepresentables.
  • [x] Optional XPC service, sandbox-friendly.
  • [x] Subclassable interface.
  • [x] Comprehensive test suite.

Requirements

  • macOS 10.12+

Modules

The Sylvester framework has two build configurations that differ in their method of communicating with SourceKit:

  • Sylvester β€” Communicates directly from within the embedding application or process. This module is not sandbox-friendly.
  • SylvesterXPC β€” Communicates through a XPC service. This module provides privilege separation, enhanced stability, and is sandbox-friendly.

πŸ“Œ Note: The XPC service itself cannot be sandboxed (due to inherent dependencies: xcrun, xcodebuild, sourcekitd), and requires an additional code signing step.

Installation

Sylvester is available for installation using Carthage or CocoaPods.

Using CocoaPods

🐞 Bug: Requires CocoaPods version >= 1.6.0 (Current pre-release, 1.6.0.rc.2)

πŸ“£ Important: The XPC service (and/or the SylvesterXPC module) is currently unavailable for CocoaPods installations.

pod "Sylvester"

Using Carthage

github "chriszielinski/Sylvester"

Dependencies

Sylvester/SylvesterXPC depends on the following frameworks/libraries, so ensure they are also embedded in the Embed Frameworks phase:

  • SylvesterCommon.framework
  • SourceKittenFramework.framework
  • SWXMLHash.framework
  • Yams.framework

Embed Frameworks Phase

Code Signing

If you decide to use the SylvesterXPC module, you will need to add a Run Script phase before embedding the SylvesterXPC.framework (i.e. before the Embed Frameworks phase). Ensure the shell launch path is /bin/sh (default). Then for Carthage installations, execute the code_sign_carthage.sh shell script in the repository’s Scripts directory.

"$SRCROOT/Carthage/Checkouts/Sylvester/Scripts/code_sign_carthage.sh"

Code Sign Phase

For other installations, modify the script’s paths as neccessary.

Supported Requests

Request Class
Code Completion SKCodeCompletion
Code Completion Session SKCodeCompletionSession
Cursor Info SKCursorInfo
Documentation Info SKDocInfo
Editor Open SKEditorOpen
Editor Extract Text From Comment SKEditorExtractTextFromComment
Convert Markup To XML SKConvertMarkupToXML
Module Info SKModule
Swift Documentation SKSwiftDocs
Syntax Map SKSyntaxMap
Custom YAML SKYAMLRequest

Other Fun Things

Type Method
XCRun SylvesterInterface.shared.xcRun(arguments:)
XcodeBuild SylvesterInterface.shared.xcodeBuild(arguments:currentDirectoryURL:)
Bash Command SylvesterInterface.shared.executeBash(command:currentDirectoryURL:)
Launch Subprocess SylvesterInterface.shared.launch(subprocess:)

Subclassing

Most of the standard requests are concrete subclasses of beautiful generic classes. Fancy your own subclass? No problem, it might be possible.

SKSubstructure, SKEntity

Also known as SKBaseSubstructure (or SKBaseEntity), a common culprit.

πŸ“Œ Note: Subclassing SKBaseEntity uses similar syntax.

final class BetterSubstructureSubclass: SKBaseSubstructure, SKFinalSubclass {

    var iAmAnImportantProperty: String = "πŸšΆβ€β™‚οΈ"

    public override func decodeChildren(from container: DecodingContainer) throws -> [SKBaseSubstructure]? {
        return try decodeChildren(BetterSubstructureSubclass.self, from: container)
    }

    /// The default iterator for `SKChildren` does a pre-order (NLR) depth-first search (DFS) traversal; however, if you want something else, for instance:
    class FunctionSubstructureIterator<Substructure: BetterSubstructureSubclass>: SKPreOrderDFSIterator<Substructure> {

        override func next() -> Substructure? {
            guard let nextSubstructure = super.next()
                else { return nil }

            if nextSubstructure.isFunction {
                return nextSubstructure
            } else {
                return next()
            }
        }

    }

    override class func iteratorClass<Substructure: BetterSubstructureSubclass>() -> SKPreOrderDFSIterator<Substructure>.Type {
        return FunctionSubstructureIterator.self
    }

}

SKEditorOpen, SKSwiftDocs

An example of a SKSwiftDocs subclass utilizing the BetterSubstructureSubclass declared above:

πŸ“Œ Note: Subclassing SKEditorOpen uses identical syntax, except it inherits from SKGenericEditorOpen.

class BetterSwiftDocs: SKGenericSwiftDocs<BetterSubstructureSubclass> {

    var mySuperCoolProperty: String = "😎"

}

SKModule

An example of a SKModule subclass utilizing the BetterSwiftDocs and BetterSubstructureSubclass classes declared above:

class BetterModule: SKGenericModule<BetterSubstructureSubclass, BetterSwiftDocs> {}

Documentation

You can explore the docs here.

// ToDo:

  • [ ] Add support for other requests.

Community

  • Found a bug? Open an issue.
  • Feature idea? Open an issue. Do it yourself & PR when done πŸ˜… (or you can open an issue πŸ™„).
  • Want to contribute? Submit a pull request.

Contributors

Frameworks & Libraries

Sylvester depends on the wonderful contributions of the Swift community, namely:

License

Sylvester is available under the MIT license, see the LICENSE file for more information.