plain_record 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,157 @@
1
+ =begin
2
+ Extention to get field value depend on user locale.
3
+
4
+ Copyright (C) 2012 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ =end
20
+
21
+ module PlainRecord::Extra
22
+ # Extention to get field value depend on user locale.
23
+ #
24
+ # You can’t use this filter for texts, because you need to set hash of
25
+ # language code to translated string. For example in YAML fiels:
26
+ #
27
+ # title:
28
+ # en: Title
29
+ # ru: Заголовок
30
+ #
31
+ # Then just set filter to `virtual` or `field`.
32
+ #
33
+ # class Post
34
+ # include PlainRecord::Resource
35
+ # include PlainRecord::Extra::I18n
36
+ #
37
+ # field :title, i18n
38
+ # end
39
+ #
40
+ # By default, this filter will use current locale from Rails I18n or R18n,
41
+ # if they are defined. If you need to take locale from another space, just
42
+ # redefine `locale` model method.
43
+ #
44
+ # class Post
45
+ # def locale
46
+ # ENV['locale']
47
+ # end
48
+ # end
49
+ module I18n
50
+ class << self
51
+ def included(base)
52
+ base.send :extend, Model
53
+ end
54
+ end
55
+
56
+ # Return default locale. By default it look in R18n or Rails I18n.
57
+ # Redefine it if you need another logic.
58
+ def locale
59
+ if defined? ::R18n::I18n
60
+ ::R18n.get
61
+ elsif defined? ::I18n
62
+ ::I18n.locale
63
+ else
64
+ raise "Can't find R18n or I18n. Redefine `locale` method."
65
+ end
66
+ end
67
+
68
+ # Return locale code depend on autodetected I18n library.
69
+ def locale_code
70
+ code = locale
71
+ if defined? R18n::I18n and code.is_a? R18n::I18n
72
+ code.locale.code
73
+ elsif code.is_a? Symbol
74
+ code.to_s
75
+ else
76
+ code
77
+ end
78
+ end
79
+
80
+ # Return subvalue of `hash` depend on user locale from `locale` method.
81
+ #
82
+ # If R18n or Rails I18n is loaded it will use them logic to translate.
83
+ def get_translation(name, hash, *params)
84
+ return hash unless hash.is_a? Hash
85
+ path = "#{self.class.name}##{name}"
86
+
87
+ if defined? R18n::I18n and locale.is_a? R18n::I18n
88
+ r18n = locale
89
+ r18n.locales.each do |lang|
90
+ code = lang.code
91
+ next unless hash.has_key? code
92
+
93
+ result = hash[code]
94
+ type = self.class.fields_i18n_types[name.to_sym]
95
+
96
+ if type
97
+ return r18n.filter_list.
98
+ process(:all, type, result, lang, path, params)
99
+ elsif result.is_a? String
100
+ result = ::R18n::TranslatedString.new(result, lang, path)
101
+ return r18n.filter_list.process_string(:all, result, path, params)
102
+ else
103
+ return result
104
+ end
105
+ end
106
+
107
+ ::R18n::Untranslated.new("#{self.class.name}#", name,
108
+ locale.locale, locale.filter_list)
109
+
110
+ elsif defined? ::I18n
111
+ hash[locale.to_s]
112
+
113
+ else
114
+ hash[locale]
115
+ end
116
+ end
117
+
118
+ module Model
119
+ # R18n type for fields.
120
+ attr_accessor :fields_i18n_types
121
+
122
+ private
123
+
124
+ # Filter to return value depend on model locale.
125
+ #
126
+ # If you use R18n, you can set `type` to specify filters.
127
+ #
128
+ # field comment_count, i18n('pl')
129
+ def i18n(r18n_type = nil)
130
+ proc do |model, field, type|
131
+ if :text == type
132
+ raise ArgumentError, "You can't set i18n filter for text"
133
+ end
134
+
135
+ model.fields_i18n_types ||= { }
136
+ model.fields_i18n_types[field] = r18n_type if r18n_type
137
+
138
+ model.send :alias_method, "untraslated_#{field}", field
139
+ model.send :alias_method, "untraslated_#{field}=", "#{field}="
140
+
141
+ model.add_accessors <<-EOS, __FILE__, __LINE__
142
+ def #{field}(*params)
143
+ get_translation("#{field}", super, *params)
144
+ end
145
+ def #{field}=(value)
146
+ if untraslated_#{field}
147
+ untraslated_#{field}[locale_code] = value
148
+ else
149
+ super({ locale_code => value })
150
+ end
151
+ end
152
+ EOS
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -1,7 +1,8 @@
1
1
  =begin
2
- Extention to get property from entry file path.
2
+ Extention to get field from entry file path.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -18,18 +19,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
  =end
19
20
 
20
21
  module PlainRecord
21
- # Extention to get properties from enrty file path. For example, your blog
22
+ # Extention to get fields from enrty file path. For example, your blog
22
23
  # post may stored in <tt>_name_/post.md</tt>, and post model will have +name+
23
- # property. Also if you set name property to Model#first or Model#all method,
24
+ # field. Also if you set name field to Model#first or Model#all method,
24
25
  # they will load entry directly only by it file.
25
26
  #
26
- # To define filepath property:
27
+ # To define filepath field:
27
28
  # 1. Use <tt>*</tt> or <tt>**</tt> pattern in model path in +enrty_in+ or
28
29
  # +list_in+.
29
- # 2. In +virtual+ method use <tt>in_filepath(i)</tt> definer after name with
30
+ # 2. In +virtual+ method use <tt>in_filepath(i)</tt> filter after name with
30
31
  # <tt>*</tt> or <tt>**</tt> number (start from 1).
31
32
  #
32
- # Define filepath property only after +entry_in+ or +list_in+ call.
33
+ # Define filepath field only after +entry_in+ or +list_in+ call.
33
34
  #
34
35
  # class Post
35
36
  # include PlainRecord::Resource
@@ -48,21 +49,31 @@ module PlainRecord
48
49
  #
49
50
  # bests = Post.all(category: 'best') # Look up only in best/ dir
50
51
  module Filepath
51
- attr_accessor :filepath_properties
52
+ attr_accessor :filepath_fields
52
53
  attr_accessor :filepath_regexp
53
54
 
54
55
  private
55
56
 
56
- # Return definer for filepath property for +number+ <tt>*</tt> or
57
+ # Return filter for filepath field for +number+ <tt>*</tt> or
57
58
  # <tt>**</tt> pattern in path.
58
59
  def in_filepath(number)
59
- proc do |property, caller|
60
- if :virtual != caller
61
- raise ArgumentError, "You must create filepath property #{property}" +
60
+ proc do |model, field, type|
61
+ if :virtual != type
62
+ raise ArgumentError, "You must create filepath field #{field}" +
62
63
  ' virtual creator'
63
64
  end
