LSLua 0.8

by doy (jluehrs2 at uiuc dot edu)
updated by THC4k (http://www.ls-themes.org)

TOC

  1. Intro
  2. Settings and Bang Commands
  3. LsLua functions
  4. Lua modules
    1. args
    2. evar
    3. evarutils
    4. textedit
    5. lsmodule

Introduction

LSLua is a scripting interface to LiteStep using the Lua scripting language. Lua is a cross platform, open source, embeddable scripting language. This module provides an interface to the LSAPI through Lua scripts.

This document explains what LSLua as a module provides; for convenience, each of lua code, litestep config code, and LSAPI code are highlighted.

See history.txt for a list of bugs and changes in LSLua.

To see how LSLua may be used, with examples, see http://wiki.litestep.com.ar/Modules:LSLua

Visual C++ 2008 runtimes are required. If Litestep cannot load the module, click here.

Module sources are managed at bitbucket.

Information about Lua

See the Lua 5.1 manual for a quick reference on using Lua. See also the book Programming in Lua. Code snippets and general information can be found at the lua-users wiki. Finally, for more specific questions not covered by those resources, feel free to ask for help in #lua on irc.freenode.net

Settings and Bang Commands

To load LSLua use *NetLoadModule lslua-0.8 load lslua.dll in your theme.rc. The load lslua.dll part is important, since, due to restrictions in NetLoadModule, all of the modules that come with LSLua must have a .dll extension for NetLoadModule to unpack them to the correct place.

LSLua can be loaded threaded, but be aware that this can produce strange behavior. Litestep has no means of syncronisation between modules and you will most likely use lslua to interact with other modules. In most cases lua's coroutines will be the better choice over loading the module threaded.


*LuaFile <filename>

Executes the given file. This registers any functions that are declared, and executes any statements not declared in a function. Any function whose name begins with bang_ is also registered as a bang command, named with the rest of the function name. For example, the function bang_test would be registered as !test. Bang functions take one optional string argument consisting of the entire argument string passed to them.


*LuaBinaryModuleDir <directory>

Declares a directory to be added to the search path for binary modules (package.cpath). Uses an extension of .dll.


*LuaScriptModuleDir <directory>

Declares a directory to be added to the search path for Lua script modules (package.path). Uses an extension of .lua or .lua.dll.


*LuaModuleDir <directory>

Equivalent to both of *LuaBinaryModuleDir and *LuaScriptModuleDir.


LSLuaErrorFile <file>

If set errors in lua scripts will be dumped to the <file> instead of shown via MessageBox.


!LuaExec <lua chunk>

Executes the argument as a Lua chunk.

LsLua Functions

LSLua defines the global table lslua which contains some important functions and variables.


lslua.message_box (text, title)

Creates a message box with text of text and optional title of title. Quotes do work correctly, unlike !alert.


lslua.exec (bang [,*args])

Executes bang with the optional args as a command, like LSXCommand or !execute [].

Uses LSExecute() for calls with no args else LSExecuteEx.


lslua.res ()

Returns a table such that lslua.res().x is the x resolution, lslua.res().y is the y resolution, and lslua.res().bpp is the bits per pixel.


lslua.mouse ()

Returns x,y such that x is the x coordinate of the mouse and y is the y coordinate of the mouse.


lslua.milli ()

Returns the current number of milliseconds since the last second (use os.date to get the rest of the time information).


lslua.version

a string containing the lslua version.


lslua.get_evar (name)

Get an evar's value in memory, not file. Returns a string, or nil if evar is not defined. Similar to what you expect $name$ would give.

Uses GetRCString().

Note: the preferred way to use this function is through the evar module.


lslua.get_line (name)

Get an evar's raw value in memory, not file. Returns a string, or nil if evar is not defined. Similar to get_evar but does not return first token.

Uses GetRCLine().


lslua.set_evar (name, value)

Set an evar's value in memory, not file.

Uses LSSetVariable().

Note: the preferred way to use this function is through the evar module.


lslua.rc_lines (prefix, file)

Get lines with given prefix from rc file. Use empty string for prefix to return all lines. If file not specified, defaults to settings already loaded in memory. Returns a table of strings.

Uses LCReadNextLine().


lslua.init

A user defined value that is called at the very end of module load, so that bangs are registered. It is also executed in a coroutine, so timers should work.


lslua.quit

Similar to lslua.init, but is called when lslua is unloaded.


Lua Modules

LSLua comes with eight modules designed to make certain tasks easier. To use one, include the line require "<modulename>" at the top of your Lua script.

4.1 - args module

The args module defines some useful argument parsing functions.


args.strjoin (table, sep)

Takes two arguments: a table of strings, and a string to use as the separator. Returns a string containing all of the elements of the table concatenated together, with elements separated by the separator string passed as the second argument. For example, args.strjoin( { "this", "is", "a", "test" }, " " ) == "this is a test"


args.strsplit (string, sep)

Takes two arguments: a string, and a delimiter (a separator). It splits the string up into a table of substrings based on the delimiter, and returns that table. For example, args.strsplit( "this is a test", " " ) == { "this", "is", "a", "test" }


args.mstrsplit (string, sep)

The same as args.strsplit, except it returns the results as multiple return values rather than as a table.


args.qsplit (string [, delimiters [, leftquotes [, rightquotes ]]])

Takes between 1 and 4 arguments, some of them optional. first argument is a string, the second is a table of strings to use as delimiters, and the third and fourth are tables of strings to use as left and right quotes, respectively. If the fourth argument is omitted, it defaults to the same as the third, and if they are both omitted, they default to { "\"", "'" }. If the second argument is omitted, it defaults to { " " }. If any of the second, third, or fourth arguments are strings, they are automatically converted to tables holding those strings. This function splits the string into substrings based on the delimiter like args.strsplit does, but it takes into account quote characters as well. For example,

args.qsplit( "this is a 'string with quotes'" ) ==
    { "this", "is", "a", "string with quotes" }
args.qsplit( "%{hello},%{world}", ",", "%{", "}" ) == { "hello", "world" }
args.qsplit( "[this is][a test]", "", "[", "]" ) == { "this is", "a test" }

args.mqsplit (string [, delimiters [, leftquotes [, rightquotes ]]])

The same as args.qsplit, except it returns the results as multiple return values rather than as a table.

4.2 - evar module

The evar module allows access to and (re)assignment of LiteStep evars.


evar.toboolean (evar)

Takes one argument, a string for an evar's name. It returns a boolean evaluating the given evar as a boolean as how LiteStep core would in RC 'scripting'/preprocessing: It returns false for an evar whose value is one of these strings: "0", "false", "off", or "no". Otherwise it returns true.

If for some reason you need to access the evar named "toboolean", access it with a different casing, like "ToBoolean".


Evar Access

The evar table itself can be indexed to access the values of evars. For example, evar.LiteStepDir == evar[ "LiteStep" .. "Dir" ] --is "C:\LiteStep\" (on my system).

The result is nil for both undefined evars and those evars previously set to nil through the evar table (a magic value is replaced with nil).

Evar Assignment

Keys of the evar table can be assigned to; doing so will set an evar's value. tostring() is applied to this value. For example, evar.nameOfEvar = 13.

Setting an evar to nil this way will actually set the evar to a magic value.

Notes on evar access and assignment and why a magic value for nil:

There is no way (aside from !reload or !recycle or restarting LS) to undefine an evar, so the evar module has its own notion of 'undefined'; It does so using its own magic value.

So it makes no sense to set an evar to nil, because evars are just key-value pairs where both key and value are strings --omitting one or the other or both makes no sense.

The choice to handle nil specially is because of the addition of evar.toboolean(); nil should be recognized as false; why not handle nil in assignment as well?

To avoid this issue, don't associate the idea that an evar can hold nil.

4.3 - evarutils module

The evarutil module provides functions to handle evars. You can use this module to save evars to a file.


evarutils.savevar(filename, varname, value [, comment])

Sets all occurances of varname in file filename to value and appends the specified comment if it is provided.


evarutils.getvalue(filename, varname)

Reads file filename and tries to find varname. Returns the var's value or nil


evarutils.deletevar(filename,varname)

Removes all occurances of varname from file filename.


evarutils.config

evarutils.config provides simple shortcuts for reading and writing config files. You can handle your Theme's settings in a very simple way with this.

The syntax for working with the config object is
evarutils.config.[<relative folders>.*]<filename>.<evarname> = <value>

The file path is constructed from evar.ThemeDir .. <relative folders> .. <filename> .. ".rc".

Example:

First, create a config object for the file you want to save your theme's settings to:
cfg = evarutils.config.Settings.themevars

You can get the full path by calling tostring on the object:
path = tostring( cfg ) ---> path = "C:\Litestep\Themes\Mytheme\Settings\themevars.rc"

Now, If you want to save a evar, simply index the config object:
cfg.MySettingOne = "'Hello file!'" ---> Writes "MySettingOne 'Hello file!'" to xx\Settings\themevars.rc.

To read a setting from the file use
setting = cfg:get('Setting') ---> Looks for a line like "Setting value" in the file and returns value

Delete a settings from the file:
cfg:delete('Setting') ---> Removes Setting from the file

Toggle a boolean evar in the file:
cfg:toggle('Setting') ---> interprets Setting as a boolean and inverts it.


4.4 - textedit module

This is designed to emulate the textedit.dll/xTextedit.dll modules in Lua. It can search through files, replace lines, insert lines, delete lines, etc. The main difference is that this module uses Lua pattern matching instead of standard regexes like textedit.dll does.

If you just want to read/write evars in a file, use the evarutils module instead!


textedit.append (filename, str)

Adds str as a line at the end of the file

filename.

textedit.insert (filename, pattern, str, mode)

Searches through the file filename for lines matching pattern, and inserts str there. 'There' is determined by part of the string mode. The mode string can be any combination of the letters g, i, b, and G. g means 'global', and means that the function will be applied to every matching line in the file. i means case insensitive, which should be self-explanitory. b means before, and only applies to insert. If b is given, str will be inserted before the matching line, otherwise it will be inserted after. G means line global, and only applies for textedit.replace(). It means that textedit.replace() should replace each match on a given line, instead of the first.

Returns the number of insertions made.


textedit.delete (filename, pattern, mode)

Deletes lines in filename that match pattern, following the mode rules from textedit.insert(). Returns the number of deletions made.


textedit.replace (filename, pattern, replace_pattern, mode)

Searches the file filename for lines that match pattern and replaces pattern with replace_pattern on those lines. Captures and stuff like that from string.gsub() are all valid here. Returns the number of times it replaced pattern.


textedit.find (filename, pattern, mode)

Searches filename for pattern, and returns either the number of the first line that matches, or a table containing all lines that match if the mode string contains g.


textedit.delete_line (filename, line_num)

Deletes line number line_num from the file filename. It returns true if the line was successfully deleted and false otherwise.


textedit.insert_line (filename, str, line_num, before)

Inserts str into the file filename either before line_num if before is true, or after line_num if before is false. Returns true if the insertion was successful and false otherwise.


textedit.get_line (filename, line_num)

Returns a string containing the contents of line line_num in file filename.

4.5 - lsmodule module

This module can abstracts Litestep modules as Lua objects, bringing all the greatness of OOP to scripting in litestep. It currently supports Andymon's xModules, timer-0.5 and rabidvwm, but it can be extended easily.
Basically this means that evars are mapped to class attributes and bangs are mapped to class methods, providing simple and direct access to them, without having to construct bangs manually from strings and running them with lslua.exec. The concrete wappers for modules are construced by a maze of clever metatables on access and are cached. Don't worry, there are examples below :-)

Basics

A wrapper class for a supported module can be created like this:

myObject = lsmodule.<module name>.<module instance name>

You always have to give a instance name, even for modules that can only exists once ( like xtray ).

traymodule= lsmodule.xtray --represents the xtray module ( which you can :unload() for example)
mytray= lsmodule.xtray.xtray --represents the xtray ( which you can :move() for example)

Now any of the wrapped module's bangs can be accessed with simple names ( "<module name><bang>" becomes "<bang>" ). If the called method has side effects (i.e. "!Move" changes the x and y position) all changed attributes are set accordingly.

myObject:<simple bang name>( <bang parameter1> , < more bangparameters>, .. )

Evars are also accessible directly through the wrapper with simple names ( "<module instance><evar>" becomes "<evar>" ).

value = myObject.<simple evar name>

"Current" Evars are used first, so myxLabel.x tries to return $labelCurrentX$ before looking for $labelx$

Module API

For a complete list of all implemented bangs see this list or open lsmodule.lua.dll in a text editor.
Almost all xModule methods have the same name and argument order as their bang equivalent, so often your first guess will work.

Examples

Now that the modules are wrapped in lua objects it is easy ( and short!) to do pretty much anything with them.
Here are some examples:
hello_litestep.lua is a small, pointless script which demonstrates manipulating labels, using timers and callbacks.
memory.lua is a "Memory" game using xlabels and lua logic ( and it's kinda fun too ;-).

Advanced

Helpers

lsmodule.getname()

returns a "random" name ( for dynamic labels etc )

myNewObject = lsmodule.xlabel[lsmodule.getname()]
myNewObject.addtogroup = "some_basic_settings"
myNewObject:create()

lsmodule.callback(function, *args)

creates callbacks to use in Litestep (for events and the like) from lua functions

local mylabel = lsmodule.xlabel.mylabel -- this shall recieve a callback
local hellofunc = function( x ) lsmodule.message_box( "Hello from the callback\nThe event was: " .. tostring(x)" end
mylabel.onenter = lsmodule.callback( hellofunc , "onenter")
mylabel.onleave = lsmodule.callback( hellofunc , "onleave")
mylabel.onclick = lsmodule.callback( mylabel.moveby , mylabel, 10, 0) -- member functions ( normally called with a ':' ) need themself as the first argument

lsmodule.unserialize( string )

returns a object from a string. Instances have the reverse operation, serialize()

lsmodule.<module name>.<module instance name>:serialize() --> "lsmodule.module_name.module_instance" (a string)
lsmodule.unserialize("lsmodule.module_name.module_instance") --> lsmodule.<module name>.<module instance name> (a object)

lsmodule.sortby( attribute ) and lsmodule.revsortby( attribute )

returns a function which can be used to sort a table of lsmodule instances by a partiular attribute

myobjects = { obj1, obj2, obj2 } -- table with all object you want to sort
table.sort( myobjects, lsmodule.sortby('x') ) -- sort by x positon
table.sort( myobjects, lsmodule.revsortby('y') ) -- reverse sort by y positon

Timer module helpers

These methods only work if the litestep module "timer-0.5" or higher is loaded. "timer-1.0" is actually the first version (timer-0.1), don't use it!

lsmodule.after( timeout, function, *args)

returns a timer instance which will run the function after a timeout.
local timer = lsmodule.after("10s", hellofunc, "a timer called!")
timer:start()

lsmodule.every( timeout, function, *args)

returns a timer instance which will run the function after each timeout in a loop
local timer = lsmodule.after("3s", lslua.message_box, "STOP! HAMMERTIME!")
timer:start()

Both lsmodule.every and lsmodule.after return a *new* timer object (as in lsmodule.timer.*) every time they are called. Don't forget the start the timer yourself!

Module class methods

<module>.version

a string with the module's version number for loaded modules, else nil

<module>:load( version )

loads the module using !NetReLoadModule. Raises an error if you try to load a module that has previously been loaded.

<module>:reload()

reloadsa module that was previously loaded.

<module>:unload()

unloads the module using !NetUnLoadModule

Instance class methods

<instance>.varfile

Changes to a instance can be automatically saved to a file if the instance has a varfile attribute with the path to a file:

<module instance name>Varfile "X:/path/to/file" or mylabel.varfile = evar.themedir .. "mylabelvarfile.rc"
mylabel.settext( "save me!") -- Updates 'mylabelvarfile.rc' with SomeLabeltext "Save me!"
taskbar.Alwaysontop = False -- Updates 'mylabelvarfile.rc' with xTaskbarAlwaysonTop false

<instance>:dockto(other, side, offset, steps,tim)

Instances can dock to others ( if they can move )

obj:dockto(other, side, offset, steps,tim) -- other = some object, side = "left" / "right" / top" / "bottom"

<instance>:covers(x,y)

Returns if a instance covers a specific point with

mylabel:covers(100, 200) -- true/false

Extending lsmodule

If you want to use additional modules, all you have to do is create a new bangset. Imagine the iNsAnE module uses strange bangs, so you want nicer commands.
Simply create a new lua file and add something like this and even the insane module behaves just as simple as your typical xlabel. If you add a large part of a modules interface feel free to send me a patch on bitbucket or per mail ( see top of this document).

lsmodule.__bangs.INSANE = { -- lsmodule.__bangs[ module name in upper letters ]
    move = function (t,x,y) t.x = x; t.y=y; lslua.exec( ("!Omgwhatastrangemovecommand %s %s %s"):format(t,x,y) )) end 
}
lsmodules.insane.insane:move(10,20) -- calls `!Omgwhatastrangemovecommand insane 10 20` ( but you dont have to type that anymore )