plist4r 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +11 -0
  3. data/LICENSE +3 -1
  4. data/README.rdoc +25 -122
  5. data/Rakefile +14 -0
  6. data/VERSION +1 -1
  7. data/bin/plist4r +2 -0
  8. data/ext/osx_plist/Makefile +157 -0
  9. data/ext/osx_plist/extconf.rb +9 -0
  10. data/ext/osx_plist/plist.c +606 -0
  11. data/ext/osx_plist/plist.o +0 -0
  12. data/lib/plist4r.rb +6 -3
  13. data/lib/plist4r/application.rb +1 -2
  14. data/lib/plist4r/backend.rb +102 -34
  15. data/lib/plist4r/backend/c_f_property_list.rb +65 -0
  16. data/lib/plist4r/backend/c_f_property_list/LICENSE +19 -0
  17. data/lib/plist4r/backend/c_f_property_list/README +34 -0
  18. data/lib/plist4r/backend/c_f_property_list/cfpropertylist.rb +6 -0
  19. data/lib/plist4r/backend/c_f_property_list/rbBinaryCFPropertyList.rb +663 -0
  20. data/lib/plist4r/backend/c_f_property_list/rbCFPlistError.rb +26 -0
  21. data/lib/plist4r/backend/c_f_property_list/rbCFPropertyList.rb +348 -0
  22. data/lib/plist4r/backend/c_f_property_list/rbCFTypes.rb +241 -0
  23. data/lib/plist4r/backend/c_f_property_list/rbXMLCFPropertyList.rb +116 -0
  24. data/lib/plist4r/backend/example.rb +37 -52
  25. data/lib/plist4r/backend/haml.rb +47 -36
  26. data/lib/plist4r/backend/libxml4r.rb +24 -20
  27. data/lib/plist4r/backend/osx_plist.rb +82 -0
  28. data/lib/plist4r/backend/ruby_cocoa.rb +172 -54
  29. data/lib/plist4r/backend/test/data_types.rb +163 -0
  30. data/lib/plist4r/backend/test/harness.rb +255 -0
  31. data/lib/plist4r/backend/test/output.rb +47 -0
  32. data/lib/plist4r/backend_base.rb +4 -2
  33. data/lib/plist4r/{options.rb → cli.rb} +2 -1
  34. data/lib/plist4r/commands.rb +13 -8
  35. data/lib/plist4r/config.rb +36 -9
  36. data/lib/plist4r/docs/Backends.html +59 -0
  37. data/lib/plist4r/docs/DeveloperGuide.rdoc +53 -0
  38. data/lib/plist4r/docs/EditingPlistFiles.rdoc +88 -0
  39. data/lib/plist4r/docs/InfoPlistExample.rdoc +33 -0
  40. data/lib/plist4r/docs/LaunchdPlistExample.rdoc +33 -0
  41. data/lib/plist4r/docs/PlistKeyNames.rdoc +47 -0
  42. data/lib/plist4r/mixin/array_dict.rb +61 -0
  43. data/lib/plist4r/mixin/data_methods.rb +178 -54
  44. data/lib/plist4r/mixin/haml4r.rb +4 -0
  45. data/lib/plist4r/mixin/haml4r/css_attributes.rb +19 -0
  46. data/lib/plist4r/mixin/haml4r/examples.rb +261 -0
  47. data/lib/plist4r/mixin/haml4r/haml_table_example.rb +79 -0
  48. data/lib/plist4r/mixin/haml4r/table.rb +157 -0
  49. data/lib/plist4r/mixin/haml4r/table_cell.rb +160 -0
  50. data/lib/plist4r/mixin/haml4r/table_cells.rb +485 -0
  51. data/lib/plist4r/mixin/haml4r/table_section.rb +101 -0
  52. data/lib/plist4r/mixin/ordered_hash.rb +9 -1
  53. data/lib/plist4r/mixin/popen4.rb +1 -1
  54. data/lib/plist4r/mixin/ruby_stdlib.rb +154 -1
  55. data/lib/plist4r/mixin/script.rb +133 -0
  56. data/lib/plist4r/mixin/table.rb +435 -0
  57. data/lib/plist4r/plist.rb +272 -94
  58. data/lib/plist4r/plist_cache.rb +42 -43
  59. data/lib/plist4r/plist_type.rb +31 -74
  60. data/lib/plist4r/plist_type/info.rb +157 -3
  61. data/lib/plist4r/plist_type/launchd.rb +54 -48
  62. data/lib/plist4r/plist_type/plist.rb +1 -3
  63. data/plist4r.gemspec +74 -14
  64. data/spec/{examples.rb → launchd_examples.rb} +131 -139
  65. data/spec/plist4r/application_spec.rb +37 -0
  66. data/spec/plist4r/backend_spec.rb +256 -0
  67. data/spec/plist4r/cli_spec.rb +25 -0
  68. data/spec/plist4r/commands_spec.rb +20 -0
  69. data/spec/plist4r/config_spec.rb +38 -0
  70. data/spec/plist4r/mixin/array_dict_spec.rb +120 -0
  71. data/spec/plist4r/mixin/data_methods_spec.rb +96 -0
  72. data/spec/plist4r/mixin/haml4r/examples.rb +261 -0
  73. data/spec/plist4r/mixin/ruby_stdlib_spec.rb +228 -0
  74. data/spec/plist4r/plist_cache_spec.rb +261 -0
  75. data/spec/plist4r/plist_spec.rb +841 -23
  76. data/spec/plist4r/plist_type_spec.rb +126 -0
  77. data/spec/plist4r_spec.rb +53 -27
  78. data/spec/scratchpad.rb +226 -0
  79. data/spec/spec_helper.rb +5 -1
  80. metadata +109 -23
  81. data/lib/plist4r/backend/plutil.rb +0 -25
  82. data/lib/plist4r/mixin.rb +0 -7
  83. data/plists/array_mini.xml +0 -14
  84. data/plists/example_big_binary.plist +0 -0
  85. data/plists/example_medium_binary_launchd.plist +0 -0
  86. data/plists/example_medium_launchd.xml +0 -53
  87. data/plists/mini.xml +0 -12
  88. data/test.rb +0 -40
@@ -0,0 +1,4 @@
1
+
2
+ # Dir.glob(File.dirname(__FILE__) + "/haml4r/**/*.rb").each {|b| require File.expand_path b}
3
+
4
+ require 'plist4r/mixin/haml4r/table'
@@ -0,0 +1,19 @@
1
+
2
+ module Haml4r
3
+ CssAttributes = %w[ css_class css_id css_style ]
4
+
5
+ module HamlObject
6
+ def haml
7
+ @haml ||= <<-'EOC'
8
+ = super
9
+ EOC
10
+ end
11
+
12
+ def to_s
13
+ require 'haml'
14
+ engine = ::Haml::Engine.new self.haml
15
+ rendered_html_output = engine.render self
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,261 @@
1
+
2
+ require 'haml'
3
+ require 'plist4r/mixin/haml4r/table'
4
+
5
+ # def table
6
+ # t = Haml4r::Table.new :size => [0..5, 0..5]
7
+ # t.cell 0,0, "foo"
8
+ # t.cell 0,0
9
+ #
10
+ # t.cell 2,0, "pineapples"
11
+ # t.cell 4,0, "some_string"
12
+ # t.fill 0..5, 1..4, 0
13
+ # puts t.inspect
14
+ # end
15
+ # table
16
+
17
+
18
+
19
+ def haml4r_table
20
+ # t = Haml4r::Table.new :size => [0..5, 0..5]
21
+ # t.cell 0,0, "foo"
22
+ # t.cell 0,0
23
+
24
+ # t.cell 2,0, "pineapples"
25
+ # t.cell 4,0, "some_string"
26
+ # t.fill 0..5, 1..4, 0
27
+ # puts t.inspect
28
+ # puts ""
29
+
30
+ # puts "col_range = #{t.col_range}"
31
+ # puts "row_range = #{t.row_range}"
32
+ # puts "size = #{t.size}.inspect"
33
+ # puts ""
34
+
35
+ # puts "pad_all \"empty\""
36
+ # t.pad_all "empty"
37
+ # puts t.inspect
38
+ # puts ""
39
+
40
+ # puts "crop 0..2, 0..2"
41
+ # t2 = t.crop 0..2, 0..2
42
+ # puts t2.inspect
43
+ # puts ""
44
+
45
+ # puts "fill 0..2, 1..2, \"fill\""
46
+ # t.fill 0..2, 1..2, "fill"
47
+ # puts t.inspect
48
+ # puts ""
49
+
50
+ # puts "fill_all, \"fill\""
51
+ # t2.fill_all "fill"
52
+ # puts t2.inspect
53
+ # puts ""
54
+
55
+ # puts "inverse_fill, 1..1, 1..1, \"inverse\""
56
+ # # t2.inverse_fill 1..1, 1..1, "inverse"
57
+ # t2.inverse_fill 1, 1, "inverse"
58
+ # puts t2.inspect
59
+ # puts ""
60
+
61
+ # puts "t3"
62
+ # fruit = %w[ apples strawberries banana grapefruit mango papaya]
63
+ # day = %w[ mon tues wed thurs fri sat]
64
+ # t3 = Haml4r::Table.new :size => [0..5, 0..5]
65
+ # (0..5).each do |col|
66
+ # (0..2).each do |row|
67
+ # t3.cell col,row, fruit[col]
68
+ # # t3.cell col,row, "S " << (97+col).chr << " " << (97+row).chr
69
+ # end
70
+ # (3..5).each do |row|
71
+ # t3.cell col,row, day[col]
72
+ # end
73
+ # end
74
+ # puts t3.inspect
75
+ # puts ""
76
+
77
+ # puts "transpose"
78
+ # t3.transpose
79
+ # puts t3.inspect
80
+ # puts ""
81
+ #
82
+ # puts "transpose 3..5, 3..5"
83
+ # t3.transpose 3..5, 3..5
84
+ # puts t3.inspect
85
+ # puts ""
86
+ #
87
+ # puts "transpose 3..5, 3..5"
88
+ # t3.transpose 3..5, 3..5
89
+ # puts t3.inspect
90
+ # puts ""
91
+ #
92
+ # puts "transpose 0..3, 0..2"
93
+ # t3.transpose 0..3, 0..2
94
+ # puts t3.inspect
95
+ # puts ""
96
+
97
+ # puts "col_replace 0, (row 3)"
98
+ # # t3.col_replace 0, t3.row(3)
99
+ # t3.col_replace 0..1, "col 0..1"
100
+ # # t3.col 0, t3.col(5)
101
+ # puts t3.inspect
102
+ # puts ""
103
+
104
+ # puts "row_replace 0, (col 5)"
105
+ # puts t3.col(5).transpose.inspect
106
+ # t3.row_replace 1, t3.col(5).transpose
107
+ # # t3.row_replace 1, "row 1"
108
+ # puts t3.inspect
109
+ # puts ""
110
+
111
+ # puts "translate 0..2, 0..2, [3,3]"
112
+ # t3.translate 0..2, 0..2, [3,3]
113
+ # puts t3.inspect
114
+ # puts ""
115
+
116
+ # puts "t3.cells(0..1, 2..2)"
117
+ # puts t3.cells(0..1,2..2).inspect
118
+ # puts ""
119
+
120
+ # puts "col_insert 3..4, 3..4, t3.cells(0..1, 2..2)"
121
+ # t3.col_insert 3..4, 3..4, t3.cells(0..1,2..2)
122
+ # puts t3.inspect
123
+ # puts ""
124
+
125
+ end
126
+ # haml4r_table
127
+
128
+ def haml4r_haml_table_cells
129
+ # t = Haml4r::TableCells.new :size => [0..3, 0..5]
130
+ # puts t.inspect
131
+ # puts ""
132
+ #
133
+ # puts "t.each content = Date::DAYNAMES[i]"
134
+ # i = 0
135
+ # require 'date'
136
+ # t.each 1..2, 1..3 do |c|
137
+ # c.content = Date::DAYNAMES[i]
138
+ # i +=1
139
+ # end
140
+ # puts t.inspect
141
+ # puts ""
142
+ #
143
+ # t.cell(1,1).content = "foos"
144
+ # c = t.content
145
+ # puts "t.content => #{c.class}"
146
+ # puts c.inspect
147
+ # puts c.cell(0,0).class
148
+ # puts c.cell(1,1).class
149
+ # puts ""
150
+ #
151
+ # puts "t.content = \"depeche mode\""
152
+ # t.content = "depeche mode"
153
+ # puts t.inspect
154
+ # puts t.cell(0,0).class
155
+ # puts t.cell(1,1).class
156
+ # puts ""
157
+ end
158
+ # haml4r_haml_table_cells
159
+
160
+ def haml4r_haml_table_cell
161
+ # puts "table cell"
162
+ # c = Haml4r::TableCell.new
163
+ # puts c.inspect
164
+ # puts ""
165
+ # c.content = "jeremy"
166
+ # puts c.inspect
167
+ # puts ""
168
+ end
169
+ # haml4r_haml_table_cell
170
+
171
+ def haml4r_haml_table_cell_spans
172
+ # t = Haml4r::TableCells.new :size => [0..3, 0..5]
173
+ # # puts t.inspect
174
+ # # puts ""
175
+ #
176
+ # puts "t.each(1..2, 1..3) content = Date::DAYNAMES[i]"
177
+ # i = 0
178
+ # require 'date'
179
+ # t.each 1..2, 1..3 do |c|
180
+ # c.content = Date::DAYNAMES[i]
181
+ # i +=1
182
+ # end
183
+ # puts t.inspect
184
+ # puts ""
185
+ #
186
+ # puts "t.span_cells 0..3, 4..5, :content => \"foos\""
187
+ # t.span_cells 0..3, 4..5, :content => "foos"
188
+ # puts t.inspect
189
+ # puts ""
190
+ #
191
+ # puts "t.cell(0,4).content = \"bar\""
192
+ # t.cell(0,4).content = "bar"
193
+ # t.cell(0,4).css_class = "bar"
194
+ # t.cell(0,4).css_class += " of_trouble"
195
+ # puts t.inspect
196
+ # puts ""
197
+ #
198
+ # puts "t.to_s"
199
+ # puts t
200
+ # puts ""
201
+ end
202
+ # haml4r_haml_table_cell_spans
203
+
204
+ def haml4r_haml_table
205
+ t = Haml4r::Table.new :size => [1..3, 1..2]
206
+
207
+ t.col_range.each do |col|
208
+ t.row_range.each do |row|
209
+ t.cell(col,row).content = "val#{col}#{row}"
210
+ end
211
+ end
212
+
213
+ t.col_header = Haml4r::TableSection.new
214
+ t.col_header.size [t.body.col_range,1..2]
215
+ t.col_header.span_cells 1..3, 1, :content => "Open"
216
+ t.col_header.col_range.each do |col|
217
+ t.col_header.cell(col,2).content = "x#{col}"
218
+ end
219
+
220
+ t.row_header = Haml4r::TableSection.new
221
+ t.row_header.size [1,t.body.row_range]
222
+ t.row_header.row_range.each do |row|
223
+ t.row_header.cell(1,row).content = "y#{row}"
224
+ end
225
+
226
+ puts t.inspect
227
+ puts ""
228
+
229
+ puts "t.to_s"
230
+ puts t
231
+ puts ""
232
+ end
233
+ # haml4r_haml_table
234
+
235
+ def haml4r_transpose_test
236
+ t = Haml4r::Table.new :size => [0..6, 2..8]
237
+ puts "strip = t.body.cells(1..5, 3..3) ; strip.span"
238
+ strip = t.body.cells(1..5, 3..3)
239
+ strip.span_cells
240
+ # strip.first.content = "foo"
241
+ strip.content = "foo"
242
+ strip.cell(6,3).content = "james"
243
+
244
+ strip.map!(1..6,3) do |c|
245
+ c.content += " bar"
246
+ end
247
+
248
+ puts t.inspect
249
+ puts ""
250
+
251
+ puts "t.body.transpose 1..5,3..3"
252
+ t.body.transpose 1..5, 3..3
253
+ # t.body.transpose 1..4, 3..3
254
+ # t.body.cell(1,7).content = "bar"
255
+ puts t.body.inspect
256
+ puts ""
257
+
258
+ end
259
+ # haml4r_transpose_test
260
+
261
+
@@ -0,0 +1,79 @@
1
+ require 'haml'
2
+ require 'plist4r/mixin/haml4r/table'
3
+
4
+ module Haml4r
5
+ class TableExample
6
+
7
+ def haml
8
+ @haml ||= <<-'EOC'
9
+ %h1 Backend Test Matrix
10
+ %div
11
+ %h3 Code to generate html table
12
+ %pre{ :class => "code" } #{create_dynamic_table}
13
+ %p Haml
14
+ %pre{ :class => "code" } #{@table.haml}
15
+ %h2 ** Generated Html **
16
+ %h3 Dynamic table
17
+ %p Inspect method (ascii representation)
18
+ %pre{ :class => "code" } #{"&nbsp;\n" + @table.inspect + "&nbsp;\n"}
19
+ %p Haml helper method
20
+ %pre{ :class => "code" } = @table
21
+ = @table
22
+ %hr
23
+ %h3 Dynamic table, transposed (flipped)
24
+ %pre{ :class => "code" } - @table.transpose
25
+ %p Inspect method (ascii representation)
26
+ - @table.transpose
27
+ %pre{ :class => "code" } #{"&nbsp;\n" + @table.inspect + "&nbsp;\n"}
28
+ %p Haml helper method
29
+ %pre{ :class => "code" } = @table
30
+ = @table
31
+ %p
32
+ EOC
33
+ end
34
+
35
+ def to_s
36
+ require 'haml'
37
+ engine = ::Haml::Engine.new self.haml
38
+ rendered_html_output = engine.render self
39
+ end
40
+
41
+ def create_dynamic_table
42
+ @create_dynamic_table ||= <<-'EOC'
43
+ t = Haml4r::Table.new :size => [1..3, 1..2]
44
+
45
+ t.col_range.each do |col|
46
+ t.row_range.each do |row|
47
+ t.cell(col,row).content = "val#{col}#{row}"
48
+ end
49
+ end
50
+
51
+ t.col_header.size [t.body.col_range,1..2]
52
+ t.col_header.span_cells 1..3, 1, :content => "Open"
53
+
54
+ t.col_header.col_range.each do |col|
55
+ t.col_header.cell(col,2).content = "x#{col}"
56
+ end
57
+
58
+ t.row_header.size [1,t.body.row_range]
59
+ t.row_header.row_range.each do |row|
60
+ t.row_header.cell(1,row).content = "y#{row}"
61
+ end
62
+ @table = t
63
+ EOC
64
+ end
65
+
66
+ def initialize *args, &blk
67
+ eval create_dynamic_table
68
+ end
69
+
70
+ def write_html_file
71
+ docs_dir = File.dirname(__FILE__) + "/../../../lib/plist4r/docs"
72
+ File.open "#{docs_dir}/BackendTestMatrix.html","w" do |o|
73
+ o << to_s
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+
@@ -0,0 +1,157 @@
1
+
2
+ require 'plist4r/mixin/haml4r/table_section'
3
+
4
+ module Haml4r
5
+
6
+ # A Table is composed of 3 sub-tables. The table of Column Headers, the table of Row Headers,
7
+ # and the body of the table, the table data. Its the responsibility of the Table to render all
8
+ # Table Sections into html/xhtml, with to_s method.
9
+ #
10
+ # The three tables are laid out by TableView as follows:
11
+ #
12
+ # :------------------------------------------:
13
+ # | :------------------: |
14
+ # | | | |
15
+ # | | | |
16
+ # | | TableSection | |
17
+ # | | @col_headers | |
18
+ # | | | |
19
+ # | | | |
20
+ # | :------------------: |
21
+ # | :------------------::------------------: |
22
+ # | | || | |
23
+ # | | || | |
24
+ # | | TableSection || TableSection | |
25
+ # | | @row_headers || @body | |
26
+ # | | || | |
27
+ # | | || | |
28
+ # | :------------------::------------------: |
29
+ # | Table |
30
+ # :------------------------------------------:
31
+ # Note: The row header TableSection can be set with CSS attributes.
32
+ # However there is no overall HTML element for row headers,
33
+ # Therefore we cannot apply the row_headers css attributes.
34
+ #
35
+ class Table
36
+
37
+ def to_s
38
+ require 'haml'
39
+ engine = ::Haml::Engine.new self.haml
40
+ rendered_html_output = engine.render self
41
+ end
42
+
43
+ def haml
44
+ @haml ||= <<-'EOC'
45
+ %table{:class => self.css_class, :id => self.css_id, :style => self.css_style}
46
+ - if @col_header
47
+ %thead{:class => @col_header.css_class, :id => @col_header.css_id, :style => @col_header.css_style}
48
+ - @col_header.row_range.each do |row|
49
+ %tr
50
+ - if @row_header
51
+ - @row_header.col_range.each do |col|
52
+ %th &nbsp;
53
+ - @col_header.col_range.each do |col|
54
+ - c = @col_header.cell col, row
55
+ - unless c.spanee
56
+ %th{:class => c.css_class, :id => c.css_id, :style => c.css_style, :colspan => @col_header.colspan(col,row), :rowspan => @col_header.rowspan(col,row)} #{c.content}
57
+ - if @body
58
+ %tbody{:class => @body.css_class, :id => @body.css_id, :style => @body.css_style}
59
+ - @body.row_range.each do |row|
60
+ %tr
61
+ - if @row_header
62
+ - @row_header.col_range.each do |col|
63
+ - c = @row_header.cell col, row
64
+ - unless c.spanee
65
+ %th{:class => c.css_class, :id => c.css_id, :style => c.css_style, :colspan => @row_header.colspan(col,row), :rowspan => @row_header.rowspan(col,row)} #{c.content}
66
+ - @body.col_range.each do |col|
67
+ - c = @body.cell col, row
68
+ - unless c.spanee
69
+ %td{:class => c.css_class, :id => c.css_id, :style => c.css_style, :colspan => @body.colspan(col,row), :rowspan => @body.rowspan(col,row)} #{c.content}
70
+ EOC
71
+ end
72
+
73
+ Attributes = Haml4r::CssAttributes + %w[ col_header row_header body ]
74
+
75
+ def initialize *args, &blk
76
+ @body = TableSection.new *args, &blk
77
+ @col_header, @row_header = [TableSection.new,TableSection.new]
78
+ end
79
+
80
+ def method_missing method_sym, *args, &blk
81
+ if Attributes.include? method_sym.to_s.chomp('=')
82
+ set_or_return method_sym.to_s, *args, &blk
83
+
84
+ elsif @body.respond_to? method_sym
85
+ @body.send method_sym, *args, &blk
86
+
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ def transpose
93
+ @col_header, @row_header = @row_header, @col_header
94
+ @col_header.transpose
95
+ @row_header.transpose
96
+ @body.transpose
97
+ self
98
+ end
99
+
100
+ def respond_to? method_sym
101
+ return true if Attributes.include? method_sym.to_s.chomp('=')
102
+ return true if @body.respond_to? method_sym
103
+ super
104
+ end
105
+
106
+ def set attribute, value
107
+ eval "@#{attribute} = value"
108
+ end
109
+
110
+ def value_for attribute
111
+ eval "@#{attribute}"
112
+ end
113
+
114
+ def set_or_return attribute, value=nil
115
+ case attribute
116
+ when /\=$/
117
+ set attribute.to_s.chomp('='), value
118
+ else
119
+ value_for attribute.to_s
120
+ end
121
+ end
122
+
123
+ def inspect start_col=0
124
+ col0 = 0
125
+ cha,ba = [0,0]
126
+
127
+ unless row_header.empty?
128
+ col0 += row_header.ascii_col_width
129
+ bw = body.ascii_col_width
130
+ chw = col_header.ascii_col_width
131
+
132
+ if bw > chw
133
+ cha += (bw-chw)/2
134
+ elsif chw > bw
135
+ ba += (chw-bw)/2
136
+ end
137
+ end
138
+
139
+ b = body.inspect(start_col+col0+ba).split "\n"
140
+
141
+ unless row_header.empty?
142
+ rh = row_header.inspect(start_col).split "\n"
143
+ (0..([rh.size, b.size].min-1)).each do |i|
144
+ b[i][0,col0] = rh[i][0,col0]
145
+ end
146
+ end
147
+
148
+ if col_header.empty?
149
+ return b.join("\n") + "\n"
150
+ else
151
+ return col_header.inspect(start_col+col0+cha) + b.join("\n") + "\n"
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ end