Login
init.fossi1ish at [f998037762]
Login

File th1ish/cgi/init.fossi1ish artifact a52988486d part of check-in f998037762


/**

*/

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')