parade 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.md +542 -0
- data/Rakefile +15 -0
- data/bin/parade +138 -0
- data/lib/parade.rb +43 -0
- data/lib/parade/commands/commands.rb +84 -0
- data/lib/parade/commands/generate_outline.rb +34 -0
- data/lib/parade/commands/generate_presentation.rb +34 -0
- data/lib/parade/commands/generate_rackup.rb +32 -0
- data/lib/parade/commands/html_output.rb +47 -0
- data/lib/parade/commands/render_from_template.rb +50 -0
- data/lib/parade/commands/static_html.rb +38 -0
- data/lib/parade/commands/static_pdf.rb +39 -0
- data/lib/parade/commands/unknown.rb +23 -0
- data/lib/parade/features/live_ruby.rb +18 -0
- data/lib/parade/features/pdf_presentation.rb +24 -0
- data/lib/parade/features/preshow.rb +11 -0
- data/lib/parade/helpers/encode_image.rb +24 -0
- data/lib/parade/helpers/template_generator.rb +130 -0
- data/lib/parade/metadata.rb +73 -0
- data/lib/parade/metadata/assignment.rb +38 -0
- data/lib/parade/metadata/css_classes.rb +22 -0
- data/lib/parade/metadata/html_id.rb +35 -0
- data/lib/parade/metadata/template.rb +31 -0
- data/lib/parade/parsers/dsl.rb +138 -0
- data/lib/parade/parsers/dsl_file_parser.rb +17 -0
- data/lib/parade/parsers/json_file_parser.rb +67 -0
- data/lib/parade/parsers/markdown_image_paths.rb +44 -0
- data/lib/parade/parsers/markdown_slide_splitter.rb +63 -0
- data/lib/parade/parsers/presentation_directory_parser.rb +36 -0
- data/lib/parade/parsers/presentation_file_parser.rb +27 -0
- data/lib/parade/parsers/presentation_filepath_parser.rb +35 -0
- data/lib/parade/parsers/slides_file_content_parser.rb +27 -0
- data/lib/parade/renderers/columns_renderer.rb +68 -0
- data/lib/parade/renderers/command_line_renderer.rb +142 -0
- data/lib/parade/renderers/html_with_pygments.rb +42 -0
- data/lib/parade/renderers/inline_images.rb +31 -0
- data/lib/parade/renderers/special_paragraph_renderer.rb +23 -0
- data/lib/parade/renderers/update_image_paths.rb +75 -0
- data/lib/parade/section.rb +183 -0
- data/lib/parade/server.rb +139 -0
- data/lib/parade/slide.rb +128 -0
- data/lib/parade/version.rb +3 -0
- data/lib/public/css/960.css +653 -0
- data/lib/public/css/fg.menu.css +114 -0
- data/lib/public/css/ghf_marked.css +180 -0
- data/lib/public/css/jquery-terminal.css +73 -0
- data/lib/public/css/onepage.css +62 -0
- data/lib/public/css/parade.css +450 -0
- data/lib/public/css/pdf.css +13 -0
- data/lib/public/css/reset.css +53 -0
- data/lib/public/css/spinner_bar.gif +0 -0
- data/lib/public/css/theme/images/ui-bg_diagonals-small_100_f0efea_40x40.png +0 -0
- data/lib/public/css/theme/images/ui-bg_flat_35_f0f0f0_40x100.png +0 -0
- data/lib/public/css/theme/images/ui-bg_glass_55_fcf0ba_1x400.png +0 -0
- data/lib/public/css/theme/images/ui-bg_glow-ball_25_2e2e28_600x600.png +0 -0
- data/lib/public/css/theme/images/ui-bg_highlight-soft_100_f0efea_1x100.png +0 -0
- data/lib/public/css/theme/images/ui-bg_highlight-soft_25_327E04_1x100.png +0 -0
- data/lib/public/css/theme/images/ui-bg_highlight-soft_25_5A9D1A_1x100.png +0 -0
- data/lib/public/css/theme/images/ui-bg_highlight-soft_95_ffedad_1x100.png +0 -0
- data/lib/public/css/theme/images/ui-bg_inset-soft_22_3b3b35_1x100.png +0 -0
- data/lib/public/css/theme/images/ui-icons_808080_256x240.png +0 -0
- data/lib/public/css/theme/images/ui-icons_8DC262_256x240.png +0 -0
- data/lib/public/css/theme/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/public/css/theme/images/ui-icons_e7e6e4_256x240.png +0 -0
- data/lib/public/css/theme/images/ui-icons_eeeeee_256x240.png +0 -0
- data/lib/public/css/theme/images/ui-icons_ffffff_256x240.png +0 -0
- data/lib/public/css/theme/ui.accordion.css +9 -0
- data/lib/public/css/theme/ui.all.css +2 -0
- data/lib/public/css/theme/ui.base.css +9 -0
- data/lib/public/css/theme/ui.core.css +37 -0
- data/lib/public/css/theme/ui.datepicker.css +62 -0
- data/lib/public/css/theme/ui.dialog.css +13 -0
- data/lib/public/css/theme/ui.progressbar.css +4 -0
- data/lib/public/css/theme/ui.resizable.css +13 -0
- data/lib/public/css/theme/ui.slider.css +17 -0
- data/lib/public/css/theme/ui.tabs.css +9 -0
- data/lib/public/css/theme/ui.theme.css +245 -0
- data/lib/public/favicon.ico +0 -0
- data/lib/public/js/coffee-script.js +8 -0
- data/lib/public/js/fg.menu.js +645 -0
- data/lib/public/js/jTypeWriter.js +26 -0
- data/lib/public/js/jquery-1.4.2.js +6240 -0
- data/lib/public/js/jquery-print.js +109 -0
- data/lib/public/js/jquery-pubsub.js +27 -0
- data/lib/public/js/jquery-terminal.js +2712 -0
- data/lib/public/js/jquery.batchImageLoad.js +56 -0
- data/lib/public/js/jquery.cycle.all.js +1284 -0
- data/lib/public/js/keyboard.js +733 -0
- data/lib/public/js/parade-code-execution.js +122 -0
- data/lib/public/js/parade-command-input.js +16 -0
- data/lib/public/js/parade-command-visor.js +92 -0
- data/lib/public/js/parade-keyboard-input.js +54 -0
- data/lib/public/js/parade.js +675 -0
- data/lib/public/js/spine.js +904 -0
- data/lib/templates/config.ru.erb +4 -0
- data/lib/templates/showoff.erb +27 -0
- data/lib/templates/slides.md.erb +25 -0
- data/lib/views/header.erb +73 -0
- data/lib/views/index.erb +53 -0
- data/lib/views/inline_css.erb +3 -0
- data/lib/views/inline_js.erb +3 -0
- data/lib/views/onepage.erb +17 -0
- data/lib/views/pdf.erb +17 -0
- data/lib/views/slide.erb +5 -0
- metadata +317 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'presentation_directory_parser'
|
2
|
+
require_relative 'presentation_file_parser'
|
3
|
+
require_relative 'slides_file_content_parser'
|
4
|
+
|
5
|
+
module Parade
|
6
|
+
module Parsers
|
7
|
+
|
8
|
+
class PresentationFilepathParser
|
9
|
+
|
10
|
+
def self.parse(filepath,options = {})
|
11
|
+
return nil unless File.exists? filepath
|
12
|
+
|
13
|
+
if File.directory? filepath
|
14
|
+
PresentationDirectoryParser.parse filepath, options
|
15
|
+
else
|
16
|
+
|
17
|
+
if presentation_file?(filepath)
|
18
|
+
PresentationFileParser.parse filepath, options
|
19
|
+
else
|
20
|
+
SlidesFileContentParser.parse filepath, options
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def self.presentation_file?(filepath)
|
29
|
+
File.basename(filepath) == "parade"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'markdown_image_paths'
|
2
|
+
require_relative 'markdown_slide_splitter'
|
3
|
+
|
4
|
+
module Parade
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
module SlidesFileContentParser
|
8
|
+
def self.parse(filepath,options = {})
|
9
|
+
slides_content = File.read(filepath)
|
10
|
+
relative_path = File.dirname(filepath).gsub(options[:root_path].gsub(/\/$/,''),'')
|
11
|
+
slides_content = MarkdownImagePaths.parse(slides_content,:path => relative_path)
|
12
|
+
|
13
|
+
create_section_with slides_content
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.create_section_with(slides_content)
|
19
|
+
section = Section.new
|
20
|
+
section.add_slides(MarkdownSlideSplitter.parse(slides_content))
|
21
|
+
section
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Parade
|
2
|
+
module Renderers
|
3
|
+
|
4
|
+
#
|
5
|
+
# With the given HTML content, search for the CSS class for the HTML element
|
6
|
+
# and when found generate columns for each element found. The size of the
|
7
|
+
# columns is a division of the number of segments.
|
8
|
+
#
|
9
|
+
class ColumnsRenderer
|
10
|
+
|
11
|
+
attr_accessor :css_class
|
12
|
+
attr_accessor :html_element
|
13
|
+
attr_accessor :segments
|
14
|
+
|
15
|
+
#
|
16
|
+
# @example Creating a ColumnsRenderer
|
17
|
+
#
|
18
|
+
# Creation of a column renderer that will look for slides with the class
|
19
|
+
# 'columns', and create columns out of all h2 elements found, dividing
|
20
|
+
# them across 12 elements.
|
21
|
+
#
|
22
|
+
# ColumnsRenderer.new(:css_class => 'columns',:html_element => "h2",:segments => 12)
|
23
|
+
#
|
24
|
+
def initialize(params={})
|
25
|
+
params.each {|k,v| send("#{k}=",v) if respond_to? "#{k}=" }
|
26
|
+
end
|
27
|
+
|
28
|
+
def render(content)
|
29
|
+
|
30
|
+
html = Nokogiri::XML.fragment(content)
|
31
|
+
parser = CommandlineParser.new
|
32
|
+
|
33
|
+
html.css(".#{css_class}").each do |slide|
|
34
|
+
|
35
|
+
columns = []
|
36
|
+
slop = []
|
37
|
+
|
38
|
+
chunks = slide.children.chunk {|child| child.name == html_element }
|
39
|
+
|
40
|
+
slide.children = ""
|
41
|
+
|
42
|
+
slide['class'] += " container_#{segments}"
|
43
|
+
current_column = slide
|
44
|
+
|
45
|
+
column_count = chunks.find_all {|is_column,contents| is_column }.count
|
46
|
+
|
47
|
+
chunks.each do |is_column,contents|
|
48
|
+
|
49
|
+
if is_column
|
50
|
+
slide.add_child current_column unless current_column == slide
|
51
|
+
current_column = Nokogiri::XML::Node.new('div',html)
|
52
|
+
current_column['class'] = "grid_#{ segments / column_count }"
|
53
|
+
end
|
54
|
+
|
55
|
+
contents.each {|content| current_column.add_child content }
|
56
|
+
end
|
57
|
+
|
58
|
+
slide.add_child current_column
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
html.to_s
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
module Parade
|
4
|
+
module Renderers
|
5
|
+
|
6
|
+
#
|
7
|
+
# Slides that have been marked as 'commandline' will be processed here to
|
8
|
+
# ensure that the command portion is played out as if typed. Followed by
|
9
|
+
# the result which appears after the command is completed.
|
10
|
+
#
|
11
|
+
# @example Slide marker to denote a command-line slide
|
12
|
+
#
|
13
|
+
# !SLIDE commandline incremental
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# To denote the command, it needs to be prefaced with a `$`. The remaining
|
17
|
+
# code is considered to be the result.
|
18
|
+
#
|
19
|
+
# @example Contents of a slide to show a command and a result
|
20
|
+
#
|
21
|
+
# ```bash
|
22
|
+
# $ git commit -am 'incremental bullet points working'
|
23
|
+
# [master ac5fd8a] incremental bullet points working
|
24
|
+
# 2 files changed, 32 insertions(+), 5 deletions(-)
|
25
|
+
# ```
|
26
|
+
#
|
27
|
+
class CommandLineRenderer
|
28
|
+
|
29
|
+
#
|
30
|
+
# @param [String] html_content the html content of a single slide that
|
31
|
+
# will have the commandline rendered correctly if it is a class on
|
32
|
+
# the slide.
|
33
|
+
# @return [String] the same html content if there is no commandline class
|
34
|
+
# or the new rendered html content with the new required HTML elements.
|
35
|
+
#
|
36
|
+
def self.render(html_content)
|
37
|
+
|
38
|
+
html = Nokogiri::XML.fragment(html_content)
|
39
|
+
parser = CommandlineParser.new
|
40
|
+
|
41
|
+
html.css('.commandline pre').each do |code|
|
42
|
+
out = code.text
|
43
|
+
code.content = ''
|
44
|
+
tree = parser.parse(out)
|
45
|
+
transform = Parslet::Transform.new do
|
46
|
+
rule(:prompt => simple(:prompt), :input => simple(:input), :output => simple(:output)) do
|
47
|
+
command = Nokogiri::XML::Node.new('pre', html)
|
48
|
+
command.set_attribute('class', 'command')
|
49
|
+
command.content = "#{prompt} #{input}"
|
50
|
+
code << command
|
51
|
+
|
52
|
+
# Add newline after the input so that users can
|
53
|
+
# advance faster than the typewriter effect
|
54
|
+
# and still keep inputs on separate lines.
|
55
|
+
code << "\n"
|
56
|
+
|
57
|
+
unless output.to_s.empty?
|
58
|
+
result = Nokogiri::XML::Node.new('pre', html)
|
59
|
+
result.set_attribute('class', 'result')
|
60
|
+
result.content = output
|
61
|
+
code << result
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
transform.apply(tree)
|
67
|
+
end
|
68
|
+
|
69
|
+
html.to_s
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# For parsing commandline slide content.
|
76
|
+
class CommandlineParser < Parslet::Parser
|
77
|
+
|
78
|
+
rule(:prompt) do
|
79
|
+
str('$') | str('#') | str('>>')
|
80
|
+
end
|
81
|
+
|
82
|
+
rule(:text) do
|
83
|
+
match['[:print:]'].repeat
|
84
|
+
end
|
85
|
+
|
86
|
+
rule(:singleline_input) do
|
87
|
+
(str("\\\n").absent? >> match['[:print:]']).repeat
|
88
|
+
end
|
89
|
+
|
90
|
+
rule(:input) do
|
91
|
+
multiline_input | singleline_input
|
92
|
+
end
|
93
|
+
|
94
|
+
rule(:multiline_input) do
|
95
|
+
|
96
|
+
# some command \
|
97
|
+
# continued \
|
98
|
+
# \
|
99
|
+
# and stop
|
100
|
+
( singleline_input >> str('\\') >> newline ).repeat(1) >> singleline_input
|
101
|
+
end
|
102
|
+
|
103
|
+
rule(:command) do
|
104
|
+
|
105
|
+
# $ some command
|
106
|
+
# some output
|
107
|
+
( prompt.as(:prompt) >> space? >> input.as(:input) >> output? ).as(:command)
|
108
|
+
end
|
109
|
+
|
110
|
+
rule(:output) do
|
111
|
+
|
112
|
+
# output
|
113
|
+
prompt.absent? >> text
|
114
|
+
end
|
115
|
+
|
116
|
+
rule(:output?) do
|
117
|
+
|
118
|
+
#
|
119
|
+
# some text
|
120
|
+
# some text
|
121
|
+
#
|
122
|
+
# some text
|
123
|
+
( newline >> ( ( output >> newline ).repeat >> output.maybe ).as(:output) ).maybe
|
124
|
+
end
|
125
|
+
|
126
|
+
rule(:commands) do
|
127
|
+
command.repeat
|
128
|
+
end
|
129
|
+
|
130
|
+
rule(:newline) do
|
131
|
+
str("\n") | str("\r\n")
|
132
|
+
end
|
133
|
+
|
134
|
+
rule(:space?) do
|
135
|
+
match['[:space:]'].repeat
|
136
|
+
end
|
137
|
+
|
138
|
+
root(:commands)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
require 'pygments.rb'
|
3
|
+
|
4
|
+
module Parade
|
5
|
+
module Renderers
|
6
|
+
class HTMLwithPygments < Redcarpet::Render::XHTML
|
7
|
+
|
8
|
+
#
|
9
|
+
# When rendering the markdown, the code should be rendered using the
|
10
|
+
# Pygments highlight which corresponds to the ghf_marked.css
|
11
|
+
#
|
12
|
+
# Additionally a class `sh_javascript` or `sh_ruby` is added that will
|
13
|
+
# assist in providing a system to provide live interactive elements
|
14
|
+
# through the javascript defined in `parade.js`.
|
15
|
+
#
|
16
|
+
# @param [String] code the fenced code to be highlighted
|
17
|
+
# @param [String] language the name of the fenced code
|
18
|
+
#
|
19
|
+
def block_code(code, language)
|
20
|
+
syntax_highlighted_html = Pygments.highlight code, :lexer => language,
|
21
|
+
:options => {:encoding => 'utf-8'}
|
22
|
+
|
23
|
+
syntax_highlighted_html.gsub('class="highlight"',"class=\"highlight sh_#{language}\"")
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.render(content)
|
27
|
+
markdown = Redcarpet::Markdown.new(Renderers::HTMLwithPygments,
|
28
|
+
:fenced_code_blocks => true,
|
29
|
+
:no_intra_emphasis => true,
|
30
|
+
:autolink => true,
|
31
|
+
:strikethrough => true,
|
32
|
+
:lax_html_blocks => true,
|
33
|
+
:superscript => true,
|
34
|
+
:hard_wrap => true,
|
35
|
+
:tables => true,
|
36
|
+
:xhtml => true)
|
37
|
+
markdown.render(content)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Parade
|
3
|
+
module Renderers
|
4
|
+
|
5
|
+
#
|
6
|
+
# This Renderer will inline the images into content output. Allowing you
|
7
|
+
# to create portable documents.
|
8
|
+
#
|
9
|
+
module InlineImages
|
10
|
+
|
11
|
+
def self.render(content,options = {})
|
12
|
+
|
13
|
+
content.gsub(/img src=["']\/?([^\/].*?)["']/) do |image_source|
|
14
|
+
image_name = Regexp.last_match(1)
|
15
|
+
|
16
|
+
base64_data = image_path_to_base64(image_name)
|
17
|
+
|
18
|
+
if base64_data
|
19
|
+
%{img src="#{base64_data}"}
|
20
|
+
else
|
21
|
+
image_source
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
extend Parade::Helpers::EncodeImage
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Parade
|
2
|
+
module Renderers
|
3
|
+
|
4
|
+
#
|
5
|
+
# Within the markdown you are able to add additional formatting by starting
|
6
|
+
# a new line with a period followed by the class name. This is commonly
|
7
|
+
# used for writing presenter notes.
|
8
|
+
#
|
9
|
+
# @example Adding a presenter note within the markdown
|
10
|
+
#
|
11
|
+
# ## This Slide has important details
|
12
|
+
# * Detail 1
|
13
|
+
# * Detail 2
|
14
|
+
# .notes Ensure that you talk about detail 1 the most!
|
15
|
+
#
|
16
|
+
class SpecialParagraphRenderer
|
17
|
+
def self.render(html_content)
|
18
|
+
html_content.gsub(/<p>\.(.*?) /, '<p class="\1">')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Parade
|
2
|
+
module Renderers
|
3
|
+
|
4
|
+
#
|
5
|
+
# UpdateImagePaths is used to ensure the img source paths in the HTML
|
6
|
+
# content is properly prefaced with the "image" path as that is necessary
|
7
|
+
# for the Sinatra server to properly know that it is to return an image.
|
8
|
+
#
|
9
|
+
# Additional processing of the image is provided if RMagick has been
|
10
|
+
# installed. Namely it sets the size correctly.
|
11
|
+
#
|
12
|
+
class UpdateImagePaths
|
13
|
+
|
14
|
+
attr_accessor :root_path
|
15
|
+
|
16
|
+
def initialize(params = {})
|
17
|
+
params.each {|k,v| send("#{k}=",v) if respond_to? "#{k}=" }
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# @param [String] content HTML content that is parsed for image srcs
|
22
|
+
# @param [Hash] options additional parameters, at the moment it is unused.
|
23
|
+
#
|
24
|
+
def self.render(content,options = {})
|
25
|
+
self.new(options).render(content)
|
26
|
+
end
|
27
|
+
|
28
|
+
def render(content,options = {})
|
29
|
+
render_root_path = options[:root_path] || root_path || "."
|
30
|
+
|
31
|
+
content.gsub(/img src=["'](?!https?:\/\/)\/?([^\/].*?)["']/) do |image_source|
|
32
|
+
image_name = Regexp.last_match(1)
|
33
|
+
|
34
|
+
html_image_path = File.join("/","image",image_name)
|
35
|
+
updated_image_source = %{img src="#{html_image_path}"}
|
36
|
+
|
37
|
+
html_asset_path = File.join(render_root_path,image_name)
|
38
|
+
width, height = get_image_size(html_asset_path)
|
39
|
+
updated_image_source << %( width="#{width}" height="#{height}") if width and height
|
40
|
+
|
41
|
+
updated_image_source
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_image_size(path) ; end
|
46
|
+
|
47
|
+
if defined?(Magick)
|
48
|
+
|
49
|
+
def get_image_size(path)
|
50
|
+
unless cached_image_size.key?(path)
|
51
|
+
|
52
|
+
image = Magick::Image.ping(path).first
|
53
|
+
|
54
|
+
# # don't set a size for svgs so they can expand to fit their container
|
55
|
+
if image.mime_type == 'image/svg+xml'
|
56
|
+
cached_image_size[path] = [nil, nil]
|
57
|
+
else
|
58
|
+
cached_image_size[path] = [image.columns, image.rows]
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
cached_image_size[path]
|
63
|
+
end
|
64
|
+
|
65
|
+
def cached_image_size
|
66
|
+
@cached_image_size ||= {}
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|