Initial work on Rendering #15

Merged
merged 24 commits into from Aug 27, 2016

Projects

None yet

3 participants

@ashfurrow
Collaborator
ashfurrow commented Aug 24, 2016 edited

Fixes #14.

Basically I'm splitting the project into two independent code paths: one for linting and one for rendering. Feedback welcome as always.

Left to complete:

  • Generate playground chapter and page directories for each chapter.
  • Generate a manifest for each chapter.
  • Generate a manifest for each page.
  • Copy each playground page to the approach playground chapter's page directory.
  • Update documentation in readme.
  • More UI output.
ashfurrow added some commits Aug 24, 2016
@ashfurrow ashfurrow Initial move. e21d2e8
@ashfurrow ashfurrow Subsequent follow-up. cfdce4f
@ashfurrow ashfurrow Further splitting. 57252f2
@ashfurrow ashfurrow Moved more files around. d9690b7
@ashfurrow ashfurrow Basics of renderer. bd8ec5e
@ashfurrow
Collaborator

I think that's it for me tonight. There're some advance features we've got to figure out later, I'm going for a minimum-viable-playgroundbook generator. So here's what I'm planning:

The yaml file you pass in specifies book metadata we need (book name & chapter names), and the chapter names have to match playgrounds in that directory. Then the tool copies the Contents.swift files from each playground into new directories created for the playground book. All we need really is to get the directory names and Manifest.plist files right. We can worry about the XPC stuff later. Sound good @rpowelll?

@ashfurrow
Collaborator

One feature that would be cool are project-level and chapter-level shared files. Hmm. Might be something we can copy from the other playgrounds.

@rpowelll rpowelll commented on the diff Aug 25, 2016
lib/playgroundbook_lint/abstract_linter.rb
@@ -1,7 +1,7 @@
require 'colored'
require 'cork'
-module PlaygroundBookLint
+module Playgroundbook
@rpowelll
rpowelll Aug 25, 2016 Collaborator

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

@ashfurrow
ashfurrow Aug 25, 2016 Collaborator

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

@rpowelll
Collaborator

From a brief look, this seems great. I'll have a more thorough look at it whenever I can find the time.

ashfurrow added some commits Aug 25, 2016
@ashfurrow ashfurrow Progress towards generating main manifest. 40bdf6f
@ashfurrow ashfurrow Adds main Contents manifest generator. 8eda4c0
@ashfurrow
Collaborator

Yeah, I need to think a little bit more about how to copy playgrounds to become individual chapters and pages in a book. I'm thinking we should expect specifically formatted playgrounds, and get chapter/page metadata in the yaml file.

@ashfurrow
Collaborator

So the real issue here is that there isn't a one-to-one mapping from playgrounds to playground books. Specifically, books have the ability to share code among chapters, and chapters can share among pages. Without some sort of fileprivate regexing, I don't see how we can automate the conversion from several playgrounds to a playground book without more faffing than it would take to do it manually.

I'll sit on this for the weekend, but I'm not sure if it's worth it to proceed. On the bright side: I no longer wonder why Apple didn't provide tools to do this – it's really tough!

ashfurrow added some commits Aug 26, 2016
@ashfurrow ashfurrow Basics of collating. 0e3a986
@ashfurrow ashfurrow Chapter manifest collating. 9f7da0e
@ashfurrow ashfurrow Finished chapter collation. b5f3059
@ashfurrow ashfurrow Page writing. 194df71
@ashfurrow ashfurrow Resources. 7793899
@ashfurrow ashfurrow Updates documentation. e791a1d
@ashfurrow
Collaborator

Okay, this is looking pretty good! I've updated the documentation in e791a1d to reflect the new usage. I still need to actually test it to see if it works, but in theory it should. Good thing we have a tool to lint playground books and find out what's wrong 😉

