csl 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.document +5 -0
  2. data/.gitignore +8 -0
  3. data/.gitmodules +6 -0
  4. data/.rspec +3 -0
  5. data/.simplecov +2 -0
  6. data/.travis.yml +13 -0
  7. data/.yardopts +2 -0
  8. data/AGPL +662 -0
  9. data/BSDL +29 -0
  10. data/Gemfile +24 -0
  11. data/Guardfile +14 -0
  12. data/README.md +39 -0
  13. data/Rakefile +45 -0
  14. data/csl.gemspec +36 -0
  15. data/cucumber.yml +1 -0
  16. data/features/locales/loading.feature +57 -0
  17. data/features/locales/ordinalize.feature +861 -0
  18. data/features/parser/info.feature +27 -0
  19. data/features/parser/localized_dates.feature +35 -0
  20. data/features/parser/terms.feature +28 -0
  21. data/features/step_definitions/locale_steps.rb +34 -0
  22. data/features/step_definitions/parser_steps.rb +28 -0
  23. data/features/step_definitions/style_steps.rb +16 -0
  24. data/features/style/loading.feature +53 -0
  25. data/features/support/env.rb +8 -0
  26. data/lib/csl.rb +54 -0
  27. data/lib/csl/compatibility.rb +19 -0
  28. data/lib/csl/errors.rb +15 -0
  29. data/lib/csl/extensions.rb +63 -0
  30. data/lib/csl/info.rb +40 -0
  31. data/lib/csl/loader.rb +78 -0
  32. data/lib/csl/locale.rb +393 -0
  33. data/lib/csl/locale/date.rb +48 -0
  34. data/lib/csl/locale/style_options.rb +10 -0
  35. data/lib/csl/locale/term.rb +185 -0
  36. data/lib/csl/node.rb +285 -0
  37. data/lib/csl/parser.rb +92 -0
  38. data/lib/csl/pretty_printer.rb +33 -0
  39. data/lib/csl/schema.rb +109 -0
  40. data/lib/csl/style.rb +53 -0
  41. data/lib/csl/style/bibliography.rb +15 -0
  42. data/lib/csl/style/citation.rb +17 -0
  43. data/lib/csl/style/conditional.rb +11 -0
  44. data/lib/csl/style/date.rb +16 -0
  45. data/lib/csl/style/group.rb +9 -0
  46. data/lib/csl/style/label.rb +14 -0
  47. data/lib/csl/style/layout.rb +10 -0
  48. data/lib/csl/style/macro.rb +9 -0
  49. data/lib/csl/style/names.rb +54 -0
  50. data/lib/csl/style/number.rb +33 -0
  51. data/lib/csl/style/sort.rb +21 -0
  52. data/lib/csl/style/text.rb +10 -0
  53. data/lib/csl/treelike.rb +442 -0
  54. data/lib/csl/version.rb +3 -0
  55. data/spec/csl/info_spec.rb +116 -0
  56. data/spec/csl/locale/date_spec.rb +63 -0
  57. data/spec/csl/locale/style_options_spec.rb +19 -0
  58. data/spec/csl/locale/term_spec.rb +96 -0
  59. data/spec/csl/locale_spec.rb +128 -0
  60. data/spec/csl/node_spec.rb +100 -0
  61. data/spec/csl/parser_spec.rb +92 -0
  62. data/spec/csl/schema_spec.rb +70 -0
  63. data/spec/csl/style/bibliography_spec.rb +7 -0
  64. data/spec/csl/style/citation_spec.rb +7 -0
  65. data/spec/csl/style/conditional_spec.rb +7 -0
  66. data/spec/csl/style/date_spec.rb +11 -0
  67. data/spec/csl/style/group_spec.rb +7 -0
  68. data/spec/csl/style/label_spec.rb +7 -0
  69. data/spec/csl/style/layout_spec.rb +7 -0
  70. data/spec/csl/style/macro_spec.rb +7 -0
  71. data/spec/csl/style/names_spec.rb +23 -0
  72. data/spec/csl/style/number_spec.rb +84 -0
  73. data/spec/csl/style/text_spec.rb +7 -0
  74. data/spec/csl/style_spec.rb +19 -0
  75. data/spec/csl/treelike_spec.rb +151 -0
  76. data/spec/spec_helper.rb +30 -0
  77. 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