wikicloth 0.7.1 → 0.8.0

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.
@@ -1,4 +1,4 @@
1
- h2. WikiCloth "!https://secure.travis-ci.org/nricciar/wikicloth.png!":http://travis-ci.org/nricciar/wikicloth
1
+ h1. WikiCloth "!https://secure.travis-ci.org/nricciar/wikicloth.png!":http://travis-ci.org/nricciar/wikicloth
2
2
 
3
3
  Ruby implementation of the MediaWiki markup language.
4
4
 
@@ -16,7 +16,7 @@ h2. Supports
16
16
  ** "Tables":https://github.com/nricciar/wikicloth/wiki/Tables
17
17
  ** Table of Contents [<code>__NOTOC__, __FORCETOC__, __TOC__</code>]
18
18
  * <code><code>,<nowiki>,<pre></code> (disable wiki markup)
19
- * <ref> and <references/> support
19
+ * <code><ref></code> and <code><references/></code> support
20
20
  * "html sanitization":https://github.com/nricciar/wikicloth/wiki/Html-Sanitization
21
21
 
22
22
  For more information about the MediaWiki markup see "http://www.mediawiki.org/wiki/Markup_spec":http://www.mediawiki.org/wiki/Markup_spec
@@ -62,7 +62,7 @@ Most features of WikiCloth can be overriden as needed...
62
62
 
63
63
  @wiki = WikiParser.new({
64
64
  :params => { "PAGENAME" => "Testing123" },
65
- :data => "{{hello|world}} From {{ PAGENAME }} -- [www.google.com]&quot;
65
+ :data => "{{hello|world}} From {{ PAGENAME }} -- [www.google.com]";
66
66
  })
67
67
 
68
68
  @wiki.to_html =>
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require 'rubygems'
1
2
  require 'rake'
2
3
  require File.join(File.dirname(__FILE__),'init')
3
4
 
@@ -10,22 +11,29 @@ Rake::TestTask.new(:test) do |test|
10
11
  test.verbose = true
11
12
  end
12
13
 
13
- require 'rcov/rcovtask'
14
- Rcov::RcovTask.new do |test|
15
- test.libs << 'test'
16
- test.pattern = 'test/**/test_*.rb'
17
- test.verbose = true
14
+ if RUBY_VERSION =~ /^1\.9/
15
+ require 'simplecov'
16
+ desc "Code coverage detail"
17
+ task :simplecov do
18
+ ENV['COVERAGE'] = "true"
19
+ Rake::Task['spec'].execute
20
+ end
21
+ else
22
+ require 'rcov/rcovtask'
23
+ Rcov::RcovTask.new do |test|
24
+ test.libs << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
18
28
  end
19
29
 
20
30
  task :default => :test
21
31
 
22
- require 'rake/rdoctask'
23
- Rake::RDocTask.new do |rdoc|
24
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
25
-
32
+ require 'rdoc/task'
33
+ RDoc::Task.new do |rdoc|
26
34
  rdoc.rdoc_dir = 'rdoc'
27
- rdoc.title = "wikicloth #{version}"
35
+ rdoc.title = "wikicloth #{WikiCloth::VERSION}"
28
36
  rdoc.rdoc_files.include('README*')
29
- rdoc.rdoc_files.include('LICENSE')
37
+ rdoc.rdoc_files.include('MIT-LICENSE')
30
38
  rdoc.rdoc_files.include('lib/**/*.rb')
31
39
  end
