Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial work on Rendering #15

Merged
merged 24 commits into from Aug 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
@@ -1 +1 @@
ruby-2.0.0-p247
ruby-2.1.1
2 changes: 1 addition & 1 deletion Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
playground-book-lint (0.0.1)
playground-book-lint (0.2.0)
colored (~> 1.2)
cork (~> 0.1)
plist (~> 3.2)
Expand Down
77 changes: 74 additions & 3 deletions README.md
@@ -1,21 +1,92 @@
[![CircleCI](https://circleci.com/gh/ashfurrow/playground-book-lint.svg?style=svg)](https://circleci.com/gh/ashfurrow/playground-book-lint)

# playground-book-lint
# playgroundbook

Linter for Swift Playground books based on [Apple's documentation](https://developer.apple.com/library/prerelease/content/documentation/Xcode/Conceptual/swift_playgrounds_doc_format/index.html#//apple_ref/doc/uid/TP40017343-CH47-SW4). It's a work in progress (see [issues](https://github.com/ashfurrow/playground-book-lint/issues)) but you can use it now.

## Installation

```sh
> [sudo] gem install playground-book-lint
> [sudo] gem install playgroundbook
```

## Usage

To lint an existing playground book:

```sh
> playground-book-lint MyPlaygroundBook.playgroundbook
> playgroundbook lint MyPlaygroundbook.playgroundbook
```

To generate a playground book:

```sh
> playgroundbook render book.yaml
```

The yml file should be in the following format:

```yaml
name: Testing book
identifier: com.ashfurrow.example
resources: assets # Optional
deployment_target: ios10.0 # Optional
imports: # Optional, defaults to UIKit
- UIKit
- CoreGraphics
chapters:
- Chapter 1
- Chapter 2
- etc...
```

Each chapter needs to have a corresponding playground; so `Chapter 1` requires there be a `Chapter 1.playground` playground. The playgrounds can reference (not copy) resources from an optionally specified directory. `import` frameworks are specified in the yaml file and are added to every page of the book. Each chapter needs to be in the following format:

```swift
// This is the preamble that is shared among all the pages within this chapter.

func sharedFunc() {
print("This should be accessible to all pages.")
}

//// Page 1

str = "Yo, it's page 1."
sharedFunc()

//// Page 2

sharedFunc()
str = "Page 2 awww yeah."
```

Pages are divided by lines beginning with a quadruple slash, followed by that pages name.

### Limitations of Book Rendering

Preamble (anything about the first `////` page) is put in its own file. That means declarations there need to be `public` to be visible within individual pages (even though when you're writing, everything is in one file). Additionally, the preamble is at the top-level and can't contain experessions. This would cause a compiler error in the Swift Playrounds iPad app:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

experessions


```swift
public let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 20, height: 20)
```

Instead, you have to wrap it in a closure, like this:

```swift
public var layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 20, height: 20)
return layout
}()
```

It's awkward; if you have suggestions, open an issue :+1:

Sharing resources is only available book-wide and not specific to chapters. Sharing code outside the preamble isn't supported yet.

Playground books support a rich set of awesome features to make learning how to code really easy, and this tool uses almost none of them. It sacrifices this experience for the sake of being able to easily write the books on your Mac.

## License

MIT, except for the `starter.playgroundbook` in the unit tests, which is licensed by Apple.
13 changes: 0 additions & 13 deletions bin/playground_book_lint

This file was deleted.

22 changes: 22 additions & 0 deletions bin/playgroundbook
@@ -0,0 +1,22 @@
#!/usr/bin/env ruby

unless ARGV.length >= 2
puts 'You must specify either lint or render. Example usage:'
puts ' playgroundbook [lint|render] file_name'
exit 1
end

command = ARGV[0]
file_name = ARGV[1]

require 'playgroundbook'

if command == 'lint'
Playgroundbook::Linter.new(file_name).lint
elsif command == 'render'
Playgroundbook::Renderer.new(file_name).render!
else
puts "Unknown command: #{command}"
exit 1
end

9 changes: 9 additions & 0 deletions lib/playgroundbook.rb
@@ -0,0 +1,9 @@
require 'playgroundbook_lint/playgroundbook_lint'
require 'playgroundbook_renderer/playgroundbook_renderer'

module Playgroundbook
ManifestFileName = 'Manifest.plist'.freeze
ContentsSwiftFileName = 'Contents.swift'.freeze
ResourcesDirectoryName = 'Resources'.freeze
PagesDirectoryName = 'Pages'.freeze
end
@@ -1,7 +1,7 @@
require 'colored'
require 'cork'

module PlaygroundBookLint
module Playgroundbook
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious what the rationale behind the change from PlaygroundBook to Playgroundbook is

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It came from wanting to call the executable playgroundbook instead of something with underscores or dashes. Might be silly, what do you think?

# AbstractLinter provides a base implementation of a linter which a concrete
# linter subclass can inherit from
class AbstractLinter
Expand Down
@@ -1,8 +1,8 @@
require 'plist'
require 'playground_book_lint/abstract_linter'
require 'playground_book_lint/chapter_manifest_linter'
require 'playgroundbook_lint/abstract_linter'
require 'playgroundbook_lint/chapter_manifest_linter'

module PlaygroundBookLint
module Playgroundbook
PAGES_DIRECTORY_NAME = 'Pages'.freeze

# A linter for verifying a chapter directory
Expand Down
@@ -1,8 +1,8 @@
require 'playground_book_lint/manifest_linter'
require 'playground_book_lint/page_linter'
require 'playground_book_lint/cutscene_page_linter'
require 'playgroundbook_lint/manifest_linter'
require 'playgroundbook_lint/page_linter'
require 'playgroundbook_lint/cutscene_page_linter'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying the contents of a chapter's Manifest.plist
class ChapterManifestLinter < ManifestLinter
attr_accessor :page_linter, :cutscene_page_linter
Expand Down
@@ -1,7 +1,7 @@
require 'playground_book_lint/abstract_linter'
require 'playground_book_lint/root_manifest_linter'
require 'playgroundbook_lint/abstract_linter'
require 'playgroundbook_lint/root_manifest_linter'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying the contents directory of a playground book
class ContentsLinter < AbstractLinter
attr_accessor :root_manfiest_linter
Expand Down
@@ -1,7 +1,7 @@
require 'playground_book_lint/abstract_linter'
require 'playground_book_lint/cutscene_page_manifest_linter'
require 'playgroundbook_lint/abstract_linter'
require 'playgroundbook_lint/cutscene_page_manifest_linter'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying cutscene pages
class CutscenePageLinter < AbstractLinter
attr_accessor :cutscene_page_manifest_linter
Expand Down
@@ -1,6 +1,6 @@
require 'playground_book_lint/manifest_linter'
require 'playgroundbook_lint/manifest_linter'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying the contents of a cutscene page's manifest
class CutscenePageManifestLinter < ManifestLinter
attr_accessor :page_manifest_linter
Expand Down
@@ -1,7 +1,7 @@
require 'plist'
require 'playground_book_lint/abstract_linter'
require 'playgroundbook_lint/abstract_linter'

module PlaygroundBookLint
module Playgroundbook
MANIFEST_FILE_NAME = 'Manifest.plist'.freeze

# A base inplementation of a linter for verifying the contents of manifest
Expand Down
@@ -1,9 +1,7 @@
require 'playground_book_lint/abstract_linter'
require 'playground_book_lint/page_manifest_linter'

module PlaygroundBookLint
CONTENTS_SWIFT_FILE_NAME = 'Contents.swift'.freeze
require 'playgroundbook_lint/abstract_linter'
require 'playgroundbook_lint/page_manifest_linter'

module Playgroundbook
# A linter for verifying the contents of a page directory
class PageLinter < AbstractLinter
attr_accessor :page_manifest_linter
Expand All @@ -13,13 +11,13 @@ def initialize(page_manifest_linter = PageManifestLinter.new)
end

def lint
fail_lint "Missing #{CONTENTS_SWIFT_FILE_NAME} in #{Dir.pwd}" unless contents_swift_file_exists?
fail_lint "Missing #{ContentsSwiftFileName} in #{Dir.pwd}" unless contents_swift_file_exists?

page_manifest_linter.lint
end

def contents_swift_file_exists?
File.exist? CONTENTS_SWIFT_FILE_NAME
File.exist? ContentsSwiftFileName
end
end
end
@@ -1,7 +1,7 @@
require 'plist'
require 'playground_book_lint/manifest_linter'
require 'playgroundbook_lint/manifest_linter'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying the contents of a page's Manifest.plist
class PageManifestLinter < ManifestLinter
SUPPORTED_LIVE_VIEW_MODES = %w(VisibleByDefault HiddenByDefault).freeze
Expand Down
@@ -1,9 +1,9 @@
require 'colored'
require 'playground_book_lint/abstract_linter'
require 'playground_book_lint/contents_linter'
require 'playgroundbook_lint/abstract_linter'
require 'playgroundbook_lint/contents_linter'
require 'pathname'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying a playground book
class Linter < AbstractLinter
attr_accessor :playground_file_name
Expand Down
@@ -1,8 +1,8 @@
require 'plist'
require 'playground_book_lint/manifest_linter'
require 'playground_book_lint/chapter_linter'
require 'playgroundbook_lint/manifest_linter'
require 'playgroundbook_lint/chapter_linter'

module PlaygroundBookLint
module Playgroundbook
# A linter for verifying the contents of a playground book's root manifest
class RootManifestLinter < ManifestLinter
attr_accessor :chapter_linter
Expand Down
77 changes: 77 additions & 0 deletions lib/playgroundbook_renderer/chapter_collator.rb
@@ -0,0 +1,77 @@
require 'plist'
require 'playgroundbook_renderer/page_writer'

module Playgroundbook
SharedSourcesDirectoryName = 'Sources'
PreambleFileName = 'Preamble.swift'

class ChapterCollator
def initialize(page_writer = PageWriter.new, ui = Cork::Board.new)
@page_writer = page_writer
@ui = ui
end

def collate!(chapter_name, chapter_file_contents, imports)
@ui.puts "Processing #{chapter_name.green}."

chater_directory_name = "#{chapter_name}.playgroundchapter"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chater_directory_name

Dir.mkdir(chater_directory_name) unless Dir.exist?(chater_directory_name)
Dir.chdir(chater_directory_name) do
pages = parse_pages(chapter_file_contents)

Dir.mkdir(PagesDirectoryName) unless Dir.exist?(PagesDirectoryName)
Dir.chdir(PagesDirectoryName) do
pages[:page_names].each_with_index do |page_name, index|
@ui.puts " Processing #{page_name.green}."

page_contents = pages[:page_contents][index]
page_dir_name = pages[:page_dir_names][index]

@page_writer.write_page!(page_name, page_dir_name, imports, page_contents)
end
end

write_chapter_manifest!(chapter_name, pages[:page_dir_names])
write_preamble!(pages[:preamble])
end
end

def parse_pages(swift)
page_names = swift.scan(/\/\/\/\/.*$/).map { |p| p.gsub('////', '').strip }
page_dir_names = page_names.map { |p| "#{p}.playgroundpage" }

split_file = swift.split(/\/\/\/\/.*$/)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 sea dragon

page_contents = split_file.drop(1).map { |p| p.strip }
preamble = split_file.first.strip

{
page_dir_names: page_dir_names,
page_names: page_names,
page_contents: page_contents,
preamble: preamble,
}
end

def write_chapter_manifest!(chapter_name, page_dir_names)
manifest_contents = {
'Name' => chapter_name,
'Pages' => page_dir_names,
'Version' => '1.0',
'ContentVersion' => '1.0',
}
File.open(ManifestFileName, 'w') do |file|
file.write(manifest_contents.to_plist)
end
end

def write_preamble!(preamble)
Dir.mkdir(SharedSourcesDirectoryName) unless Dir.exist?(SharedSourcesDirectoryName)

Dir.chdir(SharedSourcesDirectoryName) do
File.open(PreambleFileName, 'w') do |file|
file.write(preamble)
end
end
end
end
end