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.
- 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
|