@ashfurrow ashfurrow commented on the diff Aug 26, 2016
lib/playgroundbook_renderer/playgroundbook_renderer.rb
+require 'playgroundbook_renderer/contents_manifest_generator'
+require 'playgroundbook_renderer/chapter_collator'
+
+module Playgroundbook
+ ContentsDirName = 'Contents'
+ ChaptersDirName = 'Chapters'
+
+ # A renderer for playground books.
+ class Renderer < AbstractLinter
+ attr_accessor :yaml_file_name
+ attr_accessor :contents_manifest_generator
+ attr_accessor :chapter_collator
+ attr_accessor :ui
+
+ def initialize(yaml_file_name,
+ contents_manifest_generator = ContentsManifestGenerator.new,
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

This is currently passing in no parameters even though one is required. We need to move that parameter to the generate! function instead of the initializer.

@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

Fixed now.

ashfurrow added some commits Aug 26, 2016
@ashfurrow ashfurrow Fix for initializer. 75746b9
@ashfurrow ashfurrow Fix for method name & require. d9e51e2
@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
lib/playgroundbook_renderer/playgroundbook_renderer.rb
+ book = yaml_contents
+ book_dir_name = "#{book['name']}.playgroundbook"
+ book_chapter_contents = []
+ # TODO: Validate YAML contents?
+ begin
+ book_chapter_contents = book['chapters'].map do |chapter|
+ File.read("#{chapter}.playground/Contents.swift")
+ end
+ rescue => e
+ ui.puts 'Failed to open playground Contents.swift file.'
+ raise e
+ end
+
+ Dir.mkdir(book_dir_name) unless Dir.exist?(book_dir_name)
+ Dir.chdir(book_dir_name) do
+ resources_dir = book['resources']
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

This needs to go in the Contents subdirectory.

@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
...laygroundbook_renderer/contents_manifest_generator.rb
+ @ui.puts "Manifest file generated."
+ end
+
+ def write_manifest_file!
+ File.open(ManifestFileName, 'w') do |file|
+ file.write(manifest_contents.to_plist)
+ end
+ end
+
+ def manifest_contents
+ chapters = @book_metadata['chapters'].map{ |c| "#{c}.playgroundchapter" }
+ {
+ 'Name' => @book_metadata['name'],
+ 'ContentIdentifier' => @book_metadata['identifier'],
+ 'DeploymentTarget' => @book_metadata['deployment_target'] || 'ios10.0',
+ 'Chapters' => chapters
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

This dictionary is missing keys present in the reference playground book. Namely, Swift Playgrounds is complaining that "Version" is missing.

@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
lib/playgroundbook.rb
@@ -0,0 +1,8 @@
+require 'playgroundbook_lint/playgroundbook_lint'
+require 'playgroundbook_renderer/playgroundbook_renderer'
+
+module Playgroundbook
+ ManifestFileName = 'Manifest.plist'.freeze
+ CONTENTS_SWIFT_FILE_NAME = 'Contents.swift'.freeze
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

This doesn't match the others.

ashfurrow added some commits Aug 26, 2016
@ashfurrow ashfurrow Fixes constant name styling issue. 482d497
@ashfurrow ashfurrow Fixes shared resources folder location. ab0981d
@ashfurrow ashfurrow Fixes missing values in Manifest. 237c3d3
@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
lib/playgroundbook_renderer/playgroundbook_renderer.rb
+ book_chapter_contents = book['chapters'].map do |chapter|
+ File.read("#{chapter}.playground/Contents.swift")
+ end
+ rescue => e
+ ui.puts 'Failed to open playground Contents.swift file.'
+ raise e
+ end
+
+ Dir.mkdir(book_dir_name) unless Dir.exist?(book_dir_name)
+ Dir.chdir(book_dir_name) do
+ Dir.mkdir(ContentsDirName) unless Dir.exist?(ContentsDirName)
+ Dir.chdir(ContentsDirName) do
+ resources_dir = book['resources']
+ if !(resources_dir.nil? || resources_dir.empty?)
+ Dir.mkdir(ResourcesDirectoryName) unless Dir.exist?(ResourcesDirectoryName)
+ FileUtils.cp_r("../../#{resources_dir}/*", ResourcesDirectoryName)
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

This isn't working anymore.

@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
lib/playgroundbook_renderer/chapter_collator.rb
+ 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)
+ # So we need to divide the chapter_file_contents into pages, write those
+ # pages & their manifests to their respective directories, and generate a
+ # chapter manifest. Easy. DONE AWW YEAH.
+ # Oh, and write the shared preamble. ALSO DONE.
+ # And assets. Let's make that book-wide for simplicity. DONE.
+
+ pages = parse_pages(chapter_file_contents)
@ashfurrow
ashfurrow Aug 26, 2016 edited Collaborator

This entire method needs to go inside a named .playgroundchapter directory.

@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
lib/playgroundbook_renderer/chapter_collator.rb
+ 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)
+ # So we need to divide the chapter_file_contents into pages, write those
+ # pages & their manifests to their respective directories, and generate a
+ # chapter manifest. Easy. DONE AWW YEAH.
+ # Oh, and write the shared preamble. ALSO DONE.
+ # And assets. Let's make that book-wide for simplicity. DONE.
+
+ pages = parse_pages(chapter_file_contents)
+
+ pages[:page_names].each_with_index do |page_name, index|
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

This loop needs to be done inside a Pages subdirectory.

ashfurrow added some commits Aug 26, 2016
@ashfurrow ashfurrow Fixes for #15. fdd8f2d
@ashfurrow ashfurrow Updated fixtures. 1ac6605
@ashfurrow ashfurrow commented on an outdated diff Aug 26, 2016
spec/fixtures/test_chapter.playground/Contents.swift
@@ -0,0 +1,19 @@
+import UIKit
+
+public var str = "Hello, playground"
+
+public func sharedFunc() {
+ print("This should be accessible to all pages.")
+}
+
+//// Page 1.
+import UIKit
@ashfurrow
ashfurrow Aug 26, 2016 Collaborator

These currently need to be repeated for each page. Probably going to fix that. Maybe in book.yml...

ashfurrow added some commits Aug 26, 2016
@ashfurrow ashfurrow Fixes import issue. 301eee2
@ashfurrow ashfurrow Added extra logging. e01caa0
@ashfurrow
Collaborator

Okay, I think this is good to review.

ashfurrow added some commits Aug 27, 2016
@ashfurrow ashfurrow Added limitations section to readme. e4e62dc
@ashfurrow ashfurrow Hides imports. 0fe75ec
@ashfurrow
Collaborator

Okay, I've tested this out locally and I'm really happy with it. I'm going to merge + release 0.2.0 but if anyone has any feedback on this PR, please leave it and I can follow-up.

@ashfurrow ashfurrow merged commit f1c1656 into master Aug 27, 2016

1 check passed

ci/circleci Your tests passed on CircleCI!
Details
@ashfurrow ashfurrow deleted the render branch Aug 27, 2016
@orta orta commented on the diff Aug 27, 2016
README.md
+//// 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:
@orta
orta Aug 27, 2016 Member

experessions

@orta orta commented on the diff Aug 27, 2016
lib/playgroundbook_renderer/chapter_collator.rb
+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"
@orta
orta Aug 27, 2016 Member

chater_directory_name

@orta orta commented on the diff Aug 27, 2016
lib/playgroundbook_renderer/chapter_collator.rb
+ 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(/\/\/\/\/.*$/)
@orta
orta Aug 27, 2016 Member

8 sea dragon

@orta orta commented on the diff Aug 27, 2016
...undbook_renderer_spec/playgroundbook_renderer_spec.rb
+
+ it 'does not explode when the Chapters directory already exists' do
+ expect { renderer.render! }.to_not raise_error
+ end
+
+ it 'generates each chapter' do
+ expect(chapter_collator).to receive(:collate!)
+
+ renderer.render!
+ end
+ end
+ end
+ end
+ end
+ end
+end
@orta
orta Aug 27, 2016 Member

this is well tested 👍

@ashfurrow
ashfurrow Aug 27, 2016 Collaborator

🙇

@ashfurrow ashfurrow added a commit that referenced this pull request Aug 27, 2016
@ashfurrow ashfurrow Fixes from @orta on #15. 68bac2c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment