swift-playground 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +19 -0
  5. data/LICENSE +22 -0
  6. data/README.md +209 -0
  7. data/Rakefile +16 -0
  8. data/bin/swift-playground +5 -0
  9. data/lib/swift/playground.rb +193 -0
  10. data/lib/swift/playground/asset.rb +52 -0
  11. data/lib/swift/playground/assets/javascript.rb +9 -0
  12. data/lib/swift/playground/assets/stylesheet.rb +36 -0
  13. data/lib/swift/playground/cli.rb +29 -0
  14. data/lib/swift/playground/cli/commands/generate.rb +95 -0
  15. data/lib/swift/playground/cli/commands/new.rb +49 -0
  16. data/lib/swift/playground/cli/definition.rb +32 -0
  17. data/lib/swift/playground/cli/global/error_handling.rb +34 -0
  18. data/lib/swift/playground/cli/shared_attributes.rb +19 -0
  19. data/lib/swift/playground/cli/ui.rb +120 -0
  20. data/lib/swift/playground/debug.rb +4 -0
  21. data/lib/swift/playground/generator.rb +32 -0
  22. data/lib/swift/playground/metadata.rb +12 -0
  23. data/lib/swift/playground/section.rb +117 -0
  24. data/lib/swift/playground/sections/code_section.rb +19 -0
  25. data/lib/swift/playground/sections/documentation_section.rb +63 -0
  26. data/lib/swift/playground/template/Documentation/defaults.css.scss +96 -0
  27. data/lib/swift/playground/template/Documentation/section.html.erb +23 -0
  28. data/lib/swift/playground/template/contents.xcplayground.erb +8 -0
  29. data/lib/swift/playground/util.rb +3 -0
  30. data/lib/swift/playground/util/path_or_content.rb +31 -0
  31. data/lib/swift/playground/util/pipeline.rb +53 -0
  32. data/lib/swift/playground/util/pipeline/section_filter.rb +55 -0
  33. data/lib/swift/playground/util/pipeline/unicode_emoji_filter.rb +27 -0
  34. data/lib/swift/playground/util/syntax_highlighting.rb +24 -0
  35. data/swift-playground.gemspec +29 -0
  36. metadata +207 -0
