csl 1.0.0.pre1

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.
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