erlapi 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +15 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/bin/erlapi +26 -0
  8. data/erlapi.gemspec +140 -0
  9. data/lib/erlapi.rb +38 -0
  10. data/lib/erlapi/generator.rb +194 -0
  11. data/lib/erlapi/generator/shtml.rb +353 -0
  12. data/lib/erlapi/generator/template/direct/_context.rhtml +172 -0
  13. data/lib/erlapi/generator/template/direct/class.rhtml +40 -0
  14. data/lib/erlapi/generator/template/direct/file.rhtml +30 -0
  15. data/lib/erlapi/generator/template/direct/index.rhtml +14 -0
  16. data/lib/erlapi/generator/template/direct/resources/apple-touch-icon.png +0 -0
  17. data/lib/erlapi/generator/template/direct/resources/css/main.css +263 -0
  18. data/lib/erlapi/generator/template/direct/resources/css/panel.css +383 -0
  19. data/lib/erlapi/generator/template/direct/resources/css/reset.css +53 -0
  20. data/lib/erlapi/generator/template/direct/resources/favicon.ico +0 -0
  21. data/lib/erlapi/generator/template/direct/resources/i/arrows.png +0 -0
  22. data/lib/erlapi/generator/template/direct/resources/i/results_bg.png +0 -0
  23. data/lib/erlapi/generator/template/direct/resources/i/tree_bg.png +0 -0
  24. data/lib/erlapi/generator/template/direct/resources/js/jquery-1.3.2.min.js +19 -0
  25. data/lib/erlapi/generator/template/direct/resources/js/jquery-effect.js +593 -0
  26. data/lib/erlapi/generator/template/direct/resources/js/main.js +22 -0
  27. data/lib/erlapi/generator/template/direct/resources/js/searchdoc.js +628 -0
  28. data/lib/erlapi/generator/template/direct/resources/panel/index.html +71 -0
  29. data/lib/erlapi/generator/template/merge/index.rhtml +14 -0
  30. data/lib/erlapi/generator/template/shtml/_context.rhtml +164 -0
  31. data/lib/erlapi/generator/template/shtml/class.rhtml +46 -0
  32. data/lib/erlapi/generator/template/shtml/file.rhtml +37 -0
  33. data/lib/erlapi/generator/template/shtml/index.rhtml +14 -0
  34. data/lib/erlapi/generator/template/shtml/resources/apple-touch-icon.png +0 -0
  35. data/lib/erlapi/generator/template/shtml/resources/css/main.css +191 -0
  36. data/lib/erlapi/generator/template/shtml/resources/css/panel.css +383 -0
  37. data/lib/erlapi/generator/template/shtml/resources/css/reset.css +53 -0
  38. data/lib/erlapi/generator/template/shtml/resources/favicon.ico +0 -0
  39. data/lib/erlapi/generator/template/shtml/resources/i/arrows.png +0 -0
  40. data/lib/erlapi/generator/template/shtml/resources/i/results_bg.png +0 -0
  41. data/lib/erlapi/generator/template/shtml/resources/i/tree_bg.png +0 -0
  42. data/lib/erlapi/generator/template/shtml/resources/js/jquery-1.3.2.min.js +19 -0
  43. data/lib/erlapi/generator/template/shtml/resources/js/main.js +34 -0
  44. data/lib/erlapi/generator/template/shtml/resources/js/searchdoc.js +628 -0
  45. data/lib/erlapi/generator/template/shtml/resources/panel/index.html +71 -0
  46. data/lib/erlapi/helpers.rb +26 -0
  47. data/lib/erlapi/merge.rb +217 -0
  48. data/lib/erlapi/parser.rb +134 -0
  49. data/lib/erlapi/shtml.rb +351 -0
  50. data/lib/erlapi/templatable.rb +51 -0
  51. data/lib/erlapi/templates/html/direct/_context.rhtml +172 -0
  52. data/lib/erlapi/templates/html/direct/class.rhtml +40 -0
  53. data/lib/erlapi/templates/html/direct/file.rhtml +30 -0
  54. data/lib/erlapi/templates/html/direct/index.rhtml +14 -0
  55. data/lib/erlapi/templates/html/direct/resources/apple-touch-icon.png +0 -0
  56. data/lib/erlapi/templates/html/direct/resources/css/main.css +263 -0
  57. data/lib/erlapi/templates/html/direct/resources/css/panel.css +383 -0
  58. data/lib/erlapi/templates/html/direct/resources/css/reset.css +53 -0
  59. data/lib/erlapi/templates/html/direct/resources/favicon.ico +0 -0
  60. data/lib/erlapi/templates/html/direct/resources/i/arrows.png +0 -0
  61. data/lib/erlapi/templates/html/direct/resources/i/results_bg.png +0 -0
  62. data/lib/erlapi/templates/html/direct/resources/i/tree_bg.png +0 -0
  63. data/lib/erlapi/templates/html/direct/resources/js/jquery-1.3.2.min.js +19 -0
  64. data/lib/erlapi/templates/html/direct/resources/js/jquery-effect.js +593 -0
  65. data/lib/erlapi/templates/html/direct/resources/js/main.js +22 -0
  66. data/lib/erlapi/templates/html/direct/resources/js/searchdoc.js +628 -0
  67. data/lib/erlapi/templates/html/direct/resources/panel/index.html +71 -0
  68. data/lib/erlapi/templates/html/merge/index.rhtml +14 -0
  69. data/lib/erlapi/templates/html/shtml/_context.rhtml +68 -0
  70. data/lib/erlapi/templates/html/shtml/class.rhtml +25 -0
  71. data/lib/erlapi/templates/html/shtml/file.rhtml +37 -0
  72. data/lib/erlapi/templates/html/shtml/index.rhtml +14 -0
  73. data/lib/erlapi/templates/html/shtml/resources copy/apple-touch-icon.png +0 -0
  74. data/lib/erlapi/templates/html/shtml/resources copy/css/main.css +195 -0
  75. data/lib/erlapi/templates/html/shtml/resources copy/css/panel.css +383 -0
  76. data/lib/erlapi/templates/html/shtml/resources copy/css/reset.css +53 -0
  77. data/lib/erlapi/templates/html/shtml/resources copy/favicon.ico +0 -0
  78. data/lib/erlapi/templates/html/shtml/resources copy/i/arrows.png +0 -0
  79. data/lib/erlapi/templates/html/shtml/resources copy/i/results_bg.png +0 -0
  80. data/lib/erlapi/templates/html/shtml/resources copy/i/tree_bg.png +0 -0
  81. data/lib/erlapi/templates/html/shtml/resources copy/js/jquery-1.3.2.min.js +19 -0
  82. data/lib/erlapi/templates/html/shtml/resources copy/js/main.js +34 -0
  83. data/lib/erlapi/templates/html/shtml/resources copy/js/searchdoc.js +628 -0
  84. data/lib/erlapi/templates/html/shtml/resources copy/panel/index.html +71 -0
  85. data/lib/erlapi/templates/html/shtml/resources/apple-touch-icon.png +0 -0
  86. data/lib/erlapi/templates/html/shtml/resources/css/main.css +263 -0
  87. data/lib/erlapi/templates/html/shtml/resources/css/panel.css +383 -0
  88. data/lib/erlapi/templates/html/shtml/resources/css/reset.css +53 -0
  89. data/lib/erlapi/templates/html/shtml/resources/favicon.ico +0 -0
  90. data/lib/erlapi/templates/html/shtml/resources/i/arrows.png +0 -0
  91. data/lib/erlapi/templates/html/shtml/resources/i/results_bg.png +0 -0
  92. data/lib/erlapi/templates/html/shtml/resources/i/tree_bg.png +0 -0
  93. data/lib/erlapi/templates/html/shtml/resources/js/jquery-1.3.2.min.js +19 -0
  94. data/lib/erlapi/templates/html/shtml/resources/js/jquery-effect.js +593 -0
  95. data/lib/erlapi/templates/html/shtml/resources/js/main.js +22 -0
  96. data/lib/erlapi/templates/html/shtml/resources/js/searchdoc.js +620 -0
  97. data/lib/erlapi/templates/html/shtml/resources/panel/index.html +71 -0
  98. data/test/erlapi_test.rb +7 -0
  99. data/test/test_helper.rb +10 -0
  100. metadata +155 -0
