
/******************************************************************************/
/* 
 *  This file is an amalgamation of all the individual source code files for
 *  Embedthis Ejscript 0.9.5 generated on Mon Feb 16 17:59:42 PST 2009.
 *
 *  Catenating all the source into a single file makes embedding simpler and
 *  the resulting application faster, as many compilers can do whole file
 *  optimization.
 *
 *  If you want to modify ejs, you can still get the whole source
 *  as individual files if you need.
 */


/************************************************************************/
/*
 *  Start of file "../es/web/utils/parser.es"
 */
/************************************************************************/

/*
 *  parser.es -- Egen Parser - Parse an ejs file and emit a Ejscript compiled version
 *
 *  This parser handles embedded Ejscript using <% %> directives. It supports:
 *
 *    <%                    Begin an ejs directive section containing statements
 *    <%=                   Begin an ejs directive section that contains an expression to evaluate and substitute
 *    %>                    End an ejs directive
 *    <%@ include "file" %> Include an ejs file
 *    <%@ layout "file" %>  Specify a layout page to use. Use layout "" to disable layout management.
 *
 *  Directives for use outside of <% %> 
 *    @@var                 To expand the value of "var". Var can also be simple expressions (without spaces).
 */

module ejs.web.parser {

    /*
     *  Parser tokens
     */
    class Token {
        public static const Err			= -1		/* Any input error */
        public static const Eof			= 0			/* End of file */
        public static const EjsTag  	= 1			/* <% text %> */
        public static const Var 		= 2			/* @@var */
        public static const Literal		= 3			/* literal HTML */
        public static const Equals		= 4			/* <%= expression */
        public static const Control 	= 6			/* <%@ control */

        public static var tokens = [ "Err", "Eof", "EjsTag", "Var", "Literal", "Equals", "Control" ]
    }


    class EjsParser {

        private const ContentMarker: String         = "__ejs:CONTENT:ejs__"
        private const ContentPattern: RegExp        = new RegExp(ContentMarker)
        private const LayoutsDir: String            = "views/layouts"

        private var appBaseDir: String
        private var script: String
        private var pos: Number                     = 0
        private var lineNumber: Number              = 0
        private var layoutPage: String


        /*
         *  Main parser. Parse the script and return the compiled (Ejscript) result
         */
        public function parse(file: String, appDir: String, layout: string): String {

            var token: ByteArray = new ByteArray
            var out: ByteArray = new ByteArray
            var tid: Number

            appBaseDir = appDir;
            layoutPage = layout
            script = File.getString(file)

            while ((tid = getToken(token)) != Token.Eof) {

                // print("getToken => " + Token.tokens[tid + 1] + " TOKEN => \"" + token + "\"")

                switch (tid) {
                case Token.Literal:
                    out.write("\nwrite(\"" + token + "\");\n")
                    break

                case Token.Var:
                    /*
                     *	Trick to get undefined variables to evaluate to "".
                     *	Catenate with "" to cause toString to run.
                     */
                    out.write("\nwrite(\"\" + ", token, ");\n")
                    break

                case Token.Equals:
                    out.write("\nwrite(\"\" + (", token, "));\n")
                    break

                case Token.EjsTag:
                    /*
                     *  Just copy the Ejscript code straight through
                     */
                    //  TODO BUG ByteArray.write(ByteArray) is not working. Requires toString()
                    out.write(token.toString())
                    break

                case Token.Control:
                    let args: Array = token.toString().split(/\s/g)
                    let cmd: String = args[0]

                    switch (cmd) {
                    case "include":
                        let path = args[1].trim("'").trim('"')
                        let incPath = (path[0] == '/') ? path: dirname(file) + "/" + path
                        /*
                         *	Recurse and process the include script
                         */
                        let inc: EjsParser = new EjsParser
                        out.write(inc.parse(incPath, appBaseDir, undefined))
                        break

                    case "layout":
                        let path = args[1]
                        if (path == "") {
                            layoutPage = undefined
                        } else {
                            path = args[1].trim("'").trim('"') + ".ejs"
                            layoutPage = (path[0] == '/') ? path : (LayoutsDir + "/" + path)
                            if (! exists(layoutPage)) {
                                error("Can't find layout page " + layoutPage)
                            }
                        }
                        break

                    case "content":
                        out.write(ContentMarker)
                        break

                    default:
                        error("Bad control directive: " + cmd)
                    }
                    break

                default:
                case Token.Err:
                    //  TODO - should report line numbers
                    error("Bad input token: " + token)

                }
            }

            if (layoutPage != undefined && layoutPage != file) {
                let layoutText: String = new EjsParser().parse(layoutPage, appBaseDir, layoutPage)
                return layoutText.replace(ContentPattern, out.toString())
            }
            return out.toString()
        }


