asciidoctor-bibliography 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +5 -0
  3. data/.gitignore +8 -2
  4. data/.hound.yml +3 -0
  5. data/.oss-guides.rubocop.yml +1076 -0
  6. data/.rubocop.yml +4 -10
  7. data/.travis.yml +2 -1
  8. data/Gemfile +1 -1
  9. data/README.adoc +224 -24
  10. data/Rakefile +2 -2
  11. data/asciidoctor-bibliography.gemspec +23 -23
  12. data/lib/asciidoctor-bibliography.rb +2 -2
  13. data/lib/asciidoctor-bibliography/asciidoctor.rb +4 -4
  14. data/lib/asciidoctor-bibliography/asciidoctor/bibliographer_preprocessor.rb +9 -56
  15. data/lib/asciidoctor-bibliography/bibliographer.rb +6 -7
  16. data/lib/asciidoctor-bibliography/citation.rb +56 -53
  17. data/lib/asciidoctor-bibliography/citation_item.rb +19 -10
  18. data/lib/asciidoctor-bibliography/database.rb +18 -6
  19. data/lib/asciidoctor-bibliography/databases/bibtex.rb +10 -10
  20. data/lib/asciidoctor-bibliography/errors.rb +16 -0
  21. data/lib/asciidoctor-bibliography/formatters/csl.rb +13 -2
  22. data/lib/asciidoctor-bibliography/formatters/tex.rb +42 -44
  23. data/lib/asciidoctor-bibliography/helpers.rb +9 -9
  24. data/lib/asciidoctor-bibliography/index.rb +17 -13
  25. data/lib/asciidoctor-bibliography/options.rb +97 -0
  26. data/lib/asciidoctor-bibliography/version.rb +1 -1
  27. data/samples/latex_macros_in_bibtex/sample.adoc +1 -1
  28. data/samples/standard/sample-default.adoc +3 -3
  29. data/samples/standard/sample-default.html +9 -8
  30. data/samples/standard/sample-din.adoc +1 -1
  31. data/samples/standard/sample-din.html +4 -3
  32. data/samples/standard/sample-ieee.adoc +1 -1
  33. data/samples/standard/sample-ieee.html +4 -3
  34. data/samples/tex/sample-authoryear.adoc +7 -17
  35. data/samples/tex/sample-authoryear.html +10 -32
  36. data/samples/tex/sample-numbers.adoc +7 -18
  37. data/samples/tex/sample-numbers.html +7 -29
  38. data/samples/tex/{sample-ordering.adoc → sample-sort.adoc} +6 -4
  39. data/spec/citation_item_spec.rb +67 -19
  40. data/spec/database_spec.rb +21 -16
  41. data/spec/helpers_spec.rb +46 -44
  42. data/spec/options_spec.rb +43 -0
  43. data/spec/spec_helper.rb +53 -55
  44. metadata +9 -7
  45. data/lib/asciidoctor-bibliography/exceptions.rb +0 -5
  46. data/samples/tex/sample-din.adoc +0 -74
  47. data/samples/tex/sample-din.html +0 -556
  48. data/samples/tex/sample-ordering.html +0 -467
@@ -24,19 +24,18 @@ module AsciidoctorBibliography
24
24
  end
25
25
 
26
26
  def sort
27
- if options['order'] == 'alphabetical'
28
- @occurring_keys = @occurring_keys.sort_by do |target|
29
- first_author_family_name(target)
30
- end
27
+ return unless options["order"] == "alphabetical"
28
+ @occurring_keys = @occurring_keys.sort_by do |target|
29
+ first_author_family_name(target)
31
30
  end
32
31
  end
33
32
 
34
33
  private
35
34
 
36
35
  def first_author_family_name(key)
37
- authors = database.find { |h| h['id'] == key }['author']
38
- return '' if authors.nil?
39
- authors.map { |h| h['family'] }.compact.first # TODO: is the first also alphabetically the first?
36
+ authors = database.find_entry_by_id(key)["author"]
37
+ return "" if authors.nil?
38
+ authors.map { |h| h["family"] }.compact.first # TODO: is the first also alphabetically the first?
40
39
  end
