plain_record 0.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -1
- data/ChangeLog +5 -0
- data/Gemfile +8 -3
- data/Gemfile.lock +16 -18
- data/Rakefile +6 -4
- data/lib/plain_record/associations.rb +16 -10
- data/lib/plain_record/extra/git.rb +2 -6
- data/lib/plain_record/extra/i18n.rb +2 -4
- data/lib/plain_record/extra/image.rb +282 -0
- data/lib/plain_record/file.rb +91 -0
- data/lib/plain_record/filepath.rb +41 -42
- data/lib/plain_record/model.rb +4 -4
- data/lib/plain_record/rails.rb +38 -0
- data/lib/plain_record/resource.rb +7 -6
- data/lib/plain_record/type.rb +2 -0
- data/lib/plain_record/version.rb +1 -1
- data/lib/plain_record.rb +7 -9
- data/plain_record.gemspec +4 -1
- data/spec/associations_spec.rb +9 -4
- data/spec/file_spec.rb +88 -0
- data/spec/image_spec.rb +149 -0
- data/spec/spec_helper.rb +4 -0
- metadata +98 -29
data/.travis.yml
CHANGED
data/ChangeLog
CHANGED
data/Gemfile
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
3
|
gem 'rake'
|
4
|
-
gem 'yard'
|
5
4
|
gem 'rspec'
|
6
|
-
gem '
|
7
|
-
gem 'r18n-core', :require => nil, :github => 'ai/r18n'
|
5
|
+
gem 'r18n-core', :require => nil
|
8
6
|
gem 'i18n', :require => nil
|
7
|
+
gem 'rmagick', :platforms => :ruby
|
8
|
+
gem 'rmagick4j', :platforms => :jruby
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem 'redcarpet'
|
12
|
+
gem 'yard'
|
13
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,33 +1,31 @@
|
|
1
|
-
GIT
|
2
|
-
remote: git://github.com/ai/r18n.git
|
3
|
-
revision: ca6933926eb955344eeb982de35f492c76a38b1a
|
4
|
-
specs:
|
5
|
-
r18n-core (0.4.14)
|
6
|
-
|
7
1
|
GEM
|
8
2
|
remote: http://rubygems.org/
|
9
3
|
specs:
|
10
4
|
diff-lcs (1.1.3)
|
11
|
-
i18n (0.6.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
rspec-
|
18
|
-
|
19
|
-
|
5
|
+
i18n (0.6.1)
|
6
|
+
r18n-core (1.1.3)
|
7
|
+
rake (10.0.3)
|
8
|
+
redcarpet (2.2.2)
|
9
|
+
rmagick (2.13.1)
|
10
|
+
rspec (2.12.0)
|
11
|
+
rspec-core (~> 2.12.0)
|
12
|
+
rspec-expectations (~> 2.12.0)
|
13
|
+
rspec-mocks (~> 2.12.0)
|
14
|
+
rspec-core (2.12.2)
|
15
|
+
rspec-expectations (2.12.1)
|
20
16
|
diff-lcs (~> 1.1.3)
|
21
|
-
rspec-mocks (2.
|
22
|
-
yard (0.8.
|
17
|
+
rspec-mocks (2.12.1)
|
18
|
+
yard (0.8.3)
|
23
19
|
|
24
20
|
PLATFORMS
|
25
21
|
ruby
|
26
22
|
|
27
23
|
DEPENDENCIES
|
28
24
|
i18n
|
29
|
-
r18n-core
|
25
|
+
r18n-core
|
30
26
|
rake
|
31
27
|
redcarpet
|
28
|
+
rmagick
|
29
|
+
rmagick4j
|
32
30
|
rspec
|
33
31
|
yard
|
data/Rakefile
CHANGED
@@ -15,10 +15,12 @@ require 'rspec/core/rake_task'
|
|
15
15
|
|
16
16
|
RSpec::Core::RakeTask.new
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
begin
|
19
|
+
require 'yard'
|
20
|
+
YARD::Rake::YardocTask.new do |yard|
|
21
|
+
yard.options << "--title='Plain Record #{PlainRecord::VERSION}'"
|
22
|
+
end
|
23
|
+
rescue LoadError; end
|
22
24
|
|
23
25
|
task :clobber_doc do
|
24
26
|
rm_r 'doc' rescue nil
|
@@ -88,7 +88,7 @@ module PlainRecord
|
|
88
88
|
#
|
89
89
|
# will be store as:
|
90
90
|
#
|
91
|
-
# author: John Smith
|
91
|
+
# author: John Smith
|
92
92
|
# movie:
|
93
93
|
# title: Watchmen
|
94
94
|
# genre: action
|
@@ -114,8 +114,8 @@ module PlainRecord
|
|
114
114
|
map = Associations.map(model, klass, "#{field}_") if map.empty?
|
115
115
|
Associations.define_link_one(model, klass, field, map)
|
116
116
|
else
|
117
|
-
raise ArgumentError,
|
118
|
-
|
117
|
+
raise ArgumentError,
|
118
|
+
"You couldn't create association field #{field} by text creator"
|
119
119
|
end
|
120
120
|
end
|
121
121
|
end
|
@@ -133,8 +133,8 @@ module PlainRecord
|
|
133
133
|
map = Associations.map(klass, model, prefix) if map.empty?
|
134
134
|
Associations.define_link_many(model, klass, field, map)
|
135
135
|
else
|
136
|
-
raise ArgumentError,
|
137
|
-
|
136
|
+
raise ArgumentError,
|
137
|
+
"You couldn't create association field #{field} by text creator"
|
138
138
|
end
|
139
139
|
end
|
140
140
|
end
|
@@ -144,16 +144,22 @@ module PlainRecord
|
|
144
144
|
def define_real_one(klass, field, model)
|
145
145
|
name = field.to_s
|
146
146
|
klass.after :load do |result, entry|
|
147
|
-
|
147
|
+
if entry.data[name]
|
148
|
+
entry.data[name] = model.new(entry.file, entry.data[name])
|
149
|
+
end
|
148
150
|
result
|
149
151
|
end
|
150
152
|
klass.before :save do |entry|
|
151
|
-
|
152
|
-
|
153
|
+
if entry.data[name]
|
154
|
+
model.call_before_callbacks(:save, [entry.data[name]])
|
155
|
+
entry.data[name] = entry.data[name]
|
156
|
+
end
|
153
157
|
end
|
154
158
|
klass.after :save do |result, entry|
|
155
|
-
|
156
|
-
|
159
|
+
if entry.data[name]
|
160
|
+
entry.data[name] = model.new(entry.file, entry.data[name])
|
161
|
+
model.call_after_callbacks(:save, nil, [entry.data[name]])
|
162
|
+
end
|
157
163
|
result
|
158
164
|
end
|
159
165
|
end
|
@@ -35,10 +35,8 @@ module PlainRecord::Extra
|
|
35
35
|
# virtual :updated_at, git_modify_time
|
36
36
|
# end
|
37
37
|
module Git
|
38
|
-
|
39
|
-
|
40
|
-
base.send :extend, Model
|
41
|
-
end
|
38
|
+
def self.included(base)
|
39
|
+
base.send :extend, Model
|
42
40
|
end
|
43
41
|
|
44
42
|
# Return time of first commit of model file (created time).
|
@@ -67,8 +65,6 @@ module PlainRecord::Extra
|
|
67
65
|
|
68
66
|
module Model
|
69
67
|
|
70
|
-
private
|
71
|
-
|
72
68
|
# Filter to set default value to time of last file git commit.
|
73
69
|
# If file is not commited or has changes, filter will return `Time.now`.
|
74
70
|
def git_modified_time
|
@@ -47,10 +47,8 @@ module PlainRecord::Extra
|
|
47
47
|
# end
|
48
48
|
# end
|
49
49
|
module I18n
|
50
|
-
|
51
|
-
|
52
|
-
base.send :extend, Model
|
53
|
-
end
|
50
|
+
def self.included(base)
|
51
|
+
base.send :extend, Model
|
54
52
|
end
|
55
53
|
|
56
54
|
# Return default locale. By default it look in R18n or Rails I18n.
|
@@ -0,0 +1,282 @@
|
|
1
|
+
=begin
|
2
|
+
Extention to store images.
|
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
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
module PlainRecord::Extra
|
24
|
+
# Extention to store images.
|
25
|
+
#
|
26
|
+
# It make sense only with `entry_in` models. You can get created or modified
|
27
|
+
# time from first or last git commit time.
|
28
|
+
#
|
29
|
+
# It is additional extention, so you need to include `PlainRecord::Extra::Git`
|
30
|
+
# module to your model.
|
31
|
+
#
|
32
|
+
# class User
|
33
|
+
# include PlainRecord::Resource
|
34
|
+
# include PlainRecord::Extra::Image
|
35
|
+
#
|
36
|
+
# entry_in "users/*.yml"
|
37
|
+
#
|
38
|
+
# image_from do |user, field|
|
39
|
+
# "users/#{field}/#{user.name}.png"
|
40
|
+
# end
|
41
|
+
# image_url do |user, field, size|
|
42
|
+
# if size
|
43
|
+
# "users/#{user.name}/#{field}.#{size}.png"
|
44
|
+
# else
|
45
|
+
# "users/#{user.name}/#{field}.png"
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# virtual :name, in_filepath(1)
|
50
|
+
# virtual :avatar, image(small: '32x32', big: '64x64')
|
51
|
+
# virtual :photo, image
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# # There are images at `data/users/avatar/ai.png` and
|
55
|
+
# # `data/users/photo/ai.png`
|
56
|
+
#
|
57
|
+
# PlainRecord::Extra::Image.convert_images!
|
58
|
+
# user = User.first(name: 'ai')
|
59
|
+
#
|
60
|
+
# user.photo.url #=> "data/users/ai/photo.png"
|
61
|
+
# user.photo.file #=> "app/assets/images/data/users/ai/avatar.small.png"
|
62
|
+
# user.avatar(:small).url #=> "data/users/ai/avatar.small.png"
|
63
|
+
module Image
|
64
|
+
class << self
|
65
|
+
# Models, that used this extention.
|
66
|
+
attr_accessor :included_in
|
67
|
+
|
68
|
+
# Base of converted images.
|
69
|
+
# Set to <tt>app/aseets/images/data</tt> in Rails.
|
70
|
+
attr_accessor :dir
|
71
|
+
|
72
|
+
# Base of converted images URL. Set to <tt>data/</tt> in Rails.
|
73
|
+
attr_accessor :url
|
74
|
+
|
75
|
+
def included(base)
|
76
|
+
base.send :extend, Model
|
77
|
+
self.included_in ||= []
|
78
|
+
self.included_in << base
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define class variables.
|
82
|
+
def install(klass)
|
83
|
+
klass.image_sizes = { }
|
84
|
+
end
|
85
|
+
|
86
|
+
def dir
|
87
|
+
unless @dir
|
88
|
+
raise ArgumentError,
|
89
|
+
'You need to set `PlainRecord::Extra::Image.dir`'
|
90
|
+
end
|
91
|
+
@dir
|
92
|
+
end
|
93
|
+
|
94
|
+
def url
|
95
|
+
unless @url
|
96
|
+
raise ArgumentError,
|
97
|
+
'You need to set `PlainRecord::Extra::Image.url`'
|
98
|
+
end
|
99
|
+
@url
|
100
|
+
end
|
101
|
+
|
102
|
+
# Delete all converted images.
|
103
|
+
def clean_images!
|
104
|
+
Dir.glob(File.join(self.dir, '**/*')) do |file|
|
105
|
+
FileUtils.rm_r(file) if File.exists? file
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Convert images in all models.
|
110
|
+
def convert_images!
|
111
|
+
clean_images!
|
112
|
+
return unless included_in
|
113
|
+
|
114
|
+
included_in.each do |model|
|
115
|
+
model.all.each { |i| i.convert_images! }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
# Convert all images to public dir.
|
122
|
+
def convert_images!
|
123
|
+
self.class.image_sizes.each_pair do |field, sizes|
|
124
|
+
from = self.class.get_image_from(self, field)
|
125
|
+
next unless File.exists? from
|
126
|
+
|
127
|
+
if sizes.empty?
|
128
|
+
to = self.class.get_image_file(self, field, nil)
|
129
|
+
FileUtils.mkpath(File.dirname(to))
|
130
|
+
FileUtils.cp(from, to)
|
131
|
+
else
|
132
|
+
require 'RMagick'
|
133
|
+
source = ::Magick::Image.read(from).first
|
134
|
+
sizes.each_pair do |name, size|
|
135
|
+
to = self.class.get_image_file(self, field, name)
|
136
|
+
w, h = size.split('x')
|
137
|
+
image = source.resize(w.to_i, h.to_i)
|
138
|
+
self.class.use_callbacks(:convert_image, self, to) do
|
139
|
+
FileUtils.mkpath(File.dirname(to))
|
140
|
+
image.write(to)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Field value object with image paths and URL.
|
148
|
+
class Data
|
149
|
+
|
150
|
+
# Name of image size.
|
151
|
+
attr_reader :size_name
|
152
|
+
|
153
|
+
# Format of image size.
|
154
|
+
attr_reader :size
|
155
|
+
|
156
|
+
# Image width.
|
157
|
+
attr_reader :width
|
158
|
+
|
159
|
+
# Image height.
|
160
|
+
attr_reader :height
|
161
|
+
|
162
|
+
# Converted image URL.
|
163
|
+
attr_reader :url
|
164
|
+
|
165
|
+
# Converted image file.
|
166
|
+
attr_reader :file
|
167
|
+
|
168
|
+
# Original image file.
|
169
|
+
attr_reader :original
|
170
|
+
|
171
|
+
# Set image paths.
|
172
|
+
def initialize(entry, field, size)
|
173
|
+
@size_name = size
|
174
|
+
@original = entry.class.get_image_from(entry, field)
|
175
|
+
|
176
|
+
if size
|
177
|
+
@size = entry.class.image_sizes[field][size]
|
178
|
+
unless @size
|
179
|
+
raise ArgumentError, "Field `#{field}` doesn't have `#{size}` size"
|
180
|
+
end
|
181
|
+
@width, @height = @size.split('x').map { |i| i.to_i }
|
182
|
+
end
|
183
|
+
|
184
|
+
if size or entry.class.image_sizes[field].empty?
|
185
|
+
@url = entry.class.get_image_url(entry, field, size)
|
186
|
+
@file = entry.class.get_image_file(entry, field, size)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def exists?
|
191
|
+
File.exists? @original
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
module Model
|
197
|
+
|
198
|
+
# Hash of image sizes.
|
199
|
+
attr_accessor :image_sizes
|
200
|
+
|
201
|
+
# Set source image path.
|
202
|
+
def image_from(&block)
|
203
|
+
@image_from = block
|
204
|
+
end
|
205
|
+
|
206
|
+
# Set relative image URL path from +image_base+.
|
207
|
+
def image_url(&block)
|
208
|
+
@image_url = block
|
209
|
+
end
|
210
|
+
|
211
|
+
# Get source image path.
|
212
|
+
def get_image_from(entry, field)
|
213
|
+
PlainRecord.root(@image_from.call(entry, field))
|
214
|
+
end
|
215
|
+
|
216
|
+
# Get converted image path.
|
217
|
+
def get_image_file(entry, field, size)
|
218
|
+
unless @image_url
|
219
|
+
raise ArgumentError,
|
220
|
+
"You need to set `image_url` in #{to_s} to calculate file path."
|
221
|
+
end
|
222
|
+
File.join(Image.dir, @image_url.call(entry, field, size))
|
223
|
+
end
|
224
|
+
|
225
|
+
# Get relative image URL path.
|
226
|
+
def get_image_url(entry, field, size)
|
227
|
+
unless @image_url
|
228
|
+
raise ArgumentError,
|
229
|
+
"You need to set `image_url` in #{to_s} to calculate file path."
|
230
|
+
end
|
231
|
+
File.join(Image.url, @image_url.call(entry, field, size))
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
# Use pngcrush (you must install it in system) to optimize png images.
|
237
|
+
#
|
238
|
+
# class User
|
239
|
+
# include PlainRecord::Resource
|
240
|
+
# include PlainRecord::Extra::Image
|
241
|
+
#
|
242
|
+
# optimize_png
|
243
|
+
# …
|
244
|
+
# end
|
245
|
+
def optimize_png
|
246
|
+
after :convert_image do |result, entry, file|
|
247
|
+
file = file.gsub(/([^A-Za-z0-9_\-\.,:\/@])/, "\\\\\\1")
|
248
|
+
file = file.gsub(/(^|\/)\.?\.($|\/)/, '')
|
249
|
+
return unless file =~ /\.png$/
|
250
|
+
|
251
|
+
tmp = file + '.optimized'
|
252
|
+
`pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB "#{file}" "#{tmp}"`
|
253
|
+
FileUtils.rm(file)
|
254
|
+
FileUtils.mv(tmp, file)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Filter to create field with image. You must set +image_from+.
|
259
|
+
#
|
260
|
+
# If you create Rails application you must set also +image_url+.
|
261
|
+
# If you create web application with another web framework, you must
|
262
|
+
# also redefine `image_url_to_path` or set `image_to`.
|
263
|
+
#
|
264
|
+
# If you create non-web application, you must set only `image_to`.
|
265
|
+
#
|
266
|
+
# You can set sizes hash (name to ImageMagick size) to convert images.
|
267
|
+
def image(sizes = { })
|
268
|
+
proc do |model, name, type|
|
269
|
+
Image.install(model) unless model.image_sizes
|
270
|
+
model.image_sizes[name] = sizes
|
271
|
+
|
272
|
+
model.add_accessors <<-EOS, __FILE__, __LINE__
|
273
|
+
def #{name}(size = nil)
|
274
|
+
PlainRecord::Extra::Image::Data.new(self, :#{name}, size)
|
275
|
+
end
|
276
|
+
EOS
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
=begin
|
2
|
+
Extention to set to get field value from external file.
|
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
|
22
|
+
# Extention to set to get field value from external file.
|
23
|
+
#
|
24
|
+
# class Post
|
25
|
+
# include PlainRecord::Resource
|
26
|
+
#
|
27
|
+
# entry_in '*/post.md'
|
28
|
+
#
|
29
|
+
# virtual :name, in_filepath(1)
|
30
|
+
# virtuel :text, file { |p| "#{p.name}/text.#{I18n.locale}.md" }
|
31
|
+
# end
|
32
|
+
module File
|
33
|
+
# Cache of field content before save.
|
34
|
+
attr_accessor :unsaved_files
|
35
|
+
|
36
|
+
# Return file pathname for fiel field.
|
37
|
+
def field_filepath(field)
|
38
|
+
path = self.class.fields_files[field]
|
39
|
+
path = path.call(self) if path.is_a? Proc
|
40
|
+
PlainRecord.root(path)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Define class variables and events in +klass+. It should be call once.
|
46
|
+
def self.install(klass)
|
47
|
+
klass.fields_files = { }
|
48
|
+
|
49
|
+
klass.before :load do |entry|
|
50
|
+
entry.unsaved_files = { }
|
51
|
+
end
|
52
|
+
|
53
|
+
klass.before :save do |entry|
|
54
|
+
entry.unsaved_files.each_pair do |file, value|
|
55
|
+
::File.open(file, 'w') { |io| io << value; }
|
56
|
+
end
|
57
|
+
entry.unsaved_files = { }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Model
|
62
|
+
|
63
|
+
# Field file paths.
|
64
|
+
attr_accessor :fields_files
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Filter to load field value from file.
|
69
|
+
def file(path = nil, &block)
|
70
|
+
proc do |model, field, type|
|
71
|
+
File.install(model) unless model.fields_files
|
72
|
+
model.fields_files[field] = block_given? ? block : path
|
73
|
+
|
74
|
+
model.add_accessors <<-EOS, __FILE__, __LINE__
|
75
|
+
def #{field}
|
76
|
+
path = field_filepath(:#{field})
|
77
|
+
return @unsaved_files[path] if @unsaved_files.has_key? path
|
78
|
+
return nil unless ::File.exists? path
|
79
|
+
::File.read(path)
|
80
|
+
end
|
81
|
+
|
82
|
+
def #{field}=(value)
|
83
|
+
@unsaved_files[field_filepath(:#{field})] = value
|
84
|
+
end
|
85
|
+
EOS
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -59,8 +59,8 @@ module PlainRecord
|
|
59
59
|
def in_filepath(number)
|
60
60
|
proc do |model, field, type|
|
61
61
|
if :virtual != type
|
62
|
-
raise ArgumentError,
|
63
|
-
|
62
|
+
raise ArgumentError,
|
63
|
+
"You must create filepath field #{field} virtual creator"
|
64
64
|
end
|
65
65
|
|
66
66
|
Filepath.install(model) unless model.filepath_fields
|
@@ -77,57 +77,56 @@ module PlainRecord
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
class
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
klass.filepath_fields = { }
|
80
|
+
# Define class variables and events in +klass+. It should be call once on
|
81
|
+
# same class after +entry_in+ or +list_in+ call.
|
82
|
+
def self.install(klass)
|
83
|
+
klass.filepath_fields = { }
|
85
84
|
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
path = Regexp.escape(klass.path).gsub(/\\\*\\\*(\/|$)/, '(.*)').
|
86
|
+
gsub('\\*', '([^/]+)')
|
87
|
+
klass.filepath_regexp = Regexp.new(path)
|
89
88
|
|
90
|
-
|
91
|
-
|
92
|
-
|
89
|
+
klass.class_eval do
|
90
|
+
attr_accessor :filepath_data
|
91
|
+
end
|
93
92
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
93
|
+
klass.after :load do |result, entry|
|
94
|
+
if entry.path
|
95
|
+
data = klass.filepath_regexp.match(entry.path)
|
96
|
+
entry.filepath_data = { }
|
97
|
+
klass.filepath_fields.each_pair do |number, name|
|
98
|
+
entry.filepath_data[name] = data[number]
|
99
|
+
end
|
100
|
+
else
|
101
|
+
entry.filepath_data = { }
|
102
|
+
klass.filepath_fields.each_value do |name|
|
103
|
+
entry.filepath_data[name] = entry.data[name]
|
104
|
+
entry.data.delete(name)
|
107
105
|
end
|
108
|
-
result
|
109
106
|
end
|
107
|
+
result
|
108
|
+
end
|
110
109
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
110
|
+
klass.after :path do |path, matchers|
|
111
|
+
i = 0
|
112
|
+
path.gsub /(\*\*(\/|$)|\*)/ do |pattern|
|
113
|
+
i += 1
|
114
|
+
field = klass.filepath_fields[i]
|
115
|
+
unless matchers[field].is_a? Regexp or matchers[field].nil?
|
116
|
+
matchers[field]
|
117
|
+
else
|
118
|
+
pattern
|
121
119
|
end
|
122
120
|
end
|
121
|
+
end
|
123
122
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
123
|
+
klass.before :save do |entry|
|
124
|
+
unless entry.file
|
125
|
+
path = klass.path(entry.filepath_data)
|
126
|
+
entry.file = path unless path =~ /[\*\[\?\{]/
|
129
127
|
end
|
130
128
|
end
|
131
129
|
end
|
130
|
+
|
132
131
|
end
|
133
132
|
end
|