gonzui 1.2-x86-mswin32-60
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.
- data/AUTHORS.txt +9 -0
- data/History.txt +5539 -0
- data/Manifest.txt +115 -0
- data/PostInstall.txt +17 -0
- data/README.rdoc +149 -0
- data/Rakefile +28 -0
- data/bin/gonzui-db +167 -0
- data/bin/gonzui-import +177 -0
- data/bin/gonzui-remove +58 -0
- data/bin/gonzui-search +68 -0
- data/bin/gonzui-server +176 -0
- data/bin/gonzui-update +53 -0
- data/data/gonzui/catalog/catalog.ja +80 -0
- data/data/gonzui/doc/favicon.ico +0 -0
- data/data/gonzui/doc/folder.png +0 -0
- data/data/gonzui/doc/gonzui.css +279 -0
- data/data/gonzui/doc/gonzui.js +111 -0
- data/data/gonzui/doc/text.png +0 -0
- data/data/gonzuirc.sample +29 -0
- data/ext/autopack/autopack.c +88 -0
- data/ext/autopack/extconf.rb +3 -0
- data/ext/delta/delta.c +147 -0
- data/ext/delta/extconf.rb +5 -0
- data/ext/texttokenizer/extconf.rb +5 -0
- data/ext/texttokenizer/texttokenizer.c +93 -0
- data/ext/xmlformatter/extconf.rb +5 -0
- data/ext/xmlformatter/xmlformatter.c +207 -0
- data/lib/gonzui.rb +59 -0
- data/lib/gonzui/apt.rb +193 -0
- data/lib/gonzui/autopack.so +0 -0
- data/lib/gonzui/bdbdbm.rb +118 -0
- data/lib/gonzui/cmdapp.rb +14 -0
- data/lib/gonzui/cmdapp/app.rb +175 -0
- data/lib/gonzui/cmdapp/search.rb +134 -0
- data/lib/gonzui/config.rb +117 -0
- data/lib/gonzui/content.rb +19 -0
- data/lib/gonzui/dbm.rb +673 -0
- data/lib/gonzui/deindexer.rb +162 -0
- data/lib/gonzui/delta.rb +49 -0
- data/lib/gonzui/delta.so +0 -0
- data/lib/gonzui/extractor.rb +347 -0
- data/lib/gonzui/fetcher.rb +309 -0
- data/lib/gonzui/gettext.rb +144 -0
- data/lib/gonzui/importer.rb +84 -0
- data/lib/gonzui/indexer.rb +316 -0
- data/lib/gonzui/info.rb +80 -0
- data/lib/gonzui/license.rb +100 -0
- data/lib/gonzui/logger.rb +48 -0
- data/lib/gonzui/monitor.rb +177 -0
- data/lib/gonzui/progressbar.rb +235 -0
- data/lib/gonzui/remover.rb +38 -0
- data/lib/gonzui/searcher.rb +330 -0
- data/lib/gonzui/searchquery.rb +235 -0
- data/lib/gonzui/searchresult.rb +111 -0
- data/lib/gonzui/texttokenizer.so +0 -0
- data/lib/gonzui/updater.rb +254 -0
- data/lib/gonzui/util.rb +415 -0
- data/lib/gonzui/vcs.rb +128 -0
- data/lib/gonzui/webapp.rb +25 -0
- data/lib/gonzui/webapp/advsearch.rb +123 -0
- data/lib/gonzui/webapp/filehandler.rb +24 -0
- data/lib/gonzui/webapp/jsfeed.rb +61 -0
- data/lib/gonzui/webapp/markup.rb +445 -0
- data/lib/gonzui/webapp/search.rb +269 -0
- data/lib/gonzui/webapp/servlet.rb +319 -0
- data/lib/gonzui/webapp/snippet.rb +155 -0
- data/lib/gonzui/webapp/source.rb +37 -0
- data/lib/gonzui/webapp/stat.rb +137 -0
- data/lib/gonzui/webapp/top.rb +63 -0
- data/lib/gonzui/webapp/uri.rb +140 -0
- data/lib/gonzui/webapp/webrick.rb +48 -0
- data/lib/gonzui/webapp/xmlformatter.so +0 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/makemanifest.rb +21 -0
- data/tasks/extconf.rake +13 -0
- data/tasks/extconf/autopack.rake +43 -0
- data/tasks/extconf/delta.rake +43 -0
- data/tasks/extconf/texttokenizer.rake +43 -0
- data/tasks/extconf/xmlformatter.rake +43 -0
- data/test/_external_tools.rb +13 -0
- data/test/_test-util.rb +142 -0
- data/test/foo/Makefile.foo +66 -0
- data/test/foo/bar.c +5 -0
- data/test/foo/bar.h +6 -0
- data/test/foo/foo.c +25 -0
- data/test/foo/foo.spec +33 -0
- data/test/test_apt.rb +42 -0
- data/test/test_autopack_extn.rb +7 -0
- data/test/test_bdbdbm.rb +79 -0
- data/test/test_cmdapp-app.rb +35 -0
- data/test/test_cmdapp-search.rb +99 -0
- data/test/test_config.rb +28 -0
- data/test/test_content.rb +15 -0
- data/test/test_dbm.rb +171 -0
- data/test/test_deindexer.rb +50 -0
- data/test/test_delta.rb +66 -0
- data/test/test_extractor.rb +78 -0
- data/test/test_fetcher.rb +75 -0
- data/test/test_gettext.rb +50 -0
- data/test/test_gonzui.rb +11 -0
- data/test/test_helper.rb +10 -0
- data/test/test_importer.rb +56 -0
- data/test/test_indexer.rb +37 -0
- data/test/test_info.rb +82 -0
- data/test/test_license.rb +49 -0
- data/test/test_logger.rb +60 -0
- data/test/test_monitor.rb +23 -0
- data/test/test_searcher.rb +37 -0
- data/test/test_searchquery.rb +27 -0
- data/test/test_searchresult.rb +43 -0
- data/test/test_texttokenizer.rb +47 -0
- data/test/test_updater.rb +95 -0
- data/test/test_util.rb +149 -0
- data/test/test_vcs.rb +61 -0
- data/test/test_webapp-markup.rb +42 -0
- data/test/test_webapp-util.rb +19 -0
- data/test/test_webapp-xmlformatter.rb +19 -0
- metadata +292 -0
@@ -0,0 +1,309 @@
|
|
1
|
+
#
|
2
|
+
# fetcher.rb - fetch contents from various sources
|
3
|
+
#
|
4
|
+
# Copyright (C) 2004-2005 Satoru Takabayashi <satoru@namazu.org>
|
5
|
+
# All rights reserved.
|
6
|
+
# This is free software with ABSOLUTELY NO WARRANTY.
|
7
|
+
#
|
8
|
+
# You can redistribute it and/or modify it under the terms of
|
9
|
+
# the GNU General Public License version 2.
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'open-uri'
|
13
|
+
require 'webrick/httputils'
|
14
|
+
require 'ftools'
|
15
|
+
|
16
|
+
module Gonzui
|
17
|
+
class FetcherError < GonzuiError; end
|
18
|
+
class FetchFailed < FetcherError; end
|
19
|
+
|
20
|
+
module Fetcher
|
21
|
+
extend Util
|
22
|
+
FetcherRegistory = {}
|
23
|
+
|
24
|
+
module_function
|
25
|
+
def new(config, source_uri, options = {})
|
26
|
+
klass = FetcherRegistory[source_uri.scheme]
|
27
|
+
if klass.nil?
|
28
|
+
raise FetcherError.new("#{source_uri.scheme}: unsupported scheme")
|
29
|
+
end
|
30
|
+
if source_uri.path.nil?
|
31
|
+
raise FetcherError.new("#{source_uri.to_s}: malformed URI")
|
32
|
+
end
|
33
|
+
fetcher = klass.new(config, source_uri, options)
|
34
|
+
if fetcher.need_extraction? # fallback to FileFetcher
|
35
|
+
extractor = fetcher.get_extractor
|
36
|
+
directory = extractor.extract
|
37
|
+
fetcher.finish
|
38
|
+
|
39
|
+
source_uri = URI.from_path(directory)
|
40
|
+
fetcher = FileFetcher.new(config, source_uri, options)
|
41
|
+
fetcher.add_finishing_proc(lambda { extractor.clean })
|
42
|
+
end
|
43
|
+
return fetcher
|
44
|
+
end
|
45
|
+
|
46
|
+
def register(klass)
|
47
|
+
FetcherRegistory[klass.scheme] = klass
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class AbstractFetcher
|
52
|
+
include Util
|
53
|
+
|
54
|
+
def initialize(config, source_uri, options = {})
|
55
|
+
@config = config
|
56
|
+
@source_uri = source_uri
|
57
|
+
@exclude_pattern = (options[:exclude_pattern] or @config.exclude_pattern)
|
58
|
+
@finishing_procs = []
|
59
|
+
@base_uri = source_uri
|
60
|
+
end
|
61
|
+
|
62
|
+
public
|
63
|
+
def add_finishing_proc (proc)
|
64
|
+
@finishing_procs.push(proc)
|
65
|
+
end
|
66
|
+
|
67
|
+
def collect
|
68
|
+
raise NotImplementedError.new
|
69
|
+
end
|
70
|
+
|
71
|
+
def exclude?(relative_path)
|
72
|
+
@exclude_pattern.match(relative_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def fetch(relative_path)
|
76
|
+
raise NotImplementedError.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def finish
|
80
|
+
@finishing_procs.each {|proc| proc.call }
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_extractor
|
84
|
+
raise NotImplementedError.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def need_extraction?
|
88
|
+
raise NotImplementedError.new
|
89
|
+
end
|
90
|
+
|
91
|
+
def package_name
|
92
|
+
File.basename(@base_uri.path)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class FileFetcher < AbstractFetcher
|
97
|
+
def self.scheme
|
98
|
+
"file"
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(config, source_uri, options)
|
102
|
+
super(config, source_uri, options)
|
103
|
+
begin
|
104
|
+
File.ftype(source_uri.path)
|
105
|
+
rescue => e
|
106
|
+
raise FetchFailed.new(e.message)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def restore_path(relative_path)
|
112
|
+
File.join(@base_uri.path, relative_path)
|
113
|
+
end
|
114
|
+
|
115
|
+
public
|
116
|
+
def need_extraction?
|
117
|
+
not File.directory?(@source_uri.path)
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_extractor
|
121
|
+
return Extractor.new(@config, @source_uri.path)
|
122
|
+
end
|
123
|
+
|
124
|
+
def fetch(relative_path)
|
125
|
+
path = restore_path(relative_path)
|
126
|
+
content = File.read(path)
|
127
|
+
mtime = File.mtime(path)
|
128
|
+
return Content.new(content, mtime, path)
|
129
|
+
end
|
130
|
+
|
131
|
+
def collect
|
132
|
+
directory = @base_uri.path
|
133
|
+
relative_paths = []
|
134
|
+
Dir.all_files(directory).map {|file_name|
|
135
|
+
next if exclude?(file_name)
|
136
|
+
relative_path = File.relative_path(file_name, directory)
|
137
|
+
relative_paths.push(relative_path)
|
138
|
+
}
|
139
|
+
return relative_paths
|
140
|
+
end
|
141
|
+
|
142
|
+
Fetcher.register(self)
|
143
|
+
end
|
144
|
+
|
145
|
+
# FIXME: very ad hoc implementation
|
146
|
+
class HTTPFetcher < AbstractFetcher
|
147
|
+
include TemporaryDirectoryUtil
|
148
|
+
def self.scheme
|
149
|
+
"http"
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize(config, source_uri, options)
|
153
|
+
super(config, source_uri, options)
|
154
|
+
begin
|
155
|
+
open(source_uri.to_s) {|f|
|
156
|
+
@content = f.read
|
157
|
+
@content_type = f.content_type
|
158
|
+
@base_uri = f.base_uri
|
159
|
+
}
|
160
|
+
rescue OpenURI::HTTPError => e
|
161
|
+
raise FetchFailed.new("#{source_uri.to_s}: #{e.message}")
|
162
|
+
end
|
163
|
+
|
164
|
+
# http://example.com/foo/index.html => http://example.com/foo/
|
165
|
+
unless /\/$/.match(@base_uri.path) #/
|
166
|
+
@base_uri.path = File.dirname(@base_uri.path) + "/"
|
167
|
+
end
|
168
|
+
set_temporary_directory(@config.temporary_directory)
|
169
|
+
end
|
170
|
+
|
171
|
+
def restore_uri(relative_path)
|
172
|
+
u = @base_uri.to_s + relative_path
|
173
|
+
URI.parse(u)
|
174
|
+
end
|
175
|
+
|
176
|
+
public
|
177
|
+
def need_extraction?
|
178
|
+
@content_type != "text/html"
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_extractor
|
182
|
+
prepare_temporary_directory
|
183
|
+
tmp_name = File.join(self.temporary_directory,
|
184
|
+
File.basename(@source_uri.path))
|
185
|
+
File.open(tmp_name, "w") {|f| f.write(@content) }
|
186
|
+
add_finishing_proc(lambda { clean_temporary_directory })
|
187
|
+
return Extractor.new(@config, tmp_name)
|
188
|
+
end
|
189
|
+
|
190
|
+
def fetch(relative_path)
|
191
|
+
uri = restore_uri(relative_path)
|
192
|
+
content = mtime = nil
|
193
|
+
open(uri.to_s) {|f|
|
194
|
+
content = f.read
|
195
|
+
mtime = f.last_modified
|
196
|
+
}
|
197
|
+
return Content.new(content, mtime)
|
198
|
+
end
|
199
|
+
|
200
|
+
def collect
|
201
|
+
relative_paths = []
|
202
|
+
@content.scan(/href=(["'])(.*?)\1/i).each {|qmark, link|
|
203
|
+
u = URI.parse(link)
|
204
|
+
next if u.path.nil?
|
205
|
+
u.path.chomp!("/")
|
206
|
+
next unless u.relative?
|
207
|
+
next if /^\./.match(u.path)
|
208
|
+
next if exclude?(u.path)
|
209
|
+
relative_paths.push(u.path)
|
210
|
+
}
|
211
|
+
return relative_paths
|
212
|
+
end
|
213
|
+
|
214
|
+
Fetcher.register(self)
|
215
|
+
end
|
216
|
+
|
217
|
+
class AptFetcher < AbstractFetcher
|
218
|
+
def self.scheme
|
219
|
+
"apt-get"
|
220
|
+
end
|
221
|
+
|
222
|
+
def need_extraction?
|
223
|
+
true
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_extractor
|
227
|
+
package_name = @source_uri.path.prechop
|
228
|
+
return AptGet.new(@config, package_name)
|
229
|
+
end
|
230
|
+
|
231
|
+
Fetcher.register(self)
|
232
|
+
end
|
233
|
+
|
234
|
+
class CVSFetcher < AbstractFetcher
|
235
|
+
def self.scheme
|
236
|
+
"cvs"
|
237
|
+
end
|
238
|
+
|
239
|
+
def need_extraction?
|
240
|
+
true
|
241
|
+
end
|
242
|
+
|
243
|
+
def get_extractor
|
244
|
+
query = WEBrick::HTTPUtils.parse_query(@source_uri.query)
|
245
|
+
prefix = query["prefix"]
|
246
|
+
mozule = query["module"]
|
247
|
+
assert_non_nil(mozule)
|
248
|
+
root = @source_uri.path
|
249
|
+
root = @source_uri.host + ":" + root if @source_uri.host
|
250
|
+
root = prefix + "@" + root if prefix
|
251
|
+
return CVS.new(@config, root, mozule)
|
252
|
+
end
|
253
|
+
|
254
|
+
Fetcher.register(self)
|
255
|
+
end
|
256
|
+
|
257
|
+
class SubversionFetcher < AbstractFetcher
|
258
|
+
def self.scheme
|
259
|
+
"svn"
|
260
|
+
end
|
261
|
+
|
262
|
+
def need_extraction?
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
def get_extractor
|
267
|
+
query = WEBrick::HTTPUtils.parse_query(@source_uri.query)
|
268
|
+
mozule = query["module"]
|
269
|
+
assert_non_nil(mozule)
|
270
|
+
uri = @source_uri.dup
|
271
|
+
uri.scheme = query["original_scheme"] if query["original_scheme"]
|
272
|
+
uri.query = nil
|
273
|
+
root = uri.to_s
|
274
|
+
# FIXME: kludge for replacing file:/home/... ->
|
275
|
+
# file:///home/... because subversion doesn't allow
|
276
|
+
# the former URI.
|
277
|
+
root.gsub!(%r!^file:/+!, "file:///") if uri.scheme == "file"
|
278
|
+
return Subversion.new(@config, root, mozule)
|
279
|
+
end
|
280
|
+
|
281
|
+
Fetcher.register(self)
|
282
|
+
end
|
283
|
+
|
284
|
+
class GitFetcher < AbstractFetcher
|
285
|
+
def self.scheme
|
286
|
+
"git"
|
287
|
+
end
|
288
|
+
|
289
|
+
def need_extraction?
|
290
|
+
true
|
291
|
+
end
|
292
|
+
|
293
|
+
def get_extractor
|
294
|
+
query = WEBrick::HTTPUtils.parse_query(@source_uri.query)
|
295
|
+
mozule = query["module"]
|
296
|
+
uri = @source_uri.dup
|
297
|
+
uri.scheme = query["original_scheme"] if query["original_scheme"]
|
298
|
+
uri.query = nil
|
299
|
+
root = uri.to_s
|
300
|
+
# FIXME: kludge for replacing file:/home/... ->
|
301
|
+
# file:///home/... because git doesn't allow
|
302
|
+
# the former URI.
|
303
|
+
root.gsub!(%r!^file:/+!, "file:///") if uri.scheme == "file"
|
304
|
+
return Git.new(@config, root, mozule)
|
305
|
+
end
|
306
|
+
|
307
|
+
Fetcher.register(self)
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#
|
2
|
+
# gettext.rb - a simple gettext-like module
|
3
|
+
#
|
4
|
+
# Copyright (C) 2004-2005 Satoru Takabayashi <satoru@namazu.org>
|
5
|
+
# All rights reserved.
|
6
|
+
# This is free software with ABSOLUTELY NO WARRANTY.
|
7
|
+
#
|
8
|
+
# You can redistribute it and/or modify it under the terms of
|
9
|
+
# the GNU General Public License version 2.
|
10
|
+
#
|
11
|
+
|
12
|
+
module Gonzui
|
13
|
+
module GetText
|
14
|
+
def gettext(text)
|
15
|
+
return text unless @gettext_catalog
|
16
|
+
return (@gettext_catalog[text] or text)
|
17
|
+
end
|
18
|
+
alias :_ :gettext
|
19
|
+
|
20
|
+
def gettext_noop(text)
|
21
|
+
text
|
22
|
+
end
|
23
|
+
alias :N_ :gettext_noop
|
24
|
+
|
25
|
+
def set_catalog(catalog)
|
26
|
+
@gettext_catalog = catalog
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_catalog(file_name)
|
30
|
+
return eval(File.read(file_name))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class CatalogRepository
|
35
|
+
include GetText
|
36
|
+
|
37
|
+
def initialize(directory)
|
38
|
+
@catalogs = {}
|
39
|
+
Dir.entries(directory).each {|entry|
|
40
|
+
file_name = File.join(directory, entry)
|
41
|
+
if m = /^catalog\.([\w.-]+)$/.match(File.basename(file_name))
|
42
|
+
lang = m[1]
|
43
|
+
catalog = load_catalog(file_name)
|
44
|
+
@catalogs[lang] = catalog
|
45
|
+
end
|
46
|
+
}
|
47
|
+
@catalogs["en"] = Hash.new {|h, k| k }
|
48
|
+
end
|
49
|
+
|
50
|
+
public
|
51
|
+
def choose(lang_name)
|
52
|
+
@catalogs[lang_name]
|
53
|
+
end
|
54
|
+
|
55
|
+
def each
|
56
|
+
@catalogs.each {|lang_name, catalog|
|
57
|
+
yield(lang_name, catalog)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class CatalogValidator
|
64
|
+
def initialize(source_file_name, messages)
|
65
|
+
@source_file_name = source_file_name
|
66
|
+
@gettext_catalog = messages
|
67
|
+
@error_messages = []
|
68
|
+
end
|
69
|
+
attr_reader :error_messages
|
70
|
+
|
71
|
+
def read_file_with_numbering(file_name)
|
72
|
+
content = ''
|
73
|
+
File.open(file_name).each_with_index {|line, idx|
|
74
|
+
lineno = idx + 1
|
75
|
+
content << line.gsub(/\bN?_\(/, "_[#{lineno}](")
|
76
|
+
}
|
77
|
+
content
|
78
|
+
end
|
79
|
+
|
80
|
+
def collect_messages(content)
|
81
|
+
messages = []
|
82
|
+
while content.sub!(/\bN?_\[(\d+)\]\(("(?:\\"|.)*?").*?\)/m, "")
|
83
|
+
lineno = $1.to_i
|
84
|
+
message = eval($2)
|
85
|
+
messages.push([lineno, message])
|
86
|
+
end
|
87
|
+
messages
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate
|
91
|
+
@gettext_catalog or return
|
92
|
+
content = read_file_with_numbering(@source_file_name)
|
93
|
+
messages = collect_messages(content)
|
94
|
+
messages.each {|lineno, message|
|
95
|
+
translated_message = @gettext_catalog[message]
|
96
|
+
if not translated_message
|
97
|
+
message =
|
98
|
+
sprintf("%s:%d: %s", @source_file_name, lineno, message.inspect)
|
99
|
+
@error_messages.push(message)
|
100
|
+
elsif message.count("%") != translated_message.count("%")
|
101
|
+
message = sprintf("%s:%d: %s => # of %% mismatch.",
|
102
|
+
@source_file_name,
|
103
|
+
lineno, message.inspect, translated_message)
|
104
|
+
@error_messages.push(message)
|
105
|
+
end
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def ok?
|
110
|
+
@error_messages.empty?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if __FILE__ == $0
|
116
|
+
include Gonzui
|
117
|
+
include Gonzui::GetText
|
118
|
+
|
119
|
+
def main
|
120
|
+
if ARGV.length < 2
|
121
|
+
puts "usage: ruby catalog-validator.rb <catalog directory> <source...>"
|
122
|
+
exit
|
123
|
+
end
|
124
|
+
|
125
|
+
catalog_directory = ARGV.shift
|
126
|
+
catalog_repository = CatalogRepository.new(catalog_directory)
|
127
|
+
|
128
|
+
ok = true
|
129
|
+
catalog_repository.each {|lang_name, catalog|
|
130
|
+
set_catalog(catalog)
|
131
|
+
ARGV.each {|source_file|
|
132
|
+
validator = CatalogValidator.new(source_file, catalog)
|
133
|
+
validator.validate
|
134
|
+
validator.error_messages.each {|message|
|
135
|
+
printf("%s: %s\n", lang_name, message)
|
136
|
+
}
|
137
|
+
ok = (ok and validator.ok?)
|
138
|
+
}
|
139
|
+
}
|
140
|
+
if ok then exit else exit(1) end
|
141
|
+
end
|
142
|
+
|
143
|
+
main
|
144
|
+
end
|