41
40
  end
42
41
  end
@@ -1,12 +1,13 @@
1
- require 'securerandom'
2
- require_relative 'formatters/csl'
3
- require_relative 'formatters/tex'
4
- require_relative 'citation_item'
1
+ require "securerandom"
2
+ require_relative "formatters/csl"
3
+ require_relative "formatters/tex"
4
+ require_relative "citation_item"
5
5
 
6
6
  module AsciidoctorBibliography
7
7
  class Citation
8
- TEX_MACROS_NAMES = Formatters::TeX::MACROS.keys.map { |s| Regexp.escape s }.concat(['fullcite']).join('|')
9
- REGEXP = /\\?(#{TEX_MACROS_NAMES}):(?:(\S*?)?\[(|.*?[^\\])\])(?:\+(\S*?)?\[(|.*?[^\\])\])*/
8
+ MACRO_NAME_REGEXP = Formatters::TeX::MACROS.keys.concat(%w[cite fullcite]).
9
+ map { |s| Regexp.escape s }.join("|").freeze
10
+ REGEXP = /\\?(#{MACRO_NAME_REGEXP}):(?:(\S*?)?\[(|.*?[^\\])\])(?:\+(\S*?)?\[(|.*?[^\\])\])*/
10
11
  REF_ATTRIBUTES = %i[chapter page section clause].freeze
11
12
 
12
13
  attr_reader :macro, :citation_items
@@ -25,70 +26,72 @@ module AsciidoctorBibliography
25
26
  end
26
27
 
27
28
  def render(bibliographer)
28
- if macro == 'cite'
29
+ case macro
30
+ when "cite"
29
31
  render_citation_with_csl(bibliographer)
30
- elsif macro == 'fullcite'
32
+ when "fullcite"
31
33
  render_fullcite_with_csl(bibliographer)
32
- elsif Formatters::TeX::MACROS.keys.include? macro
33
- formatter = Formatters::TeX.new(bibliographer.options['citation-style'])
34
+ when *Formatters::TeX::MACROS.keys
35
+ formatter = Formatters::TeX.new(bibliographer.options.tex_style)
34
36
  formatter.import bibliographer.database
35
- formatter.render(bibliographer, self)
37
+ formatter.render bibliographer, self
36
38
  end
37
39
  end
38
40
 
39
- def render_citation_with_csl(bibliographer)
40
- formatter = Formatters::CSL.new(bibliographer.options['reference-style'])
41
+ def render_fullcite_with_csl(bibliographer)
42
+ formatter = Formatters::CSL.new(bibliographer.options.style)
43
+ prepare_fullcite_item bibliographer, formatter
44
+ formatted_citation = formatter.render(:bibliography, id: citation_items.first.key).join
45
+ formatted_citation = Helpers.html_to_asciidoc formatted_citation
46
+ # We prepend an empty interpolation to avoid interferences w/ standard syntax (e.g. block role is "\n[foo]")
47
+ "{empty}" + formatted_citation
48
+ end
41
49
 
42
- cites_with_local_attributes = citation_items.map { |cite| prepare_cite_metadata bibliographer, cite }
43
- formatter.import cites_with_local_attributes
44
- formatter.sort(mode: :citation)
45
- items = formatter.data.map(&:cite)
46
- items.each { |item| prepare_citation_item item, hyperlink: bibliographer.options['hyperlinks'] == 'true' }
50
+ def prepare_fullcite_item(bibliographer, formatter)
51
+ formatter.import([bibliographer.database.find_entry_by_id(citation_items.first.key)])
52
+ end
47
53
 
54
+ def render_citation_with_csl(bibliographer)
55
+ formatter = Formatters::CSL.new(bibliographer.options.style)
56
+ items = prepare_items bibliographer, formatter
48
57
  formatted_citation = formatter.engine.renderer.render(items, formatter.engine.style.citation)
58
+ escape_brackets_inside_xref! formatted_citation
49
59
  # We prepend an empty interpolation to avoid interferences w/ standard syntax (e.g. block role is "\n[foo]")
50
- '{empty}' + formatted_citation.gsub(/{{{(?<xref_label>.*?)}}}/) do
51
- # We escape closing square brackets inside the xref label.
52
- ['[', Regexp.last_match[:xref_label].gsub(']', '\]'), ']'].join
53
- end
60
+ "{empty}" + formatted_citation
54
61
  end
55
62
 
56
- def prepare_cite_metadata(bibliographer, cite)
57
- bibliographer.database.find { |e| e['id'] == cite.key }
58
- .merge('citation-number': bibliographer.appearance_index_of(cite.key))
59
- .merge('citation-label': cite.key) # TODO: smart label generators
60
- .merge('locator': cite.locators.any? ? ' ' : nil)
61
- # TODO: why is 'locator' necessary to display locators? (and not just in the item, later)
63
+ def escape_brackets_inside_xref!(string)
64
+ string.gsub!(/{{{(?<xref_label>.*?)}}}/) do
65
+ ["[", Regexp.last_match[:xref_label].gsub("]", '\]'), "]"].join
66
+ end
62
67
  end
63
68
 
64
- def prepare_citation_item(item, hyperlink:)
65
- # Wrap into hyperlink
66
- if hyperlink
67
- item.prefix = "xref:#{xref_id(item.id)}{{{" + item.prefix.to_s
68
- item.suffix = item.suffix.to_s + '}}}'
69
- end
70
- # Assign locator
71
- locator = citation_items.find { |cite| cite.key == item.id }.locators.first
72
- item.label, item.locator = locator unless locator.nil?
73
- # TODO: suppress_author and only_author options?
69
+ def prepare_items(bibliographer, formatter)
70
+ cites_with_local_attributes = citation_items.map { |cite| prepare_metadata bibliographer, cite }
71
+ formatter.import cites_with_local_attributes
72
+ formatter.sort(mode: :citation)
73
+ formatter.data.map(&:cite).each { |item| prepare_item bibliographer.options, item }
74
74
  end
75
75
 
76
- def render_fullcite_with_csl(bibliographer)
77
- formatter = Formatters::CSL.new(bibliographer.options['reference-style'])
76
+ def prepare_metadata(bibliographer, cite)
77
+ bibliographer.database.find_entry_by_id(cite.key).
78
+ merge('citation-number': bibliographer.appearance_index_of(cite.key)).
79
+ merge('citation-label': cite.key). # TODO: smart label generators
80
+ merge('locator': cite.locator.nil? ? nil : " ")
81
+ # TODO: why is a non blank 'locator' necessary to display locators set at a later stage?
82
+ end
78
83
 
79
- # NOTE: being able to overwrite a more general family of attributes would be neat.
80
- mergeable_attributes = Helpers.slice(citation_items.first.named_attributes || {}, *REF_ATTRIBUTES.map(&:to_s))
84
+ def prepare_item(options, item)
85
+ # TODO: hyperlink, suppress_author and only_author options
86
+ ci = citation_items.detect { |c| c.key == item.id }
87
+ wrap_item item, ci.prefix, ci.suffix
88
+ wrap_item item, "xref:#{xref_id(item.id)}{{{", "}}}" if options.hyperlinks?
89
+ item.label, item.locator = ci.locator
90
+ end
81
91
 
82
- # reject empty values
83
- mergeable_attributes.reject! do |_key, value|
84
- value.nil? || value.empty?
85
- end
86
- # TODO: as is, citation items other than the first are simply ignored.
87
- database_entry = bibliographer.database.find { |e| e['id'] == citation_items.first.key }
88
- database_entry.merge!(mergeable_attributes)
89
- formatter.import([database_entry])
90
- '{empty}' + Helpers.html_to_asciidoc(formatter.render(:bibliography, id: citation_items.first.key).join)
91
- # '{empty}' + Helpers.html_to_asciidoc(formatter.render(:citation, id: citation_items.first.key))
92
+ def wrap_item(item, prefix, suffix)
93
+ item.prefix = prefix.to_s + item.prefix.to_s
94
+ item.suffix = item.suffix.to_s + suffix.to_s
92
95
  end
93
96
 
94
97
  def uuid
@@ -96,7 +99,7 @@ module AsciidoctorBibliography
96
99
  end
97
100
 
98
101
  def xref_id(key)
99
- ['bibliography', key].compact.join('-')
102
+ ["bibliography", key].compact.join("-")
100
103
  end
101
104
 
102
105
  def xref(key, label)
@@ -1,26 +1,35 @@
1
- require 'asciidoctor/attribute_list'
1
+ require "asciidoctor/attribute_list"
2
2
 
3
3
  module AsciidoctorBibliography
4
4
  class CitationItem
5
+ LOCATORS = CiteProc::CitationItem.labels.map(&:to_s).push("locator").freeze
6
+
5
7
  attr_accessor :key, :target, :positional_attributes, :named_attributes, :locators
6
8
 
7
9
  def initialize
8
10
  yield self if block_given?
9
11
  end
10
12
 
13
+ def prefix
14
+ named_attributes["prefix"]
15
+ end
16
+
17
+ def suffix
18
+ named_attributes["suffix"]
19
+ end
20
+
11
21
  def locators
12
- Helpers
13
- .slice(named_attributes || {}, *CiteProc::CitationItem.labels.map(&:to_s))
14
- .reject { |_, value| value.nil? || value.empty? } # equivalent to Hash#compact
22
+ named_attributes.select { |key, _| LOCATORS.include? key }
23
+ end
24
+
25
+ def locator
26
+ locators.first
15
27
  end
16
28
 
17
29
  def parse_attribute_list(string)
18
- parsed_attributes =
19
- ::Asciidoctor::AttributeList.new(string).parse
20
- .group_by { |hash_key, _| hash_key.is_a? Integer }
21
- .values.map { |a| Hash[a] }
22
- self.positional_attributes = parsed_attributes.first.values
23
- self.named_attributes = parsed_attributes.last
30
+ parsed_attributes = ::Asciidoctor::AttributeList.new(string).parse
31
+ self.named_attributes = parsed_attributes.reject { |key, _| key.is_a? Integer }
32
+ self.positional_attributes = parsed_attributes.select { |key, _| key.is_a? Integer }.values
24
33
  self.key = positional_attributes.shift
25
34
  end
26
35
  end
@@ -1,5 +1,5 @@
1
- require_relative 'databases/bibtex'
2
- require_relative 'exceptions'
1
+ require_relative "databases/bibtex"
2
+ require_relative "errors"
3
3
 
4
4
  module AsciidoctorBibliography
5
5
  # This is an array of citeproc entries.
@@ -14,13 +14,25 @@ module AsciidoctorBibliography
14
14
  concat Database.load(filename)
15
15
  end
16
16
 
17
+ def find_entry_by_id(id)
18
+ result = detect { |entry| entry["id"] == id }
19
+ if result.nil?
20
+ message = "No entry with id '#{id}' was found in the bibliographic database."
21
+ raise Errors::Database::IdNotFound, message
22
+ end
23
+ result
24
+ end
25
+
17
26
  def self.load(filename)
18
- case File.extname(filename)
27
+ filepath = File.expand_path filename
28
+ raise Errors::Database::FileNotFound, filepath unless File.exist?(filepath)
29
+
30
+ fileext = File.extname filepath
31
+ case fileext
19
32
  when *Databases::BibTeX::EXTENSIONS
20
- Databases::BibTeX.load(filename)
33
+ Databases::BibTeX.load filepath
21
34
  else
22
- raise Exceptions::DatabaseFormatNotSupported,
23
- 'Bibliographic database format not supported.'
35
+ raise Errors::Database::UnsupportedFormat, fileext
24
36
  end
25
37
  end
26
38
  end
@@ -1,12 +1,12 @@
1
- require 'bibtex'
2
- require 'bibtex/filters'
3
- require 'latex/decode/base'
4
- require 'latex/decode/maths'
5
- require 'latex/decode/accents'
6
- require 'latex/decode/diacritics'
7
- require 'latex/decode/punctuation'
8
- require 'latex/decode/symbols'
9
- require 'latex/decode/greek'
1
+ require "bibtex"
2
+ require "bibtex/filters"
3
+ require "latex/decode/base"
4
+ require "latex/decode/maths"
5
+ require "latex/decode/accents"
6
+ require "latex/decode/diacritics"
7
+ require "latex/decode/punctuation"
8
+ require "latex/decode/symbols"
9
+ require "latex/decode/greek"
10
10
 
11
11
  module AsciidoctorBibliography
12
12
  module Databases
@@ -33,7 +33,7 @@ module AsciidoctorBibliography
33
33
  LaTeX::Decode::Symbols.decode!(text)
34
34
  LaTeX::Decode::Greek.decode!(text)
35
35
  text.gsub!(/\\url\{(.+?)\}/, ' \\1 ')
36
- text.gsub!(/\\\w+(?=\s+\w)/, '')
36
+ text.gsub!(/\\\w+(?=\s+\w)/, "")
37
37
  text.gsub!(/\\\w+(?:\[.+?\])?\s*\{(.+?)\}/, '\\1')
38
38
  LaTeX::Decode::Base.strip_braces(text)
39
39
  LaTeX.normalize_C(text)
@@ -0,0 +1,16 @@
1
+ module AsciidoctorBibliography
2
+ module Errors
3
+ class Error < StandardError; end
4
+
5
+ module Options
6
+ class Missing < Error; end
7
+ class Invalid < Error; end
8
+ end
9
+
10
+ module Database
11
+ class UnsupportedFormat < Error; end
12
+ class FileNotFound < Error; end
13
+ class IdNotFound < Error; end
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,6 @@
1
- require 'citeproc'
2
- require 'csl/styles'
1
+ require "citeproc"
2
+ require "csl/styles"
3
+ require "yaml"
3
4
 
4
5
  module AsciidoctorBibliography
5
6
  module Formatters
@@ -8,6 +9,16 @@ module AsciidoctorBibliography
8
9
  super style: style, format: :html
9
10
  end
10
11
 
12
+ def replace_bibliography_sort(array)
13
+ new_keys = array.map(&::CSL::Style::Sort::Key.method(:new))
14
+ new_sort = ::CSL::Style::Sort.new.add_children(*new_keys)
15
+
16
+ bibliography = engine.style.find_child("bibliography")
17
+ bibliography.find_child("sort")&.unlink
18
+
19
+ bibliography.add_child new_sort
20
+ end
21
+
11
22
  def sort(mode:)
12
23
  # Valid modes are :citation and :bibliography
13
24
  engine.sort! data, engine.style.send(mode).sort_keys if engine.style.send(mode).sort?
@@ -3,20 +3,19 @@ module AsciidoctorBibliography
3
3
  # This formatter emulates the behaviour of traditional Bib(La)TeX/NatBib citations.
4
4
  class TeX
5
5
  MACROS = {
6
- # NOTE: cite = citet
7
- 'cite' => { type: :textual, bracketed: true, authors: :abbreviated },
8
- 'citet' => { type: :textual, bracketed: true, authors: :abbreviated },
9
- 'citet*' => { type: :textual, bracketed: true, authors: :full },
10
- 'citealt' => { type: :textual, bracketed: false, authors: :abbreviated },
11
- 'citealt*' => { type: :textual, bracketed: false, authors: :full },
12
- 'citep' => { type: :parenthetical, bracketed: true, authors: :abbreviated },
13
- 'citep*' => { type: :parenthetical, bracketed: true, authors: :full },
14
- 'citealp' => { type: :parenthetical, bracketed: false, authors: :abbreviated },
15
- 'citealp*' => { type: :parenthetical, bracketed: false, authors: :full },
16
- 'citeauthor' => { type: :authors_only, bracketed: false, authors: :abbreviated },
17
- 'citeauthor*' => { type: :authors_only, bracketed: false, authors: :full },
18
- 'citeyear' => { type: :years_only, bracketed: false },
19
- 'citeyearpar' => { type: :years_only, bracketed: true }
6
+ # NOTE: \citet is equivalent to \cite, so we reserve the latter for CSL styling.
7
+ "citet" => { type: :textual, bracketed: true, authors: :abbreviated },
8
+ "citet*" => { type: :textual, bracketed: true, authors: :full },
9
+ "citealt" => { type: :textual, bracketed: false, authors: :abbreviated },
10
+ "citealt*" => { type: :textual, bracketed: false, authors: :full },
11
+ "citep" => { type: :parenthetical, bracketed: true, authors: :abbreviated },
12
+ "citep*" => { type: :parenthetical, bracketed: true, authors: :full },
13
+ "citealp" => { type: :parenthetical, bracketed: false, authors: :abbreviated },
14
+ "citealp*" => { type: :parenthetical, bracketed: false, authors: :full },
15
+ "citeauthor" => { type: :authors_only, bracketed: false, authors: :abbreviated },
16
+ "citeauthor*" => { type: :authors_only, bracketed: false, authors: :full },
17
+ "citeyear" => { type: :years_only, bracketed: false },
18
+ "citeyearpar" => { type: :years_only, bracketed: true },
20
19
  }.freeze
21
20
 
22
21
  attr_accessor :opening_bracket,
@@ -27,10 +26,10 @@ module AsciidoctorBibliography
27
26
  :years_separator
28
27
 
29
28
  def initialize(format)
30
- if format == 'numbers'
31
- bibpunct = '{[}{]}{,}{n}{,}{,}'
32
- elsif format == 'authoryear'
33
- bibpunct = '{(}{)}{;}{a}{,}{,}'
29
+ if format == "numbers"
30
+ bibpunct = "{[}{]}{,}{n}{,}{,}"
31
+ elsif format == "authoryear"
32
+ bibpunct = "{(}{)}{;}{a}{,}{,}"
34
33
  else
35
34
  raise StandardError, "Unknown TeX citation format: #{format}"
36
35
  end
@@ -50,56 +49,55 @@ module AsciidoctorBibliography
50
49
  macro_options = MACROS[citation.macro]
51
50
  output = []
52
51
  case macro_options[:type]
53
- when :full
54
52
  # NOTE: deliberately repetitive to improve redability.
55
53
  when :textual
56
54
  citation.citation_items.each do |cite|
57
55
  authors = authors(macro_options[:authors], cite)
58
- year = if @style == 'n'
56
+ year = if @style == "n"
59
57
  bibliographer.appearance_index_of(cite.key)
60
58
  else
61
59
  year(cite)
62
60
  end
63
- cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + ' ')
61
+ cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + " ")
64
62
  cetera = bracket(cetera) if macro_options[:bracketed]
