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.
@@ -0,0 +1,208 @@
1
+ module CiteProc
2
+
3
+ # CitationItems consititue the main input elements to CiteProc's processing
4
+ # methods. In order to be processed correctly, an item must contain a valid
5
+ # id attribute used to retrieve the item's bibliographic data. Additionally,
6
+ # an item may include the following, optional, attributes:
7
+ #
8
+ # * locator: a string identifying a page number or other pinpoint location
9
+ # or range within the resource;
10
+ #
11
+ # * label: a label type, indicating whether the locator is to a page, a
12
+ # chapter, or other subdivision of the target resource. Valid labels are
13
+ # defined in CitationItem.labels
14
+ #
15
+ # * suppress-author: if true, author names will not be included in the
16
+ # citation output for this cite;
17
+ #
18
+ # * author-only: if true, only the author name will be included in the
19
+ # citation output for this cite -- this optional parameter provides a
20
+ # means for certain demanding styles that require the processor output
21
+ # to be divided between the main text and a footnote.
22
+ #
23
+ # * prefix: a string to print before this cite item;
24
+ #
25
+ # * suffix: a string to print after this cite item.
26
+ #
27
+ class CitationItem
28
+
29
+ include Attributes
30
+
31
+ @labels = [
32
+ :book, :chapter, :column, :figure, :folio, :issue, :line, :note, :opus,
33
+ :page, :paragraph, :part, :section, :'sub-verbo', :verse, :volume
34
+ ].freeze
35
+
36
+ class << self
37
+ attr_reader :labels
38
+ end
39
+
40
+ attr_predicates :id, :locator, :label, :'suppress-author',
41
+ :'author-only', :prefix, :suffix
42
+
43
+ # Added by processor
44
+ attr_predicates :sortkeys, :postion, :'first-reference-note-number',
45
+ :'near-note', :unsorted
46
+
47
+ attr_accessor :data
48
+
49
+ def initialize(attributes = nil)
50
+ merge(attributes)
51
+ end
52
+
53
+ def initialize_copy(other)
54
+ @attributes = other.attributes.deep_copy
55
+ end
56
+
57
+ def inspect
58
+ "#<CiteProc::CitationItem #{id.to_s}, #{locator.to_s} >"
59
+ end
60
+
61
+ end
62
+
63
+
64
+
65
+
66
+ class CitationData
67
+
68
+ extend Forwardable
69
+ include Enumerable
70
+
71
+ @defaults = {
72
+ :footnote => 0
73
+ }.freeze
74
+
75
+ @rb2cp = {
76
+ :id => 'citationID',
77
+ :items => 'citationItems',
78
+ :sorted_items => 'sortedItems',
79
+ :footnote => 'noteIndex',
80
+ :options => 'properties'
81
+ }
82
+
83
+ @cp2rb = @rb2cp.invert.freeze
84
+ @rb2cp.freeze
85
+
86
+ class << self
87
+ attr_reader :defaults, :cp2rb, :rb2cp
88
+ end
89
+
90
+ attr_accessor :id
91
+
92
+ attr_reader :items, :options, :sorted_items
93
+
94
+ alias properties options
95
+
96
+ def_delegators :@items, :length, :empty?, :[]
97
+
98
+ # Some delegators should return self
99
+ [:push, :<<, :unshift, :concat].each do |m|
100
+ define_method(m) do |*arguments|
101
+ names.send(m, *arguments)
102
+ self
103
+ end
104
+ end
105
+
106
+ def each
107
+ if block_given?
108
+ items.each(&Proc.new)
109
+ self
110
+ else
111
+ to_enum
112
+ end
113
+ end
114
+
115
+
116
+ def initialize(attributes = nil, options = {})
117
+ @options = CitationData.defaults.merge(options)
118
+ @items, @sorted_items = [], []
119
+ merge(attributes)
120
+ end
121
+
122
+ def initialize_copy(other)
123
+ @options = other.options.dup
124
+ @items = other.items.map(&:dup)
125
+ @sorted_items = other.items.map(&:dup)
126
+ @id = other.id.dup if other.processed?
127
+ end
128
+
129
+ def merge(other)
130
+ return self if other.nil?
131
+
132
+ case other
133
+ when String, /^\s*\{/
134
+ other = MulitJson.decode(other, :symbolize_keys => true)
135
+ when Hash
136
+ # do nothing
137
+ when Array
138
+ other = { :items => other }
139
+ when Attributes
140
+ other = other.to_hash
141
+ else
142
+ raise ParseError, "failed to merge citation data and #{other.inspect}"
143
+ end
144
+
145
+ other = convert_from_citeproc(other)
146
+
147
+ items.concat(Array(other.delete(:items)).map { |i| CitationItem.create!(i) })
148
+ sorted_items.concat(Array(other.delete(:sorted_items)))
149
+
150
+ properties = other.delete(:options)
151
+ options.merge!(convert_from_citeproc(Hash[properties])) unless properties.nil?
152
+
153
+ @id = other[:id] if other.has_key?(:id)
154
+
155
+ self
156
+ end
157
+
158
+ alias update merge
159
+
160
+ def processed?
161
+ !!id
162
+ end
163
+
164
+ def index
165
+ options[:footnote]
166
+ end
167
+
168
+ def footnote?
169
+ options[:footnote] > 0
170
+ end
171
+
172
+ def to_citeproc
173
+ cp = {}
174
+
175
+ cp[CitationData.rb2cp[:items]] = items.map(&:to_citeproc)
176
+ cp[CitationData.rb2cp[:options]] = { CitationData.rb2cp[:footnote] => index }
177
+
178
+ cp[CitationData.rb2cp[:id]] = id if processed?
179
+
180
+ cp
181
+ end
182
+
183
+ def to_json
184
+ MultiJson.encode(to_citeproc)
185
+ end
186
+
187
+ alias to_s to_json
188
+
189
+ def inspect
190
+ "#<CiteProc::CitationData items=[#{length}]>"
191
+ end
192
+
193
+ private
194
+
195
+ def convert_from_citeproc(hash)
196
+ hash = hash.symbolize_keys
197
+
198
+ CitationData.cp2rb.each do |cp, rb|
199
+ cp = cp.to_sym
200
+ hash[rb] = hash.delete(cp) if hash.has_key?(cp)
201
+ end
202
+
203
+ hash
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -2,8 +2,12 @@ unless Symbol.is_a?(Comparable)
2
2
  class Symbol
3
3
  include Comparable
4
4
 
5
+ def =~(pattern)
6
+ to_s =~ pattern
7
+ end
8
+
5
9
  def <=>(other)
6
- return nil unless other.respond_to?(:to_s)
10
+ return nil unless other.is_a?(Symbol)
7
11
  to_s <=> other.to_s
8
12
  end
9
13
  end
data/lib/citeproc/date.rb CHANGED
@@ -1,166 +1,229 @@
1
1
 
2
- require 'date'
3
-
4
- begin
5
- require 'chronic'
6
- rescue LoadError => e
7
- # warn 'failed to load chronic gem'
8
- end
9
-
10
2
  module CiteProc
11
3
 
12
- class Date < Variable
13
-
14
- include Attributes
15
-
16
- alias attributes value
17
- alias to_hash value
18
-
19
- @parser = Object.const_defined?(:Chronic) ? ::Chronic : ::Date
20
-
21
- class << self
22
-
23
- def parse(date_string)
24
- new(@parser.parse(date_string))
25
- rescue => e
26
- raise ArgumentError.new("failed to parse date from #{date_string.inspect}", e)
27
- end
28
-
29
- def today
30
- new(::Date.today)
31
- end
32
-
33
- alias now today
34
-
35
- end
36
-
37
-
38
- def initialize(attributes = ::Date.today)
39
- super
40
- end
41
-
42
- def initialize_copy(other)
43
- @value = other.value.deep_copy
44
- end
45
-
46
- def parse(attributes)
47
- case
48
- when attributes.is_a?(Date)
49
- @value = attributes.dup
50
-
51
- when attributes.is_a?(Numeric)
52
- @value = { :'date-parts' => [[attributes.to_i]] }
53
-
54
- when attributes.is_a?(Hash)
55
- attributes = attributes.symbolize_keys
56
- if attributes.has_key?(:raw)
57
- @value = Date.parse(attributes.delete(:raw)).value
58
- @value.merge!(attributes)
59
- else
60
- @value = attributes.deep_copy
61
- end
62
- to_i!
63
-
64
- when attributes.respond_to?(:strftime)
65
- @value = { :'date-parts' => [attributes.strftime('%Y-%m-%d').split(/-/).map(&:to_i)] }
66
-
67
- when attributes.is_a?(Array)
68
- @value = { :'date-parts' => attributes[0].is_a?(Array) ? attributes : [attributes] }
69
- to_i!
70
-
71
- when attributes.respond_to?(:to_s)
72
- @value = Date.parse(attributes.to_s).value
73
-
74
- else
75
- raise ArgumentError, "failed to parse date from #{attributes.inspect}"
76
- end
77
- end
78
-
79
- attr_predicates :circa, :season, :literal, :'date-parts'
80
-
81
- def date_parts
82
- @value[:'date-parts'] ||= [[]]
83
- end
84
-
85
- alias parts date_parts
86
- alias parts= date_parts=
87
-
88
- %w{ year month day }.each_with_index do |m,i|
89
- define_method(m) { parts[0][i] }
90
- define_method("#{m}=") { |v| parts[0][i] = v.to_i }
91
- end
92
-
93
- def start_date
94
- ::Date.new(*parts[0])
95
- end
96
-
97
- def start_date=(date)
98
- parts[0] = date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
99
- end
100
-
101
- def end_date=(date)
102
- parts[1] = date.nil? ? [0,0,0] : date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
103
- end
104
-
105
- # Returns a Ruby date object for this instance, or Range object if this
106
- # instance is closed range
107
- def to_ruby
108
- closed_range? ? start_date ... end_date : start_date
109
- end
110
-
111
- def end_date
112
- closed_range? ? ::Date.new(*parts[1]) : nil
113
- end
114
-
115
- def has_end_date?
116
- parts[1] && !parts[1].empty?
117
- end
118
-
119
- # Returns true if this date is a range
120
- alias range? has_end_date?
121
-
122
- def open_range?
123
- range? && parts[1].uniq == [0]
124
- end
125
-
126
- def closed_range?
127
- range? && !open_range?
128
- end
129
-
130
- alias uncertain? circa?
131
-
132
- # Marks the date as uncertain
133
- def uncertain!; @value[:'circa'] = true; end
134
-
135
- # Marks the date as a certain date
136
- def certain!; @value[:'circa'] = false; end
137
-
138
- def certain?; !uncertain?; end
139
-
140
- def numeric?; false; end
141
-
142
- def bc?; year && year < 0; end
143
- def ad?; !bc? && year < 1000; end
144
-
145
- def to_s
146
- literal? ? literal : @value.inspect
147
- end
148
-
149
- def sort_order
150
- "%04d%02d%02d-%04d%02d%02d" % ((parts[0] + [0,0,0])[0,3] + ((parts[1] || []) + [0,0,0])[0,3])
151
- end
152
-
153
- def <=>(other)
154
- return nil unless other.is_a?(Date)
155
- [year, sort_order] <=> [other.year, other.sort_order]
156
- end
157
-
158
- private
159
-
160
- def to_i!
161
- parts.each { |p| p.map!(&:to_i) }
162
- end
163
-
164
- end
165
-
4
+ class Date < Variable
5
+
6
+ include Attributes
7
+
8
+ alias attributes value
9
+ private :attributes, :value=
10
+
11
+
12
+ # Date parsers (must respond to :parse)
13
+ @parsers = []
14
+
15
+ require 'date'
16
+ @parsers << ::Date
17
+
18
+ begin
19
+ require 'chronic'
20
+ @parsers << Chronic
21
+ rescue LoadError
22
+ # warn 'failed to load chronic gem'
23
+ end
24
+
25
+ # Format string used for sorting dates
26
+ @sort_order = "%04d%02d%02d-%04d%02d%02d".freeze
27
+
28
+ class << self
29
+
30
+ attr_reader :parsers, :sort_order
31
+
32
+ # Parses the passed-in string with all available date parsers. Creates
33
+ # a new CiteProc Date from the first valid date returned by a parser;
34
+ # returns nil if no parser was able to parse the string successfully.
35
+ #
36
+ # For an equivalent method that raises an error on invalid input
37
+ # @see #parse!
38
+ def parse(date_string)
39
+ parse!(date_string)
40
+ rescue ParseError
41
+ nil
42
+ end
43
+
44
+ # Like #parse but raises a ParseError if the input failed to be parsed.
45
+ def parse!(date_string)
46
+ @parsers.each do |p|
47
+ d = p.parse(date_string) rescue nil
48
+ return new(d) unless d.nil?
49
+ end
50
+
51
+ # if we get here, all parsers failed
52
+ raise ParseError, "failed to parse #{date_string.inspect}"
53
+ end
54
+
55
+ def today
56
+ new(::Date.today)
57
+ end
58
+
59
+ alias now today
60
+
61
+ end
62
+
63
+ attr_predicates :circa, :season, :literal, :'date-parts'
64
+
65
+ # Make Date behave like a regular Ruby Date
66
+ def_delegators :to_ruby,
67
+ *::Date.instance_methods(false).reject { |m| m.to_s =~ /^to_s$|^inspect$|start$|^\W/ }
68
+
69
+
70
+ def initialize(value = ::Date.today)
71
+ super
72
+ end
73
+
74
+ def initialize_copy(other)
75
+ @value = other.value.deep_copy
76
+ end
77
+
78
+ def replace(value)
79
+ case
80
+ when value.is_a?(CiteProc::Date)
81
+ initialize_copy(value)
82
+
83
+ when value.is_a?(Numeric)
84
+ @value = { :'date-parts' => [[value.to_i]] }
85
+
86
+ when value.is_a?(Hash)
87
+ attributes = value.symbolize_keys
88
+
89
+ if attributes.has_key?(:raw)
90
+ @value = Date.parse(attributes.delete(:raw)).value
91
+ @value.merge!(attributes)
92
+ else
93
+ @value = attributes.deep_copy
94
+ end
95
+ to_i!
96
+
97
+ when value.respond_to?(:strftime)
98
+ @value = { :'date-parts' => [value.strftime('%Y-%m-%d').split(/-/).map(&:to_i)] }
99
+
100
+ when value.is_a?(Array)
101
+ @value = { :'date-parts' => value[0].is_a?(Array) ? value : [value] }
102
+ to_i!
103
+
104
+ when value.respond_to?(:to_s)
105
+ @value = Date.parse(value.to_s).value
106
+
107
+ else
108
+ raise TypeError, "failed to create date from #{value.inspect}"
109
+ end
110
+
111
+ self
112
+ end
113
+
114
+ # TODO replace the date parts by two proper dates or structs
115
+
116
+ def date_parts
117
+ @value[:'date-parts'] ||= [[]]
118
+ end
119
+
120
+ alias parts date_parts
121
+ alias parts= date_parts=
122
+
123
+ def empty?
124
+ parts.flatten.compact.empty?
125
+ end
126
+
127
+ %w{ year month day }.each_with_index do |m,i|
128
+ define_method(m) { parts[0][i] }
129
+ define_method("#{m}=") { |v| parts[0][i] = v.to_i }
130
+ end
131
+
132
+ def -@
133
+ d = dup
134
+ d.year = -1 * year
135
+ d
136
+ end
137
+
138
+ def start_date
139
+ ::Date.new(*parts[0])
140
+ end
141
+
142
+ def start_date=(date)
143
+ parts[0] = date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
144
+ end
145
+
146
+ def end_date=(date)
147
+ parts[1] = date.nil? ? [0,0,0] : date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
148
+ end
149
+
150
+ # Returns a Ruby date object for this instance, or Range object if this
151
+ # instance is closed range
152
+ def to_ruby
153
+ closed_range? ? start_date ... end_date : start_date
154
+ end
155
+
156
+ def end_date
157
+ closed_range? ? ::Date.new(*parts[1]) : nil
158
+ end
159
+
160
+ def has_end_date?
161
+ parts[1] && !parts[1].empty?
162
+ end
163
+
164
+ # Returns true if this date is a range
165
+ alias range? has_end_date?
166
+
167
+ def open_range?
168
+ range? && parts[1].uniq == [0]
169
+ end
170
+
171
+ alias open? open_range?
172
+
173
+ def closed_range?
174
+ range? && !open_range?
175
+ end
176
+
177
+ alias closed? closed_range?
178
+
179
+ alias uncertain? circa?
180
+
181
+ # Marks the date as uncertain
182
+ def uncertain!
183
+ @value[:circa] = true
184
+ end
185
+
186
+ # Marks the date as a certain date
187
+ def certain!
188
+ @value[:circa] = false
189
+ end
190
+
191
+ def certain?
192
+ !uncertain?
193
+ end
194
+
195
+ def numeric?
196
+ false
197
+ end
198
+
199
+ def bc?; year and year < 0; end
200
+ def ad?; not bc? and year < 1000; end
201
+
202
+ def to_citeproc
203
+ cp = @value.stringify_keys
204
+ cp.delete('date-parts') if empty?
205
+ cp
206
+ end
207
+
208
+ def to_s
209
+ literal? ? literal : to_ruby.to_s
210
+ end
211
+
212
+ def sort_order
213
+ Date.sort_order % ((parts[0] + [0,0,0])[0,3] + ((parts[1] || []) + [0,0,0])[0,3])
214
+ end
215
+
216
+ def <=>(other)
217
+ return nil unless other.is_a?(Date)
218
+ [year, sort_order] <=> [other.year, other.sort_order]
219
+ end
220
+
221
+ private
222
+
223
+ def to_i!
224
+ parts.each { |p| p.map!(&:to_i) }
225
+ end
226
+
227
+ end
228
+
166
229
  end