jekyll-asciidoc 2.0.1 → 2.1.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +27 -8
  3. data/Gemfile +13 -3
  4. data/LICENSE.adoc +1 -1
  5. data/README.adoc +409 -103
  6. data/jekyll-asciidoc.gemspec +6 -7
  7. data/lib/jekyll-asciidoc/compat.rb +4 -3
  8. data/lib/jekyll-asciidoc/converter.rb +50 -30
  9. data/lib/jekyll-asciidoc/filters.rb +21 -1
  10. data/lib/jekyll-asciidoc/integrator.rb +42 -34
  11. data/lib/jekyll-asciidoc/mixins.rb +6 -0
  12. data/lib/jekyll-asciidoc/version.rb +1 -1
  13. data/spec/fixtures/basic_site/docid.adoc +4 -0
  14. data/spec/fixtures/basic_site/section-with-id-and-role.adoc +6 -0
  15. data/spec/fixtures/imagesdir_relative_to_root/_config.yml +1 -1
  16. data/spec/fixtures/{include_relative_to_doc → include_relative_to_docdir}/_config.yml +0 -0
  17. data/spec/fixtures/{include_relative_to_doc → include_relative_to_docdir}/_layouts/default.html +0 -0
  18. data/spec/fixtures/{include_relative_to_doc → include_relative_to_docdir}/about/_people.adoc +0 -0
  19. data/spec/fixtures/{include_relative_to_doc → include_relative_to_docdir}/about/index.adoc +0 -0
  20. data/spec/fixtures/include_relative_to_root/source/_config.yml +2 -0
  21. data/spec/fixtures/include_relative_to_root/source/_layouts/default.html +11 -0
  22. data/spec/fixtures/include_relative_to_root/source/about/_people.adoc +2 -0
  23. data/spec/fixtures/include_relative_to_root/source/about/index.adoc +13 -0
  24. data/spec/fixtures/tocify_filter/_config.yml +2 -0
  25. data/spec/fixtures/tocify_filter/_layouts/default.html +15 -0
  26. data/spec/fixtures/tocify_filter/index.adoc +25 -0
  27. data/spec/fixtures/with_posts/_config.yml +4 -0
  28. data/spec/fixtures/with_posts/_layouts/post.html +8 -0
  29. data/spec/fixtures/with_posts/_posts/2016-02-02-post-with-singular-vars.adoc +7 -0
  30. data/spec/fixtures/with_posts/_posts/2016-06-15-post-with-date.adoc +4 -0
  31. data/spec/fixtures/with_posts/_posts/2016-07-15-post-with-date-and-tz.adoc +4 -0
  32. data/spec/fixtures/with_posts/_posts/2016-07-20-post-with-date-in-revision-line.adoc +6 -0
  33. data/spec/fixtures/xhtml_syntax/images/sunset.jpg +0 -0
  34. data/spec/jekyll-asciidoc_spec.rb +320 -127
  35. data/spec/spec_helper.rb +4 -0
  36. metadata +41 -15
@@ -3,12 +3,13 @@ require File.expand_path '../lib/jekyll-asciidoc/version', __FILE__
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'jekyll-asciidoc'
5
5
  s.version = Jekyll::AsciiDoc::VERSION
6
- s.summary = 'A Jekyll plugin that converts AsciiDoc source files in your site to HTML pages using Asciidoctor.'
7
- s.description = 'A Jekyll plugin that converts AsciiDoc source files in your site to HTML pages using Asciidoctor.'
6
+ s.summary = 'A Jekyll plugin that converts the AsciiDoc source files in your site to HTML pages using Asciidoctor.'
7
+ s.description = 'A Jekyll plugin that converts the AsciiDoc source files in your site to HTML pages using Asciidoctor.'
8
8
  s.authors = ['Dan Allen', 'Paul Rayner']
9
9
  s.email = ['dan.j.allen@gmail.com']
10
10
  s.homepage = 'https://github.com/asciidoctor/jekyll-asciidoc'
11
11
  s.license = 'MIT'
12
+ s.required_ruby_version = '>= 1.9.3'
12
13
 
