showoff 0.19.4 → 0.20.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Rakefile +24 -12
- data/bin/showoff +47 -24
- data/lib/keymap.rb +19 -5
- data/lib/showoff.rb +96 -44
- 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 +27 -21
- data/public/css/font-awesome-5.6.1/css/all.min.css +5 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-brands-400.eot +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-brands-400.svg +1260 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-brands-400.ttf +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-brands-400.woff +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-brands-400.woff2 +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-regular-400.eot +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-regular-400.svg +471 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-regular-400.ttf +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-regular-400.woff +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-regular-400.woff2 +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-solid-900.eot +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-solid-900.svg +2760 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-solid-900.ttf +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-solid-900.woff +0 -0
- data/public/css/font-awesome-5.6.1/webfonts/fa-solid-900.woff2 +0 -0
- data/public/css/presenter.css +1 -0
- data/public/css/showoff.css +42 -10
- data/public/js/highlight.pack-9.15.10.js +22614 -0
- data/public/js/highlightjs-line-numbers.min.js +1 -0
- data/public/js/presenter.js +13 -11
- data/public/js/showoff.js +39 -31
- data/views/download.erb +2 -2
- data/views/header.erb +27 -26
- data/views/header_mini.erb +8 -8
- data/views/index.erb +56 -56
- data/views/onepage.erb +10 -16
- data/views/presenter.erb +124 -123
- data/views/slide.erb +29 -0
- data/views/stats.erb +1 -1
- metadata +113 -100
- data/locales/id.yml +0 -2
- data/public/css/font-awesome-4.4.0/css/font-awesome.min.css +0 -4
- data/public/css/font-awesome-4.4.0/fonts/FontAwesome.otf +0 -0
- data/public/css/font-awesome-4.4.0/fonts/fontawesome-webfont.eot +0 -0
- data/public/css/font-awesome-4.4.0/fonts/fontawesome-webfont.svg +0 -640
- data/public/css/font-awesome-4.4.0/fonts/fontawesome-webfont.ttf +0 -0
- data/public/css/font-awesome-4.4.0/fonts/fontawesome-webfont.woff +0 -0
- data/public/css/font-awesome-4.4.0/fonts/fontawesome-webfont.woff2 +0 -0
- 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
|