playgroundbook 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +2 -0
  5. data/.ruby-version +1 -0
  6. data/Changelog.md +17 -0
  7. data/Gemfile +12 -0
  8. data/Gemfile.lock +103 -0
  9. data/Guardfile +14 -0
  10. data/LICENSE +21 -0
  11. data/README.md +92 -0
  12. data/Rakefile +8 -0
  13. data/bin/playgroundbook +22 -0
  14. data/lib/playgroundbook.rb +9 -0
  15. data/lib/playgroundbook_lint/abstract_linter.rb +23 -0
  16. data/lib/playgroundbook_lint/chapter_linter.rb +34 -0
  17. data/lib/playgroundbook_lint/chapter_manifest_linter.rb +43 -0
  18. data/lib/playgroundbook_lint/contents_linter.rb +20 -0
  19. data/lib/playgroundbook_lint/cutscene_page_linter.rb +17 -0
  20. data/lib/playgroundbook_lint/cutscene_page_manifest_linter.rb +18 -0
  21. data/lib/playgroundbook_lint/manifest_linter.rb +39 -0
  22. data/lib/playgroundbook_lint/page_linter.rb +23 -0
  23. data/lib/playgroundbook_lint/page_manifest_linter.rb +18 -0
  24. data/lib/playgroundbook_lint/playgroundbook_lint.rb +31 -0
  25. data/lib/playgroundbook_lint/root_manifest_linter.rb +36 -0
  26. data/lib/playgroundbook_renderer/chapter_collator.rb +77 -0
  27. data/lib/playgroundbook_renderer/contents_manifest_generator.rb +33 -0
  28. data/lib/playgroundbook_renderer/page_writer.rb +34 -0
  29. data/lib/playgroundbook_renderer/playgroundbook_renderer.rb +75 -0
  30. data/lib/version.rb +3 -0
  31. data/playground_book_lint.gemspec +21 -0
  32. data/spec/fixtures/Starter.playgroundbook/Contents/Chapters/Chapter1.playgroundchapter/Manifest.plist +15 -0
  33. data/spec/fixtures/Starter.playgroundbook/Contents/Chapters/Chapter1.playgroundchapter/Pages/Page1.playgroundpage/Contents.swift +8 -0
  34. data/spec/fixtures/Starter.playgroundbook/Contents/Chapters/Chapter1.playgroundchapter/Pages/Page1.playgroundpage/Manifest.plist +12 -0
  35. data/spec/fixtures/Starter.playgroundbook/Contents/Chapters/Chapter1.playgroundchapter/Pages/Page2.playgroundpage/Contents.swift +8 -0
  36. data/spec/fixtures/Starter.playgroundbook/Contents/Chapters/Chapter1.playgroundchapter/Pages/Page2.playgroundpage/Manifest.plist +12 -0
  37. data/spec/fixtures/Starter.playgroundbook/Contents/Manifest.plist +20 -0
  38. data/spec/fixtures/assets/file.jpeg +0 -0
  39. data/spec/fixtures/book.yml +5 -0
  40. data/spec/fixtures/test_chapter.playground/Contents.swift +17 -0
  41. data/spec/fixtures/test_chapter.playground/contents.xcplayground +4 -0
  42. data/spec/playground_book_lint/chapter_linter_spec.rb +30 -0
  43. data/spec/playground_book_lint/chapter_manifest_linter_spec.rb +40 -0
  44. data/spec/playground_book_lint/contents_linter_spec.rb +18 -0
  45. data/spec/playground_book_lint/cutscene_page_linter_spec.rb +14 -0
  46. data/spec/playground_book_lint/cutscene_page_manifest_linter_spec.rb +63 -0
  47. data/spec/playground_book_lint/manfiest_linter_spec.rb +71 -0
  48. data/spec/playground_book_lint/page_linter_spec.rb +19 -0
  49. data/spec/playground_book_lint/page_manifest_linter_spec.rb +43 -0
  50. data/spec/playground_book_lint/playgroundbook_lint_spec.rb +38 -0
  51. data/spec/playground_book_lint/root_manifest_linter_spec.rb +35 -0
  52. data/spec/playgroundbook_renderer_spec/chapter_collator_spec.rb +70 -0
  53. data/spec/playgroundbook_renderer_spec/contents_manfiest_generator_spec.rb +37 -0
  54. data/spec/playgroundbook_renderer_spec/page_writer_spec.rb +57 -0
  55. data/spec/playgroundbook_renderer_spec/playgroundbook_renderer_spec.rb +112 -0
  56. data/spec/spec_helper.rb +75 -0
  57. metadata +141 -0