@@ -0,0 +1,24 @@
1
+ :de:
2
+ namespaces:
3
+ media: "Medium"
4
+ file: "Datei"
5
+ category: "Kategorie"
6
+ template: "Vorlage"
7
+ special: "Spezial"
8
+ talk: "Diskussion"
9
+ help: "Hilfe"
10
+
11
+ languages:
12
+ en: "Englisch"
13
+ de: "Deutsch"
14
+
15
+ behavior_switches:
16
+ notoc: "KEIN_INHALTSVERZEICHNIS"
17
+ toc: "INHALTSVERZEICHNIS"
18
+ forcetoc: "INHALTSVERZEICHNIS_ERZWINGEN"
19
+ noeditsection: "ABSCHNITTE_NICHT_BEARBEITEN"
20
+ editsection: "ABSCHNITTE_BEARBEITEN"
21
+
22
+ table of contents: "Inhaltsverzeichnis"
23
+ edit: "Bearbeiten"
24
+ edit section: "Abschnitt bearbeiten: %{name}"
@@ -0,0 +1,35 @@
1
+ en:
2
+ namespaces:
3
+ media: "Media"
4
+ file: "File,Image"
5
+ category: "Category"
6
+ special: "Special"
7
+ template: "Template"
8
+ talk: "Talk"
9
+ help: "Help"
10
+
11
+ languages:
12
+ en: "English"
13
+ de: "German"
14
+
15
+ behavior_switches:
16
+ notoc: "NOTOC"
17
+ toc: "TOC"
18
+ forcetoc: "FORCETOC"
19
+ noeditsection: "NOEDITSECTION"
20
+ editsection: "EDITSECTION"
21
+
22
+ table of contents: "Table of Contents"
23
+ edit: "edit"
24
+ edit section: "Edit section: %{name}"
25
+ template loop detected: "Template loop detected: &#123;&#123;%{tree}&#125;&#125;"
26
+ expression error: "Expression error: %{error}"
27
+ lang attribute is required: "lang attribute is required"
28
+ unknown lang: "unknown lang '%{lang}'"
29
+ unable to parse mathml: "Unable to parse MathML: %{error}"
30
+ blahtex binary not found: "%{path} binary not found"
31
+ unknown function: "unknown function '%{function}'"
32
+ lua disabled: "Lua extension not configured"
33
+ max lines of code: "Maximum lines of code limit reached"
34
+ recursion limit reached: "Recursion limit reached"
35
+ unknown error on line: "Unknown error on line %{line} row %{row}: %{tree}"
@@ -1,12 +1,20 @@
1
+ require 'rubygems' if RUBY_VERSION < '1.9'
1
2
  require 'jcode' if RUBY_VERSION < '1.9'
2
- require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "core_ext")
3
+ require 'builder'
4
+ # if i18n gem loaded use it instead
5
+ require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "i18n") unless defined?(I18n)
6
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) if defined?(I18n::Backend::Simple)
7
+ I18n.load_path += Dir[File.join(File.expand_path(File.dirname(__FILE__)), "../lang/*.yml")].collect { |f| f }
8
+
3
9
  require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "version")
10
+ require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "core_ext")
11
+ require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "namespaces")
12
+ require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "extension")
13
+ require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "section")
4
14
  require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "wiki_buffer")
5
15
  require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "wiki_link_handler")
6
16
  require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "parser")
7
- require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "section")
8
- require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "token")
9
- require File.join(File.expand_path(File.dirname(__FILE__)), "wikicloth", "lexer")
17
+
10
18
  String.send(:include, ExtendedString)
11
19
 
12
20
  module WikiCloth
@@ -14,6 +22,8 @@ module WikiCloth
14
22
  class WikiCloth
15
23
 
16
24
  def initialize(opt={})
25
+ self.options.merge!(opt)
26
+ self.options[:extensions] ||= []
17
27
  self.options[:link_handler] = opt[:link_handler] unless opt[:link_handler].nil?
18
28
  self.load(opt[:data],opt[:params]) unless opt[:data].nil?
19
29
  @current_line = 1
@@ -49,15 +59,13 @@ module WikiCloth
49
59
  self.params = p
50
60
  end
51
61
 
52
- def sections
53
- @sections ||= [Section.new]
54
- end
55
-
56
62
  def render(opt={})
57
- noedit = false
58
- self.params.merge!({ 'WIKI_VERSION' => ::WikiCloth::VERSION, 'RUBY_VERSION' => RUBY_VERSION })
59
- self.options = { :fast => true, :output => :html, :link_handler => self.link_handler, :params => self.params, :sections => self.sections }.merge(opt)
63
+ self.options = { :noedit => false, :locale => I18n.default_locale, :fast => true, :output => :html, :link_handler => self.link_handler,
64
+ :params => self.params, :sections => self.sections }.merge(self.options).merge(opt)
60
65
  self.options[:link_handler].params = options[:params]
66
+
67
+ I18n.locale = self.options[:locale]
68
+
61
69
  data = self.sections.collect { |s| s.render(self.options) }.join
62
70
  data.gsub!(/<!--(.|\s)*?-->/,"")
