playgroundbook 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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