tb 0.9 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README +13 -11
  3. data/lib/tb.rb +14 -6
  4. data/lib/tb/catreader.rb +2 -2
  5. data/lib/tb/cmd_consecutive.rb +6 -2
  6. data/lib/tb/cmd_crop.rb +22 -3
  7. data/lib/tb/cmd_cross.rb +24 -0
  8. data/lib/tb/cmd_cut.rb +20 -10
  9. data/lib/tb/cmd_git.rb +20 -7
  10. data/lib/tb/cmd_group.rb +32 -0
  11. data/lib/tb/cmd_gsub.rb +21 -0
  12. data/lib/tb/cmd_join.rb +28 -0
  13. data/lib/tb/cmd_ls.rb +9 -0
  14. data/lib/tb/cmd_melt.rb +15 -0
  15. data/lib/tb/cmd_mheader.rb +15 -0
  16. data/lib/tb/cmd_nest.rb +27 -6
  17. data/lib/tb/cmd_newfield.rb +19 -2
  18. data/lib/tb/cmd_rename.rb +20 -0
  19. data/lib/tb/{cmd_grep.rb → cmd_search.rb} +37 -23
  20. data/lib/tb/cmd_shape.rb +69 -25
  21. data/lib/tb/cmd_sort.rb +20 -0
  22. data/lib/tb/cmd_tar.rb +38 -0
  23. data/lib/tb/cmd_to_json.rb +2 -2
  24. data/lib/tb/cmd_to_ltsv.rb +3 -3
  25. data/lib/tb/cmd_to_pnm.rb +3 -3
  26. data/lib/tb/cmd_to_tsv.rb +3 -3
  27. data/lib/tb/cmd_to_yaml.rb +3 -3
  28. data/lib/tb/cmd_unmelt.rb +15 -0
  29. data/lib/tb/cmd_unnest.rb +31 -7
  30. data/lib/tb/cmdmain.rb +2 -0
  31. data/lib/tb/cmdtop.rb +1 -1
  32. data/lib/tb/cmdutil.rb +9 -62
  33. data/lib/tb/csv.rb +21 -79
  34. data/lib/tb/enumerable.rb +42 -68
  35. data/lib/tb/enumerator.rb +15 -7
  36. data/lib/tb/{fieldset.rb → hashreader.rb} +37 -56
  37. data/lib/tb/hashwriter.rb +54 -0
  38. data/lib/tb/headerreader.rb +108 -0
  39. data/lib/tb/headerwriter.rb +116 -0
  40. data/lib/tb/json.rb +17 -15
  41. data/lib/tb/ltsv.rb +35 -96
  42. data/lib/tb/ndjson.rb +63 -0
  43. data/lib/tb/numericreader.rb +66 -0
  44. data/lib/tb/numericwriter.rb +61 -0
  45. data/lib/tb/pnm.rb +206 -200
  46. data/lib/tb/ropen.rb +54 -59
  47. data/lib/tb/tsv.rb +39 -71
  48. data/sample/excel2csv +24 -25
  49. data/sample/poi-xls2csv.rb +13 -14
  50. data/tb.gemspec +154 -0
  51. data/test/test_cmd_cat.rb +28 -6
  52. data/test/test_cmd_consecutive.rb +8 -3
  53. data/test/test_cmd_cut.rb +14 -4
  54. data/test/test_cmd_git_log.rb +50 -50
  55. data/test/test_cmd_grep.rb +6 -6
  56. data/test/test_cmd_gsub.rb +7 -2
  57. data/test/test_cmd_ls.rb +70 -62
  58. data/test/test_cmd_shape.rb +43 -6
  59. data/test/test_cmd_svn_log.rb +26 -27
  60. data/test/test_cmd_to_csv.rb +10 -5
  61. data/test/test_cmd_to_json.rb +16 -0
  62. data/test/test_cmd_to_ltsv.rb +2 -2
  63. data/test/test_cmd_to_pp.rb +7 -2
  64. data/test/test_csv.rb +74 -62
  65. data/test/test_ex_enumerable.rb +0 -1
  66. data/test/test_fileenumerator.rb +3 -3
  67. data/test/test_headercsv.rb +43 -0
  68. data/test/test_json.rb +2 -2
  69. data/test/test_ltsv.rb +22 -17
  70. data/test/test_ndjson.rb +62 -0
  71. data/test/test_numericcsv.rb +36 -0
  72. data/test/test_pnm.rb +69 -70
  73. data/test/test_reader.rb +27 -124
  74. data/test/test_tbenum.rb +18 -18
  75. data/test/test_tsv.rb +21 -32
  76. data/test/util_tbtest.rb +12 -0
  77. metadata +41 -19
  78. data/lib/tb/basic.rb +0 -1070
  79. data/lib/tb/reader.rb +0 -106
  80. data/lib/tb/record.rb +0 -158
  81. data/test/test_basic.rb +0 -403
  82. data/test/test_fieldset.rb +0 -42
  83. data/test/test_record.rb +0 -61
