ESI Coding Standards
ESI coding standards have to be kept without any deviation by any individual and team contributing to the ESI standard. Code which does not adhere to the actual standard will be rejected from entering the ESI GitHub repository. Any suggestions to improve the coding standards are welcome and shall be posted in the repositories’ discussion board Issues.
Important Notice: Nothing specified in this document shall ever be treated as a nice idea, in the opposite, every statement in this document is absolutely binding for every ESI developer and contributor. Code which violates the specification will be tolerated for a short period of time, and banned from the repository in case authors refuse necessary rework.
Version History
Version | Date | Description | |
---|---|---|---|
0.1.1 |
2018-06-05 |
Introduced versioning, max code line length convention added |
|
0.1.0 |
2018-03-03 |
First published |
Code Editor
All ESI code needs to be edited and checked for errors using Microsoft Visual Studio Code, furthermore referenced as VS Code, and finally to be tested on an inmation system. VS Code can be publicly downloaded for various platforms and is free-of-charge. Microsoft updates VS Code frequently with improvements and new features. Make sure you have the latest release. The editor will notify you when a new version is available. Since the in-built inmation code editor in Data Studio doesn’t have as much code inspection as VS Code, this editor should only be used to paste code into the system to test the functionality for robustness.
Visual Studio Code can be downloaded here.
Extensions
VS Code can be more powerful by using extensions. The first two extensions are really important which makes VS Code recognize Lua code and checks it for coding errors. The ESI standard requires the following extensions to be installed:
extension | version | purpose |
---|---|---|
0.0.9 |
Lua language support for Visual Studio Code |
|
1.0.0 |
Static error checker for Lua |
|
0.0.23 |
Visual Studio Code debugger extension for Ravi and Lua 5.3 |
The versions are indicative and may also be newer. After installation make sure every extension is enabled.
Integrity Check
Extension Luacheck makes use of luacheck which is a static analyzer and a linter for Lua. Luacheck detects various issues such as usage of undefined global variables, unused variables and values, accessing uninitialized variables, unreachable code and more.
Before a piece of Lua code may be pasted into an inmation system, it must be checked for static errors. Issues will be shown in the 'Problems' panel. All issues needs to be solved before the code may be pasted into the inmation system. The following screenshots show some chunks of Lua code not qualifying for the transfer into inmation:
Let’s first remove trailing whitespace:
'exampleLib' was declared globally. Solve this by putting 'local' before the variable:
When using the colon ':' notation the first 'invisible' argument is 'self'. Since self is not used, this can be fixed by
changing the colon to a dot and place an underscore as first argument: Note: The function invocation will still be
exampleLib:multiply(10, 20)
Visual Studio Code Settings
in order to keep the source code clean across all contributors, the default tab size of 4 shall be the binding standard. Please check your editor preferences for
"editor.tabSize": 4,
Maximum line length
A code line shall never exceed 120 characters
. Break longer instructions like this:
local suc,res=pcall(
function() return inmation.setvalue(self.path..".StateManagement.StateTable",self.tstate) end
)
and use multi-line strings for larger strings:
local sql=[[--
SELECT [a_column], [and], [another_column]
FROM [this_database].[and_schema].[and_finally_the_tablename]
WHERE [a_column]='unlikely to ever by like that, but it is a sample'
--]]
query(con,sql)
ESI Conventions
There are two types of libraries. The first type is supplying functions and classes of any purpose. The second type
encapsulates complete use cases enabling declarative high level scripts. We call the first type common
ESI libraries
and the second type use case
ESI libraries.
First we introduce conventions valid for both types.
-
All functions satisfying the ESI conventions are to be
CAPITALIZED
, when such a function shall be exposed to outside callers. -
The module must be named "esi-
something
-lib.lua" when it is stored in the repository. Thesomething
part shall describe the purpose of the library clear and crisp. -
Each library has to start with a
INFO
function, which returns a set of information in a Lua table. The minimal content should be:
function lib.INFO(_)
return {
version = {
major = 0,
minor = 1,
revision = 1
},
contacts = {
{
name = "Timo Klingenmeier",
email = "timo.klingenmeier@inmation.com"
},
{
name = "Marc van de Langenberg",
email = "marc.vandelangenberg@inmation.com"
}
},
library = {
-- Filename is always modulename plus "-lib.lua" and the modulename must be used for the ScriptLibrary.LuaModuleName property.
modulename = "esi-example"
}
}
end
Please note that the INFO
function above already satisfies another ESI Convention (all public functions have to be
capitalized and called with a colon), such as
local i=LIB:INFO()
The invisible self
parameter which is handed over by such a call is not accessed inside the INFO
function body, and
as such it is marked with underscore in the function declaration.
The version numbers
major
should only be set to one '1' in case the library development has been finished and the library is in productive
use (not in testing). It should be increased above '1', in case a library has undergone major extensions or rework and
is in production again.
minor
should be increased with every code change, which brings new features / functions / classes and the like.
revision
needs to be changed with every commit,e.g. after bug fixing.
Breaking changes
Breaking changes may never be introduced without changing the major
version number part.
The module name
The ESI steering team has decided to "walk away" from any dotted, capitalized notation for ESI libraries (and any
inmation customization libraries in general). Instead, the notation shall follow the one which is used for the files in
this GIT repository, the dash-separated all lower case naming convention. Each ESI library has to start with esi-
respectively.
The module name mentioned in the table field modulename
must be used when this library is loaded into a production
system.
If the author has specified 'esi-kpi-oee' to be the modulename
, it must appear like this in inmation:
…and this modulename
is also the only "name version" of this library which shall ever be required:
local ESIOEE = require('esi-kpi-oee')
Commenting
All comments shall be written in English language. All script authors are encouraged to make use of code comments whenever a certain program logic is not completely obvious.
Keywords in comments
ESI defines three keywords for commenting. The keyword must be the first word in the comment, followed by a colon, e.g.
-- note: The following logic needs to be checked for a nil value in variable x
keyword | meaning |
---|---|
bug |
Someone has identified a bug which needs to be fixed |
note |
An important statement about the code below |
todo |
The author is aware of a required improvement but could not yet implement it |
The first code line
of each ESI library shall always be the standard modulename (followed by an optional "Library") as a comment only. This will allow DataStudio users to visually confirm the match between the module name foreseen by the author and the module name which was given by the system administrator when assigned to an inmation Script Library property compound. See the screen shot for the modulename sample above.
-- esi-kpi-oee Library
or
-- esi-kpi-oee
The Library Scope
inmation Lua libraries require a scope which needs to be returned at the end of the library. The library scope must always be
local
Important Notice: It was recently decided to steer away from any specific names for the library scope, but to use just
lib
always. An example for a valid ESI library scope is shown below:
-- esi-barebone
local lib={}
-- note: the required INFO function has been omitted for readability
function lib:UPPER(x)
return tostring(x):upper()
end
return lib
Calling Conventions
In the above example two things are notable, which are fixed requirements for ESI compatibility:
-
All ESI functions have to be implemented to be called using the colon
lib:FUNC()
notation. Whether the implicit self is required in the function body or not is not important, it is a convention. -
If the function is to be exposed by the library, its name has to be capitalized, if it is an internal (helper) function, it must be lowercase with a leading underscore
-
In cases where the intrinsic
self
parameter is not used, we need to do a classic dot-based declaration in order to not raise linter problems
-- this would not please the linter, because the invisible self parameter is not used in the function body
function lib:_makesqldate(posix)
assert('number'==type(posix))
return inmation.gettime(posix):sub(1,19):gsub("T","")
end
-- this is the 'work-around' to please the linter
function lib._makesqldate(_,posix)
assert('number'==type(posix))
return inmation.gettime(posix):sub(1,19):gsub("T","")
end
Stateful Libraries
Stateful libraries manage their own variables over the uninterrupted runtime being invoked by an inmation script object
(such as an ActionItem
or a periodic GenericItem
with the Lua generation option set). The library can declare any
variables:
lib.calls=0
lib.data={}
and by the general access to self using the colon declaration and calling convention each function in the library can access it easily:
function lib:_somefunction()
self.calls=self.calls+1
table.insert(self.data,{some="Some",other="Other"}
end
Error Handling
Error handling is different for common
and declarative
ESI libraries. The declarative ESI requirements for error
handling will be described in this
document . This is currently
work-in-progress.
For common
ESI libraries, the following rules apply:
-
The usage of
error
andassert
need to be controlled, as they will break the execution in an inmation environment, which is very unlikely to be wanted in a production environment -
Instead such functions should be wrapped so that the behavior can be changed in a single location. See the code example below.
local lib={}
lib.debug=true
function lib:_error(...)
if self.debug then
error(...)
else
-- other error handling in production
end
end
function lib:_assert(...)
if self.debug then
assert(...)
else
-- other error handling in production
end
end
-- in the rest of the library body, the wrappers are to be used *exclusively*
function lib:CALL(x)
self:_assert("number"==type(x))
self:_assert(0~=x)
self:_error("Try the error")
end
return lib
Documentation
Each library has to be documented using Markdown. Currently, there is no standard to automate the generation of the Markdown file for a particular ESI library, but this is very likely to come in near future.
Update 2018-06-05 Timo Klingenmeier: Alexandr Sapunji is currently working on a VSC Extension ESI-to-Markdown
Public Interface
By the ESI specification, the public interface of a library are all functions with CAPITALIZED
names. The author of a
particular library has to supply a call documentation (preferably with examples for functions with flexible arguments)
for each of the functions exposed in the public interface.
Non-public functions
Each library may contain any number of internal helper functions, which are not supposed to be used directly outside of the library, neither be documented in the .md file associated with the libary. By convention, each private function must begin with an underscore and have a name which is exclusively lower case characters.
-- it is a good idea to have a brief description in the source code
-- because this function is not going to be documented in the markdown
function lib:_my_helper(arg)
-- ... do whatever
end