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