citeproc 0.0.2 → 0.0.3
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.
- data/.rspec +1 -0
- data/README.md +2 -0
- data/auto.watchr +2 -0
- data/lib/citeproc.rb +16 -0
- data/lib/citeproc/assets.rb +66 -0
- data/lib/citeproc/attributes.rb +92 -16
- data/lib/citeproc/bibliography.rb +180 -0
- data/lib/citeproc/citation_data.rb +208 -0
- data/lib/citeproc/compatibility.rb +5 -1
- data/lib/citeproc/date.rb +225 -162
- data/lib/citeproc/engine.rb +6 -11
- data/lib/citeproc/errors.rb +5 -4
- data/lib/citeproc/extensions.rb +71 -3
- data/lib/citeproc/item.rb +113 -0
- data/lib/citeproc/names.rb +556 -0
- data/lib/citeproc/processor.rb +30 -4
- data/lib/citeproc/selector.rb +128 -0
- data/lib/citeproc/variable.rb +85 -45
- data/lib/citeproc/version.rb +1 -1
- data/spec/citeproc/assets_spec.rb +60 -0
- data/spec/citeproc/attributes_spec.rb +23 -10
- data/spec/citeproc/bibliography_spec.rb +72 -0
- data/spec/citeproc/citation_data_spec.rb +94 -0
- data/spec/citeproc/date_spec.rb +65 -9
- data/spec/citeproc/extensions_spec.rb +33 -0
- data/spec/citeproc/item_spec.rb +52 -0
- data/spec/citeproc/names_spec.rb +492 -0
- data/spec/citeproc/processor_spec.rb +39 -0
- data/spec/citeproc/selector_spec.rb +81 -0
- data/spec/citeproc/variable_spec.rb +9 -3
- metadata +133 -121
data/lib/citeproc/processor.rb
CHANGED
@@ -4,26 +4,52 @@ module CiteProc
|
|
4
4
|
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
-
@defaults
|
7
|
+
@defaults = {
|
8
8
|
:locale => 'en-US',
|
9
9
|
:style => 'chicago-author-date',
|
10
10
|
:engine => 'citeproc-js',
|
11
11
|
:format => 'html'
|
12
|
-
}
|
12
|
+
}.freeze
|
13
13
|
|
14
14
|
class << self
|
15
15
|
attr_reader :defaults
|
16
16
|
end
|
17
17
|
|
18
|
-
attr_reader :options, :engine, :items
|
18
|
+
attr_reader :options, :engine, :items, :style
|
19
19
|
|
20
|
-
def_delegators :@engine, :
|
20
|
+
def_delegators :@engine, :abbreviate, :abbreviations, :abbreviations=
|
21
21
|
|
22
22
|
def initialize(options = {})
|
23
23
|
@options = Processor.defaults.merge(options)
|
24
24
|
@engine = Engine.autodetect(@options).new(:processor => self)
|
25
|
+
@style = Style.load(@options[:style])
|
26
|
+
@locales = @options[:locale]
|
25
27
|
@items = {}
|
26
28
|
end
|
27
29
|
|
30
|
+
def style=(style)
|
31
|
+
@style = Style.load(style.to_s)
|
32
|
+
@engine.style = @style
|
33
|
+
@style
|
34
|
+
end
|
35
|
+
|
36
|
+
def locales=(locale)
|
37
|
+
@locales = { locale.to_sym => Locale.load(locale.to_s) }
|
38
|
+
@engine.locales = @locales
|
39
|
+
@locales
|
40
|
+
end
|
41
|
+
|
42
|
+
def process(*arguments)
|
43
|
+
@engine.process(CitationData(arguments.flatten(1)))
|
44
|
+
end
|
45
|
+
|
46
|
+
def append(*arguments)
|
47
|
+
@engine.append(CitationData(arguments.flatten(1)))
|
48
|
+
end
|
49
|
+
|
50
|
+
def bibliography(*arguments, &block)
|
51
|
+
@engine.bibliography(Selector.new(*arguments, &block))
|
52
|
+
end
|
53
|
+
|
28
54
|
end
|
29
55
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
|
2
|
+
module CiteProc
|
3
|
+
|
4
|
+
class Selector
|
5
|
+
|
6
|
+
@types = [:all, :any, :none].freeze
|
7
|
+
@matcher = Hash[*@types.zip([:all?, :any?, :none?]).flatten].freeze
|
8
|
+
|
9
|
+
@rb2cp = Hash[*(@types + [:skip]).zip(%w{
|
10
|
+
select include exclude quash
|
11
|
+
}).flatten]
|
12
|
+
|
13
|
+
@cp2rb = @rb2cp.invert.freeze
|
14
|
+
@rb2cp.freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_reader :types, :matcher, :rb2cp, :cp2rb
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :type, :conditions, :skip_conditions, :custom_matcher
|
21
|
+
|
22
|
+
def initialize(attributes = nil)
|
23
|
+
@conditions, @skip_conditions = {}, {}
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
@type = :ruby
|
27
|
+
|
28
|
+
else
|
29
|
+
unless attributes.nil? || attributes.empty?
|
30
|
+
attributes.symbolize_keys.each_pair do |key, conditions|
|
31
|
+
case key
|
32
|
+
when :all, :any, :none
|
33
|
+
@type = key
|
34
|
+
@conditions.merge!(conditions)
|
35
|
+
|
36
|
+
when :select, :include, :exclude
|
37
|
+
@type = Selector.cp2rb[key.to_s]
|
38
|
+
@conditions.merge!(conditions)
|
39
|
+
|
40
|
+
when :skip, :quash
|
41
|
+
@skip_conditions.merge!(conditions)
|
42
|
+
|
43
|
+
else
|
44
|
+
raise TypeError, "failed to create selector from #{key.inspect}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize_copy(other)
|
53
|
+
@type = other.type
|
54
|
+
@conditions = other.conditions.deep_copy
|
55
|
+
@skip_conditions = other.skip_conditions.deep_copy
|
56
|
+
@custom_matcher = other
|
57
|
+
end
|
58
|
+
|
59
|
+
def type=(type)
|
60
|
+
raise TypeError, "failed to set selector type to #{type.inspect}" unless
|
61
|
+
type.respond_to(:to_sym) && Selector.types.include?(type.to_sym)
|
62
|
+
|
63
|
+
@type = type.to_sym
|
64
|
+
end
|
65
|
+
|
66
|
+
def empty?
|
67
|
+
type.nil? && skip_conditions.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def custom_matcher?
|
71
|
+
defined?(@custom_matcher)
|
72
|
+
end
|
73
|
+
|
74
|
+
alias ruby? custom_matcher?
|
75
|
+
|
76
|
+
def matches?(item)
|
77
|
+
if custom_matcher?
|
78
|
+
custom_matcher.call(item)
|
79
|
+
else
|
80
|
+
conditions.each_pair.send(matcher) do |field, value|
|
81
|
+
item[field].to_s == value.to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def skip?(item)
|
87
|
+
if custom_matcher?
|
88
|
+
false # skips are ignored for custom matchers
|
89
|
+
else
|
90
|
+
skip_conditions.each_pair.all? do |field, value|
|
91
|
+
item[field].to_s == value.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_proc
|
97
|
+
Proc.new { |item| matches?(item) && !skip?(item) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_citeproc
|
101
|
+
return nil if empty? || custom_matcher?
|
102
|
+
|
103
|
+
cp = {}
|
104
|
+
cp[Selector.rb2cp[type]] = conditions.map do |field, value|
|
105
|
+
{ 'field' => field.to_s, 'value' => value.to_s }
|
106
|
+
end unless conditions.empty?
|
107
|
+
|
108
|
+
cp['quash'] = skip_conditions.map do |field, value|
|
109
|
+
{ 'field' => field.to_s, 'value' => value.to_s }
|
110
|
+
end unless skip_conditions.empty?
|
111
|
+
|
112
|
+
cp
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_json
|
116
|
+
MultiJson.encode(to_citeproc)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def matcher
|
122
|
+
Selector.matcher[type] || :all?
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/lib/citeproc/variable.rb
CHANGED
@@ -8,24 +8,33 @@ module CiteProc
|
|
8
8
|
class Variable
|
9
9
|
|
10
10
|
extend Forwardable
|
11
|
-
|
12
11
|
include Comparable
|
13
12
|
|
14
13
|
@fields = Hash.new { |h,k| h.fetch(k.to_sym, nil) }.merge({
|
15
|
-
:date => %w{
|
16
|
-
|
14
|
+
:date => %w{
|
15
|
+
accessed container event-date issued original-date submitted
|
16
|
+
},
|
17
|
+
|
17
18
|
:names => %w{
|
18
|
-
|
19
|
-
|
19
|
+
author collection-editor composer container-author recipient editor
|
20
|
+
editorial-director illustrator interviewer original-author translator
|
21
|
+
},
|
20
22
|
|
23
|
+
:number => %w{
|
24
|
+
chapter-number collection-number edition issue number number-of-pages
|
25
|
+
number-of-volumes volume
|
26
|
+
},
|
27
|
+
|
21
28
|
:text => %w{
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
abstract annote archive archive_location archive-place authority
|
30
|
+
call-number citation-label citation-number collection-title
|
31
|
+
container-title container-title-short dimensions DOI event event-place
|
32
|
+
first-reference-note-number genre ISBN ISSN jurisdiction keyword
|
33
|
+
locator medium note original-publisher original-publisher-place
|
34
|
+
original-title page page-first PMID PMCID publisher publisher-place
|
35
|
+
references section source status title title-short URL version
|
36
|
+
year-suffix
|
37
|
+
}
|
29
38
|
})
|
30
39
|
|
31
40
|
@fields.each_value { |v| v.map!(&:to_sym) }
|
@@ -36,21 +45,43 @@ module CiteProc
|
|
36
45
|
|
37
46
|
@fields[:name] = @fields[:names]
|
38
47
|
@fields[:dates] = @fields[:date]
|
48
|
+
@fields[:numbers] = @fields[:number]
|
39
49
|
|
40
50
|
@fields[:all] = @fields[:any] =
|
41
|
-
[:date,:names,:text].reduce([]) { |s,a| s.concat(@fields[a]) }.sort
|
51
|
+
[:date,:names,:text,:number].reduce([]) { |s,a| s.concat(@fields[a]) }.sort
|
42
52
|
|
43
53
|
@fields.freeze
|
44
54
|
|
55
|
+
@markup = /<[^>]*>/.freeze
|
56
|
+
|
57
|
+
|
45
58
|
class << self
|
46
|
-
|
59
|
+
|
60
|
+
attr_reader :fields, :types, :markup, :factories
|
47
61
|
|
48
|
-
|
62
|
+
def create(value, type = nil)
|
63
|
+
create!(value, type)
|
64
|
+
rescue
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def create!(value, type = nil)
|
69
|
+
factory = factories[type]
|
70
|
+
value.is_a?(factory) ? value : factory.new(value)
|
49
71
|
end
|
72
|
+
|
50
73
|
end
|
51
74
|
|
52
|
-
|
53
|
-
|
75
|
+
attr_accessor :value
|
76
|
+
|
77
|
+
def_delegators :@value, :to_s,
|
78
|
+
*::String.instance_methods(false).select {|m| m.to_s =~ /!$/ }
|
79
|
+
|
80
|
+
def_delegators :to_s, :=~, :===,
|
81
|
+
*::String.instance_methods(false).reject {|m| m.to_s =~ /^\W|!$|to_s/ }
|
82
|
+
|
83
|
+
def initialize(value = nil)
|
84
|
+
replace(value)
|
54
85
|
yield self if block_given?
|
55
86
|
end
|
56
87
|
|
@@ -58,35 +89,29 @@ module CiteProc
|
|
58
89
|
@value = other.value.dup
|
59
90
|
end
|
60
91
|
|
61
|
-
attr_accessor :value
|
62
92
|
|
63
|
-
|
64
|
-
|
65
|
-
|
93
|
+
# The replace method is typically called by the Variable's constructor. It
|
94
|
+
# will try to set the Variable to the passed in value and should accept
|
95
|
+
# a wide range of argument types; subclasses (especially Date and Names)
|
96
|
+
# override this method.
|
97
|
+
def replace(value)
|
98
|
+
raise TypeError, "failed to set value to #{value.inspect}" unless value.respond_to?(:to_s)
|
99
|
+
@value = value.to_s
|
100
|
+
self
|
101
|
+
end
|
66
102
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
send(writer, attributes[key]) if respond_to?(writer)
|
73
|
-
end
|
74
|
-
when attributes.respond_to?(:to_s)
|
75
|
-
@value = attributes.to_s.dup
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def type
|
80
|
-
@type ||= self.class.name.split(/::/)[-1].downcase.to_sym
|
81
|
-
end
|
82
|
-
|
103
|
+
def type
|
104
|
+
@type ||= self.class.name.split(/::/)[-1].downcase.to_sym
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns true if the Variable can be (safely) cast to a numeric value.
|
83
108
|
def numeric?
|
84
|
-
|
109
|
+
match(/\d/) ? to_i : false
|
85
110
|
end
|
86
111
|
|
87
|
-
#
|
112
|
+
# Returns (first) numeric data contained in the variable's value
|
88
113
|
def to_i
|
89
|
-
|
114
|
+
to_s =~ /(-?\d+)/ && $1.to_i || 0
|
90
115
|
end
|
91
116
|
|
92
117
|
def to_f
|
@@ -94,28 +119,43 @@ module CiteProc
|
|
94
119
|
end
|
95
120
|
|
96
121
|
def strip_markup
|
97
|
-
gsub(
|
122
|
+
gsub(Variable.markup, '')
|
98
123
|
end
|
99
124
|
|
100
125
|
def strip_markup!
|
101
|
-
gsub!(
|
126
|
+
gsub!(Variable.markup, '')
|
102
127
|
end
|
103
128
|
|
104
129
|
def <=>(other)
|
105
130
|
case
|
106
131
|
when other.respond_to?(:strip_markup)
|
107
132
|
strip_markup <=> other.strip_markup
|
133
|
+
|
108
134
|
when other && other.respond_to?(:to_s)
|
109
135
|
to_s <=> other.to_s
|
136
|
+
|
110
137
|
else
|
111
138
|
nil
|
112
139
|
end
|
113
140
|
end
|
114
141
|
|
142
|
+
alias to_citeproc to_s
|
143
|
+
|
115
144
|
def to_json
|
116
|
-
MultiJson.encode(
|
145
|
+
MultiJson.encode(to_citeproc)
|
117
146
|
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
"#<#{self.class.name}: #{to_s.inspect}>"
|
150
|
+
end
|
151
|
+
|
118
152
|
end
|
119
|
-
|
120
|
-
|
153
|
+
|
154
|
+
|
155
|
+
class Text < Variable
|
156
|
+
end
|
157
|
+
|
158
|
+
class Number < Variable
|
159
|
+
end
|
160
|
+
|
121
161
|
end
|
data/lib/citeproc/version.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module CiteProc
|
5
|
+
|
6
|
+
describe 'Assets' do
|
7
|
+
let(:file) { Tempfile.new('asset') }
|
8
|
+
let(:root) { File.dirname(file.path) }
|
9
|
+
let(:name) { File.basename(file.path) }
|
10
|
+
let(:extension) { File.extname(name) }
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
file.write("asset content\n")
|
14
|
+
file.close
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:all) { file.unlink }
|
18
|
+
|
19
|
+
describe 'Style' do
|
20
|
+
|
21
|
+
before(:all) do
|
22
|
+
@default_root = Style.root
|
23
|
+
@default_extension = Style.extension
|
24
|
+
Style.root = root
|
25
|
+
Style.extension = extension
|
26
|
+
end
|
27
|
+
|
28
|
+
after(:all) do
|
29
|
+
Style.root = @default_root
|
30
|
+
Style.extension = @default_extension
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '.load' do
|
34
|
+
|
35
|
+
it 'accepts an absolute file name' do
|
36
|
+
Style.load(file.path).to_s.should == "asset content\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'accepts a file name' do
|
40
|
+
Style.load(name).to_s.should == "asset content\n"
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'accepts a file name without extension' do
|
44
|
+
Style.load(name.sub(/#{extension}$/,'')).to_s.should == "asset content\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
it 'accepts a uri' do
|
49
|
+
pending
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns the given string if it is neither file nor uri' do
|
53
|
+
Style.load('foo bar!').to_s.should == 'foo bar!'
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|