diff --git a/README.md b/README.md index 65a96cb720d6a5632d7111e4b1318bf2ecc2e1f7..9d14a6b3cb4407a6bfecfb4cbf1c36d7151ed1cf 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,23 @@ For example, this could be rendered as: +#### Multiple citation + +You can cite multiple items in a single citation by referencing all ids +of the items you wish to quote separated by spaces. For example, +`{% cite ruby microscope %}` would produce a cite tag like: + + (Flanagan & Matsumoto 2008; Shaughnessy 2013) + +#### Page numbers and locators + +If you would like to add page numbers to your citation, you can use the +`-l` or `--locator` option. For example, `{% cite ruby -l 23-5 %}` would +produce a citation like `(Matsumoto, 2008, pp. 23-5)`. + +When quoting multiple items (see above) you can add multiple locators after +the list of ids. For example, `{% cite ruby microscope -l 2 -l 24 & 32 %}`. + #### Displaying formatted references If you want to display the full formatted reference entry, you can use the @@ -446,7 +463,7 @@ License Jekyll-Scholar is distributed under the same license as Jekyll. -Copyright (c) 2011-2013 [Sylvester Keil](http://sylvester.keil.or.at/) +Copyright (c) 2011-2014 [Sylvester Keil](http://sylvester.keil.or.at/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal diff --git a/features/bibtex.feature b/features/bibtex.feature index b41be6dbcc6453e51fc09a8860a9c4781a4e09d4..913802740110d2253812dc553a8a114dff43edae 100644 --- a/features/bibtex.feature +++ b/features/bibtex.feature @@ -204,7 +204,7 @@ Feature: BibTeX """ --- --- - {% bibliography --style mla %} + {% bibliography --style modern-language-association %} """ When I run jekyll Then the _site directory should exist diff --git a/features/citation.feature b/features/citation.feature index 67f8b44ca5eb33b9505393582a809bccbc732256..d6901d05194490af010928be94ae8e48e318412e 100644 --- a/features/citation.feature +++ b/features/citation.feature @@ -111,3 +111,107 @@ Feature: Citations Then the _site directory should exist And the "_site/scholar.html" file should exist And I should see "#a-ruby" in "_site/scholar.html" + + @tags @cite + Scenario: Multiple Citations + Given I have a scholar configuration with: + | key | value | + | source | ./_bibliography | + | bibliography | my_references | + And I have a "_bibliography" directory + And I have a file "_bibliography/my_references.bib": + """ + @book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} + } + + @book{microscope, + title = {Ruby Under a Microscope}, + author = {Pat Shaughnessy}, + year = {2013}, + publisher = {No Starch Press} + } + """ + And I have a page "scholar.html": + """ + --- + --- + {% cite ruby microscope %} + """ + When I run jekyll + Then the _site directory should exist + And the "_site/scholar.html" file should exist + And I should see "Flanagan & Matsumoto, 2008; Shaughnessy, 2013" in "_site/scholar.html" + + @tags @cite @locator + Scenario: Multiple Citations with locators + Given I have a scholar configuration with: + | key | value | + | source | ./_bibliography | + | bibliography | my_references | + And I have a "_bibliography" directory + And I have a file "_bibliography/my_references.bib": + """ + @book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} + } + + @book{microscope, + title = {Ruby Under a Microscope}, + author = {Pat Shaughnessy}, + year = {2013}, + publisher = {No Starch Press} + } + """ + And I have a page "scholar.html": + """ + --- + --- + {% cite ruby microscope -l 2-3 --locator 23 & 42 %} + """ + When I run jekyll + Then the _site directory should exist + And the "_site/scholar.html" file should exist + And I should see "Matsumoto, 2008, pp. 2-3; Shaughnessy, 2013, pp. 23 & 42" in "_site/scholar.html" + + @tags @cite @citation_number + Scenario: Multiple citations using citation numbers + Given I have a scholar configuration with: + | key | value | + | source | ./_bibliography | + | bibliography | my_references | + | style | ieee | + And I have a "_bibliography" directory + And I have a file "_bibliography/my_references.bib": + """ + @book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} + } + + @book{microscope, + title = {Ruby Under a Microscope}, + author = {Pat Shaughnessy}, + year = {2013}, + publisher = {No Starch Press} + } + """ + And I have a page "scholar.html": + """ + --- + --- + {% cite ruby microscope %} + """ + When I run jekyll + Then the _site directory should exist + And the "_site/scholar.html" file should exist + And I should see "\[1\], \[2\]" in "_site/scholar.html" + diff --git a/features/cited_only.feature b/features/cited_only.feature new file mode 100644 index 0000000000000000000000000000000000000000..7faecbf44288f37bfbf14ab50b5a50344e7c43bd --- /dev/null +++ b/features/cited_only.feature @@ -0,0 +1,72 @@ +Feature: Cited-only Bibliographies + As a scholar who likes to blog + I want to cite references on my website + And generate bibliographies for the cited items + + Scenario: Cited-only references from a single bibliography + Given I have a scholar configuration with: + | key | value | + | source | ./_bibliography | + And I have a "_bibliography" directory + And I have a file "_bibliography/references.bib": + """ + @book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} + }, + @book{smalltalk, + title = {Smalltalk Best Practice Patterns}, + author = {Kent Beck}, + year = {1996}, + publisher = {Prentice Hall} + } + + """ + And I have a page "scholar.html": + """ + --- + --- + {% cite smalltalk %} + {% bibliography --cited %} + """ + When I run jekyll + Then the _site directory should exist + And the "_site/scholar.html" file should exist + And I should not see "The Ruby Programming Language" in "_site/scholar.html" + And I should see "Smalltalk Best Practice Patterns" in "_site/scholar.html" + + Scenario: No-cited items result in empty bibliography + Given I have a scholar configuration with: + | key | value | + | source | ./_bibliography | + And I have a "_bibliography" directory + And I have a file "_bibliography/references.bib": + """ + @book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} + }, + @book{smalltalk, + title = {Smalltalk Best Practice Patterns}, + author = {Kent Beck}, + year = {1996}, + publisher = {Prentice Hall} + } + + """ + And I have a page "scholar.html": + """ + --- + --- + {% bibliography --cited %} + """ + When I run jekyll + Then the _site directory should exist + And the "_site/scholar.html" file should exist + And I should not see "The Ruby Programming Language" in "_site/scholar.html" + And I should not see "Smalltalk Best Practice Patterns" in "_site/scholar.html" + diff --git a/features/step_definitions/jekyll_steps.rb b/features/step_definitions/jekyll_steps.rb index 868e4869e00cef0fd9c4eb8cc547152324b57f66..20fef1b57d55bfd8891ea34bb9f027ef3817753a 100644 --- a/features/step_definitions/jekyll_steps.rb +++ b/features/step_definitions/jekyll_steps.rb @@ -44,7 +44,6 @@ Then(/^the (.*) directory should exist$/) do |dir| end Then(/^I should see "(.*)" in "(.*)"$/) do |text, file| - puts File.open(file).readlines.join assert_match Regexp.new(text), File.open(file).readlines.join end diff --git a/features/step_definitions/scholar_steps.rb b/features/step_definitions/scholar_steps.rb index 5bd209e10a12ecc3427376cfd72f42940baad786..7cac358c95f184740c10f43a8814e9ce4b428302 100644 --- a/features/step_definitions/scholar_steps.rb +++ b/features/step_definitions/scholar_steps.rb @@ -1,15 +1,15 @@ -Given /^I have a "([^"]*)" directory/ do |dir| +Given(/^I have a "([^"]*)" directory/) do |dir| FileUtils.mkdir(dir) end -Given /^I have a (?:page|file) "([^"]*)":$/ do |file, string| +Given(/^I have a (?:page|file) "([^"]*)":$/) do |file, string| File.open(file, 'w') do |f| f.write(string) end end -Given /^I have a configuration file with "([^\"]*)" set to:$/ do |key, table| +Given(/^I have a configuration file with "([^\"]*)" set to:$/) do |key, table| File.open('_config.yml', 'w') do |f| f.write("#{key}:\n") table.hashes.each do |row| @@ -18,7 +18,7 @@ Given /^I have a configuration file with "([^\"]*)" set to:$/ do |key, table| end end -Given /^I have a scholar configuration with:$/ do |table| +Given(/^I have a scholar configuration with:$/) do |table| File.open('_config.yml', 'w') do |f| f.write("scholar:\n") table.hashes.each do |row| @@ -28,7 +28,7 @@ Given /^I have a scholar configuration with:$/ do |table| end -Then /^"(.*)" should come before "(.*)" in "(.*)"$/ do |p1, p2, file| +Then(/^"(.*)" should come before "(.*)" in "(.*)"$/) do |p1, p2, file| data = File.open(file).readlines.join('') m1 = data.match(p1) diff --git a/features/support/hooks.rb b/features/support/hooks.rb index d7cc7974af714d367904d12ba9f3f6fea8658819..0ff5a9518ed03ce3b786e3e02fd7c5324fd47df4 100644 --- a/features/support/hooks.rb +++ b/features/support/hooks.rb @@ -1,5 +1,6 @@ Before do - FileUtils.mkdir_p(TEST_DIR) unless File.exist?(TEST_DIR) + FileUtils.rm_rf(TEST_DIR) if File.exist?(TEST_DIR) + FileUtils.mkdir_p(TEST_DIR) Dir.chdir(TEST_DIR) end diff --git a/jekyll-scholar.gemspec b/jekyll-scholar.gemspec index 56fe940ea81ff5042cc8066fe08893a34d47c01f..449bf9935e89b967357263c69266e595f9261492 100644 --- a/jekyll-scholar.gemspec +++ b/jekyll-scholar.gemspec @@ -26,7 +26,8 @@ Gem::Specification.new do |s| s.rubyforge_project = s.name s.add_runtime_dependency('jekyll', '~> 1.0') - s.add_runtime_dependency('citeproc-ruby', '~> 0.0.6') + s.add_runtime_dependency('citeproc-ruby', '~> 1.0') + s.add_runtime_dependency('csl-styles', '~> 1.0') s.add_runtime_dependency('bibtex-ruby', '~> 3.0') s.files = `git ls-files`.split("\n") diff --git a/lib/jekyll/scholar.rb b/lib/jekyll/scholar.rb index 088740d3b8dcbbbbe9f85879aea715a8e924fcc7..63c6e1d0e35790d580ae1f7b7aeb3b35bd16ea6b 100644 --- a/lib/jekyll/scholar.rb +++ b/lib/jekyll/scholar.rb @@ -4,7 +4,8 @@ require 'jekyll' require 'optparse' require 'bibtex' -require 'citeproc' +require 'citeproc/ruby' +require 'csl/styles' require 'jekyll/scholar/version' require 'jekyll/scholar/defaults' diff --git a/lib/jekyll/scholar/converters/bibtex.rb b/lib/jekyll/scholar/converters/bibtex.rb index 803a35c05e0ff53a40bede2dd71a9e9279461315..b458ffb0c5e95fed01cb2ca14a6e8405b4f44717 100644 --- a/lib/jekyll/scholar/converters/bibtex.rb +++ b/lib/jekyll/scholar/converters/bibtex.rb @@ -1,45 +1,46 @@ module Jekyll class Scholar class BibTeXConverter < Converter + include Scholar::Utilities + safe true priority :highest - attr_reader :config - + attr_reader :config + @pattern = (/bib(tex)?$/i).freeze @extension = '.html'.freeze - - class << self - attr_reader :pattern, :extension - end - + + class << self + attr_reader :pattern, :extension + end + def initialize(config = {}) super @config['scholar'] = Scholar.defaults.merge(@config['scholar'] || {}) @markdown = Jekyll::Converters::Markdown.new(config) end - + def matches(extension) - extension =~ BibTeXConverter.pattern - end - + extension =~ BibTeXConverter.pattern + end + def output_ext(extension) - BibTeXConverter.extension - end - + BibTeXConverter.extension + end + def convert(content) - content = BibTeX.parse(content, :strict => true, :include => [:meta_content], :filter => [:latex]).map do |b| + content = BibTeX.parse(content, :strict => true, :include => [:meta_content], :filter => [:latex]).map do |b| if b.respond_to?(:to_citeproc) - CiteProc.process b.to_citeproc, :style => config['style'], - :locale => config['locale'], :format => 'html' + render_bibliography b else - b.is_a?(BibTeX::MetaContent) ? b.to_s : '' + b.is_a?(BibTeX::MetaContent) ? b.to_s : '' end end @markdown.convert(content.join("\n")) end - + end end -end \ No newline at end of file +end diff --git a/lib/jekyll/scholar/tags/bibliography.rb b/lib/jekyll/scholar/tags/bibliography.rb index 401bb5c439364cd6e49b84a566e9f156bd48bef1..dde5302ced4e95211415640e90a3bd525fbaa9a2 100644 --- a/lib/jekyll/scholar/tags/bibliography.rb +++ b/lib/jekyll/scholar/tags/bibliography.rb @@ -38,15 +38,6 @@ module Jekyll end end - private - - def citeproc - @citeproc ||= CiteProc::Processor.new do |p| - p.style = config['style'] - p.format = 'html' - p.locale = config['locale'] - end - end end end diff --git a/lib/jekyll/scholar/tags/cite.rb b/lib/jekyll/scholar/tags/cite.rb index 1f5bab4d39fee0ea772b59c9fc80fa06884d01e0..a210792249dd2092e918f4d926d6bb699a8f1c34 100644 --- a/lib/jekyll/scholar/tags/cite.rb +++ b/lib/jekyll/scholar/tags/cite.rb @@ -10,14 +10,14 @@ module Jekyll super @config = Scholar.defaults.dup - @key, arguments = arguments.strip.split(/\s+/, 2) + @keys, arguments = split_arguments(arguments) optparse(arguments) end def render(context) set_context_to context - cite key + cite keys end end @@ -25,4 +25,4 @@ module Jekyll end end -Liquid::Template.register_tag('cite', Jekyll::Scholar::CiteTag) \ No newline at end of file +Liquid::Template.register_tag('cite', Jekyll::Scholar::CiteTag) diff --git a/lib/jekyll/scholar/tags/cite_details.rb b/lib/jekyll/scholar/tags/cite_details.rb index 718a8481d1f72e7c0d636864c45a3a5a6a0d642b..2cc6e3735d05e65f522156c5d6d01ce6147b1b60 100644 --- a/lib/jekyll/scholar/tags/cite_details.rb +++ b/lib/jekyll/scholar/tags/cite_details.rb @@ -8,14 +8,16 @@ module Jekyll super @config = Scholar.defaults.dup - @key, arguments = arguments.strip.split(/\s+/, 2) + @keys, arguments = split_arguments arguments optparse(arguments) end def render(context) set_context_to context - cite_details key, text + keys.map { |key| + cite_details key, text + }.join("\n") end end diff --git a/lib/jekyll/scholar/tags/quote.rb b/lib/jekyll/scholar/tags/quote.rb index 376d558d17019505ec7caddfff9b72ad18e3b016..1f590840ef24179bcc71ec3bd14fffb4798e3568 100644 --- a/lib/jekyll/scholar/tags/quote.rb +++ b/lib/jekyll/scholar/tags/quote.rb @@ -3,32 +3,32 @@ module Jekyll class QuoteTag < Liquid::Block include Scholar::Utilities - + attr_reader :pages - + def initialize(tag_name, arguments, tokens) super - + @config = Scholar.defaults.dup - @key, arguments = arguments.strip.split(/\s+/, 2) + @keys, arguments = split_arguments arguments end def render(context) set_context_to context - + quote = super.strip.gsub(/\n\n/, '

