MrMurano 1.11.1 → 1.11.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +12 -0
- data/README.markdown +1 -1
- data/Rakefile +3 -0
- data/TODO.taskpaper +9 -2
- data/bin/mr +8 -1
- data/lib/MrMurano/Account.rb +6 -6
- data/lib/MrMurano/Config.rb +2 -0
- data/lib/MrMurano/Product-Resources.rb +2 -0
- data/lib/MrMurano/Solution-Endpoint.rb +1 -1
- data/lib/MrMurano/SyncUpDown.rb +3 -0
- data/lib/MrMurano/commands/config.rb +2 -5
- data/lib/MrMurano/commands/init.rb +6 -0
- data/lib/MrMurano/commands/tsdb.rb +20 -0
- data/lib/MrMurano/verbosing.rb +5 -0
- data/lib/MrMurano/version.rb +1 -1
- data/spec/Account_spec.rb +7 -7
- data/spec/ProductResources_spec.rb +2 -2
- data/spec/cmd_init_spec.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7952441f550b0a314943de0c8bf2ced3e496b406
|
4
|
+
data.tar.gz: 1e10b41f47a6fbb668bd75c78593094f44835923
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9271fcc60621b98bdf94bf59392c473a952323eaf8d370d43542e5a88a6c91cc48d07e706a741c659c97f09c5d2bf509925ed158bf860f071189dac8fdc88a31
|
7
|
+
data.tar.gz: 98b2e10a2144ad74e0a6c55f2d7d05d773c2a31037a3ae11fc44a5f7c19c1e3362ac2ef3b28cfa98c590414fb7d0b358f4886c275dba1d411fb5e239a22cf08f
|
data/.travis.yml
CHANGED
@@ -3,3 +3,15 @@ rvm:
|
|
3
3
|
- 2.0
|
4
4
|
- 2.2
|
5
5
|
|
6
|
+
before_deploy: "rake build"
|
7
|
+
deploy:
|
8
|
+
provider: releases
|
9
|
+
api_key:
|
10
|
+
secure: oB28IBHoSQVgP0PtP1uc+cRMBNhqHCCfCCV+sLNspjCGJeIjhZ4lmLcMYSy8l0+TBWxmXl6VIQd6GbyEezbB5daA4+pxY/An0OpM649t29+QuMCK8iXrD8/HXfaUzY2CP5RP47GoC/5xyCwqv3EEUjFPEGhUb5hYc74kfe3nwBmhKliHFQpZvuDE0EHWTgcEOep+kf7O+3cVf7vJVddz+Vj05t1s6VCwhBQsbUvOV4/Jv7nhHa7q0UGoY9M7sTNpwgawFeMwqvtXxl8sVbiHPLm+u6vikSbVnSdyi7wlVAoe+DSkzmnVHYzMd/t1Io7kEiLEAyDyrlJPJYS+XTMqxQ/KUDJNhf/N2IJMW2vPA+6TjpxCqcdY4QwkLNb7JfFWgQb9X4UksAEQU24W6110+zPUGmbg37eM80OrsadTWy65IQbLJ0sFFhm8vu/e2i/3LadYscw6A0Tyc1JXgGfGKd0nqyAyKGLcSBw1+dwwzkyKNHWFAaLy5hAEKv2qZdVNrTtcLhJtuxv8VUuevd3gfT6FLOIN/XAyg71mSvC2BA12vzM6KkTPNIaSmUITDgEplT5cs5h8PZ4eRghpmnT2EA3k0O3p14Ng3eeACMImJYQRKWmU/yVWVo69VVH0GRfm5SWb6xlDLFXt/Mii1MPx9ruhC232wdRACoep18w1qUY=
|
11
|
+
file: "pkg/MrMurano-*.gem"
|
12
|
+
skip_cleanup: true
|
13
|
+
on:
|
14
|
+
repo: tadpol/MrMurano
|
15
|
+
tags: true
|
16
|
+
rvm: 2.2
|
17
|
+
|
data/README.markdown
CHANGED
@@ -29,7 +29,7 @@ Then deploy with `mr syncup`
|
|
29
29
|
There are a few steps and pieces to getting a solution with a product up and
|
30
30
|
running in Murano. Here is the list.
|
31
31
|
|
32
|
-
- Pick a
|
32
|
+
- Pick a business: `mr account --business`
|
33
33
|
- Set it: `mr config business.id ZZZZZZZZZ`
|
34
34
|
- Create a product: `mr product create myawesomeproduct`
|
35
35
|
- Save the result: `mr config product.id YYYYYYYYY`
|
data/Rakefile
CHANGED
data/TODO.taskpaper
CHANGED
@@ -28,7 +28,7 @@ Endpoints:
|
|
28
28
|
- Add directory support like in modules @done(2016-07-26)
|
29
29
|
|
30
30
|
Files:
|
31
|
-
- Add ignore patterns to config
|
31
|
+
- Add ignore patterns to config @done(2016-12-20)
|
32
32
|
- Switch to mime-types v3 @pri(low)
|
33
33
|
- Figure out how to make the hexed-sha checksum faster. @done(2016-09-23)
|
34
34
|
- Fix upload. @done(2016-08-01)
|
@@ -65,7 +65,7 @@ Product:
|
|
65
65
|
- Need to add way to set the product ID on a device eventhandler. @done(2016-08-01)
|
66
66
|
|
67
67
|
Service Device:
|
68
|
-
- When listing and
|
68
|
+
- When listing and business.id is missing, gracefully fall back to --idonly @done(2016-09-12)
|
69
69
|
|
70
70
|
Config:
|
71
71
|
- Add config tool.default_sync to set which things sync{up,down} by default
|
@@ -73,6 +73,13 @@ Config:
|
|
73
73
|
- Store passwords in system Keychain on system that have a Keychain.
|
74
74
|
mac OS does, various Linux desktops have a couple differnet ones. Not sure about
|
75
75
|
Windows.
|
76
|
+
MacOS: https://github.com/xli/mac-keychain
|
77
|
+
Windows8+: https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh465069.aspx
|
78
|
+
Linux: https://en.wikipedia.org/wiki/GNOME_Keyring
|
79
|
+
https://en.wikipedia.org/wiki/KWallet
|
80
|
+
|
81
|
+
Plus things like 1Password, LastPass, KeePass, and others.
|
82
|
+
|
76
83
|
- Add ENV['MR_CONFIGFILE'] path to file to load like --configfile @done(2016-09-22)
|
77
84
|
- Maybe add dotenv support. @done(2016-09-22)
|
78
85
|
- Think about adding dev,staging,prod system; how would that work? @done(2016-09-16)
|
data/bin/mr
CHANGED
@@ -31,8 +31,15 @@ global_option('-C', '--configfile FILE', %{Load additional configuration file})
|
|
31
31
|
}
|
32
32
|
global_option('-c', '--config KEY=VALUE', %{Set a single config key}) {|param|
|
33
33
|
key, value = param.split('=', 2)
|
34
|
+
# a=b :> ["a","b"]
|
35
|
+
# a= :> ["a",""]
|
36
|
+
# a :> ["a"]
|
34
37
|
raise "Bad config '#{param}'" if key.nil?
|
35
|
-
|
38
|
+
if value.nil? then
|
39
|
+
$cfg[key] = 'true'
|
40
|
+
else
|
41
|
+
$cfg[key] = value
|
42
|
+
end
|
36
43
|
}
|
37
44
|
|
38
45
|
default_command :help
|
data/lib/MrMurano/Account.rb
CHANGED
@@ -119,35 +119,35 @@ module MrMurano
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def products
|
122
|
-
raise "Missing
|
122
|
+
raise "Missing Business ID" if $cfg['business.id'].nil?
|
123
123
|
get('business/' + $cfg['business.id'] + '/product/')
|
124
124
|
end
|
125
125
|
|
126
126
|
## Create a new product in the current business
|
127
127
|
def new_product(name, type='onepModel')
|
128
|
-
raise "Missing
|
128
|
+
raise "Missing Business ID" if $cfg['business.id'].nil?
|
129
129
|
post('business/' + $cfg['business.id'] + '/product/', {:label=>name, :type=>type})
|
130
130
|
end
|
131
131
|
|
132
132
|
def delete_product(modelId)
|
133
|
-
raise "Missing
|
133
|
+
raise "Missing Business ID" if $cfg['business.id'].nil?
|
134
134
|
delete('business/' + $cfg['business.id'] + '/product/' + modelId)
|
135
135
|
end
|
136
136
|
|
137
137
|
def solutions
|
138
|
-
raise "Missing
|
138
|
+
raise "Missing Business ID" if $cfg['business.id'].nil?
|
139
139
|
get('business/' + $cfg['business.id'] + '/solution/')
|
140
140
|
end
|
141
141
|
|
142
142
|
## Create a new solution
|
143
143
|
def new_solution(name, type='dataApi')
|
144
|
-
raise "Missing
|
144
|
+
raise "Missing Business ID" if $cfg['business.id'].nil?
|
145
145
|
raise "Solution name must be lowercase" if name.match(/[A-Z]/)
|
146
146
|
post('business/' + $cfg['business.id'] + '/solution/', {:label=>name, :type=>type})
|
147
147
|
end
|
148
148
|
|
149
149
|
def delete_solution(apiId)
|
150
|
-
raise "Missing
|
150
|
+
raise "Missing Business ID" if $cfg['business.id'].nil?
|
151
151
|
delete('business/' + $cfg['business.id'] + '/solution/' + apiId)
|
152
152
|
end
|
153
153
|
|
data/lib/MrMurano/Config.rb
CHANGED
@@ -103,6 +103,8 @@ module MrMurano
|
|
103
103
|
set('modules.searchFor', '*.lua */*.lua', :defaults)
|
104
104
|
set('modules.ignoring', '*_test.lua *_spec.lua .*', :defaults)
|
105
105
|
|
106
|
+
set('product.spec', 'resources.yaml', :defaults)
|
107
|
+
|
106
108
|
set('diff.cmd', 'diff -u', :defaults)
|
107
109
|
end
|
108
110
|
|
@@ -132,6 +132,7 @@ module MrMurano
|
|
132
132
|
## Get a local list of items from the single file
|
133
133
|
def localitems(from)
|
134
134
|
from = Pathname.new(from) unless from.kind_of? Pathname
|
135
|
+
debug "#{self.class.to_s}: Getting local items from: #{from}"
|
135
136
|
if not from.exist? then
|
136
137
|
say_warning "Skipping missing #{from.to_s}"
|
137
138
|
return []
|
@@ -173,6 +174,7 @@ module MrMurano
|
|
173
174
|
end
|
174
175
|
here << item.reject{|k,v| k==:synckey or k==:rid}
|
175
176
|
here.map!{|i| Hash.transform_keys_to_strings(i)}
|
177
|
+
local.dirname.mkpath
|
176
178
|
local.open('wb') do |io|
|
177
179
|
io << {'resources'=>here}.to_yaml
|
178
180
|
end
|
data/lib/MrMurano/SyncUpDown.rb
CHANGED
@@ -240,6 +240,8 @@ module MrMurano
|
|
240
240
|
bitems = localitems(@locationbase + @location)
|
241
241
|
# use synckey for quicker merging.
|
242
242
|
bitems.each { |b| items[synckey(b)] = b }
|
243
|
+
else
|
244
|
+
warning "Skipping missing location #{@locationbase + @location}"
|
243
245
|
end
|
244
246
|
|
245
247
|
items.values
|
@@ -265,6 +267,7 @@ module MrMurano
|
|
265
267
|
# @param from Pathname: Directory of items to scan
|
266
268
|
# @return Array: of Hashes of item details
|
267
269
|
def localitems(from)
|
270
|
+
debug "#{self.class.to_s}: Getting local items from: #{from}"
|
268
271
|
searchIn = from.to_s
|
269
272
|
sf = searchFor.map{|i| ::File.join(searchIn, i)}
|
270
273
|
Dir[*sf].flatten.compact.reject do |p|
|
@@ -16,7 +16,6 @@ command :config do |c|
|
|
16
16
|
'mr config diff.cmd --unset'
|
17
17
|
|
18
18
|
|
19
|
-
c.option '--system', 'Use only the system config file. (/etc/mrmuranorc)'
|
20
19
|
c.option '--user', 'Use only the config file in $HOME (.mrmuranorc)'
|
21
20
|
c.option '--project', 'Use only the config file in the project (.mrmuranorc)'
|
22
21
|
c.option '--env', 'Use only the config file from $MR_CONFIGFILE'
|
@@ -32,12 +31,11 @@ command :config do |c|
|
|
32
31
|
elsif args.count == 0 then
|
33
32
|
say_error "Need a config key"
|
34
33
|
elsif args.count == 1 and not options.unset then
|
35
|
-
options.default :
|
34
|
+
options.default :user=>false, :project=>false,
|
36
35
|
:specified=>false, :env=>false
|
37
36
|
|
38
37
|
# For read, if no scopes, than all. Otherwise just those specified
|
39
38
|
scopes = []
|
40
|
-
scopes << :system if options.system
|
41
39
|
scopes << :user if options.user
|
42
40
|
scopes << :project if options.project
|
43
41
|
scopes << :env if options.env
|
@@ -47,11 +45,10 @@ command :config do |c|
|
|
47
45
|
say $cfg.get(args[0], scopes)
|
48
46
|
else
|
49
47
|
|
50
|
-
options.default :
|
48
|
+
options.default :user=>false, :project=>false,
|
51
49
|
:specified=>false, :env=>false
|
52
50
|
# For write, if scope is specified, only write to that scope.
|
53
51
|
scope = :project
|
54
|
-
scope = :system if options.system
|
55
52
|
scope = :user if options.user
|
56
53
|
scope = :project if options.project
|
57
54
|
scope = :env if options.env
|
@@ -12,6 +12,7 @@ command :init do |c|
|
|
12
12
|
c.action do |args, options|
|
13
13
|
options.default :force=>false, :mkdirs=>true
|
14
14
|
acc = MrMurano::Account.new
|
15
|
+
puts ''
|
15
16
|
|
16
17
|
if Pathname.new(Dir.pwd).realpath == Pathname.new(Dir.home).realpath then
|
17
18
|
acc.error "Cannot init a project in your HOME directory."
|
@@ -23,6 +24,8 @@ command :init do |c|
|
|
23
24
|
exit 0 unless y =~ /^n/i
|
24
25
|
end
|
25
26
|
|
27
|
+
say "Found project base directory at #{$cfg['location.base'].to_s}"
|
28
|
+
puts ''
|
26
29
|
|
27
30
|
# If they have never logged in, then asking for the business.id will also ask
|
28
31
|
# for their username and password.
|
@@ -101,6 +104,8 @@ command :init do |c|
|
|
101
104
|
say "Ok, In business ID: #{$cfg['business.id']} using Solution ID: #{$cfg['solution.id']} with Product ID: #{$cfg['product.id']}"
|
102
105
|
|
103
106
|
if options.mkdirs then
|
107
|
+
base = $cfg['location.base']
|
108
|
+
base = Pathname.new(base) unless base.kind_of? Pathname
|
104
109
|
%w{
|
105
110
|
location.files
|
106
111
|
location.endpoints
|
@@ -110,6 +115,7 @@ command :init do |c|
|
|
110
115
|
}.each do |cfgi|
|
111
116
|
path = $cfg[cfgi]
|
112
117
|
path = Pathname.new(path) unless path.kind_of? Pathname
|
118
|
+
path = base + path
|
113
119
|
path.mkpath unless path.exist?
|
114
120
|
end
|
115
121
|
say "Default directories created"
|
@@ -184,6 +184,26 @@ Also, many date-time formats can be parsed and will be converted to microseconds
|
|
184
184
|
io = File.open(options.output, 'w')
|
185
185
|
end
|
186
186
|
sol.outf sol.query(query) do |dd, ios|
|
187
|
+
# If aggregated, then we need to break up the columns. since each is now a
|
188
|
+
# hash of the aggregated functions
|
189
|
+
unless options.aggregate.nil? then
|
190
|
+
dd[:values].map! do |row|
|
191
|
+
row.map do |value|
|
192
|
+
if value.kind_of? Hash then
|
193
|
+
query[:aggregate].map{|qa| value[qa.to_sym]}
|
194
|
+
else
|
195
|
+
value
|
196
|
+
end
|
197
|
+
end.flatten
|
198
|
+
end
|
199
|
+
dd[:columns].map! do |col|
|
200
|
+
if col == 'time' then
|
201
|
+
col
|
202
|
+
else
|
203
|
+
query[:aggregate].map{|qa| "#{col}.#{qa.to_s}"}
|
204
|
+
end
|
205
|
+
end.flatten!
|
206
|
+
end
|
187
207
|
sol.tabularize({
|
188
208
|
:headers=>dd[:columns],
|
189
209
|
:rows=>dd[:values]
|
data/lib/MrMurano/verbosing.rb
CHANGED
@@ -27,6 +27,10 @@ module MrMurano
|
|
27
27
|
$stderr.puts HighLine.color(msg, :red)
|
28
28
|
end
|
29
29
|
|
30
|
+
## Output tabular data
|
31
|
+
# +data+:: Data to write. Preferably a Hash with :headers and :rows
|
32
|
+
# +ios+:: Output stream to write to, if nil, then use $stdout
|
33
|
+
# Output is either a nice visual table or CSV.
|
30
34
|
def tabularize(data, ios=nil)
|
31
35
|
fmt = $cfg['tool.outformat']
|
32
36
|
ios = $stdout if ios.nil?
|
@@ -59,6 +63,7 @@ module MrMurano
|
|
59
63
|
end
|
60
64
|
|
61
65
|
## Format and print the object
|
66
|
+
# Handles many of the raw 'unpolished' formats.
|
62
67
|
def outf(obj, ios=nil, &block)
|
63
68
|
fmt = $cfg['tool.outformat']
|
64
69
|
ios = $stdout if ios.nil?
|
data/lib/MrMurano/version.rb
CHANGED
data/spec/Account_spec.rb
CHANGED
@@ -19,7 +19,7 @@ RSpec.describe MrMurano::Account do
|
|
19
19
|
expect(uri.to_s).to eq("https://bizapi.hosted.exosite.io/api:1/")
|
20
20
|
end
|
21
21
|
|
22
|
-
it "lists
|
22
|
+
it "lists business" do
|
23
23
|
bizlist = [{"bizid"=>"XXX","role"=>"admin","name"=>"MPS"},
|
24
24
|
{"bizid"=>"YYY","role"=>"admin","name"=>"MAE"}]
|
25
25
|
stub_request(:get, "https://bizapi.hosted.exosite.io/api:1/user/BoB@place.net/membership/").
|
@@ -42,7 +42,7 @@ RSpec.describe MrMurano::Account do
|
|
42
42
|
|
43
43
|
it "lists products; without biz.id" do
|
44
44
|
allow($cfg).to receive(:get).with('business.id').and_return(nil)
|
45
|
-
expect { @acc.products }.to raise_error("Missing
|
45
|
+
expect { @acc.products }.to raise_error("Missing Business ID")
|
46
46
|
end
|
47
47
|
|
48
48
|
it "creates product" do
|
@@ -56,7 +56,7 @@ RSpec.describe MrMurano::Account do
|
|
56
56
|
|
57
57
|
it "creates product; without biz.id" do
|
58
58
|
allow($cfg).to receive(:get).with('business.id').and_return(nil)
|
59
|
-
expect { @acc.new_product("ONe") }.to raise_error("Missing
|
59
|
+
expect { @acc.new_product("ONe") }.to raise_error("Missing Business ID")
|
60
60
|
end
|
61
61
|
|
62
62
|
it "deletes product" do
|
@@ -69,7 +69,7 @@ RSpec.describe MrMurano::Account do
|
|
69
69
|
|
70
70
|
it "deletes product; without biz.id" do
|
71
71
|
allow($cfg).to receive(:get).with('business.id').and_return(nil)
|
72
|
-
expect { @acc.delete_product("ONe") }.to raise_error("Missing
|
72
|
+
expect { @acc.delete_product("ONe") }.to raise_error("Missing Business ID")
|
73
73
|
end
|
74
74
|
|
75
75
|
|
@@ -93,7 +93,7 @@ RSpec.describe MrMurano::Account do
|
|
93
93
|
|
94
94
|
it "lists solutions; without biz.id" do
|
95
95
|
allow($cfg).to receive(:get).with('business.id').and_return(nil)
|
96
|
-
expect { @acc.solutions }.to raise_error("Missing
|
96
|
+
expect { @acc.solutions }.to raise_error("Missing Business ID")
|
97
97
|
end
|
98
98
|
|
99
99
|
it "creates solution" do
|
@@ -111,7 +111,7 @@ RSpec.describe MrMurano::Account do
|
|
111
111
|
|
112
112
|
it "creates solution; without biz.id" do
|
113
113
|
allow($cfg).to receive(:get).with('business.id').and_return(nil)
|
114
|
-
expect { @acc.new_solution("one") }.to raise_error("Missing
|
114
|
+
expect { @acc.new_solution("one") }.to raise_error("Missing Business ID")
|
115
115
|
end
|
116
116
|
|
117
117
|
it "deletes solution" do
|
@@ -124,7 +124,7 @@ RSpec.describe MrMurano::Account do
|
|
124
124
|
|
125
125
|
it "deletes solution; without biz.id" do
|
126
126
|
allow($cfg).to receive(:get).with('business.id').and_return(nil)
|
127
|
-
expect { @acc.delete_solution("one") }.to raise_error("Missing
|
127
|
+
expect { @acc.delete_solution("one") }.to raise_error("Missing Business ID")
|
128
128
|
end
|
129
129
|
|
130
130
|
end
|
@@ -44,10 +44,10 @@ RSpec.describe MrMurano::ProductResources do
|
|
44
44
|
expect(loc).to eq("magical.file")
|
45
45
|
end
|
46
46
|
|
47
|
-
it "
|
47
|
+
it "Uses default spec file name" do
|
48
48
|
$cfg['product.spec'] = nil
|
49
49
|
$cfg['product.id'] = nil
|
50
|
-
expect
|
50
|
+
expect(@prd.location).to eq('specs/resources.yaml')
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
data/spec/cmd_init_spec.rb
CHANGED
@@ -10,7 +10,7 @@ RSpec.describe 'mr init' do
|
|
10
10
|
# this is in the project dir. Want to be in HOME
|
11
11
|
Dir.chdir(ENV['HOME']) do
|
12
12
|
out, err, status = Open3.capture3(capcmd('mr', 'init', '--trace'))
|
13
|
-
expect(out).to eq("")
|
13
|
+
expect(out).to eq("\n")
|
14
14
|
expect(err).to eq("\e[31mCannot init a project in your HOME directory.\e[0m\n")
|
15
15
|
expect(status.exitstatus).to eq(2)
|
16
16
|
end
|
@@ -19,7 +19,7 @@ RSpec.describe 'mr init' do
|
|
19
19
|
it "Asks to import if Solutionfile exists" do
|
20
20
|
FileUtils.touch('Solutionfile.json')
|
21
21
|
out, err, status = Open3.capture3(capcmd('mr', 'init', '--trace'), :stdin_data=>'y')
|
22
|
-
expect(out).to eq("
|
22
|
+
expect(out).to eq("\nA Solutionfile.json exists, Do you want exit and run `mr config import` instead? [yN]\n")
|
23
23
|
expect(err).to eq("")
|
24
24
|
expect(status.exitstatus).to eq(0)
|
25
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: MrMurano
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.11.
|
4
|
+
version: 1.11.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Conrad Tadpol Tilstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|