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.
- 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
|