/**
*/
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 CGI module's "this" as its 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
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)
}
}
ob.push()
const httpCode = this.httpStatus()
if(rc inherits api.Exception){
saveSession = false
rc = object{
exception: this.scrubException(rc)
}
this.httpStatus(500, "Caught Exception")
while(ob.level() > obLevel ){
ob.pop()
}
}else if(rc){
rc = object{
body: rc
}
}else if( (httpCode>=500) && (httpCode<600) ){
saveSession = false
rc = undefined
while(ob.level() > obLevel ){
ob.pop()
}
print("It's dead, Jim.");
}
if(rc){
ob.clear()
this.setContentType('application/json')
this.printJSON(rc)
}
saveSession && this.savePathSession()
this.fsl && this.fsl.finalize()
this.send()
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,
it outputs the given file and returns true, otherwise 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)){
$out {<pre class='code'>} this.htmlEscape(slurpFile((file))) {</pre>}
return ce.result = true
}
showLink && $out {<div>[<a href='?showSrc=1'>soure code for this page</a>]</div>}
return ce.result = false
}
/**
Expects obj to be an Object. Each local property in obj
with a non-undefined values gets appended in the form:
key1=val1&key2=val2&...
each key/value gets urlencoded. Values which ===undefined
are skipped.
Returns '' if ('object'!==typename obj).
Returns '' if obj has no properties to
*/
api.cgi.objToUrlParams = proc(obj){
('object' === typename obj) || return ''
const ce = argv.callee
const buf = (ce.buffer || (ce.buffer=api.Buffer(100)))
var count = 0
const cgi = this
const propFunc = (ce.propFunc || (ce.propFunc=proc(k,v){
(undefined === v) && return
count += 1
(1!==count) && buf.append('&')
buf.append(cgi.urlencode(k),'=',cgi.urlencode(''+v))
}))
buf.reset()
obj.eachProperty(propFunc)
return count ? buf.toString() : ''
}
/**
Returns the root CGI path suffixed by appName.
*/
api.cgi.getAppUrlPath = proc(appName){
return this.cgiRoot + appName
}
/**
If passed no args, returns this instance's private cache
storage (an Object). If passed 1 arg, returns the value
of cache.(argv.0). Otherwise is stores the value of argv.1
into the cache using the key argv.0 and returns the
value it cached.
*/
api.cgi.cache = proc(){
const ce = argv.callee
const c = (ce._cache || (ce._cache = object{}))
const argc = argv.length()
argc || return c
(1==argc) && return c.(argv.0)
return c.(argv.0) = argv.1
}
/**
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
}
}
api.cgi.resolveResourceName = proc(baseName){
return this.config.resourceRootDir+baseName
}
api.cgi.resolveResourcePath = proc(baseName){
const pageName = this.resolveResourceName(baseName)
const fn = api.import.path.search(pageName)
/**
Reminder to self: if this throws, ensure that search path has
a "" somewhere in it so that "near-match" lookups (right
path, wrong extension) work on my hoster!
*/
if(!fn){
const shortName = pageName.substr(this.config.resourceRootDir.length())
throw api.Exception(Fossil.rc.NOT_FOUND, "Could not find resource "+shortName)
}
return fn
}
api.cgi.html = object {
/**
Returns an HTML Anchor tag ("A") with the given URL and label. The label
defaults to the URL.
*/
createAnchor: proc(url,label){
('string'===typename label) || (label = ''.concat(label))
return "<a href='%1$s'>%2$s</a>".applyFormat(url, api.cgi.htmlEscape(label || url))
},
/**
Assumes the given URL resolves a CSS content and outputs
JavaScript code which will inject the stylesheet into the
page header, such that it will get properly added to the
page's CSS (and need not be injected directly as a STYLE tag
into the current page's output). The advantage of this approach
is that the url's destination (e.g. /download/css/...) can add
caching-related headers.
The generated script injects no new symbols and this can be called
an arbitrary number of times.
Credits go to:
http://stackoverflow.com/questions/3744270/dynamically-loading-css
*/
injectCssLink:proc(url){
$api.cgi.out <<<EOF <script type='application/javascript'>(function(){
var fileref = document.createElement("link");
fileref.setAttribute("rel", "stylesheet");
fileref.setAttribute("type", "text/css");
fileref.setAttribute("href", 'EOF url <<<EOF');
document.getElementsByTagName("head")[0].appendChild(fileref);
})()</script>EOF
}
}/*api.cgi.html*/
/**
If name's extension (the part after the LAST period) matches
(case-insensitively) a known list of mime types, this returnsthat
mime type string, else it returns the 2nd parameter's value.
*/
api.cgi.guessMimeType = proc(name, default = 'application/octet-stream'){
const ce = argv.callee
const map = (ce.map || (ce.map=scope{
const map = api.Hash(117)
map.insert('c', 'text/plain')
map.insert('h', 'text/plain')
map.insert('css', 'text/css')
map.insert('html', 'text/html')
map.insert('in', 'text/plain')
map.insert('make', 'text/plain')
map.insert('sh', 'text/plain')
map.insert('txt', 'text/plain')
map.insert('th1ish', 'text/plain')
map.insert('fossi1ish', 'text/plain')
map.insert('png', 'image/png')
map.insert('jpg', 'image/jpeg')
map.insert('jpeg', 'image/jpeg')
map
}))
return (map.search(name.split('.').pop().toLower()) || default)
}
/**
If filename's mimeType (as determined by the 2nd parameter
or guessMimeType(filename)) belongs to a known set of
"downloadable" mime types, a Content-Disposition header
is added which hints to the browser that it should "download"
instead of "render" the page. The "attachment's" name is
the non-directory part of the given filename. Returns true if
it outputs that header.
*/
api.cgi.handleContentDisposition = proc(filename, mimeType){
assert 'string' === typename filename
mimeType || (mimeType = this.guessMimeType(filename))
assert 'string' === typename mimeType
const ce = argv.callee
const baseName = filename.split('/').pop()
const map = (ce.map || (ce.map=object{
{application/octet-stream}: true
}))
if(map.hasOwnProperty(mimeType)){
cgi.setHeader("Content-Disposition", 'attachment; filename="'.concat(baseName, '"'))
return true
}
}
/**
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.
*/
api.cgi.renderWidget = proc(name){
const fn = this.resolveResourcePath({widgets/}+name)
$this.out "<div class='fsl-widget fsl-widget-" name "'>"
api.ob.push()
const oldRenderArgs = this.WIDGET_RENDER_ARGS
this.WIDGET_RENDER_ARGS = argv
const err = catch{
api.import(fn)
}
this.WIDGET_RENDER_ARGS = oldRenderArgs
if(err){
api.ob.clear();
$this.out {<div class='exception'>Exception in widget} " '" name "':<br/>"
$this.out {<textarea rows='10' cols='80' class='exception'>}
$this.out this.toJSONString(this.scrubException(err))
$this.out {</textarea></div>}
}
api.ob.flush()
api.ob.pop()
$this.out {</div><!-- 'fsl-widget' -->}
}
api.import(DIR+'routing')