wikicloth 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +3 -3
- data/Rakefile +19 -11
- data/lang/de.yml +24 -0
- data/lang/en.yml +35 -0
- data/lib/wikicloth.rb +47 -26
- data/lib/wikicloth/core_ext.rb +4 -0
- data/lib/wikicloth/extension.rb +60 -0
- data/lib/wikicloth/extensions/lua.rb +82 -0
- data/lib/wikicloth/extensions/lua/COPYING +18 -0
- data/lib/wikicloth/extensions/lua/luawrapper.lua +245 -0
- data/lib/wikicloth/extensions/math.rb +39 -0
- data/lib/wikicloth/extensions/poem.rb +15 -0
- data/lib/wikicloth/extensions/references.rb +48 -0
- data/lib/wikicloth/extensions/source.rb +46 -0
- data/lib/wikicloth/i18n.rb +108 -0
- data/lib/wikicloth/namespaces.rb +62 -0
- data/lib/wikicloth/parser.rb +21 -15
- data/lib/wikicloth/section.rb +1 -5
- data/lib/wikicloth/version.rb +1 -1
- data/lib/wikicloth/wiki_buffer.rb +151 -39
- data/lib/wikicloth/wiki_buffer/html_element.rb +24 -59
- data/lib/wikicloth/wiki_buffer/link.rb +3 -2
- data/lib/wikicloth/wiki_buffer/table.rb +2 -2
- data/lib/wikicloth/wiki_buffer/var.rb +80 -28
- data/lib/wikicloth/wiki_link_handler.rb +22 -29
- data/sample_documents/lua.wiki +58 -0
- data/test/locales.yml +264 -0
- data/test/test_helper.rb +1 -0
- data/test/wiki_cloth_test.rb +142 -27
- data/wikicloth.gemspec +2 -1
- metadata +34 -8
- data/lib/wikicloth/lexer.rb +0 -105
- data/lib/wikicloth/token.rb +0 -41
data/README.textile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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
|
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]
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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 '
|
23
|
-
|
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 #{
|
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
|
data/lang/de.yml
ADDED
@@ -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}"
|
data/lang/en.yml
ADDED
@@ -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: {{%{tree}}}"
|
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}"
|
data/lib/wikicloth.rb
CHANGED
@@ -1,12 +1,20 @@
|
|
1
|
+
require 'rubygems' if RUBY_VERSION < '1.9'
|
1
2
|
require 'jcode' if RUBY_VERSION < '1.9'
|
2
|
-
require
|
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
|
-
|
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
|
-
|
58
|
-
|
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 "
|
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.
|
99
|
+
buffer.send("to_#{self.options[:output]}")
|
92
100
|
end
|
93
101
|
|
94
|
-
def
|
95
|
-
|
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
|
data/lib/wikicloth/core_ext.rb
CHANGED
@@ -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
|