@@ -15,7 +15,44 @@ class TestTbCmdShape < Test::Unit::TestCase
15
15
  FileUtils.rmtree @tmpdir
16
16
  end
17
17
 
18
- def test_basic
18
+ def test_ndjson
19
+ File.open(i="i.ndjson", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
20
+ {"a":0, "b":1}
21
+ {"a":4, "b":5, "c":6}
22
+ End
23
+ Tb::Cmd.main_shape(['-o', o="o.csv", i])
24
+ assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
25
+ filename,records,min_pairs,max_pairs
26
+ i.ndjson,2,2,3
27
+ End
28
+ end
29
+
30
+ def test_csv
31
+ File.open(i="i.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
32
+ a,b,c
33
+ 0,1
34
+ 4,5,6
35
+ End
36
+ Tb::Cmd.main_shape(['-o', o="o.csv", i])
37
+ assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
38
+ filename,records,min_pairs,max_pairs,header_fields,min_fields,max_fields
39
+ i.csv,2,2,3,3,2,3
40
+ End
41
+ end
42
+
43
+ def test_output_ndjson
44
+ File.open(i="i.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
45
+ a,b,c
46
+ 0,1
47
+ 4,5,6
48
+ End
49
+ Tb::Cmd.main_shape(['-o', o="o.ndjson", i])
50
+ assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
51
+ {"filename":"i.csv","records":2,"min_pairs":2,"max_pairs":3,"header_fields":3,"min_fields":2,"max_fields":3}
52
+ End
53
+ end
54
+
55
+ def test_extra_field
19
56
  File.open(i="i.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
20
57
  a,b,c
21
58
  0,1
@@ -23,8 +60,8 @@ class TestTbCmdShape < Test::Unit::TestCase
23
60
  End
24
61
  Tb::Cmd.main_shape(['-o', o="o.csv", i])
25
62
  assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
26
- header_fields,min_fields,max_fields,records,filename
27
- 3,2,4,2,i.csv
63
+ filename,records,min_pairs,max_pairs,header_fields,min_fields,max_fields
64
+ i.csv,2,2,3,3,2,4
28
65
  End
29
66
  end
30
67
 
@@ -41,9 +78,9 @@ class TestTbCmdShape < Test::Unit::TestCase
41
78
  End
42
79
  Tb::Cmd.main_shape(['-o', o="o.csv", i1, i2])
43
80
  assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
44
- header_fields,min_fields,max_fields,records,filename
45
- 1,1,1,2,i1.csv
46
- 2,2,2,2,i2.csv
81
+ filename,records,min_pairs,max_pairs,header_fields,min_fields,max_fields
82
+ i1.csv,2,1,1,1,1,1
83
+ i2.csv,2,2,2,2,2,2
47
84
  End
48
85
  end
49
86
 
@@ -34,10 +34,10 @@ class TestTbCmdSvnLog < Test::Unit::TestCase
34
34
  system("svn commit -q -m baz foo hoge")
35
35
  system("svn update -q") # update the revision of the directory.
36
36
  Tb::Cmd.main_svn(['-o', o="o.csv"])
37
- result = File.read(o)
38
- tb = Tb.parse_csv(result)
39
- assert_equal(1, tb.size)
40
- assert_match(/baz/, tb.get_record(0)["msg"])
37
+ aa = CSV.read(o)
38
+ assert_equal(2, aa.length)
39
+ header, row = aa
40
+ assert_match(/baz/, row[header.index "msg"])
41
41
  end
42
42
 
43
43
  def test_verbose
@@ -49,16 +49,15 @@ class TestTbCmdSvnLog < Test::Unit::TestCase
49
49
  system("svn commit -q -m baz foo hoge")
50
50
  system("svn update -q") # update the revision of the directory.
51
51
  Tb::Cmd.main_svn(['-o', o="o.csv", '--', '-v'])
52
- result = File.read(o)
53
- tb = Tb.parse_csv(result)
54
- assert_equal(2, tb.size)
55
- recs = [tb.get_record(0), tb.get_record(1)]
56
- recs = recs.sort_by {|rec| rec['path'] }
57
- recs.each {|rec|
58
- assert_match(/baz/, rec["msg"])
52
+ aa = CSV.read(o)
53
+ assert_equal(3, aa.length)
54
+ header, *rows = aa
55
+ rows = rows.sort_by {|rec| rows[header.index 'path'] }
56
+ rows.each {|row|
57
+ assert_match(/baz/, row[header.index "msg"])
59
58
  }
60
- assert_match(/foo/, recs[0]["path"])
61
- assert_match(/hoge/, recs[1]["path"])
59
+ assert_match(/foo/, rows[0][header.index "path"])
60
+ assert_match(/hoge/, rows[1][header.index "path"])
62
61
  end
63
62
 
64
63
  def test_log_xml
@@ -71,18 +70,18 @@ class TestTbCmdSvnLog < Test::Unit::TestCase
71
70
  system("svn update -q") # update the revision of the directory.
72
71
  ###
73
72
  Tb::Cmd.main_svn(['-o', o="o.csv"])
74
- result = File.read(o)
75
- tb = Tb.parse_csv(result)
76
- assert_equal(1, tb.size)
77
- assert_match(/baz/, tb.get_record(0)["msg"])
73
+ aa = CSV.read(o)
74
+ assert_equal(2, aa.length)
75
+ header, row = aa
76
+ assert_match(/baz/, row[header.index "msg"])
78
77
  ###
79
78
  system("svn log --xml > log.xml")
80
79
  FileUtils.rmtree('.svn')
81
80
  Tb::Cmd.main_svn(['-o', o="o.csv", '--svn-log-xml=log.xml'])
82
- result = File.read(o)
83
- tb = Tb.parse_csv(result)
84
- assert_equal(1, tb.size)
85
- assert_match(/baz/, tb.get_record(0)["msg"])
81
+ aa = CSV.read(o)
82
+ assert_equal(2, aa.length)
83
+ header, row = aa
84
+ assert_match(/baz/, row[header.index "msg"])
86
85
  end
87
86
 
88
87
  def test_no_props
@@ -98,11 +97,11 @@ class TestTbCmdSvnLog < Test::Unit::TestCase
98
97
  system("svn propdel -q svn:log --revprop -r 1 .")
99
98
  ###
100
99
  Tb::Cmd.main_svn(['-o', o="o.csv"])
101
- result = File.read(o)
102
- tb = Tb.parse_csv(result)
103
- assert_equal(1, tb.size)
104
- assert_equal('(no author)', tb.get_record(0)["author"])
105
- assert_equal('(no date)', tb.get_record(0)["date"])
106
- assert_equal('', tb.get_record(0)["msg"])
100
+ aa = CSV.read(o)
101
+ assert_equal(2, aa.length)
102
+ header, row = aa
103
+ assert_equal('(no author)', row[header.index "author"])
104
+ assert_equal('(no date)', row[header.index "date"])
105
+ assert_equal('', row[header.index "msg"])
107
106
  end
108
107
  end
@@ -1,6 +1,7 @@
1
1
  require 'test/unit'
2
2
  require 'tb/cmdtop'
3
3
  require 'tmpdir'
4
+ require_relative 'util_tbtest'
4
5
 
5
6
  class TestTbCmdToCSV < Test::Unit::TestCase
6
7
  def setup
@@ -49,18 +50,22 @@ class TestTbCmdToCSV < Test::Unit::TestCase
49
50
  End
50
51
  end
51
52
 
52
- def test_complement_header
53
+ def test_short_header
53
54
  File.open(i="i.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
54
55
  a,b
55
56
  0,1,2
56
57
  4,5,6
57
58
  End
58
- Tb::Cmd.main_to_csv(['-o', o="o.csv", i])
59
+ o = "o.csv"
60
+ stderr = capture_stderr {
61
+ Tb::Cmd.main_to_csv(['-o', o, i])
62
+ }
59
63
  assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
60
- a,b,1
61
- 0,1,2
62
- 4,5,6
64
+ a,b
65
+ 0,1
66
+ 4,5
63
67
  End
68
+ assert_match(/Header too short/, stderr)
64
69
  end
65
70
 
66
71
  def test_numeric
@@ -68,6 +68,22 @@ class TestTbCmdToJSON < Test::Unit::TestCase
68
68
  End
69
69
  end
70
70
 
71
+ def test_ltsv_to_json1
72
+ File.open(i="i.ltsv", "w") {|f| f << "foo:bar\n" }
73
+ Tb::Cmd.main_to_json(['-o', o="o.json", i])
74
+ assert_equal(<<-"End".gsub(/\s/, ''), File.read(o).gsub(/\s/, ''))
75
+ [{"foo":"bar"}]
76
+ End
77
+ end
78
+
79
+ def test_ltsv_to_json2
80
+ File.open(i="i.ltsv", "w") {|f| f << "foo:bar\nbaz:qux\n" }
81
+ Tb::Cmd.main_to_json(['-o', o="o.json", i])
82
+ assert_equal(<<-"End".gsub(/\s/, ''), File.read(o).gsub(/\s/, ''))
83
+ [{"foo":"bar"}, {"baz":"qux"}]
84
+ End
85
+ end
86
+
71
87
  def test_twofile
72
88
  File.open(i1="i1.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
73
89
  a,b
@@ -57,8 +57,8 @@ class TestTbCmdToLTSV < Test::Unit::TestCase
57
57
  assert_equal(<<-"End".gsub(/^[ \t]+/, ''), File.read(o))
58
58
  a:1\tb:2
59
59
  a:3\tb:4
60
- a:6\tb:5
61
- a:8\tb:7
60
+ b:5\ta:6
61
+ b:7\ta:8
62
62
  End
63
63
  end
64
64
 
@@ -1,6 +1,7 @@
1
1
  require 'test/unit'
2
2
  require 'tb/cmdtop'
3
3
  require 'tmpdir'
4
+ require_relative 'util_tbtest'
4
5
 
5
6
  class TestTbCmdToPP < Test::Unit::TestCase
6
7
  def setup
@@ -33,10 +34,14 @@ class TestTbCmdToPP < Test::Unit::TestCase
33
34
  a,b
34
35
  0,1,2,3
35
36
  End
36
- Tb::Cmd.main_to_pp(['-o', o="o.pp", i])
37
+ o = "o.pp"
38
+ stderr = capture_stderr {
39
+ Tb::Cmd.main_to_pp(['-o', o, i])
40
+ }
37
41
  assert_equal(<<-"End".gsub(/\s/, ''), File.read(o).gsub(/\s/, ''))
38
- { "a" => "0", "b" => "1", "1" => "2", "2" => "3" }
42
+ { "a" => "0", "b" => "1" }
39
43
  End
44
+ assert_match(/Header too short/, stderr)
40
45
  end
41
46
 
42
47
  def test_twofile
@@ -1,98 +1,110 @@
1
1
  require 'tb'
2
2
  require 'test/unit'
3
+ require_relative 'util_tbtest'
3
4
 
4
5
  class TestTbCSV < Test::Unit::TestCase
6
+ def parse_csv(csv)
7
+ Tb::HeaderCSVReader.new(StringIO.new(csv)).to_a
8
+ end
9
+
10
+ def generate_csv(ary)
11
+ writer = Tb::HeaderCSVWriter.new(out = '')
12
+ ary.each {|h| writer.put_hash h }
13
+ writer.finish
14
+ out
15
+ end
16
+
5
17
  def test_parse
6
- csv = <<-'End'.gsub(/^\s+/, '')
18
+ t = parse_csv(<<-'End'.gsub(/^\s+/, ''))
19
+ a,b
20
+ 1,2
21
+ 3,4
22
+ End
23
+ assert_equal(
24
+ [{"a"=>"1", "b"=>"2"},
25
+ {"a"=>"3", "b"=>"4"}],
26
+ t)
27
+ end
28
+
29
+ def test_parse_empty_line_before_header
30
+ empty_line = "\n"
31
+ t = parse_csv(empty_line + <<-'End'.gsub(/^\s+/, ''))
7
32
  a,b
8
33
  1,2
9
34
  3,4
10
35
  End
11
- t = Tb.parse_csv(csv)
12
- records = []
13
- t.each_record {|record|
14
- records << record.to_h_with_reserved
15
- }
16
36
  assert_equal(
17
- [{"_recordid"=>0, "a"=>"1", "b"=>"2"},
18
- {"_recordid"=>1, "a"=>"3", "b"=>"4"}],
19
- records)
37
+ [{"a"=>"1", "b"=>"2"},
38
+ {"a"=>"3", "b"=>"4"}],
39
+ t)
20
40
  end
21
41
 
22
- def test_parse_empty
23
- csv = <<-'End'.gsub(/^\s+/, '')
42
+ def test_parse_empty_value
43
+ t = parse_csv(<<-'End'.gsub(/^\s+/, ''))
24
44
  a,b,c
25
45
  1,,2
26
46
  3,"",4
27
47
  End
28
- t = Tb.parse_csv(csv)
29
- records = []
30
- t.each_record {|record|
31
- records << record.to_h_with_reserved
32
- }
33
48
  assert_equal(
34
- [{"_recordid"=>0, "a"=>"1", "c"=>"2"},
35
- {"_recordid"=>1, "a"=>"3", "b"=>"", "c"=>"4"}],
36
- records)
49
+ [{"a"=>"1", "b"=>nil, "c"=>"2"},
50
+ {"a"=>"3", "b"=>"", "c"=>"4"}],
51
+ t)
37
52
  end
38
53
 
39
- def test_parse_conv
40
- csv = "foo\na,b\n1,2\n"
41
- t = Tb.parse_csv(csv) {|aa|
42
- assert_equal([%w[foo],
43
- %w[a b],
44
- %w[1 2]],
45
- aa)
46
- aa.shift
47
- aa
48
- }
49
- records = []
50
- t.each_record {|record|
51
- records << record.to_h_with_reserved
52
- }
53
- assert_equal(
54
- [{"_recordid"=>0, "a"=>"1", "b"=>"2"}],
55
- records)
54
+ def test_parse_newline
55
+ t = parse_csv("\n")
56
+ assert_equal([], t)
56
57
  end
57
58
 
58
59
  def test_generate
59
- t = Tb.new %w[a b], [1, 2], [3, 4]
60
- out = t.generate_csv('', ['a', 'b'])
61
- assert_equal(<<-'End'.gsub(/^\s+/, ''), out)
60
+ t = [{'a' => 1, 'b' => 2},
61
+ {'a' => 3, 'b' => 4}]
62
+ assert_equal(<<-'End'.gsub(/^\s+/, ''), generate_csv(t))
62
63
  a,b
63
64
  1,2
64
65
  3,4
65
66
  End
66
67
  end
67
68
 
68
- def test_generate_without_explicit_fields
69
- t = Tb.new %w[a b], [1, 2], [3, 4]
70
- out = t.generate_csv('')
71
- assert_equal(<<-'End'.gsub(/^\s+/, ''), out)
72
- a,b
73
- 1,2
74
- 3,4
75
- End
76
- end
77
-
78
- def test_generate_with_block
79
- t = Tb.new %w[a b], [1, 2], [3, 4]
80
- out = t.generate_csv('', ['a', 'b']) {|recids| recids.reverse }
81
- assert_equal(<<-'End'.gsub(/^\s+/, ''), out)
82
- a,b
83
- 3,4
84
- 1,2
85
- End
86
- end
87
-
88
69
  def test_generate_empty
89
- t = Tb.new %w[a b c], [1, nil, 2], [3, '', 4]
90
- out = t.generate_csv('', ['a', 'b', 'c'])
91
- assert_equal(<<-'End'.gsub(/^\s+/, ''), out)
70
+ t = [{'a' => 1, 'b' => nil, 'c' => 2},
71
+ {'a' => 3, 'b' => '', 'c' => 4}]
72
+ assert_equal(<<-'End'.gsub(/^\s+/, ''), generate_csv(t))
92
73
  a,b,c
93
74
  1,,2
94
75
  3,"",4
95
76
  End
96
77
  end
97
78
 
79
+ def test_parse_ambiguous_header
80
+ t = nil
81
+ stderr = capture_stderr {
82
+ t = parse_csv(<<-'End'.gsub(/^\s+/, ''))
83
+ a,b,a,b,c
84
+ 0,1,2,3,4
85
+ 5,6,7,8,9
86
+ End
87
+ }
88
+ assert_equal(
89
+ [{"a"=>"0", "b"=>"1", "c"=>"4"},
90
+ {"a"=>"5", "b"=>"6", "c"=>"9"}],
91
+ t)
92
+ assert_match(/Ambiguous header field/, stderr)
93
+ end
94
+
95
+ def test_parse_empty_header_field
96
+ t = nil
97
+ stderr = capture_stderr {
98
+ t = parse_csv(<<-'End'.gsub(/^\s+/, ''))
99
+ a,,c
100
+ 0,1,2
101
+ 5,6,7
102
+ End
103
+ }
104
+ assert_equal(
105
+ [{"a"=>"0", "c"=>"2"},
106
+ {"a"=>"5", "c"=>"7"}],
107
+ t)
108
+ assert_match(/Empty header field/, stderr)
109
+ end
98
110
  end
@@ -46,7 +46,6 @@ class TestTbEnumerable < Test::Unit::TestCase
46
46
  a.tb_categorize(:color, lambda {|e| true }, :seed=>0, :op=>lambda {|s, v| s+1 }))
47
47
 
48
48
  assert_raise(ArgumentError) { a.tb_categorize(:color, lambda {|e| true }, :seed=>0,
49
- :seed=>nil,
50
49
  :op=>lambda {|s, v| s+1 },
51
50
  :update=>lambda {|ks, s, v| s+1 }) }
52
51
 
@@ -204,7 +204,7 @@ class TestTbFileEnumerator < Test::Unit::TestCase
204
204
  end
205
205
 
206
206
  def test_to_fileheaderenumerator_reader
207
- tb = Tb.new %w[a b], [1, 2], [3, 4]
207
+ tb = Tb::Enumerator.from_header_and_values %w[a b], [1, 2], [3, 4]
208
208
  fe = tb.to_fileenumerator
209
209
  iter = fe.each
210
210
  assert_respond_to(iter, :next)
@@ -222,7 +222,7 @@ class TestTbFileEnumerator < Test::Unit::TestCase
222
222
  end
223
223
 
224
224
  def test_to_fileheaderenumerator_with_header_reader
225
- tb = Tb.new %w[a b], [1, 2], [3, 4]
225
+ tb = Tb::Enumerator.from_header_and_values %w[a b], [1, 2], [3, 4]
226
226
  header = nil
227
227
  fe = tb.with_header {|h0|
228
228
  header = h0
@@ -244,7 +244,7 @@ class TestTbFileEnumerator < Test::Unit::TestCase
244
244
  end
245
245
 
246
246
  def test_fileheaderenumerator_open_reader
247
- tb = Tb.new %w[a b], [1, 2], [3, 4]
247
+ tb = Tb::Enumerator.from_header_and_values %w[a b], [1, 2], [3, 4]
248
248
  fe = tb.to_fileenumerator
249
249
  iter0 = nil
250
250
  fe.open_reader {|iter|