@@ -0,0 +1,43 @@
1
+ require 'playgroundbook_lint/manifest_linter'
2
+ require 'playgroundbook_lint/page_linter'
3
+ require 'playgroundbook_lint/cutscene_page_linter'
4
+
5
+ module Playgroundbook
6
+ # A linter for verifying the contents of a chapter's Manifest.plist
7
+ class ChapterManifestLinter < ManifestLinter
8
+ attr_accessor :page_linter, :cutscene_page_linter
9
+
10
+ def initialize(page_linter = PageLinter.new, cutscene_page_linter = CutscenePageLinter.new)
11
+ @page_linter = page_linter
12
+ @cutscene_page_linter = cutscene_page_linter
13
+ end
14
+
15
+ def lint
16
+ super()
17
+
18
+ fail_lint "Chapter has no pages in #{Dir.pwd}" unless chapter_has_manifest_pages?
19
+
20
+ manifest_plist_contents['Pages'].each do |page_directory_name|
21
+ # All pages exist inside the /Pages subdirectory, we need to chdir to there first.
22
+ Dir.chdir PAGES_DIRECTORY_NAME do
23
+ fail_lint "Chapter page directory #{page_directory_name} missing in #{Dir.pwd}" unless Dir.exist?(page_directory_name)
24
+ lint_page page_directory_name
25
+ end
26
+ end
27
+ end
28
+
29
+ def lint_page(page_directory_name)
30
+ Dir.chdir page_directory_name do
31
+ if page_directory_name =~ /.+\.playgroundpage$/
32
+ page_linter.lint
33
+ elsif page_directory_name =~ /.+\.cutscenepage$/
34
+ cutscene_page_linter.lint
35
+ end
36
+ end
37
+ end
38
+
39
+ def chapter_has_manifest_pages?
40
+ value_defined_in_manifest?('Pages')
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ require 'playgroundbook_lint/abstract_linter'
2
+ require 'playgroundbook_lint/root_manifest_linter'
3
+
4
+ module Playgroundbook
5
+ # A linter for verifying the contents directory of a playground book
6
+ class ContentsLinter < AbstractLinter
7
+ attr_accessor :root_manfiest_linter
8
+
9
+ def initialize(root_manfiest_linter = RootManifestLinter.new)
10
+ @root_manfiest_linter = root_manfiest_linter
11
+ end
12
+
13
+ def lint
14
+ Dir.chdir 'Contents' do
15
+ root_manfiest_linter.lint
16
+ # TODO: Other linting?
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ require 'playgroundbook_lint/abstract_linter'
2
+ require 'playgroundbook_lint/cutscene_page_manifest_linter'
3
+
4
+ module Playgroundbook
5
+ # A linter for verifying cutscene pages
6
+ class CutscenePageLinter < AbstractLinter
7
+ attr_accessor :cutscene_page_manifest_linter
8
+
9
+ def initialize(cutscene_page_manifest_linter = CutscenePageManifestLinter.new)
10
+ @cutscene_page_manifest_linter = cutscene_page_manifest_linter
11
+ end
12
+
13
+ def lint
14
+ cutscene_page_manifest_linter.lint
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ require 'playgroundbook_lint/manifest_linter'
2
+
3
+ module Playgroundbook
4
+ # A linter for verifying the contents of a cutscene page's manifest
5
+ class CutscenePageManifestLinter < ManifestLinter
6
+ attr_accessor :page_manifest_linter
7
+
8
+ def lint
9
+ super()
10
+
11
+ # Cutscene references should point to an existent HTML file
12
+ cutscene_reference = manifest_plist_contents['CutsceneReference']
13
+ fail_lint "Cutscene manifest doesn't reference a cutscene file" if cutscene_reference.nil?
14
+ fail_lint "Cutscene file at '#{cutscene_reference}' isn't HTML" unless cutscene_reference =~ /^.+\.html$/i
15
+ fail_lint "Cutscene file at '#{cutscene_reference}' doesn't exist" unless File.exist? cutscene_reference
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ require 'plist'
2
+ require 'playgroundbook_lint/abstract_linter'
3
+
4
+ module Playgroundbook
5
+ MANIFEST_FILE_NAME = 'Manifest.plist'.freeze
6
+
7
+ # A base inplementation of a linter for verifying the contents of manifest
8
+ # files.
9
+ class ManifestLinter < AbstractLinter
10
+ # TODO: Should load manifest file in initialize instead of lazily.
11
+
12
+ def lint
13
+ fail_lint "No Manifest file in #{Dir.pwd}" unless manifest_file_exists?
14
+ fail_lint "Manifest file missing Name in #{Dir.pwd}" unless name?
15
+ end
16
+
17
+ def manifest_file_exists?
18
+ File.exist? MANIFEST_FILE_NAME
19
+ end
20
+
21
+ def manifest_plist_contents
22
+ return @manifest_plist_contents unless @manifest_plist_contents.nil?
23
+ require 'plist'
24
+ @manifest_plist_contents = Plist.parse_xml(MANIFEST_FILE_NAME)
25
+ @manifest_plist_contents
26
+ end
27
+
28
+ def name?
29
+ value_defined_in_manifest?('Name')
30
+ end
31
+
32
+ def value_defined_in_manifest?(key)
33
+ return false if manifest_plist_contents.nil?
34
+ return false if manifest_plist_contents[key].nil?
35
+ return false if manifest_plist_contents[key].empty?
36
+ true
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ require 'playgroundbook_lint/abstract_linter'
2
+ require 'playgroundbook_lint/page_manifest_linter'
3
+
4
+ module Playgroundbook
5
+ # A linter for verifying the contents of a page directory
6
+ class PageLinter < AbstractLinter
7
+ attr_accessor :page_manifest_linter
8
+
9
+ def initialize(page_manifest_linter = PageManifestLinter.new)
10
+ @page_manifest_linter = page_manifest_linter
11
+ end
12
+
13
+ def lint
14
+ fail_lint "Missing #{ContentsSwiftFileName} in #{Dir.pwd}" unless contents_swift_file_exists?
15
+
16
+ page_manifest_linter.lint
17
+ end
18
+
19
+ def contents_swift_file_exists?
20
+ File.exist? ContentsSwiftFileName
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ require 'plist'
2
+ require 'playgroundbook_lint/manifest_linter'
3
+
4
+ module Playgroundbook
5
+ # A linter for verifying the contents of a page's Manifest.plist
6
+ class PageManifestLinter < ManifestLinter
7
+ SUPPORTED_LIVE_VIEW_MODES = %w(VisibleByDefault HiddenByDefault).freeze
8
+
9
+ def lint
10
+ super()
11
+
12
+ live_view_mode = manifest_plist_contents['LiveViewMode']
13
+ unless live_view_mode.nil?
14
+ fail_lint "Unsopported LiveViewMoode '#{live_view_mode}'" unless SUPPORTED_LIVE_VIEW_MODES.include? live_view_mode
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ require 'colored'
2
+ require 'playgroundbook_lint/abstract_linter'
3
+ require 'playgroundbook_lint/contents_linter'
4
+ require 'pathname'
5
+
6
+ module Playgroundbook
7
+ # A linter for verifying a playground book
8
+ class Linter < AbstractLinter
9
+ attr_accessor :playground_file_name
10
+ attr_accessor :contents_linter
11
+
12
+ def initialize(playground_file_name, contents_linter = ContentsLinter.new)
13
+ @playground_file_name = playground_file_name
14
+ @contents_linter = contents_linter
15
+ end
16
+
17
+ def lint
18
+ message "Validating #{playground_file_name.yellow}..."
19
+
20
+ fail_lint 'No Contents directory' unless contents_dir_exists?
21
+
22
+ Dir.chdir playground_file_name do
23
+ contents_linter.lint
24
+ end
25
+ end
26
+
27
+ def contents_dir_exists?
28
+ Dir.exist?(playground_file_name + '/Contents')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ require 'plist'
2
+ require 'playgroundbook_lint/manifest_linter'
3
+ require 'playgroundbook_lint/chapter_linter'
4
+
5
+ module Playgroundbook
6
+ # A linter for verifying the contents of a playground book's root manifest
7
+ class RootManifestLinter < ManifestLinter
8
+ attr_accessor :chapter_linter
9
+
10
+ def initialize(chapter_linter = ChapterLinter.new)
11
+ @chapter_linter = chapter_linter
12
+ end
13
+
14
+ def lint
15
+ super()
16
+
17
+ fail_lint 'No Chapters directory' unless chapters_directory_exist?
18
+ fail_lint 'No Chapters specified' unless chapters_exist?
19
+
20
+ # Go into Chapters/ and then each chapter directory, then lint it.
21
+ Dir.chdir 'Chapters' do
22
+ manifest_plist_contents['Chapters'].each do |chapter_directory_name|
23
+ chapter_linter.lint(chapter_directory_name)
24
+ end
25
+ end
26
+ end
27
+
28
+ def chapters_directory_exist?
29
+ Dir.exist? 'Chapters'
30
+ end
31
+
32
+ def chapters_exist?
33
+ value_defined_in_manifest?('Chapters')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ require 'plist'
2
+ require 'playgroundbook_renderer/page_writer'
3
+
4
+ module Playgroundbook
5
+ SharedSourcesDirectoryName = 'Sources'
6
+ PreambleFileName = 'Preamble.swift'
7
+
8
+ class ChapterCollator
9
+ def initialize(page_writer = PageWriter.new, ui = Cork::Board.new)
10
+ @page_writer = page_writer
11
+ @ui = ui
12
+ end
13
+
14
+ def collate!(chapter_name, chapter_file_contents, imports)
15
+ @ui.puts "Processing #{chapter_name.green}."
16
+
17
+ chater_directory_name = "#{chapter_name}.playgroundchapter"
18
+ Dir.mkdir(chater_directory_name) unless Dir.exist?(chater_directory_name)
19
+ Dir.chdir(chater_directory_name) do
20
+ pages = parse_pages(chapter_file_contents)
21
+
22
+ Dir.mkdir(PagesDirectoryName) unless Dir.exist?(PagesDirectoryName)
23
+ Dir.chdir(PagesDirectoryName) do
24
+ pages[:page_names].each_with_index do |page_name, index|
25
+ @ui.puts " Processing #{page_name.green}."
26
+
27
+ page_contents = pages[:page_contents][index]
28
+ page_dir_name = pages[:page_dir_names][index]
29
+
30
+ @page_writer.write_page!(page_name, page_dir_name, imports, page_contents)
31
+ end
32
+ end
33
+
34
+ write_chapter_manifest!(chapter_name, pages[:page_dir_names])
35
+ write_preamble!(pages[:preamble])
36
+ end
37
+ end
38
+
39
+ def parse_pages(swift)
40
+ page_names = swift.scan(/\/\/\/\/.*$/).map { |p| p.gsub('////', '').strip }
41
+ page_dir_names = page_names.map { |p| "#{p}.playgroundpage" }
42
+
43
+ split_file = swift.split(/\/\/\/\/.*$/)
44
+ page_contents = split_file.drop(1).map { |p| p.strip }
45
+ preamble = split_file.first.strip
46
+
47
+ {
48
+ page_dir_names: page_dir_names,
49
+ page_names: page_names,
50
+ page_contents: page_contents,
51
+ preamble: preamble,
52
+ }
53
+ end
54
+
55
+ def write_chapter_manifest!(chapter_name, page_dir_names)
56
+ manifest_contents = {
57
+ 'Name' => chapter_name,
58
+ 'Pages' => page_dir_names,
59
+ 'Version' => '1.0',
60
+ 'ContentVersion' => '1.0',
61
+ }
62
+ File.open(ManifestFileName, 'w') do |file|
63
+ file.write(manifest_contents.to_plist)
64
+ end
65
+ end
66
+
67
+ def write_preamble!(preamble)
68
+ Dir.mkdir(SharedSourcesDirectoryName) unless Dir.exist?(SharedSourcesDirectoryName)
69
+
70
+ Dir.chdir(SharedSourcesDirectoryName) do
71
+ File.open(PreambleFileName, 'w') do |file|
72
+ file.write(preamble)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,33 @@
1
+ require 'plist'
2
+
3
+ module Playgroundbook
4
+ class ContentsManifestGenerator
5
+ def initialize(ui = Cork::Board.new)
6
+ @ui = ui
7
+ end
8
+
9
+ def generate!(book_metadata)
10
+ @ui.puts "Generating main manifest file."
11
+ write_manifest_file!(book_metadata)
12
+ @ui.puts "Manifest file generated."
13
+ end
14
+
15
+ def write_manifest_file!(book_metadata)
16
+ File.open(ManifestFileName, 'w') do |file|
17
+ file.write(manifest_contents(book_metadata).to_plist)
18
+ end
19
+ end
20
+
21
+ def manifest_contents(book_metadata)
22
+ chapters = book_metadata['chapters'].map{ |c| "#{c}.playgroundchapter" }
23
+ {
24
+ 'Name' => book_metadata['name'],
25
+ 'ContentIdentifier' => book_metadata['identifier'],
26
+ 'DeploymentTarget' => book_metadata['deployment_target'] || 'ios10.0',
27
+ 'Chapters' => chapters,
28
+ 'Version' => '1.0',
29
+ 'ContentVersion' => '1.0',
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ require 'plist'
2
+
3
+ module Playgroundbook
4
+ class PageWriter
5
+
6
+ def initialize(ui = Cork::Board.new)
7
+ @ui = ui
8
+ end
9
+
10
+ def write_page!(page_name, page_dir_name, imports, page_contents)
11
+ Dir.mkdir(page_dir_name) unless Dir.exist?(page_dir_name)
12
+
13
+ contents_with_import = "//#-hidden-code\n"
14
+ contents_with_import += imports.map { |i| "import #{i}" }.join("\n") + "\n"
15
+ contents_with_import += "//#-end-hidden-code\n"
16
+ contents_with_import += page_contents
17
+
18
+ Dir.chdir(page_dir_name) do
19
+ File.open(ContentsSwiftFileName, 'w') do |file|
20
+ file.write(contents_with_import)
21
+ end
22
+
23
+ File.open(MANIFEST_FILE_NAME, 'w') do |file|
24
+ file.write ({
25
+ 'Name' => page_name,
26
+ 'LiveViewMode' => 'HiddenByDefault',
27
+ 'Version' => '1.0',
28
+ 'ContentVersion' => '1.0',
29
+ }.to_plist)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,75 @@
1
+ require 'colored'
2
+ require 'pathname'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'playgroundbook_renderer/contents_manifest_generator'
6
+ require 'playgroundbook_renderer/chapter_collator'
7
+
8
+ module Playgroundbook
9
+ ContentsDirName = 'Contents'
10
+ ChaptersDirName = 'Chapters'
11
+
12
+ # A renderer for playground books.
13
+ class Renderer < AbstractLinter
14
+ attr_accessor :yaml_file_name
15
+ attr_accessor :contents_manifest_generator
16
+ attr_accessor :chapter_collator
17
+ attr_accessor :ui
18
+
19
+ def initialize(yaml_file_name,
20
+ contents_manifest_generator = ContentsManifestGenerator.new,
21
+ chapter_collator = ChapterCollator.new,
22
+ ui = Cork::Board.new)
23
+ @yaml_file_name = yaml_file_name
24
+ @contents_manifest_generator = contents_manifest_generator
25
+ @chapter_collator = chapter_collator
26
+ @ui = ui
27
+ end
28
+
29
+ def render!
30
+ ui.puts "Rendering #{yaml_file_name.green}..."
31
+
32
+ book = yaml_contents
33
+ book_dir_name = "#{book['name']}.playgroundbook"
34
+ book_chapter_contents = []
35
+ # TODO: Validate YAML contents?
36
+ begin
37
+ book_chapter_contents = book['chapters'].map do |chapter|
38
+ File.read("#{chapter}.playground/Contents.swift")
39
+ end
40
+ rescue => e
41
+ ui.puts 'Failed to open playground Contents.swift file.'
42
+ raise e
43
+ end
44
+
45
+ Dir.mkdir(book_dir_name) unless Dir.exist?(book_dir_name)
46
+ Dir.chdir(book_dir_name) do
47
+ Dir.mkdir(ContentsDirName) unless Dir.exist?(ContentsDirName)
48
+ Dir.chdir(ContentsDirName) do
49
+ resources_dir = book['resources']
50
+ if !(resources_dir.nil? || resources_dir.empty?)
51
+ @ui.puts "Copying resource directory (#{resources_dir.green}) contents."
52
+ Dir.mkdir(ResourcesDirectoryName) unless Dir.exist?(ResourcesDirectoryName)
53
+ Dir.glob("../../#{resources_dir}/*").each do |file|
54
+ FileUtils.cp(file, ResourcesDirectoryName)
55
+ end
56
+ end
57
+ @contents_manifest_generator.generate!(book)
58
+
59
+ Dir.mkdir(ChaptersDirName) unless Dir.exist?(ChaptersDirName)
60
+ Dir.chdir(ChaptersDirName) do
61
+ # Chapter file name becomes chapter name in playground book.
62
+ book['chapters'].each_with_index do |chapter_file_name, index|
63
+ chapter_file_contents = book_chapter_contents[index]
64
+ @chapter_collator.collate!(chapter_file_name, chapter_file_contents, book['imports'] || ['UIKit'])
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def yaml_contents
72
+ YAML.load(File.open(@yaml_file_name))
73
+ end
74
+ end
75
+ end