eggplant 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +66 -0
- data/README.md +30 -0
- data/Rakefile +8 -0
- data/bin/eggplant +5 -0
- data/eggplant.gemspec +34 -0
- data/lib/eggplant.rb +8 -0
- data/lib/eggplant/assets/css/eggplant/main.css +77 -0
- data/lib/eggplant/assets/css/vendor/deck.core.css +393 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.codemirror.css +89 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.goto.css +41 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.hash.css +13 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.menu.css +24 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.navigation.css +43 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.scale.css +16 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.status.css +14 -0
- data/lib/eggplant/assets/css/vendor/extensions/deck.subslides.css +9 -0
- data/lib/eggplant/assets/css/vendor/themes/neon.css +114 -0
- data/lib/eggplant/assets/css/vendor/themes/swiss.css +75 -0
- data/lib/eggplant/assets/css/vendor/themes/web-2.0.css +187 -0
- data/lib/eggplant/assets/css/vendor/transitions/fade.css +43 -0
- data/lib/eggplant/assets/css/vendor/transitions/horizontal-slide.css +79 -0
- data/lib/eggplant/assets/css/vendor/transitions/vertical-slide.css +97 -0
- data/lib/eggplant/assets/js/eggplant/main.js +38 -0
- data/lib/eggplant/assets/js/eggplant/remote.js +0 -0
- data/lib/eggplant/assets/js/vendor/coffee-script.js +8 -0
- data/lib/eggplant/assets/js/vendor/deck.core.js +457 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.goto.js +118 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.hash.js +113 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.menu.js +127 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.navigation.js +83 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.scale.js +155 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.status.js +42 -0
- data/lib/eggplant/assets/js/vendor/extensions/deck.subslides.js +50 -0
- data/lib/eggplant/assets/js/vendor/jquery.js +9046 -0
- data/lib/eggplant/assets/js/vendor/less.js +2769 -0
- data/lib/eggplant/assets/js/vendor/modernizr.js +1116 -0
- data/lib/eggplant/cli.rb +113 -0
- data/lib/eggplant/markdown.rb +76 -0
- data/lib/eggplant/server.rb +168 -0
- data/lib/eggplant/slides.rb +65 -0
- data/lib/eggplant/version.rb +3 -0
- data/lib/eggplant/views/remote.slim +17 -0
- data/lib/eggplant/views/show.slim +47 -0
- metadata +281 -0
data/lib/eggplant/cli.rb
ADDED
@@ -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,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
|