eggplant 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 (46) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +6 -0
  3. data/Gemfile.lock +66 -0
  4. data/README.md +30 -0
  5. data/Rakefile +8 -0
  6. data/bin/eggplant +5 -0
  7. data/eggplant.gemspec +34 -0
  8. data/lib/eggplant.rb +8 -0
  9. data/lib/eggplant/assets/css/eggplant/main.css +77 -0
  10. data/lib/eggplant/assets/css/vendor/deck.core.css +393 -0
  11. data/lib/eggplant/assets/css/vendor/extensions/deck.codemirror.css +89 -0
  12. data/lib/eggplant/assets/css/vendor/extensions/deck.goto.css +41 -0
  13. data/lib/eggplant/assets/css/vendor/extensions/deck.hash.css +13 -0
  14. data/lib/eggplant/assets/css/vendor/extensions/deck.menu.css +24 -0
  15. data/lib/eggplant/assets/css/vendor/extensions/deck.navigation.css +43 -0
  16. data/lib/eggplant/assets/css/vendor/extensions/deck.scale.css +16 -0
  17. data/lib/eggplant/assets/css/vendor/extensions/deck.status.css +14 -0
  18. data/lib/eggplant/assets/css/vendor/extensions/deck.subslides.css +9 -0
  19. data/lib/eggplant/assets/css/vendor/themes/neon.css +114 -0
  20. data/lib/eggplant/assets/css/vendor/themes/swiss.css +75 -0
  21. data/lib/eggplant/assets/css/vendor/themes/web-2.0.css +187 -0
  22. data/lib/eggplant/assets/css/vendor/transitions/fade.css +43 -0
  23. data/lib/eggplant/assets/css/vendor/transitions/horizontal-slide.css +79 -0
  24. data/lib/eggplant/assets/css/vendor/transitions/vertical-slide.css +97 -0
  25. data/lib/eggplant/assets/js/eggplant/main.js +38 -0
  26. data/lib/eggplant/assets/js/eggplant/remote.js +0 -0
  27. data/lib/eggplant/assets/js/vendor/coffee-script.js +8 -0
  28. data/lib/eggplant/assets/js/vendor/deck.core.js +457 -0
  29. data/lib/eggplant/assets/js/vendor/extensions/deck.goto.js +118 -0
  30. data/lib/eggplant/assets/js/vendor/extensions/deck.hash.js +113 -0
  31. data/lib/eggplant/assets/js/vendor/extensions/deck.menu.js +127 -0
  32. data/lib/eggplant/assets/js/vendor/extensions/deck.navigation.js +83 -0
  33. data/lib/eggplant/assets/js/vendor/extensions/deck.scale.js +155 -0
  34. data/lib/eggplant/assets/js/vendor/extensions/deck.status.js +42 -0
  35. data/lib/eggplant/assets/js/vendor/extensions/deck.subslides.js +50 -0
  36. data/lib/eggplant/assets/js/vendor/jquery.js +9046 -0
  37. data/lib/eggplant/assets/js/vendor/less.js +2769 -0
  38. data/lib/eggplant/assets/js/vendor/modernizr.js +1116 -0
  39. data/lib/eggplant/cli.rb +113 -0
  40. data/lib/eggplant/markdown.rb +76 -0
  41. data/lib/eggplant/server.rb +168 -0
  42. data/lib/eggplant/slides.rb +65 -0
  43. data/lib/eggplant/version.rb +3 -0
  44. data/lib/eggplant/views/remote.slim +17 -0
  45. data/lib/eggplant/views/show.slim +47 -0
  46. metadata +281 -0
@@ -0,0 +1,113 @@
1
+ require 'thor'
2
+ require 'eggplant/server'
3
+ require 'em-websocket'
4
+ require 'thin'
5
+ require 'json'
6
+
7
+ module Eggplant
8
+
9
+ class Channel < EventMachine::Channel
10
+ # Add items to the channel, which are pushed out to all subscribers except `name`
11
+ def push_except(name, *items)
12
+ items = items.dup
13
+ EM.schedule do
14
+ items.each do |i|
15
+ @subs.each do |key, value|
16
+ if key != name
17
+ value.call i
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ class CLI < Thor
26
+ include Thor::Actions
27
+
28
+ desc "new", "Create new presentation"
29
+ def new path='.'
30
+ path = File.expand_path path
31
+ puts "Created new presentation in #{path}"
32
+ end
33
+
34
+ desc "build", "Build presentation"
35
+ method_option :ui, :type => :array, :default => ['goto', 'status', 'nav']
36
+ def build html_file='./index.html'
37
+ path = File.expand_path '.'
38
+ html_file = File.expand_path html_file
39
+ puts "Build presentation in #{path}"
40
+ Eggplant::Server.configure path, options[:ui]
41
+ File.unlink html_file if File.exists?(html_file)
42
+ Eggplant::Server.build_html html_file
43
+ end
44
+
45
+ desc "show", "Run presentation server"
46
+ method_option :port, :type => :numeric, :aliases => '-p', :default => 4242
47
+ method_option :remote_port, :type => :numeric, :aliases => '-r', :default => 4444
48
+ method_option :mode, :type => :string, :aliases => '-m', :default => 'show'
49
+ method_option :ui, :type => :array, :default => ['goto']
50
+ def show
51
+ path = File.expand_path('.')
52
+ puts "Serving presentation from #{path}"
53
+
54
+ EventMachine.run do
55
+ # Server options
56
+ server_options = {
57
+ :port => options[:port],
58
+ :presentation_root => path,
59
+ :ui => options[:ui]
60
+ }
61
+ if options[:mode] == 'show'
62
+ server_options[:environment] = :production
63
+ end
64
+
65
+ # Eggplant::Server
66
+ Eggplant::Server.run! server_options
67
+
68
+ # Eggplant::WebSocket
69
+ @channel = Eggplant::Channel.new
70
+ @count = 0
71
+ @current_slide = 0
72
+
73
+ EventMachine::WebSocket.start(:host => '0.0.0.0', :port => options[:remote_port]) do |ws|
74
+ ws.onopen do
75
+ sid = @channel.subscribe do |msg|
76
+ ws.send msg
77
+ end
78
+ @count += 1
79
+ @channel.push({:type => 'join', :total_clients => @count, :current_slide => @current_slide}.to_json)
80
+
81
+ ws.onmessage do |msg|
82
+ message = JSON.parse msg
83
+ if message['type'] == 'goto'
84
+ @current_slide = message['idx']
85
+ end
86
+ @channel.push_except sid, "#{msg}"
87
+ end
88
+
89
+ ws.onclose do
90
+ @channel.unsubscribe sid
91
+ @count -= 1
92
+ @channel.push({:type => 'leave', :total_clients => @count}.to_json)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ desc "heroku", "Prepare for serving from heroku"
100
+ method_option :ui, :type => :array, :default => ['goto', 'status', 'nav']
101
+ def heroku
102
+ path = File.expand_path '.'
103
+ config_path = File.join path, 'config.ru'
104
+ gemfile_path = File.join path, 'Gemfile'
105
+ File.unlink(config_path) if File.exists?(config_path)
106
+ File.open(config_path, 'w') do |f|
107
+ f << "require 'eggplant/server'\n"
108
+ f << "Eggplant::Server.configure File.expand_path('.'), #{options[:ui].inspect}\n"
109
+ f << "run Eggplant::Server\n"
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,76 @@
1
+ #require 'cgi'
2
+ require 'albino'
3
+
4
+ module Eggplant
5
+ class Markdown
6
+ def initialize(markdown)
7
+ @markdown = markdown
8
+ end
9
+
10
+ def to_html
11
+ highlight!
12
+ #CGI.unescapeHTML
13
+ doc.search('body > *').to_html
14
+ end
15
+
16
+ private
17
+
18
+ def doc
19
+ @doc ||= Nokogiri::HTML(markup)
20
+ end
21
+
22
+ def highlight!
23
+ doc.search('div.code').each do |div|
24
+ lexer = div['rel'] || :javascript
25
+
26
+ lexted_text = Albino.new(div.text, lexer).to_s
27
+
28
+ highlighted = Nokogiri::HTML(lexted_text).at('div')
29
+
30
+ klasses = highlighted['class'].split(/\s+/)
31
+ klasses << lexer
32
+ klasses << 'code'
33
+ klasses << 'highlight'
34
+ highlighted['class'] = klasses.join(' ')
35
+
36
+ div.replace(highlighted)
37
+ end
38
+ end
39
+
40
+ # def markup
41
+ # @markup ||= begin
42
+ # t = RDiscount.new(@markdown.dup).to_html
43
+ # t.gsub!(/^(?:<p>)?@@@(?:<\/p>)?$/, '</div>')
44
+ # t.gsub!(/^(?:<p>)?@@@\s*([\w\+]+)(?:<\/p>)?$/, '<div class="code" rel="\1">')
45
+ # t
46
+ # end
47
+ # end
48
+
49
+ # def highlight!
50
+ # doc.search('code[data-mode]').each do |code|
51
+ # if ['javascript', 'coffeescript'].include?(code['data-mode'])
52
+ # code['data-exec'] = 'true'
53
+ # end
54
+ # if code['data-mode'] == 'html'
55
+ # code['data-mode'] = 'htmlmixed'
56
+ # end
57
+ # end
58
+ # end
59
+
60
+ def markup
61
+ @markup ||= begin
62
+ contents = []
63
+ markdown = @markdown.dup.gsub(/^@@@$/, '~@@@').gsub(/^\s*@@@\s*([\w\+]+)?$([^~]*)\n~@{3}$/) do |content|
64
+ i = contents.size
65
+ contents << $2 + "\n"
66
+ '<div class="code" rel="' + $1 + '">%@' + i.to_s + '@%</div>'
67
+ end
68
+ t = RDiscount.new(markdown).to_html
69
+ contents.size.times do |i|
70
+ t.gsub!("%@#{i}@%", contents[i])
71
+ end
72
+ t.gsub(/<p>\.(.*?)[\s\n]/, '<p class="\1">')
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,168 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'sinatra/base'
5
+ require 'slim'
6
+
7
+ require 'sinatra/assetpack'
8
+
9
+ require 'eggplant'
10
+
11
+ module Sinatra::AssetPack
12
+ class NoneEngine < Engine
13
+ def js(str, options={})
14
+ str
15
+ end
16
+ end
17
+
18
+ Compressor.register :js, :none, NoneEngine
19
+ end
20
+
21
+ module Eggplant
22
+ class Server < Sinatra::Base
23
+
24
+ set :root, File.dirname(__FILE__)
25
+
26
+ register Sinatra::AssetPack
27
+
28
+ assets do
29
+ serve '/js', from: 'assets/js'
30
+ serve '/css', from: 'assets/css'
31
+
32
+ js :eggplant, '/js/eggplant.js',
33
+ [
34
+ '/js/vendor/modernizr.js',
35
+ '/js/vendor/jquery.js',
36
+ # Deck
37
+ '/js/vendor/deck.core.js',
38
+ '/js/vendor/extensions/*.js',
39
+ '/js/eggplant/main.js'
40
+ ]
41
+
42
+ js :eggplant_processors, '/js/eggplant_processors.js',
43
+ [
44
+ #'/js/vendor/coffee-script.js',
45
+ '/js/vendor/less.js'
46
+ ]
47
+
48
+ js :eggplant_remote, '/js/eggplant_remote.js',
49
+ [
50
+ '/js/vendor/jquery.js',
51
+ '/js/eggplant/remote.js'
52
+ ]
53
+
54
+ css :eggplant, '/css/eggplant.css',
55
+ [
56
+ '/css/vendor/deck.core.css',
57
+ '/css/vendor/transitions/horizontal-slide.css',
58
+ '/css/vendor/extensions/*.css',
59
+ '/css/eggplant/main.css'
60
+ ]
61
+
62
+ js_compression :none, :no_fallback => true
63
+ end
64
+
65
+ get '/' do
66
+ slim :show
67
+ end
68
+
69
+ get '/remote' do
70
+ slim :remote
71
+ end
72
+
73
+ get '/images/:filename' do |filename|
74
+ path = File.join(settings.presentation_root, 'images', filename)
75
+ send_file_if_exists path
76
+ end
77
+
78
+ get '/assets/:filename' do |filename|
79
+ path = File.join(settings.presentation_root, 'assets', filename)
80
+ send_file_if_exists path
81
+ end
82
+
83
+ get '/slides.:format' do |format|
84
+ path = asset_path(format)
85
+ send_file_if_exists path
86
+ end
87
+
88
+ get '/download.:format' do |format|
89
+
90
+ end
91
+
92
+ def index
93
+ slim :show
94
+ end
95
+
96
+ def self.configure path, ui=['goto']
97
+ set :environment, :production
98
+ set :presentation_root, path
99
+ set :ui, ui
100
+ end
101
+
102
+ def self.build_html path
103
+ showtime = Eggplant::Server.new
104
+ while !showtime.is_a?(Eggplant::Server)
105
+ showtime = showtime.instance_variable_get(:@app)
106
+ end
107
+ html = showtime.send('index')
108
+ File.open(path, 'w') do |f|
109
+ f << html
110
+ end
111
+ end
112
+
113
+ helpers do
114
+ def render_slides
115
+ slides.to_html
116
+ end
117
+
118
+ def slides
119
+ @slides ||= Eggplant::Slides.new settings.presentation_root
120
+ end
121
+
122
+ def send_file_if_exists path
123
+ if File.exists?(path)
124
+ send_file path
125
+ else
126
+ not_found
127
+ end
128
+ end
129
+
130
+ def asset_path format
131
+ File.join(settings.presentation_root, "slides.#{format}")
132
+ end
133
+
134
+ def remote_enabled?
135
+ false
136
+ end
137
+
138
+ def has_js?
139
+ File.exists?(asset_path('js'))
140
+ end
141
+
142
+ def has_coffee?
143
+ File.exists?(asset_path('coffee'))
144
+ end
145
+
146
+ def has_css?
147
+ File.exists?(asset_path('css'))
148
+ end
149
+
150
+ def has_less?
151
+ File.exists?(asset_path('less'))
152
+ end
153
+
154
+ def show_status?
155
+ settings.ui.include?('status')
156
+ end
157
+
158
+ def show_goto?
159
+ settings.ui.include?('goto')
160
+ end
161
+
162
+ def show_navigation?
163
+ settings.ui.include?('nav')
164
+ end
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,65 @@
1
+ module Eggplant
2
+ class Slides
3
+
4
+ def initialize path, options={}
5
+ @slides = []
6
+ @title = nil
7
+ Dir[path+'/**/*.md'].each do |file|
8
+ if !(file =~ /README.md$/)
9
+ process file, File.read(file)
10
+ end
11
+ end
12
+ end
13
+
14
+ def size
15
+ @slides.size
16
+ end
17
+
18
+ def to_html
19
+ @slides.map do |slide|
20
+ render_slide slide[:body], slide[:options]
21
+ end.join "\n"
22
+ end
23
+
24
+ private
25
+
26
+ def process filename, content
27
+ # if there are no !SLIDE markers, then make the file define one slide
28
+ unless content =~ /^!SLIDE/m
29
+ content = "!SLIDE\n" + content
30
+ end
31
+
32
+ title = filename.split('/').pop.gsub(/\.md$/, '')
33
+ slides = content.split(/^!SLIDE/)
34
+ slides.delete('')
35
+ seq = 1 if slides.size > 1
36
+ slides.each do |slide|
37
+ slide, options = parse_options(title, slide.split("\n"))
38
+ @slides << {:body => slide, :options => options}
39
+ end
40
+ end
41
+
42
+ def render_slide body, options
43
+ body = Eggplant::Markdown.new(body).to_html
44
+ <<-HTML
45
+ <div class="slide #{options[:class]}"
46
+ data-title="#{options[:title]}">
47
+ #{body}
48
+ </div>
49
+ HTML
50
+ end
51
+
52
+ def parse_options title, lines
53
+ options = lines.shift.strip.scan(/[a-z]*="[^"]*"/i) rescue []
54
+ options_hash = {}
55
+ options.each do |option|
56
+ key, value = option.split('=')
57
+ options_hash[key.to_sym] = value.gsub('"', '')
58
+ end
59
+ options_hash[:title] ||= title
60
+ options_hash[:class] ||= ''
61
+ return lines.join("\n"), options_hash
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module Eggplant
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,17 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title ShowThem Remote
5
+ meta name="socket-port" content="4343"
6
+ == js :showtime_remote
7
+ == css :showtime_remote
8
+ body
9
+ nav
10
+ button#beginning
11
+ button#previous
12
+ button#next
13
+ #timer
14
+ time
15
+ .controls
16
+ button#start
17
+ button#reset