63
71
  data << "\n" if data.last(1) != "\n"
@@ -83,25 +91,20 @@ module WikiCloth
83
91
  end
84
92
  rescue => err
85
93
  debug_tree = buffer.buffers.collect { |b| b.debug }.join("-->")
86
- puts "Unknown error on line #{@current_line} row #{@current_row}: #{debug_tree}"
94
+ puts I18n.t("unknown error on line", :line => @current_line, :row => @current_row, :tree => debug_tree)
87
95
  raise err
88
96
  end
89
97
 
90
98
  buffer.eof()
91
- buffer.to_s
99
+ buffer.send("to_#{self.options[:output]}")
92
100
  end
93
101
 
94
- def add_current_char(buffer,c)
95
- if c == "\n"
96
- @current_line += 1
97
- @current_row = 1
98
- else
99
- @current_row += 1
100
- end
101
- buffer.add_char(c)
102
+ def sections
103
+ @sections ||= [Section.new]
102
104
  end
105
+
103
106
  def to_html(opt={})
104
- self.render(opt)
107
+ self.render(opt.merge(:output => :html))
105
108
  end
106
109
 
107
110
  def link_handler
@@ -112,7 +115,29 @@ module WikiCloth
112
115
  @page_params ||= {}
113
116
  end
114
117
 
118
+ def options
119
+ @options ||= {}
120
+ end
121
+
122
+ def method_missing(method, *args)
123
+ if self.link_handler.respond_to?(method)
124
+ self.link_handler.send(method, *args)
125
+ else
126
+ super(method, *args)
127
+ end
128
+ end
129
+
115
130
  protected
131
+ def add_current_char(buffer,c)
132
+ if c == "\n"
133
+ @current_line += 1
134
+ @current_row = 1
135
+ else
136
+ @current_row += 1
137
+ end
138
+ buffer.add_char(c)
139
+ end
140
+
116
141
  def sections=(val)
117
142
  @sections = val
118
143
  end
@@ -129,10 +154,6 @@ module WikiCloth
129
154
  @options = val
130
155
  end
131
156
 
132
- def options
133
- @options ||= {}
134
- end
135
-
136
157
  def params=(val)
137
158
  @page_params = val
138
159
  end
@@ -38,6 +38,10 @@ module ExtendedString
38
38
  respond_to?(:empty?) ? empty? : !self
39
39
  end
40
40
 
