asciidoctor-bibliography 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +5 -0
- data/.gitignore +8 -2
- data/.hound.yml +3 -0
- data/.oss-guides.rubocop.yml +1076 -0
- data/.rubocop.yml +4 -10
- data/.travis.yml +2 -1
- data/Gemfile +1 -1
- data/README.adoc +224 -24
- data/Rakefile +2 -2
- data/asciidoctor-bibliography.gemspec +23 -23
- data/lib/asciidoctor-bibliography.rb +2 -2
- data/lib/asciidoctor-bibliography/asciidoctor.rb +4 -4
- data/lib/asciidoctor-bibliography/asciidoctor/bibliographer_preprocessor.rb +9 -56
- data/lib/asciidoctor-bibliography/bibliographer.rb +6 -7
- data/lib/asciidoctor-bibliography/citation.rb +56 -53
- data/lib/asciidoctor-bibliography/citation_item.rb +19 -10
- data/lib/asciidoctor-bibliography/database.rb +18 -6
- data/lib/asciidoctor-bibliography/databases/bibtex.rb +10 -10
- data/lib/asciidoctor-bibliography/errors.rb +16 -0
- data/lib/asciidoctor-bibliography/formatters/csl.rb +13 -2
- data/lib/asciidoctor-bibliography/formatters/tex.rb +42 -44
- data/lib/asciidoctor-bibliography/helpers.rb +9 -9
- data/lib/asciidoctor-bibliography/index.rb +17 -13
- data/lib/asciidoctor-bibliography/options.rb +97 -0
- data/lib/asciidoctor-bibliography/version.rb +1 -1
- data/samples/latex_macros_in_bibtex/sample.adoc +1 -1
- data/samples/standard/sample-default.adoc +3 -3
- data/samples/standard/sample-default.html +9 -8
- data/samples/standard/sample-din.adoc +1 -1
- data/samples/standard/sample-din.html +4 -3
- data/samples/standard/sample-ieee.adoc +1 -1
- data/samples/standard/sample-ieee.html +4 -3
- data/samples/tex/sample-authoryear.adoc +7 -17
- data/samples/tex/sample-authoryear.html +10 -32
- data/samples/tex/sample-numbers.adoc +7 -18
- data/samples/tex/sample-numbers.html +7 -29
- data/samples/tex/{sample-ordering.adoc → sample-sort.adoc} +6 -4
- data/spec/citation_item_spec.rb +67 -19
- data/spec/database_spec.rb +21 -16
- data/spec/helpers_spec.rb +46 -44
- data/spec/options_spec.rb +43 -0
- data/spec/spec_helper.rb +53 -55
- metadata +9 -7
- data/lib/asciidoctor-bibliography/exceptions.rb +0 -5
- data/samples/tex/sample-din.adoc +0 -74
- data/samples/tex/sample-din.html +0 -556
- data/samples/tex/sample-ordering.html +0 -467
| @@ -24,19 +24,18 @@ module AsciidoctorBibliography | |
| 24 24 | 
             
                end
         | 
| 25 25 |  | 
| 26 26 | 
             
                def sort
         | 
| 27 | 
            -
                   | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 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. | 
| 38 | 
            -
                  return  | 
