Hi, all,

News from the libfossil scripting front...

Consider this script output:

Wiki pages:
 AmalgamationBuild @ 2014-03-28 19:56:08.191 by stephan size: 2431 bytes
 building @ 2014-02-26 17:12:27.403 by stephan size: 3996 bytes
 DbFunctions @ 2014-03-18 16:08:14.015 by stephan size: 4238 bytes
 download @ 2014-03-23 10:53:38.255 by stephan size: 1293 bytes
 f-tools @ 2014-05-30 18:32:29.051 by stephan size: 12498 bytes
 FossilApp @ 2014-02-11 14:36:06.290 by stephan size: 1378 bytes
 HackersGuide @ 2014-02-05 19:43:45.187 by stephan size: 2213 bytes
 home @ 2014-03-23 10:50:36.201 by stephan size: 8640 bytes
 scratchpad @ 2014-03-13 16:12:55.012 by stephan size: 314 bytes
 SQLSnippets @ 2014-05-07 18:31:05.070 by stephan size: 8508 bytes
 test-page @ 2013-08-27 20:49:55.964 by stephan size: 1073 bytes
 th1ish @ 2014-07-28 19:48:14.139 by stephan size: 3486 bytes


The interesting thing is not the data, but how it's fetched and generated.
Taking a few lessons from requirejs (http://requirejs.org), the
implementation looks like:


Fossil.require(
    ['fsl/context', 'fsl/wiki/util'],
    function(fsl, wikiUtil){
        print('Wiki pages:');
        wikiUtil.getPageNames().eachIndex(function(name){
                var page = fsl.loadManifest( wikiUtil.getLatestRid(name) );
                print('\t',page.L,
                      '@', Fossil.time.julianToHuman(page.D,true),
                      'by', page.U,
                      'size:', page.W.lengthBytes(), 'bytes'
                     );
            });
    });


Basically what that's doing is loading two "modules", called fsl/context
and fsl/wiki/util. Each module's result is then passed on to the Function
provided by the caller. The module loader caches each script it loads, so
multiple calls to a given module will (unless it is configured otherwise)
return the same instances. That means that multiple calls to
Fossil.require() which use the same modules can share state even though
they are many scopes removed from one another.

In the above case, the fsl/context module provides the code with a shared
Fossil.Context instance, so that all parts of the app can use the same
instance without having to know a global symbol name for it (there isn't
one).

Here's a similar one for a basic timeline view:

Most recent timeline entries:
ci e54de856af @ 2014-08-09 16:52:32 by stephan
latest s2, started work on a require.rs-based app prototype.
ci a5a9733acd @ 2014-08-09 14:32:17 by stephan
more s2.
ci 23f20bb428 @ 2014-08-09 07:12:05 by stephan
latest s2 for "this" changes in non-property func calls.
ci a9d46c815a @ 2014-08-08 23:20:18 by stephan
latest s2.
ci 51413ceef4 @ 2014-08-04 18:13:29 by stephan
latest upstream s2 for (new) do/while loop and updated unit tests.

The code:

Fossil.require(
    ['fsl/timeline/basic', 'ostream'],
    function(timelineList, os){
        os << "Most recent timeline entries:\n";
        timelineList.eachIndex(function(v){
            os << v.type << ' ' << v.uuid.substr(0,10)
                << ' @ ' << Fossil.time.julianToHuman(v.mtime)
                << ' by '
                << (v.euser ||| v.user)
                << '\n\t'
                << (v.ecomment ||| v.comment)
                << '\n';
        })
    });

(@Brad Harder: there goes the leaky db abstraction!)

The 'ostream' module simply overloads the '<<' operator to send its
arguments to the configured output channel, giving the caller a C++-like
output streaming mechanism. It's trivial to add new modules this way, and
require() supports requirejs-like plugins, so it can be used to load
arbitrary things, not just script files:

Fossil.require([
  'dll!cgimod', // load the CGI plugin from a DLL
  'fsl-manifest!trunk', // loads a manifest in Object form
  'fsl-wiki!Pagename', // loads the manifest for the given page
  'fsl-blob!trunk', // loads blob content as a Buffer
  'tmpl-compiled!my_template' // load a TH1-style
text-file-with-embedded-script-code
], function( cgi, trunk, wikiPage, blob, template ){
...
    eval -> template; // we just generated a web page
});


The primary advantages to requirejs' async-like execution model, compared
to linear procedural coding in JS/s2:

- it abstracts away dependency loading. Any given module may require()
other modules if they need them. If loading any module triggers an
exception, the user's callback function is never called. i.e. the client
has to do no error checking/handling for the dependencies.

- it cleans up the namespace, effectively replacing global symbols (with
fixed names) with local symbols (which the caller can name however he
likes). If you want to call your copy of jQuery "abc", then go right ahead
- the change is local to your function.

- it incidentally plays very nicely with garbage collectors (be it JS or
s2), providing lifetime "anchors" via the function call stack. It's more
difficult to predict lifetimes (i.e. peak memory usage) in code which does
all its work in a global scope. This model has memory overhead from the
function calls, but the lifetimes are very predictable and generally
briefer, allowing for more recycling of memory.


At this point it's all experimentation - finding out what modules and
structures are more useful - but i'm very pleased with how it's turning out
so far.

Happy Fossiling!

-- 
----- stephan beal
http://wanderinghorse.net/home/stephan/
http://gplus.to/sgbeal
"Freedom is sloppy. But since tyranny's the only guaranteed byproduct of
those who insist on a perfect world, freedom will have to do." -- Bigby Wolf
_______________________________________________
fossil-users mailing list
fossil-users@lists.fossil-scm.org
http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users

Reply via email to