        /*
         *  Get the next input token. Read from script[pos]. Return the next token ID and update the token byte array
         */
        function getToken(token: ByteArray): Number {

            var tid = Token.Literal

            token.flush()

            var c
            while (pos < script.length) {
                c = script[pos++]

                switch (c) {

                case '<':
                    if (script[pos] == '%' && (pos < 2 || script[pos - 2] != '\\')) {
                        if (token.available > 0) {
                            pos--
                            return Token.Literal
                        }
                        pos++
                        eatSpace()
                        if (script[pos] == '=') {
                            /*
                             *  <%=  directive
                             */
                            pos++
                            eatSpace()
                            while ((c = script[pos]) != undefined && 
                                    (c != '%' || script[pos+1] != '>' || script[pos-1] == '\\')) {
                                token.write(c)
                                pos++
                            }
                            pos += 2
                            return Token.Equals

                        } else if (script[pos] == '@') {
                            /*
                             *  <%@  directive
                             */
                            pos++
                            eatSpace()
                            while ((c = script[pos]) != undefined && (c != '%' || script[pos+1] != '>')) {
                                token.write(c)
                                pos++
                            }
                            pos += 2
                            return Token.Control

                        } else {
                            while ((c = script[pos]) != undefined && 
                                    (c != '%' || script[pos+1] != '>' || script[pos-1] == '\\')) {
                                token.write(c)
                                pos++
                            }
                            pos += 2
                            return Token.EjsTag
                        }
                    }
                    token.write(c)
                    break

                case '@':
                    if (script[pos] == '@' && (pos < 1 || script[pos-1] != '\\')) {
                        if (token.available > 0) {
                            pos--
                            return Token.Literal
                        }
                        pos++
                        c = script[pos++]
                        while (c.isAlpha || c.isDigit || c == '[' || c == ']' || c == '.' || c == '$' || 
                                c == '_' || c == "'") {
                            token.write(c)
                            c = script[pos++]
                        }
                        pos--
                        return Token.Var
                    }
                    token.write(c)
                    break

                case "\r":
                case "\n":
                    lineNumber++
                    token.write(c)
                    tid = Token.Literal
                    break

                default:
                    //  TODO - triple quotes would eliminate the need for this
                    if (c == '\"' || c == '\\') {
                        token.write('\\')
                    }
                    token.write(c)
                    break
                }
            }
            if (token.available == 0 && pos >= script.length) {
                return Token.Eof
            }
            return tid
        }


        function eatSpace(): Void {
            while (script[pos].isSpace) {
                pos++
            }
        }


        function error(msg: String): Void {
            throw "egen: " + msg + ". At line " + lineNumber
        }
    }
}


/*
 *	@copy	default
 *
 *	Copyright (c) Embedthis Software LLC, 2003-2009. All Rights Reserved.
 *	Copyright (c) Michael O'Brien, 1993-2009. All Rights Reserved.
 *
 *	This software is distributed under commercial and open source licenses.
 *	You may use the GPL open source license described below or you may acquire
 *	a commercial license from Embedthis Software. You agree to be fully bound
 *	by the terms of either license. Consult the LICENSE.TXT distributed with
 *	this software for full details.
 *
 *	This software is open source; you can redistribute it and/or modify it
 *	under the terms of the GNU General Public License as published by the
 *	Free Software Foundation; either version 2 of the License, or (at your
 *	option) any later version. See the GNU General Public License for more
 *	details at: http://www.embedthis.com/downloads/gplLicense.html
 *
 *	This program is distributed WITHOUT ANY WARRANTY; without even the
 *	implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	This GPL license does NOT permit incorporating this software into
 *	proprietary programs. If you are unable to comply with the GPL, you must
 *	acquire a commercial license to use this software. Commercial licenses
 *	for this software and support services are available from Embedthis
 *	Software at http://www.embedthis.com
 *
 *	@end
 */
/************************************************************************/
/*
 *  End of file "../es/web/utils/parser.es"
 */
/************************************************************************/



/************************************************************************/
/*
 *  Start of file "../es/web/utils/egen.es"
 */
/************************************************************************/

#!/Users/mob/hg/ejs/bin/ejs

/*
 *  egen.es -- Ejscript web framework generator. This generates, compiles, cleans and packages Ejscript web applications. 
 *  TODO - create constant for '.es' '.ejs' 
 */

GC.enabled = false

/*
   TODO .ejsrc

some {
    standard: "ecma4" | "ecma3" | "ejs" 
    featureOne: enable | disable
}

compiler {
    same...
}
 */


use module ejs.web.parser

class Egen {

    private const DIR_PERMS: Number = 0775
    private const FILE_PERMS: Number = 0666
    private const RC: String = ".ejsrc"
    private const DefaultLayout: String = "views/layouts/default.ejs"

    private var keep: Boolean = false
    private var layoutPage: String = DefaultLayout
    private var overwrite: Boolean = false
    private var verbose: Boolean = false
    private var command: String 
    private var config: Object
    private var mode: String = "debug"


    function Egen() {
        config = loadEcf("config/config.ecf", false)
        if (config) {
            mode = config.app.mode
        } else {
            config = {}
            config.compiler = {}
            config.compiler[mode] = {}
            config.compiler[mode].command = App.dir + "/ec --debug --optimize 9 --use ejs.db --use ejs.web"
        }
    }


    /*
     *  Load the various default files
     */
    function loadDefaults(): Void {

        loadConfigFile(RC, "defaults") || loadConfigFile("~/" + RC, "defaults")
    }


    /*
     *  Parse args and invoke required commands
     */
    function parseArgs(args: Array): Boolean {
        let cmdArgs: Array

        for (let i: Number = 1; i < args.length; i++) {
            switch (args[i]) {
            case "--keep":
                keep = true
                break

            case "--layout":
                layoutPage = args[++i]
                break

            case "--overwrite":
                overwrite = true
                break

            case "-v":
            case "--verbose":
                verbose = true
                break

            default:
                if (args[i].startsWith("-")) {
                    usage()
                    return false
                }
                cmdArgs = args.slice(i)
                //  TODO BUG. Break not working
                i = 9999
                break
            }
        }

        if (cmdArgs == null || cmdArgs.length == 0) {
            usage()
            return false
        }

        let rest: Array = cmdArgs.slice(1)
        let cmd: String = cmdArgs[0].toLower()

        switch (cmd) {
        case "clean":
            clean(rest)
            break

        case "compile":
            compile(rest)
            break

        case "console":
            console(rest)
            break

        case "deploy":
            deploy(rest)
            break

        case "install":
            install(rest)
            break

        case "generate":
            generate(rest)
            break

        case "run":
            run(rest)
            break

        default:
            return false
        }
        command = rest[0]

        return true
    }