@@ -0,0 +1,71 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
3
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
6
+ <head>
7
+ <title>layout</title>
8
+ <link rel="stylesheet" href="../css/reset.css" type="text/css" media="screen" charset="utf-8" />
9
+ <link rel="stylesheet" href="../css/panel.css" type="text/css" media="screen" charset="utf-8" />
10
+ <script src="search_index.js" type="text/javascript" charset="utf-8"></script>
11
+ <script src="tree.js" type="text/javascript" charset="utf-8"></script>
12
+ <script src="../js/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
13
+ <script src="../js/searchdoc.js" type="text/javascript" charset="utf-8"></script>
14
+ <script type="text/javascript" charset="utf-8">
15
+ //<![CDATA[
16
+ function placeholder() {
17
+ if (jQuery.browser.safari) return;
18
+ $('#search-label').click(function() {
19
+ $('#search').focus();
20
+ $('#search-label').hide();
21
+ });
22
+
23
+ $('#search').focus(function() {
24
+ $('#search-label').hide();
25
+ });
26
+ $('#search').blur(function() {
27
+ this.value == '' && $('#search-label').show()
28
+ });
29
+
30
+ $('#search')[0].value == '' && $('#search-label').show();
31
+ }
32
+ $(function() {
33
+ placeholder();
34
+ var panel = new Searchdoc.Panel($('#panel'), search_data, tree, top.frames[1]);
35
+ $('#search').focus();
36
+
37
+ var s = window.parent.location.search.match(/\?q=([^&]+)/);
38
+ if (s) {
39
+ s = decodeURIComponent(s[1]).replace(/\+/g, ' ');
40
+ if (s.length > 0)
41
+ {
42
+ $('#search').val(s);
43
+ panel.search(s, true);
44
+ }
45
+ }
46
+ })
47
+ //]]>
48
+ </script>
49
+ </head>
50
+ <body>
51
+ <div class="panel panel_tree" id="panel">
52
+ <div class="header">
53
+ <div>
54
+ <label for="search" id="search-label" style="display: none">Search</label>
55
+ <table>
56
+ <tr><td>
57
+ <input type="Search" placeholder="Search" autosave="searchdoc" results="10" id="search" autocomplete="off"/>
58
+ </td></tr>
59
+ </table></div>
60
+ </div>
61
+ <div class="tree">
62
+ <ul>
63
+ </ul>
64
+ </div>
65
+ <div class="result">
66
+ <ul>
67
+ </ul>
68
+ </div>
69
+ </div>
70
+ </body>
71
+ </html>
@@ -0,0 +1,26 @@
1
+ module Erlapi::Helpers
2
+ def each_letter_group(methods, &block)
3
+ group = {:name => '', :methods => []}
4
+ methods.sort{ |a, b| a.name <=> b.name }.each do |method|
5
+ gname = group_name method.name
6
+ if gname != group[:name]
7
+ yield group unless group[:methods].size == 0
8
+ group = {
9
+ :name => gname,
10
+ :methods => []
11
+ }
12
+ end
13
+ group[:methods].push(method)
14
+ end
15
+ yield group unless group[:methods].size == 0
16
+ end
17
+
18
+ protected
19
+ def group_name name
20
+ if match = name.match(/^([a-z])/i)
21
+ match[1].upcase
22
+ else
23
+ '#'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,217 @@
1
+ require 'optparse'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'json'
5
+
6
+ require 'sdoc/templatable'
7
+
8
+ class SDoc::Merge
9
+ include SDoc::Templatable
10
+
11
+ FLAG_FILE = "created.rid"
12
+
13
+ def initialize()
14
+ @names = []
15
+ @urls = []
16
+ @op_dir = 'doc'
17
+ @title = ''
18
+ @directories = []
19
+ template_dir = RDoc::Generator::SHtml.template_dir('merge')
20
+ @template_dir = Pathname.new File.expand_path(template_dir)
21
+ end
22
+
23
+ def merge(options)
24
+ parse_options options
25
+
26
+ @outputdir = Pathname.new( @op_dir )
27
+
28
+ check_directories
29
+ setup_output_dir
30
+ setup_names
31
+ copy_files
32
+ copy_docs if @urls.empty?
33
+ merge_search_index
34
+ merge_tree
35
+ generate_index_file
36
+ end
37
+
38
+ def parse_options(options)
39
+ opts = OptionParser.new do |opt|
40
+ opt.banner = "Usage: sdoc-merge [options] directories"
41
+
42
+ opt.on("-n", "--names [NAMES]", "Names of merged repositories. Comma separated") do |v|
43
+ @names = v.split(',').map{|name| name.strip }
44
+ end
45
+
46
+ opt.on("-o", "--op [DIRECTORY]", "Set the output directory") do |v|
47
+ @op_dir = v
48
+ end
49
+
50
+ opt.on("-t", "--title [TITLE]", "Set the title of merged file") do |v|
51
+ @title = v
52
+ end
53
+
54
+ opt.on("-u", "--urls [URLS]", "Paths to merged docs. If you \n" +
55
+ "set this files and classes won't be actualy copied to merged build") do |v|
56
+ @urls = v.split(' ').map{|name| name.strip }
57
+ end
58
+ end
59
+ opts.parse! options
60
+ @directories = options.dup
61
+ end
62
+
63
+ def merge_tree
64
+ tree = []
65
+ @directories.each_with_index do |dir, i|
66
+ name = @names[i]
67
+ url = @urls.empty? ? name : @urls[i]
68
+ filename = File.join dir, RDoc::Generator::SHtml::TREE_FILE
69
+ data = open(filename).read.sub(/var tree =\s*/, '')
70
+ subtree = JSON.parse data, :max_nesting => 35
71
+ item = [
72
+ name,
73
+ url + '/' + extract_index_path(dir),
74
+ '',
75
+ append_path(subtree, url)
76
+ ]
77
+ tree << item
78
+ end
79
+
80
+ dst = File.join @op_dir, RDoc::Generator::SHtml::TREE_FILE
81
+ FileUtils.mkdir_p File.dirname(dst)
82
+ File.open(dst, "w", 0644) do |f|
83
+ f.write('var tree = '); f.write(tree.to_json)
84
+ end
85
+ end
86
+
87
+ def append_path subtree, path
88
+ subtree.map do |item|
89
+ item[1] = path + '/' + item[1] unless item[1].empty?
90
+ item[3] = append_path item[3], path
91
+ item
92
+ end
93
+ end
94
+
95
+ def merge_search_index
96
+ items = []
97
+ @indexes = {}
98
+ @directories.each_with_index do |dir, i|
99
+ name = @names[i]
100
+ url = @urls.empty? ? name : @urls[i]
101
+ filename = File.join dir, RDoc::Generator::SHtml::SEARCH_INDEX_FILE
102
+ data = open(filename).read.sub(/var search_data =\s*/, '')
103
+ subindex = JSON.parse data, :max_nesting => 35
104
+ @indexes[name] = subindex
105
+
106
+ searchIndex = subindex["index"]["searchIndex"]
107
+ longSearchIndex = subindex["index"]["longSearchIndex"]
108
+ subindex["index"]["info"].each_with_index do |info, j|
109
+ info[2] = url + '/' + info[2]
110
+ info[6] = i
111
+ items << {
112
+ :info => info,
113
+ :searchIndex => searchIndex[j],
114
+ :longSearchIndex => name + ' ' + longSearchIndex[j]
115
+ }
116
+ end
117
+ end
118
+ items.sort! do |a, b|
119
+ # type (class/method/file) or name or doc part or namespace
120
+ [a[:info][5], a[:info][0], a[:info][6], a[:info][1]] <=> [b[:info][5], b[:info][0], b[:info][6], b[:info][1]]
121
+ end
122
+
123
+ index = {
124
+ :searchIndex => items.map{|item| item[:searchIndex]},
125
+ :longSearchIndex => items.map{|item| item[:longSearchIndex]},
126
+ :info => items.map{|item| item[:info]}
127
+ }
128
+ search_data = {
129
+ :index => index,
130
+ :badges => @names
131
+ }
132
+
133
+ dst = File.join @op_dir, RDoc::Generator::SHtml::SEARCH_INDEX_FILE
134
+ FileUtils.mkdir_p File.dirname(dst)
135
+ File.open(dst, "w", 0644) do |f|
136
+ f.write('var search_data = '); f.write(search_data.to_json)
137
+ end
138
+ end
139
+
140
+ def extract_index_path dir
141
+ filename = File.join dir, 'index.html'
142
+ content = File.open(filename) { |f| f.read }
143
+ match = content.match(/<frame\s+src="([^"]+)"\s+name="docwin"/mi)
144
+ if match
145
+ match[1]
146
+ else
147
+ ''
148
+ end
149
+ end
150
+
151
+ def generate_index_file
152
+ templatefile = @template_dir + 'index.rhtml'
153
+ outfile = @outputdir + 'index.html'
154
+ url = @urls.empty? ? @names[0] : @urls[0]
155
+ index_path = url + '/' + extract_index_path(@directories[0])
156
+
157
+ render_template templatefile, binding(), outfile
158
+ end
159
+
160
+ def setup_names
161
+ unless @names.size > 0
162
+ @directories.each do |dir|
163
+ name = File.basename dir
164
+ name = File.basename File.dirname(dir) if name == 'doc'
165
+ @names << name
166
+ end
167
+ end
168
+ end
169
+
170
+ def copy_docs
171
+ @directories.each_with_index do |dir, i|
172
+ name = @names[i]
173
+ index_dir = File.dirname(RDoc::Generator::SHtml::TREE_FILE)
174
+ FileUtils.mkdir_p(File.join(@op_dir, name))
175
+
176
+ Dir.new(dir).each do |item|
177
+ if File.directory?(File.join(dir, item)) && item != '.' && item != '..' && item != index_dir
178
+ FileUtils.cp_r File.join(dir, item), File.join(@op_dir, name, item), :preserve => true
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ def copy_files
185
+ dir = @directories.first
186
+ Dir.new(dir).each do |item|
187
+ if item != '.' && item != '..' && item != RDoc::Generator::SHtml::FILE_DIR && item != RDoc::Generator::SHtml::CLASS_DIR
188
+ FileUtils.cp_r File.join(dir, item), @op_dir, :preserve => true
189
+ end
190
+ end
191
+ end
192
+
193
+ def setup_output_dir
194
+ if File.exists? @op_dir
195
+ error "#{@op_dir} allready exists"
196
+ end
197
+ FileUtils.mkdir_p @op_dir
198
+ end
199
+
200
+ def check_directories
201
+ @directories.each do |dir|
202
+ unless File.exists?(File.join(dir, FLAG_FILE)) &&
203
+ File.exists?(File.join(dir, RDoc::Generator::SHtml::TREE_FILE)) &&
204
+ File.exists?(File.join(dir, RDoc::Generator::SHtml::SEARCH_INDEX_FILE))
205
+ error "#{dir} does not seem to be an sdoc directory"
206
+ end
207
+ end
208
+ end
209
+
210
+ ##
211
+ # Report an error message and exit
212
+
213
+ def error(msg)
214
+ raise RDoc::Error, msg
215
+ end
216
+
217
+ end
@@ -0,0 +1,134 @@
1
+ require 'nokogiri'
2
+
3
+ module Erlapi::Parser
4
+ class Base
5
+ attr_accessor :src_dir, :template_dir, :files, :modules, :entities_coder
6
+ def initialize(src_dir = nil)
7
+ self.files = []
8
+ self.modules = {}
9
+ self.src_dir = Pathname.new(src_dir || File.join(File.dirname(__FILE__), '..', 'src'))
10
+ self.template_dir = File.join(File.dirname(__FILE__), 'templates')
11
+ end
12
+
13
+ def parse
14
+ files.each do |file|
15
+ puts "Parsing #{file}"
16
+ doc = Nokogiri::XML(File.read(file))
17
+
18
+ mod = Erlapi::Parser::Module.new(self, doc)
19
+ self.modules[mod.title] = mod if mod.valid
20
+ end
21
+ end
22
+
23
+ def inner_xml(node)
24
+ node.is_a?(Nokogiri::XML::NodeSet) ? node.collect{|j| j.children.to_xml}.join('') : node.children.to_xml
25
+ end
26
+
27
+ def special_tags(str)
28
+ str.gsub!(/<c>(.*?)<\/c>/mi, '<tt>\1</tt>')
29
+ str.gsub!(/<code.*?>(.*?)<\/code>/mi, '<pre>\1</pre>')
30
+ str.gsub!(/<v.*?>(.*?)<\/v>/mi, '<b>\1</b><br/>')
31
+ str.gsub!(/<input.*?>(.*?)<\/input>/mi, '<tt>\1</tt><br/>')
32
+ str
33
+ end
34
+
35
+ def prepare_string(str)
36
+ str.gsub(/\\\\/, '\\')
37
+ end
38
+
39
+ def files
40
+ load_files(src_dir.realpath)
41
+ @files
42
+ end
43
+
44
+ def load_files(dir_path)
45
+ dir = Dir.new(dir_path)
46
+ dir.each do |file_name|
47
+ full_path = File.join(dir.path, file_name)
48
+ if file_name != '.' && file_name != '..'
49
+ if File.directory?(full_path)
50
+ load_files(full_path)
51
+ else
52
+ @files << full_path if File.extname(file_name) == '.xml'
53
+ end
54
+ end
55
+ end
56
+ dir.close
57
+ end
58
+
59
+ end
60
+
61
+ class Module
62
+ attr_accessor :parser, :title, :summary, :desc, :datatypes, :funcs, :sections, :valid, :doc
63
+ def initialize(parser, doc)
64
+ self.doc = doc
65
+ self.parser = parser
66
+ self.title = doc.css('module').inner_html
67
+ self.summary = doc.css('modulesummary').inner_html || ''
68
+ if self.title && self.title != ''
69
+ self.valid = true
70
+ else
71
+ self.valid = false
72
+ end
73
+ if valid
74
+ define_desc
75
+ define_sections
76
+ define_funcs
77
+ end
78
+ end
79
+
80
+ def define_desc
81
+ self.desc = self.parser.special_tags(self.parser.inner_xml(doc.css('description')))
82
+ end
83
+
84
+ def define_sections
85
+ self.sections = []
86
+ doc.css('section').each do |node|
87
+ self.sections << Erlapi::Parser::Section.new(self, node)
88
+ end
89
+ end
90
+
91
+ def define_funcs
92
+ self.funcs = []
93
+ doc.css('funcs func').each do |node|
94
+ func = Erlapi::Parser::Func.new(self, node)
95
+ self.funcs << func if func.short_name
96
+ end
97
+ end
98
+ end
99
+
100
+ class Func
101
+ attr_accessor :p_module, :short_name, :names, :summary, :types, :desc, :ref, :short_names
102
+
103
+ def initialize(p_module, node)
104
+ self.p_module = p_module
105
+ self.names = node.css('name').map {|v| v.inner_html}
106
+ if self.names
107
+ self.short_names = self.names.map { |n| n.match(/([\w\d_-]*)\(/) ? $~[1] : nil }.compact.uniq
108
+ self.short_name = self.short_names.first if self.short_names
109
+ end
110
+ self.ref = self.names.first.gsub(/[^\w]/, '')
111
+ self.summary = parser.inner_xml(node.css('fsummary'))
112
+ self.types = parser.special_tags(parser.inner_xml(node.css('type')))
113
+ self.desc = parser.special_tags(parser.inner_xml(node.css('desc')))
114
+ end
115
+
116
+ def parser
117
+ self.p_module.parser
118
+ end
119
+ end
120
+
121
+ class Section
122
+ attr_accessor :p_module, :name, :desc
123
+
124
+ def initialize(p_module, node)
125
+ self.p_module = p_module
126
+ self.name = node.css('title').inner_html
127
+ self.desc = parser.special_tags(parser.inner_xml(node))
128
+ end
129
+
130
+ def parser
131
+ self.p_module.parser
132
+ end
133
+ end
134
+ end