65
- label = Helpers.join_nonempty([authors, cetera], ' ')
63
+ label = Helpers.join_nonempty([authors, cetera], " ")
66
64
  output << citation.xref(cite.key, label)
67
65
  end
68
- output = output.join(@cites_separator + ' ')
66
+ output = output.join(@cites_separator + " ")
69
67
  when :parenthetical
70
68
  citation.citation_items.each do |cite|
71
- if @style == 'n'
69
+ if @style == "n"
72
70
  authors = nil
73
71
  year = bibliographer.appearance_index_of(cite.key)
74
72
  else
75
73
  authors = authors(macro_options[:authors], cite)
76
74
  year = year(cite)
77
75
  end
78
- cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + ' ')
79
- label = Helpers.join_nonempty([authors, cetera], @author_year_separator + ' ')
76
+ cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + " ")
77
+ label = Helpers.join_nonempty([authors, cetera], @author_year_separator + " ")
80
78
  output << citation.xref(cite.key, label)
81
79
  end
82
- output = output.join(@cites_separator + ' ')
80
+ output = output.join(@cites_separator + " ")
83
81
  output = bracket(output) if macro_options[:bracketed]
84
82
  when :authors_only
85
83
  citation.citation_items.each do |cite|
86
84
  authors = authors(macro_options[:authors], cite)
