assert api.cgi
/**
Dispatches to one of api.cgi.routes, depending on
the PATH_INFO. It either throws or exit()s, but never
returns successfully.
*/
api.cgi.dispatchRoute = proc(){
const R = this.request
this.util.assertHasRepo()
// Look for a CGI.routes entry matching PATH_INFO...
var ps = R.ENV.PATH_INFO_SPLIT
var next = this.routes
var rightMost = next
var leftMost = array[]
// th1ish bug: cloning of arrays returns a Function:
// var remainingPath = ps ? clone ps : undefined
var remainingPath = ps ? ps.slice()/*clone*/ : undefined
//print(typename ps, ps)
ps && for{var x, i = 0, key, psLen = [ps.length]}
{next && (i<psLen)}{i+=1}{
key = ps.(i)
next.hasOwnProperty(key) || continue
x = next.(key)
next = x || undefined
next && (rightMost = next)
leftMost.push(remainingPath.shift())
}
var cb
if( rightMost && ('function' === typename rightMost.render) ){
cb = rightMost
}else if ('function' === typename rightMost){
cb = rightMost
}else{
if(ps && ps.length()){
this.httpStatus(404)
throw api.Exception(Fossil.rc.NOT_FOUND, "Unknown page: "+R.ENV.PATH_INFO)
//throw "Unknown page: "+R.ENV.PATH_INFO
}else{
cb = this.routes._default
}
}
//print('cb',cb)
//R.ENV.remainingPath = remainingPath.join('/')
//R.ENV.leftPath = leftMost.join('/')
//R.remainingPath = remainingPath
R.routeAfterApp = remainingPath
this.runFuncAsPage(cb)
throw __SRC+": cannot be reached"
}
api.cgi.createPageProxyForFile = proc(pageBaseName){
const pf = api.import.path
const pageName = {pages/}+pageBaseName
const fn = pf.search(pageName)
fn || throw api.Exception(Fossil.rc.NOT_FOUND, "Could not find page "+pageName)
return proc(cgi){ import(fn) }.importSymbols(nameof fn)
}
/**
A helper class for setting up routes to render
via the 'skins' mechanism.
Calling this function returns an Object which inherits
from this function. Its (inherited) render() method
it used by the framework to render a skinable page.
*/
api.cgi.SkinnedPageRoute = proc(pageFile,title){
assert 'string' === typename pageFile
const rc = object{
pageFile: pageFile,
pageTitle: (title || this.request.ENV.PATH_INFO)
}
rc.prototype = argv.callee
return rc
}
api.cgi.SkinnedPageRoute.__typename = 'SkinnedPageRoute'
/**
If a SkinnedPageRoute instance matches during dispatchRoute(),
this function will be called to render the content using
the configured skin (api.cgi.config.defaultSkinName).
*/
api.cgi.SkinnedPageRoute.render = proc(cgi){
const pageBaseName = this.pageFile
const pageTitle = this.pageTitle
assert 'string' === typename pageBaseName
assert 'string' === typename pageTitle
const pageName = cgi.resolveResourceName({pages/}+pageBaseName)
const out = api.io.output
const ob = api.ob
const skinName = (cgi.config.defaultSkinName || 'default');
const importSkin = proc(name){
const key = cgi.resolveResourcePath('skins/'+name);
var skin = cgi.cache(key)
if(!skin){
skin = api.import(key)
skin || throw api.Exception(Fossil.rc.NOT_FOUND, "Could not find skin "+skinName)
cgi.cache(key, skin)
}
return skin
}.importSymbols(nameof cgi)
const skin = importSkin(skinName)
//cgi.currentSkin(skin)
skin.pageTitle = pageTitle
//cgi.printJSON(skin)
skin.outputOpener(cgi)
ob.push()
const obLevel = ob.level()
out({<div class='page-content'>})
const err = catch{
const pf = api.import.path
assert 'PathFinder' === typename pf
const fn = pf.search(pageName)
fn || throw api.Exception(Fossil.rc.NOT_FOUND, 'Could not find page ['+pageBaseName+']')
skin.showingPageSource = cgi.handleShowSource(fn)
if(!skin.showingPageSource){
ob.push()
import(fn)
ob.flush()
ob.pop()
}
}
if(err){
/* Discard output and generate exception message... */
while(ob.level() > obLevel){
ob.pop()
}
out({<span class='exception'>Exception while running page:</span>},
{<div><textarea class='exception' rows='20' cols='100'>},
cgi.toJSONString(cgi.scrubException(err)),
{</textarea></div>});
}
out({</div><!-- .page-content -->})
while(ob.level() >= obLevel){
ob.flush()
ob.pop()
}
skin.outputCloser(cgi)
}
api.cgi.DownloadRoute = proc(resourceName, contentType = undefined /*===guess!*/){
assert 'string' === typename resourceName
const rc = object{
mimeType: (contentType || this.guessMimeType(resourceName)),
resourceName: resourceName,
maxAge: 3601
}
rc.prototype = argv.callee
return rc
}
api.cgi.DownloadRoute.render = proc(cgi){
assert 'string' === typename this.resourceName
const fn = cgi.resolveResourcePath(this.resourceName)
const mimeType = (this.mimeType || cgi.guessMimeType(fn))
cgi.setContentType( mimeType )
cgi.handleContentDisposition(fn, mimeType)
const mtime = Fossil.file.mtime(fn)
if(this.maxAge){
cgi.setHeader('Cache-Control', 'max-age='+this.maxAge)
}else{
cgi.setHeader('Cache-Control', 'public' /* most content is immutable!*/)
}
const timeFmt = "%a %d %b %Y %H:%M:%S GMT"
cgi.setHeader( 'Last-Modified', strftime(timeFmt, mtime) )
Fossil.file.passthrough(fn)
//this.resolvedName = fn
//return this
}
var CGI = api.cgi
api.cgi.routes = object {
skinned: CGI.SkinnedPageRoute('timeline', 'api.cgi.SkinnedPageRoute test'),
foo: object{
bar:proc(cgi){
cgi.setContentType('text/plain')
print("should not be emitted")
api.ob.clear()
print(__SRC,"hi from", cgi.request.ENV.PATH_INFO)
print('.remainingPath ==> ', cgi.request.remainingPath)
}
},
err: CGI.SkinnedPageRoute('no-such-file', 'File Listing'),
dir: CGI.SkinnedPageRoute('dir', 'File Listing'),
download: object{
foo: CGI.DownloadRoute('skins/default.fossi1ish.css'),
css: object{
'dir.css': CGI.DownloadRoute('pages/dir.css'),
'default.css': CGI.DownloadRoute('skins/default.fossi1ish.css')
/* FIXME: ^^^^ plug that in to cgi.config.defaultSkinName */
},
uuid: proc(cgi){
// incomplete/experimental...
const R = cgi.request
const F = cgi.getFossilInstance()
//Fossil.sorter(cgi.request.ENV)
//print(cgi.toJSONString(cgi.request));
const sym = R.ENV.PATH_INFO_SPLIT.pop()
const blob = F.loadBlob(sym)
const name = cgi.getVar('name')
var mimeType = cgi.getVar('mimeType')
if(name){
mimeType || (mimeType = cgi.guessMimeType(name))
//cgi.setHeader("Content-Disposition", 'attachment; filename="'.concat(name, '"'))
}else if(!mimeType){
mimeType = 'text/plain'
}
cgi.setHeader('Cache-Control', 'max-age=290304000, public' /*content is immutable!*/)
cgi.setContentType(mimeType)
cgi.out(blob)
}
},
env: CGI.SkinnedPageRoute('env','CGI Environment'),
exception: CGI.SkinnedPageRoute('exception', 'Exception Example'),
manifest: CGI.SkinnedPageRoute('manifest', 'Manifest Browser'),
reports: scope{
const pmain = CGI.SkinnedPageRoute('reports', 'Activity Reports')
pmain.'by-user' = CGI.SkinnedPageRoute('reports-by-user', 'By-user Activity Reports')
pmain.'by-month' = CGI.SkinnedPageRoute('reports-by-month', 'By-month Activity Reports')
pmain.'by-year' = CGI.SkinnedPageRoute('reports-by-year', 'By-year Activity Reports')
pmain.'by-week' = CGI.SkinnedPageRoute('reports-by-week', 'By-week Activity Reports')
pmain
},
snap: proc(cgi){
//const x = api.prototypes.Exception(3,"!!!");
//throw x ; //"Aw, snap! Google changed its APIs again!";
throw api.Exception(Fossil.rc.ERROR, "Aw, snap! Google v8 changed its APIs again!")
},
timeline: scope{
//var pmain = CGI.createPageProxyForFile('timeline')
//pmain.html = CGI.createPageProxyForFile('timeline-html')
//pmain
CGI.SkinnedPageRoute('timeline', 'Timeline Overview')
}/*object{
text: CGI.createPageProxyForFile('timeline'),
html: CGI.createPageProxyForFile('timeline-html')
}*/,
throw: proc(cgi){
throw __SRC+" A test exception."
},
test: proc(cgi){
const out = api.io.output
cgi.setContentType('text/plain')
const skin = api.import('cgi/skins/default')
assert 'object' === typename skin
//cgi.printJSON(skin)
skin.pageTitle = cgi.request.ENV.PATH_INFO
skin.outputOpener(cgi)
$out "Hi, world!"
skin.outputCloser(cgi)
},
test2: CGI.SkinnedPageRoute('test','Test Page & < Page #2'),
_default: CGI.SkinnedPageRoute('default', 'Home'),
// proc(){return api.cgi.getFossilInstance().getProjectName()}),
_defaultOld: proc(cgi){
const R = cgi.request
const now = time()
//cgi.setCookie( 'default-page', object{value: now, path:'/foo/', httpOnly:true})
cgi.setHeader('X-my-header', 1);
var rc = object {
timestamp: [time],
//cookies: R.COOKIES,
get: R.GET,
//ARGV: ARGV
cgiRoot: cgi.cgiRoot,
//session: cgi.getSession(),
{Fossil.rc}: Fossil.rc
}
if(cgi.isLocalServer){
rc.privileged = object{
env: R.ENV
}
}
const fsl = cgi.getFossilInstance()
if(fsl && fsl.db && fsl.db.repo){
rc.context = "%1$p".applyFormat(fsl)
rc.repo = object{
trunkVersion: fsl.symToUuid("trunk"),
projectName: fsl.getProjectName()
}
}
return rc
}
}
unset CGI.createPageProxyForFile, CGI