13
14
  files = begin
14
15
  output = IO.popen('git ls-files -z', err: File::NULL) {|io| io.read }.split %(\0)
@@ -16,9 +17,9 @@ Gem::Specification.new do |s|
16
17
  rescue
17
18
  Dir['**/*']
18
19
  end
19
- s.files = files.grep /^(?:lib\/.+|Gemfile|Rakefile|(CHANGELOG|LICENSE|README)\.adoc|#{s.name}\.gemspec)$/
20
- s.test_files = files.grep /^spec\//
21
-
20
+ s.files = files.grep %r/^(?:lib\/.+|Gemfile|Rakefile|(?:CHANGELOG|LICENSE|README)\.adoc|#{s.name}\.gemspec)$/
21
+ s.test_files = files.grep %r/^spec\//
22
+
22
23
  s.require_paths = ['lib']
23
24
 
24
25
  s.add_runtime_dependency 'asciidoctor', '>= 1.5.0'
@@ -26,6 +27,4 @@ Gem::Specification.new do |s|
26
27
 
27
28
  s.add_development_dependency 'rake'
28
29
  s.add_development_dependency 'rspec', '~> 3.5.0'
29
- # enable pygments.rb dependency here once https://github.com/tmm1/pygments.rb/pull/162 is merged & released
30
- #s.add_development_dependency 'pygments.rb', '~> 0.6.3'
31
30
  end
@@ -1,6 +1,7 @@
1
1
  module Jekyll
2
2
  module AsciiDoc
3
- Jekyll3Compatible = (::Gem::Version.new ::Jekyll::VERSION) >= (::Gem::Version.new '3.0.0')
3
+ Jekyll3Compatible = (jekyll_version = ::Gem::Version.new ::Jekyll::VERSION) >= (::Gem::Version.new '3.0.0')
4
+ Jekyll3_1 = (::Gem::Requirement.new '~> 3.1.0').satisfied_by? jekyll_version
4
5
  end
5
6
  end
6
7
 
@@ -9,11 +10,11 @@ module Jekyll
9
10
  # Backport {::Jekyll::Site#find_converter_instance} to Jekyll 2.
10
11
  def find_converter_instance type
11
12
  converters.find {|candidate| type === candidate } || (raise %(No Converters found for #{type}))
12
- end unless respond_to? :find_converter_instance
13
+ end unless method_defined? :find_converter_instance
13
14
 
14
15
  # Introduce complement to {::Jekyll::Site#find_converter_instance} for generators.
15
16
  def find_generator_instance type
16
17
  generators.find {|candidate| type === candidate } || (raise %(No Generators found for #{type}))
17
- end unless respond_to? :find_generator_instance
18
+ end unless method_defined? :find_generator_instance
18
19
  end
19
20
  end
@@ -34,7 +34,7 @@ module Jekyll
34
34
  def initialize config
35
35
  @config = config
36
36
  @logger = ::Jekyll.logger
37
- @path_info = nil
37
+ @page_context = {}
38
38
  @setup = false
39
39
 
40
40
  # NOTE jekyll-watch reinitializes plugins using a shallow clone of config, so no need to reconfigure
@@ -100,10 +100,10 @@ module Jekyll
100
100
  'site-baseurl' => config['baseurl'],
101
101
  'site-url' => config['url']
102
102
  }
103
- attrs = asciidoctor_config[:attributes] = hashify_attributes asciidoctor_config[:attributes],
103
+ attrs = asciidoctor_config[:attributes] = assemble_attributes asciidoctor_config[:attributes],
104
104
  ((site_attributes.merge ImplicitAttributes).merge DefaultAttributes)
105
105
  if (imagesdir = attrs['imagesdir']) && !(attrs.key? 'imagesoutdir') && (imagesdir.start_with? '/')
106
- attrs['imagesoutdir'] = ::File.join dest, imagesdir
106
+ attrs['imagesoutdir'] = ::File.join dest, (imagesdir.chomp '@')
107
107
  end
108
108
  asciidoctor_config.extend Configured
109
109
  end
@@ -151,47 +151,47 @@ module Jekyll
151
151
  end
152
152
 
153
153
  def before_render document, payload
154
- record_path_info document
154
+ # NOTE Jekyll 3.1 incorrectly mapped the page payload to document.data instead of payload['page']
155
+ @page_context[:data] = ::Jekyll::AsciiDoc::Jekyll3_1 ? document.data : payload['page']
156
+ record_paths document
155
157
  end
156
158
 
157
159
  def after_render document
158
- clear_path_info
160
+ @page_context.clear
159
161
  end
160
162
 
161
- def record_path_info document, opts = {}
162
- @path_info = {
163
+ def record_paths document, opts = {}
164
+ @page_context[:paths] = paths = {
163
165
  'docfile' => (docfile = ::File.join document.site.source, document.relative_path),
164
166
  'docdir' => (::File.dirname docfile),
165
167
  'docname' => (::File.basename docfile, (::File.extname docfile))
166
168
  }
167
- unless opts[:source_only]
168
- @path_info.update({
169
- 'outfile' => (outfile = document.destination document.site.dest),
170
- 'outdir' => (::File.dirname outfile),
171
- 'outpath' => document.url
172
- })
173
- end
169
+ paths.update({
170
+ 'outfile' => (outfile = document.destination document.site.dest),
171
+ 'outdir' => (::File.dirname outfile),
172
+ 'outpath' => document.url
173
+ }) unless opts[:source_only]
174
174
  end
175
175
 
176
- def clear_path_info
177
- @path_info = nil
176
+ def clear_paths
177
+ @page_context.delete :paths
178
178
  end
179
179
 
180
180
  def load_header document
181
181
  setup
182
- record_path_info document, source_only: true if defined? ::Jekyll::Hooks
182
+ record_paths document, source_only: true if defined? ::Jekyll::Hooks
183
183
  # NOTE merely an optimization; if this doesn't match, the header still gets isolated by the processor
184
184
  header = (document.content.split HeaderBoundaryRx, 2)[0]
185
185
  case @asciidoc_config['processor']
186
186
  when 'asciidoctor'
187
187
  opts = @asciidoctor_config.merge parse_header_only: true
188
- if @path_info
188
+ if (paths = @page_context[:paths])
189
189
  if opts[:base_dir] == :docdir
190
- opts[:base_dir] = @path_info['docdir'] # NOTE this assignment happens inside the processor anyway
190
+ opts[:base_dir] = paths['docdir'] # NOTE this assignment happens inside the processor anyway
191
191
  else
192
- @path_info.delete 'docdir'
192
+ paths.delete 'docdir'
193
193
  end
194
- opts[:attributes] = opts[:attributes].merge @path_info
194
+ opts[:attributes] = opts[:attributes].merge paths
195
195
  end
196
196
  # NOTE return instance even if header is empty since attributes may be inherited from config
197
197
  doc = ::Asciidoctor.load header, opts
@@ -199,7 +199,7 @@ module Jekyll
199
199
  @logger.warn MessageTopic, %(Unknown AsciiDoc processor: #{@asciidoc_config['processor']}. Cannot load document header.)
200
200
  doc = nil
201
201
  end
202
- clear_path_info if defined? ::Jekyll::Hooks
202
+ clear_paths if defined? ::Jekyll::Hooks
203
203
  doc
204
204
  end
205
205
 
@@ -212,15 +212,18 @@ module Jekyll
212
212
  case @asciidoc_config['processor']
213
213
  when 'asciidoctor'
214
214
  opts = @asciidoctor_config.merge header_footer: standalone
215
- if @path_info
215
+ if (paths = @page_context[:paths])
216
216
  if opts[:base_dir] == :docdir
217
- opts[:base_dir] = @path_info['docdir'] # NOTE this assignment happens inside the processor anyway
217
+ opts[:base_dir] = paths['docdir'] # NOTE this assignment happens inside the processor anyway
218
218
  else
219
- @path_info.delete 'docdir'
219
+ paths.delete 'docdir'
220
220
  end
221
- opts[:attributes] = opts[:attributes].merge @path_info
221
+ opts[:attributes] = opts[:attributes].merge paths
222
+ # for auto-extracted excerpt, paths are't available since hooks don't get triggered
223
+ elsif opts[:base_dir] == :docdir
224
+ opts.delete :base_dir
222
225
  end
223
- ::Asciidoctor.convert content, opts
226
+ ((@page_context[:data] || {})['document'] = ::Asciidoctor.load content, opts).extend(Liquidable).convert
224
227
  else
225
228
  @logger.warn MessageTopic, %(Unknown AsciiDoc processor: #{@asciidoc_config['processor']}. Passing through unparsed content.)
226
229
  content
@@ -233,7 +236,7 @@ module Jekyll
233
236
  hash.each_with_object({}) {|(key, val), accum| accum[key.to_sym] = val }
234
237
  end
235
238
 
236
- def hashify_attributes attrs, initial = {}
239
+ def assemble_attributes attrs, initial = {}
237
240
  if (is_array = ::Array === attrs) || ::Hash === attrs
238
241
  attrs.each_with_object(initial) {|entry, new_attrs|
239
242
  key, val = is_array ? ((entry.split '=', 2) + ['', ''])[0..1] : entry
@@ -241,8 +244,25 @@ module Jekyll
241
244
  new_attrs[key[1..-1]] = nil
242
245
  elsif key.end_with? '!'
243
246
  new_attrs[key.chop] = nil
247
+ # we're reserving -name to mean "unset implicit value but allow doc to override"
248
+ elsif key.start_with? '-'
249
+ new_attrs.delete key[1..-1]
244
250
  else
245
- new_attrs[key] = val ? (resolve_attribute_refs val, new_attrs) : nil
251
+ new_attrs[key] = if val
252
+ case val
253
+ when ::String
254
+ resolve_attribute_refs val, new_attrs
255
+ when ::Numeric
256
+ val.to_s
257
+ when true
258
+ ''
259
+ else
260
+ val
261
+ end
262
+ else
263
+ # we may preserve false in the future to mean "unset implicit value but allow doc to override"
264
+ nil
265
+ end
246
266
  end
247
267
  }
248
268
  else
@@ -254,7 +274,7 @@ module Jekyll
254
274
  if text.empty?
255
275
  text
256
276
  elsif text.include? '{'
257
- text.gsub(AttributeReferenceRx) { ((m = $&).start_with? '\\') ? m[1..-1] : (attrs.fetch $1, m) }
277
+ text.gsub(AttributeReferenceRx) { ($&.start_with? '\\') ? $&[1..-1] : ((attrs.fetch $1, $&).to_s.chomp '@') }
258
278
  else
259
279
  text
260
280
  end
@@ -6,11 +6,31 @@ module Jekyll
6
6
  # input - The AsciiDoc String to convert.
7
7
  # doctype - The target AsciiDoc doctype (optional, default: nil).
8
8
  #
9
- # Returns the HTML formatted String.
9
+ # Examples
10
+ #
11
+ # {{ page.excerpt | asciidocify: 'inline' }}
12
+ #
13
+ # Returns the converted result as an HTML-formatted String.
10
14
  def asciidocify input, doctype = nil
11
15
  (@context.registers[:cached_asciidoc_converter] ||= (Converter.get_instance @context.registers[:site]))
12
16
  .convert(doctype ? %(:doctype: #{doctype}#{Utils::NewLine}#{input}) : (input || ''))
13
17
  end
18
+
19
+ # A Liquid filter for generating a table of contents in HTML from a parsed AsciiDoc document.
20
+ #
21
+ # document - The parsed AsciiDoc document from which to generate a table of contents in HTML.
22
+ # levels - The max section depth to include (optional, default: value of toclevels document attribute).
23
+ #
24
+ # Examples
25
+ #
26
+ # {{ page.document | tocify_asciidoc: 3 }}
27
+ #
28
+ # Returns the table of contents as an HTML-formatted String.
29
+ def tocify_asciidoc document, levels = nil
30
+ if ::Asciidoctor::Document === document
31
+ document.converter.convert document, 'outline', toclevels: (levels.nil_or_empty? ? nil : levels.to_i)
32
+ end
33
+ end
14
34
  end
15
35
 
16
36
  ::Liquid::Template.register_filter Filters
@@ -14,8 +14,8 @@ module Jekyll
14
14
  site.find_generator_instance self
15
15
  end
16
16
 
17
- # This method is triggered each time the site is generated, including after any file has changed when
18
- # running in watch mode (regardless of incremental setting).
17
+ # This method is triggered each time the site is generated, including after any file has changed when running in
18
+ # watch mode (regardless of incremental setting).
19
19
  def generate site
20
20
  @converter = converter = (Converter.get_instance site).setup
21
21
 
@@ -47,47 +47,59 @@ module Jekyll
47
47
  end
48
48
  end
49
49
 
50
- # Integrate the page-related attributes from the AsciiDoc document header
51
- # into the data Array of the specified {::Jekyll::Page}, {::Jekyll::Post}
52
- # or {::Jekyll::Document}.
50
+ # Integrate the page-related attributes from the AsciiDoc document header into the data Array of the specified
51
+ # {::Jekyll::Page}, {::Jekyll::Post} or {::Jekyll::Document}.
53
52
  #
54
53
  # document - the Page, Post or Document instance to integrate.
55
- # collection_name - the String name of the collection to which this
56
- # document belongs (optional, default: nil).
54
+ # collection_name - the String name of the collection to which this document belongs (optional, default: nil).
57
55
  #
58
56
  # Returns a [Boolean] indicating whether the document should be published.
59
57
  def integrate document, collection_name = nil
60
- document.extend Document
61
- document.content = [%(:#{@page_attr_prefix}layout: _auto), document.content] * NewLine unless document.data.key? 'layout'
62
- return unless (doc = @converter.load_header document)
63
-
64
- document.data['title'] = doc.doctitle if doc.header?
65
- document.data['author'] = doc.author if doc.author
66
- document.data['date'] = (::DateTime.parse doc.revdate).to_time if collection_name == 'posts' && (doc.attr? 'revdate')
58
+ data = document.data
59
+ document.content = [%(:#{@page_attr_prefix}layout: _auto), document.content] * NewLine unless data.key? 'layout'
60
+ return true unless (doc = @converter.load_header document)
61
+
62
+ # NOTE id is already reserved in Jekyll for another purpose, so we'll map id to docid instead
63
+ data['docid'] = doc.id if doc.id
64
+ data['title'] = doc.doctitle if doc.header?
65
+ data['author'] = doc.author if doc.author
66
+ if collection_name == 'posts' && (doc.attr? 'revdate')
67
+ data['date'] = ::Jekyll::Utils.parse_date doc.revdate,
68
+ %(Document '#{document.relative_path}' does not have a valid revdate in the AsciiDoc header.)
69
+ # NOTE Jekyll 2.3 requires date field to be set explicitly
70
+ document.date = data['date'] if document.respond_to? :date=
71
+ end
67
72
 
68
- no_prefix = (prefix_size = @page_attr_prefix.length).zero?
69
- unless (adoc_header_data = doc.attributes
70
- .each_with_object({}) {|(key, val), accum|
73
+ no_prefix = (prefix_size = @page_attr_prefix.length) == 0
74
+ unless (adoc_data = doc.attributes.each_with_object({}) {|(key, val), accum|
71
75
  if no_prefix || ((key.start_with? @page_attr_prefix) && key = key[prefix_size..-1])
72
76
  accum[key] = ::String === val ? (parse_yaml_value val) : val
73
77
  end
74
78
  }).empty?
75
- document.data.update adoc_header_data
79
+ data.update adoc_data
76
80
  end
77
81
 
78
- case document.data['layout']
82
+ { 'category' => 'categories', 'tag' => 'tags' }.each do |sole_key, coll_key|
83
+ if (sole_val = data.delete sole_key) &&
84
+ !((coll_val = (data[coll_key] ||= [])).include? sole_val)
85
+ coll_val << sole_val
86
+ end
87
+ end
88
+
89
+ case data['layout']
79
90
  when nil
80
- document.content = %(#{StandaloneOptionLine}#{document.content}) unless document.data.key? 'layout'
91
+ document.content = %(#{StandaloneOptionLine}#{document.content}) unless data.key? 'layout'
81
92
  when '', '_auto'
82
93
  layout = collection_name ? (collection_name.chomp 's') : 'page'
83
- document.data['layout'] = (document.site.layouts.key? layout) ? layout : 'default'
94
+ data['layout'] = (document.site.layouts.key? layout) ? layout : 'default'
84
95
  when false
85
- document.data.delete 'layout'
96
+ data.delete 'layout'
86
97
  document.content = %(#{StandaloneOptionLine}#{document.content})
87
98
  end
88
99
 
89
- document.extend NoLiquid unless document.data['liquid']
90
- document.data.fetch 'published', true
100
+ document.extend Document
101
+ document.extend NoLiquid unless data['liquid']
102
+ data.fetch 'published', true
91
103
  end
92
104
 
93
105
  def generate_pygments_stylesheet site, attrs
@@ -114,26 +126,22 @@ module Jekyll
114
126
 
115
127
  private
116
128
 
117
- # Parse the specified value as though it is a single-line value part of a
118
- # YAML key/value pair.
129
+ # Parse the specified value as though it is a single-line value part of a YAML key/value pair.
119
130
  #
120
- # Attempt to parse the specified String value as though it is a
121
- # single-line value part of a YAML key/value pair. If the value fails to
122
- # parse, wrap the value in single quotes (after escaping any single
123
- # quotes in the value) and parse it as a character sequence. If the value
124
- # is empty, return an empty String.
131
+ # Attempt to parse the specified String value as though it is a single-line value part of a YAML key/value pair.
132
+ # If the value fails to parse, wrap the value in single quotes (after escaping any single quotes in the value) and
133
+ # parse it as a character sequence. If the value is empty, return an empty String.
125
134
  #
126
135
  # val - The String value to parse.
127
136
  #
128
- # Returns an [Object] parsed from the string-based YAML value or empty
129
- # [String] if the specified value is empty.
137
+ # Returns an [Object] parsed from the string-based YAML value or empty [String] if the specified value is empty.
130
138
  def parse_yaml_value val
131
139
  if val.empty?
132
140
  ''
133
141
  else
134
142
  begin
135
143
  ::SafeYAML.load %(--- #{val})
136
- rescue
144
+ rescue ::StandardError, ::SyntaxError
137
145
  val = val.gsub '\'', '\'\'' if val.include? '\''
138
146
  ::SafeYAML.load %(--- \'#{val}\')
139
147
  end
@@ -3,6 +3,12 @@ module Jekyll
3
3
  Configured = ::Module.new
4
4
  Document = ::Module.new
5
5
 
6
+ module Liquidable
7
+ def to_liquid
8
+ self
9
+ end
10
+ end
11
+
6
12
  module NoLiquid
7
13
  def render_with_liquid?
8
14
  false
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module AsciiDoc
3
- VERSION = '2.0.1'
3
+ VERSION = '2.1.0'
4
4
  end
5
5
  end
@@ -0,0 +1,4 @@
1
+ [#page-id]
2
+ = Page Title
3
+
4
+ content
@@ -0,0 +1,6 @@
1
+ = Page Title
2
+
3
+ [#section-id.section-role]
4
+ == Section Title
5
+
6
+ content
@@ -2,4 +2,4 @@ gems:
2
2
  - jekyll-asciidoc
3
3
  asciidoctor:
4
4
  attributes:
5
- imagesdir: /images
5
+ imagesdir: /images@
@@ -0,0 +1,2 @@
1
+ gems:
2
+ - jekyll-asciidoc
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{ page.title }}</title>
5
+ </head>
6
+ <body>
7
+ <div class="page-content">
8
+ {{ content }}
9
+ </div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,2 @@
1
+ * Doc Writer
2
+ * Word Smith