41
+ def addslashes
42
+ self.gsub(/['"\\\x0]/,'\\\\\0');
43
+ end
44
+
41
45
  def to_slug
42
46
  self.gsub(/\W+/, '-').gsub(/^-+/,'').gsub(/-+$/,'').downcase
43
47
  end
@@ -0,0 +1,60 @@
1
+ module WikiCloth
2
+ class Extension
3
+
4
+ def initialize(options={})
5
+ @options = options
6
+ end
7
+
8
+ class << self
9
+
10
+ def html_elements
11
+ @@html_elements ||= {}
12
+ end
13
+
14
+ def functions
15
+ @@functions ||= {}
16
+ end
17
+
18
+ def element(*args,&block)
19
+ options = args.last.is_a?(Hash) ? args.pop : {}
20
+ key = args.shift
21
+
22
+ html_elements[key] = { :klass => self, :block => block, :options => {
23
+ :skip_html => false, :run_globals => true }.merge(options) }
24
+ end
25
+
26
+ def skip_html?(elem)
27
+ return true if !element_exists?(elem)
28
+ html_elements[elem][:options][:skip_html]
29
+ end
30
+
31
+ def run_globals?(elem)
32
+ return false if !element_exists?(elem)
33
+ html_elements[elem][:options][:run_globals]
34
+ end
35
+
36
+ def element_exists?(elem)
37
+ html_elements.has_key?(elem)
38
+ end
39
+
40
+ def function(name,&block)
41
+ functions[name] = { :klass => self, :block => block }
42
+ end
43
+
44
+ def function_exists?(name)
45
+ functions.has_key?(name)
46
+ end
47
+
48
+ protected
49
+ def html_elements=(val)
50
+ @@html_elements = val
51
+ end
52
+
53
+ def functions=(val)
54
+ @@functions = val
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,82 @@
1
+ begin
2
+ require 'rubyluabridge'
3
+ DISABLE_LUA = false
4
+ rescue LoadError
5
+ DISABLE_LUA = true
6
+ end
7
+
8
+ module WikiCloth
9
+ class LuaExtension < Extension
10
+
11
+ # <lua var1="value" ...>lua code</lua>
12
+ #
13
+ element 'lua', :skip_html => true, :run_globals => false do |buffer|
14
+ init_lua
15
+ unless @options[:disable_lua]
16
+ begin
17
+ arglist = ''
18
+ buffer.element_attributes.each do |key,value|
19
+ arglist += "#{key} = '#{value.addslashes}';"
20
+ end
21
+ lua_eval("#{arglist}\n#{buffer.element_content}").to_s
22
+ rescue => err
23
+ "<span class=\"error\">#{err.message}</span>"
24
+ end
25
+ else
26
+ "<!-- #{I18n.t('lua disabled')} -->"
27
+ end
28
+ end
29
+
30
+ # {{#luaexpr:lua expression}}
31
+ #
32
+ function '#luaexpr' do |params|
33
+ init_lua
34
+ unless @options[:disable_lua]
35
+ begin
36
+ lua_eval("print(#{params.first})").to_s
37
+ rescue => err
38
+ "<span class=\"error\">#{err.message}</span>"
39
+ end
40
+ else
41
+ "<!-- #{I18n.t('lua disabled')} -->"
42
+ end
43
+ end
44
+
45
+ protected
46
+ def init_lua
47
+ if @options[:disable_lua].nil?
48
+ begin
49
+ @options[:disable_lua] ||= DISABLE_LUA
50
+ lua_max_lines = @options[:lua_max_lines] || 1000000
51
+ lua_max_calls = @options[:lua_max_calls] || 20000
52
+
53
+ unless @options[:disable_lua]
54
+ @options[:luabridge] = Lua::State.new
55
+ @options[:luabridge].eval(File.read(File.join(File.expand_path(File.dirname(__FILE__)), "lua", "luawrapper.lua")))
56
+ @options[:luabridge].eval("wrap = make_wrapper(#{lua_max_lines},#{lua_max_calls})")
57
+ end
58
+ rescue
59
+ @options[:disable_lua] = true
60
+ end
61
+ end
62
+ end
63
+
64
+ def lua_eval(code)
65
+ @options[:luabridge]['chunkstr'] = code
66
+ @options[:luabridge].eval("res, err = wrap(chunkstr, env, hook)")
67
+ unless @options[:luabridge]['err'].nil?
68
+ if @options[:luabridge]['err'] =~ /LOC_LIMIT/
69
+ "<span class=\"error\">#{I18n.t("max lines of code")}</span>"
70
+ elsif @options[:luabridge]['err'] =~ /RECURSION_LIMIT/
71
+ "<span class=\"error\">#{I18n.t("recursion limit reached")}</span>"
72
+ else
73
+ "<span class=\"error\">#{@options[:luabridge]['err']}</span>"
74
+ end
75
+ nil
76
+ else
77
+ @options[:luabridge]['res']
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,18 @@
1
+ Lua parser extensions for MediaWiki
2
+ Copyright (C) 2008 Fran Rogers
3
+
4
+ This software is provided 'as-is', without any express or implied
5
+ warranty. In no event will the authors be held liable for any damages
6
+ arising from the use of this software.
7
+
8
+ Permission is granted to anyone to use this software for any purpose,
9
+ including commercial applications, and to alter it and redistribute it
10
+ freely, subject to the following restrictions:
11
+
12
+ 1. The origin of this software must not be misrepresented; you must not
13
+ claim that you wrote the original software. If you use this software
14
+ in a product, an acknowledgment in the product documentation would be
15
+ appreciated but is not required.
16
+ 2. Altered source versions must be plainly marked as such, and must not be
17
+ misrepresented as being the original software.
18
+ 3. This notice may not be removed or altered from any source distribution.
@@ -0,0 +1,245 @@
1
+ -- Lua parser extensions for MediaWiki - Wrapper for Lua interpreter
2
+ -- (c) 2008 Fran Rogers - see 'COPYING' for license
3
+
4
+ -- Creates a new sandbox environment for scripts to safely run in.
5
+ function make_sandbox()
6
+ -- Dummy function that returns nil, to quietly replace unsafe functions
7
+ local function dummy(...)
8
+ return nil
9
+ end
10
+
11
+ -- Deep-copy an object; optionally replace all its leaf members with the
12
+ -- value 'override' if it's non-nil
13
+ local function deepcopy(object, override)
14
+ local lookup_table = {}
15
+ local function _copy(object, override)
16
+ if type(object) ~= "table" then
17
+ return object
18
+ elseif lookup_table[object] then
19
+ return lookup_table[object]
20
+ end
21
+ local new_table = {}
22
+ lookup_table[object] = new_table
23
+ for index, value in pairs(object) do
24
+ if override ~= nil then
25
+ value = override
26
+ end
27
+ new_table[_copy(index)] = _copy(value, override)
28
+ end
29
+ return setmetatable(new_table, _copy(getmetatable(object), override))
30
+ end
31
+ return _copy(object, override)
32
+ end
33
+
34
+ -- Our new environment
35
+ local env = {}
36
+
37
+ -- "_OUTPUT" will accumulate the results of print() and friends
38
+ env._OUTPUT = ""
39
+
40
+ -- _OUTPUT wrapper for io.write()
41
+ local function writewrapper(...)
42
+ local out = ""
43
+ for n = 1, select("#", ...) do
44
+ if out == "" then
45
+ out = tostring(select(n, ...))
46
+ else
47
+ out = out .. tostring(select(n, ...))
48
+ end
49
+ end
50
+ env._OUTPUT = env._OUTPUT .. out
51
+ end
52
+
53
+ -- _OUTPUT wrapper for io.stdout:output()
54
+ local function outputwrapper(file)
55
+ if file == nil then
56
+ local file = {}
57
+ file.close = dummy
58
+ file.lines = dummy
59
+ file.read = dummy
60
+ file.flush = dummy
61
+ file.seek = dummy
62
+ file.setvbuf = dummy
63
+ function file:write(...) writewrapper(...); end
64
+ return file
65
+ else
66
+ return nil
67
+ end
68
+ end
69
+
70
+ -- _OUTPUT wrapper for print()
71
+ local function printwrapper(...)
72
+ local out = ""
73
+ for n = 1, select("#", ...) do
74
+ if out == "" then
75
+ out = tostring(select(n, ...))
76
+ else
77
+ out = out .. '\t' .. tostring(select(n, ...))
78
+ end
79
+ end
80
+ env._OUTPUT =env._OUTPUT .. out .. "\n"
81
+ end
82
+
83
+ -- Safe wrapper for loadstring()
84
+ local oldloadstring = loadstring
85
+ local function safeloadstring(s, chunkname)
86
+ local f, message = oldloadstring(s, chunkname)
87
+ if not f then
88
+ return f, message
89
+ end
90
+ setfenv(f, getfenv(2))
91
+ return f
92
+ end
93
+
94
+ -- Populate the sandbox environment
95
+ env.assert = _G.assert
96
+ env.error = _G.error
97
+ env._G = env
98
+ env.ipairs = _G.ipairs
99
+ env.loadstring = safeloadstring
100
+ env.next = _G.next
101
+ env.pairs = _G.pairs
102
+ env.pcall = _G.pcall
103
+ env.print = printwrapper
104
+ env.write = writewrapper
105
+ env.select = _G.select
106
+ env.tonumber = _G.tonumber
107
+ env.tostring = _G.tostring
108
+ env.type = _G.type
109
+ env.unpack = _G.unpack
110
+ env._VERSION = _G._VERSION
111
+ env.xpcall = _G.xpcall
112
+ env.coroutine = deepcopy(_G.coroutine)
113
+ env.string = deepcopy(_G.string)
114
+ env.string.dump = nil
115
+ env.table = deepcopy(_G.table)
116
+ env.math = deepcopy(_G.math)
117
+ env.io = {}
118
+ env.io.write = writewrapper
119
+ env.io.flush = dummy
120
+ env.io.type = typewrapper
121
+ env.io.output = outputwrapper
122
+ env.io.stdout = outputwrapper()
123
+ env.os = {}
124
+ env.os.clock = _G.os.clock
125
+ -- env.os.date = _G.os.date
126
+ env.os.difftime = _G.os.difftime
127
+ env.os.time = _G.os.time
128
+
129
+ -- Return the new sandbox environment
130
+ return env
131
+ end
132
+
133
+ -- Creates a new debug hook that aborts with 'error("LOC_LIMIT")' after
134
+ -- 'maxlines' lines have been passed, or 'error("RECURSION_LIMIT")' after
135
+ -- 'maxcalls' levels of recursion have been entered.
136
+ function make_hook(maxlines, maxcalls, diefunc)
137
+ local lines = 0
138
+ local calls = 0
139
+ function _hook(event, ...)
140
+ if event == "line" then
141
+ lines = lines + 1
142
+ if lines > maxlines then
143
+ error("LOC_LIMIT")
144
+ end
145
+ elseif event == "call" then
146
+ calls = calls + 1
147
+ if calls > maxcalls then
148
+ error("RECURSION_LIMIT")
149
+ end
150
+ elseif event == "return" then
151
+ calls = calls - 1
152
+ end
153
+ end
154
+ return _hook
155
+ end
156
+
157
+ -- Creates and returns a function, 'wrap(input)', which reads a string into
158
+ -- a Lua chunk and executes it in a persistent sandbox environment, returning
159
+ -- 'output, err' where 'output' is the combined output of print() and friends
160
+ -- from within the chunk and 'err' is either nil or an error incurred while
161
+ -- executing the chunk; or halting after 'maxlines' lines or 'maxcalls' levels
162
+ -- of recursion.
163
+ function make_wrapper(maxlines, maxcalls)
164
+ -- Create the debug hook and sandbox environment.
165
+ local hook = make_hook(maxlines, maxcalls)
166
+ local env = make_sandbox()
167
+
168
+ -- The actual 'wrap()' function.
169
+ -- All of the above variables will be bound in its closure.
170
+ function _wrap(chunkstr)
171
+ local chunk, err, done
172
+ -- Clear any leftover output from the last call
173
+ env._OUTPUT = ""
174
+ err = nil
175
+
176
+ -- Load the string into a chunk; fail on error
177
+ chunk, err = loadstring(chunkstr)
178
+ if err ~= nil then
179
+ return nil, err
180
+ end
181
+
182
+ -- Set the chunk's environment, enable the debug hook, and execute it
183
+ setfenv(chunk, env)
184
+ co = coroutine.create(chunk)
185
+ debug.sethook(co, hook, "crl")
186
+ done, err = coroutine.resume(co)
187
+
188
+ if done == true then
189
+ err = nil
190
+ end
191
+
192
+ -- Collect and return the results
193
+ return env._OUTPUT, err
194
+ end
195
+ return _wrap
196
+ end
197
+
198
+ -- Listen on stdin for Lua chunks, parse and execute them, and print the
199
+ -- results of each on stdout.
200
+ function main(arg)
201
+ if #arg ~= 2 then
202
+ io.stderr:write(string.format("usage: %s MAXLINES MAXCALLS\n", arg[0]))
203
+ os.exit(1)
204
+ end
205
+
206
+ -- Create a wrapper function, wrap()
207
+ local wrap = make_wrapper(tonumber(arg[1]), tonumber(arg[2]))
208
+
209
+ -- Turn off buffering, and loop through the input
210
+ io.stdout:setvbuf("no")
211
+ while true do
212
+ -- Read in a chunk
213
+ local chunkstr = ""
214
+ while true do
215
+ local line = io.stdin:read("*l")
216
+ if chunkstr == "" and line == nil then
217
+ -- On EOF, exit.
218
+ os.exit(0)
219
+ elseif line == "." or line == nil then
220
+ -- Finished this chunk; move on to the next step
221
+ break
222
+ elseif chunkstr ~= "" then
223
+ chunkstr = chunkstr .. "\n" .. line
224
+ else
225
+ chunkstr = line
226
+ end
227
+ end
228
+
229
+ -- Parse and execute the chunk
230
+ local res, err
231
+ res, err = wrap(chunkstr, env, hook)
232
+
233
+ -- Write out the results
234
+ if err == nil then
235
+ io.stdout:write("'", res, "', true\n.\n")
236
+ else
237
+ io.stdout:write("'", err, "', false\n.\n")
238
+ end
239
+ end
240
+ end
241
+
242
+ -- If called as a script instead of imported as a library, run main().
243
+ if arg ~= nil then
244
+ main(arg)
245
+ end