@@ -0,0 +1,52 @@
1
+ module Swift
2
+ class Playground
3
+ assets_path = Pathname.new('swift/playground/assets')
4
+ autoload :Stylesheet, assets_path.join('stylesheet')
5
+ autoload :Javascript, assets_path.join('javascript')
6
+
7
+ class Asset
8
+ include Util::PathOrContent
9
+
10
+ class << self
11
+ protected
12
+
13
+ def default_filename(filename = nil)
14
+ @default_filename = filename unless filename.nil?
15
+ @default_filename
16
+ end
17
+ end
18
+
19
+ attr_accessor :content
20
+
21
+ def initialize(content, options = {})
22
+ pathname_or_content = path_or_content_as_io(content)
23
+ self.content = pathname_or_content.read
24
+
25
+ filename = options[:filename] || derived_filename(pathname_or_content)
26
+ @filename = filename || default_filename
27
+ end
28
+
29
+ def filename(number)
30
+ @filename % number
31
+ end
32
+
33
+ def save(destination_path, number)
34
+ destination_path = Pathname.new(destination_path)
35
+
36
+ expanded_filename = filename(number)
37
+ path = destination_path.join(expanded_filename)
38
+
39
+ FileUtils.mkdir_p path.dirname
40
+ path.open('w') do |file|
41
+ file.write content
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ def default_filename
48
+ self.class.send(:default_filename)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ require 'sass'
2
+
3
+ module Swift
4
+ class Playground
5
+ class Javascript < Asset
6
+ default_filename 'javascript-%d.js'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ require 'sass'
2
+
3
+ module Swift
4
+ class Playground
5
+ class Stylesheet < Asset
6
+ default_filename 'stylesheet-%d.css'
7
+
8
+ def save(destination_path, number)
9
+ save_content
10
+
11
+ self.content = Sass.compile(content)
12
+ super(destination_path, number)
13
+
14
+ restore_content
15
+ end
16
+
17
+ protected
18
+
19
+ def derived_filename(pathname_or_content)
20
+ filename = super(pathname_or_content)
21
+ filename.gsub(/\.scss$/, '')
22
+ end
23
+
24
+ private
25
+
26
+ def save_content
27
+ @saved_content = content
28
+ end
29
+
30
+ def restore_content
31
+ self.content = @saved_content
32
+ @saved_content = nil
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ require 'gli'
2
+ require_relative 'cli/definition'
3
+ require_relative 'cli/shared_attributes'
4
+ require_relative 'cli/ui'
5
+ require_relative 'cli/commands/new'
6
+ require_relative 'cli/commands/generate'
7
+ require_relative 'cli/global/error_handling'
8
+
9
+ require_relative 'generator'
10
+
11
+ module Swift
12
+ class Playground
13
+ module CLI
14
+ extend GLI::App
15
+
16
+ program_desc SUMMARY
17
+ version VERSION
18
+
19
+ subcommand_option_handling :normal
20
+ arguments :strict
21
+ sort_help :manually
22
+
23
+ include Commands::Generate
24
+ include Commands::New
25
+
26
+ include Global::ErrorHandling
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,95 @@
1
+ module Swift::Playground::CLI
2
+ module Commands
3
+ module Generate
4
+ extend Definition
5
+
6
+ definition do
7
+ desc 'Generate a playground file from the provided Markdown file'
8
+ arg '<markdown_file>'
9
+ arg '<playground_file>', :optional
10
+ command :generate do |c|
11
+ c.extend SharedCreationSwitches
12
+
13
+ c.flag :stylesheet,
14
+ arg_name: '<file>',
15
+ type: String,
16
+ desc: 'CSS stylesheet for the HTML documentation sections of the playground. SASS/SCSS syntax is supported. This will be included after the default stylesheet.'
17
+
18
+ c.flag :javascript,
19
+ arg_name: '<file>',
20
+ type: String,
21
+ desc: 'A javascript file for the HTML documentation sections of the playground. Each section is rendered independently of another and the script will not have access to the DOM from any other sections.'
22
+
23
+ c.switch :emoji,
24
+ default_value: true,
25
+ desc: "Convert emoji aliases (e.g. `:+1:`) into emoji characters."
26
+
27
+ c.switch :highlighting,
28
+ default_value: true,
29
+ desc: "Detect non-swift code blocks and add syntax highlighting. Only has an effect if 'github-linguist' and 'pygments.rb' gems are installed."
30
+
31
+ c.flag :'highlighting-style',
32
+ arg_name: '<style>',
33
+ type: 'String',
34
+ default_value: 'default',
35
+ desc: "The name of a pygments (http://pygments.org/) style to apply to syntax highlighted code blocks. Set to 'custom' if providing your own pygments-compatible stylesheet. Ignored if --no-highlighting is set."
36
+
37
+ # c.flag :resources,
38
+ # arg_name: '<directory>',
39
+ # type: String,
40
+ # desc: 'A directory of resources to be bundled with the playground.'
41
+
42
+ c.action do |_, options, args|
43
+ markdown_file = Pathname.new(args[0]).expand_path
44
+ if args[1]
45
+ playground_file = Pathname.new(args[1]).expand_path
46
+ else
47
+ playground_file = markdown_file.sub_ext('.playground')
48
+ end
49
+
50
+ playground = Swift::Playground::Generator.generate(markdown_file)
51
+ playground.platform = options['platform']
52
+ playground.allow_reset = options['reset']
53
+ playground.convert_emoji = options['emoji']
54
+
55
+ if options['highlighting']
56
+ playground.syntax_highlighting = options['highlighting-style']
57
+ else
58
+ playground.syntax_highlighting = false
59
+ end
60
+
61
+ if options['stylesheet']
62
+ stylesheet_path = Pathname.new(options['stylesheet']).expand_path
63
+
64
+ unless stylesheet_path.exist?
65
+ raise "Stylesheet file does not exist: '#{stylesheet_path}'."
66
+ end
67
+
68
+ stylesheet = Swift::Playground::Stylesheet.new(stylesheet_path)
69
+ playground.stylesheets << stylesheet
70
+ end
71
+
72
+ if options['javascript']
73
+ javascript_path = Pathname.new(options['javascript']).expand_path
74
+
75
+ unless javascript_path.exist?
76
+ raise "Javascript file does not exist: '#{javascript_path}'."
77
+ end
78
+
79
+ javascript = Swift::Playground::Javascript.new(javascript_path)
80
+ playground.javascripts << javascript
81
+ end
82
+
83
+ playground.save(playground_file)
84
+
85
+ UI.say "Created playground at #{playground_file}"
86
+
87
+ if options['open']
88
+ system('open', playground_file.to_s)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
3
+ module Swift::Playground::CLI
4
+ module Commands
5
+ module New
6
+ extend Definition
7
+
8
+ definition do
9
+ desc 'Create an empty playground (just as Xcode would via "File > New > Playground...")'
10
+ arg '<playground_file>'
11
+ command :new do |c|
12
+ c.extend SharedCreationSwitches
13
+
14
+ c.action do |_, options, args|
15
+ playground_file = Pathname.new(args[0]).expand_path
16
+
17
+ playground = Swift::Playground.new(platform: options[:platform])
18
+
19
+ case options[:platform]
20
+ when 'ios'
21
+ contents = <<-IOS.strip_heredoc
22
+ // Playground - noun: a place where people can play
23
+
24
+ import UIKit
25
+
26
+ var str = "Hello, playground"
27
+ IOS
28
+ when 'osx'
29
+ contents = <<-OSX.strip_heredoc
30
+ // Playground - noun: a place where people can play
31
+
32
+ import Cocoa
33
+
34
+ var str = "Hello, playground"
35
+ OSX
36
+ end
37
+
38
+ playground.sections << Swift::Playground::CodeSection.new(contents)
39
+ playground.save(playground_file)
40
+
41
+ if options['open']
42
+ system('open', playground_file.to_s)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/concern'
2
+
3
+ # This class makes it possible to provide helper methods in a module that will
4
+ # be included inside the main Swift::Playground::CLI module.
5
+ #
6
+ # It's unfortunately a little magical, but its difficult to work around this
7
+ # due to the way the GLI dsl is not designed to use anywhere except at the top
8
+ # level (or at best, inside a module) and not in a class.
9
+ module Swift::Playground::CLI
10
+ module Definition
11
+ # Include ActiveSupport::Concern methods, so this module behaves like
12
+ # ActiveSupport::Concern for any other module or class that extends it:
13
+ include ActiveSupport::Concern
14
+
15
+ def self.extended(mod)
16
+ # Use the behaviour of the ActiveSupport::Concern modules `self.extended`
17
+ # implementation:
18
+ ActiveSupport::Concern.extended(mod)
19
+ end
20
+
21
+ def definition(&block)
22
+ self.included do
23
+ # The use of `extend(self)` here makes sure that a module that extends
24
+ # the Definition module will have access to its methods from within
25
+ # the GLI command actions it defines. It will define these commands
26
+ # inside the block it passes to its call of the `definition` method.
27
+ extend(self)
28
+ self.class_eval(&block)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Swift::Playground::CLI
2
+ module Global
3
+ module ErrorHandling
4
+ extend Definition
5
+
6
+ definition do
7
+ on_error do |exception|
8
+ case exception
9
+ when Interrupt
10
+ UI.error
11
+ UI.error("Execution interrupted.")
12
+ when SystemExit
13
+ # An intentional early exit has occurred and all relevant messages
14
+ # have already been displayed, so do nothing
15
+ else
16
+ # We only want to display details of the exception under debug if it
17
+ # is not a GLI exception (as a GLI exception relates to parsing
18
+ # errors - e.g. wrong command, that we do not need to expand upon):
19
+ debug_exception = (exception.class.to_s !~ /\AGLI/) ? exception : nil
20
+
21
+ if exception.message
22
+ UI.error("Execution failed: #{exception.message}", debug_exception)
23
+ else
24
+ UI.error("Execution failed.", debug_exception)
25
+ end
26
+ end
27
+
28
+ false # Prevent default GLI error handling
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module Swift::Playground::CLI
2
+ module SharedCreationSwitches
3
+ def self.extended(command)
4
+ command.flag :platform,
5
+ default_value: 'ios',
6
+ arg_name: '[ios|osx]',
7
+ must_match: %w{ios osx},
8
+ desc: 'The target platform for the generated playground.'
9
+
10
+ command.switch :reset,
11
+ default_value: true,
12
+ desc: 'Allow the playground to be reset to it\'s original state via "Editor > Reset Playground" in Xcode.'
13
+
14
+ command.switch :open,
15
+ negatable: false,
16
+ desc: 'Open the playground in Xcode once it has been created.'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,120 @@
1
+ require 'highline/import'
2
+ require 'paint'
3
+ require 'forwardable'
4
+ require 'active_support/core_ext/module'
5
+
6
+ unless STDOUT.tty?
7
+ # If we aren't using a TTY, then we need to avoid Highline attempting to set
8
+ # TTY specific features (such as 'no echo mode'), as these will fail. We can
9
+ # do so by monkey patching Highline to make the methods that peform these
10
+ # functions no-ops:
11
+ class HighLine
12
+ module SystemExtensions
13
+ def raw_no_echo_mode
14
+ end
15
+
16
+ def restore_mode
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Paint::SHORTCUTS[:swift_playground] = {
23
+ :red => Paint.color(:red),
24
+ :blue => Paint.color(:blue),
25
+ :cyan => Paint.color(:cyan),
26
+ :bright => Paint.color(:bright)
27
+ }
28
+ $paint = Paint::SwiftPlayground
29
+
30
+ # Convenience module for accessing Highline features
31
+ module Swift::Playground::CLI
32
+ module UI
33
+ extend SingleForwardable
34
+
35
+ mattr_accessor :show_debug, :color_mode, :silence
36
+
37
+ def_delegators :$terminal, :agree, :ask, :choose
38
+ def_delegators :$paint, *Paint::SHORTCUTS[:swift_playground].keys
39
+
40
+ class << self
41
+ def say(message = "\n")
42
+ return if silence
43
+
44
+ terminal.say message
45
+ end
46
+
47
+ def error(message = nil, exception = nil)
48
+ return if silence
49
+
50
+ stderr.puts red(message)
51
+ if exception && show_debug
52
+ exception_details = ["Handled <#{exception.class}>:",
53
+ exception.message,
54
+ *exception.backtrace]
55
+ stderr.puts red("\n" + exception_details.join("\n") + "\n")
56
+ end
57
+ end
58
+
59
+ def debug(message = nil, &block)
60
+ return if silence
61
+
62
+ if show_debug
63
+ message = formatted_log_message(message, &block)
64
+ stderr.puts blue(message)
65
+ end
66
+ end
67
+
68
+ def info(message = nil, &block)
69
+ return if silence
70
+
71
+ if show_debug
72
+ message = formatted_log_message(message, &block)
73
+ stderr.puts cyan(message)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def formatted_log_message(message = nil, &block)
80
+ if block
81
+ lines = block.call.split("\n")
82
+ if message
83
+ message = "#{message}: "
84
+ message += "\n " if lines.count > 1
85
+ message += lines.join("\n ")
86
+ else
87
+ message += lines.join("\n")
88
+ end
89
+ end
90
+
91
+ message
92
+ end
93
+
94
+ def colorize(stream)
95
+ case (color_mode || 'auto')
96
+ when 'auto'
97
+ if stream.tty?
98
+ Paint.mode = Paint.detect_mode
99
+ else
100
+ Paint.mode = 0
101
+ end
102
+ when 'always'
103
+ Paint.mode = Paint.detect_mode
104
+ when 'never'
105
+ Paint.mode = 0
106
+ end
107
+ end
108
+
109
+ def terminal
110
+ colorize($stdout)
111
+ $terminal
112
+ end
113
+
114
+ def stderr
115
+ colorize($stderr)
116
+ $stderr
117
+ end
118
+ end
119
+ end
120
+ end