| 39 | 
            -
                  authors.map { |h| h[ | 
| 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  | 
| 2 | 
            -
            require_relative  | 
| 3 | 
            -
            require_relative  | 
| 4 | 
            -
            require_relative  | 
| 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 | 
            -
                 | 
| 9 | 
            -
             | 
| 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 | 
            -
                   | 
| 29 | 
            +
                  case macro
         | 
| 30 | 
            +
                  when "cite"
         | 
| 29 31 | 
             
                    render_citation_with_csl(bibliographer)
         | 
| 30 | 
            -
                   | 
| 32 | 
            +
                  when "fullcite"
         | 
| 31 33 | 
             
                    render_fullcite_with_csl(bibliographer)
         | 
| 32 | 
            -
                   | 
| 33 | 
            -
                    formatter = Formatters::TeX.new(bibliographer.options | 
| 34 | 
            +
                  when *Formatters::TeX::MACROS.keys
         | 
| 35 | 
            +
                    formatter = Formatters::TeX.new(bibliographer.options.tex_style)
         | 
| 34 36 | 
             
                    formatter.import bibliographer.database
         | 
| 35 | 
            -
                    formatter.render | 
| 37 | 
            +
                    formatter.render bibliographer, self
         | 
| 36 38 | 
             
                  end
         | 
| 37 39 | 
             
                end
         | 
| 38 40 |  | 
| 39 | 
            -
                def  | 
| 40 | 
            -
                  formatter = Formatters::CSL.new(bibliographer.options | 
| 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 | 
            -
             | 
| 43 | 
            -
                  formatter.import | 
| 44 | 
            -
             | 
| 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 | 
            -
                   | 
| 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  | 
| 57 | 
            -
                   | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 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  | 
| 65 | 
            -
                   | 
| 66 | 
            -
                   | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 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  | 
| 77 | 
            -
                   | 
| 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 | 
            -
             | 
| 80 | 
            -
                   | 
| 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 | 
            -
             | 
| 83 | 
            -
                   | 
| 84 | 
            -
             | 
| 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 | 
            -
                  [ | 
| 102 | 
            +
                  ["bibliography", key].compact.join("-")
         | 
| 100 103 | 
             
                end
         | 
| 101 104 |  | 
| 102 105 | 
             
                def xref(key, label)
         | 
| @@ -1,26 +1,35 @@ | |
| 1 | 
            -
            require  | 
| 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 | 
            -
                   | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 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 | 
            -
             | 
| 20 | 
            -
             | 
| 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  | 
| 2 | 
            -
            require_relative  | 
| 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 | 
            -
                   | 
| 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 | 
| 33 | 
            +
                    Databases::BibTeX.load filepath
         | 
| 21 34 | 
             
                  else
         | 
| 22 | 
            -
                    raise  | 
| 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  | 
| 2 | 
            -
            require  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 6 | 
            -
            require  | 
| 7 | 
            -
            require  | 
| 8 | 
            -
            require  | 
| 9 | 
            -
            require  | 
| 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  | 
| 2 | 
            -
            require  | 
| 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  | 
| 7 | 
            -
                     | 
| 8 | 
            -
                     | 
| 9 | 
            -
                     | 
| 10 | 
            -
                     | 
| 11 | 
            -
                     | 
| 12 | 
            -
                     | 
| 13 | 
            -
                     | 
| 14 | 
            -
                     | 
| 15 | 
            -
                     | 
| 16 | 
            -
                     | 
| 17 | 
            -
                     | 
| 18 | 
            -
                     | 
| 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 ==  | 
| 31 | 
            -
                      bibpunct =  | 
| 32 | 
            -
                    elsif format ==  | 
| 33 | 
            -
                      bibpunct =  | 
| 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 ==  | 
| 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 ==  | 
| 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. | 
| 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[ | 
| 123 | 
            +
                    issued = entry["issued"]
         | 
| 126 124 |  | 
| 127 125 | 
             
                    if issued.nil?
         | 
| 128 | 
            -
                       | 
| 129 | 
            -
                      return  | 
| 126 | 
            +
                      warn "asciidoctor-bibliography: citation (#{cite.key}) has no 'issued' information"
         | 
| 127 | 
            +
                      return ""
         | 
| 130 128 | 
             
                    end
         | 
| 131 129 |  | 
| 132 | 
            -
                    date_parts = issued[ | 
| 133 | 
            -
                    return  | 
| 130 | 
            +
                    date_parts = issued["date-parts"]
         | 
| 131 | 
            +
                    return "" if date_parts.nil?
         | 
| 134 132 |  | 
| 135 | 
            -
                    return  | 
| 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[ | 
| 169 | 
            +
                    authors = entry["author"]
         | 
| 172 170 | 
             
                    return [] if authors.nil?
         | 
| 173 | 
            -
                    authors.map { |h| h[ | 
| 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  | 
| 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  | 
| 182 | 
            +
                    return "" if authors.empty?
         | 
| 185 183 | 
             
                    Helpers.to_sentence authors
         | 
| 186 184 | 
             
                  end
         | 
| 187 185 | 
             
                end
         |