87
85
  year = nil
88
- cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + ' ')
89
- label = Helpers.join_nonempty([authors, cetera], @author_year_separator + ' ')
86
+ cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + " ")
87
+ label = Helpers.join_nonempty([authors, cetera], @author_year_separator + " ")
90
88
  output << citation.xref(cite.key, label)
91
89
  end
92
- output = output.join(@cites_separator + ' ')
90
+ output = output.join(@cites_separator + " ")
93
91
  output = bracket(output) if macro_options[:bracketed]
94
92
  when :years_only
95
93
  citation.citation_items.each do |cite|
96
94
  authors = nil
97
95
  year = year(cite)
98
- cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + ' ')
99
- label = Helpers.join_nonempty([authors, cetera], @author_year_separator + ' ')
96
+ cetera = Helpers.join_nonempty([year].concat(extra(cite)), @years_separator + " ")
97
+ label = Helpers.join_nonempty([authors, cetera], @author_year_separator + " ")
100
98
  output << citation.xref(cite.key, label)
101
99
  end
102
- output = output.join(@cites_separator + ' ')
100
+ output = output.join(@cites_separator + " ")
103
101
  output = bracket(output) if macro_options[:bracketed]
104
102
  else
105
103
  raise StandardError, "Unknown TeX citation macro type: #{macro_options[:type]}"
