hammer_cli_katello 0.1.3 → 0.2.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/lib/hammer_cli_katello.rb +1 -0
- data/lib/hammer_cli_katello/content_view.rb +1 -1
- data/lib/hammer_cli_katello/filter.rb +19 -0
- data/lib/hammer_cli_katello/filter_rule.rb +0 -10
- data/lib/hammer_cli_katello/host_extensions.rb +2 -0
- data/lib/hammer_cli_katello/id_name_options_validator.rb +73 -0
- data/lib/hammer_cli_katello/package.rb +2 -0
- data/lib/hammer_cli_katello/repository.rb +17 -5
- data/lib/hammer_cli_katello/version.rb +1 -1
- data/test/functional/content_view/create_test.rb +0 -3
- data/test/functional/content_view/filter/delete_test.rb +93 -0
- data/test/functional/content_view/filter/info_test.rb +92 -0
- data/test/functional/content_view/filter/list_test.rb +98 -0
- data/test/functional/content_view/filter/update_test.rb +93 -0
- data/test/functional/filter_rule/create_test.rb +0 -79
- data/test/functional/host/extensions/data/host.json +4 -2
- data/test/functional/host/extensions/info_test.rb +3 -1
- data/test/functional/lifecycle_environment/create_test.rb +14 -0
- data/test/functional/lifecycle_environment/list_test.rb +38 -0
- data/test/functional/lifecycle_environment/update_test.rb +14 -0
- data/test/functional/organization/organization_helpers.rb +2 -2
- data/test/functional/package/list_test.rb +48 -0
- data/test/functional/repository/delete_test.rb +101 -0
- data/test/functional/repository/upload_test.rb +43 -0
- data/test/unit/id_name_options_validator_test.rb +96 -0
- metadata +26 -13
- data/test/functional/filter_rule/delete_test.rb +0 -104
- data/test/functional/filter_rule/info_test.rb +0 -104
- data/test/functional/filter_rule/list_test.rb +0 -91
- data/test/functional/filter_rule/update_test.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ee67377eb8631f27bc3ac6fb8893aa4f3e19cd6
|
4
|
+
data.tar.gz: 3aa751e158e61a5e65854d7423c280ff9be55493
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8af86a66650da765757af0c2ea262f08bbdeadfd27f88e5bb8dcac35f9318f7badfd7885bb0d844010d860bf636de433ffd032402de81e3774d635e4b6f86d76
|
7
|
+
data.tar.gz: b7db6c9f52337875421b12022cb1a2ea4dddfce2e88707c5035636395cfa8cfbe70ad1ef71430d2bf0dc6446e0cd22cfaf5eacfba16e6d58ec903125a80d78c5
|
data/lib/hammer_cli_katello.rb
CHANGED
@@ -25,6 +25,7 @@ module HammerCLIKatello
|
|
25
25
|
require "hammer_cli_katello/version"
|
26
26
|
require 'hammer_cli_katello/id_resolver'
|
27
27
|
require 'hammer_cli_katello/capsule'
|
28
|
+
require 'hammer_cli_katello/id_name_options_validator'
|
28
29
|
|
29
30
|
# commands
|
30
31
|
HammerCLI::MainCommand.lazy_subcommand("activation-key", _("Manipulate activation keys."),
|
@@ -207,7 +207,7 @@ module HammerCLIKatello
|
|
207
207
|
success_message _("Content view objects are being removed task %{id}")
|
208
208
|
failure_message _("Could not remove objects from content view")
|
209
209
|
|
210
|
-
build_options :without => %w(content_view_version_ids
|
210
|
+
build_options :without => %w(content_view_version_ids environment_ids)
|
211
211
|
end
|
212
212
|
|
213
213
|
class AddContentViewVersionCommand < HammerCLIKatello::AddAssociatedCommand
|
@@ -7,6 +7,9 @@ module HammerCLIKatello
|
|
7
7
|
desc 'View and manage filters'
|
8
8
|
|
9
9
|
class ListCommand < HammerCLIKatello::ListCommand
|
10
|
+
include OrganizationOptions
|
11
|
+
extend IdNameOptionsValidator
|
12
|
+
|
10
13
|
output do
|
11
14
|
field :id, _("Filter ID")
|
12
15
|
field :name, _("Name")
|
@@ -15,9 +18,13 @@ module HammerCLIKatello
|
|
15
18
|
end
|
16
19
|
|
17
20
|
build_options
|
21
|
+
validate_id_or_name_with_parent :content_view
|
18
22
|
end
|
19
23
|
|
20
24
|
class InfoCommand < HammerCLIKatello::InfoCommand
|
25
|
+
include OrganizationOptions
|
26
|
+
extend IdNameOptionsValidator
|
27
|
+
|
21
28
|
output do
|
22
29
|
field :id, _("Filter ID")
|
23
30
|
field :name, _("Name")
|
@@ -47,6 +54,8 @@ module HammerCLIKatello
|
|
47
54
|
end
|
48
55
|
|
49
56
|
build_options :without => [:content_view_id]
|
57
|
+
validate_id_or_name_with_parent parent: :content_view
|
58
|
+
validate_id_or_name_with_parent :content_view, required: false
|
50
59
|
end
|
51
60
|
|
52
61
|
class CreateCommand < HammerCLIKatello::CreateCommand
|
@@ -68,17 +77,27 @@ module HammerCLIKatello
|
|
68
77
|
end
|
69
78
|
|
70
79
|
class UpdateCommand < HammerCLIKatello::UpdateCommand
|
80
|
+
include OrganizationOptions
|
81
|
+
extend IdNameOptionsValidator
|
82
|
+
|
71
83
|
success_message _("Filter updated")
|
72
84
|
failure_message _("Could not update the filter")
|
73
85
|
|
74
86
|
build_options :without => [:content_view_id]
|
87
|
+
validate_id_or_name_with_parent parent: :content_view
|
88
|
+
validate_id_or_name_with_parent :content_view, required: false
|
75
89
|
end
|
76
90
|
|
77
91
|
class DeleteCommand < HammerCLIKatello::DeleteCommand
|
92
|
+
include OrganizationOptions
|
93
|
+
extend IdNameOptionsValidator
|
94
|
+
|
78
95
|
success_message _("Filter deleted")
|
79
96
|
failure_message _("Could not delete the filter")
|
80
97
|
|
81
98
|
build_options :without => [:content_view_id]
|
99
|
+
validate_id_or_name_with_parent parent: :content_view
|
100
|
+
validate_id_or_name_with_parent :content_view, required: false
|
82
101
|
end
|
83
102
|
|
84
103
|
HammerCLIKatello::AssociatingCommands::Repository.extend_command(self)
|
@@ -5,8 +5,6 @@ module HammerCLIKatello
|
|
5
5
|
desc 'View and manage filter rules'
|
6
6
|
|
7
7
|
class ListCommand < HammerCLIKatello::ListCommand
|
8
|
-
include OrganizationOptions
|
9
|
-
|
10
8
|
output do
|
11
9
|
field :id, _("Rule ID")
|
12
10
|
field :content_view_filter_id, _("Filter ID")
|
@@ -24,8 +22,6 @@ module HammerCLIKatello
|
|
24
22
|
end
|
25
23
|
|
26
24
|
class InfoCommand < HammerCLIKatello::InfoCommand
|
27
|
-
include OrganizationOptions
|
28
|
-
|
29
25
|
output do
|
30
26
|
field :id, _("Rule ID")
|
31
27
|
field :content_view_filter_id, _("Filter ID")
|
@@ -47,8 +43,6 @@ module HammerCLIKatello
|
|
47
43
|
end
|
48
44
|
|
49
45
|
class CreateCommand < HammerCLIKatello::CreateCommand
|
50
|
-
include OrganizationOptions
|
51
|
-
|
52
46
|
success_message _("Filter rule created")
|
53
47
|
failure_message _("Could not create the filter rule")
|
54
48
|
|
@@ -64,8 +58,6 @@ module HammerCLIKatello
|
|
64
58
|
end
|
65
59
|
|
66
60
|
class UpdateCommand < HammerCLIKatello::UpdateCommand
|
67
|
-
include OrganizationOptions
|
68
|
-
|
69
61
|
success_message _("Filter rule updated")
|
70
62
|
failure_message _("Could not update the filter rule")
|
71
63
|
|
@@ -73,8 +65,6 @@ module HammerCLIKatello
|
|
73
65
|
end
|
74
66
|
|
75
67
|
class DeleteCommand < HammerCLIKatello::DeleteCommand
|
76
|
-
include OrganizationOptions
|
77
|
-
|
78
68
|
success_message _("Filter rule deleted")
|
79
69
|
failure_message _("Could not delete the filter rule")
|
80
70
|
|
@@ -21,6 +21,8 @@ module HammerCLIKatello
|
|
21
21
|
from :content_facet_attributes do
|
22
22
|
field :content_view_name, _('Content View')
|
23
23
|
field :lifecycle_environment_name, _('Lifecycle Environment')
|
24
|
+
field :applicable_package_count, _('Applicable Packages')
|
25
|
+
field :upgradable_package_count, _('Upgradable Packages')
|
24
26
|
|
25
27
|
label _('Applicable Errata') do
|
26
28
|
from :errata_counts do
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module HammerCLIKatello
|
2
|
+
module IdNameOptionsValidator
|
3
|
+
# This is a method that requires:
|
4
|
+
# 1. either name or id has been supplied
|
5
|
+
# 2. if name is supplied, parent id/name/etc is also required
|
6
|
+
#
|
7
|
+
# Normally the parent will be organization as with products, content views,
|
8
|
+
# etc but this could also apply to repos for example whose names are unique
|
9
|
+
# per product
|
10
|
+
#
|
11
|
+
# Some examples:
|
12
|
+
#
|
13
|
+
# validate_id_or_name_with_parent
|
14
|
+
# validate_id_or_name_with_parent :content_view
|
15
|
+
# validate_id_or_name_with_parent :repository, parent: 'product', required: false
|
16
|
+
# validate_id_or_name_with_parent :content_view, parent: {organization: ['id', 'name']}
|
17
|
+
def validate_id_or_name_with_parent(record_name = nil, parent: 'organization', required: true)
|
18
|
+
child_options = IdNameOptionsValidator.build_child_options(record_name)
|
19
|
+
parent_options = IdNameOptionsValidator.build_parent_options(parent)
|
20
|
+
|
21
|
+
validate_options do
|
22
|
+
any(*child_options).required if required
|
23
|
+
|
24
|
+
if (name_option = child_options.detect { |opt| opt.end_with?('_name') }) &&
|
25
|
+
option(name_option).exist?
|
26
|
+
any(*parent_options).required
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method simply checks that either id or name is supplied
|
32
|
+
#
|
33
|
+
# Some examples:
|
34
|
+
#
|
35
|
+
# # checks for a --id or --name option
|
36
|
+
# validate_id_or_name
|
37
|
+
# # checks for --content-view-id or --content-view-name
|
38
|
+
# validate_id_or_name :content_view
|
39
|
+
def validate_id_or_name(record_name = nil)
|
40
|
+
child_options = IdNameOptionsValidator.build_child_options(record_name)
|
41
|
+
|
42
|
+
validate_options do
|
43
|
+
any(*child_options).required
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.build_child_options(record_name)
|
48
|
+
if record_name.nil?
|
49
|
+
%w(option_id option_name)
|
50
|
+
else
|
51
|
+
IdNameOptionsValidator.build_options(record_name, %w(id name))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.build_parent_options(parent)
|
56
|
+
if parent.is_a?(String) || parent.is_a?(Symbol)
|
57
|
+
opts = parent.to_s == 'organization' ? %w(id name label) : %w(id name)
|
58
|
+
elsif parent.is_a?(Hash)
|
59
|
+
parent, opts = parent.first.to_a
|
60
|
+
else
|
61
|
+
raise "Unkown parent class: #{parent.class} for #{parent}"
|
62
|
+
end
|
63
|
+
|
64
|
+
IdNameOptionsValidator.build_options(parent, opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.build_options(record_name, options)
|
68
|
+
options.map do |opt|
|
69
|
+
"option_#{record_name}_#{opt}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -172,6 +172,7 @@ module HammerCLIKatello
|
|
172
172
|
|
173
173
|
class DeleteCommand < HammerCLIKatello::DeleteCommand
|
174
174
|
include RepositoryScopedToProduct
|
175
|
+
include OrganizationOptions
|
175
176
|
|
176
177
|
success_message _("Repository deleted")
|
177
178
|
failure_message _("Could not delete the Repository")
|
@@ -194,8 +195,10 @@ module HammerCLIKatello
|
|
194
195
|
|
195
196
|
if File.directory?(fullpath)
|
196
197
|
Dir["#{fullpath}/*"]
|
197
|
-
|
198
|
+
elsif File.exist?(fullpath)
|
198
199
|
[fullpath]
|
200
|
+
else
|
201
|
+
Dir[fullpath]
|
199
202
|
end
|
200
203
|
end
|
201
204
|
end
|
@@ -206,7 +209,14 @@ module HammerCLIKatello
|
|
206
209
|
|
207
210
|
def execute
|
208
211
|
@failure = false
|
209
|
-
|
212
|
+
files = option_content
|
213
|
+
|
214
|
+
if files.length.zero?
|
215
|
+
output.print_error _("Could not find any files matching PATH")
|
216
|
+
return HammerCLI::EX_NOINPUT
|
217
|
+
end
|
218
|
+
|
219
|
+
files.each do |file_path|
|
210
220
|
File.open(file_path, 'rb') { |file| upload_file file }
|
211
221
|
end
|
212
222
|
|
@@ -223,7 +233,9 @@ module HammerCLIKatello
|
|
223
233
|
build_options(:without => [:content]) do |o|
|
224
234
|
o.expand.including(:products, :organizations)
|
225
235
|
end
|
226
|
-
option "--path", "PATH", _("Upload file
|
236
|
+
option "--path", "PATH", _("Upload file, directory of files, or glob of files " \
|
237
|
+
"as content for a repository.\n" \
|
238
|
+
"Globs must be escaped by single or double quotes."),
|
227
239
|
:attribute_name => :option_content,
|
228
240
|
:required => true, :format => BinaryPath.new
|
229
241
|
|
@@ -251,8 +263,8 @@ module HammerCLIKatello
|
|
251
263
|
print_message _("Successfully uploaded file '%s'.") % filename
|
252
264
|
rescue
|
253
265
|
@failure = true
|
254
|
-
|
255
|
-
|
266
|
+
output.print_error _("Failed to upload file '%s' to repository. Please check "\
|
267
|
+
"the file and try again.") % filename
|
256
268
|
ensure
|
257
269
|
content_upload_resource.call(:destroy, :repository_id => get_identifier, :id => upload_id)
|
258
270
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../../test_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), '../content_view_helpers')
|
3
|
+
require File.join(File.dirname(__FILE__), '../../organization/organization_helpers')
|
4
|
+
|
5
|
+
module HammerCLIForeman
|
6
|
+
describe DeleteCommand do
|
7
|
+
include ContentViewHelpers
|
8
|
+
include OrganizationHelpers
|
9
|
+
|
10
|
+
before do
|
11
|
+
@cmd = %w(content-view filter delete)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'accepts filter id' do
|
15
|
+
params = ['--id=1']
|
16
|
+
|
17
|
+
api_expects(:content_view_filters, :destroy) do |par|
|
18
|
+
par['id'] == '1'
|
19
|
+
end
|
20
|
+
|
21
|
+
run_cmd(@cmd + params)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'accepts filter name, content view name, and org name' do
|
25
|
+
params = ['--name=scanner', '--content-view=darkly', '--organization=pkd']
|
26
|
+
|
27
|
+
expect_organization_search('pkd', 1)
|
28
|
+
expect_content_view_search(1, 'darkly', 1)
|
29
|
+
expect_content_view_search(1, 'darkly', 1) # redmine #15930
|
30
|
+
|
31
|
+
ex = api_expects(:content_view_filters, :index, 'Content view filters list') do |par|
|
32
|
+
par['content_view_id'] == 1 && par['name'] == 'scanner'
|
33
|
+
end
|
34
|
+
ex.returns(index_response([{'id' => '1'}]))
|
35
|
+
|
36
|
+
api_expects(:content_view_filters, :destroy) do |par|
|
37
|
+
par['id'] == '1'
|
38
|
+
end
|
39
|
+
|
40
|
+
run_cmd(@cmd + params)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'accepts filter name, content view name, and org label' do
|
44
|
+
params = ['--name=scanner', '--content-view=darkly', '--organization-label=pkd']
|
45
|
+
|
46
|
+
expect_organization_search('pkd', 1, field: 'label')
|
47
|
+
expect_content_view_search(1, 'darkly', 1)
|
48
|
+
expect_content_view_search(1, 'darkly', 1) # redmine #15930
|
49
|
+
|
50
|
+
ex = api_expects(:content_view_filters, :index, 'Content view filters list') do |par|
|
51
|
+
par['content_view_id'] == 1 && par['name'] == 'scanner'
|
52
|
+
end
|
53
|
+
ex.returns(index_response([{'id' => '1'}]))
|
54
|
+
|
55
|
+
api_expects(:content_view_filters, :destroy) do |par|
|
56
|
+
par['id'] == '1'
|
57
|
+
end
|
58
|
+
|
59
|
+
run_cmd(@cmd + params)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'accepts filter name, content view name, and org id' do
|
63
|
+
params = ['--name=scanner', '--content-view=darkly', '--organization-id=1']
|
64
|
+
|
65
|
+
expect_content_view_search('1', 'darkly', 1)
|
66
|
+
expect_content_view_search('1', 'darkly', 1) # redmine #15930
|
67
|
+
|
68
|
+
ex = api_expects(:content_view_filters, :index, 'Content view filters list') do |par|
|
69
|
+
par['content_view_id'] == 1 && par['name'] == 'scanner'
|
70
|
+
end
|
71
|
+
ex.returns(index_response([{'id' => '1'}]))
|
72
|
+
|
73
|
+
api_expects(:content_view_filters, :destroy) do |par|
|
74
|
+
par['id'] == '1'
|
75
|
+
end
|
76
|
+
|
77
|
+
run_cmd(@cmd + params)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'requires organization name or id if content view name is supplied' do
|
81
|
+
params = ["--name=high-castle", "--content-view=grasshopper"]
|
82
|
+
expected_result = usage_error_result(
|
83
|
+
@cmd,
|
84
|
+
'At least one of options --organization-id, --organization, --organization-label ' \
|
85
|
+
'is required',
|
86
|
+
'Could not delete the filter'
|
87
|
+
)
|
88
|
+
api_expects_no_call
|
89
|
+
result = run_cmd(@cmd + params)
|
90
|
+
assert_cmd(expected_result, result)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../../test_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), '../content_view_helpers')
|
3
|
+
require File.join(File.dirname(__FILE__), '../../organization/organization_helpers')
|
4
|
+
|
5
|
+
module HammerCLIForeman
|
6
|
+
describe InfoCommand do
|
7
|
+
include ContentViewHelpers
|
8
|
+
include OrganizationHelpers
|
9
|
+
|
10
|
+
before do
|
11
|
+
@cmd = %w(content-view filter info)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'accepts filter id' do
|
15
|
+
params = ['--id=1']
|
16
|
+
|
17
|
+
api_expects(:content_view_filters, :show) do |par|
|
18
|
+
par['id'] == '1'
|
19
|
+
end
|
20
|
+
|
21
|
+
run_cmd(@cmd + params)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'accepts filter name, content view name, and org name' do
|
25
|
+
params = ['--name=scanner', '--content-view=darkly', '--organization=pkd']
|
26
|
+
|
27
|
+
expect_organization_search('pkd', 1)
|
28
|
+
expect_content_view_search(1, 'darkly', 1)
|
29
|
+
expect_content_view_search(1, 'darkly', 1) # redmine #15930
|
30
|
+
|
31
|
+
ex = api_expects(:content_view_filters, :index, 'Content view filters list') do |par|
|
32
|
+
par['content_view_id'] == 1 && par['name'] == 'scanner'
|
33
|
+
end
|
34
|
+
ex.returns(index_response([{'id' => '1'}]))
|
35
|
+
|
36
|
+
api_expects(:content_view_filters, :show) do |par|
|
37
|
+
par['id'] == '1'
|
38
|
+
end
|
39
|
+
|
40
|
+
run_cmd(@cmd + params)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'accepts filter name, content view name, and org label' do
|
44
|
+
params = ['--name=scanner', '--content-view=darkly', '--organization-label=pkd']
|
45
|
+
|
46
|
+
expect_organization_search('pkd', 1, field: 'label')
|
47
|
+
expect_content_view_search(1, 'darkly', 1)
|
48
|
+
expect_content_view_search(1, 'darkly', 1) # redmine #15930
|
49
|
+
|
50
|
+
ex = api_expects(:content_view_filters, :index, 'Content view filters list') do |par|
|
51
|
+
par['content_view_id'] == 1 && par['name'] == 'scanner'
|
52
|
+
end
|
53
|
+
ex.returns(index_response([{'id' => '1'}]))
|
54
|
+
|
55
|
+
api_expects(:content_view_filters, :show) do |par|
|
56
|
+
par['id'] == '1'
|
57
|
+
end
|
58
|
+
|
59
|
+
run_cmd(@cmd + params)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'accepts filter name, content view name, and org id' do
|
63
|
+
params = ['--name=scanner', '--content-view=darkly', '--organization-id=1']
|
64
|
+
|
65
|
+
expect_content_view_search('1', 'darkly', 1)
|
66
|
+
expect_content_view_search('1', 'darkly', 1) # redmine #15930
|
67
|
+
|
68
|
+
ex = api_expects(:content_view_filters, :index, 'Content view filters list') do |par|
|
69
|
+
par['content_view_id'] == 1 && par['name'] == 'scanner'
|
70
|
+
end
|
71
|
+
ex.returns(index_response([{'id' => '1'}]))
|
72
|
+
|
73
|
+
api_expects(:content_view_filters, :show) do |par|
|
74
|
+
par['id'] == '1'
|
75
|
+
end
|
76
|
+
|
77
|
+
run_cmd(@cmd + params)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'requires organization name or id if content view name is supplied' do
|
81
|
+
params = ["--name=high-castle", "--content-view=grasshopper"]
|
82
|
+
expected_result = usage_error_result(
|
83
|
+
@cmd,
|
84
|
+
'At least one of options --organization-id, --organization, --organization-label ' \
|
85
|
+
'is required'
|
86
|
+
)
|
87
|
+
api_expects_no_call
|
88
|
+
result = run_cmd(@cmd + params)
|
89
|
+
assert_cmd(expected_result, result)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|