    function usage(): Void {
        print("\nUsage: egen [options] [commands] ...\n" +
            "  Options:\n" + 
            "    --database [sqlite | mysql]     Sqlite only currently supported adapter\n" + 
            "    --keep\n" + 
            "    --layout layoutPage\n" + 
            "    --overwrite\n" + 
            "    --verbose\n")

        /*
         *  TODO
            "    egen generate package\n" +
            "    egen install (not implemented yet)\n" +
            "    egen mode [development | test | production] (not implemented yet)\n" +
            "    egen deploy path.zip (not implemented yet)\n" +
        */
        print("  Commands:\n" +
            "    egen clean\n" +
            "    egen compile [all | app | controller names | model names | view names]\n" +
            "    egen compile path/name.ejs ...\n" +
            "    egen generate [app name | controller name [action [, action] ...]| model name]\n" +
            "    egen generate scaffold model [controller] [action [, action]...]\n" +
            "    egen run" +
            "")
        App.exit(1)
    }


    function clean(args: Array): Void {
        let files: Array = glob(".", /\.mod$/)
        trace("Clean", files)
        for each (f in files) {
            rm(f)
        }
    }


    function compile(args: Array): Void {
        var files: Array

        if (args.length == 0) {
            args.append("everything")
        }
        let kind: String = args[0].toLower()
        let rest: Array = args.slice(1)
        let app: String = basename(App.workingDir)

        loadConfigFile("config/compiler.ecf", "compiler", false)

        let ejspat = /\.ejs$/
        let pat = /\.es$/

        switch (kind) {
        case "everything":
            /*
             *  Build all items but NOT as one module
             */
            checkApp()
            buildFiles("src/" + app.toPascal() + ".mod", glob("config", pat) + glob("src", pat) + glob("models", pat) + 
                glob("controllers", /Application.es$/))
            for each (name in glob("controllers", pat)) {
                buildController(name)
            }
            files = glob("views", ejspat)
            for each (name in files) {
                buildView(name)
            }
            files = glob("web", ejspat)
            layoutPage = undefined;
            for each (name in files) {
                buildWebPage(name)
            }
            break

        case "all":
            /*
             *  Build entire app as one module
             */
            checkApp()
            files = glob("config", pat) + glob("src", pat) + glob("controllers", pat) + glob("views", pat) + 
                glob("models", pat)
            webFiles = glob("web", ejspat)
            layoutPage = undefined
            for each (name in webFiles) {
                files.append(buildWebPage(name, false))
            }
            buildFiles("src/" + app + ".mod", files)
            break

        case "app":
            /*
             *  Build app portions. This includes all config, src, models and ApplicationController
             */
            checkApp()
            files = glob("config", pat) + glob("src", pat) + glob("models", pat) + glob("controllers", /Application.es$/)
            if (files.length == 0) {
                throw "No application found at " + app
            }
            buildFiles("src/" + app + ".mod", files)
            break

        case "controller":
        case "controllers":
            /*
             *  Build controllers
             */
            checkApp()
            if (rest.length == 0) {
                for each (name in glob("controllers", pat)) {
                    buildController(name)
                }
            } else {
                for each (name in rest) {
                    buildController(name)
                }
            }
            break

        case "model":
        case "models":
            throw "WARNING: models must be built with the app. Use \"egen compile app\""
            /*
             *  Build models
             */
            checkApp()
            if (rest.length == 0) {
                for each (name in glob("models", pat)) {
                    buildModel(name)
                }
            } else {
                for each (name in rest) {
                    buildModel(name)
                }
            }
            break

        case "view":
        case "views":
            /*
             *  Comile views
             */
            checkApp()
            if (rest.length == 0) {
                for each (view in glob("views", ejspat)) {
                    buildView(view)
                }
            } else {
                for each (view in rest) {
                    buildView(view)
                }
            }
            break

        default:
            for (f in args) {
                let file: String = args[0]
                if (extension(file) == '.ejs') {
                    layoutPage = undefined
                    buildWebPage(file)
                } else if (extension(file) == '.es') {
                    build(file)
                } else {
                    throw new ArgError("Unknown compile category: " + kind)
                }
            }
        }
    }


    function console(args: Array): Void {
        appName = basename(App.workingDir)
        // cmd = 'ejs --use "' + appName + '"'
        let cmd = "ejs"
        //  TODO - this won't work without stdin
        System.run(cmd)
    }


    function buildController(file: String) {
        let testFile = "controllers/" + file.toPascal() + ".es"
        if (exists(testFile)) {
            file = testFile
        }
        if (!file.startsWith("controllers/")) {
            throw "File \"" + file + " \" is not a controller"
        }
        if (!exists(file)) {
            throw "Controller : \"" + file + "\" does not exist"   
        }
        build(file)
    }


    function buildModel(file: String) {
        let testFile = "models/" + file.toPascal() + ".es"
        if (exists(testFile)) {
            file = testFile
        }
        if (!file.startsWith("models/")) {
            throw "File \"" + file + " \" is not a controller"
        }
        if (!exists(file)) {
            throw "Model : \"" + file + "\" does not exist"   
        }
        build(file)
    }


