Skip to content

Latest commit

 

History

History
285 lines (179 loc) · 10.3 KB

plugins.md

File metadata and controls

285 lines (179 loc) · 10.3 KB

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:

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:

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:

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:

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:

features = 'completion' -- make sure you're not setting a local!

If you wish you provide multiple features, simply use a table:

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.