ezframe 0.0.4 → 0.4.0
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.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +1 -1
- data/asset/css/materialize.min.css +13 -0
- data/asset/css/style.css +3 -0
- data/asset/html/index.html +1 -0
- data/{app_template/asset/image/favicon.ico → asset/image/c_e.ico} +0 -0
- data/asset/js/ezframe.js +387 -0
- data/exe/check_column_yml +64 -0
- data/exe/console +2 -2
- data/exe/{create_table.rb → create_table} +7 -4
- data/exe/dbmigrate +174 -0
- data/exe/html2ruby +61 -0
- data/ezframe.gemspec +10 -8
- data/lib/ezframe.rb +9 -3
- data/lib/ezframe/auth.rb +50 -31
- data/lib/ezframe/column_set.rb +314 -103
- data/lib/ezframe/column_type.rb +456 -99
- data/lib/ezframe/config.rb +27 -6
- data/lib/ezframe/controller.rb +41 -38
- data/lib/ezframe/database.rb +171 -52
- data/lib/ezframe/editor_common.rb +74 -0
- data/lib/ezframe/email.rb +34 -0
- data/lib/ezframe/ezlog.rb +40 -0
- data/lib/ezframe/ht.rb +42 -17
- data/lib/ezframe/html.rb +47 -31
- data/lib/ezframe/japanese_utils.rb +15 -0
- data/lib/ezframe/jquery-ui.rb +29 -0
- data/lib/ezframe/loader.rb +4 -4
- data/lib/ezframe/main_editor.rb +19 -0
- data/lib/ezframe/main_page_kit.rb +226 -0
- data/lib/ezframe/materialize.rb +10 -14
- data/lib/ezframe/message.rb +46 -0
- data/lib/ezframe/page_base.rb +59 -71
- data/lib/ezframe/route.rb +126 -0
- data/lib/ezframe/server.rb +16 -5
- data/lib/ezframe/single_page_editor.rb +22 -0
- data/lib/ezframe/single_page_kit.rb +199 -0
- data/lib/ezframe/sub_editor.rb +25 -0
- data/lib/ezframe/sub_page_kit.rb +213 -0
- data/lib/ezframe/template.rb +5 -4
- data/lib/ezframe/util.rb +45 -23
- data/lib/ezframe/version.rb +1 -1
- metadata +74 -34
- data/.rubocop.yml +0 -44
- data/app_template/asset/js/ezframe.js +0 -288
- data/app_template/config.ru +0 -10
- data/app_template/config/generic.yml +0 -3
- data/app_template/config/materialize.yml +0 -5
- data/app_template/pages/basic.rb +0 -5
- data/exe/setup.rb +0 -15
- data/lib/ezframe/editor.rb +0 -188
- data/lib/ezframe/model.rb +0 -52
- data/lib/ezframe/page_kit.rb +0 -63
data/lib/ezframe/column_set.rb
CHANGED
@@ -2,55 +2,192 @@
|
|
2
2
|
|
3
3
|
module Ezframe
|
4
4
|
class ColumnSets
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
class << self
|
6
|
+
def init(dir = nil)
|
7
|
+
dir ||= "./column"
|
8
|
+
unless @colset_h
|
9
|
+
@colset_h = {}
|
10
|
+
load_files(dir)
|
11
|
+
end
|
12
|
+
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
def load_files(dir)
|
15
|
+
Dir["#{dir}/*.yml"].each do |filename|
|
16
|
+
load_one_file(filename)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_one_file(filename)
|
21
|
+
colset_name = $1 if filename =~ /(\w+).ya?ml$/
|
22
|
+
yaml = YAML.load(File.open(filename), symbolize_names: true)
|
23
|
+
if yaml.length == 0
|
24
|
+
EzLog.error("[ERROR] columns file is empty: #{filename}")
|
25
|
+
return
|
26
|
+
end
|
27
|
+
column_info = yaml # .recursively_symbolize_keys
|
28
|
+
# puts "load_one_file: filename=#{filename} column_info=#{column_info.inspect}"
|
29
|
+
add(colset_name, column_info)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(colset_name, columns)
|
33
|
+
@colset_h[colset_name.to_sym] = cs = ColumnSet.new(parent: self, name: colset_name, columns: columns)
|
34
|
+
cs.set(columns)
|
35
|
+
return cs
|
36
|
+
end
|
37
|
+
|
38
|
+
def clone
|
39
|
+
@colset_h.deep_dup
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_key?(key)
|
43
|
+
return nil unless key
|
44
|
+
return @colset_h[key.to_sym]
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(colset_name)
|
48
|
+
return nil unless colset_name
|
49
|
+
return @colset_h[colset_name.to_sym].deep_dup
|
50
|
+
end
|
51
|
+
|
52
|
+
def refer(colset_name)
|
53
|
+
return nil unless colset_name
|
54
|
+
return @colset_h[colset_name.to_sym]
|
55
|
+
end
|
56
|
+
|
57
|
+
def [](colset_name)
|
58
|
+
return get(colset_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def each
|
62
|
+
@colset_h.each {|k, v| yield(k, v) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
return @colset_h.map do |name, colset|
|
67
|
+
# "[#{name}]:#{colset.inspect}"
|
68
|
+
"[#{name}]:\n"
|
69
|
+
end.join
|
14
70
|
end
|
15
|
-
end
|
16
71
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
72
|
+
def create_tables
|
73
|
+
self.each do |table_name, column_set|
|
74
|
+
begin
|
75
|
+
create_one_table(table_name, column_set)
|
76
|
+
rescue => e
|
77
|
+
EzLog.error("create_tables: #{e.inspect}\n#{$@.inspect}")
|
78
|
+
end
|
79
|
+
end
|
24
80
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
81
|
+
|
82
|
+
def create_one_table(table_name, column_set)
|
83
|
+
col_h = column_set.get_hash(:db_type)
|
84
|
+
EzLog.info "create_one_table: col_h=#{col_h.inspect}"
|
85
|
+
DB.create_table(table_name, col_h)
|
86
|
+
end
|
87
|
+
|
88
|
+
# foreignから生成したテーブル連結情報を返す
|
89
|
+
def full_join_structure(colset_id)
|
90
|
+
struct = { tables: [colset_id] }
|
91
|
+
colset = @colset_h[colset_id.to_sym]
|
92
|
+
colset_keys = colset.keys
|
93
|
+
struct[:column_list] = colset_keys.map { |k| "#{colset_id}.#{k}" }
|
94
|
+
join_cond_h = {}
|
95
|
+
colset_keys.each do |key|
|
96
|
+
column = colset[key]
|
97
|
+
if column.type.to_s == "foreign"
|
98
|
+
# 連結するテーブル名をtable: で指定する。
|
99
|
+
foreign_table = column.attribute[:table]
|
100
|
+
# 指定されてなければ、キーの名称をテーブル名とする
|
101
|
+
# そのテーブルが定義されてなければ、エラーとしてログに残す。
|
102
|
+
unless foreign_table
|
103
|
+
if @colset_h[key]
|
104
|
+
foreign_table = key
|
105
|
+
else
|
106
|
+
EzLog.error "There is no related table: #{key}"
|
107
|
+
next
|
108
|
+
end
|
109
|
+
end
|
110
|
+
raise "no table: key=#{key}" unless foreign_table
|
111
|
+
foreign_column = column.attribute[:column]&.to_sym || :id
|
112
|
+
foreign_table = foreign_table.to_sym
|
113
|
+
next if struct[:tables].include?(foreign_table)
|
114
|
+
# join_cond_h["#{colset_id}.#{key}"] = "#{colset_id}.#{key} = #{foreign_table}.#{foreign_column}"
|
115
|
+
join_cond_h[foreign_table] = "#{colset_id}.#{key} = #{foreign_table}.#{foreign_column}"
|
116
|
+
struct[:tables].push(foreign_table)
|
117
|
+
struct[:column_list] += ColumnSets.refer(foreign_table).keys.map {|k| "#{foreign_table}.#{k}" }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
struct[:join_condition] = join_cond_h
|
121
|
+
return struct
|
122
|
+
end
|
123
|
+
|
124
|
+
def join_complex_column
|
125
|
+
|
28
126
|
end
|
29
|
-
column_info = yaml.recursively_symbolize_keys
|
30
|
-
puts "load_one_file: filename=#{filename} column_info=#{column_info.inspect}"
|
31
|
-
add(table_name, column_info)
|
32
127
|
end
|
128
|
+
end
|
33
129
|
|
34
|
-
|
35
|
-
|
36
|
-
|
130
|
+
# ColumnSetを複数組み合わせて扱う
|
131
|
+
class ColumnSetCollection
|
132
|
+
attr_accessor :colset_list
|
133
|
+
|
134
|
+
def initialize(default_table=nil)
|
135
|
+
@colset_h = {}
|
136
|
+
@default_table = default_table
|
37
137
|
end
|
38
138
|
|
39
|
-
def
|
40
|
-
|
139
|
+
def values=(data)
|
140
|
+
@colset_h.each {|key, colset| colset.clear }
|
141
|
+
set_values(data)
|
41
142
|
end
|
42
143
|
|
43
|
-
def
|
44
|
-
|
144
|
+
def set_values(data)
|
145
|
+
data.each do |key, value|
|
146
|
+
if key.to_s.index(".")
|
147
|
+
table_key, col_key = key.to_s.split(".")
|
148
|
+
colset = @colset_h[table_key.to_sym]
|
149
|
+
unless colset
|
150
|
+
@colset_h[table_key.to_sym] = colset = ColumnSets[table_key]
|
151
|
+
end
|
152
|
+
elsif @default_table
|
153
|
+
col_key = key
|
154
|
+
colset = @colset_h[@default_table.to_sym]
|
155
|
+
unless colset
|
156
|
+
@colset_h[table_key.to_sym] = colset = ColumnSets[@default_table]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
colset[col_key].value = value
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def get(colset_key, col_key=nil)
|
164
|
+
if col_key.nil?
|
165
|
+
if colset_key.to_s.index(".")
|
166
|
+
colset_key, col_key = colset_key.to_s.split(".")
|
167
|
+
elsif @default_table
|
168
|
+
colset_key, col_key = @default_table, colset_key
|
169
|
+
else
|
170
|
+
EzLog.error "ColumnSetCollection.get: illegal arguments: #{colset_key}, #{col_key}"
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
end
|
174
|
+
colset = @colset_h[colset_key.to_sym]
|
175
|
+
return nil unless colset
|
176
|
+
# EzLog.debug("Collection.get: colset_key=#{colset_key}, col_key=#{col_key}, value=#{colset[col_key].value}")
|
177
|
+
return colset[col_key]
|
178
|
+
end
|
179
|
+
|
180
|
+
def [](k)
|
181
|
+
return get(k)
|
45
182
|
end
|
46
183
|
end
|
47
184
|
|
48
185
|
class ColumnSet
|
49
|
-
attr_accessor :name, :parent, :edit_keys, :view_keys
|
186
|
+
attr_accessor :name, :parent # , :edit_keys, :view_keys
|
50
187
|
|
51
|
-
def initialize(parent
|
188
|
+
def initialize(parent: nil, name: nil, columns: nil)
|
52
189
|
@parent = parent
|
53
|
-
@name = name
|
190
|
+
@name = name
|
54
191
|
@columns ||= {}
|
55
192
|
set(columns) if columns
|
56
193
|
end
|
@@ -61,10 +198,23 @@ module Ezframe
|
|
61
198
|
end
|
62
199
|
end
|
63
200
|
|
201
|
+
def keys
|
202
|
+
@columns.keys
|
203
|
+
end
|
204
|
+
|
205
|
+
def edit_keys
|
206
|
+
@columns.keys.select {|k| !@columns[k].no_edit? }
|
207
|
+
end
|
208
|
+
|
209
|
+
def view_keys
|
210
|
+
@columns.keys.select {|k| !@columns[k].no_view? }
|
211
|
+
end
|
212
|
+
|
213
|
+
# 配列を初期化する
|
64
214
|
def set(attr_a)
|
65
|
-
@columns[:id] = IdType.new(key: "id", label: "ID",
|
66
|
-
attr_a.each do |
|
67
|
-
attr =
|
215
|
+
@columns[:id] = IdType.new(key: "id", label: "ID", hidden: true)
|
216
|
+
attr_a.each do |attribute|
|
217
|
+
attr = attribute.clone
|
68
218
|
col_key = attr[:key]
|
69
219
|
raise "no column key: #{attr.inspect}" unless col_key
|
70
220
|
klass = TypeBase.get_class(attr[:type])
|
@@ -74,97 +224,147 @@ module Ezframe
|
|
74
224
|
@columns[col_key.to_sym] = klass.new(attr)
|
75
225
|
end
|
76
226
|
end
|
77
|
-
@columns[:created_at] = DatetimeType.new(type: "datetime", key: "created_at", label: "生成日時",
|
78
|
-
@columns[:updated_at] = DatetimeType.new(type: "datetime", key: "updated_at", label: "更新日時",
|
79
|
-
|
80
|
-
@columns.values.each {|col| col.parent = self }
|
227
|
+
@columns[:created_at] = DatetimeType.new(type: "datetime", key: "created_at", label: "生成日時", hidden: true)
|
228
|
+
@columns[:updated_at] = DatetimeType.new(type: "datetime", key: "updated_at", label: "更新日時", hidden: true)
|
229
|
+
@columns[:deleted_at] = DatetimeType.new(type: "datetime", key: "deleted_at", label: "削除日時", hidden: true)
|
230
|
+
@columns.values.each { |col| col.parent = self }
|
81
231
|
return @columns
|
82
232
|
end
|
83
233
|
|
84
234
|
def dataset
|
85
|
-
|
86
|
-
return @parent.model.db.dataset(@name)
|
235
|
+
return DB.dataset(@name)
|
87
236
|
end
|
88
237
|
|
89
238
|
def set_from_db(id)
|
90
239
|
data = dataset.where(id: id).first
|
91
240
|
return nil unless data
|
92
|
-
self.
|
241
|
+
self.set_values(data, from_db: true)
|
93
242
|
return data
|
94
243
|
end
|
95
244
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
dataset.where(id: id.value).update(col_h)
|
245
|
+
def set_from_form(form, key_suffix: nil)
|
246
|
+
self.set_values(form)
|
247
|
+
end
|
248
|
+
|
249
|
+
# データベースに新規に値を登録する
|
250
|
+
def create(value_h, from_db: nil, key_suffix: nil)
|
251
|
+
if from_db
|
252
|
+
self.set_values(value_h, from_db: true, key_suffix: key_suffix)
|
105
253
|
else
|
106
|
-
|
254
|
+
self.set_values(value_h, key_suffix: key_suffix)
|
107
255
|
end
|
256
|
+
db_value_h = self.get_hash(:db_value)
|
257
|
+
EzLog.debug("column_set.create: #{db_value_h}")
|
258
|
+
db_value_h.delete(:id)
|
259
|
+
db_value_h[:updated_at] = Time.now
|
260
|
+
db_value_h[:created_at] = Time.now
|
261
|
+
EzLog.debug("create: sql=#{dataset.insert_sql(db_value_h)}")
|
262
|
+
return dataset.insert(db_value_h)
|
108
263
|
end
|
109
264
|
|
265
|
+
# データベース上の値の更新
|
110
266
|
def update(id, value_h)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
267
|
+
self.set_from_db(id)
|
268
|
+
updated_values = {}
|
269
|
+
@columns.each do |colkey, column|
|
270
|
+
next if column.no_edit?
|
271
|
+
if column.respond_to?(:form_to_value)
|
272
|
+
new_value = column.form_to_value(value_h)
|
273
|
+
else
|
274
|
+
new_value = value_h[colkey]
|
275
|
+
end
|
276
|
+
prev_value = column.db_value
|
277
|
+
column.value = new_value
|
278
|
+
# EzLog.debug("key=#{colkey}, pre_value=#{prev_value}, new_value=#{column.db_value}")
|
279
|
+
if column.respond_to?("value_equal?")
|
280
|
+
unless column.value_equal?(prev_value, column.db_value)
|
281
|
+
updated_values[colkey] = column.db_value
|
282
|
+
end
|
283
|
+
elsif prev_value != column.db_value
|
284
|
+
updated_values[colkey] = column.db_value
|
285
|
+
end
|
286
|
+
end
|
287
|
+
if updated_values.length > 0
|
288
|
+
updated_values[:updated_at] = Time.now
|
289
|
+
sql = dataset.where(id: id).update_sql(updated_values)
|
290
|
+
EzLog.debug("update: sql=#{sql}")
|
291
|
+
dataset.where(id: id).update(updated_values)
|
115
292
|
end
|
116
|
-
dataset.where(id: id).update(values)
|
117
|
-
set_values(values)
|
118
293
|
end
|
119
294
|
|
120
|
-
|
121
|
-
|
122
|
-
|
295
|
+
# 各カラムに値を格納する
|
296
|
+
def set_values(value_h, from_db: nil, key_suffix: nil)
|
297
|
+
self.clear
|
298
|
+
merge_values(value_h, from_db: from_db, key_suffix: key_suffix)
|
123
299
|
end
|
124
300
|
|
125
|
-
def
|
126
|
-
return unless value_h
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
301
|
+
def merge_values(value_h, from_db: nil, key_suffix: nil)
|
302
|
+
return self unless value_h
|
303
|
+
@columns.keys.each do |key|
|
304
|
+
next if key.to_s.empty?
|
305
|
+
target_key = key
|
306
|
+
target_key = "#{key}#{key_suffix}" if key_suffix
|
307
|
+
column = @columns[key.to_sym]
|
308
|
+
if !from_db && column.respond_to?(:form_to_value) # && !value_h.has_key?(key)
|
309
|
+
val = column.form_to_value(value_h, target_key: target_key)
|
310
|
+
else
|
311
|
+
val = value_h[target_key.to_sym] || value_h[key]
|
312
|
+
end
|
313
|
+
column.value = val
|
131
314
|
end
|
315
|
+
return self
|
316
|
+
end
|
317
|
+
|
318
|
+
def values=(value_h)
|
319
|
+
set_values(value_h)
|
132
320
|
end
|
133
321
|
|
134
|
-
|
322
|
+
# 各カラムのバリデーション
|
323
|
+
# 戻り値は[ 正規化した値, エラーシンボル(Messageのキーと紐づく) ]を値として、
|
324
|
+
# カラムキーをキーとするハッシュ
|
325
|
+
def validate(value_h)
|
326
|
+
return {} unless value_h
|
135
327
|
clear_error
|
136
|
-
|
137
|
-
@columns.values.each do |col|
|
138
|
-
|
139
|
-
|
328
|
+
result_h = {}
|
329
|
+
@columns.values.each do |col|
|
330
|
+
res = []
|
331
|
+
if col.respond_to?(:form_to_value) && !value_h.has_key?(col.key)
|
332
|
+
orig_val = col.form_to_value(value_h)
|
333
|
+
else
|
334
|
+
orig_val = value_h[col.key]
|
335
|
+
end
|
336
|
+
new_val = col.normalize(orig_val)
|
337
|
+
res[0] = new_val if orig_val != new_val
|
338
|
+
res[1] = col.validate(new_val)
|
339
|
+
result_h[col.key] = res if res[0] || res[1]
|
140
340
|
end
|
141
|
-
return
|
341
|
+
return result_h
|
142
342
|
end
|
143
343
|
|
144
344
|
def clear_error
|
145
|
-
@columns.values.each {|col| col.error = nil }
|
345
|
+
@columns.values.each { |col| col.error = nil }
|
146
346
|
end
|
147
347
|
|
148
348
|
def values
|
149
|
-
@columns.map {|key, col| col.value}
|
150
|
-
end
|
349
|
+
@columns.map { |key, col| col.value }
|
350
|
+
end
|
151
351
|
|
152
352
|
def each
|
153
|
-
@columns.values.each {|column| yield(column) }
|
353
|
+
@columns.values.each { |column| yield(column) }
|
154
354
|
end
|
155
355
|
|
156
356
|
def map
|
157
|
-
@columns.values.map {|column| yield(column) }
|
357
|
+
@columns.values.map { |column| yield(column) }
|
158
358
|
end
|
159
359
|
|
160
360
|
def get_matrix(method_a)
|
161
361
|
return @columns.map do |_key, col|
|
162
|
-
|
163
|
-
|
362
|
+
method_a.map { |method| col.send(method) }
|
363
|
+
end
|
164
364
|
end
|
165
365
|
|
166
366
|
def get_hash(method)
|
167
|
-
res_h = {}
|
367
|
+
res_h = {}
|
168
368
|
@columns.map do |key, col|
|
169
369
|
res_h[key.to_sym] = col.send(method)
|
170
370
|
end
|
@@ -174,41 +374,52 @@ module Ezframe
|
|
174
374
|
def [](col_key)
|
175
375
|
return @columns[col_key.to_sym]
|
176
376
|
end
|
177
|
-
|
377
|
+
|
178
378
|
def form
|
179
379
|
if @edit_keys
|
180
|
-
return @edit_keys.map do |key|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
380
|
+
return @edit_keys.map do |key|
|
381
|
+
col = @columns[key.to_sym]
|
382
|
+
unless col
|
383
|
+
EzLog.info "[ERROR] @edit_keys has unknown column:name=#{@name}:key=#{key}"
|
384
|
+
next
|
385
|
+
end
|
386
|
+
col.form
|
387
|
+
end
|
188
388
|
else
|
189
|
-
return
|
190
|
-
end
|
389
|
+
return @columns.values.map { |coltype| coltype.form }
|
390
|
+
end
|
191
391
|
end
|
192
392
|
|
193
393
|
def view
|
194
394
|
if @view_keys
|
195
|
-
return @view_keys.map do |key|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
395
|
+
return @view_keys.map do |key|
|
396
|
+
col = @columns[key.to_sym]
|
397
|
+
unless col
|
398
|
+
EzLog.info "[ERROR] @view_keys has unknown column:name=#{@name}:key=#{key}"
|
399
|
+
next
|
400
|
+
end
|
401
|
+
col.view
|
402
|
+
end
|
203
403
|
else
|
204
|
-
return
|
404
|
+
return @columns.values.map { |coltype| coltype.view }
|
205
405
|
end
|
206
406
|
end
|
207
407
|
|
408
|
+
def get_full_join(opts = {})
|
409
|
+
struct = ColumnSets.full_join_structure(self.name)
|
410
|
+
return DB.get_join_table(struct, opts)
|
411
|
+
end
|
412
|
+
|
208
413
|
def hidden_form
|
209
414
|
return @columns.map do |colkey, coltype|
|
210
|
-
|
211
|
-
|
415
|
+
{ tag: "input", id: colkey, name: colkey, type: "hidden", value: coltype.value }
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def inpsect
|
420
|
+
@columns.map do |colkey, coltype|
|
421
|
+
"#{colkey}=#{coltype.value}"
|
422
|
+
end.join(" ")
|
212
423
|
end
|
213
424
|
end
|
214
425
|
end
|