    function buildView(file: String) {
        if (file.contains(/^views\/layouts\//)) {
            /*
             *  Skip layouts
             */
            return
        }
        if (!file.startsWith("views/")) {
            throw "File \"" + file + " \" is not a view"
        }
        buildWebPage(file, true, true)
    }


    function buildWebPage(file: String, compile: Boolean = true, isView: Boolean = false): String {

        let ext = extension(file)
        if (ext == "") {
            file += ".ejs"
        } else if (ext != ".ejs") {
            throw "File is not an Ejscript web file: " + file
        }
        if (!exists(file)) {
            throw "Cant find view file: " + file
        }

        let sansExt = file.replace(/.ejs$/,"")
        let controller: String
        let controllerMod: String
        let controllerSource: String
        let controllerPrefix: String
        let viewName: String

        if (isView) {
            controller = getNthSegment(sansExt, 1).toPascal()
            viewName = basename(sansExt)
        } else {
            viewName = "StandAlone"
            controllerPrefix = "Application" + "_"
        }

        /*
         *  Ensure the corresponding controller (if there is one) is built first
         */
        controllerMod = "controllers/" + controller + ".mod"
        controllerSource = "controllers/" + controller + ".es"

        if (isView && exists(controllerSource)) {
            if (!exists(controllerMod)) {
                build("controllers/" + controller + ".es")
            }
            controllerPrefix = controller + "_"
        }

        /*
         *  Parse the ejs file and create an ".es" equivalent
         */
        trace("Parse", file)
        let ep: EjsParser = new EjsParser
        results = Templates.ViewHeader + ep.parse(file, App.workingDir, layoutPage) + Templates.ViewFooter
        results = results.replace(/\${CONTROLLER}/g, controllerPrefix)
        results = results.replace(/\${VIEW}/g, viewName)

        let esfile = sansExt + ".es"
        File.put(esfile, 0664, results)

        if (compile) {
            let out = sansExt + ".mod"
            rm(out)

            let cmd: String
            if (exists(controllerMod)) {
                cmd = config.compiler[mode].command + " --out " + out + " " + controllerMod + " " + esfile
            } else {
                cmd = config.compiler[mode].command + " --out " + out + " " + esfile
            }

            trace("Compile", cmd)
            command(cmd)

            if (!exists(out)) {
                throw "Compilation failed for " + out + "\n" + results
            }
            if (!keep) {
                rm(esfile)
            }
        }
        return esfile
    }


    /*
     *  Build the entire app into a single mod file
     */
    function buildFiles(out: String, files: Array) {
        rm(out)
        let cmd = config.compiler[mode].command + " --out " + out + " " + files.join(" ")
        trace("Compile", cmd)
        let results = command(cmd)
        if (!exists(out)) {
            throw "Compilation failed for " + out + "\n" + results
        }
    }


    /*
     *  Build a single file. Used for controllers and models.
     */
    function build(files: String) {
        let out = files.replace(/.es$/,"") + ".mod"
        rm(out)

        let cmd = config.compiler[mode].command + " --out " + out + " " + files
        trace("Compile", cmd)
        let results = command(cmd)
        if (!exists(out)) {
            throw "Compilation failed for " + out + "\n" + results
        }
    }


    function deploy(args: Array): Void {
    }


    function install(args: Array): Void {
    }


    function generate(args: Array): Void {
        if (args.length == 0) {
            args.append("all")
        }

        let kind: String = args[0].toLower()
        let rest: Array = args.slice(1)

        if (rest.length == 0) {
            usage()
            return
        }

        switch (kind) {
        case "app":
            generateApp(rest)
            break

        case "controller":
            generateController(rest)
            break

        case "model":
            generateModel(rest)
            break

        case "scaffold":
            generateScaffold(rest)
            break

        default:
            usage()
            return
        }
    }


    function run(args: Array): Void {
        let cmd = config.app.webserver
        System.run(cmd)
    }


    /*
     *  Generate an application.
     *
     *  egen generate app appName
     */
    function generateApp(args: Array): Void {

        let name: String = args[0].toLower()
        let f: File = new File(name)

        trace("Generate App", name)
        makeDir(name)
        makeDir(name + "/.tmp")
        makeDir(name + "/.ejs")
        makeDir(name + "/bin")
        makeDir(name + "/config")
        makeDir(name + "/controllers")
        makeDir(name + "/db")
        makeDir(name + "/doc")
        makeDir(name + "/logs")
        makeDir(name + "/models")
        makeDir(name + "/messages")
        makeDir(name + "/test")
        makeDir(name + "/src")
        makeDir(name + "/utils")
        makeDir(name + "/views")
        makeDir(name + "/views/layouts")
        makeDir(name + "/web")
        makeDir(name + "/web/images")
        makeDir(name + "/web/themes")

        generateConfig(name)
        generateLayouts(name)
        generateAppClass(name)
        generateAppController(name)
        generateStyles(name)
        generateReadme(name)
    }


    function generateConfig(name: String): Void {
        makeDir(name + "/config")
        makeConfigFile(name, "/config/config.ecf", Templates.Config)
        makeConfigFile(name, "/config/compiler.ecf", Templates.Compiler)
        makeConfigFile(name, "/config/database.ecf", Templates.Database)
        makeConfigFile(name, "/config/view.ecf", Templates.View)
    }


    function generateLayouts(name: String): Void {
        makeFile(name + "/web/themes/default.css", Templates.DefaultTheme, "Theme")
        makeFile(name + "/web/layout.css", Templates.DefaultLayoutStyle, "Layout Style")
    }


    function generateStyles(name: String): Void {
        makeDir(name + "/web/")
        makeFile(name + "/views/layouts/default.ejs", Templates.DefaultLayout, "Layout")
    }


    function generateAppClass(name: String): Void {
        makeDir(name + "/src")
        let app = name.toPascal()
        let path = name + "/src/" + app + ".es"
        let data = Templates.App.replace(/\${NAME}/g, app)
        makeFile(path, data, "App Class")
    }


    function generateAppController(name: String): Void {
        makeDir(name + "/controllers")
        let app = name.toPascal()
        let path = name + "/controllers/Application.es"
        let data = Templates.AppController.replace(/\${NAME}/g, app)
        makeFile(path, data, "ApplicationController")
    }


    function generateReadme(app: String): Void {
        let name: String = app.toPascal()
        let path: String = app + "/README"
        let data: String = Templates.Readme.replace(/\${NAME}/g, name)
        makeFile(path, data, "README")
    }


    /*
     *  egen generate controller controllerName [action ...]
     */
    function generateController(args: Array): Void {
        checkApp()
        let app = basename(App.workingDir).toPascal()
        let name: String = args[0].toPascal()
        let actions = args.slice(1)
        let path: String = "controllers/" + name + ".es"
        let data: String = Templates.Controller.replace(/\${NAME}/g, name)
        data = data.replace(/\${APP}/g, app)

        if (actions.length == 0) {
            actions.append("index")
        }
        for each (action in actions) {
            let actionData = Templates.Action.replace(/\${NAME}/g, action)
            data = data.replace(/NEXT_ACTION/, actionData + "NEXT_ACTION")
        }
        data = data.replace(/NEXT_ACTION/, "")
        makeFile(path, data, "Controller")
    }


    /*
     *  egen generate model modelName [field:type ...]
     */
    function generateModel(args: Array): Void {
        checkApp()
        let name: String = args[0]
        // makeDir(name + "/models")
        if (name.endsWith("s")) {
            //  TODO - should got to stdout
            print("WARNING: Models should typically be singluar not plural")
        }
        name = name.toPascal()
        let path = "models/" + name + ".es"
        let data = Templates.Model.replace(/\${NAME}/g, name)
        makeFile(path, data, "Model")
    }


    /*
     *  egen generate scaffold modelName [controllerName] [action ...]
     */
    function generateScaffold(args: Array): Void {
        checkApp()
        let model = args[0]
        let controller = (args.length >= 2) ? args[1] : model
        controller = controller.toPascal()
        let actions = args.slice(2)

        makeDir("models")
        makeDir("controllers")
        makeDir("views")
        makeDir("views/" + controller)

        generateModel([model])
        generateScaffoldController(controller, model, actions)
        generateScaffoldViews(controller, model, actions)
    }


    /*
     *  Create a controller with scaffolding. Usage: controllerName [actions ...]
     */
    function generateScaffoldController(controller: String, model: String, extraActions: Array): Void {

        let app = basename(App.workingDir).toPascal()
        let name = controller.toPascal()
        let path = "controllers/" + name + ".es"

        let stndActions: Array = [ "index", "list", "edit", "create", "update", "destroy" ]
        let views: Array = [ "list", "edit" ]
        let actions: Array = []

        for each (action in extraActions) {
            if (! stndActions.contains(action)) {
                actions.append(action.toCamel())
            }
        }

        let data: String = Templates.ScaffoldController.replace(/\${APP}/g, app)
        data = data.replace(/\${NAME}/g, name)
        data = data.replace(/\${MODEL}/g, model.toPascal())
        data = data.replace(/\${LOWER_MODEL}/g, model.toLower())

        for each (action in actions) {
            let actionData = Templates.Action.replace(/\${NAME}/g, action)
            data = data.replace(/NEXT_ACTION/, actionData + "NEXT_ACTION")
        }
        data = data.replace(/NEXT_ACTION/, "")

        makeFile(path, data, "Controller")
    }


    /*
     *  Create a scaffold views.  Usage: controllerName [actions ...]
     */
    function generateScaffoldViews(controller: String, model: String, extraActions: Array): Void {

        let stndActions: Array = [ "index", "list", "edit", "create", "update", "destroy" ]
        let views: Array = [ "list", "edit" ]
        let actions: Array = []

        for each (action in extraActions) {
            if (! stndActions.contains(action)) {
                views.append(action.toCamel())
            }
        }

        model.toPascal()
        let data: String

        for each (view in views) {
            switch (view) {
            case "edit":
                data = Templates.ScaffoldEditView.replace(/\${MODEL}/g, model.toPascal())
                data = data.replace(/\${LOWER_MODEL}/g, model)
                break
            case "list":
                data = Templates.ScaffoldListView.replace(/\${MODEL}/g, model.toPascal())
                break
            default:
                data = Templates.ScaffoldView.replace(/\${MODEL}/g, model.toPascal())
                data = data.replace(/\${LOWER_MODEL}/g, model)
                data = data.replace(/\${CONTROLLER}/g, controller)
                data = data.replace(/\${VIEW}/g, view)
                break
            }
            let path: String = "views/" + controller + "/" + view + ".ejs"
            makeFile(path, data, "View")
        }
    }


    function checkApp(requiredDirs: Array = []): Void {
        let dirs: Array = [ "config", "controllers", "views"  ] + requiredDirs
        for each (d in dirs) {
            if (! isDir(d)) {
                error("Can't find \"" + d + "\" directory. Run from inside the application directory")
            }
        }

        let files: Array = [ "config/compiler.ecf", "config/config.ecf", "config/database.ecf", "config/view.ecf" ]
        for each (f in files) {
            if (! exists(f)) {
                error("Can't find \"" + f + "\" Run from inside the application directory\n" +
                      "Use egen generate app NAME to create a new Ejscript web application")
            }
        }
    }


    function loadConfigFile(file: String, objName: String, mandatory: Boolean = false): Boolean {
        let settings: Object = loadEcf(file, mandatory)
        if (settings == null) {
            return false
        }
        let obj = config[objName] = {}
        for (key in settings) {
            obj[key] = settings[key]
        }
        return true
    }


    function loadEcf(path: String, mandatory: Boolean = false): Object {
        if (!exists(path)) {
            if (mandatory) {
                throw new IOError("Can't open required configuration file: " + path)
            } else {
                return null
            }
        }
        try {
            let data = "{ " + File.getString(path) + " }"
            return deserialize(data)
        } catch (e: Error) {
            throw new IOError("Can't load " + path + e)
        }
    }


    /*
     *  Make an ECF file that usually lives under ./config
     */
    function makeConfigFile(app: String, path: String, data: String): Void {
        let file = app + path
        //  TODO - need to prompt user to overwrite
        if (exists(file) && !overwrite) {
            return
        }
        data = data.replace(/\${NAME}/g, app)
        makeFile(file, data, "Config File")
    }


    function makeFile(path: String, data: String, msg: String): Void {

        let f: File = new File(path)
        if (f.exists && !overwrite) {
            traceFile(path, msg + " exists")
            return
        }

        if (! f.exists) {
            traceFile(path, msg + " created")
        } else {
            traceFile(path, msg + " overwritten")
        }

        f.open(File.Write | File.Create | File.Truncate)
        f.write(data)
        f.close()
    }


    function makeDir(path: String): Void {
        if (isDir(path)) {
            return
        }
        trace("Make Directory", path)
        mkdir(path, DIR_PERMS)
    }


    /*
     *  Find all files matching the pattern 
     */
    function glob(path: String, pattern: RegExp, recurse: Boolean = true): Array {
        let result: Array = new Array
        if (isDir(path)) {
            if (recurse) {
                for each (f in ls(path, true)) {
                    let got: Array = glob(f, pattern)
                    for each (i in got) {
                        result.append(i)
                    }
                }
            }

        } else {
            if (path.match(pattern)) {
                result.append(path)
            }
        }
        return result
    }


    function globSubdirs(path: String): Array {
        let result: Array = new Array
        for each (f in ls(path, true)) {
            if (isDir(f)) {
                result.append(f)
            }
        }
        return result
    }


    function getNthSegment(path: String, nth: Number) {
        let segments: Array = path.split(/(\\|\/)+/g)
        for (let i: Number = segments.length - 1; i >= 0; i--) {
            if (segments[0] == ".") {
                segments.remove(i, i)
            }
        }
        return segments[nth]
    }


    function command(cmd: String): String {
        let results
        try {
            results = System.run(cmd + " 2>&1")
        } 
        catch (e) {
            msg = "Compilation failure, for " + cmd + "\n" +
                e.toString().replace(/Error Exception: Command failed: Status code [0-9]*.\n/, "")
            throw msg
        }
        return results
    }


    function error(...args): Void {
        //  TODO - need a better way to write error messages without the "ejs: Error: String: ejs" prefix
        throw("egen: " + args.join(" "))
        App.exit(1)
    }


    function traceFile(path: String, msg: String): Void {
        //  TODO - string method to add quotes would be useful
        trace(msg, '"' + path + '"')
    }


    function trace(tag: String, ...args): Void {
        if (verbose) {
            print("  " + tag + ": " + args.join(" "))
        }
    }
}


/*
 *  Templates for various files
 */
class Templates {
    
/*
 ********************* src/NAME.es template ***********************
 */
    public static const App = 
'/*
 *  ${NAME}.es - ${NAME} Application Setup
 */

module ${NAME} {
    // public var app: ${NAME}

    class ${NAME} extends Application {
        function ${NAME}(dir: String) {
            super(dir)
        }
    }
}
'



/*
 ***************** config/config.ecf template ***********************
 */
    public static const Config =
'
app: {
    mode: "debug",
    webserver: "appweb --home /etc/appweb --config appweb.conf --log /dev/tty:1"
},
'



/*
 ***************** config/compiler.ecf template ***********************
 */
    public static const Compiler = 
'
debug: {
    command: "ec --searchpath src --debug --optimize 9 --use ejs.db --use ejs.web",
},

test: {
    command: "ec --searchpath src --debug --optimize 9 --use ejs.db --use ejs.web",
},

production: {
    command: "ec --searchpath src --optimize 9 --use ejs.db --use ejs.web",
},
'


/*
 ***************** config/database.ecf template ***********************
 */
    public static const Database = 
'
debug: {
    adapter: "sqlite3",
    database: "db/${NAME}.sdb",
    username: "",
    password: "",
    timeout: 5000,
},

test: {
    adapter: "sqlite3",
    database: "db/${NAME}.sdb",
    username: "",
    password: "",
    timeout: 5000,
},

production: {
    adapter: "sqlite3",
    database: "db/${NAME}.sdb",
    username: "",
    password: "",
    timeout: 5000,
},
'


/*
 ***************** config/view.ecf template ***********************
 */
    public static const View = 
'
connectors: {
    table: "html",
    chart: "google",
    rest: "html",
},

'


