Skip to content

Commit

Permalink
Merge pull request #17 from finestructure/swift-pg-book
Browse files Browse the repository at this point in the history
Swift pg book
  • Loading branch information
finestructure authored Mar 1, 2020
2 parents 5107f9a + 5e001d0 commit 1610767
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 10 deletions.
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,33 @@

Arena is a macOS command line tool to create an Xcode project with a Swift Playground that's readily set up to use a Swift Package Manager library. You can reference both Github and local repositories. The latter is especially useful to spin up a Playground while working on a library.

Arena can also create a Playground in "Playground Book" format, which is the file format supported by ["Swift Playgrounds"](https://apps.apple.com/gb/app/swift-playgrounds/id1496833156?mt=12). These playgrounds can then be synced and opened on the iOS version of "Swift Playgrounds" as well.

Here is an overview of the `arena` command line interface:

```
arena --help
OVERVIEW: Creates an Xcode project with a Playground and one or more SPM libraries imported and ready for use.
USAGE: arena [--name <name>] [--libs <libs> ...] [--platform <platform>] [--force] [--outputdir <outputdir>] [--version] [<dependencies> ...]
USAGE: arena [--name <name>] [--libs <libs> ...] [--platform <platform>] [--force] [--outputdir <outputdir>] [--version] [--skip-open] [--book] [<dependencies> ...]
ARGUMENTS:
<dependencies> Dependency url(s) and (optionally) version specification
<dependencies> Dependency url(s) and (optionally) version specification
OPTIONS:
-n, --name <name> Name of directory and Xcode project (default: SPM-Playground)
-l, --libs <libs> Names of libraries to import (inferred if not provided)
-l, --libs <libs> Names of libraries to import (inferred if not provided)
-p, --platform <platform>
Platform for Playground (one of 'macos', 'ios', 'tvos') (default: macos)
-f, --force Overwrite existing file/directory
-f, --force Overwrite existing file/directory
-o, --outputdir <outputdir>
Directory where project folder should be saved (default: /Users/sas/Projects/Arena)
-v, --version Show version
-v, --version Show version
--skip-open Do not open project in Xcode on completion
--book Create a Swift Playgrounds compatible Playground Book bundle (experimental).
-h, --help Show help information.
```

## Why Arena?

Arena – Spanish for "sand" – is where you battle-test your SPM packages and sand is, well, abundant in playgrounds, isn't it? 🙂

## Examples

### Import Github repository
Expand Down Expand Up @@ -116,6 +118,12 @@ make install

This will copy the binary `arena` to `/usr/local/bin`.

## Why Arena?

Arena – Spanish for "sand" – is where you battle-test your SPM packages and sand is, well, abundant in playgrounds, isn't it? 🙂

## Compatibility

`arena` was built and tested on macOS 10.15 Catalina using Swift 5.1.3. It should work on other versions of macOS and Swift as well.

Playground books created by `arena` should equally run on macOS 10.15 Catalina as well as iOS 13. Please bear in mind that the Swift packages you import when creating playground books will need to be iOS compatible.
22 changes: 21 additions & 1 deletion Sources/ArenaCore/ArenaCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum ArenaError: LocalizedError {
case missingDependency
case pathExists(String)
case noLibrariesFound
case noSourcesFound

public var errorDescription: String? {
switch self {
Expand All @@ -24,6 +25,8 @@ public enum ArenaError: LocalizedError {
return "'\(path)' already exists, use '-f' to overwrite"
case .noLibrariesFound:
return "no libraries found, make sure the referenced dependencies define library products"
case .noSourcesFound:
return "no source files found, make sure the referenced dependencies contain swift files in their 'Sources' folders"
}
}
}
Expand Down Expand Up @@ -65,6 +68,9 @@ public struct Arena: ParsableCommand {
@Flag(name: .long, help: "Do not open project in Xcode on completion")
var skipOpen: Bool

@Flag(name: .long, help: "Create a Swift Playgrounds compatible Playground Book bundle (experimental).")
var book: Bool

@Argument(help: "Dependency url(s) and (optionally) version specification")
var dependencies: [Dependency]

Expand Down Expand Up @@ -184,7 +190,12 @@ extension Arena {
do {
try playgroundPath.mkdir()
let libsToImport = !libNames.isEmpty ? libNames : libs.map({ $0.libraryName })
let importClauses = libsToImport.map { "import \($0)" }.joined(separator: "\n") + "\n"
let importClauses =
"""
// ℹ️ Make sure to build to make the module available!
// If autocomple is not working, please restart Xcode.
""" + "\n\n" +
libsToImport.map { "import \($0)" }.joined(separator: "\n") + "\n"
try importClauses.write(to: playgroundPath/"Contents.swift")
try """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
Expand All @@ -194,6 +205,15 @@ extension Arena {
""".write(to: playgroundPath/"contents.xcplayground")
}

if book {
let modules = dependencies
.compactMap { $0.path ?? $0.checkoutDir(projectDir: projectPath) }
.compactMap(Module.init)
if modules.isEmpty { throw ArenaError.noSourcesFound }
try PlaygroundBook.make(named: projectName, in: projectPath, with: modules)
print("📙 created Playground Book in folder '\(projectPath.relative(to: Path.cwd))'")
}

print("✅ created project in folder '\(projectPath.relative(to: Path.cwd))'")
if skipOpen {
print("Run")
Expand Down
135 changes: 135 additions & 0 deletions Sources/ArenaCore/PlaygroundBook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//
// PlaygroundBook.swift
//
//
// Created by Sven A. Schmidt on 01/03/2020.
//

import Path


public struct Module {
var name: String
var sources: [Path]

init?(path: Path) {
let sourceDir = path.join("Sources")
guard sourceDir.exists else { return nil }
self.name = path.basename()
self.sources = sourceDir.find().extension("swift").type(.file).map { $0 }
}
}


public enum PlaygroundBook {
public static func make(named name: String, in parent: Path, with modules: [Module]) throws {
let book = try parent.join("\(name).playgroundbook").mkdir()
try mkContents(parent: book, modules: modules)
}
}


private extension PlaygroundBook {

static let chapter1Manifest = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Name</key>
<string>My Playground</string>
<key>TemplatePageFilename</key>
<string>Template.playgroundpage</string>
<key>InitialUserPages</key>
<array>
<string>My Playground.playgroundpage</string>
</array>
</dict>
</plist>
"""

static let contentsManifest = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Chapters</key>
<array>
<string>Chapter1.playgroundchapter</string>
</array>
<key>ContentIdentifier</key>
<string>com.apple.playgrounds.blank</string>
<key>ContentVersion</key>
<string>1.0</string>
<key>DeploymentTarget</key>
<string>ios-current</string>
<key>DevelopmentRegion</key>
<string>en</string>
<key>SwiftVersion</key>
<string>5.1</string>
<key>Version</key>
<string>7.0</string>
<key>UserAutoImportedAuxiliaryModules</key>
<array/>
<key>UserModuleMode</key>
<string>Full</string>
</dict>
</plist>
"""

static func myPlaygroundPageManifest(named name: String) -> String {
"""
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Name</key>
<string>\(name)</string>
<key>LiveViewEdgeToEdge</key>
<false/>
<key>LiveViewMode</key>
<string>HiddenByDefault</string>
</dict>
</plist>
"""
}

static let mainSwift = """
// ℹ️ The source files of your dependencies have been copied into the
// UserModule/Sources folder and their public interfaces are
// available without requiring a module import.
"""

static func mkContents(parent: Path, modules: [Module]) throws {
let contents = try parent.join("Contents").mkdir()
try contentsManifest.write(to: contents/"Manifest.plist")
try mkChapters(in: contents)
try mkUserModules(in: contents, modules: modules)
}

static func mkChapters(in parent: Path) throws {
let chapters = try parent.join("Chapters").mkdir()
let chapter1 = try chapters.join("Chapter1.playgroundchapter").mkdir()
try chapter1Manifest.write(to: chapter1/"Manifest.plist")
let pages = try chapter1.join("Pages").mkdir()
try mkPlaygroundPage(in: pages, named: "My Playground")
try mkPlaygroundPage(in: pages, named: "Template")
}

static func mkPlaygroundPage(in parent: Path, named name: String) throws {
let page = try parent.join("\(name).playgroundpage").mkdir()
try myPlaygroundPageManifest(named: name).write(to: page/"Manifest.plist")
try mainSwift.write(to: page/"main.swift")
}

static func mkUserModules(in parent: Path, modules: [Module]) throws {
let sources = try parent.join("UserModules/UserModule.playgroundmodule/Sources").mkdir(.p)
for module in modules {
for src in module.sources {
let target = sources.join(module.name + "-" + src.basename())
try src.copy(to: target, overwrite: false)
}
}
}

}

0 comments on commit 1610767

Please sign in to comment.