diff --git a/lib/review/book/bibliography.rb b/lib/review/book/bibliography.rb new file mode 100644 index 000000000..d0f3e139a --- /dev/null +++ b/lib/review/book/bibliography.rb @@ -0,0 +1,73 @@ +begin + require 'bibtex' + require 'citeproc' + require 'csl/styles' + require 'review/book/bibliography_format' +rescue LoadError + # raise ReVIEW::ConfigError inside the class +end + +module ReVIEW + module Book + class Bibliography + def initialize(bibfile, config = nil) + @bibtex = BibTeX.parse(bibfile, filter: :latex) + @config = config + format('text') + rescue NameError + raise ReVIEW::ConfigError, 'not found bibtex libraries. disabled bibtex feature.' + end + + def format(format) + style = @config['bib-csl-style'] || 'acm-siggraph' + @citeproc = CiteProc::Processor.new(style: style, format: format, locale: 'en-US') + @citeproc.import(@bibtex.to_citeproc) + self + rescue NameError + raise ReVIEW::ConfigError, 'not found bibtex libraries. disabled bibtex feature.' + end + + def ref(keys) + authors = [] + keys.split(',').each do |key| + authors << { id: key.strip } + end + + cited = @citeproc.render(:citation, authors) + + # FIXME: need to apply CSL style + if cited.gsub(/[\[\]()\s,]/, '') == '' + refnums = [] + refnames = authors.map(&:values).flatten + @citeproc.bibliography.ids.each_with_index do |key, idx| + if refnames.include?(key) + refnums << (idx + 1) + end + end + cited = "[#{refnums.join(', ')}]" + end + + cited + end + + def list(key = nil) + b = @citeproc.bibliography + content = [] + + (0..(b.references.size - 1)).each do |i| + id = b.ids[i] + reference = b.references[i] + + if key.blank? || key == id + content << [b.prefix, reference, b.suffix].compact.join + end + end + [ + b.header, + content.join(b.connector), + b.footer + ].compact.join(b.connector) + end + end + end +end diff --git a/lib/review/book/bibliography_format.rb b/lib/review/book/bibliography_format.rb new file mode 100644 index 000000000..68d991969 --- /dev/null +++ b/lib/review/book/bibliography_format.rb @@ -0,0 +1,29 @@ +begin + require 'citeproc' + require 'citeproc/ruby' +rescue LoadError + # raise ReVIEW::ConfigError inside the class +end + +# https://github.com/inukshuk/citeproc-ruby +module CiteProc + module Ruby + module Formats + # class Html + # end + + class Latex + def bibliography(bibliography) + bibliography.header = '\\begin{description}' + bibliography.footer = '\\end{description}' + + bibliography.prefix = '\\item[] ' + bibliography.suffix = '' + + bibliography.connector = "\n" + bibliography + end + end + end + end +end diff --git a/lib/review/book/chapter.rb b/lib/review/book/chapter.rb index c9fc475fd..8f26315c0 100644 --- a/lib/review/book/chapter.rb +++ b/lib/review/book/chapter.rb @@ -10,11 +10,12 @@ require 'review/book/book_unit' require 'review/lineinput' require 'review/preprocessor' +require 'review/book/bibliography' module ReVIEW module Book class Chapter < BookUnit - attr_reader :number, :book + attr_reader :number, :book, :bibliography def self.mkchap(book, name, number = nil) name += book.ext if File.extname(name).empty? @@ -51,6 +52,19 @@ def initialize(book, number, name, path, io = nil) if !@content && @path && File.exist?(@path) @content = File.read(@path, mode: 'rt:BOM|utf-8') @number = nil if %w[nonum nodisp notoc].include?(find_first_header_option) + + # bibliography + if @book && @book.config + chapter_bibfile = File.join(File.dirname(@path), File.basename(@path, '.re') + '.bib') + if File.exist?(chapter_bibfile) + @bibliography = Book::Bibliography.new(chapter_bibfile, @book.config) + else + book_bibfile = File.join(@book.basedir, @book.config['bookname'] + '.bib') + if File.exist?(book_bibfile) + @bibliography = Book::Bibliography.new(book_bibfile, @book.config) + end + end + end end super() diff --git a/lib/review/builder.rb b/lib/review/builder.rb index c39ec9881..09402229e 100644 --- a/lib/review/builder.rb +++ b/lib/review/builder.rb @@ -436,6 +436,14 @@ def bibpaper(lines, id, caption) puts end + def inline_bibref(id) + @chapter.bibliography.format('text').ref(id) + end + + def bibliography + puts @chapter.bibliography.format('text').list + end + def inline_hd(id) m = /\A([^|]+)\|(.+)/.match(id) if m && m[1] diff --git a/lib/review/compiler.rb b/lib/review/compiler.rb index cc2843e45..6e0a02a7e 100644 --- a/lib/review/compiler.rb +++ b/lib/review/compiler.rb @@ -211,6 +211,7 @@ def inline_defined?(name) defsingle :firstlinenum, 1 defsingle :beginchild, 0 defsingle :endchild, 0 + defsingle :bibliography, 0 definline :chapref definline :chap @@ -274,6 +275,7 @@ def inline_defined?(name) definline :pageref definline :w definline :wb + definline :bibref private diff --git a/lib/review/htmlbuilder.rb b/lib/review/htmlbuilder.rb index d61749cf1..4f126df9a 100644 --- a/lib/review/htmlbuilder.rb +++ b/lib/review/htmlbuilder.rb @@ -1095,6 +1095,14 @@ def inline_bib(id) app_error "unknown bib: #{id}" end + def inline_bibref(id) + @chapter.bibliography.format('html').ref(id) + end + + def bibliography + puts @chapter.bibliography.format('html').list + end + def inline_hd_chap(chap, id) n = chap.headline_index.number(id) str = if n.present? && chap.number && over_secnolevel?(n) diff --git a/lib/review/latexbuilder.rb b/lib/review/latexbuilder.rb index 1a7a4d9e5..fa69c5833 100644 --- a/lib/review/latexbuilder.rb +++ b/lib/review/latexbuilder.rb @@ -1275,6 +1275,14 @@ def inline_bib(id) macro('reviewbibref', "[#{@chapter.bibpaper(id).number}]", bib_label(id)) end + def inline_bibref(id) + @chapter.bibliography.format('latex').ref(id) + end + + def bibliography + puts @chapter.bibliography.format('latex').list + end + def inline_hd_chap(chap, id) n = chap.headline_index.number(id) str = if n.present? && chap.number && over_secnolevel?(n) diff --git a/review.gemspec b/review.gemspec index f81be4450..a05e6e6cc 100644 --- a/review.gemspec +++ b/review.gemspec @@ -25,6 +25,9 @@ Gem::Specification.new do |gem| gem.add_dependency('rouge') gem.add_dependency('rubyzip') gem.add_dependency('tty-logger') + gem.add_development_dependency('bibtex-ruby') + gem.add_development_dependency('citeproc-ruby') + gem.add_development_dependency('csl-styles') gem.add_development_dependency('mini_magick') gem.add_development_dependency('pygments.rb') gem.add_development_dependency('rake') diff --git a/test/test_bibliography.rb b/test/test_bibliography.rb new file mode 100644 index 000000000..39ccee96a --- /dev/null +++ b/test/test_bibliography.rb @@ -0,0 +1,151 @@ +require 'book_test_helper' +require 'review/book/bibliography' + +class BibliographyTest < Test::Unit::TestCase + include BookTestHelper + def setup + mktmpbookdir do |_dir, book, _files| + @book = book + end + @bib = Book::Bibliography.new(bibfile, @book.config) + end + + def test_new + assert @bib + end + + def test_ref_text + @book.config['bib-csl-style'] = 'acm-siggraph' + assert_equal '[Thomas et al. 2009]', @bib.format('text').ref('pickaxe') + + @book.config['bib-csl-style'] = 'apa' + assert_equal '(Thomas et al., 2009)', @bib.format('text').ref('pickaxe') + + @book.config['bib-csl-style'] = 'ieee' + assert_equal '[1]', @bib.format('text').ref('pickaxe') + end + + def test_ref_text_multiple + @book.config['bib-csl-style'] = 'acm-siggraph' + assert_equal '[Thomas et al. 2009; van Fraassen 1989]', + @bib.format('text').ref('pickaxe,fraassen_1989') + + assert_equal '[Thomas et al. 2009; van Fraassen 1989]', + @bib.format('text').ref('pickaxe, fraassen_1989') + + @book.config['bib-csl-style'] = 'apa' + assert_equal '(Thomas et al., 2009; van Fraassen, 1989)', + @bib.format('text').ref('pickaxe,fraassen_1989') + + @book.config['bib-csl-style'] = 'ieee' + assert_equal '[1, 2]', + @bib.format('text').ref('pickaxe,fraassen_1989') + end + + def test_cite_html + @book.config['bib-csl-style'] = 'acm-siggraph' + assert_equal '[Thomas et al. 2009]', @bib.format('html').ref('pickaxe') + + @book.config['bib-csl-style'] = 'apa' + assert_equal '(Thomas et al., 2009)', @bib.format('html').ref('pickaxe') + end + + def test_ref_latex + @book.config['bib-csl-style'] = 'acm-siggraph' + assert_equal '[Thomas et al. 2009]', @bib.format('latex').ref('pickaxe') + + @book.config['bib-csl-style'] = 'apa' + assert_equal '(Thomas et al., 2009)', @bib.format('latex').ref('pickaxe') + end + + def test_list + @book.config['bib-csl-style'] = 'acm-siggraph' + expect = <<-EOS +Fraassen, B.C. van. 1989. Laws and Symmetry. Oxford University Press, Oxford. +Thomas, D. and Hunt, A. 2019. The Pragmatic Programmer: Your Journey to Mastery, 20th Anniversary Edition. The Pragmatic Bookshelf. +Thomas, D., Fowler, C., and Hunt, A. 2009. Programming Ruby 1.9: The Pragmatic Programmer’s Guide. The Pragmatic Bookshelf, Raleigh, North Carolina. +EOS + assert_equal expect.chomp, @bib.format('text').list + end + + def test_list_html + @book.config['bib-csl-style'] = 'acm-siggraph' + expect = <<-EOS +