    /*
     *****************  ApplicationController template ***********************
     */
    public static const AppController = 
'/*
 *  ApplicationController.es - Base class for all controllers
 */

module ${NAME} {
    public class ApplicationController extends Controller {

        public var title: String = "${NAME}"
        public var style: String

        /*
         *  Turn on SQL statement logging
         *      Record.trace(true)
         */

        function ApplicationController() {
            style = app.url + "/web/style.css"
        }
    }
}
'


    /*
     *****************  Controller template ***********************
     */
    public static const Controller = 
'/*
 *  ${NAME}Controller.es - ${NAME} Controller Class
 */

use module ${APP}

public class ${NAME}Controller extends ApplicationController {

    function ${NAME}Controller() {
    }

    NEXT_ACTION
}
'


    /*
     *****************  ScaffoldController template ******************
     */
    public static const ScaffoldController = 
'/*
 *  ${NAME}Controller.es - ${NAME} Controller Class
 */
use module ${APP}
use namespace action

public class ${NAME}Controller extends ApplicationController {

    function ${NAME}Controller() {
    }

    public var ${LOWER_MODEL}: ${MODEL}

    action function index() { 
        renderView("list")
    }


    action function list() { 
    }


    action function edit() {
        ${LOWER_MODEL} = ${MODEL}.find(params.id)
    }


