csl 1.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +8 -0
- data/.gitmodules +6 -0
- data/.rspec +3 -0
- data/.simplecov +2 -0
- data/.travis.yml +13 -0
- data/.yardopts +2 -0
- data/AGPL +662 -0
- data/BSDL +29 -0
- data/Gemfile +24 -0
- data/Guardfile +14 -0
- data/README.md +39 -0
- data/Rakefile +45 -0
- data/csl.gemspec +36 -0
- data/cucumber.yml +1 -0
- data/features/locales/loading.feature +57 -0
- data/features/locales/ordinalize.feature +861 -0
- data/features/parser/info.feature +27 -0
- data/features/parser/localized_dates.feature +35 -0
- data/features/parser/terms.feature +28 -0
- data/features/step_definitions/locale_steps.rb +34 -0
- data/features/step_definitions/parser_steps.rb +28 -0
- data/features/step_definitions/style_steps.rb +16 -0
- data/features/style/loading.feature +53 -0
- data/features/support/env.rb +8 -0
- data/lib/csl.rb +54 -0
- data/lib/csl/compatibility.rb +19 -0
- data/lib/csl/errors.rb +15 -0
- data/lib/csl/extensions.rb +63 -0
- data/lib/csl/info.rb +40 -0
- data/lib/csl/loader.rb +78 -0
- data/lib/csl/locale.rb +393 -0
- data/lib/csl/locale/date.rb +48 -0
- data/lib/csl/locale/style_options.rb +10 -0
- data/lib/csl/locale/term.rb +185 -0
- data/lib/csl/node.rb +285 -0
- data/lib/csl/parser.rb +92 -0
- data/lib/csl/pretty_printer.rb +33 -0
- data/lib/csl/schema.rb +109 -0
- data/lib/csl/style.rb +53 -0
- data/lib/csl/style/bibliography.rb +15 -0
- data/lib/csl/style/citation.rb +17 -0
- data/lib/csl/style/conditional.rb +11 -0
- data/lib/csl/style/date.rb +16 -0
- data/lib/csl/style/group.rb +9 -0
- data/lib/csl/style/label.rb +14 -0
- data/lib/csl/style/layout.rb +10 -0
- data/lib/csl/style/macro.rb +9 -0
- data/lib/csl/style/names.rb +54 -0
- data/lib/csl/style/number.rb +33 -0
- data/lib/csl/style/sort.rb +21 -0
- data/lib/csl/style/text.rb +10 -0
- data/lib/csl/treelike.rb +442 -0
- data/lib/csl/version.rb +3 -0
- data/spec/csl/info_spec.rb +116 -0
- data/spec/csl/locale/date_spec.rb +63 -0
- data/spec/csl/locale/style_options_spec.rb +19 -0
- data/spec/csl/locale/term_spec.rb +96 -0
- data/spec/csl/locale_spec.rb +128 -0
- data/spec/csl/node_spec.rb +100 -0
- data/spec/csl/parser_spec.rb +92 -0
- data/spec/csl/schema_spec.rb +70 -0
- data/spec/csl/style/bibliography_spec.rb +7 -0
- data/spec/csl/style/citation_spec.rb +7 -0
- data/spec/csl/style/conditional_spec.rb +7 -0
- data/spec/csl/style/date_spec.rb +11 -0
- data/spec/csl/style/group_spec.rb +7 -0
- data/spec/csl/style/label_spec.rb +7 -0
- data/spec/csl/style/layout_spec.rb +7 -0
- data/spec/csl/style/macro_spec.rb +7 -0
- data/spec/csl/style/names_spec.rb +23 -0
- data/spec/csl/style/number_spec.rb +84 -0
- data/spec/csl/style/text_spec.rb +7 -0
- data/spec/csl/style_spec.rb +19 -0
- data/spec/csl/treelike_spec.rb +151 -0
- data/spec/spec_helper.rb +30 -0
- metadata +192 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
module CSL
|
2
|
+
class Locale
|
3
|
+
|
4
|
+
class Terms < Node
|
5
|
+
attr_children :term
|
6
|
+
|
7
|
+
alias terms term
|
8
|
+
def_delegators :terms, :size, :length
|
9
|
+
|
10
|
+
undef_method :[]=
|
11
|
+
|
12
|
+
def initialize(attributes = {})
|
13
|
+
super(attributes)
|
14
|
+
@registry, children[:term] = Hash.new { |h,k| h[k] = [] }, []
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
alias each each_child
|
20
|
+
|
21
|
+
def lookup(query)
|
22
|
+
terms = if Regexp === query
|
23
|
+
registry.keys.select { |t| t =~ query }.flatten(1)
|
24
|
+
else
|
25
|
+
registry[(Hash === query) ? query[:name] : query.to_s]
|
26
|
+
end
|
27
|
+
|
28
|
+
terms.detect { |t| t.matches?(query) }
|
29
|
+
end
|
30
|
+
|
31
|
+
alias [] lookup
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @!attribute [r] registry
|
36
|
+
# @return [Hash] a private registry to map term names to the respective
|
37
|
+
# term objects for quick term look-up
|
38
|
+
attr_reader :registry
|
39
|
+
|
40
|
+
def added_child(term)
|
41
|
+
raise ValidationError, "failed to register term #{term.inspect}: name attribute missing" unless
|
42
|
+
term.attribute?(:name)
|
43
|
+
|
44
|
+
registry[term[:name]].push(term)
|
45
|
+
term
|
46
|
+
end
|
47
|
+
|
48
|
+
def deleted_child(term)
|
49
|
+
registry[term[:name]].delete(term)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Term < Node
|
54
|
+
attr_struct :name, :form, :gender, :'gender-form'
|
55
|
+
attr_children :single, :multiple
|
56
|
+
|
57
|
+
attr_accessor :text
|
58
|
+
|
59
|
+
def_delegators :attributes, :hash, :eql?, :name, :form, :gender
|
60
|
+
|
61
|
+
def gendered?
|
62
|
+
!attributes.gender.blank?
|
63
|
+
end
|
64
|
+
|
65
|
+
def neutral?
|
66
|
+
!gendered?
|
67
|
+
end
|
68
|
+
|
69
|
+
def textnode?
|
70
|
+
!text.blank?
|
71
|
+
end
|
72
|
+
|
73
|
+
def singularize
|
74
|
+
return text if textnode?
|
75
|
+
children.single.to_s
|
76
|
+
end
|
77
|
+
|
78
|
+
alias singular singularize
|
79
|
+
|
80
|
+
def pluralize
|
81
|
+
return text if textnode?
|
82
|
+
children.multiple.to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
alias plural pluralize
|
86
|
+
|
87
|
+
# Tests whether or not the Term matches the passed-in query. Tests
|
88
|
+
# vary slightly depending on the the query's type: if a String or
|
89
|
+
# Regexp is passed-in, the return value will be true if the Term's
|
90
|
+
# name matches the query (disregarding all other attributes); if the
|
91
|
+
# query is a Hash, however, the return value will only be true if
|
92
|
+
# all passed-in attributes
|
93
|
+
#
|
94
|
+
# @param query [Hash,Regexp,#to_s] the query
|
95
|
+
# @raise [ArgumentError] if the term cannot be matched using query
|
96
|
+
#
|
97
|
+
# @return [Boolean] whether or not the query matches the term
|
98
|
+
def match?(query)
|
99
|
+
case
|
100
|
+
when query.is_a?(Hash)
|
101
|
+
query.symbolize_keys.values_at(*attributes.keys) == attributes.values_at(*attributes.keys)
|
102
|
+
when query.is_a?(Regexp)
|
103
|
+
query =~ name
|
104
|
+
when query.respond_to?(:to_s)
|
105
|
+
query.to_s == name
|
106
|
+
else
|
107
|
+
raise ArgumentError, "cannot match term to query: #{query.inspect}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
alias matches? match?
|
111
|
+
|
112
|
+
|
113
|
+
# @!method masculine?
|
114
|
+
# @return [Boolean] whether or not the term is masculine
|
115
|
+
|
116
|
+
# @!method masculine!
|
117
|
+
# @return [self,nil] the term with the gender attribute set to
|
118
|
+
# 'masculine', or nil if the term was already masculine
|
119
|
+
|
120
|
+
# @!method feminine?
|
121
|
+
# @return [Boolean] whether or not the term is feminie
|
122
|
+
|
123
|
+
# @!method feminine!
|
124
|
+
# @return [self,nil] the term with the gender attribute set to
|
125
|
+
# 'feminine', or nil if the term was already feminine
|
126
|
+
%w{ masculine feminine }.each do |name|
|
127
|
+
define_method("#{name}?") do
|
128
|
+
attributes.gender.to_s == name
|
129
|
+
end
|
130
|
+
|
131
|
+
define_method("#{name}!") do
|
132
|
+
return nil if attributes.gender.to_s == name
|
133
|
+
attributes.gender = name
|
134
|
+
self
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def tags
|
139
|
+
if textnode?
|
140
|
+
["<#{[nodename, *attribute_assignments].join(' ')}>", text, "</#{nodename}>"]
|
141
|
+
else
|
142
|
+
super
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param options [Hash,nil] an optional configuration hash
|
147
|
+
#
|
148
|
+
# @option options [:singular,:plural] :number (:singular) whether to
|
149
|
+
# return the term's singular or plural variant.
|
150
|
+
#
|
151
|
+
# @return [String] the term as a string
|
152
|
+
def to_s(options = nil)
|
153
|
+
if textnode?
|
154
|
+
text
|
155
|
+
else
|
156
|
+
if pluralize?(options)
|
157
|
+
pluralize
|
158
|
+
else
|
159
|
+
singularize
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class Single < TextNode; end
|
165
|
+
class Multiple < TextNode; end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def pluralize?(options)
|
170
|
+
return false if options.nil?
|
171
|
+
|
172
|
+
key = options[:number] || options['number']
|
173
|
+
|
174
|
+
if key.is_a?(Fixnum) || key.to_s =~ /^[+-]?\d+$/
|
175
|
+
key.to_i > 1
|
176
|
+
else
|
177
|
+
!key.blank? && key.to_s =~ /^plural/i
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
TextNode.types << Term
|
184
|
+
end
|
185
|
+
end
|
data/lib/csl/node.rb
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
module CSL
|
2
|
+
|
3
|
+
class Node
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
include Treelike
|
11
|
+
include PrettyPrinter
|
12
|
+
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def inherited(subclass)
|
17
|
+
types << subclass
|
18
|
+
subclass.nesting.each do |klass|
|
19
|
+
klass.types << subclass if klass < Node
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def types
|
24
|
+
@types ||= Set.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_attributes
|
28
|
+
@default_attributes ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def constantize(name)
|
32
|
+
types.detect do |t|
|
33
|
+
t.name.split(/::/)[-1].gsub(/([[:lower:]])([[:upper:]])/, '\1-\2').downcase == name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a new node with the passed in name and attributes.
|
38
|
+
def create(name, attributes = {}, &block)
|
39
|
+
klass = constantize(name)
|
40
|
+
|
41
|
+
unless klass.nil?
|
42
|
+
klass.new(attributes, &block)
|
43
|
+
else
|
44
|
+
node = new(attributes, &block)
|
45
|
+
node.nodename = name
|
46
|
+
node
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_attributes(attributes)
|
51
|
+
if const?(:Attributes)
|
52
|
+
const_get(:Attributes).new(default_attributes.merge(attributes))
|
53
|
+
else
|
54
|
+
default_attributes.merge(attributes)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def attr_defaults(attributes)
|
61
|
+
@default_attributes = attributes
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a new Struct for the passed-in attributes. Node instances
|
65
|
+
# will create an instance of this struct to manage their respective
|
66
|
+
# attributes.
|
67
|
+
#
|
68
|
+
# The new Struct will be available as Attributes in the current node's
|
69
|
+
# class scope.
|
70
|
+
def attr_struct(*attributes)
|
71
|
+
const_set(:Attributes, Struct.new(*attributes) {
|
72
|
+
|
73
|
+
# 1.8 Compatibility
|
74
|
+
@keys = attributes.map(&:to_sym).freeze
|
75
|
+
|
76
|
+
class << self
|
77
|
+
attr_reader :keys
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize(attrs = {})
|
81
|
+
super(*attrs.symbolize_keys.values_at(*keys))
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [<Symbol>] a list of symbols representing the names/keys
|
85
|
+
# of the attribute variables.
|
86
|
+
def keys
|
87
|
+
self.class.keys
|
88
|
+
end
|
89
|
+
|
90
|
+
def values
|
91
|
+
super.compact
|
92
|
+
end
|
93
|
+
|
94
|
+
# def to_a
|
95
|
+
# keys.zip(values_at(*keys)).reject { |k,v| v.nil? }
|
96
|
+
# end
|
97
|
+
|
98
|
+
# @return [Boolean] true if all the attribute values are nil;
|
99
|
+
# false otherwise.
|
100
|
+
def empty?
|
101
|
+
values.compact.empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
def fetch(key, default = nil)
|
105
|
+
value = keys.include?(key.to_sym) && send(key)
|
106
|
+
|
107
|
+
if block_given?
|
108
|
+
value || yield(key)
|
109
|
+
else
|
110
|
+
value || default
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Merges the current with the passed-in attributes.
|
115
|
+
#
|
116
|
+
# @param other [#each_pair] the other attributes
|
117
|
+
# @return [self]
|
118
|
+
def merge(other)
|
119
|
+
raise ArgumentError, "failed to merge #{other.class} into Attributes" unless
|
120
|
+
other.respond_to?(:each_pair)
|
121
|
+
|
122
|
+
other.each_pair do |part, value|
|
123
|
+
writer = "#{part}="
|
124
|
+
send(writer, value) if !value.nil? && respond_to?(writer)
|
125
|
+
end
|
126
|
+
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
# @overload values_at(selector, ... )
|
131
|
+
# Returns an array containing the attributes in self according
|
132
|
+
# to the given selector(s). The selectors may be either integer
|
133
|
+
# indices, ranges (functionality inherited from Struct) or
|
134
|
+
# symbols idenifying valid keys (similar to Hash#values_at).
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# attributes.values_at(:family, :nick) #=> ['Matsumoto', 'Matz']
|
138
|
+
#
|
139
|
+
# @see Struct#values_at
|
140
|
+
# @return [Array] the list of values
|
141
|
+
def values_at(*arguments)
|
142
|
+
super(*arguments.flatten.map { |k| k.is_a?(Symbol) ? keys.index(k) : k })
|
143
|
+
end
|
144
|
+
|
145
|
+
})
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
attr_reader :attributes
|
152
|
+
|
153
|
+
def_delegators :attributes, :[], :[]=, :values, :values_at, :length, :size
|
154
|
+
|
155
|
+
def initialize(attributes = {})
|
156
|
+
@attributes = self.class.create_attributes(attributes)
|
157
|
+
@children = self.class.create_children
|
158
|
+
|
159
|
+
yield self if block_given?
|
160
|
+
end
|
161
|
+
|
162
|
+
# Iterates through the Node's attributes
|
163
|
+
def each
|
164
|
+
if block_given?
|
165
|
+
attributes.each_pair(&Proc.new)
|
166
|
+
self
|
167
|
+
else
|
168
|
+
to_enum
|
169
|
+
end
|
170
|
+
end
|
171
|
+
alias each_pair each
|
172
|
+
|
173
|
+
# Returns true if the node contains an attribute with the passed-in name;
|
174
|
+
# false otherwise.
|
175
|
+
def attribute?(name)
|
176
|
+
attributes.fetch(name, false)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns true if the node contains any attributes (ignores nil values);
|
180
|
+
# false otherwise.
|
181
|
+
def has_attributes?
|
182
|
+
!attributes.empty?
|
183
|
+
end
|
184
|
+
|
185
|
+
def textnode?
|
186
|
+
false
|
187
|
+
end
|
188
|
+
alias has_text? textnode?
|
189
|
+
|
190
|
+
def <=>(other)
|
191
|
+
[nodename, attributes, children] <=> [other.nodename, other.attributes, other.children]
|
192
|
+
rescue
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns the node' XML tags (including attribute assignments) as an
|
197
|
+
# array of strings.
|
198
|
+
def tags
|
199
|
+
if has_children?
|
200
|
+
tags = []
|
201
|
+
tags << "<#{[nodename, *attribute_assignments].join(' ')}>"
|
202
|
+
|
203
|
+
tags << children.map do |node|
|
204
|
+
node.respond_to?(:tags) ? node.tags : [node.to_s]
|
205
|
+
end
|
206
|
+
|
207
|
+
tags << "</#{nodename}>"
|
208
|
+
tags
|
209
|
+
else
|
210
|
+
["<#{[nodename, *attribute_assignments].join(' ')}/>"]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def inspect
|
215
|
+
"#<#{[self.class.name, *attribute_assignments].join(' ')} children=[#{children.length}]>"
|
216
|
+
end
|
217
|
+
|
218
|
+
alias to_s pretty_print
|
219
|
+
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def attribute_assignments
|
224
|
+
each_pair.map { |name, value|
|
225
|
+
value.nil? ? nil: [name, value.to_s.inspect].join('=')
|
226
|
+
}.compact
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
class TextNode < Node
|
233
|
+
|
234
|
+
has_no_children
|
235
|
+
|
236
|
+
class << self
|
237
|
+
undef_method :attr_children
|
238
|
+
end
|
239
|
+
|
240
|
+
attr_accessor :text
|
241
|
+
alias to_s text
|
242
|
+
|
243
|
+
# TextNodes quack like a string.
|
244
|
+
# def_delegators :to_s, *String.instance_methods(false).reject do |m|
|
245
|
+
# m.to_s =~ /^\W|!$|(?:^(?:hash|eql?|to_s|length|size|inspect)$)/
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# String.instance_methods(false).select { |m| m.to_s =~ /!$/ }.each do |m|
|
249
|
+
# define_method(m) do
|
250
|
+
# content.send(m) if content.respond_to?(m)
|
251
|
+
# end
|
252
|
+
# end
|
253
|
+
|
254
|
+
def initialize(argument = '')
|
255
|
+
case
|
256
|
+
when argument.is_a?(Hash)
|
257
|
+
super
|
258
|
+
when argument.respond_to?(:to_s)
|
259
|
+
super({})
|
260
|
+
@text = argument.to_s
|
261
|
+
yield self if block_given?
|
262
|
+
else
|
263
|
+
raise ArgumentError, "failed to create text node from #{argument.inspect}"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def textnode?
|
268
|
+
true
|
269
|
+
end
|
270
|
+
|
271
|
+
def tags
|
272
|
+
tags = []
|
273
|
+
tags << "<#{attribute_assignments.unshift(nodename).join(' ')}>"
|
274
|
+
tags << text
|
275
|
+
tags << "</#{nodename}>"
|
276
|
+
tags
|
277
|
+
end
|
278
|
+
|
279
|
+
def inspect
|
280
|
+
"#<#{[self.class.name, text.inspect, *attribute_assignments].join(' ')}>"
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|