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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/Gemfile +21 -0
  4. data/Gemfile.lock +41 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.adoc +101 -0
  7. data/SYNTAX.adoc +117 -0
  8. data/asciidoctor-bibliography.gemspec +37 -0
  9. data/deprecated/asciidoctor-bibliography/asciidoctor/bibliographer_postprocessor.rb +23 -0
  10. data/deprecated/asciidoctor-bibliography/asciidoctor/bibliography_block_macro.rb +77 -0
  11. data/deprecated/asciidoctor-bibliography/asciidoctor/citation_processor.rb +144 -0
  12. data/deprecated/asciidoctor-bibliography/asciidoctor/cite_inline_macro.rb +30 -0
  13. data/deprecated/asciidoctor-bibliography/citationdata.rb +23 -0
  14. data/deprecated/asciidoctor-bibliography/citations.rb +45 -0
  15. data/deprecated/asciidoctor-bibliography/citationutils.rb +67 -0
  16. data/deprecated/asciidoctor-bibliography/extensions.rb +64 -0
  17. data/deprecated/asciidoctor-bibliography/filehandlers.rb +32 -0
  18. data/deprecated/asciidoctor-bibliography/index.rb +31 -0
  19. data/deprecated/asciidoctor-bibliography/processor.rb +208 -0
  20. data/deprecated/asciidoctor-bibliography/processorutils.rb +34 -0
  21. data/deprecated/asciidoctor-bibliography/styles.rb +27 -0
  22. data/lib/asciidoctor-bibliography.rb +3 -0
  23. data/lib/asciidoctor-bibliography/asciidoctor.rb +17 -0
  24. data/lib/asciidoctor-bibliography/asciidoctor/bibliographer_preprocessor.rb +68 -0
  25. data/lib/asciidoctor-bibliography/bibliographer.rb +31 -0
  26. data/lib/asciidoctor-bibliography/citation.rb +73 -0
  27. data/lib/asciidoctor-bibliography/database.rb +20 -0
  28. data/lib/asciidoctor-bibliography/databases/bibtex.rb +43 -0
  29. data/lib/asciidoctor-bibliography/formatters/csl.rb +12 -0
  30. data/lib/asciidoctor-bibliography/formatters/tex.rb +164 -0
  31. data/lib/asciidoctor-bibliography/helpers.rb +40 -0
  32. data/lib/asciidoctor-bibliography/index.rb +43 -0
  33. data/lib/asciidoctor-bibliography/version.rb +3 -0
  34. data/samples/.byebug_history +245 -0
  35. data/samples/biblio.bib +31 -0
  36. data/samples/latex_macros_in_bibtex/reference.bib +16 -0
  37. data/samples/latex_macros_in_bibtex/sample.adoc +13 -0
  38. data/samples/sample-authoryear.adoc +72 -0
  39. data/samples/sample-authoryear.html +550 -0
  40. data/samples/sample-numbers.adoc +72 -0
  41. data/samples/sample-numbers.html +550 -0
  42. 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.&#160;#{pages}"
125
+ else
126
+ "p.&#160;#{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!(",", "&#44;") 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,3 @@
1
+ require 'byebug'
2
+
3
+ require_relative 'asciidoctor-bibliography/asciidoctor'
@@ -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
+