    action function create() {
        ${LOWER_MODEL} = new ${MODEL}
        renderView("edit")
    }


    action function update() {
        if (params.commit == "Cancel") {
            redirect("list")

        } else if (params.commit == "Delete") {
            destroy()

        } else if (params.id) {
            ${LOWER_MODEL} = ${MODEL}.find(params.id)
            if (${LOWER_MODEL}.saveUpdate(params.${LOWER_MODEL})) {
                inform("${MODEL} updated successfully.")
                redirect("list")
            } else {
                /* Validation failed */
                renderView("edit")
            }

        } else {
            ${LOWER_MODEL} = new ${MODEL}(params.${LOWER_MODEL})
            if (${LOWER_MODEL}.save()) {
                inform("New ${LOWER_MODEL} created")
                redirect("list")
            } else {
                renderView("create")
            }
        }
    }


    action function destroy() {
        ${MODEL}.remove(params.id)
        inform("${MODEL} " + params.id + " removed")
        redirect("list")
    }

    NEXT_ACTION
}
'


    /*
     *****************  ScaffoldListView template ******************
     */
    public static const ScaffoldListView = 
'<h1>${MODEL} List</h1>

<% table(${MODEL}.findAll(), {click: "edit"}) %>
<br/>
<% buttonLink("New ${MODEL}", "create") %>
'


