asciidoctor-bibliography 0.0.1.dev

Sign up to get free protection for your applications and to get access to all the features.
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
+