jekyll-pandoc-multiple-formats-jekyll34 0.2.10 → 0.2.12

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.
@@ -0,0 +1,81 @@
1
+ # Copyright (c) 2012-2015 Nicolás Reynolds <fauno@endefensadelsl.org>
2
+ # 2012-2013 Mauricio Pasquier Juan <mpj@endefensadelsl.org>
3
+ # 2013 Brian Candler <b.candler@pobox.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ # Namespacing and metaprogramming FTW
25
+ module JekyllPandocMultipleFormats
26
+ # Determines the correct module where it lives the converter
27
+ def self.namespace
28
+ Jekyll::VERSION >= '1.0.0' ? Jekyll::Converters : Jekyll
29
+ end
30
+
31
+ # Determines the correct class name. Jekyll has the converters class kinda
32
+ # hardcoded
33
+ def self.class_name
34
+ Jekyll::VERSION >= '1.0.0' ? 'Markdown' : 'MarkdownConverter'
35
+ end
36
+
37
+ def self.build
38
+ namespace::const_get(class_name).send :include, ConverterMethods
39
+ end
40
+
41
+ # When included in the correspondant markdown class this module redefines the
42
+ # three needed Converter instance methods
43
+ module ConverterMethods
44
+ def self.included(base)
45
+ base.class_eval do
46
+ # Just return html5
47
+ def convert(content)
48
+ flags = "#{@config['pandoc']['flags']} #{@config['pandoc']['site_flags']}"
49
+
50
+ output = ''
51
+ Dir::chdir(@config['source']) do
52
+ Open3::popen3("pandoc -t html5 #{flags}") do |stdin, stdout, stderr, thread|
53
+ stdin.puts content
54
+ stdin.close
55
+
56
+ output = stdout.read.strip
57
+ STDERR.print stderr.read
58
+
59
+ # Wait for the process to finish
60
+ thread.value
61
+ end
62
+ end
63
+
64
+ output
65
+ end
66
+
67
+ def matches(ext)
68
+ rgx = '(' + @config['markdown_ext'].gsub(',','|') +')'
69
+ ext =~ Regexp.new(rgx, Regexp::IGNORECASE)
70
+ end
71
+
72
+ def output_ext(ext)
73
+ '.html'
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # Conjures the metamagic
81
+ JekyllPandocMultipleFormats.build
@@ -0,0 +1,110 @@
1
+ # Copyright (c) 2012-2015 Nicolás Reynolds <fauno@endefensadelsl.org>
2
+ # 2012-2013 Mauricio Pasquier Juan <mpj@endefensadelsl.org>
3
+ # 2013 Brian Candler <b.candler@pobox.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Jekyll
25
+
26
+ class PandocGenerator < Generator
27
+ safe true
28
+
29
+ attr_accessor :site, :config
30
+
31
+ def generate(site)
32
+ @site ||= site
33
+ @config ||= JekyllPandocMultipleFormats::Config.new(@site.config['pandoc'])
34
+
35
+ return if @config.skip?
36
+
37
+ # we create a single array of files
38
+ @pandoc_files = []
39
+
40
+ @config.outputs.each_pair do |output, extra_flags|
41
+ @site.posts.docs.each do |post|
42
+
43
+ pandoc_file = PandocFile.new(@site, output, post)
44
+ next unless pandoc_file.write
45
+
46
+ @site.keep_files << pandoc_file.relative_path
47
+ @pandoc_files << pandoc_file
48
+ end
49
+
50
+ @site.post_attr_hash('categories').each_pair do |title, posts|
51
+ posts.sort!
52
+ pandoc_file = PandocFile.new(@site, output, posts, title)
53
+
54
+ if @site.keep_files.include? pandoc_file.relative_path
55
+ puts "#{pandoc_file.relative_path} is a category file AND a post file"
56
+ puts 'change the category name to fix this'
57
+ next
58
+ end
59
+
60
+ next unless pandoc_file.write
61
+
62
+ @site.keep_files << pandoc_file.relative_path
63
+ @pandoc_files << pandoc_file
64
+ end
65
+ end
66
+
67
+ @pandoc_files.each do |pandoc_file|
68
+ # If output is PDF, we also create the imposed PDF
69
+ if pandoc_file.pdf?
70
+
71
+ if @config.imposition?
72
+
73
+ imposed_file = JekyllPandocMultipleFormats::Imposition
74
+ .new(pandoc_file.path, pandoc_file.papersize,
75
+ pandoc_file.sheetsize, pandoc_file.signature)
76
+
77
+ imposed_file.write
78
+ @site.keep_files << imposed_file.relative_path(@site.dest)
79
+ end
80
+
81
+ # If output is PDF, we also create the imposed PDF
82
+ if @config.binder?
83
+
84
+ binder_file = JekyllPandocMultipleFormats::Binder
85
+ .new(pandoc_file.path, pandoc_file.papersize,
86
+ pandoc_file.sheetsize)
87
+
88
+ binder_file.write
89
+ @site.keep_files << binder_file.relative_path(@site.dest)
90
+ end
91
+
92
+ # Add covers to PDFs after building ready for print files
93
+ if pandoc_file.has_cover?
94
+ # Generate the cover
95
+ next unless pandoc_file.pdf_cover!
96
+ united_output = pandoc_file.path.gsub(/\.pdf\Z/, '-cover.pdf')
97
+ united_file = JekyllPandocMultipleFormats::Unite
98
+ .new(united_output, [pandoc_file.pdf_cover,pandoc_file.path])
99
+
100
+ if united_file.write
101
+ # Replace the original file with the one with cover
102
+ FileUtils.rm_f(pandoc_file.path)
103
+ FileUtils.mv(united_output, pandoc_file.path)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright (c) 2012-2015 Nicolás Reynolds <fauno@endefensadelsl.org>
2
+ # 2012-2013 Mauricio Pasquier Juan <mpj@endefensadelsl.org>
3
+ # 2013 Brian Candler <b.candler@pobox.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module JekyllPandocMultipleFormats
25
+ class Imposition < Printer
26
+
27
+ attr_accessor :rounded_pages, :blank_pages, :signature
28
+
29
+ def initialize(file, papersize = nil, sheetsize = nil, signature = nil, extra_options = nil)
30
+ super(file, papersize, sheetsize, extra_options)
31
+ @output_file = file.gsub(/\.pdf\Z/, '-imposed.pdf')
32
+ # Total pages must be modulo 4
33
+ @rounded_pages = round_to_nearest(@pages, 4)
34
+ @blank_pages = @rounded_pages - @pages
35
+ # If we don't use a signature, make a single fold
36
+ @signature = signature || @rounded_pages
37
+ # Also if we specified 0
38
+ @signature = @rounded_pages if signature == 0
39
+
40
+ render_template
41
+ self
42
+ end
43
+
44
+ def to_nup
45
+ # 14 pages example:
46
+ # [ {}, 1, 2, {}, 14, 3, 4, 13, 12, 5, 6, 11, 10, 7, 8, 9 ]
47
+ #
48
+ # * Add {} to missing pages
49
+ # [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, {}, {}]
50
+ # * Take first half [ 1, 2, 3, 4, 5, 6, 7, 8 ]
51
+ # * Reverse second half [ {}, {}, 14, 13, 12, 11, 10, 9 ]
52
+ # * Intercalate the first half into the second half by two
53
+ # elements
54
+ # [ {}, 1, 2, {}, 14, 3, 4, 13, 12, 5, 6, 11, 10, 7, 8, 9 ]
55
+ #
56
+ # An array of numbered pages padded with blank pages ('{}')
57
+ #
58
+ # [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ] + [ '{}', '{}' ]
59
+ padded = @pages.times.map{|i|i+1} + Array.new(@blank_pages, '{}')
60
+
61
+ # If we have a signature, we have to split in groups up to the
62
+ # amount of pages per signature, and then continue with the rest
63
+ #
64
+ # If we have no signature, we assume it's equal to the total
65
+ # amount of pages, so you only have one fold
66
+ signed = padded.each_slice(@signature).to_a
67
+ folds = []
68
+ signed.each do |fold|
69
+ #
70
+ # Split in halves
71
+ # [ [ 1, 2, 3, 4, 5, 6, 7, 8 ],
72
+ # [ 9, 10, 11, 12, 13, 14, '{}', '{}' ] ]
73
+ halved = fold.each_slice(fold.size / 2).to_a
74
+ # Add a nil as last page. When we reverse it and intercalate by
75
+ # two pages, we'll have [nil, last_page] instead of
76
+ # [last_page,second_to_last_page]
77
+ #
78
+ # [ [ 1, 2, 3, 4, 5, 6, 7, 8 ],
79
+ # [ 9, 10, 11, 12, 13, 14, '{}', '{}', nil ] ]
80
+ halved.last << nil
81
+ # Reverse the second half and intercalate by two pages into the
82
+ # first one. Then remove nil elements and flatten the array.
83
+ #
84
+ # [ [ 1, 2, 3, 4, 5, 6, 7, 8 ],
85
+ # [ nil, '{}', '{}', 14, 13, 12, 11, 10 ] ]
86
+ #
87
+ # [ {}, 1, 2, {}, 14, 3, 4, 13, 12, 5, 6, 11, 10, 7, 8, 9 ]
88
+ folds << halved.last.reverse.each_slice(2).zip(halved.first.each_slice(2).to_a).flatten.compact
89
+ end
90
+
91
+ # Create the matrix of pages (N-Up) per fold
92
+ #
93
+ # ["{}", 1, "{}", 1, 2, "{}", 2, "{}", 14, 3, 14, 3, 4, 13, 4, 13, 12, 5, 12, 5, 6, 11, 6, 11, 10, 7, 10, 7, 8, 9, 8, 9]
94
+ folds.map { |o| o.each_slice(2).map{ |i| a=[]; (@nup/2).times { a = a+i }; a }}.flatten
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,276 @@
1
+ # Copyright (c) 2012-2015 Nicolás Reynolds <fauno@endefensadelsl.org>
2
+ # 2012-2013 Mauricio Pasquier Juan <mpj@endefensadelsl.org>
3
+ # 2013 Brian Candler <b.candler@pobox.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Jekyll
25
+ class PandocFile
26
+ include Convertible
27
+
28
+
29
+ attr_reader :format, :site, :config, :flags, :posts, :slug, :title, :url
30
+ attr_reader :papersize, :sheetsize, :signature
31
+
32
+ def initialize(site, format, posts, title = nil)
33
+ @site = site
34
+ @config = JekyllPandocMultipleFormats::Config.new(@site.config['pandoc']).config
35
+ @format = format
36
+ @flags = []
37
+
38
+ if posts.is_a? Array
39
+ @posts = posts
40
+
41
+ raise ArgumentError.new "'title' argument is required for multipost file" unless title
42
+
43
+ @title = title
44
+ else
45
+ @posts = [posts]
46
+ @title = posts.data['title'] unless title
47
+ end
48
+
49
+ @slug = Utils.slugify(title)
50
+ end
51
+
52
+ def path
53
+ # path is full destination path with all elements of permalink
54
+ path = @site.in_dest_dir(relative_path)
55
+ end
56
+
57
+ def relative_path
58
+ path = URL.unescape_path(url)
59
+ path.gsub! /^\//, ''
60
+
61
+ # but if the post is going to be index.html, use slug + format
62
+ # (ie /year/month/slug/slug.pdf)
63
+ if url.end_with? '/'
64
+ path = File.join(path, @slug)
65
+ path << '.'
66
+ path << @format
67
+ end
68
+
69
+ path
70
+ end
71
+
72
+ # if it's just one article, the url is the same except it's not html
73
+ # otherwise we use the permalink template with some placeholder
74
+ def url
75
+ @url = if single_post?
76
+ single_post.url.gsub(/\.html$/, ".#{@format}")
77
+ else
78
+ URL.new({
79
+ template: @config['bundle_permalink'],
80
+ placeholders: url_placeholders,
81
+ permalink: nil }).to_s
82
+ end
83
+ end
84
+
85
+ def url_placeholders
86
+ { output_ext: @format,
87
+ slug: @slug,
88
+ title: @title }
89
+ end
90
+
91
+ # adds post metadata as yaml metadata
92
+ def yaml_metadata
93
+ if single_post?
94
+
95
+ # if we were to merge config to data, the default options would
96
+ # take precedence
97
+ data = Jekyll::Utils.deep_merge_hashes @config, single_post.data
98
+ single_post.merge_data! data
99
+
100
+
101
+ # we extract the excerpt because it serializes as an object and
102
+ # breaks pandoc
103
+ metadata = single_post.data.reject{ |k| k == 'excerpt' }
104
+
105
+ if @config['date_format']
106
+ metadata['date'] = metadata['date'].strftime(@config['date_format'])
107
+ else
108
+ metadata.delete('date')
109
+ end
110
+ else
111
+ # we have to use this fugly syntax because jekyll doesn't do
112
+ # symbols
113
+ metadata = {
114
+ 'date' => @config['date_format'] ? Date.today.strftime(@config['date_format']) : nil,
115
+ 'title' => @title,
116
+ 'author' => nil,
117
+ 'papersize' => papersize,
118
+ 'sheetsize' => sheetsize,
119
+ 'signature' => signature
120
+ }
121
+ end
122
+
123
+ # fix page sizes, pandoc uses 'A4' while printer.rb uses
124
+ # 'a4paper'
125
+ %w[papersize sheetsize].each do |size|
126
+ metadata[size] = fix_size metadata[size]
127
+ end
128
+
129
+ metadata.to_yaml << "\n---\n"
130
+ end
131
+
132
+ def content
133
+ if single_post?
134
+ single_post.content
135
+ else
136
+ header_re = /^(#+.*\n*|.*\n[=-]+\n*)\Z/
137
+ bib_title = ""
138
+ @posts.map do |post|
139
+ bib_title = post.content.match(header_re).to_s if bib_title.empty?
140
+ # remove bibliography titles
141
+ # since pandoc does it's own bibliography output, it recommends
142
+ # leaving an empty chapter title to mark it as such
143
+ post.content.gsub(header_re, '')
144
+ # we add the first bibliography title we can find in the end
145
+ end.join("\n\n\n") << bib_title
146
+ end
147
+ end
148
+
149
+ def write
150
+ FileUtils.mkdir_p(File.dirname(path))
151
+ # Remove the file before creating it
152
+ FileUtils.rm_f(path)
153
+ # Move to the source dir since everything will be relative to that
154
+ Dir::chdir(@site.config['source']) do
155
+ # Do the stuff
156
+ Open3::popen3(command) do |stdin, stdout, stderr, thread|
157
+ stdin.puts yaml_metadata
158
+ stdin.puts content
159
+ stdin.close
160
+ STDERR.print stderr.read
161
+
162
+ # Wait for the process to finish
163
+ thread.value
164
+ end
165
+ end
166
+
167
+ File.exists?(path)
168
+ end
169
+
170
+ # Returns a cover, without checking if it exists
171
+ #
172
+ # It assumes covers are in PNG format
173
+ def cover
174
+ if single_post? && single_post.data['cover']
175
+ File.join(@site.config['source'], single_post.data['cover'])
176
+ else
177
+ File.join(@site.config['source'], @config['covers_dir'], "#{@slug}.png")
178
+ end
179
+ end
180
+
181
+ # Returns a PDF cover
182
+ def pdf_cover
183
+ cover.gsub(/\.[^\.]+\Z/, '.pdf')
184
+ end
185
+
186
+ def pdf_cover!
187
+ if has_cover? && !File.exists?(pdf_cover)
188
+ Open3::popen3("convert \"#{cover}\" \"#{pdf_cover}\"") do |stdin, stdout, stderr, thread|
189
+ STDERR.print stderr.read
190
+
191
+ # Wait for the process to finish
192
+ thread.value
193
+ end
194
+ end
195
+
196
+ File.exists?(pdf_cover)
197
+ end
198
+
199
+ def flags
200
+ return @flags.join ' ' unless @flags.empty?
201
+
202
+ @flags << @config['flags']
203
+ @flags << @config['outputs'][@format] if @config['outputs'].key?(@format)
204
+ @flags << '-o'
205
+ @flags << path
206
+
207
+ # Binary formats don't need a -t flag
208
+ unless binary?
209
+ @flags << '-t'
210
+ @flags << @format
211
+ end
212
+
213
+ if epub? && has_cover?
214
+ @flags << '--epub-cover-image'
215
+ @flags << cover
216
+ end
217
+
218
+ @flags.join ' '
219
+ end
220
+
221
+ def command
222
+ 'pandoc ' << flags
223
+ end
224
+
225
+ def pdf?
226
+ @format == 'pdf'
227
+ end
228
+
229
+ def epub?
230
+ %w[epub epub3].include? @format
231
+ end
232
+
233
+ # These formats are binary files and must use the -o flag
234
+ def binary?
235
+ %w[pdf epub epub3 odt docx].include? @format
236
+ end
237
+
238
+ def single_post?
239
+ @posts.count == 1
240
+ end
241
+
242
+ def has_cover?
243
+ File.exists? cover
244
+ end
245
+
246
+ def papersize
247
+ @papersize ||= find_option 'papersize'
248
+ end
249
+
250
+ def sheetsize
251
+ @sheetsize ||= find_option 'sheetsize'
252
+ end
253
+
254
+ def signature
255
+ @signature ||= find_option 'signature'
256
+ end
257
+
258
+ private
259
+
260
+ def single_post
261
+ @posts.first
262
+ end
263
+
264
+ def find_option(name)
265
+ if @posts.any? { |p| p.data.key? name }
266
+ @posts.select { |p| p.data.key? name }.first.data[name]
267
+ else
268
+ @config[name]
269
+ end
270
+ end
271
+
272
+ def fix_size(size)
273
+ size.gsub /paper$/, ''
274
+ end
275
+ end
276
+ end