    /*
     *****************  ScaffoldEditView template ******************
     */
    public static const ScaffoldEditView = 
'<h1><%= (${LOWER_MODEL}.id) ? "Edit" : "Create" %> ${MODEL}</h1>

<% form(${LOWER_MODEL}) %>

    <table border="0">
    <% for each (name in ${MODEL}.columnNames) {
        if (name == "id") continue
        uname = name.toPascal()
    %>
        <tr><td>@@uname</td><td><% text(name) %></td></tr>
    <% } %>
    </table>

    <% button("OK", "commit") %>
    <% if (!${LOWER_MODEL}.id) button("Cancel", "commit") %>
    <% if (${LOWER_MODEL}.id) button("Delete", "commit") %>
<% endform() %>
'


    /*
     *****************  ScaffoldView template ******************
     */
    public static const ScaffoldView = 
'<h1>View "${CONTROLLER}/${VIEW}" for Model ${MODEL}</h1>}
<p>Edit in "views/${CONTROLLER}/${VIEW}.ejs"</p>
'


    /*
     ***********************  Action template ***********************
     */
    public static const Action = '
    action function ${NAME}() {
    }

'


    /*
     ***********************  Model template ***********************
     */
    public static const Model = 
'/*
 *  ${NAME}.es - ${NAME} Model Class
 */

public dynamic class ${NAME} implements Record {

    setup()

    function ${NAME}(fields: Object = null) {
        constructor(fields)
    }
}
'



    /*
     **************************** README template ***********************
     */
    public static const Readme = 
'
README - Overview of files and documentation generated by egen

These Directories are created via "egen generate ${NAME}:"

    bin                       Programs and scripts
    config                    Configuration files
    controllers               Controller source
    doc                       Documentation for the application
    logs                      Log files
    messages                  Internationalization messages
    models                    Database models
    src                       Extra application source
    test                      Test files
    views                     View source files
    views/layouts             View layout files
    web                       Public web directory
    .ejs                      State files used by egen
    .tmp                      Temporary files

These files are also created:

    src/appName.es            Primary application class 
    config/compiler.ecf       Compiler options
    config/config.ecf         General application configuration 
    config/database.ecf       Database connector configuration 
    config/view.ecf           View connector configuration 
    views/layouts/default.ejs Default template page for all views
'



    /*
     ***************************  View header and footer templates ******************
     */
    public static const ViewHeader = 
'

public dynamic class ${CONTROLLER}${VIEW}View extends View {
    function ${CONTROLLER}${VIEW}View(c: Controller) {
        super(c)
    }

    override public function render() {
'

    public static const ViewFooter = '
    }
}
'

    /*
     ***************************  Default Layout templates ******************
     */
    public static const DefaultLayout = 
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <title>@@title</title>
    <link rel="stylesheet" type="text/css" href="web/layout.css"/>
    <link rel="stylesheet" type="text/css" href="web/themes/default.css"/>
</head>

<body>
    <div class="top">
        <!-- Top section of all pages -->
    </div>

    <% flash("notice") %>
    <div class="content">
        <%@ content %>
    </div>

    <div class="bottom">
        <p class="footnote">Generated Layout. Modify in views/layouts/default.ejs</p>
    </div>
</body>
</html>
'


    /*
     ***************************  Default Layout Style templates ******************
     */
    public static const DefaultLayoutStyle = 
'
/*
 * 	Default layout style sheet
 */

html {
}

body {
	margin: 0;
	padding: 0;
}

div.top {
}

div.bottom {
}

div.content {
	padding: 0 10px 20px 20px;
}

div.contentLeft {
    width: 76%;
    min-height: 470px;
    /* Fix for IE */
    display: inline;
}

div.contentRight { 
    width: 20px;
    min-width: 150px;
    float: right;
}
'

