Skip to content

Commit

Permalink
Cleaned up caching. Added namespace tagging and caching. Added implic…
Browse files Browse the repository at this point in the history
…it calling (via onMissingMethod). Added installation to a scope from a configuration. Updated main example to show three usage patterns. Added advanced / automated example using Application.cfc to automatically install Clojure and namespaces based on configuration.
  • Loading branch information
seancorfield committed Sep 12, 2010
1 parent 2701e7c commit 5603015
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 48 deletions.
3 changes: 3 additions & 0 deletions Application.cfc
@@ -0,0 +1,3 @@
component {
this.name = hash( getBaseTemplatePath() );
}
22 changes: 22 additions & 0 deletions advanced/Application.cfc
@@ -0,0 +1,22 @@
component {
this.name = hash( getBaseTemplatePath() );
// cfmljure configuration:
config = {
project = 'cfml',
files = 'cfml/examples',
ns = 'cfml.examples, clojure.core'
};

// magic that auto-installs Clojure stuff into variables scope:
function onRequestStart() {
if ( !structKeyExists( application, 'clj') ||
( structKeyExists( URL, 'reload' ) && isBoolean( URL.reload ) && URL.reload ) ) {
application.clj = new cfmljure();
}
application.clj.install( config, variables );
}

function onRequest( string targetPath ) {
include targetPath;
}
}
31 changes: 31 additions & 0 deletions advanced/index.cfm
@@ -0,0 +1,31 @@
<cfset start = getTickCount() />
<cfscript>
writeOutput( '<h1>Calls via implicit method lookup after implicit installation</h1>' );
// call functions:
// simple function call:
writeOutput( '(greet "World") = ' & cfml.examples.greet( 'World' ) & '<br />' );
// pass CFML array to Clojure and loop over Clojure sequence that comes back:
list = cfml.examples.twice( [ 1, 2, 3 ] );
writeOutput( '(twice [ 1 2 3 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
// simple function call (times2 is def'd to an anonymous function literal:
writeOutput( '(times2 42) = ' & cfml.examples.times2( 42 ) & '<br />' );
// call built-in Clojure function, passing raw definition of times2 function:
list = clojure.core.map( cfml.examples.get( 'times2' ).defn, [ 4, 5, 6 ] );
writeOutput( '(map times2 [ 4 5 6 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
</cfscript>
<cfset end = getTickCount() />
<cfoutput>
Time taken: #end - start#ms.<br />
<a href="?reload=true">Reload the runtime</a>.
</cfoutput>
3 changes: 3 additions & 0 deletions basic/Application.cfc
@@ -0,0 +1,3 @@
component {
this.name = hash( getBaseTemplatePath() );
}
113 changes: 113 additions & 0 deletions basic/index.cfm
@@ -0,0 +1,113 @@
<cfset start = getTickCount() />
<cfscript>
// load Clojure runtime (for cfml project - search path root is clj/cfml/src/):
clj = new cfmljure( 'cfml' );
// load scripts (from project source folder - that's clj/cfml/src/cfml/examples.clj):
clj.load( 'cfml/examples' );
// we can either get handles on specific functions like this:
writeOutput( '<h1>Calls via explicit handles on methods</h1>' );
// get handle on individual functions (from namespace cfml.examples):
greet = clj.get( 'cfml.examples.greet' );
twice = clj.get( 'cfml.examples.twice' );
times2 = clj.get( 'cfml.examples.times2' );
// get handle on built-in map function (from namespace clojure.core):
map = clj.get( 'clojure.core.map' );
// call functions:
// simple function call:
writeOutput( '(greet "World") = ' & greet.call( 'World' ) & '<br />' );
// pass CFML array to Clojure and loop over Clojure sequence that comes back:
list = twice.call( [ 1, 2, 3 ] );
writeOutput( '(twice [ 1 2 3 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
// simple function call (times2 is def'd to an anonymous function literal:
writeOutput( '(times2 42) = ' & times2.call( 42 ) & '<br />' );
// call built-in Clojure function, passing raw definition of times2 function:
list = map.call( times2.defn, [ 4, 5, 6 ] );
writeOutput( '(map times2 [ 4 5 6 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
</cfscript>
<cfset end = getTickCount() />
<cfoutput>Time taken: #end - start#ms.<br /></cfoutput>
<cfset start = getTickCount() />
<cfscript>
// or we can get the namespaces and then call methods directly:
// setup my namespaces:
cfml.examples = clj.ns( 'cfml.examples' );
clojure.core = clj.ns( 'clojure.core' );
writeOutput( '<h1>Calls via implicit method lookup (lowercase only, no -)</h1>' );
// call functions:
// simple function call:
writeOutput( '(greet "World") = ' & cfml.examples.greet( 'World' ) & '<br />' );
// pass CFML array to Clojure and loop over Clojure sequence that comes back:
list = cfml.examples.twice( [ 1, 2, 3 ] );
writeOutput( '(twice [ 1 2 3 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
// simple function call (times2 is def'd to an anonymous function literal:
writeOutput( '(times2 42) = ' & cfml.examples.times2( 42 ) & '<br />' );
// call built-in Clojure function, passing raw definition of times2 function:
list = clojure.core.map( cfml.examples.get( 'times2' ).defn, [ 4, 5, 6 ] );
writeOutput( '(map times2 [ 4 5 6 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
</cfscript>
<cfset end = getTickCount() />
<cfoutput>Time taken: #end - start#ms.<br /></cfoutput>
<cfset start = getTickCount() />
<cfscript>
// by configuration and installation:
config = {
project = 'cfml',
files = 'cfml/examples',
ns = 'cfml.examples, clojure.core'
};
target = { }; // normally you'd target a scope - this is just an example
// install the configuration to the target 'scope':
new cfmljure().install( config, target );
writeOutput( '<h1>Calls via implicit method lookup after installation to a target scope on every request</h1>' );
// call functions:
// simple function call:
writeOutput( '(greet "World") = ' & target.cfml.examples.greet( 'World' ) & '<br />' );
// pass CFML array to Clojure and loop over Clojure sequence that comes back:
list = target.cfml.examples.twice( [ 1, 2, 3 ] );
writeOutput( '(twice [ 1 2 3 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
// simple function call (times2 is def'd to an anonymous function literal:
writeOutput( '(times2 42) = ' & target.cfml.examples.times2( 42 ) & '<br />' );
// call built-in Clojure function, passing raw definition of times2 function:
list = target.clojure.core.map( target.cfml.examples.get( 'times2' ).defn, [ 4, 5, 6 ] );
writeOutput( '(map times2 [ 4 5 6 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
</cfscript>
<cfset end = getTickCount() />
<cfoutput>Time taken: #end - start#ms.</cfoutput>
85 changes: 71 additions & 14 deletions cfmljure.cfc
Expand Up @@ -15,38 +15,60 @@
limitations under the License.
*/

variables.clj = { };

public any function init( string project = '' ) {
variables.project = project;
variables.rt = createObject( 'java', 'clojure.lang.RT' );
// constructor
public any function init( string project = '', any rt = 0, string ns = '' ) {
variables._project = project;
variables._ns = ns;
variables._files = '';
variables._refCache = { };
variables._nsCache = { };
if ( isObject( rt ) ) {
variables._rt = rt;
} else {
variables._rt = createObject( 'java', 'clojure.lang.RT' );
}
return this;
}

// load a list of files
public void function load( string fileList ) {
var prefix = variables.project == '' ? '' : variables.project & '/src/';
// clear the reference cache if we load any files
variables._refCache = { };
var prefix = variables._project == '' ? '' : variables._project & '/src/';
var files = listToArray( fileList );
var file = 0; // CFBuilder barfs on for ( var file in files ) so declare it separately!
for ( file in files ) {
variables.rt.loadResourceScript( 'clj/' & prefix & file & '.clj' );
variables._rt.loadResourceScript( 'clj/' & prefix & trim( file ) & '.clj' );
}
}

// get a specific Clojure function
public any function get( string ref ) {
if ( !structKeyExists( variables.clj, ref ) ) {
var fn = listLast( ref , '.' );
var ns = left( ref, len( ref ) - len( fn ) - 1 );
var r = variables.rt.var( ns, fn );
variables.clj[ref] = createObject( 'component', 'cfmljure' ).def( r );
var fqRef = listAppend( variables._ns, ref, '.' );
if ( !structKeyExists( variables._refCache, fqRef ) ) {
var fn = listLast( fqRef , '.' );
var ns = left( fqRef, len( fqRef ) - len( fn ) - 1 );
var r = variables._rt.var( ns, fn );
variables._refCache[ref] = new cfmljure( variables._project, variables._rt, variables._ns ).def( r );
}
return variables.clj[ref];
return variables._refCache[ref];
}


// set up a context for a Clojure namespace
public any function ns( string ref ) {
if ( !structKeyExists( variables._nsCache, ref ) ) {
variables._nsCache[ref] = new cfmljure( variables._project, variables._rt, ref );
}
return variables._nsCache[ref];
}

// tag this instance with a specific Clojure function definition so it can be called
public any function def( any defn ) {
this.defn = defn;
return this;
}

// explicit call method with up to five positional arguments
public any function call() {
switch ( arrayLen( arguments ) ) {
case 0:
Expand All @@ -66,4 +88,39 @@
}
}

// support dynamic calling of any method in the current namespace
public any function onMissingMethod( string missingMethodName, any missingMethodArguments ) {
var ref = get( lCase( missingMethodName ) );
return ref.call( argumentCollection = missingMethodArguments );
}

// install from a configuration into a target
public void function install( struct config, struct target ) {
var project = structKeyExists( config, 'project' ) ? config.project : '';
var fileList = structKeyExists( config, 'files' ) ? config.files : '';
var namespaceList = structKeyExists( config, 'ns' ) ? config.ns : '';
var clj = this;
if ( project != variables._project ) clj = new cfmljure( project, variables._rt, variables._ns );
clj.load( fileList );
target.clj = clj;
var namespaces = listToArray( namespaceList );
var ns = 0; // CFBuilder barfs on for ( var ns in namespaces ) so declare it separately!
for ( ns in namespaces ) {
var _ns = trim( ns );
var pair = _makePath( _ns, target );
pair.s[pair.key] = clj.ns( _ns );
}
}

// helper for installing namespace paths
private struct function _makePath( string path, struct target ) {
var head = listFirst( path, '.' );
if ( listLen( path, '.' ) == 1 ) {
return { s = target, key = head };
} else {
if ( !structKeyExists( target, head ) ) target[head] = { };
return _makePath( listRest( path, '.' ), target[head] );
}
}

}
39 changes: 5 additions & 34 deletions index.cfm
@@ -1,34 +1,5 @@
<cfscript>
// load Clojure runtime (for cfml project - search path root is clj/cfml/src/):
clj = new cfmljure( 'cfml' );
// load scripts (from project source folder - that's clj/cfml/src/cfml/examples.clj):
clj.load( 'cfml/examples' );
// get handle on individual functions (from namespace cfml.examples):
greet = clj.get( 'cfml.examples.greet' );
twice = clj.get( 'cfml.examples.twice' );
times2 = clj.get( 'cfml.examples.times2' );
// get handle on built-in map function (from namespace clojure.core):
map = clj.get( 'clojure.core.map' );
// call functions:
// simple function call:
writeOutput( '(greet "World") = ' & greet.call( 'World' ) & '<br />' );
// pass CFML array to Clojure and loop over Clojure sequence that comes back:
list = twice.call( [ 1, 2, 3 ] );
writeOutput( '(twice [ 1 2 3 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
// simple function call (times2 is def'd to an anonymous function literal:
writeOutput( '(times2 42) = ' & times2.call( 42 ) & '<br />' );
// call built-in Clojure function, passing raw definition of times2 function:
list = map.call( times2.defn, [ 4, 5, 6 ] );
writeOutput( '(map times2 [ 4 5 6 ]) = ' );
for ( n in list ) writeOutput( n & ' ' );
writeOutput( '<br />' );
</cfscript>
<h1>cfmljure examples</h1>
<h2>Basic example</h2>
<p><a href="basic/index.cfm" target="_blank">Three low-level examples of using cfmljure</a></p>
<h2>Advanced example</h2>
<p><a href="advanced/index.cfm" target="_blank">Integrated / automated cfmljure example</a></p>

0 comments on commit 5603015

Please sign in to comment.