/swish

Shell emulator written in Swift.

Primary LanguageSwiftGNU Affero General Public License v3.0AGPL-3.0

Alt text

Swift build License iOS macOS tvOS

Swish is a shell emulator written in Swift. It has a variety of potential uses in some applications, including emulating a terminal within a game or app, testing new features without a UI, and more.

Developers can expand the capabilities of Swish by creating their own commands. Instructions can be found here.

The nature of this Swift Package should be compliant with Apple's App Store policies. Although this has not been tested.

Example

To run the example project, clone the repo, open SwishTerminal.xcodeproj within the Terminal-Example folder, and run the project.

Alt text

Installation

Swift Package Manager

To integrate using Apple's Swift Package Manager, add the following as a dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/fatihbalsoy/swish.git", from: "0.1.0")
]

Alternatively, navigate to your Xcode project, go to File > Add Packages... and search for https://github.com/fatihbalsoy/swish.

Manually

If you prefer not to use a dependency manager, you can integrate Swish into your project manually. Simply drag the files in the Sources folder into your Xcode project.

Usage

import Swish

class Example: ShellSessionDelegate {
    
    var swish: Swish?
    var prefix: String?

    init() {
        // Initialize Shell environment in Documents folder
        Shell(root: Shell.documentsURL)
            // Session for user
            .session(user: "user", hostname: "swish", uuid: UUID().uuidString) { (exists, session) in
            self.swish = Swish(session: session)
            self.prefix = session.prompt
        }
        swish?.session.delegate = self

        // Execute `echo` command
        execute("echo hello there!")

        // Execute `touch` command
        execute("touch kenobi.txt")
    }

    // Command execution
    func execute(_ args: String) {
        swish?.execute(args, completion: { 
            (exit) in
            
            // Use `self.swish?.session.stdout`
            // to pull output from shell session.
            // - stdin for input
            // - stderr for errors

            // You can refresh your 
            // terminal UI here
        })
    }

}

ShellSessionDelegate functions

func terminal(didUpdateOutput session: ShellSession)

Triggered when an output was added to stdout or stderr. Can be used to update the terminal interface.


func terminal(didClearOutput session: ShellSession)

Triggered when the clear command is executed. Can be used to clear a custom terminal interface.


func terminal(didExit session: ShellSession)

Triggered when the exit command is executed. Can be used to close a view controller or the entire app by calling exit(0) in Swift.

Commands

  • alias - create an alias for a command
  • cat - display file contents
  • cd - change directory
  • clear - clear terminal
  • cp - copy files
  • date - display date
  • defaults - modify UserDefaults on Darwin systems
  • echo - display anything
  • eval - concatenate arguments into a single command
  • exec - replace shell without creating a new process
  • exit - exit terminal
  • export - save variables
  • expr - evaluate math expressions
  • getopts - parse positional parameters
  • hash - remember command pathname
  • help - list commands
  • history - display session history
  • let - evaluate arithmetic expressions
  • local - create a local variable
  • logout - logout of user session
  • ls - list files
  • kill - stop a process
  • man - a more detailed help page for commands
  • mkdir - create folders
  • mv - move files and folders
  • printf - display a formatted string
  • pwd - display current working directory
  • read - read one line from standard input
  • return - stop a command and returns a value
  • rm - remove files and folders
  • set - display all variables
  • shift - shift positional parameters
  • sleep - delay session
  • source - run a swish file
  • times - display times used by the shell
  • touch - create files
  • test - return 0 or 1 according to given conditional
  • unalias - remove an alias
  • uname - display basic system info
  • unset - remove variables
  • unzip - unzip archives
  • wait - wait until a process ends
  • zip - zip files

Flow control statements like if, while, and for are not yet available.

Custom Commands

You can make more commands by simply creating a swift file within your project in this format.

import Swish

// Replace `example` in class name
class _command_`example`: Command {

    // Initialize the name and usage
    required init(_ session: ShellSession) {
        super.init(session)
        name = "example"
        usage = "usage: example [-s] ..."
    }

    // Parse arguments and execute command.
    // Return an exit code:
    // 0: successful
    // 1: failure
    // more can be found in its documentation
    override func execute(_ args: [String]) -> Int { /* ... */ }

    // Implement auto-completion functionality
    override func tab(_ args: [String], count: Int) -> String { /* ... */ }
}

Finally, add the custom command into Swish like so:

Shell(root: Shell.documentsURL).session(user: "user", hostname: "swish", uuid: UUID().uuidString) { (exists, session) in
    self.swish = Swish(session: session)
    
    // Integrate custom commands
    let commands = [
        _command_example.init(session),
    ]
    self.swish.commands.append(contentsOf: commands)
}

File structure

Swish creates the following directories when a session is created for the first time at the given root path.

root
├─ home/
   ├─ user/

Dependencies

  • Zip
    • zip command
    • Used to zip files and folders.
  • DDMathParser
    • expr command
    • Used to solve mathematical expressions.

License

Swish is available under the AGPL license. See the LICENSE file for more info.