    /*
     ***************************  Default Theme templates ******************
     */
    public static const DefaultTheme = 
'
/*
 * 	Default theme style sheet
 */

html {
}

body {
	font-family: Verdana, Arial, Helvetica, sans-serif; 
	font-size: small;
}

div.top {
	height: 125px;
	width: 900px;
    background-color: #284898;
	background-image: url("../images/banner.jpg");
	background-position: top right;
	background-repeat: no-repeat;
}

div.bottom {
	text-align: left;
}

div.content {
	color: #000000;
	background-color: #FFFFFF;
	text-decoration: none;
}

div.content a {
	color: #284898; 
	text-decoration: none; 
}

div.content a:hover { 
	color: #CC4477; 
	text-decoration: underline; 
}

div.content ul li {
	font-size: 100%;
	line-height: 140%; 
	margin: 0;
}

div.content ol li {
	font-size: 100%;
	line-height: 140%; 
	margin: 0;
	padding: 10px 0 0 0;
}

div.contentLeft {
	margin: 0;
	padding: 8px 10px 0 0;
	text-decoration: none;
	background-color: #FFFFFF;
}

div.contentRight { 
	margin: 0 0 0 10px;
	padding: 0 10px 100px 20px;
	text-decoration: none;
    background-color: #FFFFFF;
	font-size: 100%;
	line-height: 145%;
	text-decoration: none;
}

div.contentRight ul {
    padding: 0;
    margin: 0;
	list-style-type: none;
}

div.contentRight h2 {
	font-size: 120%;
	position: relative; 
	left: -11px;
	margin: 20px 0 4px 0px;
	padding: 2px 4px 3px 10px;
	color: #284898; 
	background-color: #f2f2f2;
}

p { 
	line-height: 130%; 
	margin-bottom: 5px;
}

h1 {
	padding: 20px 0 10px 0;
	margin: 0;
	color: #284898; 
	font-size: 150%;
	font-weight: bold;
}

h2 {
	padding: 12px 0 4px 0;
	margin: 0;
	color: #284898; 
	font-size: 120%;
}

h2.section {
	border-bottom: 3px solid #AAAAAA;
    border-color: #DDDDFF;
	padding: 14px 0 2px 0;
}

h3 {
    padding: 10px 0 4px 0;
    margin: 0px;
    color: #284898; 
    font-size: 100%;
    font-weight: bold;
}

h4 {
    font-size: 100%;
    font-style: italic;
    color: #284898; 
}

hr {
	margin: 0 0 0 0;
	border-style: none;
	border: 0;
	height: 0px;
	width: 0px;
}

ul { 
	font-size: 100%;
    margin-top: 2px;
    margin-bottom: 6px;
}

ul.bare {
	list-style: none;
	padding: 0;
	margin: 0;
}

ol { 
	font-size: 100%;
    margin-top: 2px;
    margin-bottom: 6px;
}

pre { 
	font-size: 110%;
	line-height: 140%; 
	font-family: Courier New, Courier, monospace; 
	padding: 10px 15px 10px 15px;
	margin: 10px 0 10px 0;
	background-color: #FFFFFF;
	color: #102070;
	-moz-border-radius: 0px;
	-webkit-border-radius: 0px;
	-webkit-box-shadow: 0px 0px 0px #FFF;
}

pre a {
	color: #FFFFFF;
}

pre a:link, pre a:visited, pre a:hover {
	color: #FFFFFF;
}

p.finePrint { 
	font-size: 80%;
	color: #313131; 
}

a:link { 
	color: #284898; 
	text-decoration: none; 
}

a:visited { 
	color: #000000;
	text-decoration: none; 
}

a:hover { 
	color: #CC4477; 
}

p.footnote {
	height: 50px;
	margin: 0;
	padding: 30px 0 0 20px;
	font-size: 85%;
	color: #AAAAAA; 
	line-height: 70%; 
}

p.footnote a:link, p.footnote a:visited { 
	color: #AAAAAA; 
	text-decoration: none;
}

p.footnote a:hover { 
	color: #FFFFFF; 
}

img.screen {
    margin: 8px 0 4px 0;
	border: 1px solid #AAA;
	-moz-border-radius: 7px;
	-webkit-border-radius: 7px;
	-webkit-box-shadow: 9px 9px 9px #AAA;
}

table {
	border: 1px solid #A0A0A0;
	border-collapse: collapse;
	border-spacing: 0px;
	-webkit-box-shadow: 4px 4px 8px #888;
    margin-bottom: 15px;
}

td {
	padding: 4px 8px 4px 8px;
	border: 1px solid #A0A0A0;
	border-collapse: collapse;
	border-spacing: 0px;
}


th {
    padding: 6px 10px 6px 6px;
    background-color: #70A0D0;
    background: -webkit-gradient(linear, left bottom, left top, from(rgba(90,80,210,.75)), color-stop(1, #cff));
	border: 1px solid #e060B0;
	color: #183878;
    font-weight: bold;
    font-size: 100%;
	text-align: left;
}

.oddRow {
    background: -webkit-gradient(linear, left bottom, left top, from(rgba(200,210,250,.85)), color-stop(1, #eff));
    color: #FF0000;
}

.evenRow {
    xbackground-color: #00FF00;
}

td a {
	text-decoration: none;
}

tr:hover { 
	background: #F2F2F2; 
}

button {
	-webkit-box-shadow: 4px 4px 8px #888;
}
'


/* End of class Templates */
}


/*
 *  Main program
 */
var egen: Egen = new Egen
egen.loadDefaults()

try {
    if (!egen.parseArgs(App.args)) {
        egen.usage()
    }
}
catch (e) {
    print(e)
    App.exit(2)
}


/*
 *  @copy   default
 *
 *  Copyright (c) Embedthis Software LLC, 2003-2009. All Rights Reserved.
 *  Copyright (c) Michael O'Brien, 1993-2009. All Rights Reserved.
 *
 *  This software is distributed under commercial and open source licenses.
 *  You may use the GPL open source license described below or you may acquire
 *  a commercial license from Embedthis Software. You agree to be fully bound
 *  by the terms of either license. Consult the LICENSE.TXT distributed with
 *  this software for full details.
 *
 *  This software is open source; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version. See the GNU General Public License for more
 *  details at: http://www.embedthis.com/downloads/gplLicense.html
 *
 *  This program is distributed WITHOUT ANY WARRANTY; without even the
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 *  This GPL license does NOT permit incorporating this software into
 *  proprietary programs. If you are unable to comply with the GPL, you must
 *  acquire a commercial license to use this software. Commercial licenses
 *  for this software and support services are available from Embedthis
 *  Software at http://www.embedthis.com
 *
 *  @end
 */
/************************************************************************/
/*
 *  End of file "../es/web/utils/egen.es"
 */
/************************************************************************/

