/**
*/
assert 'function' === typename api.loadModule
api.cgi || api.loadModule('cgiish')
//api.import.path.suffix.push('.fossi1ish')
const DIR = Fossil.file.dirPart(Fossil.file.canonicalName(__FILE))
api.import('fossil-extend')
api.import(DIR+'extend-cgiish')
//print(Fossil.file.dirPart(Fossil.file.canonicalName(__FILE)))
api.cgi.config.resourceRootDir = DIR
api.cgi.config.showPageSourceLinks = true
api.cgi.config.defaultSkinName = 'default'
/**
Expects func() to be a page generation function. To return
JSON, the function must simply return an Object or Array.
To generate other output, the function must call
api.cgi.setContentType(), use api.io.output() (or its
convenience form print(), or similar) to generate output,
and must return a falsy value.
func() is passed the api.cgi module as its only parameter.
exit()s on success. Propagates an exception on serious
errors but catches errors thrown by func()
*/
api.cgi.runFuncAsPage = proc(func){
const ob = api.ob
const obLevel = ob.level()
var saveSession = false
ob.push()
var rc = catch{
//assert func inherits
assert func inherits api.prototypes.Function
if('function' === typename func.render){
toss func.render(this)
}else{
toss func(this)
}
}
const httpCode = this.httpStatus()
if(rc inherits api.Exception){ /* Renderer threw */
saveSession = false
rc = object{
exception: this.scrubException(rc)
}
this.httpStatus(500, "Caught Exception")
while(ob.level() > obLevel ){
ob.pop()
}
}else if(rc && rc.eachProperty){ /* assume JSON-compatible */
/*rc = object{
body: rc
}*/
}else if( (httpCode>=500) && (httpCode<600) ){ /* Renderer set an HTTP error */
saveSession = false
rc = undefined
while(ob.level() > obLevel ){
ob.pop()
}
print("It's dead, Jim.");
}
if(rc && rc.eachProperty){ // Output JSON...
ob.clear()
this.setContentType('application/json')
this.out("%1$2J".applyFormat(rc))
}
saveSession && this.savePathSession()
this.fsl && this.fsl.finalize()
this.send() /* NOTE: this expects ob.push() to have
been called, and will flush any pending output */
exit 0
}
/**
A helper routine for pages which want to show their source code
if the user provides the showSrc=1 parameter. If that is set,
this function outputs the given file and returns true, otherwise
if showLink is true it outputs a link to the current page with
showSrc=1 set and returns false.
*/
api.cgi.handleShowSource = proc(file, showLink = false){
const ce = argv.callee
(undefined != ce.result) && return ce.result
if(+this.getVar('showSrc',0)){
$this.out {<pre class='code'>} this.htmlEscape(slurpFile((file))) {</pre>}
return ce.result = true
}else{
showLink && $this.out {<div>[<a href='?showSrc=1'>soure code for this page</a>]</div>}
return ce.result = false
}
}
/**
Returns the root CGI path suffixed by appName.
*/
api.cgi.getAppUrlPath = proc(appName){
return this.cgiRoot + appName
}
/**
Returns a shared fossil instance created using
Fossil.createContext(). It is created, and its repo
(possibly) opened on the first call and returned
as-is on subsequent calls.
*/
api.cgi.getFossilInstance = proc(){
const ce = argv.callee
return ce.fsl || (ce.fsl = Fossil.createContext())
}
api.cgi.util = object {
assertHasRepo: proc(){
const F = api.cgi.getFossilInstance()
assert F
assert F.db
assert F.db.repo
return F.db.repo
},
assertHasCheckout: proc(){
const F = api.cgi.getFossilInstance()
assert F
assert F.db
assert F.db.checkout
return F.db.checkout
}
}
/**
Evaluates tmplish()-compiled input text, importing any symbols
defined in obj as local symbols for template resolution purposes.
If the template throws, it ouputs an error description instead.
If the 3rd argument is passed, it is assumed to be the uncompiled
template code and is displayed in error messages. The template (if
the author knows it will be processed by this function) may use
the symbol __TMPLISH_SRC to access that original template source
code.
Returns this object.
*/
api.cgi.runTmplish = proc(tmplCompiled, obj, __TMPLISH_SRC){
assert 'buffer' === typename tmplCompiled
assert self
const out = self.out
const ob = api.ob
proc(){
ob.push()
const err = catch {eval tmplCompiled}
if(err){
ob.clear()
$out {Error evaluating template:<pre class='exception'>} \
self.toJSONString(self.scrubException(err)) {</div>}
$out {<br/>The compiled template:<br/>} {<textarea rows='20' cols='80'>} \
self.htmlEscape(tmplCompiled) {</textarea><br/>}
__TMPLISH_SRC &&
$out {<br/>The original template:<br/>} {<textarea rows='20' cols='80'>} \
self.htmlEscape(__TMPLISH_SRC) {</textarea><br/>}
}
ob.flush().pop()
}.importSymbols(nameof ob, nameof out, nameof __TMPLISH_SRC, obj || object{})()
return self
}.importSymbols(object{self: api.cgi})
/**
Runs tmplish()-compatible input text tmpl through tmplish(),
then calls runTmplish(compiledTemplate,obj,tmpl). Returns this
object.
This function binds its 'this' to api.cgi when it is created, so a
reference to the function may be stored and used independently of
the api.cgi symbol without breaking.
The tmplishOpt (optional) parameter is used passed as the 2nd
parameter to tmplish(). This can be used to configure the
opening/closing tags in the input. The properties supported
tmplish():
object {
valueOpen: string, // opening tag for VALUE blocks
valueClose: string, // closing tag for VALUE blocks
codeOpen: string, // opening tag for CODE blocks
codeClose: string // closing tag for CODE blocks
}
If one of the Open tags is set, its corresponding closing tag must
also be set or it will be ignored.
*/
api.cgi.processTmplish = proc(tmpl, obj, tmplishOpt){
return self.runTmplish(tmplish(tmpl, tmplishOpt), obj, tmpl)
}.importSymbols(nameof tmplish, object{self: api.cgi})
/**
Expects name to be the base name of a script file under
resolveResourcePath('widgets/'). It creates a DIV wrapper
with two classes: fsl-widget and fsl-widget-name (the
literal name passed to this function). In that DIV
the widget is called. The widget gets all arguments
passed to this function, and may expect the 2nd argument
to contain an Object with configuration parameters.
If an exception is thrown in the widget, it is displayed
(scrubbed) in place of the widget's output.
Widget code is imported via api.import(). It gets its
arguments via api.cgi.WIDGET_RENDER_ARGS, an array
containing all arguments passed to this function.
if opt is set then some of its properties are interpreted by
this function, as follows:
.cssClass may be a string of space-separated CSS classes to
apply to the element (in addition to the default ones). It may
also be an array of names, which gets join()'d into a string here.
.domId specifies the optional HTML element ID to apply
to the widget's wrapper element.
*/
api.cgi.renderWidget = proc(name, opt){
const fn = this.resolveResourcePath({widgets/}+name)
$this.out "<div class='fsl-widget fsl-widget-" name
if('object' !== typename opt){
$this.out "'"// class= part
argv.1 = object{} // simplifies widget code if they know they get an object
}else if(opt){
if('string' === typename opt.cssClass){
$this.out " " opt.cssClass;
}
else if('array' === typename opt.cssClass){
$this.out " " opt.cssClass.join(' ')
}
$this.out "' "// class= part
if(opt.domId){
$this.out " id='" opt.domId "'"
}
}
$this.out ">" // main widget opener tag
api.ob.push()
argv.callee.renderArgs = argv.slice() // Copy to a real array instead of propagating argv
const err = catch{
import.call(argv.1, fn)
}
unset this.renderArgs
if(err){
api.ob.clear();
$this.out {<div>Exception in widget} " '" name "':<br/>"
$this.renderException err
$this.out {</div>}
}
api.ob.flush().pop()
$this.out {</div><!-- 'fsl-widget' -->}
}
api.import(DIR+'routing')