@@ -115,24 +113,24 @@ module AsciidoctorBibliography
115
113
  end
116
114
 
117
115
  def find_entry(key)
118
- entry = @database.find { |h| h['id'] == key }
116
+ entry = @database.detect { |h| h["id"] == key }
119
117
  raise StandardError, "Can't find entry: #{key}" if entry.nil?
120
118
  entry
121
119
  end
122
120
 
123
121
  def year(cite)
124
122
  entry = find_entry(cite.key)
125
- issued = entry['issued']
123
+ issued = entry["issued"]
126
124
 
127
125
  if issued.nil?
128
- puts "asciidoctor-bibliography: citation (#{cite.key}) has no 'issued' information"
129
- return ''
126
+ warn "asciidoctor-bibliography: citation (#{cite.key}) has no 'issued' information"
127
+ return ""
130
128
  end
131
129
 
132
- date_parts = issued['date-parts']
133
- return '' if date_parts.nil?
130
+ date_parts = issued["date-parts"]
131
+ return "" if date_parts.nil?
134
132
 
135
- return '' if date_parts.first.nil?
133
+ return "" if date_parts.first.nil?
136
134
  date_parts.first.first
137
135
  end
138
136
 
@@ -168,20 +166,20 @@ module AsciidoctorBibliography
168
166
 
169
167
  def authors_list(cite)
170
168
  entry = find_entry(cite.key)
171
- authors = entry['author']
169
+ authors = entry["author"]
172
170
  return [] if authors.nil?
173
- authors.map { |h| h['family'] }.compact
171
+ authors.map { |h| h["family"] }.compact
174
172
  end
175
173
 
176
174
  def authors_abbreviated(cite)
177
175
  authors = authors_list(cite)
178
- return '' if authors.empty?
176
+ return "" if authors.empty?
179
177
  authors.length > 1 ? "#{authors.first} et al." : authors.first
180
178
  end
181
179
 
182
180
  def authors_full(cite)
183
181
  authors = authors_list(cite)
184
- return '' if authors.empty?
182
+ return "" if authors.empty?
185
183
  Helpers.to_sentence authors
186
184
  end
187
185
  end