heart_seed 0.0.1.beta1 → 0.0.1.beta2
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/README.md +99 -4
- data/lib/heart_seed/converter.rb +14 -4
- data/lib/heart_seed/db_seed.rb +28 -9
- data/lib/heart_seed/helper.rb +12 -0
- data/lib/heart_seed/railtie.rb +1 -1
- data/lib/heart_seed/tasks.rb +1 -0
- data/lib/heart_seed/tasks/heart_seed.rake +42 -21
- data/lib/heart_seed/version.rb +1 -1
- data/spec/data/articles.xls +0 -0
- data/spec/heart_seed/converter_spec.rb +16 -0
- data/spec/heart_seed/db_seed_spec.rb +52 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6cdf153eeacd0a2bb25e04f0b086d6c3d5b47f5
|
4
|
+
data.tar.gz: d7c2518396273ff9de9771c6985b6f3590fedd97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6634e532e3fbef27ceb93dce2d138428df7ed6992fab00f5164b8b8a4918888fa396a7d2998da703533bc2f9613a4ac31f0496c9ae07dfd5322e9c2c666a8e90
|
7
|
+
data.tar.gz: d99648ac07a0816d9c11c9c7e5c9dfda693010b63b6d2a224a7ea90655a63cff751ceeaa65290bf19966712cbce72a7ab5f7f9ada57c7944e3e160bccff96e48
|
data/README.md
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# [WIP] HeartSeed
|
2
2
|
|
3
|
-
seed util (excel -> yaml -> db)
|
3
|
+
seed util (excel -> yaml -> db)
|
4
4
|
|
5
|
+
[](http://badge.fury.io/rb/heart_seed)
|
5
6
|
[](https://travis-ci.org/sue445/heart_seed)
|
6
7
|
[](https://codeclimate.com/github/sue445/heart_seed)
|
7
8
|
[](https://coveralls.io/r/sue445/heart_seed?branch=master)
|
8
9
|
[](https://gemnasium.com/sue445/heart_seed)
|
9
10
|
|
11
|
+
[](https://waffle.io/sue445/heart_seed)
|
12
|
+
|
10
13
|
## Installation
|
11
14
|
|
12
15
|
Add this line to your application's Gemfile:
|
@@ -27,13 +30,105 @@ Or install it yourself as:
|
|
27
30
|
* create `config/heart_seed.yml`, `db/xls`, `db/seeds`
|
28
31
|
* append to `db/seeds.rb`
|
29
32
|
2. Create xls
|
33
|
+
* sheet name (xls,xlsx) = table name (DB)
|
30
34
|
* example https://github.com/sue445/heart_seed/tree/master/spec/dummy/db/xls
|
31
35
|
3. `bundle exec rake heart_seed:xls`
|
32
36
|
* Generate yml to `db/seeds`
|
33
|
-
* If you want to specify files: `FILES=comments_and_likes.xls SHEETS=comments,likes bundle exec rake heart_seed:xls`
|
34
|
-
4. `bundle exec rake db:seed`
|
37
|
+
* If you want to specify files: `FILES=comments_and_likes.xls SHEETS=comments,likes bundle exec rake heart_seed:xls`
|
38
|
+
4. `bundle exec rake db:seed` or `bundle exec rake heart_seed:db:seed`
|
35
39
|
* Import yml to db
|
36
|
-
*
|
40
|
+
* Exists `TABLES`, `CATALOGS` options
|
41
|
+
|
42
|
+
examples
|
43
|
+
|
44
|
+
```sh
|
45
|
+
TABLES=articles,comments bundle exec rake db:seed
|
46
|
+
CATALOGS=article,user bundle exec rake db:seed
|
47
|
+
```
|
48
|
+
|
49
|
+
### other Rails
|
50
|
+
|
51
|
+
append this to `Rakefile`
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'heart_seed/tasks'
|
55
|
+
```
|
56
|
+
|
57
|
+
### Snippets
|
58
|
+
#### config/heart_seed.yml
|
59
|
+
```yml
|
60
|
+
seed_dir: db/seeds
|
61
|
+
xls_dir: db/xls
|
62
|
+
catalogs:
|
63
|
+
# user:
|
64
|
+
# - users
|
65
|
+
# - user_profiles
|
66
|
+
```
|
67
|
+
|
68
|
+
#### db/seeds.rb
|
69
|
+
```ruby
|
70
|
+
# Appended by `rake heart_seed:init`
|
71
|
+
HeartSeed::DbSeed.import_all
|
72
|
+
```
|
73
|
+
|
74
|
+
## Specification
|
75
|
+
### Supported xls/xlsx format
|
76
|
+
|
77
|
+
Example sheet
|
78
|
+
|
79
|
+
id | title | description | created_at | | this is dummy
|
80
|
+
--- | ------ | ------------ | -------------- | --- | --------------
|
81
|
+
1 | title1 | description1 | 2014/6/1 12:10 | | foo
|
82
|
+
2 | title2 | description2 | 2014/6/2 12:10 | | baz
|
83
|
+
|
84
|
+
* Sheet name is mapped table name
|
85
|
+
* If sheet name is not found in database, this is ignored
|
86
|
+
* 1st row : table column names
|
87
|
+
* If the spaces are included in the middle, right columns are ignored
|
88
|
+
* 2nd row ~ : records
|
89
|
+
* [ActiveSupport::TimeWithZone](http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html) is used for timezone
|
90
|
+
|
91
|
+
### Yaml format
|
92
|
+
|
93
|
+
example
|
94
|
+
|
95
|
+
```yaml
|
96
|
+
---
|
97
|
+
articles_1:
|
98
|
+
id: 1
|
99
|
+
title: title1
|
100
|
+
description: description1
|
101
|
+
created_at: '2014-06-01 12:10:00 +0900'
|
102
|
+
articles_2:
|
103
|
+
id: 2
|
104
|
+
title: title2
|
105
|
+
description: description2
|
106
|
+
created_at: '2014-06-02 12:10:00 +0900'
|
107
|
+
```
|
108
|
+
|
109
|
+
* same as [ActiveRecord::FixtureSet](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html) format
|
110
|
+
|
111
|
+
### Catalog
|
112
|
+
Catalog is table groups defined in heart_seed.yml
|
113
|
+
|
114
|
+
example
|
115
|
+
|
116
|
+
```yml
|
117
|
+
catalogs:
|
118
|
+
user:
|
119
|
+
- users
|
120
|
+
- user_profiles
|
121
|
+
```
|
122
|
+
|
123
|
+
`user` catalog = `users`, `user_profiles` tables
|
124
|
+
|
125
|
+
|
126
|
+
You can specify the catalogs at `db:seed` task
|
127
|
+
|
128
|
+
```sh
|
129
|
+
CATALOGS=user bundle exec rake db:seed
|
130
|
+
# same to) TABLES=users,user_profiles bundle exec rake db:seed
|
131
|
+
```
|
37
132
|
|
38
133
|
## Contributing
|
39
134
|
|
data/lib/heart_seed/converter.rb
CHANGED
@@ -87,7 +87,7 @@ module HeartSeed
|
|
87
87
|
#
|
88
88
|
# @return [ Hash{ String => Hash{ String => Object } } ]
|
89
89
|
def self.read_sheet(sheet, row_prefix)
|
90
|
-
header_keys = sheet.row(HEADER_ROW)
|
90
|
+
header_keys = select_left_of_blank(sheet.row(HEADER_ROW))
|
91
91
|
fixtures = {}
|
92
92
|
|
93
93
|
(HEADER_ROW + 1 .. sheet.last_row).each do |row_num|
|
@@ -95,11 +95,11 @@ module HeartSeed
|
|
95
95
|
header_keys.each_with_index do |key, col_index|
|
96
96
|
value = sheet.cell(row_num, col_index + 1)
|
97
97
|
|
98
|
-
case
|
99
|
-
when
|
98
|
+
case value
|
99
|
+
when Float
|
100
100
|
# ex) 1.0 -> 1
|
101
101
|
value = value.to_i if value == value.to_i
|
102
|
-
when
|
102
|
+
when DateTime
|
103
103
|
# value is DateTime and localtime, but not included TimeZone(UTC)
|
104
104
|
time = Time.zone.at(value.to_i - Time.zone.utc_offset)
|
105
105
|
value = time.to_s
|
@@ -116,5 +116,15 @@ module HeartSeed
|
|
116
116
|
|
117
117
|
fixtures
|
118
118
|
end
|
119
|
+
|
120
|
+
def self.select_left_of_blank(array)
|
121
|
+
response = []
|
122
|
+
array.each do |element|
|
123
|
+
break unless element
|
124
|
+
break if element.respond_to?(:blank?) && element.blank?
|
125
|
+
response << element
|
126
|
+
end
|
127
|
+
response
|
128
|
+
end
|
119
129
|
end
|
120
130
|
end
|
data/lib/heart_seed/db_seed.rb
CHANGED
@@ -21,30 +21,49 @@ module HeartSeed
|
|
21
21
|
#
|
22
22
|
# @param seed_dir [String]
|
23
23
|
# @param tables [Array<String>,String] table names array or comma separated table names. if empty, import all seed yaml. if not empty, import only these tables.
|
24
|
-
def self.import_all(seed_dir: HeartSeed::Helper.seed_dir, tables: [])
|
25
|
-
|
24
|
+
def self.import_all(seed_dir: HeartSeed::Helper.seed_dir, tables: ENV["TABLES"], catalogs: ENV["CATALOGS"])
|
25
|
+
# use tables in catalogs
|
26
|
+
target_tables = parse_arg_catalogs(catalogs)
|
27
|
+
if target_tables.empty?
|
28
|
+
# use tables
|
29
|
+
target_tables = parse_string_or_array_arg(tables)
|
30
|
+
end
|
26
31
|
|
32
|
+
ActiveRecord::Migration.verbose = true
|
27
33
|
Dir.glob(File.join(seed_dir, "*.yml")) do |file|
|
28
34
|
table_name = File.basename(file, '.*')
|
29
35
|
next unless target_table?(table_name, target_tables)
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
+
ActiveRecord::Migration.say_with_time("#{file} -> #{table_name}") do
|
38
|
+
begin
|
39
|
+
model_class = table_name.classify.constantize
|
40
|
+
bulk_insert(source_file: file, model_class: model_class)
|
41
|
+
ActiveRecord::Migration.say("[INFO] success", true)
|
42
|
+
rescue => e
|
43
|
+
ActiveRecord::Migration.say("[ERROR] #{e.message}", true)
|
44
|
+
end
|
37
45
|
end
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
41
|
-
def self.
|
49
|
+
def self.parse_string_or_array_arg(tables)
|
42
50
|
return [] unless tables
|
43
51
|
return tables if tables.class == Array
|
44
52
|
|
45
53
|
tables.class == String ? tables.split(",") : []
|
46
54
|
end
|
47
55
|
|
56
|
+
def self.parse_arg_catalogs(catalogs)
|
57
|
+
array_catalogs = parse_string_or_array_arg(catalogs)
|
58
|
+
return [] if array_catalogs.empty?
|
59
|
+
|
60
|
+
tables = []
|
61
|
+
array_catalogs.each do |catalog|
|
62
|
+
tables += HeartSeed::Helper.catalog_tables(catalog)
|
63
|
+
end
|
64
|
+
tables.compact
|
65
|
+
end
|
66
|
+
|
48
67
|
private
|
49
68
|
def self.target_table?(source_table, target_tables)
|
50
69
|
return true if target_tables.empty?
|
data/lib/heart_seed/helper.rb
CHANGED
@@ -11,6 +11,7 @@ module HeartSeed
|
|
11
11
|
{
|
12
12
|
"seed_dir" => "db/seeds",
|
13
13
|
"xls_dir" => "db/xls",
|
14
|
+
"catalogs" => {},
|
14
15
|
}
|
15
16
|
end
|
16
17
|
end
|
@@ -45,5 +46,16 @@ module HeartSeed
|
|
45
46
|
def self.root_dir=(dir)
|
46
47
|
@root_dir = Pathname.new(dir)
|
47
48
|
end
|
49
|
+
|
50
|
+
# @return [Hash{String => Array<String>}] key: catalog name, value: table names
|
51
|
+
def self.catalogs
|
52
|
+
config["catalogs"] || {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param catalog_name [String]
|
56
|
+
# @return [Array<String>] table names in a specify catalog
|
57
|
+
def self.catalog_tables(catalog_name)
|
58
|
+
self.catalogs[catalog_name] || []
|
59
|
+
end
|
48
60
|
end
|
49
61
|
end
|
data/lib/heart_seed/railtie.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
load "heart_seed/tasks/heart_seed.rake"
|
@@ -1,10 +1,10 @@
|
|
1
1
|
namespace :heart_seed do
|
2
2
|
desc "create dir and file"
|
3
|
-
task :init => ["config/heart_seed.yml", "db/xls", "db/seeds"] do
|
3
|
+
task :init => ["config/heart_seed.yml", "db/xls/.gitkeep", "db/seeds/.gitkeep"] do
|
4
4
|
template = <<RUBY
|
5
5
|
|
6
6
|
# Appended by `rake heart_seed:init`
|
7
|
-
HeartSeed::DbSeed.import_all
|
7
|
+
HeartSeed::DbSeed.import_all
|
8
8
|
|
9
9
|
RUBY
|
10
10
|
|
@@ -15,43 +15,64 @@ RUBY
|
|
15
15
|
template = <<YAML
|
16
16
|
seed_dir: db/seeds
|
17
17
|
xls_dir: db/xls
|
18
|
+
catalogs:
|
19
|
+
# user:
|
20
|
+
# - users
|
21
|
+
# - user_profiles
|
18
22
|
YAML
|
19
23
|
|
20
24
|
create_file("config/heart_seed.yml", template)
|
21
25
|
end
|
22
26
|
|
27
|
+
file "db/xls/.gitkeep" => "db/xls" do
|
28
|
+
create_file("db/xls/.gitkeep")
|
29
|
+
end
|
30
|
+
|
31
|
+
file "db/seeds/.gitkeep" => "db/seeds" do
|
32
|
+
create_file("db/seeds/.gitkeep")
|
33
|
+
end
|
34
|
+
|
23
35
|
directory "config"
|
24
36
|
directory "db/xls"
|
25
37
|
directory "db/seeds"
|
26
38
|
|
27
|
-
desc "create seed files by xls directory"
|
39
|
+
desc "create seed files by xls directory (options: FILES=table1.xls,table2.xlsx SHEETS=sheet1,sheet2)"
|
28
40
|
task :xls => :environment do
|
41
|
+
ActiveRecord::Migration.verbose = true
|
29
42
|
Dir.glob(File.join(HeartSeed::Helper.xls_dir, "*.{xls,xlsx}")) do |file|
|
30
43
|
next if File.basename(file) =~ /^~/
|
31
44
|
|
32
45
|
next unless target_file?(file)
|
33
46
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
ActiveRecord::Migration.say_with_time("Source File: #{file}") do
|
48
|
+
sheets = HeartSeed::Converter.table_sheets(file)
|
49
|
+
sheets.each do |sheet|
|
50
|
+
unless ActiveRecord::Base.connection.table_exists?(sheet)
|
51
|
+
ActiveRecord::Migration.say("[#{sheet}] Table is not found", true)
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
next unless target_sheet?(sheet)
|
56
|
+
|
57
|
+
dist_file = File.join(HeartSeed::Helper.seed_dir, "#{sheet}.yml")
|
58
|
+
fixtures = HeartSeed::Converter.convert_to_yml(source_file: file, source_sheet: sheet, dist_file: dist_file)
|
59
|
+
if fixtures
|
60
|
+
ActiveRecord::Migration.say("[#{sheet}] Create seed: #{dist_file}", true)
|
61
|
+
else
|
62
|
+
ActiveRecord::Migration.say(" [#{sheet}] Sheet is empty", true)
|
63
|
+
end
|
50
64
|
end
|
51
65
|
end
|
52
66
|
end
|
53
67
|
end
|
54
68
|
|
69
|
+
namespace :db do
|
70
|
+
desc "Load the seed data from db/seeds/*.yml (options: TABLES=table1,table2 CATALOGS=catalog1,catalog2)"
|
71
|
+
task :seed => :environment do
|
72
|
+
HeartSeed::DbSeed.import_all
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
55
76
|
private
|
56
77
|
def target_file?(file)
|
57
78
|
return true if ENV["FILES"].blank?
|
@@ -65,9 +86,9 @@ YAML
|
|
65
86
|
ENV["SHEETS"].split(",").include?(sheet)
|
66
87
|
end
|
67
88
|
|
68
|
-
def create_file(file, str)
|
89
|
+
def create_file(file, str = nil)
|
69
90
|
open(file, "w") do |out|
|
70
|
-
out.write(str)
|
91
|
+
out.write(str) if str
|
71
92
|
end
|
72
93
|
|
73
94
|
puts "create: #{file}"
|
data/lib/heart_seed/version.rb
CHANGED
data/spec/data/articles.xls
CHANGED
Binary file
|
@@ -56,4 +56,20 @@ YAML
|
|
56
56
|
|
57
57
|
it{ should == ["articles", "Sheet2"] }
|
58
58
|
end
|
59
|
+
|
60
|
+
describe "#select_left_of_blank" do
|
61
|
+
subject{ HeartSeed::Converter.select_left_of_blank(array) }
|
62
|
+
|
63
|
+
context "When not include blank cell" do
|
64
|
+
let(:array){ ["id", "name"] }
|
65
|
+
|
66
|
+
it{ should eq ["id", "name"] }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "When include blank cell" do
|
70
|
+
let(:array){ ["id", "name", "", "this is not table column"] }
|
71
|
+
|
72
|
+
it{ should eq ["id", "name"] }
|
73
|
+
end
|
74
|
+
end
|
59
75
|
end
|
@@ -9,9 +9,16 @@ describe HeartSeed::DbSeed do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe "#import_all" do
|
12
|
-
subject{ HeartSeed::DbSeed.import_all(seed_dir: seed_dir, tables: tables) }
|
12
|
+
subject{ HeartSeed::DbSeed.import_all(seed_dir: seed_dir, tables: tables, catalogs: catalogs) }
|
13
13
|
|
14
14
|
let(:seed_dir){ FIXTURE_DIR }
|
15
|
+
let(:tables) { [] }
|
16
|
+
let(:catalogs){ [] }
|
17
|
+
|
18
|
+
before do
|
19
|
+
# FIXME can not clear if using `DatabaseRewinder.clean`
|
20
|
+
DatabaseRewinder.clean_all
|
21
|
+
end
|
15
22
|
|
16
23
|
after do
|
17
24
|
# FIXME can not clear if using `DatabaseRewinder.clean`
|
@@ -19,8 +26,6 @@ describe HeartSeed::DbSeed do
|
|
19
26
|
end
|
20
27
|
|
21
28
|
context "When empty tables" do
|
22
|
-
let(:tables) { [] }
|
23
|
-
|
24
29
|
it{ expect{ subject }.to change(Article, :count).by(2) }
|
25
30
|
it{ expect{ subject }.to change(Comment, :count).by(2) }
|
26
31
|
it{ expect{ subject }.to change(Like , :count).by(1) }
|
@@ -33,10 +38,24 @@ describe HeartSeed::DbSeed do
|
|
33
38
|
it{ expect{ subject }.to change(Comment, :count).by(0) }
|
34
39
|
it{ expect{ subject }.to change(Like , :count).by(0) }
|
35
40
|
end
|
41
|
+
|
42
|
+
context "When specify catalogs" do
|
43
|
+
let(:catalogs){ ["article"] }
|
44
|
+
|
45
|
+
before do
|
46
|
+
allow(HeartSeed::Helper).to receive(:catalogs){
|
47
|
+
{ "article" => ["articles", "likes"] }
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it{ expect{ subject }.to change(Article, :count).by(2) }
|
52
|
+
it{ expect{ subject }.to change(Comment, :count).by(0) }
|
53
|
+
it{ expect{ subject }.to change(Like , :count).by(1) }
|
54
|
+
end
|
36
55
|
end
|
37
56
|
|
38
|
-
describe "#
|
39
|
-
subject{ HeartSeed::DbSeed.
|
57
|
+
describe "#parse_string_or_array_arg" do
|
58
|
+
subject{ HeartSeed::DbSeed.parse_string_or_array_arg(tables) }
|
40
59
|
|
41
60
|
where(:tables, :expected) do
|
42
61
|
[
|
@@ -51,4 +70,32 @@ describe HeartSeed::DbSeed do
|
|
51
70
|
it{ should eq expected }
|
52
71
|
end
|
53
72
|
end
|
73
|
+
|
74
|
+
describe "#parse_arg_catalogs" do
|
75
|
+
subject{ HeartSeed::DbSeed.parse_arg_catalogs(catalogs) }
|
76
|
+
|
77
|
+
before do
|
78
|
+
allow(HeartSeed::Helper).to receive(:catalogs){
|
79
|
+
{
|
80
|
+
"article" => ["articles", "likes"],
|
81
|
+
"user" => ["users", "user_profiles"],
|
82
|
+
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
where(:catalogs, :expected) do
|
87
|
+
[
|
88
|
+
[nil , []],
|
89
|
+
["" , []],
|
90
|
+
["article" , %w(articles likes)],
|
91
|
+
["article,user" , %w(articles likes users user_profiles)],
|
92
|
+
[%w(article) , %w(articles likes)],
|
93
|
+
[%w(article user) , %w(articles likes users user_profiles)],
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
with_them do
|
98
|
+
it{ should eq expected }
|
99
|
+
end
|
100
|
+
end
|
54
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heart_seed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sue445
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -298,6 +298,7 @@ files:
|
|
298
298
|
- lib/heart_seed/db_seed.rb
|
299
299
|
- lib/heart_seed/helper.rb
|
300
300
|
- lib/heart_seed/railtie.rb
|
301
|
+
- lib/heart_seed/tasks.rb
|
301
302
|
- lib/heart_seed/tasks/heart_seed.rake
|
302
303
|
- lib/heart_seed/version.rb
|
303
304
|
- spec/data/articles.xls
|