|
||||||||||||||||||||
Page Content
Reference:Reference: | |
This is the official and complete guide to the SLiM module specification and implementation. It is meant for people who wish to understand all the tidbits about SLiM. This is not a tutorial. The best way to dive into slim is to look at the quick-start first and then browse any additional information you need at a glance. Do use the examples provided in this page.
note!
In the following document, I use the terms "module" "library module" "library" "slim module" "a slim" and unless the context explicitely points to another meaning, all the above terms should be considered one and the same thing.
** if you have ANY questions, do not hesitate to speak out on REBOL 3 altme world. **
1: SETTING-UP SLIM | |
1.1. Loading slim.r | |
Setting-up slim is very easy, you simply need to execute ('DO) the %slim.r file anytime before an application will be using it.
We usually set this up in the user.r file. This really is easy, you only need to add the following line for slim to be functional for any application:
do %/path/to/slim.r
Note that it is possible to add this line at the begining of the application itself, if you do not want to edit your user.r file.
1.2. Where is your user.r file? | |
That is a much more complex question to answer. throught the years, on different version of rebol and on different OSes, the user.r file has shifted a lot. In theory, when using REBOL/view 1.3.2 it is located in your home directory. If you are using core 2.6.2, then its in the same directory as your rebol executable. But, depending on if you are using rebol installed or not and how REBOL itself was lauched, you might get surprises as to where the user.r file was really setup. Here are a few guidelines I have discovered with time:
I was scanning the system/options object and realized that on start up there are four possible paths which you can collect which could hold a user.r file:
system/options/home --- the value of your home system variable (if set).
system/options/root --- the path to the rebol.exe which launched the script.
system/options/script --- the path to the actual current script.r FILE.
system/options/path: --- Seems to holds the what-dir rebol value. It can be forcefully set in the windows icon by specifying the "start in" parameter or points to the script's path otherwise. but note that in any event this is a directory path not a file path, so its always different from system/options/script nonetheless. I guess in any unix shell, this is the actual directory you where in when you started the script.
Depending on the way you are setup and the way the icon was built-up (by windows at install, by copying a shortcut, or by creating it by hand) any of these paths could contain a valid user.r file.
Perhaps the worst part about all of this is that you can have multiple user.r files hanging around on your system and you don't even realize it. So even if you really do add slim to your setup, it might be that in another instance, slim is not setup. In any such case, try to figure which of the above stated conditions has happened and try to get to the correct user.r file.
In any case, it is a good idea to use a disk search tool in order to find all the user.r files on your system and edit all of them... you might realize that you have more of them than you think, on your system.
1.3. All about the slim/paths setup. | |
You have to tell slim where there are modules installed. That is where we get the notion of library from. Slim is there to index and remember where you can get access to external sources of code and data. Usually, you will store all your libraries in one directory. This allows it to be easy to manage the tools you have installed. If any of the tool vendors you use updates a library module, then you just need to replace the file in your library directory.
This is an example of what should be in your user.r, (remember to change the paths to those you wish to use)
do %/path/to/slim.r slim/paths: [%/d/libs/]
1.3.1 Multiple dirs
It is possible that you wish to keep older versions of modules, or keep release and beta versions of your own tools separate. You might also wish to keep some libraries relative to the applications that use them. That is possible by adding an indefinite number of paths in the slim-paths word block. They will be searched in order they occur in the block, so you should put the most used versions in the first paths and so on.
Once the slim/paths value is set, any application wishing to use slims can do so, without knowing where those files are. You must understand that even modules can open modules of their own, that is why the search path must be set, otherwise they would not load. The author of a module cannot guess where a user will want to store his files.
This is an example of a setup with more than one path:
do %/path/to/slim.r slim/paths: [%/usr/lib/rebol-slims/ %~/slim-libs/ %/usr/local/bin/application_whatever/libs/]
remember that in rebol, paths must explicitely end with the trailing "/", otherwise it will be considered to be a file.
1.3.2 Automatic paths
In addition to the paths you explicitely identify in your setup, slim automatically tries to find a file called slim-paths.r next to your slim.r installation, allowing you to setup the paths outside of your user.r and/or for all users of a shared system.
Slim also adds your current script path and a subfolder called ./libs/ in its search path, so you can actually use slim as a package, and place your libs next to your application, and the user does not have to know about slim. But you wont have to change your application code. Even if your shared code is centralised amongst many projects.
2: USING SLIMS | |
2.1. Installation of a slim module!? | |
Actually, there aren't any special steps to the installation of a slim module, a part from copying it (and its resource directory, if any) in any of the directories specified in your slim/paths.
The only thing of note is that you should not change the name of the file. This won't keep the module from functioning, but it will force you to change the name you use to access it to the new file's name.
2.2. The resource directory | |
The resource directory is simply a resource repository which is used by a specific library module. It can load files from it no matter where you have installed your libs without needing to search for it. The only rule is that this resource directory with its content, must be in the same directory as the library module it comes with. Its name must also match that of the library with a trailling "_rsrc".
So if module %myLib.r has a resource directory, then it has to be called %myLib_rsrc/.
2.3. Opening the Module!? | |
To open any installed module, simply use slim/open() method.
It expects a library name (in word or string format) and a version number. You can also use none as the version to indicate that you will accept any version (more precisely, the first version) slim may find in any of the slim/paths.
Here is an example of using a library module named myLib:
rebol [] myLib: slim/open 'myLib 1.0
An interesting thing to note is that we assigned the module to an arbitrary word. This is actualy slim's first namespace feature. You are not forced to use a predetermined name to access the content of a module. So if you encounter a library that is called 'superThing and you already have a word defined as superThing, you are not forced to edit your code or the module itself.
SLiM caches all libraries iternally, so that if you or a module ever needs to open a module again, it won't go through the effort of allocating a new one... open() will actually return THE SAME object. This is a feature which allows a module to share private data for all uses of itself, without crowding/conflicting with the global namespace with words which might already exist.
To prevent from using a cached version of a library, (if you simply want to reload the module without restarting your application, for example!) you can use the /new refinement to slim/open(), which will allocate a new instance of the library, reloading the file on disk.
2.4. Using a module | |
To use the module, once it is opened, you have nothing special to do. The word you assigned to the library is a simple object, which you use with standard object/member notation.
example of using one of the librarie's functions directly. (in Red)
rebol [] myLib: slim/open 'myLib 1.0 print myLib/someFunction
2.5. Exposing words | |
SLiM allows you to expose some or all of the words included within the module into the global namespace. This is practical for many uses of modules most of which are patch modules or rebol functionality extensions.
Note that the word is still bound to its module, but it is usable directly within the global namespace.
One nice feature is that it will add a prefix to all globally bound words exposed from your lib, by default. More details on exploiting the prefix feature to your needs is described in more detail later...
There are various ways to specify what words to expose, but all use the slim/expose method. It expects a pointer to a library module as returned by slim/open(), and an extra parameter which can be a word or a block, which specifies what words to expose.
2.5.1 You can expose words in the following various ways:
Here are a few notes you should consider or understand with regards to exposing parts of a module:
Examples of exposing words:
rebol [] myLib: slim/open 'coolModule 1.0 slim/expose myLib 'foobar ; exposes word 'foobar slim/expose myLib [foobar coolFunc] ; exposes words 'foobar and 'coolFunc slim/expose myLib none ; exposes all words in myLib ; The following exposes words 'coolFunc and 'foobar (as 'fubar) slim/expose myLib [coolFunc [fubar: foobar]] ; the following are now all valid global expressions value: foobar value: fubar ; (same as foobar) value: coolFunc myLib/anotherCoolThing ; using module with standard object notation (recommended) anotherCoolThing ; its also available since we use an expose with none, so all members where exposed
2.5.2 Prefix options
SLiM by default, will add prefixes to any exposed words. This is yet another namespace protection feature.
This allows you to keep a sense of toolset and is a common practice in many programming environments. Here again, you have several options on how you can manage prefixes. This is done by using the prefix refinement when doing your slim/expose call. In the example above, no prefixes where added because that module would have disable automatic prefix in its header.
If the modules header contains the lib-prefix: specification (it is optional), then its value, will be used by default on expose calls.
If it does not exist then the library name will be used.
IF you really want to control what the prefix will within your application, then just add the /prefix refinement and give it any word or string value and that is what will be used instead.
Using none as the prefix value, will effectively cancel the prefix notation.
Here is an example of using the /prefix refinement:
rebol [] myLib: slim/open 'coolModule 1.0 slim/expose/prefix myLib [foobar coolFunc] 'cm ;you can then use exposed words like so: cm-foobar cm-coolFunc ; OR do: slim/expose/prefix myLib [foobar coolFunc] none ;you can then use exposed words like so: foobar coolFunc
2.5.3 Renaming exposed words
When words are exposed, you have the option of renaming them so that they suit your needs. The words in the loaded module stay as is.
This allows you to protect your namespace with the most control. The renaming is applied BEFORE the prefix handling is done, so the words you rename are still subject to prefix handling rules.
When you specify your expose with a rename block at the end (this is shown earlier), every pair of set-word: word values will attempt to rename one word of the module to another of your choosing. The word which will be set in the global namespace is the first one (supplied as a word of type set-word!) this can be any word. The second word in the pair is a name which should be found in module's specification. Any invalid names are simply ignored if possible.
The first expose example code clearly shows an example of exposing the foobar module function to an equivalently named fubar in the global namespace.
Note that if any word is in the expose block and also in the rename block, then it will only be exposed once using the renamed specification.
here is the renamed expose again:
; The following exposes words 'coolFunc and 'foobar (as 'fubar) slim/expose myLib [coolFunc [fubar: foobar]]
2.5.4 Additional Detail(s)
You can call the expose function as many times as you wish with any refinement combination.
3: CREATING SLIMS | |
3.1. A note about backwards compatibility | |
Before you know anything about creating a slim module, there is one point which must be addressed, regardless of what tools you write.
With SLiM Its easy to share your efforts and its easy to use them in a customized manner. But all of this is worthless if you do not stick to your original code and design.
Slim and ALL users of a slim module expects a module's api (application programming interface) to be set in stone.
This is something I cannot stress enough! When anyone uses a tool and puts effort into learning it, its strenghts, its style and its weeknesses, then they expect their time and effort to be worth the time that this tool should save them.
If the creator of a tool constantly changes the way in which he has coded his tool, changes words or their meaning, replaces words by new theoretically better ones, then he is going to loose his user base REAL FAST.
PLEEEAASE, when you program shared tools, take in consideration the fact that there are alternatives, and that your users have put trust into your work, but they will get pissed if all that effort has to be redone over and over. Eventually they might learn another inferior tool which is more stable... In the field of IT... stability is often much more rewarding than all-out features.
If you know in advance that your tools will change or that they are still in design stage, make sure you let your potential users know. In any case, they will expect the changes and code in consideration of this possibility.
Try to stay backwards compatible in every new version of your tool. Create patches to old functions so that they call the new better ones, but DON't remove the old ones, that makes all the current applications which use your tool DEAD AS A BRICK (and users don't quites like that ;-)
On behalf of all users, thanks for keeping this in mind (and this applies to any tool, using slim or not).
3.2. SLiM library file format and mechanism | |
The SLiM file modules themselves are incredibly simple, they consist of a rebol header and, code which is evaluated. The standard procedure is to call slim/register on a block of code which will then become an object, passed back to the calling object. You can put more code, but that is not specifically supported, although it will work.
Here is an example slim module:
rebol [
; -- basic rebol header --
version: 0.0.6
date: 2003-10-06
title: "bogus.r - slim module example"
author: "Maxim Olivier-Adlhoch"
purpose: "To test the lib mechanism thru a simple lib."
copyright: "Copyright (c) 2002,2003 Maxim Olivier-Adlhoch"
; -- SLiM stuff --
slim-requires: [package [core 2.3 view+ 1.2.1 ] SLiMs [ steel-console 0.0.5 ]]
slim-name: 'bogus
slim-id: 0
slim-prefix: 'bog
slim-version: 0.9
]
slim/register [
image-data: none
--init--: does [
slim/vprint ["AVAILABLE RESOURCES FOR BOGUS MODULE:" read/resource %./]
image-data: load/resource %polished-bogus.png
; we must indicate that all is well in --init--, otherwise slim/open will fail.
slim/vprint "BOGUS INITIALIZED"
return true
]
show-img: does [
view layout [
image image-data
button "close" [unview]
]
]
]
3.3. The rebol header | |
The rebol header used in a slim file is treated just as any standard rebol header. If you include a needs: argument in the header, then any standard version matching will be dealt, possibly causing a file load interruption.
In addition to the standard fields there are a few new fields related specifically to slim:
Also note that the header remains available to your library and any scrutinizing application. To see the library's header, just use the lib/header member. This is added dynamically by slim within your loaded module, at run-time. Note that for exposing purposes, this member is considered a reserved word, so cannot be exported using integrated slim tools.
3.4. Registering your module | |
As you can see, the module itself is responsible for calling the slim/register function on itself. Odd you might think, but I assure you there is a very good reason for this, trust me.
The process of registering a module implies that it will go through several computations before actually becoming a usable module. There will be new members added to the module object, and a process known as validation will be done before slim accepts the module as valid.
slim/Register is also responsible for calling the --init-- method of your library (if any, explained below).
3.5. The slim-requires dialect | |
In the header, as explained above, you can add an attribute called slim-requires.
This attribute really makes your job easy for checking if your library is in the right environment for execution.
The dialect is currently quite basic, but it allows some extensions to the rebol header needs: word. This includes checking if rebol is of sufficient version, is EXACTLY a version or if its too recent to properly execute the library. You can currently check for core and view versions.
Matching packages and versions:
slim-requires: [package [core= 2.3 view 1.2.1 ] ]
This will match core 2.3 exactly (2.5.6 would not match) or any view version 1.2.1 or higher.
slim-requires: [package [view- 1.2.1 ] ]
This will match any view version 1.2.1 or lower.
slim-requires: [package [core_ 1.2.1 ] ]
This would match any view version lower than 1.2.1
As you have noticed, the last character of a package word or string dictates how the version is used and the word itself is used to specify what package the version applies to.
Here is a list of characters which dictate how the version will be interpreted:
So if you do not specify any special character as the last for a package, then the version will match any version which is equal or greater than what is specified, which is thus the default.
The main thing to remember is that THE FIRST PACKAGE which succeeds in a match will return true.
Extending the 'requires dialect!
With user request, I am open to adding many types of validation techniques to the requires dialect. The most prominent ideas being those of operating system, user licenses or specific component checks (like the rebol [needs:] dialect).
3.6. lib/--init--() | |
The --init-- function of a slim module is simply a practical tool which allows you to execute code just after the library is loaded.
You can include anything in an --init-- function but you must remember that you only really have access to the slim tool and your module.
The --init-- function is the perfect place for you to load a module which this slim needs. Because module opening is cached, you do not need to worry about endless looping when loading, because the second time any module is opened, it really only returns the previously loaded library.
This being said, you must be carefull if your module really needs to open a module with the /new refinement, because in that case, a new module will be loaded and its --init-- function WILL also be run, so if its opening your module with the /new refinement as, well, then you've just created a deadlock... some consideration in design is then in order to prevent this. BUT THIS IS AN EXCEPTIONAL CIRCUMSTANCE so you should not shy away from slim simply for this. That is such an easy problem to circumvent.
3.6.1 --init-- is part of the validation...
You must be carefull when returning from an --init-- method, for if it returns anything else than true, then your library will not load and an error might be thrown. This allows you to use the --init-- method as a custom validator as well as a data initialiser... isn't that sweet?
3.6.2 calling slim/expose from within --init--
You can call the slim/expose method from within an init, as it is called after the library is loaded and the object created, BUT you must be carefull to call it ONLY if the validation (if any) proves to be successfull. In other words, you MUST only call expose if the --init-- function returns true. Otherwise, you are creating a situation where the library is not really loaded, but yet some of its members exist in the global space.
This won't make an application crash per se, but can cause to instability if loading of a module is not mandatory to an application and if the user of the module, checks for existing words directly. In that case, calling one of those exposed words can be disastrous because the library will not have initialized properly and the user of the library cannot expect the reactions your library may take in that circumstance. Just be clean and then the user will not have any problems. REMEMBER that it is your duty to make run-time linkable libraries bullet-proof, the user is not expect to error-check anything if a call to slim/open returns successfully or not. The fact that slim/open returns none IS his indication that something is right or wrong.
3.7. lib/--private-- | |
The --private-- member of a slim module is quite handy. It allows you to place a list of words which your module considers private. So any expose attempts on these words will be ignored.
Note that you can specify --private-- as a single word or as a block of one or more words.
--private--: 'image-data
OR
--private--: [image-data imgload imgsave]
This is an optional feature and if the --private-- word is not defined in your module object, then all non-reserved words are considered public.
3.8. The resource directory handling | |
3.8.1 What is the resource directory!?
The resource directory is a unique feature to slim which allows a slim module to use a path local to itself anytime it needs access to an external file, even after the code has been loaded into ram and the main application has resumed operation, in which case the current-dir does not print to any library code.
Note that this is valid for input from files as well as to output to them.
3.8.2 Why?
This allows you to store data for your library next to your library. You can access this resource as if it where the current-directory while execution of your code occurs, even though the current directory of your application really is set to something completely different.
To enable this, I have encompased a version of the four main file I/O functions to remember the path of the library when it was loaded.
Diligent use by you, the module implementor, makes the resource directory a kind of sandbox for the execution of you module, instead of trying to store or load data directory information in a user.r or in your code, or in a system setup... you don't take care of it and you know that you cannot adversly affect the system of someone using your module.
Users knowing that you are using a resource directory can also feel safer about your file handling, since they will realize that you are not tampering randomly on disk, but rather within a well controled environment, which they setup (they set the library's position on their system, not you).
3.8.3 Additional and Official sandbox measures...
It is well possible that later releases of slim will alert the user of a slim module if it attempts to access data outside the resource directory.
This would further improve the user's confidence in modules. This setup would be module specific as it is obvious that some modules will be meant to perform I/O as their base functionality.
3.8.4 How do we use this fantastic feature? ;-)
On any file I/O, whether it is READ LOAD WRITE or SAVE, you can simply use the new /resource refinement. All other refinements continue to work as expected and remain unchanded due to the encompass function's non-transgressive nature.
; to use the resource simply use the /resource refinement! ; any of the following are an example of a valid resource directory input data: load/resource %datafile.db data: read/binary/resource %archive.zip ; list files in resource dir probe read/resource %./ ; any of the following are an example of a valid resource directory output save/png/resource %bg-img.png to-image layout [ origin 0x0 image polished.png effect [gradcol 1x1 255.0.0 100.100.255] ]
In the bogus slim module example, you have an example of an image load which uses the /resource refinement. You must also understand that if you have multiple versions of a slim module on disk, that /resource really points to the content relative to the slim which actually was loaded, which exploits one of its main features.
3.8.5 How do I create this resource directory!?
You simply create a directory which has the same name as the library, without .r extension and an extra %_rsrc appended to it.
So, for a library that is named %bogus.r, its resource directory would be named %bogus_rsrc.
3.8.6 is the resource directory mandatory???
NO!, it is simply a handy tool to help you with extra data which shouldn't or can't be included in the code but which must absolutely stay linked to your module.
IF YOU ARE USING a resource directory, be sure to mention it in the docs of your module file and be sure to include it in any distribution. I strongly suggest packing both the slim and its resource directory in an archive file, so that they always get installed together and at the same time.
This usually takes the form of a .zip file on windows or a .tar file on unix, for example. Be sure to make your archives as easy to open as possible for all platforms. The previous two archive formats have extractors on pretty much ALL operating systems, so both are a good and open archiving format.
3.8.7 WHAT can I put in the resource directory!?
Anything, SLiM just handles the access to it, but you can fill it up with whatever you wish. A common file type is images, database are another, especially when they must be saved between sessions, the use of a resource directory, ensures that any data used by a version of a library do not get mumbled up by the useage of another library.
Don't forget that you can also create sub directories, the resource directory really is like a root for your library files.
The resource directory can also be a good way to separate the usage of a tool between multiple users, when each user has a separate library setup on the same server or workstation.
3.9. slim/findpath() | |
The SLiM.r tool also has a method called findpath, which will return a path to a filename you specify, IF that filename exists within the slim/paths block.
You could, for example, find the path to the %polished.png image like so:
img: slim/findpath %polished.png
This would return an absolute file path to a file named polished.png.
The filename can include a path, but must be relative, otherwise an invalid path might be generated.
The findpath method usually is used internally by slim to find libraries, but its use is legal if you wish to find existence of files in the complete repository.
An example of a findpath usage, might be in the creation of shared data packages, which all libraries may use. This is forward looking, but having a set of images used as standard images in a variety of tools, might be a very usefull feature in the future, so the fact that you are aware of findpath, might help you in seeing its use in a specific context.
There is a possibility that, in a future version, a database of standard steel resources be included in a directory of its own. This would store data shared amongst all steel tools or be part of a package of files which is supposed to be usable by anyone using steel. Things like locales or standard images would be prime candidates for such a collection of data.
page last updated: 17-Oct-2006