').gsub(/\n/, '
') quote = content_tag :p, quote - - citation = cite key - + + citation = cite keys + quote << content_tag(:cite, citation) - + content_tag :blockquote, quote end - + end - + end end -Liquid::Template.register_tag('quote', Jekyll::Scholar::QuoteTag) \ No newline at end of file +Liquid::Template.register_tag('quote', Jekyll::Scholar::QuoteTag) diff --git a/lib/jekyll/scholar/tags/reference.rb b/lib/jekyll/scholar/tags/reference.rb index 5a325061a8893b90ff8615b14481fdc5f2e9cff6..d5e346179ff42094e6196b0097a7cbd8e1c9211e 100644 --- a/lib/jekyll/scholar/tags/reference.rb +++ b/lib/jekyll/scholar/tags/reference.rb @@ -8,16 +8,16 @@ module Jekyll super @config = Scholar.defaults.dup - @key, arguments = arguments.strip.split(/\s+/, 2) + @keys, arguments = split_arguments arguments optparse(arguments) end def render(context) set_context_to context - reference_tag bibliography[key] - rescue - "(#{key})" + keys.map { |key| + reference_tag bibliography[key] + }.join("\n") end end diff --git a/lib/jekyll/scholar/utilities.rb b/lib/jekyll/scholar/utilities.rb index 09d668b7dfb416263b81dd0db77b2ed56cb1a85a..2732bed00be564de64a446e99a32c60943ff16c9 100644 --- a/lib/jekyll/scholar/utilities.rb +++ b/lib/jekyll/scholar/utilities.rb @@ -1,13 +1,31 @@ module Jekyll class Scholar + # Load styles into static memory. + # They should be thread safe as long as they are + # treated as being read-only. + STYLES = Hash.new do |h, k| + h[k.to_s] = CSL::Style.load k + end + + # Utility methods used by several Scholar plugins. The methods in this # module may depend on the presence of #config, #bibtex_files, and # #site readers module Utilities attr_reader :config, :site, :query, - :context, :prefix, :key, :text + :context, :prefix, :keys, :text + + def split_arguments(arguments) + + tokens = arguments.strip.split(/\s+/) + + args = tokens.take_while { |a| !a.start_with?('-') } + opts = (tokens - args).join(' ') + + [args, opts] + end def optparse(arguments) return if arguments.nil? || arguments.empty? @@ -34,6 +52,10 @@ module Jekyll @text = text end + opts.on('-l', '--locator LOCATOR') do |locator| + locators << locator + end + opts.on('-s', '--style STYLE') do |style| @style = style end @@ -43,11 +65,15 @@ module Jekyll end end - argv = arguments.split(/(\B-[cfqptTs]|\B--(?:cited|file|query|prefix|text|style|template|))/) + argv = arguments.split(/(\B-[cfqptTsl]|\B--(?:cited|file|query|prefix|text|style|template|locator|))/) parser.parse argv.map(&:strip).reject(&:empty?) end + def locators + @locators ||= [] + end + def bibtex_files @bibtex_files ||= [config['bibliography']] end @@ -145,12 +171,11 @@ module Jekyll p end - def reference_tag(entry) + def reference_tag(entry, index = nil) return missing_reference unless entry entry = entry.convert(*bibtex_filters) unless bibtex_filters.empty? - reference = CiteProc.process entry.to_citeproc, - :style => style, :locale => config['locale'], :format => 'html' + reference = render_bibliography entry, index content_tag reference_tagname, reference, :id => [prefix, entry.key].compact.join('-') @@ -192,7 +217,7 @@ module Jekyll liquid_template.render({ 'entry' => liquidify(entry), - 'reference' => reference_tag(entry), + 'reference' => reference_tag(entry, index), 'key' => entry.key, 'type' => entry.type, 'link' => repository_link_for(entry), @@ -253,23 +278,55 @@ module Jekyll config['details_dir'] end - def cite(key) - context['cited'] ||= [] - context['cited'] << key + def renderer + @renderer ||= CiteProc::Ruby::Renderer.new :format => 'html', + :style => style, :locale => config['locale'] + end - if bibliography.key?(key) - entry = bibliography[key] - entry = entry.convert(*bibtex_filters) unless bibtex_filters.empty? + def render_citation(items) + renderer.render items.zip(locators).map { |entry, locator| + cited_keys << entry.key - citation = CiteProc.process entry.to_citeproc, :style => style, - :locale => config['locale'], :format => 'html', :mode => :citation + item = citation_item_for entry, citation_number + item.locator = locator - link_to "##{[prefix, entry.key].compact.join('-')}", citation.join - else - missing_reference + item + }, STYLES[style].citation + end + + def render_bibliography(entry, index = nil) + renderer.render citation_item_for(entry, index), + STYLES[style].bibliography + end + + def citation_item_for(entry, citation_number = nil) + CiteProc::CitationItem.new id: entry.id do |c| + c.data = CiteProc::Item.new entry.to_citeproc + c.data[:'citation-number'] = citation_number end - rescue - "(#{key})" + end + + def cited_keys + context['cited'] ||= [] + end + + def citation_number + number = context['citation_number'] || 1 + context['citation_number'] = number.succ + number + end + + def cite(keys) + items = keys.map do |key| + if bibliography.key?(key) + entry = bibliography[key] + entry = entry.convert(*bibtex_filters) unless bibtex_filters.empty? + else + return missing_reference + end + end + + link_to "##{[prefix, keys[0]].compact.join('-')}", render_citation(items) end def cite_details(key, text) diff --git a/lib/jekyll/scholar/version.rb b/lib/jekyll/scholar/version.rb index c4e65441347020118905a0923c71ed8d1f9f67d2..9edc5371bff13f77b8fa7adad036c69670e80137 100644 --- a/lib/jekyll/scholar/version.rb +++ b/lib/jekyll/scholar/version.rb @@ -1,5 +1,5 @@ module Jekyll class Scholar - VERSION = '2.0.0'.freeze + VERSION = '3.0.0'.freeze end end