asciidoctor-bibliography 0.0.1.dev
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 +7 -0
- data/.gitignore +50 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.adoc +101 -0
- data/SYNTAX.adoc +117 -0
- data/asciidoctor-bibliography.gemspec +37 -0
- data/deprecated/asciidoctor-bibliography/asciidoctor/bibliographer_postprocessor.rb +23 -0
- data/deprecated/asciidoctor-bibliography/asciidoctor/bibliography_block_macro.rb +77 -0
- data/deprecated/asciidoctor-bibliography/asciidoctor/citation_processor.rb +144 -0
- data/deprecated/asciidoctor-bibliography/asciidoctor/cite_inline_macro.rb +30 -0
- data/deprecated/asciidoctor-bibliography/citationdata.rb +23 -0
- data/deprecated/asciidoctor-bibliography/citations.rb +45 -0
- data/deprecated/asciidoctor-bibliography/citationutils.rb +67 -0
- data/deprecated/asciidoctor-bibliography/extensions.rb +64 -0
- data/deprecated/asciidoctor-bibliography/filehandlers.rb +32 -0
- data/deprecated/asciidoctor-bibliography/index.rb +31 -0
- data/deprecated/asciidoctor-bibliography/processor.rb +208 -0
- data/deprecated/asciidoctor-bibliography/processorutils.rb +34 -0
- data/deprecated/asciidoctor-bibliography/styles.rb +27 -0
- data/lib/asciidoctor-bibliography.rb +3 -0
- data/lib/asciidoctor-bibliography/asciidoctor.rb +17 -0
- data/lib/asciidoctor-bibliography/asciidoctor/bibliographer_preprocessor.rb +68 -0
- data/lib/asciidoctor-bibliography/bibliographer.rb +31 -0
- data/lib/asciidoctor-bibliography/citation.rb +73 -0
- data/lib/asciidoctor-bibliography/database.rb +20 -0
- data/lib/asciidoctor-bibliography/databases/bibtex.rb +43 -0
- data/lib/asciidoctor-bibliography/formatters/csl.rb +12 -0
- data/lib/asciidoctor-bibliography/formatters/tex.rb +164 -0
- data/lib/asciidoctor-bibliography/helpers.rb +40 -0
- data/lib/asciidoctor-bibliography/index.rb +43 -0
- data/lib/asciidoctor-bibliography/version.rb +3 -0
- data/samples/.byebug_history +245 -0
- data/samples/biblio.bib +31 -0
- data/samples/latex_macros_in_bibtex/reference.bib +16 -0
- data/samples/latex_macros_in_bibtex/sample.adoc +13 -0
- data/samples/sample-authoryear.adoc +72 -0
- data/samples/sample-authoryear.html +550 -0
- data/samples/sample-numbers.adoc +72 -0
- data/samples/sample-numbers.html +550 -0
- metadata +187 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
#
|
2
|
+
# Manage the current set of citations, the document settings,
|
3
|
+
# and main operations.
|
4
|
+
#
|
5
|
+
|
6
|
+
module AsciidoctorBibliography
|
7
|
+
|
8
|
+
# Class used through utility method to hold data about citations for
|
9
|
+
# current document, and run the different steps to add the citations
|
10
|
+
# and bibliography
|
11
|
+
class Processor
|
12
|
+
include ProcessorUtils
|
13
|
+
|
14
|
+
attr_reader :biblio, :links, :style, :citations
|
15
|
+
|
16
|
+
def initialize biblio, links, style, numeric_in_appearance_order = false, output = :asciidoc, bibfile = ""
|
17
|
+
@biblio = biblio
|
18
|
+
@links = links
|
19
|
+
@numeric_in_appearance_order = numeric_in_appearance_order
|
20
|
+
@style = style
|
21
|
+
@citations = Citations.new
|
22
|
+
@filenames = Set.new
|
23
|
+
@output = output
|
24
|
+
@bibfile = bibfile
|
25
|
+
|
26
|
+
if output != :latex
|
27
|
+
@citeproc = CiteProc::Processor.new style: @style, format: :html
|
28
|
+
@citeproc.import @biblio.to_citeproc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return the complete citation text for given cite_data
|
33
|
+
def complete_citation cite_data
|
34
|
+
|
35
|
+
if @output == :latex
|
36
|
+
result = '+++'
|
37
|
+
cite_data.cites.each do |cite|
|
38
|
+
# NOTE: xelatex does not support "\citenp", so we output all
|
39
|
+
# references as "cite" here.
|
40
|
+
# result << "\\" << cite_data.type
|
41
|
+
result << "\\" << 'cite'
|
42
|
+
if cite.pages != ''
|
43
|
+
result << "[p. " << cite.pages << "]"
|
44
|
+
end
|
45
|
+
result << "{" << "#{cite.ref}" << "},"
|
46
|
+
end
|
47
|
+
if result[-1] == ','
|
48
|
+
result = result[0..-2]
|
49
|
+
end
|
50
|
+
result << "+++"
|
51
|
+
return result
|
52
|
+
else
|
53
|
+
result = ''
|
54
|
+
ob, cb = '(', ')'
|
55
|
+
|
56
|
+
cite_data.cites.each_with_index do |cite, index|
|
57
|
+
# before all items apart from the first, insert appropriate separator
|
58
|
+
result << "#{separator} " unless index.zero?
|
59
|
+
|
60
|
+
# @links requires adding hyperlink to reference
|
61
|
+
result << "<<#{cite.ref}," if @links and (cite_data.type != 'fullcite')
|
62
|
+
|
63
|
+
# if found, insert reference information
|
64
|
+
unless biblio[cite.ref].nil?
|
65
|
+
item = biblio[cite.ref].clone
|
66
|
+
cite_text, ob, cb = make_citation item, cite.ref, cite_data, cite
|
67
|
+
else
|
68
|
+
puts "Unknown reference: #{cite.ref}"
|
69
|
+
cite_text = "#{cite.ref}"
|
70
|
+
end
|
71
|
+
|
72
|
+
result << cite_text.html_to_asciidoc
|
73
|
+
# @links requires finish hyperlink
|
74
|
+
result << ">>" if @links and (cite_data.type != 'fullcite')
|
75
|
+
end
|
76
|
+
|
77
|
+
unless @links
|
78
|
+
# combine numeric ranges
|
79
|
+
if Styles.is_numeric? @style
|
80
|
+
result = combine_consecutive_numbers result
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return include_pretext result, cite_data, ob, cb
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Retrieve text for reference in given style
|
89
|
+
# - ref is reference for item to give reference for
|
90
|
+
def get_reference ref
|
91
|
+
result = ""
|
92
|
+
result << ". " if Styles.is_numeric? @style
|
93
|
+
|
94
|
+
begin
|
95
|
+
cptext = @citeproc.render :bibliography, id: ref
|
96
|
+
rescue Exception => e
|
97
|
+
puts "Failed to render #{ref}: #{e}"
|
98
|
+
end
|
99
|
+
result << "[[#{ref}]]" if @links
|
100
|
+
if cptext.nil?
|
101
|
+
return result+ref
|
102
|
+
else
|
103
|
+
result << cptext.first
|
104
|
+
end
|
105
|
+
|
106
|
+
return result.html_to_asciidoc
|
107
|
+
end
|
108
|
+
|
109
|
+
def separator
|
110
|
+
if Styles.is_numeric? @style
|
111
|
+
','
|
112
|
+
else
|
113
|
+
';'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Format pages with pp/p as appropriate
|
118
|
+
def with_pp pages
|
119
|
+
return '' if pages.empty?
|
120
|
+
|
121
|
+
if @style.include? "chicago"
|
122
|
+
pages
|
123
|
+
elsif pages.include? '-'
|
124
|
+
"pp. #{pages}"
|
125
|
+
else
|
126
|
+
"p. #{pages}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return page string for given cite
|
131
|
+
def page_str cite
|
132
|
+
result = ''
|
133
|
+
unless cite.pages.empty?
|
134
|
+
result << "," unless Styles.is_numeric? @style
|
135
|
+
result << " #{with_pp(cite.pages)}"
|
136
|
+
end
|
137
|
+
|
138
|
+
return result
|
139
|
+
end
|
140
|
+
|
141
|
+
def include_pretext result, cite_data, ob, cb
|
142
|
+
pretext = cite_data.pretext
|
143
|
+
pretext += ' ' unless pretext.empty? # add space after any content
|
144
|
+
|
145
|
+
if Styles.is_numeric? @style
|
146
|
+
"#{pretext}#{ob}#{result}#{cb}"
|
147
|
+
elsif cite_data.type == "cite"
|
148
|
+
"#{ob}#{pretext}#{result}#{cb}"
|
149
|
+
else
|
150
|
+
"#{pretext}#{result}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Numeric citations are handled by computing the position of the reference
|
155
|
+
# in the list of used citations.
|
156
|
+
# Other citations are formatted by citeproc.
|
157
|
+
def make_citation item, ref, cite_data, cite
|
158
|
+
if cite_data.type == "fullcite"
|
159
|
+
cite_text = @citeproc.render(:bibliography, id: ref).join
|
160
|
+
|
161
|
+
fc = ''
|
162
|
+
lc = ''
|
163
|
+
elsif Styles.is_numeric? @style
|
164
|
+
cite_text = if @numeric_in_appearance_order
|
165
|
+
"#{@citations.cites_used.index(cite.ref) + 1}"
|
166
|
+
else
|
167
|
+
"#{sorted_cites.index(cite.ref) + 1}"
|
168
|
+
end
|
169
|
+
fc = '['
|
170
|
+
lc = ']'
|
171
|
+
else
|
172
|
+
cite_text = @citeproc.process id: ref, mode: :citation
|
173
|
+
|
174
|
+
fc = cite_text[0,1]
|
175
|
+
lc = cite_text[-1,1]
|
176
|
+
cite_text = cite_text[1..-2]
|
177
|
+
end
|
178
|
+
|
179
|
+
if cite_data.type == "fullcite"
|
180
|
+
cite_text = cite_text[0...-1] + page_str(cite) + cite_text[-1]
|
181
|
+
elsif Styles.is_numeric? @style
|
182
|
+
cite_text << "#{page_str(cite)}"
|
183
|
+
elsif cite_data.type == "citenp"
|
184
|
+
cite_text.gsub!(item.year, "#{fc}#{item.year}#{page_str(cite)}#{lc}")
|
185
|
+
cite_text.gsub!(", #{fc}", " #{fc}")
|
186
|
+
else
|
187
|
+
cite_text << page_str(cite)
|
188
|
+
end
|
189
|
+
|
190
|
+
cite_text.gsub!(",", ",") if @links # replace comma
|
191
|
+
|
192
|
+
return cite_text, fc, lc
|
193
|
+
end
|
194
|
+
|
195
|
+
def sorted_cites
|
196
|
+
@citations.sorted_cites @biblio
|
197
|
+
end
|
198
|
+
|
199
|
+
def cites
|
200
|
+
if Styles.is_numeric?(@style) and @numeric_in_appearance_order
|
201
|
+
@citations.cites_used
|
202
|
+
else
|
203
|
+
sorted_cites
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module AsciidoctorBibliography
|
3
|
+
module ProcessorUtils
|
4
|
+
# Used with numeric styles to combine consecutive numbers into ranges
|
5
|
+
# e.g. 1,2,3 -> 1-3, or 1,2,3,6,7,8,9,12 -> 1-3,6-9,12
|
6
|
+
# leave references with page numbers alone
|
7
|
+
def combine_consecutive_numbers str
|
8
|
+
nums = str.split(",").collect(&:strip)
|
9
|
+
res = ""
|
10
|
+
# Loop through ranges
|
11
|
+
start_range = 0
|
12
|
+
while start_range < nums.length do
|
13
|
+
end_range = start_range
|
14
|
+
while (end_range < nums.length-1 and
|
15
|
+
nums[end_range].is_i? and
|
16
|
+
nums[end_range+1].is_i? and
|
17
|
+
nums[end_range+1].to_i == nums[end_range].to_i + 1) do
|
18
|
+
end_range += 1
|
19
|
+
end
|
20
|
+
if end_range - start_range >= 2
|
21
|
+
res += "#{nums[start_range]}-#{nums[end_range]}, "
|
22
|
+
else
|
23
|
+
start_range.upto(end_range) do |i|
|
24
|
+
res += "#{nums[i]}, "
|
25
|
+
end
|
26
|
+
end
|
27
|
+
start_range = end_range + 1
|
28
|
+
end
|
29
|
+
# finish by removing last comma
|
30
|
+
res.gsub(/, $/, '')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#
|
2
|
+
# styles.rb
|
3
|
+
# Simple checks on available styles through CSL
|
4
|
+
#
|
5
|
+
|
6
|
+
module AsciidoctorBibliography
|
7
|
+
|
8
|
+
module Styles
|
9
|
+
|
10
|
+
def Styles.available
|
11
|
+
CSL::Style.ls
|
12
|
+
end
|
13
|
+
|
14
|
+
def Styles.default_style
|
15
|
+
'apa'
|
16
|
+
end
|
17
|
+
|
18
|
+
def Styles.valid? style
|
19
|
+
Styles.available.include? style
|
20
|
+
end
|
21
|
+
|
22
|
+
def Styles.is_numeric? style
|
23
|
+
CSL::Style.load(style).citation_format == :numeric
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'asciidoctor/extensions'
|
2
|
+
|
3
|
+
require_relative 'asciidoctor/bibliographer_preprocessor'
|
4
|
+
require_relative 'bibliographer'
|
5
|
+
|
6
|
+
Asciidoctor::Extensions.register do
|
7
|
+
preprocessor AsciidoctorBibliography::Asciidoctor::BibliographerPreprocessor
|
8
|
+
end
|
9
|
+
|
10
|
+
module Asciidoctor
|
11
|
+
class Document
|
12
|
+
# All our document-level permanence passes through this attribute accessor.
|
13
|
+
def bibliographer
|
14
|
+
@bibliographer ||= AsciidoctorBibliography::Bibliographer.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'asciidoctor'
|
2
|
+
|
3
|
+
require_relative '../helpers'
|
4
|
+
require_relative '../formatters/csl'
|
5
|
+
require_relative '../formatters/tex'
|
6
|
+
require_relative '../database'
|
7
|
+
require_relative '../citation'
|
8
|
+
require_relative '../index'
|
9
|
+
|
10
|
+
module AsciidoctorBibliography
|
11
|
+
module Asciidoctor
|
12
|
+
class BibliographerPreprocessor < ::Asciidoctor::Extensions::Preprocessor
|
13
|
+
def process document, reader
|
14
|
+
|
15
|
+
# We peek at the document attributes we need, without perturbing the parsing flow.
|
16
|
+
# NOTE: we're in a preprocessor and they haven't been parsed yet; doing it manually.
|
17
|
+
document_attributes =
|
18
|
+
::Asciidoctor::Parser
|
19
|
+
.parse(reader, ::Asciidoctor::Document.new, header_only: true)
|
20
|
+
.attributes
|
21
|
+
# We extract only the ones we recognize.
|
22
|
+
document.bibliographer.options = Hash[Helpers.slice(document_attributes, 'bibliography-citation-style', 'bibliography-order', 'bibliography-reference-style', 'bibliography-database').map {|k, v| [k.sub(/^bibliography-/, ''), v] }]
|
23
|
+
|
24
|
+
# We're handling single database/formatters; generalization will be straightforward when needed.
|
25
|
+
document.bibliographer.database = Database.new(document.bibliographer.options['database'])
|
26
|
+
document.bibliographer.index_formatter = Formatters::CSL.new(document.bibliographer.options['reference-style'])
|
27
|
+
document.bibliographer.index_formatter.import document.bibliographer.database
|
28
|
+
document.bibliographer.citation_formatter = Formatters::TeX.new(document.bibliographer.options['citation-style'])
|
29
|
+
document.bibliographer.citation_formatter.import document.bibliographer.database
|
30
|
+
|
31
|
+
# Find, store and replace citations with uuids.
|
32
|
+
processed_lines = reader.read_lines.map do |line|
|
33
|
+
line.gsub(Citation::REGEXP) do
|
34
|
+
citation = Citation.new(*Regexp.last_match.captures)
|
35
|
+
document.bibliographer.add_citation(citation)
|
36
|
+
citation.uuid
|
37
|
+
end
|
38
|
+
end
|
39
|
+
reader = ::Asciidoctor::Reader.new processed_lines
|
40
|
+
|
41
|
+
# NOTE: retrieval and formatting are separated to allow sorting and numeric styles.
|
42
|
+
|
43
|
+
# Find and replace uuids with formatted citations.
|
44
|
+
processed_lines = reader.lines.join("\n") # for quicker matching
|
45
|
+
document.bibliographer.citations.each do |citation|
|
46
|
+
processed_lines.sub!(citation.uuid) do
|
47
|
+
citation.render document.bibliographer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
processed_lines = processed_lines.lines.map(&:chomp)
|
51
|
+
|
52
|
+
reader = ::Asciidoctor::Reader.new processed_lines
|
53
|
+
|
54
|
+
# Find and format indices.
|
55
|
+
processed_lines = reader.read_lines.map do |line|
|
56
|
+
if line =~ Index::REGEXP
|
57
|
+
index = Index.new(*Regexp.last_match.captures)
|
58
|
+
index.render document.bibliographer
|
59
|
+
else
|
60
|
+
line
|
61
|
+
end
|
62
|
+
end
|
63
|
+
processed_lines.flatten!
|
64
|
+
reader = ::Asciidoctor::Reader.new processed_lines
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AsciidoctorBibliography
|
2
|
+
class Bibliographer
|
3
|
+
attr_accessor :citations
|
4
|
+
attr_accessor :indices
|
5
|
+
attr_accessor :database
|
6
|
+
attr_accessor :index_formatter
|
7
|
+
attr_accessor :citation_formatter
|
8
|
+
attr_reader :occurring_keys
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
# NOTE: while database and formatter are singular, they're meant for future generalization.
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@options = {}
|
15
|
+
@citations = []
|
16
|
+
@indices = []
|
17
|
+
@database = nil
|
18
|
+
@index_formatter = nil
|
19
|
+
@citation_formatter = nil
|
20
|
+
@occurring_keys = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_citation(citation)
|
24
|
+
citations << citation
|
25
|
+
@occurring_keys.concat(citation.keys).uniq!
|
26
|
+
citations.last.cites.each do |cite|
|
27
|
+
cite.occurrence_index = @occurring_keys.index(cite.key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'asciidoctor/attribute_list'
|
3
|
+
|
4
|
+
module AsciidoctorBibliography
|
5
|
+
class Citation
|
6
|
+
TEX_MACROS_NAMES = Formatters::TeX::MACROS.keys.map { |s| Regexp.escape s }.concat(['fullcite']).join('|')
|
7
|
+
REGEXP = /\\?(#{TEX_MACROS_NAMES}):(?:(\S*?)?\[(|.*?[^\\])\])(?:\+(\S*?)?\[(|.*?[^\\])\])*/
|
8
|
+
|
9
|
+
# No need for a fully fledged class right now.
|
10
|
+
Cite = Struct.new(:key, :occurrence_index, :target, :positional_attributes, :named_attributes)
|
11
|
+
|
12
|
+
attr_reader :macro, :cites
|
13
|
+
|
14
|
+
def initialize(macro, *targets_and_attributes_list)
|
15
|
+
@uuid = SecureRandom.uuid
|
16
|
+
@macro = macro
|
17
|
+
@cites = []
|
18
|
+
targets_and_attributes_list.compact.each_slice(2).each do |target, attributes|
|
19
|
+
positional_attributes, named_attributes = # true, false
|
20
|
+
::Asciidoctor::AttributeList.new(attributes).parse
|
21
|
+
.group_by { |hash_key, _| hash_key.is_a? Integer }
|
22
|
+
.values.map { |a| Hash[a] }
|
23
|
+
positional_attributes = positional_attributes.values
|
24
|
+
@cites << Cite.new(
|
25
|
+
positional_attributes.first,
|
26
|
+
nil,
|
27
|
+
target,
|
28
|
+
positional_attributes,
|
29
|
+
named_attributes
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def render(bibliographer)
|
35
|
+
if macro == 'fullcite'
|
36
|
+
# NOTE: we reinstantiate to avoid tracking used keys.
|
37
|
+
formatter = Formatters::CSL.new(bibliographer.options['reference-style'])
|
38
|
+
formatter.import bibliographer.database
|
39
|
+
# TODO: as is, cites other than the first are simply ignored.
|
40
|
+
'{empty}' + Helpers.html_to_asciidoc(formatter.render(:bibliography, id: cites.first.key).join)
|
41
|
+
elsif Formatters::TeX::MACROS.keys.include? macro
|
42
|
+
bibliographer.citation_formatter.render(self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def uuid
|
47
|
+
":#{@uuid}:"
|
48
|
+
end
|
49
|
+
|
50
|
+
def keys
|
51
|
+
@cites.map { |h| h[:key] }
|
52
|
+
end
|
53
|
+
|
54
|
+
def xref(key, label)
|
55
|
+
"xref:#{self.render_id(key)}[#{label.gsub(']','\]')}]"
|
56
|
+
end
|
57
|
+
|
58
|
+
def render_id(key)
|
59
|
+
['bibliography', key].compact.join('-')
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def render_label(formatter, key)
|
65
|
+
formatter.render(:citation, id: key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_xref(formatter, key)
|
69
|
+
"xref:#{render_id(key)}[#{render_label(formatter, key)}]"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|