add a cool lua repl
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{Makefile,*.makefile}]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
doc/
|
||||
*.tar.gz
|
||||
*.rock
|
||||
@@ -0,0 +1,2 @@
|
||||
--ext .lua
|
||||
--exec lua
|
||||
@@ -0,0 +1,31 @@
|
||||
language: generic
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
- LUA=5.1 LUAROCKS=2.3.0
|
||||
- LUA=5.1.5 LUAROCKS=2.3.0
|
||||
- LUA=5.2.0 LUAROCKS=2.3.0
|
||||
- LUA=5.2.4 LUAROCKS=2.3.0
|
||||
- LUA=5.3.0 LUAROCKS=2.3.0
|
||||
- LUA=5.3.3 LUAROCKS=2.3.0
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
directories:
|
||||
- $HOME/luas/
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
|
||||
before_install:
|
||||
- luarocks --local install vert
|
||||
|
||||
install:
|
||||
- ~/.luarocks/bin/vert init --lua-version=$LUA --luarocks-version=$LUAROCKS $HOME/luas/$LUA
|
||||
- source $HOME/luas/$LUA/bin/activate
|
||||
- luarocks install lua-testmore
|
||||
|
||||
script:
|
||||
- make test
|
||||
@@ -0,0 +1,17 @@
|
||||
Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,71 @@
|
||||
NEXT
|
||||
|
||||
0.10 Jan 14 2022
|
||||
--- Bug Fixes ---
|
||||
- Fixed issue where current buffer is stuck after certain syntax errors (GH #61, thanks Justin Blanchard!)
|
||||
|
||||
0.9 Oct 30 2018
|
||||
--- Bug Fixes ---
|
||||
- Fixed rlwrap plugin under Lua 5.2 and greater (GH #57)
|
||||
|
||||
--- Features/Enhancements ---
|
||||
- Improve support for Lua 5.3 (thanks, Iqbal Ansari!)
|
||||
|
||||
0.8
|
||||
Aug 23 2015
|
||||
--- Bug Fixes ---
|
||||
- Use package.searchers if package.loaders is not available (thanks, Simon Cozens!)
|
||||
|
||||
--- Backwards Incompatible Changes ---
|
||||
- The default plugin bundle is no longer loaded if an rcfile is present. See
|
||||
https://github.com/hoelzro/lua-repl/blob/0.8/README.md#removal-of-default-plugins-in-08
|
||||
for details.
|
||||
|
||||
0.7 May 22 2015
|
||||
--- Features/Enhancements ---
|
||||
- iffeature is implemented, thanks to Henry Kielmann!
|
||||
- loadplugin now returns any values returned by the plugin function.
|
||||
|
||||
--- Deprecations ---
|
||||
- 0.8 will break backwards compatability due a change in rcfile handling;
|
||||
please consult the README for details.
|
||||
|
||||
--- Plugins ---
|
||||
- we now have a filename completion plugin, thanks to Henry Kielmann!
|
||||
- rlwrap functionality has been moved into a plugin.
|
||||
|
||||
0.6 Oct 18 2014
|
||||
--- Bug Fixes ---
|
||||
- Fix rcfile plugin for Lua 5.2
|
||||
|
||||
0.5 Oct 17 2014
|
||||
--- Features/Enhancements ---
|
||||
- Lua 5.2 support (Thanks to Simon Cozens!)
|
||||
|
||||
0.4 Mar 29 2013
|
||||
--- API Additions ---
|
||||
- The repl object now has a VERSION property.
|
||||
|
||||
--- Bug Fixes ---
|
||||
- Overriding/augmenting getcontext will actually have an effect.
|
||||
- Fix pretty_print with 'unsupported' types
|
||||
|
||||
--- Features/Enhancements ---
|
||||
- Change the example REPL's prompt to resemble the standalone interpreter's
|
||||
- The example REPL now prints a welcome message
|
||||
- If linenoise is not available and rlwrap is, the example REPL falls back to rlwrap
|
||||
|
||||
--- Plugins ---
|
||||
- Added some new plugins
|
||||
- semicolon_suppress_output
|
||||
|
||||
0.3 Oct 17 2012
|
||||
- Add plugins API.
|
||||
- Add a bunch of plugins.
|
||||
|
||||
0.2 Sep 28 2012
|
||||
- Add linenoise REPL.
|
||||
- Add completion support.
|
||||
|
||||
0.1 Oct 20 2011
|
||||
Initial release. Basic REPL functionality.
|
||||
@@ -0,0 +1,43 @@
|
||||
* plugins
|
||||
* replace /^=/ with 'return'
|
||||
* handle locals
|
||||
* override debug.debug
|
||||
* supple (http://cgit.gitano.org.uk/supple.git)
|
||||
* handle locals (debug.sethook?)
|
||||
* debug.sethook, catch return of our chunk and grab its locals
|
||||
* rewrite source code/bytecode before evaluation
|
||||
* custom interpreter patch to "pcall and get bindings"
|
||||
* custom module that dips into internals to "pcall and get bindings"
|
||||
* some sort of debugger?
|
||||
* don't contaminate globals
|
||||
* tab completion (\_\_complete metamethod)
|
||||
* "safe" evaluation (don't allow calling of C functions, except for those in a whitelist?)
|
||||
* displaystack instead of displayerror(err)? (should xpcall return false, stack\_table?)
|
||||
* visual REPL (like Factor; being able to print multi-colored/multi-sized text, images, etc)
|
||||
* syntax highlighting
|
||||
* paren/brace matching?
|
||||
* snippets?
|
||||
* code navigation (go to definition?)
|
||||
* repls that "attach" to different objects (ie. inspect a single object; self is that object. completions happen against that object?)
|
||||
* browsable/searchable REPL history
|
||||
* not entirely sure what I mean here...
|
||||
* safe termination of evaluated code (if I Control-C during an evaluation)
|
||||
* store stdout/stderr output in a variable somewhere?
|
||||
* persistence (pluto-based image)
|
||||
|
||||
hooks
|
||||
=====
|
||||
|
||||
* what to do when we encounter an incomplete Lua fragment
|
||||
* processing a line
|
||||
* something for debug.debug...
|
||||
|
||||
Implementations
|
||||
===============
|
||||
|
||||
* Console
|
||||
* GUI
|
||||
* Web
|
||||
* IRC
|
||||
* safety hooks
|
||||
* Awesome
|
||||
@@ -0,0 +1,14 @@
|
||||
LUA_FILES=$(shell find repl -type f -name '*.lua')
|
||||
.PHONY: doc install
|
||||
|
||||
doc:
|
||||
luadoc -d doc $(LUA_FILES)
|
||||
|
||||
install:
|
||||
# TODO
|
||||
|
||||
test:
|
||||
LUA_INIT='' LUA_PATH=';;$(LUA_PATH);?.lua;?/init.lua;t/lib/?.lua' prove
|
||||
|
||||
clean:
|
||||
rm -rf doc/
|
||||
@@ -0,0 +1,102 @@
|
||||
# REPL.lua - a reusable Lua REPL written in Lua, and an alternative to /usr/bin/lua
|
||||
|
||||
This project has two uses:
|
||||
|
||||
- An alternative to the standalone interpreter included with Lua, one that supports
|
||||
things like plugins, tab completion, and automatic insertion of `return` in front
|
||||
of expressions.
|
||||
|
||||
- A REPL library you may embed in your application, to provide all of the niceties
|
||||
of the standalone interpreter included with Lua and then some.
|
||||
|
||||
Many software projects have made the choice to embed Lua in their projects to
|
||||
allow their users some extra flexibility. Some of these projects would also
|
||||
like to provide a Lua REPL in their programs for debugging or rapid development.
|
||||
Most Lua programmers are familiar with the standalone Lua interpreter as a Lua REPL;
|
||||
however, it is bound to the command line. Until now, Lua programmers would have to
|
||||
implement their own REPL from scratch if they wanted to include one in their programs.
|
||||
This project aims to provide a REPL implemented in pure Lua that almost any project can
|
||||
make use of.
|
||||
|
||||
This library also includes an example application (rep.lua), which serves as an alternative
|
||||
to the standalone interpreter included with Lua. If the lua-linenoise library is installed,
|
||||
it uses linenoise for history and tab completion; otherwise, it tries to use rlwrap for
|
||||
basic line editing. If you would like the arrow keys to work as expected rather than printing
|
||||
things like `^[[A`, please install the lua-linenoise library or the rlwrap program.
|
||||
|
||||
# Project Goals
|
||||
|
||||
* Provide REPL logic to Lua programs that include this module.
|
||||
|
||||
* Be extensible through polymorphism and plugins.
|
||||
|
||||
* Abstract away I/O, so you can run this REPL on the command line or in your own event loop and expect the same behavior.
|
||||
|
||||
# Building
|
||||
|
||||
* You need Luadoc (http://keplerproject.github.com/luadoc/) installed to build the documentation.
|
||||
|
||||
* You need Test.More (http://fperrad.github.com/lua-TestMore/testmore.html) installed to run the tests.
|
||||
|
||||
# Compatibility
|
||||
|
||||
The current version of the software runs on Lua 5.1, LuaJIT ?.? etc.
|
||||
A port to Lua 5.2 is envisaged, but is not at this stage a priority.
|
||||
Since it is written purely in Lua, it should work on any platform that
|
||||
has one of those versions of Lua installed.
|
||||
|
||||
XXX Check which version of LuaJIT this works with
|
||||
XXX Check that it works with other Lua interpreters
|
||||
|
||||
# Installation
|
||||
|
||||
You can install lua-repl via LuaRocks:
|
||||
|
||||
luarocks install luarepl
|
||||
|
||||
You can also install it by hand by copying the `repl`
|
||||
directory to a location in your `package.path`, and
|
||||
copying rep.lua to somewhere in your `PATH`.
|
||||
|
||||
# Recommended packages
|
||||
|
||||
`rep.lua` works best if you also have `linenoise` installed,
|
||||
available from https://github.com/hoelzro/lua-linenoise.
|
||||
`rep.lua` will fallback to using rlwrap if you have that as well;
|
||||
without either of these, you will have command editing, history,
|
||||
or other features generally provided by `readline`.
|
||||
|
||||
# Features
|
||||
|
||||
`rep.lua` prints the results of simple expressions without requiring
|
||||
a `return ` or a `= ` in front of it. If `linenoise` is installed,
|
||||
it also offers persistent history and tab completion. It also offers
|
||||
a number of plugins; see plugins.md for a list of plugins that come
|
||||
with lua-repl.
|
||||
|
||||
# Backwards Compatibility Changes
|
||||
|
||||
## Removal of default plugins in 0.8
|
||||
|
||||
Lua REPL 0.8 breaks backwards compatability by disabling the loading of the
|
||||
default plugins (currently `linenoise`, `rlwrap`, `history`, `completion`, and
|
||||
`autoreturn`) if an rcfile is found for a user. This is so that plugins may
|
||||
not be forced onto a user if they don't want them, or play tricks with their
|
||||
setup (see issue #47). If you would like to continue using these plugins, please
|
||||
put the following code into your `~/.rep.lua`:
|
||||
|
||||
```lua
|
||||
if repl.VERSION >= 0.8 then
|
||||
-- default plugins
|
||||
repl:loadplugin 'linenoise'
|
||||
repl:loadplugin 'history'
|
||||
repl:loadplugin 'completion'
|
||||
repl:loadplugin 'autoreturn'
|
||||
end
|
||||
|
||||
-- suppress warning message
|
||||
repl.quiet_default_plugins = true
|
||||
```
|
||||
|
||||
As mentioned in the code snippet, `repl.quiet_default_plugins` suppresses the warning.
|
||||
You can remove this after upgrading to Lua REPL 0.8.
|
||||
@@ -0,0 +1,34 @@
|
||||
# lua-repl release guide
|
||||
|
||||
- [ ] Bump VERSION (in repl/init.lua, look for references to current version)
|
||||
- [ ] Update Changelog
|
||||
- [ ] Rename and update rockspec
|
||||
- [ ] Make sure tests pass
|
||||
- [ ] Push & tag latest release
|
||||
- [ ] Submit rockspec to luarocks
|
||||
- [ ] E-mail lua-l
|
||||
- [ ] Submit lua-l gmane link to reddit
|
||||
|
||||
## E-mail template for lua-l
|
||||
|
||||
Hi Lua users,
|
||||
|
||||
I have just released version {{VERSION}} of lua-repl, an alternative to the
|
||||
standalone REPL included with Lua and a library for embedding a Lua
|
||||
REPL within a Lua application.
|
||||
|
||||
lua-repl provides a library for adding a Lua REPL to your Lua-powered
|
||||
application. It also provides an example REPL in the form of rep.lua,
|
||||
which can take the place of the standalone interpreter provided with
|
||||
Lua. It has a plugin facility; plugins for things like history and tab
|
||||
completion of symbols are included in the lua-repl distribution.
|
||||
|
||||
{{CHANGES}}
|
||||
|
||||
You can install lua-repl via Luarocks (called luarepl there), or
|
||||
manually from the source [{{REFERENCE}}].
|
||||
|
||||
-Rob
|
||||
|
||||
[{{REFERENCE}}] https://github.com/hoelzro/lua-repl/archive/0.8.tar.gz
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
0.10
|
||||
===
|
||||
|
||||
* Process Lua command line options with rep.lua
|
||||
* Verify that it works with LuaJIT, Lua 5.0, Lua 5.2, LuaJ or something
|
||||
* __pretty support for pretty print plugin
|
||||
* __complete support for completion plugin
|
||||
* Documentation improvements
|
||||
* More clearly reference PLUGINS.md from README.md
|
||||
* More clearly reference rep.lua from README.md
|
||||
* Make sure that autocompletion is talked up in plugins.md (and mention in readme that many default/optional behaviors are present there)
|
||||
* Make sure documentation on ~/.rep.lua is clear
|
||||
* Move docs into doc/
|
||||
|
||||
Future
|
||||
======
|
||||
|
||||
* Plugins
|
||||
* where do plugins store values (self, storage object, etc?)
|
||||
* configuration
|
||||
* global assignments in plugins
|
||||
* we need a way to do method advice in REPL "subclasses"
|
||||
* test: using advice from within ifplugin/iffeature
|
||||
* luaish plugin
|
||||
* moonscript plugin - compile Moonscript instead of Lua
|
||||
* Steal ideas from ilua
|
||||
* Variables in ilua must be declared before use
|
||||
* -L is like -l, except it automatically brings it into the global NS
|
||||
* require() wrapper that does this ↑
|
||||
* table display logic control, float precision control
|
||||
* print\_handler (custom print logic for types)
|
||||
* \_\_pretty
|
||||
* global\_handler (custom lookup logic to complement strict mode)
|
||||
* easily done via a plugin
|
||||
* line\_handler (custom handling of lines before being processed)
|
||||
* Steal ideas from luaish
|
||||
* Shell commands (lines beginning with ., filename completion)
|
||||
* Steal ideas from http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print\_loop
|
||||
* Steal ideas from pry, ipython, bpython, Devel::REPL, Factor REPL
|
||||
* Steal ideas from https://github.com/tpope/vim-foreplay
|
||||
* Async implementation
|
||||
* GTK implementation
|
||||
* IRC bot implementation
|
||||
* Awesome library
|
||||
@@ -0,0 +1,21 @@
|
||||
To: <lua-l@lists.lua.org>
|
||||
Subject: [ANN] lua-repl $VERSION
|
||||
|
||||
Hi everyone,
|
||||
|
||||
I'm happy to announce a new version of lua-repl!
|
||||
|
||||
For those of you who aren't aware of it, lua-repl is a library to make
|
||||
it easy to embed a REPL similar to the standalone interpreter in your
|
||||
Lua application. It also comes with a suite of plugins for enhancing
|
||||
itself, as well as a "demo" console REPL that may be used as an
|
||||
alternative to the standalone REPL (named rep.lua).
|
||||
|
||||
$CHANGE_SUMMARY
|
||||
|
||||
The source for lua-repl is located on Github:
|
||||
https://github.com/hoelzro/lua-repl. The release tarball is here:
|
||||
https://github.com/hoelzro/lua-repl/archive/$VERSION.tar.gz
|
||||
|
||||
Thanks,
|
||||
Rob
|
||||
@@ -0,0 +1,38 @@
|
||||
package = 'luarepl'
|
||||
version = '0.10-1'
|
||||
source = {
|
||||
url = 'https://github.com/hoelzro/lua-repl/archive/0.10.tar.gz',
|
||||
dir = 'lua-repl-0.10',
|
||||
}
|
||||
description = {
|
||||
summary = 'A reusable REPL component for Lua, written in Lua',
|
||||
homepage = 'https://github.com/hoelzro/lua-repl',
|
||||
license = 'MIT/X11',
|
||||
}
|
||||
dependencies = {
|
||||
'lua >= 5.1'
|
||||
}
|
||||
build = {
|
||||
type = 'builtin',
|
||||
modules = {
|
||||
['repl'] = 'repl/init.lua',
|
||||
['repl.utils'] = 'repl/utils.lua',
|
||||
['repl.sync'] = 'repl/sync.lua',
|
||||
['repl.compat'] = 'repl/compat.lua',
|
||||
['repl.console'] = 'repl/console.lua',
|
||||
['repl.plugins.autoreturn'] = 'repl/plugins/autoreturn.lua',
|
||||
['repl.plugins.completion'] = 'repl/plugins/completion.lua',
|
||||
['repl.plugins.example'] = 'repl/plugins/example.lua',
|
||||
['repl.plugins.history'] = 'repl/plugins/history.lua',
|
||||
['repl.plugins.keep_last_eval'] = 'repl/plugins/keep_last_eval.lua',
|
||||
['repl.plugins.linenoise'] = 'repl/plugins/linenoise.lua',
|
||||
['repl.plugins.pretty_print'] = 'repl/plugins/pretty_print.lua',
|
||||
['repl.plugins.rcfile'] = 'repl/plugins/rcfile.lua',
|
||||
['repl.plugins.semicolon_suppress_output'] = 'repl/plugins/semicolon_suppress_output.lua',
|
||||
['repl.plugins.filename_completion'] = 'repl/plugins/filename_completion.lua',
|
||||
['repl.plugins.rlwrap'] = 'repl/plugins/rlwrap.lua',
|
||||
},
|
||||
install = {
|
||||
bin = { 'rep.lua' },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
# Plugins
|
||||
|
||||
As of version 0.3, lua-repl supports a plugin mechanism. For an example, please download the source code for
|
||||
lua-repl and look at `repl/plugins/example.lua`.
|
||||
|
||||
# Available Plugins
|
||||
|
||||
lua-repl 0.3 ships with several plugins. If you use the example script `rep.lua`, some of them are loaded automatically;
|
||||
these are marked with an asterisk.
|
||||
|
||||
## autoreturn (\*)
|
||||
|
||||
Automatically returns the the results of the evaluated expression. So instead of having to type 'return 3', you may
|
||||
simply type '3'.
|
||||
|
||||
## completion (\*)
|
||||
|
||||
Provides completion facilities via the 'completion' feature. Other plugins may hook into this to provide tab completion.
|
||||
|
||||
## example
|
||||
|
||||
Simply an example plugin.
|
||||
|
||||
## history (\*)
|
||||
|
||||
Provides history facilities (and the 'history' feature), storing each line entered as an individual history entry,
|
||||
and persisting the history in `$HOME/.rep.lua.history`. Other plugins may hook into this.
|
||||
|
||||
## keep\_last\_eval
|
||||
|
||||
Retains the results of the previously evaluated expression in global variables. The first result is stored in `_1`, the
|
||||
second in `_2`, etc. For brevity's sake, the first result is also stored in `_`.
|
||||
|
||||
## linenoise (\*)
|
||||
|
||||
Hooks into the linenoise library. Allows the use of tab completion and history.
|
||||
|
||||
## pretty\_print
|
||||
|
||||
'Pretty prints' return values. Tables are printed in expanded form. Colors are provided if lua-term is installed.
|
||||
|
||||
## rcfile (\*)
|
||||
|
||||
Loads Lua code in `$HOME/.rep.lua` if the file exists. The repl object is provided to the file in a variable named `repl`, so
|
||||
users may load plugins of their choosing.
|
||||
|
||||
## semicolon_suppress_output
|
||||
|
||||
Suppresses automatic printing of an expression's result if the expression ends in a semicolon.
|
||||
|
||||
# Creating a Plugin
|
||||
|
||||
If you would like to create your own plugin, it must be in a file under `repl/plugins/` in your `package.path`.
|
||||
|
||||
# Plugin Objects
|
||||
|
||||
When a plugin is loaded, it is provided with five special objects that are used to affect the behavior of the REPL
|
||||
object loading the plugin.
|
||||
|
||||
## repl
|
||||
|
||||
The `repl` object is a proxy object for the REPL object loading the plugin; you can invoke methods on it, create methods on
|
||||
it, set properties on it, etc. However, if you try to create a method on the `repl` object that already exists, an error
|
||||
will occur. This is to keep plugin authors from stepping on each others' toes.
|
||||
|
||||
## before
|
||||
|
||||
The `before` object is another proxy object from the plugin environment. If you add methods to the `before` object, the original
|
||||
method remains intact; however, the method you added will be called before the original. For example, if you wanted to print
|
||||
"I got some results!" before you display them on the command line, your plugin could do this:
|
||||
|
||||
```lua
|
||||
function before:displayresults(results)
|
||||
print 'I got some results!'
|
||||
end
|
||||
```
|
||||
|
||||
This is called *advice*, and is stolen from Moose, an object system for the Perl programming language.
|
||||
|
||||
When you apply multiple pieces of advice via `before`, they are called in last-in-first-out order:
|
||||
|
||||
```lua
|
||||
function before:method()
|
||||
print 'Second!'
|
||||
end
|
||||
|
||||
function before:method()
|
||||
print 'First!'
|
||||
end
|
||||
```
|
||||
|
||||
`before` also receives all of the parameters to the original method. If they are tables, userdata, etc, you may alter them,
|
||||
which can alter the behavior of the original method, for better or for worse.
|
||||
|
||||
If the method you are applying advice to does not exist on the current REPL object, an error will occur. This way, developers
|
||||
can find out about API changes quickly, albeit noisily.
|
||||
|
||||
## after
|
||||
|
||||
The `after` object is another proxy object that attaches advice to the loading REPL object. As you can likely tell from its name,
|
||||
advice applied via the `after` object occurs after the original method. Advice applied via after is called in first-in-first-out order:
|
||||
|
||||
```lua
|
||||
function after:method()
|
||||
print 'First!'
|
||||
end
|
||||
|
||||
function after:method()
|
||||
print 'Second!'
|
||||
end
|
||||
```
|
||||
|
||||
Like `before`, if you try applying advice to a method that doesn't exist, an error will occur. Also like `before`, after advice receives
|
||||
all of the parameters passed to the original method.
|
||||
|
||||
## around
|
||||
|
||||
The `around` object is another advice object, but it works a little differently than `before` or `after`. `around` replaces the current
|
||||
method will the advice, and like `before` and `after`, receives all of the parameters that would be passed to the original. However,
|
||||
`around` also receives an additional parameter immediately before the parameters: the original method. This way, you can invoke
|
||||
the method's original functionality if needed. For example:
|
||||
|
||||
```lua
|
||||
function around:displayresults(orig, results)
|
||||
print "I'm displaying some results!"
|
||||
orig(self, results) -- don't forget self!
|
||||
print "Now I'm done!"
|
||||
end
|
||||
```
|
||||
|
||||
Like the other advice objects, you can't apply advice to a method that doesn't exist. Also, be warned: the `around` advice does nothing
|
||||
to make sure that the parameters are passed to the original function, and it doesn't make sure that the return values from the original
|
||||
function are returned. You need to do that yourself.
|
||||
|
||||
## override
|
||||
|
||||
The `override` object isn't really an advice object; adding methods to it will replace the methods in the REPL object itself. However,
|
||||
it will fail if that method does not already exist. The rule of thumb is if you want to add new methods to the REPL object, use
|
||||
`repl`; if you want to completely override an existing method, use `override`. Keep in mind this will blow away all advice applied to
|
||||
a method from other plugins; use with caution!
|
||||
|
||||
# Features
|
||||
|
||||
Sometimes, different plugins will want to provide a method, but implemented in a different way. For example, the completion plugin
|
||||
included with lua-repl implements a tab completion method; however, if you are embedding lua-repl into your own environment, you may
|
||||
have a more sophisicated way to provide completions. Other plugins (like the linenoise plugin) may want to hook into the completion
|
||||
feature itself, without being tied to a particular implementation. So plugins may advertise a list of *features* that they provide,
|
||||
so that they can develop loose relationships between one another. To advertise features for your plugin, simply set the features variable:
|
||||
|
||||
```lua
|
||||
features = 'completion' -- make sure you're not setting a local!
|
||||
```
|
||||
|
||||
If you wish you provide multiple features, simply use a table:
|
||||
|
||||
```lua
|
||||
features = { 'completion', 'something_else' }
|
||||
```
|
||||
|
||||
Obviously, plugins providing a feature need to agree on a standard interface of methods that they provide. No framework is in place for this as
|
||||
of yet.
|
||||
|
||||
REPL objects may provide features as well; for example, `repl.console` provides the 'console' feature. You can use this to make sure your
|
||||
plugins are only loaded in certain environments.
|
||||
|
||||
# REPL Methods
|
||||
|
||||
Now that you know how to affect the behavior of lua-repl with plugins, let's go over the methods you may advise/override, or call yourself from
|
||||
within your advised/overridden methods. Please keep in mind that since lua-repl is still a young project, this API is subject to change.
|
||||
|
||||
## repl:getprompt(level)
|
||||
|
||||
Returns the prompt string displayed for the given prompt level, which is either 1 or 2.
|
||||
1 signifies that the REPL is not in a multi-line expression (like a for loop); 2 signifies
|
||||
otherwise.
|
||||
|
||||
## repl:prompt(level)
|
||||
|
||||
Actually displays the prompt for the given level. You more likely want to deal with
|
||||
getprompt or showprompt.
|
||||
|
||||
## repl:name()
|
||||
|
||||
Returns the name of the REPL, used when compiling the chunks for evaluation.
|
||||
|
||||
## repl:traceback(err)
|
||||
|
||||
Returns a stack trace, prefixed by the given error message.
|
||||
|
||||
## repl:detectcontinue(err)
|
||||
|
||||
Detects whether or not the given error message means that more input is needed for a complete
|
||||
chunk. You probably shouldn't touch this.
|
||||
|
||||
## repl:compilechunk(code)
|
||||
|
||||
Compiles the given chunk of code, returning a function, or a falsy value and an error message.
|
||||
|
||||
## repl:getcontext()
|
||||
|
||||
Returns the function environment that the REPL evaluates code in.
|
||||
|
||||
## repl:handleline(line)
|
||||
|
||||
Handles a line of input, returning the prompt level (1 or 2). Note that if this method is
|
||||
called, an evaluation does not necessarily occur.
|
||||
|
||||
## repl:showprompt(prompt)
|
||||
|
||||
Displays the given prompt.
|
||||
|
||||
## repl:displayresults(results)
|
||||
|
||||
Displays the results from an evaluation. `results` is a table with the individual values in
|
||||
the integer indices of the table, with the `n` key containing the number of values in the table.
|
||||
|
||||
## repl:displayerror(err)
|
||||
|
||||
Displays an error from an evaluation.
|
||||
|
||||
## repl:hasplugin(plugin)
|
||||
|
||||
Returns `true` if the given plugin has been loaded, `false` otherwise.
|
||||
|
||||
## repl:hasfeature(feature)
|
||||
|
||||
Returns `true` if the given feature has been loaded, `false` otherwise.
|
||||
|
||||
## repl:requirefeature(feature)
|
||||
|
||||
If the given feature has been loaded, do nothing. Otherwise, raise an error.
|
||||
|
||||
## repl:ifplugin(plugin, action)
|
||||
|
||||
If the given plugin has been loaded, call `action`. Otherwise, if the plugin
|
||||
is ever loaded in the future, call `action` after that loading occurs.
|
||||
|
||||
## repl:iffeature(feature, action)
|
||||
|
||||
If the given feature has been loaded, call `action`. Otherwise, if the feature
|
||||
is ever loaded in the future, call `action` after that loading occurs.
|
||||
|
||||
## repl:loadplugin(plugin)
|
||||
|
||||
Loads the given plugin. If the plugin returns a value, that value is returned.
|
||||
|
||||
## repl:shutdown()
|
||||
|
||||
Called when the REPL is exited. Don't call this yourself!
|
||||
|
||||
## repl:lines() -- repl.sync only
|
||||
|
||||
Returns an iterator that yields a line of input per invocation.
|
||||
|
||||
# The Future
|
||||
|
||||
This is the first release of lua-repl with plugins. The future will bring various
|
||||
refinements to the plugin interface, along with the following planned features:
|
||||
|
||||
## Feature Interfaces
|
||||
|
||||
Earlier I mentioned that features have a sort of "gentlemen's aggreement" on what
|
||||
methods they will provide. It would be nice if the plugin system had a way of
|
||||
enforcing that.
|
||||
|
||||
## Attribute Storage
|
||||
|
||||
Currently, if a plugin wants to store some information between method calls, it needs
|
||||
to store it on the REPL object (`self`) and hope no other plugins (or REPL clone) will
|
||||
use the same name. Plugin-specific storage is a high priority.
|
||||
|
||||
## Configuration
|
||||
|
||||
Currently, plugins don't have any sort of configuration mechanism. I plan to change that.
|
||||
|
||||
## Library Plugins
|
||||
|
||||
Some plugins may want to leverage functionality of others without loading those others into
|
||||
the REPL itself. I call these *library plugins*.
|
||||
|
||||
## Better Diagnostics
|
||||
|
||||
If you try to add a method that has already been added, or provide a feature that has already been
|
||||
provided, you receive no information on which plugin provided the method or feature in question.
|
||||
It would be nice to know.
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- Not as cool a name as re.pl, but I tried.
|
||||
|
||||
local repl = require 'repl.console'
|
||||
local rcfile_loaded = repl:loadplugin 'rcfile'
|
||||
|
||||
if not rcfile_loaded then
|
||||
local has_linenoise = pcall(require, 'linenoise')
|
||||
|
||||
if has_linenoise then
|
||||
repl:loadplugin 'linenoise'
|
||||
else
|
||||
pcall(repl.loadplugin, repl, 'rlwrap')
|
||||
end
|
||||
|
||||
repl:loadplugin 'history'
|
||||
repl:loadplugin 'completion'
|
||||
repl:loadplugin 'autoreturn'
|
||||
end
|
||||
|
||||
print('Lua REPL ' .. tostring(repl.VERSION))
|
||||
repl:run()
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
return {
|
||||
-- unpack was moved to table.unpack on Lua version 5.2
|
||||
-- See https://www.lua.org/manual/5.2/manual.html#8
|
||||
unpack = unpack or table.unpack,
|
||||
-- loadstring was deprecated in favor of load, which was updated
|
||||
-- to handle string arguments
|
||||
loadstring = loadstring or load,
|
||||
-- package.loaders was renamed package.searchers in Lua 5.2
|
||||
package = {
|
||||
searchers = package.loaders or package.searchers
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- @class repl.console
|
||||
--- This module implements a command line-based REPL,
|
||||
--- similar to the standalone Lua interpreter.
|
||||
|
||||
local sync_repl = require 'repl.sync'
|
||||
local compat = require 'repl.compat'
|
||||
local console_repl = sync_repl:clone()
|
||||
local stdout = io.stdout
|
||||
local stdin = io.stdin
|
||||
local print = print
|
||||
local unpack = unpack
|
||||
|
||||
-- @see repl:showprompt(prompt)
|
||||
function console_repl:showprompt(prompt)
|
||||
stdout:write(prompt .. ' ')
|
||||
end
|
||||
|
||||
-- @see repl.sync:lines()
|
||||
function console_repl:lines()
|
||||
return stdin:lines()
|
||||
end
|
||||
|
||||
-- @see repl:displayresults(results)
|
||||
function console_repl:displayresults(results)
|
||||
if results.n == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
print(compat.unpack(results, 1, results.n))
|
||||
end
|
||||
|
||||
-- @see repl:displayerror(err)
|
||||
function console_repl:displayerror(err)
|
||||
print(err)
|
||||
end
|
||||
|
||||
console_repl._features.console = true
|
||||
|
||||
return console_repl
|
||||
@@ -0,0 +1,416 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- @class repl
|
||||
--- This module implements the core functionality of a REPL.
|
||||
|
||||
local plugins_lookup_meta = { __mode = 'k' }
|
||||
|
||||
local repl = { _buffer = '', _plugins = setmetatable({}, plugins_lookup_meta), _features = {}, _ifplugin = {}, _iffeature = {}, VERSION = 0.10 }
|
||||
local compat = require 'repl.compat'
|
||||
local select = select
|
||||
local dtraceback = debug.traceback
|
||||
local setmetatable = setmetatable
|
||||
local sformat = string.format
|
||||
local smatch = string.match
|
||||
local error = error
|
||||
local setfenv = require('repl.utils').setfenv
|
||||
|
||||
local function gather_results(success, ...)
|
||||
local n = select('#', ...)
|
||||
return success, { n = n, ... }
|
||||
end
|
||||
|
||||
local function tcopy(t, copy)
|
||||
copy = copy or {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
copy[k] = v
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
--- Returns the prompt for a given level.
|
||||
-- @param level The prompt level. Either 1 or 2.
|
||||
function repl:getprompt(level)
|
||||
return level == 1 and '>' or '>>'
|
||||
end
|
||||
|
||||
--- Displays a prompt for the given prompt level.
|
||||
-- @param level The prompt level. Either 1 or 2.
|
||||
function repl:prompt(level)
|
||||
self:showprompt(self:getprompt(level))
|
||||
end
|
||||
|
||||
--- Returns the name of the REPL. For usage in chunk compilation.
|
||||
-- @return The REPL's name.
|
||||
-- @see load
|
||||
function repl:name()
|
||||
return 'REPL'
|
||||
end
|
||||
|
||||
--- Gets a traceback for an error.
|
||||
-- @param ... All of the stuff that xpcall passes to error functions.
|
||||
-- @see xpcall
|
||||
-- @return A stack trace. The default implementation returns a simple string-based trace.
|
||||
function repl:traceback(...)
|
||||
return dtraceback(...)
|
||||
end
|
||||
|
||||
--- Uses the compilation error to determine whether or not further input
|
||||
--- is pending after the last line. You shouldn't have to override this
|
||||
--- unless you use an implementation of Lua that varies in its error
|
||||
--- messages.
|
||||
-- @param err The compilation error from Lua.
|
||||
-- @return Whether or not the input should continue after this line.
|
||||
function repl:detectcontinue(err)
|
||||
return smatch(err, "'<eof>'$") or smatch(err, "<eof>$")
|
||||
end
|
||||
|
||||
function repl:compilechunk(chunk)
|
||||
return compat.loadstring(chunk, self:name())
|
||||
end
|
||||
|
||||
--- Evaluates a line of input, and displays return value(s).
|
||||
-- @param line The line to evaluate
|
||||
-- @return The prompt level (1 or 2)
|
||||
function repl:handleline(line)
|
||||
local chunk = self._buffer .. line
|
||||
local f, err = self:compilechunk(chunk)
|
||||
|
||||
if f then
|
||||
self._buffer = ''
|
||||
|
||||
setfenv(f, self:getcontext())
|
||||
local success, results = gather_results(xpcall(f, function(...) return self:traceback(...) end))
|
||||
if success then
|
||||
self:displayresults(results)
|
||||
else
|
||||
self:displayerror(results[1])
|
||||
end
|
||||
else
|
||||
if self:detectcontinue(err) then
|
||||
self._buffer = chunk .. '\n'
|
||||
return 2
|
||||
else
|
||||
self:displayerror(err)
|
||||
self._buffer = ''
|
||||
end
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
--- Creates a new REPL object, so you can override methods without fear.
|
||||
-- @return A REPL clone.
|
||||
function repl:clone()
|
||||
local plugins_copy = tcopy(self._plugins, setmetatable({}, plugins_lookup_meta))
|
||||
local features_copy = tcopy(self._features)
|
||||
local ifplugin_copy = {}
|
||||
local iffeature_copy = {}
|
||||
|
||||
for k, v in pairs(self._ifplugin) do
|
||||
ifplugin_copy[k] = tcopy(v)
|
||||
end
|
||||
|
||||
for k, v in pairs(self._iffeature) do
|
||||
iffeature_copy[k] = tcopy(v)
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
_buffer = '',
|
||||
_plugins = plugins_copy,
|
||||
_features = features_copy,
|
||||
_ifplugin = ifplugin_copy,
|
||||
_iffeature = iffeature_copy,
|
||||
}, { __index = self })
|
||||
end
|
||||
|
||||
--- Displays the given prompt to the user. Must be overriden.
|
||||
-- @param prompt The prompt to display.
|
||||
function repl:showprompt(prompt)
|
||||
error 'You must implement the showprompt method'
|
||||
end
|
||||
|
||||
--- Displays the results from evaluate(). Must be overriden.
|
||||
-- @param results The results to display. The results are a table, with the integer keys containing the results, and the 'n' key containing the highest integer key.
|
||||
|
||||
function repl:displayresults(results)
|
||||
error 'You must implement the displayresults method'
|
||||
end
|
||||
|
||||
--- Displays errors from evaluate(). Must be overriden.
|
||||
-- @param err The error value returned from repl:traceback().
|
||||
-- @see repl:traceback
|
||||
function repl:displayerror(err)
|
||||
error 'You must implement the displayerror method'
|
||||
end
|
||||
|
||||
--- Checks whether this REPL object has loaded the given plugin.
|
||||
-- @param plugin The plugin that the REPL may have loaded.
|
||||
function repl:hasplugin(plugin)
|
||||
return self._plugins[plugin]
|
||||
end
|
||||
|
||||
function repl:hasfeature(feature)
|
||||
return self._features[feature]
|
||||
end
|
||||
|
||||
function repl:requirefeature(feature)
|
||||
if not self:hasfeature(feature) then
|
||||
error(sformat('required feature %q not present', feature), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function repl:ifplugin(plugin, action)
|
||||
if self:hasplugin(plugin) then
|
||||
action()
|
||||
else
|
||||
local pending_actions = self._ifplugin[plugin]
|
||||
|
||||
if not pending_actions then
|
||||
pending_actions = {}
|
||||
self._ifplugin[plugin] = pending_actions
|
||||
end
|
||||
|
||||
pending_actions[#pending_actions + 1] = action
|
||||
end
|
||||
end
|
||||
|
||||
--- If the given feature has been loaded, call `action`. Otherwise, if the
|
||||
-- feature is ever loaded in the future, call `action` after that loading occurs.
|
||||
function repl:iffeature(feature, action)
|
||||
if self:hasfeature(feature) then
|
||||
action()
|
||||
else
|
||||
local pending_features = self._iffeature[feature]
|
||||
|
||||
if not pending_features then
|
||||
pending_features = {}
|
||||
self._iffeature[feature] = pending_features
|
||||
end
|
||||
|
||||
pending_features[#pending_features + 1] = action
|
||||
end
|
||||
end
|
||||
|
||||
local function setup_before(repl)
|
||||
local mt = {}
|
||||
|
||||
function mt:__newindex(key, value)
|
||||
if type(value) ~= 'function' then
|
||||
error(tostring(value) .. " is not a function", 2)
|
||||
end
|
||||
|
||||
local old_value = repl[key]
|
||||
|
||||
if old_value == nil then
|
||||
error(sformat("The '%s' method does not exist", key), 2)
|
||||
end
|
||||
|
||||
repl[key] = function(...)
|
||||
value(...)
|
||||
return old_value(...)
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
local function setup_after(repl)
|
||||
local mt = {}
|
||||
|
||||
function mt:__newindex(key, value)
|
||||
if type(value) ~= 'function' then
|
||||
error(tostring(value) .. " is not a function", 2)
|
||||
end
|
||||
|
||||
local old_value = repl[key]
|
||||
|
||||
if old_value == nil then
|
||||
error(sformat("The '%s' method does not exist", key), 2)
|
||||
end
|
||||
|
||||
repl[key] = function(...)
|
||||
local _, results = gather_results(true, old_value(...))
|
||||
value(...)
|
||||
return compat.unpack(results, 1, results.n)
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
local function setup_around(repl)
|
||||
local mt = {}
|
||||
|
||||
function mt:__newindex(key, value)
|
||||
if type(value) ~= 'function' then
|
||||
error(tostring(value) .. " is not a function", 2)
|
||||
end
|
||||
|
||||
local old_value = repl[key]
|
||||
|
||||
if old_value == nil then
|
||||
error(sformat("The '%s' method does not exist", key), 2)
|
||||
end
|
||||
|
||||
repl[key] = function(self, ...)
|
||||
return value(self, old_value, ...)
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
local function setup_override(repl)
|
||||
local mt = {}
|
||||
|
||||
function mt:__newindex(key, value)
|
||||
if type(value) ~= 'function' then
|
||||
error(tostring(value) .. " is not a function", 2)
|
||||
end
|
||||
|
||||
local old_value = repl[key]
|
||||
|
||||
if old_value == nil then
|
||||
error(sformat("The '%s' method does not exist", key), 2)
|
||||
end
|
||||
|
||||
repl[key] = value
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
local function setup_repl(repl)
|
||||
local mt = {}
|
||||
|
||||
function mt:__newindex(key, value)
|
||||
local old_value = repl[key]
|
||||
|
||||
if old_value ~= nil then
|
||||
error(sformat("The '%s' method already exists", key), 2)
|
||||
end
|
||||
|
||||
repl[key] = value
|
||||
end
|
||||
|
||||
function mt:__index(key)
|
||||
local value = repl[key]
|
||||
|
||||
if type(value) == 'function' then
|
||||
-- XXX cache this?
|
||||
return function(_, ...)
|
||||
return value(repl, ...)
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
-- TODO use lua-procure for this (eventually)
|
||||
local function findchunk(name)
|
||||
for _, loader in pairs(compat.package.searchers) do
|
||||
local chunk = loader(name)
|
||||
|
||||
if type(chunk) == 'function' then
|
||||
return chunk
|
||||
end
|
||||
end
|
||||
|
||||
error('unable to locate plugin', 3)
|
||||
end
|
||||
|
||||
function repl:loadplugin(chunk)
|
||||
if self:hasplugin(chunk) then
|
||||
error(sformat('plugin %q has already been loaded', tostring(chunk)), 2)
|
||||
end
|
||||
self._plugins[chunk] = true
|
||||
|
||||
local plugin_actions = self._ifplugin[chunk]
|
||||
self._ifplugin[chunk] = nil
|
||||
|
||||
if type(chunk) == 'string' then
|
||||
chunk = findchunk('repl.plugins.' .. chunk)
|
||||
end
|
||||
|
||||
local plugin_env = {
|
||||
repl = setup_repl(self),
|
||||
before = setup_before(self),
|
||||
after = setup_after(self),
|
||||
around = setup_around(self),
|
||||
override = setup_override(self),
|
||||
init = function() end,
|
||||
}
|
||||
|
||||
local function ro_globals(_, key, _)
|
||||
error(sformat('global environment is read-only (key = %q)', key), 2)
|
||||
end
|
||||
|
||||
plugin_env._G = plugin_env
|
||||
plugin_env.features = {}
|
||||
setmetatable(plugin_env, { __index = _G, __newindex = ro_globals })
|
||||
|
||||
setfenv(chunk, plugin_env)
|
||||
local _, results = gather_results(nil, chunk())
|
||||
|
||||
local features = plugin_env.features or {}
|
||||
|
||||
if type(features) == 'string' then
|
||||
features = { features }
|
||||
end
|
||||
|
||||
for _, feature in ipairs(features) do
|
||||
if self._features[feature] then
|
||||
error(sformat('feature %q already present', feature), 2)
|
||||
end
|
||||
|
||||
self._features[feature] = true
|
||||
|
||||
local feature_actions = self._iffeature[feature]
|
||||
self._iffeature[feature] = nil
|
||||
if feature_actions then
|
||||
for _, action in ipairs(feature_actions) do
|
||||
action()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if plugin_actions then
|
||||
for _, action in ipairs(plugin_actions) do
|
||||
action()
|
||||
end
|
||||
end
|
||||
|
||||
return compat.unpack(results, 1, results.n)
|
||||
end
|
||||
|
||||
-- XXX how to guarantee this gets called?
|
||||
function repl:shutdown()
|
||||
end
|
||||
|
||||
function repl:getcontext()
|
||||
return _G
|
||||
end
|
||||
|
||||
return repl
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- A plugin that causes the REPL to automatically return evaluation results
|
||||
|
||||
function around:compilechunk(orig, chunk)
|
||||
local f, err = orig(self, 'return ' .. chunk)
|
||||
|
||||
if not f then
|
||||
f, err = orig(self, chunk)
|
||||
end
|
||||
|
||||
return f, err
|
||||
end
|
||||
@@ -0,0 +1,192 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
local utils = require 'repl.utils'
|
||||
local getmetatable = getmetatable
|
||||
local pairs = pairs
|
||||
local sfind = string.find
|
||||
local sgmatch = string.gmatch
|
||||
local smatch = string.match
|
||||
local ssub = string.sub
|
||||
local tconcat = table.concat
|
||||
local tsort = table.sort
|
||||
local type = type
|
||||
|
||||
local function isindexable(value)
|
||||
if type(value) == 'table' then
|
||||
return true
|
||||
end
|
||||
|
||||
local mt = getmetatable(value)
|
||||
|
||||
return mt and mt.__index
|
||||
end
|
||||
|
||||
local function getcompletions(t)
|
||||
local union = {}
|
||||
|
||||
while isindexable(t) do
|
||||
if type(t) == 'table' then
|
||||
-- XXX what about the pairs metamethod in 5.2?
|
||||
-- either we don't care, we implement a __pairs-friendly
|
||||
-- pairs for 5.1, or implement a 'rawpairs' for 5.2
|
||||
for k, v in pairs(t) do
|
||||
if union[k] == nil then
|
||||
union[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local mt = getmetatable(t)
|
||||
t = mt and mt.__index or nil
|
||||
end
|
||||
|
||||
return pairs(union)
|
||||
end
|
||||
|
||||
local function split_ns(expr)
|
||||
if expr == '' then
|
||||
return { '' }
|
||||
end
|
||||
|
||||
local pieces = {}
|
||||
|
||||
-- XXX method calls too (option?)
|
||||
for m in sgmatch(expr, '[^.]+') do
|
||||
pieces[#pieces + 1] = m
|
||||
end
|
||||
|
||||
-- logic for determining whether to pad the matches with the empty
|
||||
-- string (ugly)
|
||||
if ssub(expr, -1) == '.' then
|
||||
pieces[#pieces + 1] = ''
|
||||
end
|
||||
|
||||
return pieces
|
||||
end
|
||||
|
||||
local function determine_ns(expr)
|
||||
local ns = _G -- XXX what if the REPL lives in a special context? (option?)
|
||||
local pieces = split_ns(expr)
|
||||
|
||||
for index = 1, #pieces - 1 do
|
||||
local key = pieces[index]
|
||||
-- XXX rawget? or regular access? (option?)
|
||||
ns = ns[key]
|
||||
|
||||
if not isindexable(ns) then
|
||||
return {}, '', ''
|
||||
end
|
||||
end
|
||||
|
||||
expr = pieces[#pieces]
|
||||
|
||||
local prefix = ''
|
||||
|
||||
if #pieces > 1 then
|
||||
prefix = tconcat(pieces, '.', 1, #pieces - 1) .. '.'
|
||||
end
|
||||
|
||||
local last_piece = pieces[#pieces]
|
||||
|
||||
local before, after = smatch(last_piece, '(.*):(.*)')
|
||||
|
||||
if before then
|
||||
ns = ns[before] -- XXX rawget
|
||||
prefix = prefix .. before .. ':'
|
||||
expr = after
|
||||
end
|
||||
|
||||
return ns, prefix, expr
|
||||
end
|
||||
|
||||
local isidentifierchar
|
||||
|
||||
do
|
||||
local ident_chars_set = {}
|
||||
-- XXX I think this can be done with isalpha in C...
|
||||
local ident_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:_0123456789'
|
||||
|
||||
for i = 1, #ident_chars do
|
||||
local char = ssub(ident_chars, i, i)
|
||||
ident_chars_set[char] = true
|
||||
end
|
||||
|
||||
function isidentifierchar(char)
|
||||
return ident_chars_set[char]
|
||||
end
|
||||
end
|
||||
|
||||
local function extract_innermost_expr(expr)
|
||||
local index = #expr
|
||||
|
||||
while index > 0 do
|
||||
local char = ssub(expr, index, index)
|
||||
if isidentifierchar(char) then
|
||||
index = index - 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
index = index + 1
|
||||
|
||||
return ssub(expr, 1, index - 1), ssub(expr, index)
|
||||
end
|
||||
|
||||
-- XXX is this logic (namely, returning the entire line) too specific to
|
||||
-- linenoise?
|
||||
function repl:complete(expr, callback)
|
||||
if utils.ends_in_unfinished_string(expr) then
|
||||
return
|
||||
end
|
||||
|
||||
local ns, prefix, path
|
||||
|
||||
prefix, expr = extract_innermost_expr(expr)
|
||||
|
||||
ns, path, expr = determine_ns(expr)
|
||||
|
||||
prefix = prefix .. path
|
||||
|
||||
local completions = {}
|
||||
|
||||
for k, v in getcompletions(ns) do
|
||||
if sfind(k, expr, 1, true) == 1 then
|
||||
local suffix = ''
|
||||
local type = type(v)
|
||||
|
||||
-- XXX this should be optional
|
||||
if type == 'function' then
|
||||
suffix = '('
|
||||
elseif type == 'table' then
|
||||
suffix = '.'
|
||||
end
|
||||
|
||||
completions[#completions + 1] = prefix .. k .. suffix
|
||||
end
|
||||
end
|
||||
|
||||
tsort(completions)
|
||||
|
||||
for _, completion in ipairs(completions) do
|
||||
callback(completion)
|
||||
end
|
||||
end
|
||||
|
||||
features = 'completion'
|
||||
@@ -0,0 +1,41 @@
|
||||
-- Example plugin that demonstrates the objects available to a
|
||||
-- plugin, as well as the methods that a plugin should make use
|
||||
-- of
|
||||
|
||||
-- Adding methods and properties to the repl object adds them to
|
||||
-- the REPL object loading the plugin. If such a method or property
|
||||
-- already exists, the current plugin will fail to load.
|
||||
function repl:newmethod(...)
|
||||
end
|
||||
|
||||
-- Adding methods to the before object causes them to be called
|
||||
-- before the actual method itself. If the method being added
|
||||
-- (in this case displayresults) does not exist on the REPL object
|
||||
-- loading this plugin, the current plugin will fail to load.
|
||||
function before:displayresults(results)
|
||||
end
|
||||
|
||||
-- Adding methods to the after object causes them to be called
|
||||
-- after the actual method itself. If the method being added
|
||||
-- (in this case displayresults) does not exist on the REPL object
|
||||
-- loading this plugin, the current plugin will fail to load.
|
||||
function after:displayresults(results)
|
||||
end
|
||||
|
||||
-- Adding methods to the around object causes them to be called
|
||||
-- instead of the original method of the same name. The new
|
||||
-- method receives all of the arguments that the original would,
|
||||
-- except it also receives the original method as the first argument.
|
||||
-- This way, the new method may invoke the original as it pleases.
|
||||
-- If the method being added (in this case displayresults) does not exist on
|
||||
-- the REPL object loading this plugin, the current plugin will fail to load.
|
||||
function around:evalute(orig, chunk)
|
||||
end
|
||||
|
||||
-- Adding methods to the override object causes them to be called
|
||||
-- instead of the original method of the same name. If the method being added
|
||||
-- (in this case displayresults) does not exist on the REPL object loading this
|
||||
-- plugin, the current plugin will fail to load.
|
||||
function override:name()
|
||||
return 'Plugin!'
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
local utils = require 'repl.utils'
|
||||
local lfs = require 'lfs'
|
||||
|
||||
repl:requirefeature 'completion'
|
||||
|
||||
local function guess_directory_separator(file_name)
|
||||
return file_name:match('/') or
|
||||
file_name:match('\\') or
|
||||
'/'
|
||||
end
|
||||
|
||||
local function split_parent_directory(file_name)
|
||||
local parent_directory, directory_entry =
|
||||
file_name:match('^(.+)[\\/](.+)$')
|
||||
if not parent_directory then
|
||||
parent_directory = '.'
|
||||
directory_entry = file_name
|
||||
end
|
||||
return parent_directory, directory_entry
|
||||
end
|
||||
|
||||
local function is_ignored_directory_entry(entry)
|
||||
return entry == '.' or
|
||||
entry == '..'
|
||||
end
|
||||
|
||||
local function replace_end_of_string(str, suffix, replacement)
|
||||
assert(str:sub(-#suffix) == suffix)
|
||||
return str:sub(1, -(#suffix+1)) .. replacement
|
||||
end
|
||||
|
||||
local function complete_file_name(file_name, expr, callback)
|
||||
local directory, partial_entry = split_parent_directory(file_name)
|
||||
for entry in lfs.dir(directory) do
|
||||
if not is_ignored_directory_entry(entry) and
|
||||
entry:find(partial_entry, 1, true) == 1 then
|
||||
callback(replace_end_of_string(expr, partial_entry, entry))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function complete_directory(directory, expr, callback)
|
||||
for entry in lfs.dir(directory) do
|
||||
if not is_ignored_directory_entry(entry) then
|
||||
callback(expr..entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function after:complete(expr, callback)
|
||||
if utils.ends_in_unfinished_string(expr) then
|
||||
local file_name = expr:match('[%w@/\\.-_+#$%%{}[%]!~ ]+$')
|
||||
if file_name then
|
||||
if file_name:find('[/\\]$') then
|
||||
complete_directory(file_name, expr, callback)
|
||||
else
|
||||
complete_file_name(file_name, expr, callback)
|
||||
end
|
||||
else
|
||||
complete_directory('.', expr, callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
local history_file
|
||||
|
||||
local function invokecallback(self, name, ...)
|
||||
if not self._history_callbacks then
|
||||
return
|
||||
end
|
||||
|
||||
local impl = self._history_callbacks[name]
|
||||
return impl(...)
|
||||
end
|
||||
|
||||
local function init()
|
||||
if os.getenv 'HOME' then
|
||||
history_file = os.getenv('HOME') .. '/.rep.lua.history'
|
||||
end
|
||||
end
|
||||
|
||||
-- XXX I don't know if this callback setup way
|
||||
-- is the best way to go about this (in fact
|
||||
-- I'm pretty sure it isn't), but I just need
|
||||
-- something that works right now.
|
||||
function repl:setuphistorycallbacks(callbacks)
|
||||
self._history_callbacks = callbacks
|
||||
|
||||
if history_file then
|
||||
invokecallback(self, 'load', history_file)
|
||||
end
|
||||
end
|
||||
|
||||
function after:handleline(line)
|
||||
invokecallback(self, 'addline', line)
|
||||
end
|
||||
|
||||
function before:shutdown()
|
||||
if history_file then
|
||||
invokecallback(self, 'save', history_file)
|
||||
end
|
||||
end
|
||||
|
||||
features = 'history'
|
||||
|
||||
init()
|
||||
@@ -0,0 +1,43 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- A plugin that stores the results of the last evaluation in _G._
|
||||
|
||||
local tostring = tostring
|
||||
|
||||
function before:displayresults(results)
|
||||
local context = self:getcontext()
|
||||
|
||||
if self._keep_eval_lastn then
|
||||
context._ = nil
|
||||
|
||||
for i = 1, self._keep_eval_lastn do
|
||||
context['_' .. tostring(i)] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if results.n > 0 then
|
||||
context._ = results[1]
|
||||
|
||||
for i = 1, results.n do
|
||||
context['_' .. tostring(i)] = results[i]
|
||||
end
|
||||
|
||||
self._keep_eval_lastn = results.n
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- A plugin that uses linenoise (https://github.com/hoelzro/lua-linenoise) for prompting
|
||||
|
||||
local ln = require 'linenoise'
|
||||
|
||||
repl:requirefeature 'console'
|
||||
|
||||
function override:showprompt(prompt)
|
||||
self._prompt = prompt -- XXX how do we make sure other plugins don't step on this?
|
||||
end
|
||||
|
||||
function override:lines()
|
||||
return function()
|
||||
return ln.linenoise(self._prompt .. ' ')
|
||||
end
|
||||
end
|
||||
|
||||
repl:iffeature('completion', function()
|
||||
ln.setcompletion(function(completions, line)
|
||||
repl:complete(line, function(completion)
|
||||
ln.addcompletion(completions, completion)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
repl:ifplugin('history', function()
|
||||
repl:setuphistorycallbacks {
|
||||
load = function(filename)
|
||||
ln.historyload(filename)
|
||||
end,
|
||||
|
||||
addline = function(line)
|
||||
ln.historyadd(line)
|
||||
end,
|
||||
|
||||
save = function(filename)
|
||||
ln.historysave(filename)
|
||||
end,
|
||||
}
|
||||
end)
|
||||
|
||||
features = 'input'
|
||||
@@ -0,0 +1,262 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- Pretty prints expression results (console only)
|
||||
|
||||
local format = string.format
|
||||
local tconcat = table.concat
|
||||
local tsort = table.sort
|
||||
local tostring = tostring
|
||||
local type = type
|
||||
local floor = math.floor
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local error = error
|
||||
local stderr = io.stderr
|
||||
|
||||
pcall(require, 'luarocks.require')
|
||||
local ok, term = pcall(require, 'term')
|
||||
if not ok then
|
||||
term = nil
|
||||
end
|
||||
|
||||
local keywords = {
|
||||
['and'] = true,
|
||||
['break'] = true,
|
||||
['do'] = true,
|
||||
['else'] = true,
|
||||
['elseif'] = true,
|
||||
['end'] = true,
|
||||
['false'] = true,
|
||||
['for'] = true,
|
||||
['function'] = true,
|
||||
['if'] = true,
|
||||
['in'] = true,
|
||||
['local'] = true,
|
||||
['nil'] = true,
|
||||
['not'] = true,
|
||||
['or'] = true,
|
||||
['repeat'] = true,
|
||||
['return'] = true,
|
||||
['then'] = true,
|
||||
['true'] = true,
|
||||
['until'] = true,
|
||||
['while'] = true,
|
||||
}
|
||||
|
||||
local function compose(f, g)
|
||||
return function(...)
|
||||
return f(g(...))
|
||||
end
|
||||
end
|
||||
|
||||
local emptycolormap = setmetatable({}, { __index = function()
|
||||
return function(s)
|
||||
return s
|
||||
end
|
||||
end})
|
||||
|
||||
local colormap = emptycolormap
|
||||
|
||||
if term then
|
||||
colormap = {
|
||||
['nil'] = term.colors.blue,
|
||||
string = term.colors.yellow,
|
||||
punctuation = compose(term.colors.green, term.colors.bright),
|
||||
ident = term.colors.red,
|
||||
boolean = term.colors.green,
|
||||
number = term.colors.cyan,
|
||||
path = term.colors.white,
|
||||
misc = term.colors.magenta,
|
||||
}
|
||||
end
|
||||
|
||||
local function isinteger(n)
|
||||
return type(n) == 'number' and floor(n) == n
|
||||
end
|
||||
|
||||
local function isident(s)
|
||||
return type(s) == 'string' and not keywords[s] and s:match('^[a-zA-Z_][a-zA-Z0-9_]*$')
|
||||
end
|
||||
|
||||
-- most of these are arbitrary, I *do* want numbers first, though
|
||||
local type_order = {
|
||||
number = 0,
|
||||
string = 1,
|
||||
userdata = 2,
|
||||
table = 3,
|
||||
thread = 4,
|
||||
boolean = 5,
|
||||
['function'] = 6,
|
||||
cdata = 7,
|
||||
}
|
||||
|
||||
local function cross_type_order(a, b)
|
||||
local pos_a = type_order[ type(a) ]
|
||||
local pos_b = type_order[ type(b) ]
|
||||
|
||||
if pos_a == pos_b then
|
||||
return a < b
|
||||
else
|
||||
return pos_a < pos_b
|
||||
end
|
||||
end
|
||||
|
||||
local function sortedpairs(t)
|
||||
local keys = {}
|
||||
|
||||
local seen_non_string
|
||||
|
||||
for k in pairs(t) do
|
||||
keys[#keys + 1] = k
|
||||
|
||||
if not seen_non_string and type(k) ~= 'string' then
|
||||
seen_non_string = true
|
||||
end
|
||||
end
|
||||
|
||||
local sort_func = seen_non_string and cross_type_order or nil
|
||||
tsort(keys, sort_func)
|
||||
|
||||
local index = 1
|
||||
return function()
|
||||
if keys[index] == nil then
|
||||
return nil
|
||||
else
|
||||
local key = keys[index]
|
||||
local value = t[key]
|
||||
index = index + 1
|
||||
|
||||
return key, value
|
||||
end
|
||||
end, keys
|
||||
end
|
||||
|
||||
local function find_longstring_nest_level(s)
|
||||
local level = 0
|
||||
|
||||
while s:find(']' .. string.rep('=', level) .. ']', 1, true) do
|
||||
level = level + 1
|
||||
end
|
||||
|
||||
return level
|
||||
end
|
||||
|
||||
local function dump(params)
|
||||
local pieces = params.pieces
|
||||
local seen = params.seen
|
||||
local path = params.path
|
||||
local v = params.value
|
||||
local indent = params.indent
|
||||
|
||||
local t = type(v)
|
||||
|
||||
if t == 'nil' or t == 'boolean' or t == 'number' then
|
||||
pieces[#pieces + 1] = colormap[t](tostring(v))
|
||||
elseif t == 'string' then
|
||||
if v:match '\n' then
|
||||
local level = find_longstring_nest_level(v)
|
||||
pieces[#pieces + 1] = colormap.string('[' .. string.rep('=', level) .. '[' .. v .. ']' .. string.rep('=', level) .. ']')
|
||||
else
|
||||
pieces[#pieces + 1] = colormap.string(format('%q', v))
|
||||
end
|
||||
elseif t == 'table' then
|
||||
if seen[v] then
|
||||
pieces[#pieces + 1] = colormap.path(seen[v])
|
||||
return
|
||||
end
|
||||
|
||||
seen[v] = path
|
||||
|
||||
local lastintkey = 0
|
||||
|
||||
pieces[#pieces + 1] = colormap.punctuation '{\n'
|
||||
for i, v in ipairs(v) do
|
||||
for j = 1, indent do
|
||||
pieces[#pieces + 1] = ' '
|
||||
end
|
||||
dump {
|
||||
pieces = pieces,
|
||||
seen = seen,
|
||||
path = path .. '[' .. tostring(i) .. ']',
|
||||
value = v,
|
||||
indent = indent + 1,
|
||||
}
|
||||
pieces[#pieces + 1] = colormap.punctuation ',\n'
|
||||
lastintkey = i
|
||||
end
|
||||
|
||||
for k, v in sortedpairs(v) do
|
||||
if not (isinteger(k) and k <= lastintkey and k > 0) then
|
||||
for j = 1, indent do
|
||||
pieces[#pieces + 1] = ' '
|
||||
end
|
||||
|
||||
if isident(k) then
|
||||
pieces[#pieces + 1] = colormap.ident(k)
|
||||
else
|
||||
pieces[#pieces + 1] = colormap.punctuation '['
|
||||
dump {
|
||||
pieces = pieces,
|
||||
seen = seen,
|
||||
path = path .. '.' .. tostring(k),
|
||||
value = k,
|
||||
indent = indent + 1,
|
||||
}
|
||||
pieces[#pieces + 1] = colormap.punctuation ']'
|
||||
end
|
||||
pieces[#pieces + 1] = colormap.punctuation ' = '
|
||||
dump {
|
||||
pieces = pieces,
|
||||
seen = seen,
|
||||
path = path .. '.' .. tostring(k),
|
||||
value = v,
|
||||
indent = indent + 1,
|
||||
}
|
||||
pieces[#pieces + 1] = colormap.punctuation ',\n'
|
||||
end
|
||||
end
|
||||
|
||||
for j = 1, indent - 1 do
|
||||
pieces[#pieces + 1] = ' '
|
||||
end
|
||||
|
||||
pieces[#pieces + 1] = colormap.punctuation '}'
|
||||
else
|
||||
pieces[#pieces + 1] = colormap.misc(tostring(v))
|
||||
end
|
||||
end
|
||||
|
||||
repl:requirefeature 'console'
|
||||
|
||||
function override:displayresults(results)
|
||||
local pieces = {}
|
||||
|
||||
for i = 1, results.n do
|
||||
dump {
|
||||
pieces = pieces,
|
||||
seen = {},
|
||||
path = '<topvalue>',
|
||||
value = results[i],
|
||||
indent = 1,
|
||||
}
|
||||
pieces[#pieces + 1] = '\n'
|
||||
end
|
||||
|
||||
stderr:write(tconcat(pieces, ''))
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-- A plugin that runs code in $HOME/.rep.lua before the REPL starts
|
||||
|
||||
local setfenv = require('repl.utils').setfenv
|
||||
|
||||
local function readable(filename)
|
||||
local f = io.open(filename, 'r')
|
||||
if not f then
|
||||
return false
|
||||
end
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
|
||||
local function init()
|
||||
local home = os.getenv 'HOME'
|
||||
|
||||
if not home then
|
||||
return
|
||||
end
|
||||
|
||||
local rcfile = home .. '/.rep.lua'
|
||||
|
||||
if not readable(rcfile) then
|
||||
return
|
||||
end
|
||||
|
||||
local chunk = assert(loadfile(rcfile))
|
||||
local env = setmetatable({ repl = repl }, { __index = _G, __newindex = _G })
|
||||
|
||||
setfenv(chunk, env)
|
||||
|
||||
chunk()
|
||||
return true
|
||||
end
|
||||
|
||||
return init()
|
||||
@@ -0,0 +1,41 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
if os.getenv 'LUA_REPL_RLWRAP' then
|
||||
features = 'input'
|
||||
else
|
||||
-- XXX check that we're not receiving input from a non-tty
|
||||
local has_rlwrap = os.execute('which rlwrap >/dev/null 2>/dev/null')
|
||||
|
||||
if type(has_rlwrap) ~= 'boolean' then
|
||||
has_rlwrap = has_rlwrap == 0
|
||||
end
|
||||
|
||||
if not has_rlwrap then
|
||||
error 'Please install rlwrap in order to use the rlwrap plugin'
|
||||
end
|
||||
|
||||
local lowest_index = -1
|
||||
|
||||
while arg[lowest_index] ~= nil do
|
||||
lowest_index = lowest_index - 1
|
||||
end
|
||||
lowest_index = lowest_index + 1
|
||||
os.execute(string.format('LUA_REPL_RLWRAP=1 rlwrap %q %q', arg[lowest_index], arg[0]))
|
||||
os.exit(0)
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
local smatch = string.match
|
||||
|
||||
-- XXX will this affect any other plugins?
|
||||
function around:compilechunk(orig, chunk)
|
||||
local f, err = orig(self, chunk)
|
||||
|
||||
if not f then
|
||||
return f, err
|
||||
end
|
||||
|
||||
if smatch(chunk, ';%s*$') then
|
||||
return function()
|
||||
f()
|
||||
end
|
||||
end
|
||||
|
||||
return f
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
local repl = require 'repl'
|
||||
local sync_repl = repl:clone()
|
||||
local error = error
|
||||
|
||||
-- @class repl.sync
|
||||
--- This module implements a synchronous REPL. It provides
|
||||
--- a run() method for actually running the REPL, and requires
|
||||
--- that implementors implement the lines() method.
|
||||
|
||||
--- Run a REPL loop in a synchronous fashion.
|
||||
-- @name repl.sync:run
|
||||
function sync_repl:run()
|
||||
self:prompt(1)
|
||||
for line in self:lines() do
|
||||
local level = self:handleline(line)
|
||||
self:prompt(level)
|
||||
end
|
||||
self:shutdown()
|
||||
end
|
||||
|
||||
--- Returns an iterator that yields lines to be evaluated.
|
||||
-- @name repl.sync:lines
|
||||
-- @return An iterator.
|
||||
function sync_repl:lines()
|
||||
error 'You must implement the lines method'
|
||||
end
|
||||
|
||||
return sync_repl
|
||||
@@ -0,0 +1,70 @@
|
||||
-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
local setfenv = setfenv or function(f, t)
|
||||
local upvalue_index = 1
|
||||
|
||||
-- XXX we may need a utility library if debug isn't available
|
||||
while true do
|
||||
local name = debug.getupvalue(f, upvalue_index)
|
||||
-- some functions don't have an _ENV upvalue, because
|
||||
-- they never refer to globals
|
||||
if not name then
|
||||
return
|
||||
end
|
||||
if name == '_ENV' then
|
||||
debug.setupvalue(f, upvalue_index, t)
|
||||
return
|
||||
end
|
||||
upvalue_index = upvalue_index + 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Tests wheter an expression ends in an unfinished string literal.
|
||||
-- @return First position in the unfinished string literal or `nil`.
|
||||
local function ends_in_unfinished_string(expr)
|
||||
local position = 0
|
||||
local quote
|
||||
local current_delimiter
|
||||
local last_unmatched_start
|
||||
while true do
|
||||
-- find all quotes:
|
||||
position, quote = expr:match('()([\'"])', position+1)
|
||||
if not position then break end
|
||||
-- if we're currently in a string:
|
||||
if current_delimiter then
|
||||
-- would end current string?
|
||||
if current_delimiter == quote then
|
||||
-- not escaped?
|
||||
if expr:sub(position-1, position-1) ~= '\\' then
|
||||
current_delimiter = nil
|
||||
last_unmatched_start = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
current_delimiter = quote
|
||||
last_unmatched_start = position+1
|
||||
end
|
||||
end
|
||||
return last_unmatched_start
|
||||
end
|
||||
|
||||
return {
|
||||
setfenv = setfenv,
|
||||
ends_in_unfinished_string = ends_in_unfinished_string
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
|
||||
plan(8)
|
||||
|
||||
do -- getprompt tests {{{
|
||||
is(repl:getprompt(1), '>')
|
||||
is(repl:getprompt(2), '>>')
|
||||
end -- }}}
|
||||
|
||||
do -- prompt abstract tests {{{
|
||||
error_like(function()
|
||||
repl:prompt(1)
|
||||
end, 'You must implement the showprompt method')
|
||||
|
||||
error_like(function()
|
||||
repl:prompt(2)
|
||||
end, 'You must implement the showprompt method')
|
||||
end -- }}}
|
||||
|
||||
do -- name tests {{{
|
||||
is(repl:name(), 'REPL')
|
||||
end -- }}}
|
||||
|
||||
do -- handleline abstract tests {{{
|
||||
is(_G.testresult, nil)
|
||||
error_like(function()
|
||||
repl:handleline '_G.testresult = 17'
|
||||
end, 'You must implement the displayresults method')
|
||||
is(_G.testresult, 17)
|
||||
end -- }}}
|
||||
@@ -0,0 +1,119 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
|
||||
plan(45)
|
||||
|
||||
local clone = repl:clone()
|
||||
local prompt
|
||||
local results
|
||||
local errmsg
|
||||
|
||||
isnt(type(clone), 'nil')
|
||||
|
||||
function clone:showprompt(p)
|
||||
prompt = p
|
||||
end
|
||||
|
||||
function clone:displayresults(r)
|
||||
results = r
|
||||
end
|
||||
|
||||
function clone:displayerror(err)
|
||||
errmsg = err
|
||||
end
|
||||
|
||||
do -- prompt tests {{{
|
||||
lives_ok(function()
|
||||
clone:prompt(1)
|
||||
end)
|
||||
|
||||
is(prompt, '>')
|
||||
|
||||
lives_ok(function()
|
||||
clone:prompt(2)
|
||||
end)
|
||||
|
||||
is(prompt, '>>')
|
||||
end -- }}}
|
||||
|
||||
do -- handleline tests {{{
|
||||
is(_G.testresult, nil)
|
||||
is(results, nil)
|
||||
|
||||
lives_ok(function()
|
||||
clone:handleline '_G.testresult = 18'
|
||||
end)
|
||||
|
||||
is(_G.testresult, 18)
|
||||
|
||||
is(type(results), 'table')
|
||||
is(results.n, 0)
|
||||
is(#results, 0)
|
||||
|
||||
lives_ok(function()
|
||||
clone:handleline 'return 19'
|
||||
end)
|
||||
|
||||
is(type(results), 'table')
|
||||
is(results.n, 1)
|
||||
is(#results, 1)
|
||||
is(results[1], 19)
|
||||
|
||||
lives_ok(function()
|
||||
clone:handleline 'return 20, 21, 22'
|
||||
end)
|
||||
|
||||
is(type(results), 'table')
|
||||
is(results.n, 3)
|
||||
is(#results, 3)
|
||||
is(results[1], 20)
|
||||
is(results[2], 21)
|
||||
is(results[3], 22)
|
||||
|
||||
lives_ok(function()
|
||||
clone:handleline 'return 1, nil, nil, nil, nil, nil, nil, 2'
|
||||
end)
|
||||
|
||||
is(type(results), 'table')
|
||||
is(results.n, 8)
|
||||
is(results[1], 1)
|
||||
for i = 2, 7 do
|
||||
is(results[i], nil)
|
||||
end
|
||||
is(results[8], 2)
|
||||
end -- }}}
|
||||
|
||||
do -- error handling tests {{{
|
||||
lives_ok(function()
|
||||
clone:handleline '3 4'
|
||||
end)
|
||||
|
||||
isnt(type(errmsg), 'nil')
|
||||
|
||||
errmsg = nil
|
||||
|
||||
lives_ok(function()
|
||||
clone:handleline 'error "foo"'
|
||||
end)
|
||||
|
||||
like(errmsg, 'foo')
|
||||
end -- }}}
|
||||
|
||||
do -- multi-line input tests {{{
|
||||
errmsg = nil
|
||||
_G.t = {}
|
||||
|
||||
lives_ok(function()
|
||||
clone:handleline 'for i = 1, 3 do'
|
||||
clone:handleline ' table.insert(_G.t, i)'
|
||||
clone:handleline 'end'
|
||||
end)
|
||||
|
||||
is(errmsg, nil)
|
||||
is(#_G.t, 3)
|
||||
is(_G.t[1], 1)
|
||||
is(_G.t[2], 2)
|
||||
is(_G.t[3], 3)
|
||||
end -- }}}
|
||||
@@ -0,0 +1,56 @@
|
||||
local _M = {}
|
||||
|
||||
function _M.next_line_number()
|
||||
local info = debug.getinfo(2, 'l')
|
||||
return info.currentline + 1 -- doesn't work with whitespace
|
||||
end
|
||||
|
||||
function _M.cmp_tables(lhs, rhs)
|
||||
local ok = true
|
||||
local got
|
||||
local expected
|
||||
local failing_k
|
||||
|
||||
for k, v in pairs(lhs) do
|
||||
local rv = rhs[k]
|
||||
|
||||
if v ~= rv then
|
||||
ok = false
|
||||
failing_k = k
|
||||
got = v
|
||||
expected = rv
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if ok then
|
||||
for k, v in pairs(rhs) do
|
||||
local lv = lhs[k]
|
||||
|
||||
if v ~= lv then
|
||||
ok = false
|
||||
failing_k = k
|
||||
got = lv
|
||||
expected = v
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ok then
|
||||
pass()
|
||||
else
|
||||
fail 'value mismatch'
|
||||
diag(string.format(' got[%q]: %s', tostring(failing_k), tostring(got)))
|
||||
diag(string.format('expected[%q]: %s', tostring(failing_k), tostring(expected)))
|
||||
end
|
||||
end
|
||||
|
||||
function _M.gather_results(...)
|
||||
return {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
|
||||
return _M
|
||||
@@ -0,0 +1,202 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
local utils = require 'test-utils'
|
||||
|
||||
plan(26)
|
||||
|
||||
local clone = repl:clone()
|
||||
|
||||
do -- basic tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_normal
|
||||
local has_called_after
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_normal = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo()
|
||||
has_called_after = true
|
||||
ok(has_called_normal)
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
ok(has_called_normal)
|
||||
ok(has_called_after)
|
||||
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
function after:nonexistent()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
|
||||
|
||||
_, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
after.foo = 17
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format('%d: 17 is not a function', line_no))
|
||||
end -- }}}
|
||||
|
||||
do -- arguments tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
local orig_args
|
||||
local got_args
|
||||
|
||||
function with_plugin:foo(...)
|
||||
orig_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo(...)
|
||||
got_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
is(got_args.n, 0)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
|
||||
with_plugin:foo(1, 2, 3)
|
||||
is(got_args.n, 3)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], 2)
|
||||
is(got_args[3], 3)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
|
||||
with_plugin:foo(1, nil, nil, nil, 5)
|
||||
is(got_args.n, 5)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], nil)
|
||||
is(got_args[3], nil)
|
||||
is(got_args[4], nil)
|
||||
is(got_args[5], 5)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
end -- }}}
|
||||
|
||||
do -- exception tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_original
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_original = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo()
|
||||
error 'uh-oh'
|
||||
end
|
||||
end)
|
||||
|
||||
local _, err = pcall(with_plugin.foo, with_plugin)
|
||||
|
||||
like(err, 'uh%-oh')
|
||||
ok(has_called_original)
|
||||
end -- }}}
|
||||
|
||||
do -- return value tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
function with_plugin:foo()
|
||||
return 17
|
||||
end
|
||||
|
||||
function with_plugin:bar()
|
||||
return 18, 19, 20
|
||||
end
|
||||
|
||||
function with_plugin:baz()
|
||||
return 1, nil, nil, nil, 5
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo()
|
||||
return 18
|
||||
end
|
||||
|
||||
function after:bar()
|
||||
return 18
|
||||
end
|
||||
|
||||
function after:baz()
|
||||
return 18
|
||||
end
|
||||
end)
|
||||
|
||||
local result = with_plugin:foo()
|
||||
is(result, 17)
|
||||
|
||||
local results = utils.gather_results(with_plugin:bar())
|
||||
utils.cmp_tables(results, { n = 3, 18, 19, 20 })
|
||||
|
||||
results = utils.gather_results(with_plugin:baz())
|
||||
utils.cmp_tables(results, { n = 5, 1, nil, nil, nil, 5 })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, multiple plugins {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo()
|
||||
calls[#calls + 1] = 'first'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo()
|
||||
calls[#calls + 1] = 'second'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'original', 'first', 'second' })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, single plugin {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function after:foo()
|
||||
calls[#calls + 1] = 'first'
|
||||
end
|
||||
|
||||
function after:foo()
|
||||
calls[#calls + 1] = 'second'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'original', 'first', 'second' })
|
||||
end -- }}}
|
||||
@@ -0,0 +1,218 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
local compat = require 'repl.compat'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
local utils = require 'test-utils'
|
||||
|
||||
plan(27)
|
||||
|
||||
local clone = repl:clone()
|
||||
|
||||
do -- basic tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_normal
|
||||
local has_called_around
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_normal = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo(orig, ...)
|
||||
has_called_around = true
|
||||
ok(not has_called_normal)
|
||||
local return_values = utils.gather_results(orig(self, ...))
|
||||
ok(has_called_normal)
|
||||
return compat.unpack(return_values, 1, return_values.n)
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
ok(has_called_normal)
|
||||
ok(has_called_around)
|
||||
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
function around:nonexistent()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
|
||||
|
||||
_, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
around.foo = 17
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format('%d: 17 is not a function', line_no))
|
||||
end -- }}}
|
||||
|
||||
do -- arguments tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
local orig_args
|
||||
local got_args
|
||||
|
||||
function with_plugin:foo(...)
|
||||
orig_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo(orig, ...)
|
||||
got_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
|
||||
return orig(self, ...)
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
is(got_args.n, 0)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
|
||||
with_plugin:foo(1, 2, 3)
|
||||
is(got_args.n, 3)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], 2)
|
||||
is(got_args[3], 3)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
|
||||
with_plugin:foo(1, nil, nil, nil, 5)
|
||||
is(got_args.n, 5)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], nil)
|
||||
is(got_args[3], nil)
|
||||
is(got_args[4], nil)
|
||||
is(got_args[5], 5)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
end -- }}}
|
||||
|
||||
do -- exception tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_original
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_original = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo()
|
||||
error 'uh-oh'
|
||||
end
|
||||
end)
|
||||
|
||||
local _, err = pcall(with_plugin.foo, with_plugin)
|
||||
|
||||
like(err, 'uh%-oh')
|
||||
ok(not has_called_original)
|
||||
end -- }}}
|
||||
|
||||
do -- return value tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
function with_plugin:foo()
|
||||
return 17
|
||||
end
|
||||
|
||||
function with_plugin:bar()
|
||||
return 18, 19, 20
|
||||
end
|
||||
|
||||
function with_plugin:baz()
|
||||
return 1, nil, nil, nil, 5
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo()
|
||||
return 18
|
||||
end
|
||||
|
||||
function around:bar()
|
||||
return 19, 20, 21
|
||||
end
|
||||
|
||||
function around:baz()
|
||||
return 1, nil, nil, 4
|
||||
end
|
||||
end)
|
||||
|
||||
local result = with_plugin:foo()
|
||||
is(result, 18)
|
||||
|
||||
local results = utils.gather_results(with_plugin:bar())
|
||||
utils.cmp_tables(results, { n = 3, 19, 20, 21 })
|
||||
|
||||
results = utils.gather_results(with_plugin:baz())
|
||||
utils.cmp_tables(results, { n = 4, 1, nil, nil, 4 })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, multiple plugins {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo(orig)
|
||||
calls[#calls + 1] = 'before_one'
|
||||
orig()
|
||||
calls[#calls + 1] = 'after_one'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo(orig)
|
||||
calls[#calls + 1] = 'before_two'
|
||||
orig()
|
||||
calls[#calls + 1] = 'after_two'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'before_two', 'before_one', 'original',
|
||||
'after_one', 'after_two' })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, single plugin {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function around:foo(orig)
|
||||
calls[#calls + 1] = 'before_one'
|
||||
orig()
|
||||
calls[#calls + 1] = 'after_one'
|
||||
end
|
||||
|
||||
function around:foo(orig)
|
||||
calls[#calls + 1] = 'before_two'
|
||||
orig()
|
||||
calls[#calls + 1] = 'after_two'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'before_two', 'before_one', 'original',
|
||||
'after_one', 'after_two' })
|
||||
end -- }}}
|
||||
@@ -0,0 +1,206 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
local utils = require 'test-utils'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
|
||||
plan(28)
|
||||
|
||||
local clone = repl:clone()
|
||||
|
||||
do -- basic tests {{{
|
||||
local loaded
|
||||
|
||||
clone:loadplugin(function()
|
||||
loaded = true
|
||||
end)
|
||||
|
||||
ok(loaded)
|
||||
|
||||
error_like(function()
|
||||
clone:loadplugin(function()
|
||||
error 'uh-oh'
|
||||
end)
|
||||
end, 'uh%-oh')
|
||||
|
||||
end -- }}}
|
||||
|
||||
do -- loading the same plugin twice {{{
|
||||
local function plugin()
|
||||
end
|
||||
|
||||
local line_no
|
||||
|
||||
clone:loadplugin(plugin)
|
||||
local _, err = pcall(function()
|
||||
line_no = utils.next_line_number()
|
||||
clone:loadplugin(plugin)
|
||||
end)
|
||||
like(err, tostring(line_no) .. ': plugin "function:%s+%S+" has already been loaded')
|
||||
|
||||
_, err = pcall(function()
|
||||
line_no = utils.next_line_number()
|
||||
clone:clone():loadplugin(plugin)
|
||||
end)
|
||||
like(err, tostring(line_no) .. ': plugin "function:%s+%S+" has already been loaded')
|
||||
|
||||
repl:clone():loadplugin(plugin)
|
||||
repl:clone():loadplugin(plugin)
|
||||
end -- }}}
|
||||
|
||||
do -- loading plugins by name {{{
|
||||
local loaded
|
||||
|
||||
package.preload['repl.plugins.test'] = function()
|
||||
loaded = true
|
||||
end
|
||||
|
||||
clone:clone():loadplugin 'test'
|
||||
|
||||
ok(loaded)
|
||||
loaded = false
|
||||
|
||||
clone:clone():loadplugin 'test'
|
||||
|
||||
ok(loaded, 'loading a plugin twice should initialize it twice')
|
||||
|
||||
package.preload['repl.plugins.test'] = function()
|
||||
error 'uh-oh'
|
||||
end
|
||||
|
||||
error_like(function()
|
||||
clone:clone():loadplugin 'test'
|
||||
end, 'uh%-oh')
|
||||
|
||||
package.preload['repl.plugins.test'] = nil
|
||||
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
line_no = utils.next_line_number()
|
||||
clone:clone():loadplugin 'test'
|
||||
end)
|
||||
like(err, tostring(line_no) .. ': unable to locate plugin')
|
||||
end -- }}}
|
||||
|
||||
do -- hasplugin tests {{{
|
||||
local child = repl:clone()
|
||||
|
||||
local plugin = function()
|
||||
end
|
||||
|
||||
child:loadplugin(plugin)
|
||||
|
||||
local grandchild = child:clone()
|
||||
|
||||
ok(not repl:hasplugin(plugin))
|
||||
ok(child:hasplugin(plugin))
|
||||
ok(grandchild:hasplugin(plugin))
|
||||
|
||||
plugin = function()
|
||||
end
|
||||
|
||||
child:loadplugin(plugin)
|
||||
|
||||
ok(not repl:hasplugin(plugin))
|
||||
ok(child:hasplugin(plugin))
|
||||
ok(not grandchild:hasplugin(plugin))
|
||||
end -- }}}
|
||||
|
||||
do -- global tests {{{
|
||||
local clone = repl:clone()
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
clone:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
foo = 17
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, tostring(line_no) .. ': global environment is read%-only %(key = "foo"%)')
|
||||
|
||||
_, err = pcall(function()
|
||||
clone:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
_G.foo = 17
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, tostring(line_no) .. ': global environment is read%-only %(key = "foo"%)')
|
||||
end -- }}}
|
||||
|
||||
do -- ifplugin tests {{{
|
||||
local clone = repl:clone()
|
||||
local has_run
|
||||
|
||||
package.preload['repl.plugins.test'] = function()
|
||||
end
|
||||
|
||||
clone:ifplugin('test', function()
|
||||
has_run = true
|
||||
end)
|
||||
|
||||
ok(not has_run)
|
||||
|
||||
clone:loadplugin 'test'
|
||||
|
||||
ok(has_run)
|
||||
|
||||
has_run = false
|
||||
|
||||
clone:ifplugin('test', function()
|
||||
has_run = true
|
||||
end)
|
||||
|
||||
ok(has_run)
|
||||
end -- }}}
|
||||
|
||||
do -- ifplugin multiple times {{{
|
||||
local clone = repl:clone()
|
||||
local has_run
|
||||
local has_run2
|
||||
|
||||
package.preload['repl.plugins.test'] = function()
|
||||
end
|
||||
|
||||
clone:ifplugin('test', function()
|
||||
has_run = true
|
||||
end)
|
||||
|
||||
clone:ifplugin('test', function()
|
||||
has_run2 = true
|
||||
end)
|
||||
|
||||
clone:loadplugin 'test'
|
||||
|
||||
ok(has_run)
|
||||
ok(has_run2)
|
||||
end -- }}}
|
||||
|
||||
do -- plugin return value {{{
|
||||
local clone = repl:clone()
|
||||
|
||||
local result = clone:loadplugin(function()
|
||||
return 17
|
||||
end)
|
||||
|
||||
local result2 = clone:loadplugin(function()
|
||||
end)
|
||||
|
||||
local result3, result4 = clone:loadplugin(function()
|
||||
return 18, 19
|
||||
end)
|
||||
|
||||
local result5, result6, result7 = clone:loadplugin(function()
|
||||
return 20, nil, 21
|
||||
end)
|
||||
|
||||
is(result, 17)
|
||||
is(result2, nil)
|
||||
is(result3, 18)
|
||||
is(result4, 19)
|
||||
is(result5, 20)
|
||||
is(result6, nil)
|
||||
is(result7, 21)
|
||||
end -- }}}
|
||||
@@ -0,0 +1,201 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
local utils = require 'test-utils'
|
||||
|
||||
plan(26)
|
||||
|
||||
local clone = repl:clone()
|
||||
|
||||
do -- basic tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_normal
|
||||
local has_called_before
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_normal = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo()
|
||||
has_called_before = true
|
||||
ok(not has_called_normal)
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
ok(has_called_normal)
|
||||
ok(has_called_before)
|
||||
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
function before:nonexistent()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
|
||||
|
||||
_, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
before.foo = 17
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format('%d: 17 is not a function', line_no))
|
||||
end -- }}}
|
||||
|
||||
do -- arguments tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
local orig_args
|
||||
local got_args
|
||||
|
||||
function with_plugin:foo(...)
|
||||
orig_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo(...)
|
||||
got_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
is(got_args.n, 0)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
|
||||
with_plugin:foo(1, 2, 3)
|
||||
is(got_args.n, 3)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], 2)
|
||||
is(got_args[3], 3)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
|
||||
with_plugin:foo(1, nil, nil, nil, 5)
|
||||
is(got_args.n, 5)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], nil)
|
||||
is(got_args[3], nil)
|
||||
is(got_args[4], nil)
|
||||
is(got_args[5], 5)
|
||||
utils.cmp_tables(orig_args, got_args)
|
||||
end -- }}}
|
||||
|
||||
do -- exception tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_original
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_original = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo()
|
||||
error 'uh-oh'
|
||||
end
|
||||
end)
|
||||
|
||||
local _, err = pcall(with_plugin.foo, with_plugin)
|
||||
|
||||
like(err, 'uh%-oh')
|
||||
ok(not has_called_original)
|
||||
end -- }}}
|
||||
|
||||
do -- return value tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
function with_plugin:foo()
|
||||
return 17
|
||||
end
|
||||
|
||||
function with_plugin:bar()
|
||||
return 18, 19, 20
|
||||
end
|
||||
|
||||
function with_plugin:baz()
|
||||
return 1, nil, nil, nil, 5
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo()
|
||||
return 18
|
||||
end
|
||||
|
||||
function before:bar()
|
||||
return 18
|
||||
end
|
||||
|
||||
function before:baz()
|
||||
end
|
||||
end)
|
||||
|
||||
local result = with_plugin:foo()
|
||||
is(result, 17)
|
||||
|
||||
local results = utils.gather_results(with_plugin:bar())
|
||||
utils.cmp_tables(results, { n = 3, 18, 19, 20 })
|
||||
|
||||
results = utils.gather_results(with_plugin:baz())
|
||||
utils.cmp_tables(results, { n = 5, 1, nil, nil, nil, 5 })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, multiple plugins {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo()
|
||||
calls[#calls + 1] = 'first'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo()
|
||||
calls[#calls + 1] = 'second'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'second', 'first', 'original' })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, single plugin {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function before:foo()
|
||||
calls[#calls + 1] = 'first'
|
||||
end
|
||||
|
||||
function before:foo()
|
||||
calls[#calls + 1] = 'second'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'second', 'first', 'original' })
|
||||
end -- }}}
|
||||
@@ -0,0 +1,132 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
local utils = require 'test-utils'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
|
||||
plan(19)
|
||||
|
||||
do -- basic tests {{{
|
||||
local clone = repl:clone()
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
ok(clone:hasfeature 'foo')
|
||||
ok(not clone:hasfeature 'bar')
|
||||
ok(not clone:hasfeature 'baz')
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = { 'bar', 'baz' }
|
||||
end)
|
||||
|
||||
ok(clone:hasfeature 'foo')
|
||||
ok(clone:hasfeature 'bar')
|
||||
ok(clone:hasfeature 'baz')
|
||||
end -- }}}
|
||||
|
||||
do -- requirefeature {{{
|
||||
local clone = repl:clone()
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
clone:requirefeature 'foo'
|
||||
|
||||
local line_no
|
||||
local _, err = pcall(function()
|
||||
line_no = utils.next_line_number()
|
||||
clone:requirefeature 'bar'
|
||||
end)
|
||||
|
||||
like(err, tostring(line_no) .. ': required feature "bar" not present')
|
||||
end -- }}}
|
||||
|
||||
do -- conflicts {{{
|
||||
local clone = repl:clone()
|
||||
local line_no
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
local _, err = pcall(function()
|
||||
line_no = utils.next_line_number()
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
end)
|
||||
like(err, tostring(line_no) .. ': feature "foo" already present')
|
||||
|
||||
-- XXX what about methods injected into the object?
|
||||
end -- }}}
|
||||
|
||||
do -- clone:hasfeature {{{
|
||||
local child = repl:clone()
|
||||
|
||||
child:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
local grandchild = child:clone()
|
||||
|
||||
ok(not repl:hasfeature 'foo')
|
||||
ok(child:hasfeature 'foo')
|
||||
ok(grandchild:hasfeature 'foo')
|
||||
|
||||
child:loadplugin(function()
|
||||
features = 'bar'
|
||||
end)
|
||||
|
||||
ok(not repl:hasfeature 'bar')
|
||||
ok(child:hasfeature 'bar')
|
||||
ok(not grandchild:hasfeature 'bar')
|
||||
end -- }}}
|
||||
|
||||
do -- iffeature tests {{{
|
||||
local clone = repl:clone()
|
||||
local has_run
|
||||
|
||||
clone:iffeature('foo', function()
|
||||
has_run = true
|
||||
end)
|
||||
|
||||
ok(not has_run)
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
ok(has_run)
|
||||
|
||||
has_run = false
|
||||
|
||||
clone:iffeature('foo', function()
|
||||
has_run = true
|
||||
end)
|
||||
|
||||
ok(has_run)
|
||||
end -- }}}
|
||||
|
||||
do -- iffeature multiple times {{{
|
||||
local clone = repl:clone()
|
||||
local has_run
|
||||
local has_run2
|
||||
|
||||
clone:iffeature('foo', function()
|
||||
has_run = true
|
||||
end)
|
||||
|
||||
clone:iffeature('foo', function()
|
||||
has_run2 = true
|
||||
end)
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
ok(has_run)
|
||||
ok(has_run2)
|
||||
end -- }}}
|
||||
@@ -0,0 +1,201 @@
|
||||
-- vim:foldmethod=marker
|
||||
local repl = require 'repl'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
local utils = require 'test-utils'
|
||||
|
||||
plan(25)
|
||||
|
||||
local clone = repl:clone()
|
||||
|
||||
do -- basic tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_normal
|
||||
local has_called_override
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_normal = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo()
|
||||
has_called_override = true
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
ok(not has_called_normal)
|
||||
ok(has_called_override)
|
||||
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
function override:nonexistent()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
|
||||
|
||||
_, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
override.foo = 17
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format('%d: 17 is not a function', line_no))
|
||||
end -- }}}
|
||||
|
||||
do -- arguments tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
local orig_args
|
||||
local got_args
|
||||
|
||||
function with_plugin:foo(...)
|
||||
orig_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo(...)
|
||||
got_args = {
|
||||
n = select('#', ...),
|
||||
...,
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
is(got_args.n, 0)
|
||||
ok(not orig_args)
|
||||
|
||||
with_plugin:foo(1, 2, 3)
|
||||
is(got_args.n, 3)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], 2)
|
||||
is(got_args[3], 3)
|
||||
ok(not orig_args)
|
||||
|
||||
with_plugin:foo(1, nil, nil, nil, 5)
|
||||
is(got_args.n, 5)
|
||||
is(got_args[1], 1)
|
||||
is(got_args[2], nil)
|
||||
is(got_args[3], nil)
|
||||
is(got_args[4], nil)
|
||||
is(got_args[5], 5)
|
||||
ok(not orig_args)
|
||||
end -- }}}
|
||||
|
||||
do -- exception tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
local has_called_original
|
||||
|
||||
function with_plugin:foo()
|
||||
has_called_original = true
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo()
|
||||
error 'uh-oh'
|
||||
end
|
||||
end)
|
||||
|
||||
local _, err = pcall(with_plugin.foo, with_plugin)
|
||||
|
||||
like(err, 'uh%-oh')
|
||||
ok(not has_called_original)
|
||||
end -- }}}
|
||||
|
||||
do -- return value tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
function with_plugin:foo()
|
||||
return 17
|
||||
end
|
||||
|
||||
function with_plugin:bar()
|
||||
return 18, 19, 20
|
||||
end
|
||||
|
||||
function with_plugin:baz()
|
||||
return 1, nil, nil, nil, 5
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo()
|
||||
return 18
|
||||
end
|
||||
|
||||
function override:bar()
|
||||
return 19, 20, 21
|
||||
end
|
||||
|
||||
function override:baz()
|
||||
return 1, nil, nil, 4
|
||||
end
|
||||
end)
|
||||
|
||||
local result = with_plugin:foo()
|
||||
is(result, 18)
|
||||
|
||||
local results = utils.gather_results(with_plugin:bar())
|
||||
utils.cmp_tables(results, { n = 3, 19, 20, 21 })
|
||||
|
||||
results = utils.gather_results(with_plugin:baz())
|
||||
utils.cmp_tables(results, { n = 4, 1, nil, nil, 4 })
|
||||
end -- }}}
|
||||
|
||||
do -- multiple advice, multiple plugins {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo()
|
||||
calls[#calls + 1] = 'first'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo()
|
||||
calls[#calls + 1] = 'second'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'second' })
|
||||
end
|
||||
|
||||
do -- multiple advice, single plugin {{{
|
||||
local with_plugin = clone:clone()
|
||||
local calls = {}
|
||||
|
||||
function with_plugin:foo()
|
||||
calls[#calls + 1] = 'original'
|
||||
end
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function override:foo()
|
||||
calls[#calls + 1] = 'first'
|
||||
end
|
||||
|
||||
function override:foo()
|
||||
calls[#calls + 1] = 'second'
|
||||
end
|
||||
end)
|
||||
|
||||
with_plugin:foo()
|
||||
|
||||
utils.cmp_tables(calls, { 'second' })
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
-- vim:foldmethod=marker
|
||||
local r = require 'repl' -- we don't call it 'repl' so we don't shadow
|
||||
-- repl in the plugin environment
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
local utils = require 'test-utils'
|
||||
|
||||
plan(5)
|
||||
|
||||
local clone = r:clone()
|
||||
|
||||
do -- basic tests {{{
|
||||
local with_plugin = clone:clone()
|
||||
|
||||
function with_plugin:foo()
|
||||
end
|
||||
|
||||
local line_no
|
||||
|
||||
local _, err = pcall(function()
|
||||
with_plugin:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
function repl:foo()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, string.format("%d: The 'foo' method already exists", line_no))
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
function repl:bar()
|
||||
return 17
|
||||
end
|
||||
end)
|
||||
|
||||
is(with_plugin:bar(), 17)
|
||||
|
||||
with_plugin:loadplugin(function()
|
||||
repl.baz = 18
|
||||
end)
|
||||
|
||||
is(with_plugin.baz, 18)
|
||||
end -- }}}
|
||||
|
||||
do -- conflict tests {{{
|
||||
local clone = r:clone()
|
||||
local line_no
|
||||
|
||||
clone:loadplugin(function()
|
||||
function repl:foo()
|
||||
end
|
||||
end)
|
||||
|
||||
local _, err = pcall(function()
|
||||
clone:loadplugin(function()
|
||||
line_no = utils.next_line_number()
|
||||
function repl:foo()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
like(err, tostring(line_no) .. ": The 'foo' method already exists")
|
||||
end -- }}}
|
||||
|
||||
do -- proxy tests {{{
|
||||
local clone = r:clone()
|
||||
|
||||
clone:loadplugin(function()
|
||||
features = 'foo'
|
||||
end)
|
||||
|
||||
clone:loadplugin(function()
|
||||
ok(repl:hasfeature 'foo')
|
||||
end)
|
||||
end -- }}}
|
||||
@@ -0,0 +1,52 @@
|
||||
-- vim:foldmethod=marker
|
||||
local sync = require 'repl.sync'
|
||||
pcall(require, 'luarocks.loader')
|
||||
require 'Test.More'
|
||||
|
||||
plan(13)
|
||||
|
||||
local clone = sync:clone()
|
||||
local resultlist = {}
|
||||
|
||||
function clone:lines()
|
||||
local index = 0
|
||||
local function iterator(s)
|
||||
index = index + 1
|
||||
return s[index]
|
||||
end
|
||||
|
||||
return iterator, {
|
||||
'return foo',
|
||||
'return 1',
|
||||
'return "bar"',
|
||||
'return {}',
|
||||
'return 1, 2, 3',
|
||||
}
|
||||
end
|
||||
|
||||
function clone:showprompt()
|
||||
end
|
||||
|
||||
function clone:displayresults(results)
|
||||
resultlist[#resultlist + 1] = results
|
||||
end
|
||||
|
||||
clone:run()
|
||||
|
||||
is(#resultlist, 5)
|
||||
is(resultlist[1].n, 1)
|
||||
is(resultlist[1][1], nil)
|
||||
|
||||
is(resultlist[2].n, 1)
|
||||
is(resultlist[2][1], 1)
|
||||
|
||||
is(resultlist[3].n, 1)
|
||||
is(resultlist[3][1], 'bar')
|
||||
|
||||
is(resultlist[4].n, 1)
|
||||
is(type(resultlist[4][1]), 'table')
|
||||
|
||||
is(resultlist[5].n, 3)
|
||||
is(resultlist[5][1], 1)
|
||||
is(resultlist[5][2], 2)
|
||||
is(resultlist[5][3], 3)
|
||||
Reference in New Issue
Block a user