64
- Filepath.define_property(self, property, number)
65
- nil
65
+
66
+ Filepath.install(model) unless model.filepath_fields
67
+ model.filepath_fields[number] = field
68
+
69
+ model.add_accessors <<-EOS, __FILE__, __LINE__
70
+ def #{field}
71
+ @filepath_data[:#{field}]
72
+ end
73
+ def #{field}=(value)
74
+ @filepath_data[:#{field}] = value
75
+ end
76
+ EOS
66
77
  end
67
78
  end
68
79
 
@@ -70,7 +81,7 @@ module PlainRecord
70
81
  # Define class variables and events in +klass+. It should be call once on
71
82
  # same class after +entry_in+ or +list_in+ call.
72
83
  def install(klass)
73
- klass.filepath_properties = { }
84
+ klass.filepath_fields = { }
74
85
 
75
86
  path = Regexp.escape(klass.path).gsub(/\\\*\\\*(\/|$)/, '(.*)').
76
87
  gsub('\\*', '([^/]+)')
@@ -84,12 +95,12 @@ module PlainRecord
84
95
  if entry.path
85
96
  data = klass.filepath_regexp.match(entry.path)
86
97
  entry.filepath_data = { }
87
- klass.filepath_properties.each_pair do |number, name|
98
+ klass.filepath_fields.each_pair do |number, name|
88
99
  entry.filepath_data[name] = data[number]
89
100
  end
90
101
  else
91
102
  entry.filepath_data = { }
92
- klass.filepath_properties.each_value do |name|
103
+ klass.filepath_fields.each_value do |name|
93
104
  entry.filepath_data[name] = entry.data[name]
94
105
  entry.data.delete(name)
95
106
  end
@@ -101,9 +112,9 @@ module PlainRecord
101
112
  i = 0
102
113
  path.gsub /(\*\*(\/|$)|\*)/ do |pattern|
103
114
  i += 1
104
- property = klass.filepath_properties[i]
105
- unless matchers[property].is_a? Regexp or matchers[property].nil?
106
- matchers[property]
115
+ field = klass.filepath_fields[i]
116
+ unless matchers[field].is_a? Regexp or matchers[field].nil?
117
+ matchers[field]
107
118
  else
108
119
  pattern
109
120
  end
@@ -117,25 +128,6 @@ module PlainRecord
117
128
  end
118
129
  end
119
130
  end
120
-
121
- # Define in +klass+ filepath property with +name+ for +number+ <tt>*</tt>
122
- # or <tt>**</tt> pattern in path.
123
- def define_property(klass, name, number)
124
- unless klass.filepath_properties
125
- install(klass)
126
- end
127
-
128
- klass.filepath_properties[number] = name
129
-
130
- klass.class_eval <<-EOS, __FILE__, __LINE__
131
- def #{name}
132
- @filepath_data[:#{name}]
133
- end
134
- def #{name}=(value)
135
- @filepath_data[:#{name}] = value
136
- end
137
- EOS
138
- end
139
131
  end
140
132
  end
141
133
  end
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Class with static methods for entry_in model class.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Class with static methods for list_in model class.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Class with static methods for model class.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -28,16 +29,18 @@ module PlainRecord
28
29
  autoload :List, (dir + 'list').to_s
29
30
 
30
31
  include PlainRecord::Callbacks
32
+ include PlainRecord::Default
31
33
  include PlainRecord::Filepath
32
34
  include PlainRecord::Associations
35
+ include PlainRecord::Type
33
36
 
34
- # YAML properties names.
35
- attr_accessor :properties
37
+ # YAML fields names.
38
+ attr_accessor :fields
36
39
 
37
- # Name of special properties with big text.
40
+ # Name of special fields with big text.
38
41
  attr_accessor :texts
39
42
 
40
- # Properties names with dynamic value.
43
+ # Fields names with dynamic value.
41
44
  attr_accessor :virtuals
42
45
 
43
46
  # Storage type: +:entry+ or +:list+.
@@ -46,11 +49,15 @@ module PlainRecord
46
49
  # Content of already loaded files.
47
50
  attr_accessor :loaded
48
51
 
49
- def self.extended(base) #:nodoc:
50
- base.properties = []
51
- base.virtuals = []
52
- base.texts = []
53
- base.loaded = { }
52
+ # Named modules, created by +add_accessors+.
53
+ attr_accessor :accessors_modules
54
+
55
+ def self.extended(base)
56
+ base.fields = []
57
+ base.virtuals = []
58
+ base.texts = []
59
+ base.loaded = { }
60
+ base.accessors_modules = { }
54
61
  end
55
62
 
56
63
  # Load and return all entries in +file+.
@@ -87,7 +94,7 @@ module PlainRecord
87
94
  # Return all entries, which is match for +matchers+ and return true on
88
95
  # +block+.
89
96
  #
90
- # Matchers is a Hash with property name in key and String or Regexp for
97
+ # Matchers is a Hash with field name in key and String or Regexp for
91
98
  # match in value.
92
99
  #
93
100
  # Post.all(title: 'Post title')
@@ -103,7 +110,7 @@ module PlainRecord
103
110
  # Return first entry, which is match for +matchers+ and return true on
104
111
  # +block+.
105
112
  #
106
- # Matchers is a Hash with property name in key and String or Regexp for
113
+ # Matchers is a Hash with field name in key and String or Regexp for
107
114
  # match in value.
108
115
  #
109
116
  # Post.first(title: 'Post title')
@@ -137,6 +144,50 @@ module PlainRecord
137
144
  end
138
145
  end
139
146
 
147
+ # Create new anonymous module and include in model.
148
+ #
149
+ # You can set +name+ and it will old module, if it was created with same
150
+ # name.
151
+ #
152
+ # It is helper to create model fields accessors and filters for it with
153
+ # +super+ support.
154
+ #
155
+ # add_accessors <<-EOS, __FILE__, __LINE__
156
+ # def #{name}
157
+ # @data['#{name}']
158
+ # end
159
+ # EOS
160
+ def add_accessors(name = nil, file = nil, line = nil, code = nil)
161
+ if name.is_a? String
162
+ code = line
163
+ line = file
164
+ file = name
165
+ name = nil
166
+ end
167
+
168
+ if file and code.nil?
169
+ code = file
170
+ file = line = nil
171
+ end
172
+
173
+ if name and @accessors_modules.has_key? name
174
+ mod = @accessors_modules[name]
175
+ else
176
+ mod = Module.new
177
+ if name
178
+ @accessors_modules[name] = mod
179
+ end
180
+ include mod
181
+ end
182
+
183
+ if file and code
184
+ mod.module_eval(file, line, code)
185
+ elsif code
186
+ mod.module_eval(code)
187
+ end
188
+ mod
189
+ end
190
+
140
191
  private
141
192
 
142
193
  # Return all model entries, which is may be match for +matchers+.
@@ -200,13 +251,12 @@ module PlainRecord
200
251
  self.extend PlainRecord::Model::List
201
252
  end
202
253
 
203
- # Add virtual property with some +name+ to model. It value willn’t be in
254
+ # Add virtual field with some +name+ to model. It value willn’t be in
204
255
  # file and will be calculated dynamically.
205
256
  #
206
- # You _must_ provide your own define logic by +definers+. Definer Proc
207
- # will be call with property name in first argument and may return
208
- # +:accessor+, +:writer+ or +:reader+ this method create standard methods
209
- # to access to property.
257
+ # You _must_ provide your own define logic by +filters+. Filter Proc
258
+ # will be call with models class as first argument, field name as second and
259
+ # field type as second.
210
260
  #
211
261
  # class Post
212
262
  # include PlainRecord::Resource
@@ -215,61 +265,53 @@ module PlainRecord
215
265
  #
216
266
  # virtual :name, in_filepath(1)
217
267
  # end
218
- def virtual(name, *definers)
268
+ def virtual(name, *filters)
219
269
  @virtuals ||= []
220
270
  @virtuals << name
221
271
 
222
- accessors = call_definers(definers, name, :virtual)
223
-
224
- if accessors[:reader] or accessors[:writer]
272
+ if filters.length.zero?
225
273
  raise ArgumentError, 'You must provide you own accessors for virtual ' +
226
- "property #{name}"
274
+ "field #{name} by any filter"
227
275
  end
276
+
277
+ field_filters(filters, name, :virtual)
228
278
  end
229
279
 
230
- # Add property with some +name+ to model. It will be stored as YAML.
280
+ # Add field with some +name+ to model. It will be stored as YAML.
231
281
  #
232
- # You can provide your own define logic by +definers+. Definer Proc
233
- # will be call with property name in first argument and may return
234
- # +:accessor+, +:writer+ or +:reader+ this method create standard methods
235
- # to access to property.
282
+ # You may provide your own define logic by +filters+. Filter Proc will be
283
+ # call with models class as first argument, field name as second and
284
+ # field type as second.
236
285
  #
237
286
  # class Post
238
287
  # include PlainRecord::Resource
239
288
  #
240
289
  # entry_in 'posts/*/post.md'
241
290
  #
242
- # property :title
291
+ # field :title
243
292
  # end
244
- def property(name, *definers)
245
- @properties ||= []
246
- @properties << name
247
-
248
- accessors = call_definers(definers, name, :property)
249
-
250
- if accessors[:reader]
251
- class_eval <<-EOS, __FILE__, __LINE__
252
- def #{name}
253
- @data['#{name}']
254
- end
255
- EOS
256
- end
257
- if accessors[:writer]
258
- class_eval <<-EOS, __FILE__, __LINE__
259
- def #{name}=(value)
260
- @data['#{name}'] = value
261
- end
262
- EOS
263
- end
293
+ def field(name, *filters)
294
+ @fields ||= []
295
+ @fields << name
296
+
297
+ add_accessors :main, <<-EOS, __FILE__, __LINE__
298
+ def #{name}(*params)
299
+ @data['#{name}']
300
+ end
301
+ def #{name}=(value)
302
+ @data['#{name}'] = value
303
+ end
304
+ EOS
305
+
306
+ field_filters(filters, name, :field)
264
307
  end
265
308
 
266
- # Add special property with big text (for example, blog entry content). It
267
- # will stored after 3 dashes (<tt>---</tt>).
309
+ # Add special field with big text (for example, blog entry content).
310
+ # It will stored after 3 dashes (<tt>---</tt>).
268
311
  #
269
- # You can provide your own define logic by +definers+. Definer Proc
270
- # will be call with property name in first argument and may return
271
- # +:accessor+, +:writer+ or +:reader+ this method create standard methods
272
- # to access to property.
312
+ # You may provide your own define logic by +filter+. Filter Proc will be
313
+ # call with models class as first argument, field name as second and
314
+ # field type as second.
273
315
  #
274
316
  # Note, that text is supported by only +entry_in+ models, which entry store
275
317
  # in separated files.
@@ -283,9 +325,9 @@ module PlainRecord
283
325
  #
284
326
  # entry_in 'posts/*/post.md'
285
327
  #
286
- # property :title
287
- # text :summary
288
- # text :content
328
+ # field :title
329
+ # text :summary
330
+ # text :content
289
331
  # end
290
332
  #
291
333
  # File:
@@ -295,7 +337,7 @@ module PlainRecord
295
337
  # Post summary
296
338
  # ---
297
339
  # Post text
298
- def text(name, *definers)
340
+ def text(name, *filters)
299
341
  if :list == @storage
300
342
  raise ArgumentError, 'Text is supported by only entry_in models'
301
343
  end
@@ -304,41 +346,28 @@ module PlainRecord
304
346
  @texts << name
305
347
  number = @texts.length - 1
306
348
 
307
- accessors = call_definers(definers, name, :text)
349
+ add_accessors :main, <<-EOS, __FILE__, __LINE__
350
+ def #{name}
351
+ @texts[#{number}]
352
+ end
353
+ def #{name}=(value)
354
+ @texts[#{number}] = value
355
+ end
356
+ EOS
308
357
 
309
- if accessors[:reader]
310
- class_eval <<-EOS, __FILE__, __LINE__
311
- def #{name}
312
- @texts[#{number}]
313
- end
314
- EOS
315
- end
316
- if accessors[:writer]
317
- class_eval <<-EOS, __FILE__, __LINE__
318
- def #{name}=(value)
319
- @texts[#{number}] = value
320
- end
321
- EOS
322
- end
358
+ field_filters(filters, name, :text)
323
359
  end
324
360
 
325
- # Call +definers+ from +caller+ (<tt>:virtual</tt>, <tt>:property</tt> or
326
- # <tt>:text</tt>) for property with +name+ and return accessors, which will
327
- # be created as standart by +property+ or +text+ method.
328
- def call_definers(definers, name, caller)
329
- accessors = { :reader => true, :writer => true }
330
-
331
- definers.each do |definer|
332
- access = definer.call(name, caller)
333
- if :writer == access or access.nil?
334
- accessors[:reader] = false
335
- end
336
- if :reader == access or access.nil?
337
- accessors[:writer] = false
361
+ # Call all +filters+ for some +field+ with +type+.
362
+ def field_filters(filters, field, type)
363
+ filters.each do |filter|
364
+ if filter.is_a? Hash
365
+ filter = filter.map { |name, param| send(name, param) }
366
+ field_filters(filter, field, type)
367
+ else
368
+ filter.call(self, field, type)
338
369
  end
339
370
  end
340
-
341
- accessors
342
371
  end
343
372
  end
344
373
  end
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Module to be included into model class.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -29,7 +30,7 @@ module PlainRecord
29
30
  # * <tt>save(entry)</tt> – write entry to file.
30
31
  # See PlainRecord::Callbacks for details.
31
32
  #
32
- # You can define properties from entry file path, by +in_filepath+ definer.
33
+ # You can define fields from entry file path, by +in_filepath+ filter.
33
34
  # See PlainRecord::Filepath for details.
34
35
  #
35
36
  # class Post
@@ -42,18 +43,18 @@ module PlainRecord
42
43
  # end
43
44
  #
44
45
  # virtual :name, in_filepath(1)
45
- # property :title
46
- # text :summary
47
- # text :content
46
+ # field :title
47
+ # text :summary
48
+ # text :content
48
49
  # end
49
50
  module Resource
50
51
  class << self
51
- def included(base) #:nodoc:
52
+ def included(base)
52
53
  base.send :extend, Model
53
54
  end
54
55
  end
55
56
 
56
- # Properties values.
57
+ # Fields values.
57
58
  attr_reader :data
58
59
 
59
60
  # Texts values.
@@ -102,7 +103,7 @@ module PlainRecord
102
103
  self.file = self.class.path
103
104
  else
104
105
  raise ArgumentError, "There isn't file to save entry. " +
105
- "Set filepath properties or file."
106
+ "Set filepath fields or file."
106
107
  end
107
108
  end
108
109
 
@@ -122,7 +123,7 @@ module PlainRecord
122
123
  @data.to_yaml(opts)
123
124
  end
124
125
 
125
- # Compare if its properties and texts are equal.
126
+ # Compare if its fields and texts are equal.
126
127
  def eql?(other)
127
128
  return false unless other.kind_of?(self.class)
128
129
  @file == other.file and @data == other.data and @texts == @texts