showoff 0.20.1 → 0.20.2
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.
- checksums.yaml +5 -5
- data/Rakefile +24 -12
- data/bin/showoff +47 -24
- data/lib/showoff.rb +43 -20
- data/lib/showoff/compiler.rb +106 -0
- data/lib/showoff/compiler/downloads.rb +91 -0
- data/lib/showoff/compiler/fixups.rb +142 -0
- data/lib/showoff/compiler/form.rb +236 -0
- data/lib/showoff/compiler/glossary.rb +164 -0
- data/lib/showoff/compiler/i18n.rb +24 -0
- data/lib/showoff/compiler/notes.rb +73 -0
- data/lib/showoff/compiler/table_of_contents.rb +51 -0
- data/lib/showoff/compiler/variables.rb +71 -0
- data/lib/showoff/config.rb +218 -0
- data/lib/showoff/locale.rb +132 -0
- data/lib/showoff/logger.rb +15 -0
- data/lib/showoff/monkeypatches.rb +28 -0
- data/lib/showoff/presentation.rb +181 -0
- data/lib/showoff/presentation/section.rb +70 -0
- data/lib/showoff/presentation/slide.rb +113 -0
- data/lib/showoff/state.rb +89 -0
- data/lib/showoff/version.rb +2 -2
- data/lib/showoff_ng.rb +99 -0
- data/lib/showoff_utils.rb +21 -19
- data/public/css/showoff.css +14 -1
- data/public/js/highlight.pack-9.15.10.js +22614 -0
- data/public/js/showoff.js +3 -3
- data/views/header.erb +3 -3
- data/views/header_mini.erb +2 -2
- data/views/onepage.erb +4 -10
- data/views/presenter.erb +5 -5
- data/views/slide.erb +29 -0
- metadata +24 -21
- data/locales/id.yml +0 -2
- data/public/js/highlight.pack-9.2.0.js +0 -15448
@@ -0,0 +1,164 @@
|
|
1
|
+
# adds glossary processing to the compiler
|
2
|
+
class Showoff::Compiler::Glossary
|
3
|
+
|
4
|
+
# Scan for glossary links and add definitions. This does not create the
|
5
|
+
# glossary page at the end.
|
6
|
+
#
|
7
|
+
# @param doc [Nokogiri::HTML::DocumentFragment]
|
8
|
+
# The slide document
|
9
|
+
#
|
10
|
+
# @return [Nokogiri::HTML::DocumentFragment]
|
11
|
+
# The slide DOM with all glossary entries rendered.
|
12
|
+
#
|
13
|
+
# @see
|
14
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L650-L706
|
15
|
+
def self.render!(doc)
|
16
|
+
|
17
|
+
# Find all callout style definitions on the slide and add links to the glossary page
|
18
|
+
doc.search('.callout.glossary').each do |item|
|
19
|
+
next unless item.content =~ /^([^|]+)\|([^:]+):(.*)$/
|
20
|
+
item['data-term'] = $1
|
21
|
+
item['data-target'] = $2
|
22
|
+
item['data-text'] = $3.strip
|
23
|
+
item.content = $3.strip
|
24
|
+
|
25
|
+
glossary = (item.attr('class').split - ['callout', 'glossary']).first
|
26
|
+
address = glossary ? "#{glossary}/#{$2}" : $2
|
27
|
+
|
28
|
+
link = Nokogiri::XML::Node.new('a', doc)
|
29
|
+
link.add_class('processed label')
|
30
|
+
link.set_attribute('href', "glossary://#{address}")
|
31
|
+
link.content = $1
|
32
|
+
|
33
|
+
item.prepend_child(link)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Find glossary links and add definitions to the notes
|
37
|
+
doc.search('a').each do |link|
|
38
|
+
next unless link['href']
|
39
|
+
next unless link['href'].start_with? 'glossary://'
|
40
|
+
next if link.classes.include? 'processed'
|
41
|
+
|
42
|
+
link.add_class('term')
|
43
|
+
|
44
|
+
term = link.content
|
45
|
+
text = link['title']
|
46
|
+
href = link['href']
|
47
|
+
|
48
|
+
parts = href.split('/')
|
49
|
+
target = parts.pop
|
50
|
+
name = parts.pop # either the glossary name or nil
|
51
|
+
|
52
|
+
label = link.clone
|
53
|
+
label.add_class('label processed')
|
54
|
+
|
55
|
+
definition = Nokogiri::XML::Node.new('p', doc)
|
56
|
+
definition.add_class("callout glossary #{name}")
|
57
|
+
definition.set_attribute('data-term', term)
|
58
|
+
definition.set_attribute('data-text', text)
|
59
|
+
definition.set_attribute('data-target', target)
|
60
|
+
definition.content = text
|
61
|
+
definition.prepend_child(label)
|
62
|
+
|
63
|
+
# @todo this duplication is annoying but it makes it less order dependent
|
64
|
+
doc.add_child '<div class="notes-section notes"></div>' if doc.search('div.notes-section.notes').empty?
|
65
|
+
doc.add_child '<div class="notes-section handouts"></div>' if doc.search('div.notes-section.handouts').empty?
|
66
|
+
|
67
|
+
[doc.css('div.notes-section.notes'), doc.css('div.notes-section.handouts')].each do |section|
|
68
|
+
section.first.add_child(definition.clone)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
doc
|
74
|
+
end
|
75
|
+
|
76
|
+
# Generate and add the glossary page
|
77
|
+
#
|
78
|
+
# @param doc [Nokogiri::HTML::DocumentFragment]
|
79
|
+
# The presentation document
|
80
|
+
#
|
81
|
+
# @return [Nokogiri::HTML::DocumentFragment]
|
82
|
+
# The presentation DOM with the glossary page rendered.
|
83
|
+
#
|
84
|
+
# @see
|
85
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L770-L810
|
86
|
+
def self.generatePage!(doc)
|
87
|
+
doc.search('.slide.glossary .content').each do |glossary|
|
88
|
+
name = (glossary.attr('class').split - ['content', 'glossary']).first
|
89
|
+
list = Nokogiri::XML::Node.new('ul', doc)
|
90
|
+
list.add_class('glossary terms')
|
91
|
+
seen = []
|
92
|
+
|
93
|
+
doc.search('.callout.glossary').each do |item|
|
94
|
+
target = (item.attr('class').split - ['callout', 'glossary']).first
|
95
|
+
|
96
|
+
# if the name matches or if we didn't name it to begin with.
|
97
|
+
next unless target == name
|
98
|
+
|
99
|
+
# the definition can exist in multiple places, so de-dup it here
|
100
|
+
term = item.attr('data-term')
|
101
|
+
next if seen.include? term
|
102
|
+
seen << term
|
103
|
+
|
104
|
+
# excrutiatingly find the parent slide content and grab the ref
|
105
|
+
# in a library less shitty, this would be something like
|
106
|
+
# $(this).parent().siblings('.content').attr('ref')
|
107
|
+
href = nil
|
108
|
+
item.ancestors('.slide').first.traverse do |element|
|
109
|
+
next if element['class'].nil?
|
110
|
+
next unless element['class'].split.include? 'content'
|
111
|
+
|
112
|
+
href = element.attr('ref').gsub('/', '_')
|
113
|
+
end
|
114
|
+
|
115
|
+
text = item.attr('data-text')
|
116
|
+
link = item.attr('data-target')
|
117
|
+
page = glossary.attr('ref')
|
118
|
+
anchor = "#{page}+#{link}"
|
119
|
+
next if href.nil? or text.nil? or link.nil?
|
120
|
+
|
121
|
+
entry = Nokogiri::XML::Node.new('li', doc)
|
122
|
+
|
123
|
+
label = Nokogiri::XML::Node.new('a', doc)
|
124
|
+
label.add_class('label')
|
125
|
+
label.set_attribute('id', anchor)
|
126
|
+
label.content = term
|
127
|
+
|
128
|
+
link = Nokogiri::XML::Node.new('a', doc)
|
129
|
+
label.add_class('return')
|
130
|
+
link.set_attribute('href', "##{href}")
|
131
|
+
link.content = '↩'
|
132
|
+
|
133
|
+
entry.add_child(label)
|
134
|
+
entry.add_child(Nokogiri::XML::Text.new(text, doc))
|
135
|
+
entry.add_child(link)
|
136
|
+
|
137
|
+
list.add_child(entry)
|
138
|
+
end
|
139
|
+
|
140
|
+
glossary.add_child(list)
|
141
|
+
end
|
142
|
+
|
143
|
+
# now fix all the links to point to the glossary page
|
144
|
+
doc.search('a').each do |link|
|
145
|
+
next if link['href'].nil?
|
146
|
+
next unless link['href'].start_with? 'glossary://'
|
147
|
+
|
148
|
+
href = link['href']
|
149
|
+
href.slice!('glossary://')
|
150
|
+
|
151
|
+
parts = href.split('/')
|
152
|
+
target = parts.pop
|
153
|
+
name = parts.pop # either the glossary name or nil
|
154
|
+
|
155
|
+
classes = name.nil? ? ".slide.glossary" : ".slide.glossary.#{name}"
|
156
|
+
href = doc.at("#{classes} .content").attr('ref') rescue nil
|
157
|
+
|
158
|
+
link['href'] = "##{href}+#{target}"
|
159
|
+
end
|
160
|
+
|
161
|
+
doc
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Adds slide language selection to the compiler
|
2
|
+
class Showoff::Compiler::I18n
|
3
|
+
|
4
|
+
def self.selectLanguage!(content)
|
5
|
+
translations = {}
|
6
|
+
content.scan(/^((~~~LANG:([\w-]+)~~~\n)(.+?)(\n~~~ENDLANG~~~))/m).each do |match|
|
7
|
+
markup, opentag, code, text, closetag = match
|
8
|
+
translations[code] = {:markup => markup, :content => text}
|
9
|
+
end
|
10
|
+
|
11
|
+
lang = Showoff::Locale.resolve(translations.keys).to_s
|
12
|
+
|
13
|
+
translations.each do |code, translation|
|
14
|
+
if code == lang
|
15
|
+
content.sub!(translation[:markup], translation[:content])
|
16
|
+
else
|
17
|
+
content.sub!(translation[:markup], "\n")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
content
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# adds presenter notes processing to the compiler
|
2
|
+
class Showoff::Compiler::Notes
|
3
|
+
|
4
|
+
# Generate the presenter notes sections, including personal notes
|
5
|
+
#
|
6
|
+
# @param doc [Nokogiri::HTML::DocumentFragment]
|
7
|
+
# The slide document
|
8
|
+
#
|
9
|
+
# @param profile [String]
|
10
|
+
# The markdown engine profile to use when rendering
|
11
|
+
#
|
12
|
+
# @param options [Hash] Options used for rendering any embedded markdown
|
13
|
+
# @option options [String] :name The markdown slide name
|
14
|
+
# @option options [String] :seq The sequence number for multiple slides in one file
|
15
|
+
#
|
16
|
+
# @return [Nokogiri::HTML::DocumentFragment]
|
17
|
+
# The slide DOM with all notes sections rendered.
|
18
|
+
#
|
19
|
+
# @see
|
20
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L616-L716
|
21
|
+
# @note
|
22
|
+
# A ton of the functionality in the original method got refactored to its logical location
|
23
|
+
def self.render!(doc, profile, options = {})
|
24
|
+
# Turn tags into classed divs.
|
25
|
+
doc.search('p').select {|p| p.text.start_with?('~~~SECTION:') }.each do |p|
|
26
|
+
klass = p.text.match(/~~~SECTION:([^~]*)~~~/)[1]
|
27
|
+
|
28
|
+
# Don't bother creating this if we don't want to use it
|
29
|
+
next unless Showoff::Config.includeNotes?(klass)
|
30
|
+
|
31
|
+
notes = Nokogiri::XML::Node.new('div', doc)
|
32
|
+
notes.add_class("notes-section #{klass}")
|
33
|
+
nodes = []
|
34
|
+
iter = p.next_sibling
|
35
|
+
until iter.text == '~~~ENDSECTION~~~' do
|
36
|
+
nodes << iter
|
37
|
+
iter = iter.next_sibling
|
38
|
+
|
39
|
+
# if the author forgot the closing tag, let's not crash, eh?
|
40
|
+
break unless iter
|
41
|
+
end
|
42
|
+
iter.remove if iter # remove the extraneous closing ~~~ENDSECTION~~~ tag
|
43
|
+
|
44
|
+
# We need to collect the list before moving or the iteration crashes since the iterator no longer has a sibling
|
45
|
+
nodes.each {|n| n.parent = notes }
|
46
|
+
|
47
|
+
p.replace(notes)
|
48
|
+
end
|
49
|
+
|
50
|
+
filename = [
|
51
|
+
File.join(Showoff::Config.root, '_notes', "#{options[:name]}.#{options[:seq]}.md"),
|
52
|
+
File.join(Showoff::Config.root, '_notes', "#{options[:name]}.md"),
|
53
|
+
].find {|path| File.file?(path) }
|
54
|
+
|
55
|
+
if filename and Showoff::Config.includeNotes?('notes')
|
56
|
+
# Make sure we've got a notes div to hang personal notes from
|
57
|
+
doc.add_child '<div class="notes-section notes"></div>' if doc.search('div.notes-section.notes').empty?
|
58
|
+
doc.search('div.notes-section.notes').each do |section|
|
59
|
+
text = Tilt[:markdown].new(nil, nil, options[:profile]) { File.read(filename) }.render
|
60
|
+
frag = "<div class=\"personal\"><h1>#{I18n.t('presenter.notes.personal')}</h1>#{text}</div>"
|
61
|
+
section.prepend_child(frag)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# return notes separately from content so that it can be rendered outside the slide
|
66
|
+
# @see https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L726-L732
|
67
|
+
notes = doc.search('div.notes-section')
|
68
|
+
doc.search('div.notes-section').each {|n| n.remove }
|
69
|
+
|
70
|
+
[doc, notes]
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# adds table of content generation to the compiler
|
2
|
+
class Showoff::Compiler::TableOfContents
|
3
|
+
|
4
|
+
# Render a table of contents
|
5
|
+
#
|
6
|
+
# @param doc [Nokogiri::HTML::DocumentFragment]
|
7
|
+
# The presentation document
|
8
|
+
#
|
9
|
+
# @return [Nokogiri::HTML::DocumentFragment]
|
10
|
+
# The presentation DOM with the table of contents rendered.
|
11
|
+
#
|
12
|
+
# @see
|
13
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L747-L768
|
14
|
+
def self.generate!(doc)
|
15
|
+
container = doc.search('p').find {|p| p.text == '~~~TOC~~~' }
|
16
|
+
return doc unless container
|
17
|
+
|
18
|
+
section = nil
|
19
|
+
toc = Nokogiri::XML::Node.new('ol', doc)
|
20
|
+
toc.set_attribute('id', 'toc')
|
21
|
+
|
22
|
+
doc.search('div.slide:not(.toc)').each do |slide|
|
23
|
+
next if slide.search('.content').first.classes.include? 'cover'
|
24
|
+
|
25
|
+
heads = slide.search('div.content h1:not(.section_title)')
|
26
|
+
title = heads.empty? ? slide['data-title'] : heads.first.text
|
27
|
+
href = "##{slide['id']}"
|
28
|
+
|
29
|
+
entry = Nokogiri::XML::Node.new('li', doc)
|
30
|
+
entry.add_class('tocentry')
|
31
|
+
link = Nokogiri::XML::Node.new('a', doc)
|
32
|
+
link.set_attribute('href', href)
|
33
|
+
link.content = title
|
34
|
+
entry.add_child(link)
|
35
|
+
|
36
|
+
if (section and slide['data-section'] == section['data-section'])
|
37
|
+
section.add_child(entry)
|
38
|
+
else
|
39
|
+
section = Nokogiri::XML::Node.new('ol', doc)
|
40
|
+
section.add_class('major')
|
41
|
+
section.set_attribute('data-section', slide['data-section'])
|
42
|
+
entry.add_child(section)
|
43
|
+
toc.add_child(entry)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
container.replace(toc)
|
48
|
+
|
49
|
+
doc
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Adds variable interpolation to the compiler
|
2
|
+
class Showoff::Compiler::Variables
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# @param content [String]
|
6
|
+
# A string of Markdown content which may contain Showoff variables.
|
7
|
+
# @return [String]
|
8
|
+
# The content with variables interpolated.
|
9
|
+
# @note
|
10
|
+
# Had side effects of altering state datastore.
|
11
|
+
# @see
|
12
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L557-L614
|
13
|
+
def self.interpolate!(content)
|
14
|
+
# update counters, incrementing section:minor if needed
|
15
|
+
content.gsub!("~~~CURRENT_SLIDE~~~", Showoff::State.get(:slide_count).to_s)
|
16
|
+
content.gsub!("~~~SECTION:MAJOR~~~", Showoff::State.get(:section_major).to_s)
|
17
|
+
if content.include? "~~~SECTION:MINOR~~~"
|
18
|
+
Showoff::State.increment(:section_minor)
|
19
|
+
content.gsub!("~~~SECTION:MINOR~~~", Showoff::State.get(:section_minor).to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
# scan for pagebreak tags. Should really only be used for handout notes or supplemental materials
|
23
|
+
content.gsub!("~~~PAGEBREAK~~~", '<div class="pagebreak">continued...</div>')
|
24
|
+
|
25
|
+
# replace with form rendering placeholder
|
26
|
+
content.gsub!(/~~~FORM:([^~]*)~~~/, '<div class="form wrapper" title="\1"></div>')
|
27
|
+
|
28
|
+
# Now check for any kind of options
|
29
|
+
content.scan(/(~~~CONFIG:(.*?)~~~)/).each do |match|
|
30
|
+
parts = match[1].split('.') # Use dots ('.') to separate Hash keys
|
31
|
+
value = Showoff::Config.get(*parts)
|
32
|
+
|
33
|
+
unless value.is_a?(String)
|
34
|
+
msg = "#{match[0]} refers to a non-String data type (#{value.class})"
|
35
|
+
msg = "#{match[0]}: not found in settings data" if value.nil?
|
36
|
+
Showoff::Logger.warn(msg)
|
37
|
+
next
|
38
|
+
end
|
39
|
+
|
40
|
+
content.gsub!(match[0], value)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Load and replace any file tags
|
44
|
+
content.scan(/(~~~FILE:([^:~]*):?(.*)?~~~)/).each do |match|
|
45
|
+
# make a list of code highlighting classes to include
|
46
|
+
css = match[2].split.collect {|i| "language-#{i.downcase}" }.join(' ')
|
47
|
+
|
48
|
+
# get the file content and parse out html entities
|
49
|
+
name = match[1]
|
50
|
+
file = File.read(File.join(Showoff::Config.root, '_files', name)) rescue "Nonexistent file: #{name}"
|
51
|
+
file = "Empty file: #{name}" if file.empty?
|
52
|
+
file = HTMLEntities.new.encode(file) rescue "HTML encoding of #{name} failed"
|
53
|
+
|
54
|
+
content.gsub!(match[0], "<pre class=\"highlight\"><code class=\"#{css}\">#{file}</code></pre>")
|
55
|
+
end
|
56
|
+
|
57
|
+
# insert font awesome icons
|
58
|
+
content.gsub!(/\[(fa\w?)-(\S*) ?(.*)\]/, '<i class="\1 fa-\2 \3"></i>')
|
59
|
+
|
60
|
+
# For fenced code blocks, translate the space separated classes into one
|
61
|
+
# colon separated string so Commonmarker doesn't ignore the rest
|
62
|
+
content.gsub!(/^`{3} *(.+)$/) {|s| "``` #{$1.split.join(':')}"}
|
63
|
+
|
64
|
+
# escape any tags left and ensure they're in distinctly separate p tags so
|
65
|
+
# that renderers that accept a string of tildes for fenced code blocks don't blow up.
|
66
|
+
# @todo This is terrible and we need to design a better tag syntax.
|
67
|
+
content.gsub!(/^~~~(.*?)~~~/, "\n\\~~~\\1~~~\n")
|
68
|
+
|
69
|
+
content
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Showoff::Config
|
4
|
+
|
5
|
+
def self.keys
|
6
|
+
@@config.keys
|
7
|
+
end
|
8
|
+
|
9
|
+
# Retrieve settings from the config hash.
|
10
|
+
# If multiple arguments are given then it will dig down through data
|
11
|
+
# structures argument by argument.
|
12
|
+
#
|
13
|
+
# Returns the data type & value requested, nil on error.
|
14
|
+
def self.get(*setting)
|
15
|
+
@@config.dig(*setting) rescue nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.sections
|
19
|
+
@@sections
|
20
|
+
end
|
21
|
+
|
22
|
+
# Absolute root of presentation
|
23
|
+
def self.root
|
24
|
+
@@root
|
25
|
+
end
|
26
|
+
|
27
|
+
# Relative path to an item in the presentation directory structure
|
28
|
+
def self.path(path)
|
29
|
+
File.expand_path(File.join(@@root, path)).sub(/^#{@@root}\//, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Identifies whether we're including a given notes section
|
33
|
+
#
|
34
|
+
# @param section [String] The name of the notes section of interest.
|
35
|
+
# @return [Boolean] Whether to include this section in the output
|
36
|
+
def self.includeNotes?(section)
|
37
|
+
return true # todo make this work
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.load(path = 'showoff.json')
|
41
|
+
raise 'Presentation file does not exist at the specified path' unless File.exist? path
|
42
|
+
|
43
|
+
@@root = File.dirname(path)
|
44
|
+
@@config = JSON.parse(File.read(path))
|
45
|
+
@@sections = self.expand_sections
|
46
|
+
|
47
|
+
self.load_defaults!
|
48
|
+
end
|
49
|
+
|
50
|
+
# Expand and normalize all the different variations that the sections structure
|
51
|
+
# can exist in. When finished, this should return an ordered hash of one or more
|
52
|
+
# section titles pointing to an array of filenames, for example:
|
53
|
+
#
|
54
|
+
# {
|
55
|
+
# "Section name": [ "array.md, "of.md, "files.md"],
|
56
|
+
# "Another Section": [ "two/array.md, "two/of.md, "two/files.md"],
|
57
|
+
# }
|
58
|
+
#
|
59
|
+
# See valid input forms at
|
60
|
+
# https://puppetlabs.github.io/showoff/documentation/PRESENTATION_rdoc.html#label-Defining+slides+using+the+sections+setting.
|
61
|
+
# Source:
|
62
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff_utils.rb#L427-L475
|
63
|
+
def self.expand_sections
|
64
|
+
begin
|
65
|
+
if @@config.is_a?(Hash)
|
66
|
+
# dup so we don't overwrite the original data structure and make it impossible to re-localize
|
67
|
+
sections = @@config['sections'].dup
|
68
|
+
else
|
69
|
+
sections = @@config.dup
|
70
|
+
end
|
71
|
+
|
72
|
+
if sections.is_a? Array
|
73
|
+
sections = self.legacy_sections(sections)
|
74
|
+
elsif sections.is_a? Hash
|
75
|
+
raise "Named sections are unsupported on Ruby versions less than 1.9." if RUBY_VERSION.start_with? '1.8'
|
76
|
+
sections.each do |key, value|
|
77
|
+
next if value.is_a? Array
|
78
|
+
path = File.dirname(value)
|
79
|
+
data = JSON.parse(File.read(File.join(@@root, value)))
|
80
|
+
raise "The section file #{value} must contain an array of filenames." unless data.is_a? Array
|
81
|
+
|
82
|
+
# get relative paths to each slide in the array
|
83
|
+
sections[key] = data.map do |filename|
|
84
|
+
Pathname.new("#{path}/#{filename}").cleanpath.to_path
|
85
|
+
end
|
86
|
+
end
|
87
|
+
else
|
88
|
+
raise "The `sections` key must be an Array or Hash, not a #{sections.class}."
|
89
|
+
end
|
90
|
+
|
91
|
+
rescue => e
|
92
|
+
Showoff::Logger.error "There was a problem with the presentation file #{index}"
|
93
|
+
Showoff::Logger.error e.message
|
94
|
+
Showoff::Logger.debug e.backtrace
|
95
|
+
sections = {}
|
96
|
+
end
|
97
|
+
|
98
|
+
sections
|
99
|
+
end
|
100
|
+
|
101
|
+
# Source:
|
102
|
+
# https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff_utils.rb#L477-L545
|
103
|
+
def self.legacy_sections(data)
|
104
|
+
# each entry in sections can be:
|
105
|
+
# - "filename.md"
|
106
|
+
# - "directory"
|
107
|
+
# - { "section": "filename.md" }
|
108
|
+
# - { "section": "directory" }
|
109
|
+
# - { "section": [ "array.md, "of.md, "files.md"] }
|
110
|
+
# - { "include": "sections.json" }
|
111
|
+
sections = {}
|
112
|
+
counters = {}
|
113
|
+
lastpath = nil
|
114
|
+
|
115
|
+
data.map do |entry|
|
116
|
+
next entry if entry.is_a? String
|
117
|
+
next nil unless entry.is_a? Hash
|
118
|
+
next entry['section'] if entry.include? 'section'
|
119
|
+
|
120
|
+
section = nil
|
121
|
+
if entry.include? 'include'
|
122
|
+
file = entry['include']
|
123
|
+
path = File.dirname(file)
|
124
|
+
data = JSON.parse(File.read(File.join(@@root, file)))
|
125
|
+
if data.is_a? Array
|
126
|
+
if path == '.'
|
127
|
+
section = data
|
128
|
+
else
|
129
|
+
section = data.map do |source|
|
130
|
+
"#{path}/#{source}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
section
|
136
|
+
end.flatten.compact.each do |entry|
|
137
|
+
# We do this in two passes simply because most of it was already done
|
138
|
+
# and I don't want to waste time on legacy functionality.
|
139
|
+
|
140
|
+
# Normalize to a proper path from presentation root
|
141
|
+
if File.directory? File.join(@@root, entry)
|
142
|
+
sections[entry] = Dir.glob("#{@@root}/#{entry}/**/*.md").map {|e| e.sub(/^#{@@root}\//, '') }
|
143
|
+
lastpath = entry
|
144
|
+
else
|
145
|
+
path = File.dirname(entry)
|
146
|
+
|
147
|
+
# this lastpath business allows us to reference files in a directory that aren't
|
148
|
+
# necessarily contiguous.
|
149
|
+
if path != lastpath
|
150
|
+
counters[path] ||= 0
|
151
|
+
counters[path] += 1
|
152
|
+
end
|
153
|
+
|
154
|
+
# now record the last path we've seen
|
155
|
+
lastpath = path
|
156
|
+
|
157
|
+
# and if there are more than one disparate occurences of path, add a counter to this string
|
158
|
+
path = "#{path} (#{counters[path]})" unless counters[path] == 1
|
159
|
+
|
160
|
+
sections[path] ||= []
|
161
|
+
sections[path] << entry
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
sections
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.load_defaults!
|
169
|
+
# use a symbol which cannot clash with a string key loaded from json
|
170
|
+
@@config['markdown'] ||= :default
|
171
|
+
renderer = @@config['markdown']
|
172
|
+
defaults = case renderer
|
173
|
+
when 'rdiscount'
|
174
|
+
{
|
175
|
+
:autolink => true,
|
176
|
+
}
|
177
|
+
when 'maruku'
|
178
|
+
{
|
179
|
+
:use_tex => false,
|
180
|
+
:png_dir => 'images',
|
181
|
+
:html_png_url => '/file/images/',
|
182
|
+
}
|
183
|
+
when 'bluecloth'
|
184
|
+
{
|
185
|
+
:auto_links => true,
|
186
|
+
:definition_lists => true,
|
187
|
+
:superscript => true,
|
188
|
+
:tables => true,
|
189
|
+
}
|
190
|
+
when 'kramdown'
|
191
|
+
{}
|
192
|
+
else
|
193
|
+
{
|
194
|
+
:autolink => true,
|
195
|
+
:no_intra_emphasis => true,
|
196
|
+
:superscript => true,
|
197
|
+
:tables => true,
|
198
|
+
:underline => true,
|
199
|
+
:escape_html => false,
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
@@config[renderer] ||= {}
|
204
|
+
@@config[renderer] = defaults.merge!(@@config[renderer])
|
205
|
+
|
206
|
+
# run `wkhtmltopdf --extended-help` for a full list of valid options here
|
207
|
+
pdf_defaults = {
|
208
|
+
:page_size => 'Letter',
|
209
|
+
:orientation => 'Portrait',
|
210
|
+
:print_media_type => true,
|
211
|
+
:quiet => false}
|
212
|
+
pdf_options = @@config['pdf_options'] || {}
|
213
|
+
pdf_options = Hash[pdf_options.map {|k, v| [k.to_sym, v]}]
|
214
|
+
|
215
|
+
@@config['pdf_options'] = pdf_defaults.merge!(pdf_options)
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|