diff --git a/app.js b/app.js index 0c76edd15..b9b4ae1b9 100644 --- a/app.js +++ b/app.js @@ -98,8 +98,9 @@ function bootApplication(next) { // Load placeholder, replaced later app.use(app.mwHelpers.staticMiddleware('')); - // Media paths + // Core static paths app.use(express["static"](path + '/media', {maxAge: 86400000})); + app.use(express["static"](path + '/lib/client/js', {maxAge: 86400000})); // connect-form app.use(form({ diff --git a/bin/calipso b/bin/calipso index ca4510e97..5c5b81655 100755 --- a/bin/calipso +++ b/bin/calipso @@ -65,12 +65,11 @@ function runLauncher(appLauncher) { // Check if this is a calipso src folder if(isLibrary() && !appLauncher.src) { - console.log('\x1b[1mTo run this from the src folder, you need to add an "-s true" parameter, or please run from an empty directory or an installed calipso site\x1b[0m\r\n'); - return; + console.log('\r\nWARNING:'.yellow.bold + ' You are running this from a Calipso source folder.'.white.bold); } // Check if this is a calipso site - if(!isCalipso() && appLauncher.command != 'site' && !appLauncher.src) { + if(!isCalipso() && appLauncher.command != 'site' && !isLibrary()) { console.log('\x1b[1mThis is not a Calipso site - you must run:\x1b[0m calipso site SiteName\r\n'); return; } @@ -95,7 +94,7 @@ function runLauncher(appLauncher) { var modules = require('lib/cli/Modules') modules.moduleRouter(path,appLauncher.script.params,true,function(err) { if(err) { - console.log("\r\n" + err.message.red + "\r\n"); + console.log("\r\n" + err.message.red.bold + "\r\n"); } process.exit(); }); @@ -108,7 +107,7 @@ function runLauncher(appLauncher) { var themes = require('lib/cli/Themes') themes.themeRouter(path,appLauncher.script.params,true,function(err) { if(err) { - console.log("\r\n" + err.message.red + "\r\n"); + console.log("\r\n" + err.message.red.bold + "\r\n"); } process.exit(); }); @@ -230,7 +229,7 @@ function createApplicationAt(path) { mkdir(path + '/tmp',self.parallel()); copy(calipsoPath + '/app-cluster.js',path + '/',self.parallel()); copy(calipsoPath + '/app.js',path + '/',self.parallel()); - copy(calipsoPath + '/package.json',path + '/',self.parallel()); + copy(calipsoPath + '/package.json',path + '/',self.parallel()); copy(calipsoPath + '/logo.js',path + '/',self.parallel()); }, function done() { diff --git a/lib/Helpers.js b/lib/Helpers.js index 9fb8d7059..916f7226f 100644 --- a/lib/Helpers.js +++ b/lib/Helpers.js @@ -36,7 +36,7 @@ exports = module.exports = { request: function(req, res, calipso){ return req; }, - + /** * Config shortcut */ @@ -156,16 +156,14 @@ exports = module.exports = { }; }, - + /** * Retrieves the params parsed during module routing */ - getParams: function(req, res, calipso) { - + getParams: function(req, res, calipso) { return function() { return res.params; } - }, /** @@ -191,32 +189,30 @@ exports = module.exports = { return urlFrags.join('-'); }, - /** - * TODO Include a script in the response. - * Thoughts ... What this will actually do is add the file to a internal list. - * It will check if any of the files are different to the last request, if so, - * it will minify the contents of all the files, write to a temp js file, and reference - * that in the response (e.g. in the header of the template via another helper function). - * If nothing has changed it will just include the link to the previously generated file? - */ - includeScript: function(req, res, calipso){ - - return function(block) { - // TODO - not yet implemented - }; + addScript: function(req, res, calipso) { + return function(options) { + res.client.addScript(options); + } }, - /** - * TODO Retrieve the concatenated / minified js scripts to add to template. - */ - getScripts: function(req, res, calipso){ + getScripts: function(req, res, calipso) { + return function(next) { + res.client.listScripts(next); + } + }, - return function() { - // TODO - not yet implemented + addStyle: function(req, res, calipso) { + return function(options) { + res.client.addStyle(options); + } + }, - }; + getStyles: function(req, res, calipso) { + return function(next) { + res.client.listStyles(next); + } }, /** diff --git a/lib/Theme.js b/lib/Theme.js index 2e5032e15..df709c370 100644 --- a/lib/Theme.js +++ b/lib/Theme.js @@ -109,9 +109,9 @@ module.exports.Theme = function(theme, next) { // Scan through each layout var layout = res.layout ? res.layout : "default"; - + calipso.silly("Using layout " + layout); - + if(!theme.config.layouts[layout]) { layout = "default"; if(!theme.config.layouts[layout]) { @@ -120,8 +120,8 @@ module.exports.Theme = function(theme, next) { return; } } - - + + processTheme(req,res,layout,theme,function(err) { // If something went wrong ... @@ -129,7 +129,7 @@ module.exports.Theme = function(theme, next) { next(err,null); return; } - + // Now, process the layout template itself var themeOptions = createOptions(req,res,res.bufferedOutput); @@ -225,7 +225,7 @@ function processSection(req, res, section, sectionPath, layoutConfig, theme, nex if(typeof sectionCacheFn === "function") { sectionCacheFn(req,themeOptions,function(err,fnOptions) { - + if(err) { err.xMessage = "Issue executing the theme function for section " + section + ", check " + sectionPath.replace(".","/") + ".js"; localNext(err); @@ -287,25 +287,25 @@ function processTheme(req, res, layout, theme, next) { var copyConfig = theme.config.layouts[layoutConfig.copyFrom].layout; layoutConfig.sections = layoutConfig.sections || {}; - + // Copy over any missing sections from default for(var copySection in copyConfig.sections) { - + var sectionExists = layoutConfig.sections && layoutConfig.sections[copySection]; - var disable = layoutConfig.sections && layoutConfig.sections[copySection] && layoutConfig.sections[copySection].disable; + var disable = layoutConfig.sections && layoutConfig.sections[copySection] && layoutConfig.sections[copySection].disable; if(!sectionExists && !disable) { layoutConfig.sections[copySection] = copyConfig.sections[copySection]; layoutConfig.sections[copySection].layout = "default"; // Flag override as below } - + } } - + // Create a section array var sections = []; for(section in layoutConfig.sections) { - disable = layoutConfig.sections[section].disable; + disable = layoutConfig.sections[section].disable; if(!disable) { sections.push(section); } @@ -318,11 +318,11 @@ function processTheme(req, res, layout, theme, next) { // and better ability to debug function localNext(err) { totalDone += 1; - + if(totalDone == totalCount) { next(); } - + } for(section in layoutConfig.sections) { @@ -333,13 +333,13 @@ function processTheme(req, res, layout, theme, next) { var sectionPath = layoutOverride ? layoutOverride + "." + section : layout + "." + section; var cache = layoutConfig.sections[section].cache; var params = layoutConfig.sections[section].varyParams; - var cacheEnabled = calipso.config.get('performance:cache:enabled'); + var cacheEnabled = calipso.config.get('performance:cache:enabled'); var isAdmin = req.session.user && req.session.user.isAdmin; - - disable = layoutConfig.sections[section].disable; + + disable = layoutConfig.sections[section].disable; calipso.silly("Processing " + section + " ..."); - + // Sections are cacheable if(!disable) { if(cache && cacheEnabled && !isAdmin) { @@ -436,7 +436,7 @@ function cacheTheme(themeConfig, themePath, next) { // Add the templates - for(var section in layoutConfig.sections) { + for(var section in layoutConfig.sections) { var template = layoutConfig.sections[section].template; if(template) { templates.push({ @@ -532,7 +532,7 @@ function loadTemplate(templateCache, template, themePath, next) { path.exists(templatePath,function(exists) { if(exists) { - + fs.readFile(templatePath, 'utf8', function(err,data) { if(!err) { @@ -556,14 +556,14 @@ function loadTemplate(templateCache, template, themePath, next) { if(exists) { try { templateCache[template.name].fn = require(templateFnPath); - } catch(ex) { + } catch(ex) { next(ex); return; } } next(null,templateCache); }); - } else { + } else { next(err); } }); diff --git a/lib/Utils.js b/lib/Utils.js index 2fb0a0e34..bde9a61a0 100644 --- a/lib/Utils.js +++ b/lib/Utils.js @@ -1,7 +1,7 @@ /** * General utility methods */ - + exports = module.exports = { /** * Basically like getProperty, different return @@ -55,4 +55,4 @@ exports = module.exports = { } return obj; } -} \ No newline at end of file +} diff --git a/lib/calipso.js b/lib/calipso.js index 74011e586..c038446b0 100644 --- a/lib/calipso.js +++ b/lib/calipso.js @@ -52,7 +52,8 @@ var app, prettyDate: require(path.join(rootpath, 'utils/prettyDate.js')), crypto: require(path.join(rootpath, 'utils/crypto.js')), connect: require('connect'), - _:require('underscore') + _:require('underscore'), + async: require('async') }, sessionCache: {}, dynamicHelpers: require('./Helpers'), @@ -61,7 +62,7 @@ var app, for(var helper in this.dynamicHelpers) { req.helpers[helper] = this.dynamicHelpers[helper](req, res, this); } - }, + }, mongoConnect: mongoConnect, reloadConfig: reloadConfig, // Track running MR (Map Reduce) operations @@ -70,9 +71,9 @@ var app, theme: {}, // Holds global config data data: {}, - modules: {}, + modules: {}, // Core libraries - date: require('./Date').CalipsoDate, + date: require('./Date').CalipsoDate, table: require('./Table').CalipsoTable, link: require('./Link').CalipsoLink, menu: require('./Menu'), @@ -94,13 +95,13 @@ var app, // Set app and config references var app = require(path.join(rootpath, 'app')).app; - + app.calipso = app; calipso.app = app; - + // Load the calipso package.json into about loadAbout(app, rootpath, 'package.json'); - + calipso.config = app.config; // Configure the cache @@ -111,12 +112,12 @@ var app, initCallback(); }; - // Create our calipso event emitter + // Create our calipso event emitter calipso.e = new calipso.event.CalipsoEventEmitter({ hook:{ name:calipso.config.get('server:hookio:name'), 'hook-host':calipso.config.get('server:hookio:host'), - 'hook-port': calipso.config.get('server:hookio:port'), + 'hook-port': calipso.config.get('server:hookio:port'), debug: calipso.config.get('server:hookio:debug') } }); @@ -138,6 +139,10 @@ var app, secondary:new calipso.menu.CalipsoMenu('secondary','name','root',{cls:'secondary'})}; + // Initialise our clientJS library linked to this request + var Client = require('./client/Client'); + res.client = new Client(); + // Initialise helpers - first pass calipso.getDynamicHelpers(req, res); @@ -147,7 +152,7 @@ var app, // Best to parse the form very early in the chain if(req.form) { - calipso.form.process(req, function() { + calipso.form.process(req, function() { // Route the modules eventRouteModules(req, res, next); @@ -187,16 +192,16 @@ function initialiseCalipso(reloadConfig) { calipso.e.init(); // Configure the logging - calipso.logging.configureLogging(); + calipso.logging.configureLogging(); // Check / Connect Mongo mongoConnect(calipso.config.get('database:uri'),false,function(err,connected) { - + if(err) { console.log("There was an error connecting to the database: " + err.message); process.exit(); } - + // Load all the themes loadThemes(function() { @@ -212,7 +217,7 @@ function initialiseCalipso(reloadConfig) { }); }); - + }); } @@ -227,7 +232,7 @@ function eventRouteModules(req, res, next) { // Ignore static content // TODO : Make this more connect friendly or at least configurable if(req.url.match(/^\/images|^\/js|^\/css|^\/favicon.ico/)) { - return next(); + return next(); } req.timeStart = new Date(); @@ -348,7 +353,7 @@ function doLastModules(req, res, next) { } } - + // Execute their routing functions calipso.lib.step( @@ -368,7 +373,7 @@ function doLastModules(req, res, next) { } next(); - + } ); @@ -378,7 +383,7 @@ function doLastModules(req, res, next) { * Standard response to all modules completing their routing */ function doResponse(req, res, next) { - + // If we are in install mode, and are not in the installation process, then redirect if(!calipso.config.get('installed') && !req.url.match(/^\/admin\/install/)) { calipso.silly("Redirecting to admin/install ..."); @@ -398,7 +403,7 @@ function doResponse(req, res, next) { if(res.statusCode === 404 || res.statusCode === 500 || res.statusCode === 200) { calipso.theme.render(req, res, function(err, content) { - + if(err) { // Something went wrong at the layout, cannot use layout to render. @@ -429,19 +434,19 @@ function doResponse(req, res, next) { } /** -* Called both via a hook.io event as +* Called both via a hook.io event as * well as via the server that initiated it. */ -function reloadConfig(event, data, next) { - +function reloadConfig(event, data, next) { + // Create a callback - calipso.initCallback = function (err) { + calipso.initCallback = function (err) { // If called via event emitter rather than hook if(typeof next === "function") next(err); } - initialiseCalipso(true); + initialiseCalipso(true); return; - + } /** @@ -453,7 +458,7 @@ function initModules() { // Reset calipso.initComplete = false; - + // Create a list of all our enabled modules var enabledModules = []; for(var module in calipso.modules) { @@ -936,7 +941,7 @@ function configureTheme(next, overrideTheme) { var defaultTheme = calipso.config.get("theme:default"); if(theme) { - + require('./Theme').Theme(theme,function(loadedTheme) { calipso.theme = loadedTheme; @@ -998,30 +1003,30 @@ function configureTheme(next, overrideTheme) { */ function mongoConnect(dbUri,checkInstalling,next) { - // Test the mongodb configuration + // Test the mongodb configuration var isInstalled = calipso.config.get('installed'); - + // If first option is callback, ste dbUri to config value if(typeof dbUri === "function") { next = dbUri; dbUri = calipso.config.get('database:uri'); checkInstalling = false; } - + // Check we are installing ... - if(checkInstalling) { - var db = mongoose.createConnection(dbUri,function(err) { - next(err,false); + if(checkInstalling) { + var db = mongoose.createConnection(dbUri,function(err) { + next(err,false); }); - return; + return; } - + if(isInstalled) { - + // Always disconnect first just in case any left overs from installation mongoose.disconnect(function() { - - mongoose.connect(dbUri,function(err) { + + mongoose.connect(dbUri,function(err) { if (err) { // Add additional detail to know errors switch (err.code) { @@ -1032,11 +1037,11 @@ function mongoConnect(dbUri,checkInstalling,next) { calipso.error("Fatal unknown error: ".magenta + err); } mongoose.disconnect(function() { - next(err); - }); - } else { - calipso.silly("Database connection to " + dbUri + " was successful."); - + next(err); + }); + } else { + calipso.silly("Database connection to " + dbUri + " was successful."); + // Replace the inmemory session with mongodb backed one var foundMiddleware = false, mw; calipso.app.stack.forEach(function(middleware,key) { @@ -1044,21 +1049,21 @@ function mongoConnect(dbUri,checkInstalling,next) { foundMiddleware = true; mw = calipso.lib.express.session({ secret: calipso.config.get('session:secret') , store: mongoStore({ url: calipso.config.get('database:uri') }) }); mw.tag = 'session' - calipso.app.stack[key].handle = mw; + calipso.app.stack[key].handle = mw; } }); - + if(!foundMiddleware) { return next(new Error("Unable to load the MongoDB backed session, please check your session and db configuration"),false); } - - return next(null,true); - + + return next(null,true); + } }); }); - + } else { calipso.silly("Database connection not attempted to " + dbUri + " as in installation mode.") next(null,false); diff --git a/lib/cli/Download.js b/lib/cli/Download.js index a98e25670..9616854a5 100644 --- a/lib/cli/Download.js +++ b/lib/cli/Download.js @@ -15,12 +15,16 @@ var rootpath = process.cwd() + '/', path = require('path') calipso = require(path.join(rootpath, 'lib/calipso')), - Api = require(path.join(rootpath, 'lib/cli/RepoApi')), + api = require(path.join(rootpath, 'lib/cli/RepoApi')), + moduleCli = require(path.join(rootpath, 'lib/cli/Modules')), exec = require('child_process').exec, sys = require('sys'), colors = require('colors'), sys = require('sys'), util = require('util'), + zip = require('zipfile'), + fs = require('fs'), + rimraf = require('rimraf') semver = require('semver'); /** @@ -45,24 +49,21 @@ function download(type,fromUrl,toPath,cli,next) { if(err) { next(err); } else { - processDownload(type,path,next); + processDownload(type, path, next); } } if(fromUrl.match(/^http.*/)) { - downloadUrl(fromUrl,toPath,cli,localNext); - return; + return downloadUrl(fromUrl, toPath, cli, localNext); } if(fromUrl.match(/^(.*)\/(.*)$/)) { - downloadGithub(fromUrl,toPath,cli,localNext); - return; + return downloadGithub(fromUrl, toPath, cli, localNext); } // Otherwise assume repo - append type fromUrl = "module/" + fromUrl; - downloadRepo(type,fromUrl,toPath,cli,localNext); - return; + return downloadRepo(type, fromUrl, toPath, cli, localNext); } @@ -84,14 +85,14 @@ function downloadRepo(type,fromUrl,toPath,cli,next) { constructRepoUrl(repoName,version,function(err,url) { if(!err && url) { - download(type,url,toPath,cli,next); + return downloadGithub(url, toPath, cli, next); } else { next(err); } }); } else { - next(new Error('B You need to provide a calipso repository project name - e.g. ElasticSearch')); + next(new Error('You need to provide a calipso repository project name - e.g. ElasticSearch')); } } @@ -159,7 +160,7 @@ function downloadUrl(fromUrl,toPath,cli,next) { } -function downloadFile(url,fileName,toPath,next) { +function downloadFile(url, fileName, toPath, next) { var u = require('url'), fs = require('fs'), path = require('path'); var parts = u.parse(url); @@ -180,7 +181,8 @@ function downloadFile(url,fileName,toPath,next) { } } - console.log("Downloading file:".cyan); + console.log("\r\nResolving file location, and downloading ...".cyan); + client.get({ host: parts.hostname, port: parts.port, path: parts.pathname }, function(res) { if(res.statusCode === 302) { @@ -272,17 +274,17 @@ function constructGithubUrl(userProject,tag) { /** * Create a url based on the repository details */ -function constructRepoUrl(repoName,version,next) { +function constructRepoUrl(repoName, version, next) { // Create our API wrapper - var repo = new Api("http://localhost:3001/repo/api/"); + var repo = new api(); // Variables for lookup var type = repoName.split("/")[0]; var name = repoName.split("/")[1]; var version = version || "master"; - repo.list({type:type,name:name,version:version},function(err,r) { + repo.get({type:type,name:name,version:version},function(err,r) { if(err || !r) { next(err); } else { @@ -294,7 +296,7 @@ function constructRepoUrl(repoName,version,next) { if(v.version === version) { next(null,v.url); versionMatched = true; - console.log("Resolved ".cyan.bold + repoName.white + "@".white + v.version.white + " to ".cyan.bold + v.url + (v.version === "master" ? "" : "@" + v.version)); + console.log("Resolved ".cyan.bold + name.green.bold + "@".white + v.version.green.bold + " to github repo ".cyan.bold + v.url + (v.version === "master" ? "" : "@" + v.version)); return; } }); @@ -302,7 +304,15 @@ function constructRepoUrl(repoName,version,next) { next(new Error("Unable to locate the version specified.")); } } else { - next(new Error("There was an error locating that module, multiple entries returned")); + if(r.length === 0) { + console.log("\r\nNo entries found, searching the repository for something along the same lines ...".white.bold); + moduleCli.findModule(["",name], true, function(err,data) { + // Throw a blank error + next(new Error("Please try again using one of the module names listed above, or perhaps this is the inspiration for you to build one? :)")); + }); + } else { + next(new Error("There was an error locating that module, " + r.length.toString().red.bold + " entries returned".red)); + } } } }); @@ -313,7 +323,7 @@ function constructRepoUrl(repoName,version,next) { * Process a downloaded module, place into modules folder */ -function processDownload(type,file,next) { +function processDownload(type, file, next) { // Checks var isValid; @@ -323,7 +333,14 @@ function processDownload(type,file,next) { // #2 - unzip it, check contents if(isValid) { - unzipDownload(type,file,next); + unzipDownload(type, file, function(err, tmpName, tmpFolder) { + // Intercept to enable a cleanup + if(err) { + fs.unlinkSync(file); + err.message = err.message + "The downloaded file has been deleted."; + } + next(err, tmpName, tmpFolder); + }); } else { next(new Error("The file downloaded must be a valid zip archive.")); } @@ -336,16 +353,17 @@ function processDownload(type,file,next) { function unzipDownload(type, file, callback) { - var zip = require('zipfile'), - fs = require('fs'), - rimraf = require('rimraf'), - path = require('path'); - - var zf = new zip.ZipFile(file), + var zf, baseFolder, tmpFolder, tmpName; + try { + zf = new zip.ZipFile(file) + } catch(ex) { + return callback(ex); + } + zf.names.forEach(function(name) { // First result is the basefolder @@ -378,57 +396,57 @@ function unzipDownload(type, file, callback) { if(path.existsSync(tmpFolder)) { rimraf.sync(tmpFolder); } - + // First run through and create every directory synchronously - var folders = []; - zf.names.forEach(function(name) { - folders.push(name.replace(baseFolder,"").split("/")); + var folders = []; + zf.names.forEach(function(name) { + folders.push(name.replace(baseFolder,"").split("/")); }); - - folders.forEach(function(folderList) { - var folder = tmpFolder; + + folders.forEach(function(folderList) { + var folder = tmpFolder; folderList.forEach(function (currFolder) { - var isDir = (!path.extname(currFolder) || currFolder[0] === '.'); + var isDir = (!path.extname(currFolder) || currFolder[0] === '.'); folder = path.join(folder, currFolder); if(isDir) { dirExists(folder); - } - }); - }); - + } + }); + }); + // Now, lets extract all the files - var remaining = zf.names.length; - - zf.names.forEach(function(name) { - + var remaining = zf.names.length; + + zf.names.forEach(function(name) { + var dest = path.join( tmpFolder, name.replace(baseFolder,"") ); // Skip directories, hiddens. - var isDir = (!path.extname(name) || name[0] === '.' || name[name.length] === "/"); + var isDir = (!path.extname(name) || name[0] === '.' || name[name.length] === "/"); if (isDir) { remaining--; - if (!remaining) return callback(null); - } else { - zf.readFile(name, function(err, buff) { + if (!remaining) return callback(null); + } else { + zf.readFile(name, function(err, buff) { if (err) return callback(err); - fs.open(dest, 'w', 0644, function(err, fd) { + fs.open(dest, 'w', 0644, function(err, fd) { if(err) { if(err.code !== "EISDIR") { // fs.unlinkSync(file); - return callback(err); + return callback(err); } else { remaining--; - } - + } + } else { fs.write(fd, buff, 0, buff.length, null, function(err) { if (err) { fs.unlinkSync(file); - return callback(err); - } + return callback(err); + } fs.close(fd, function(err) { if (err) return callback(err); remaining--; @@ -437,13 +455,13 @@ function unzipDownload(type, file, callback) { callback(null,tmpName,tmpFolder); } }); - }); + }); } }); - }); + }); } }); - + } else { if(type === 'module') { @@ -457,12 +475,12 @@ function unzipDownload(type, file, callback) { } /** - * + * */ function dirExists(dest) { - + var fs = require('fs'); - + // Try to create the folder try { fs.mkdirSync(dest, 0755) @@ -474,7 +492,7 @@ function dirExists(dest) { return false; } } - + return true; } diff --git a/lib/cli/Modules.js b/lib/cli/Modules.js index 10e1af4d7..b96634ae4 100644 --- a/lib/cli/Modules.js +++ b/lib/cli/Modules.js @@ -23,7 +23,8 @@ var rootpath = process.cwd() + '/', sys = require('sys'), colors = require('colors'), semver = require('semver'), - download = require('lib/cli/Download'); + api = require(path.join(rootpath, 'lib/cli/RepoApi')), + download = require(path.join(rootpath, 'lib/cli/Download')); /** @@ -51,24 +52,70 @@ exports.moduleRouter = function(path,options,cli,next) { case 'disable': toggleModule(false,options,cli,next); break; + case 'install': // Default is github + downloadModule(options, cli, next); + break; case 'download': // Default is github - var toPath = calipso.app.path + "/modules/downloaded/"; - var fromUrl = options[1]; - download('module',fromUrl,toPath,cli,function(err,moduleName,path) { - if(err) { - next(err); - } else { - installViaNpm(moduleName,path,next); - } - }); + downloadModule(options, cli, next); + break; + case 'find': + findModule(options, cli, next) break; - default: next(new Error("You need to specify a valid command, please refer to the help available for valid options.")); } } +/** + *Find module + */ + +function findModule(options, cli, next) { + + var search = options[1]; // Second parameter is our query + var searchRegex = new RegExp(search,"ig"); + search = search.replace(/\*/g,""); + + var repo = new api(); + repo.find('module', options, function(err, data) { + console.log(""); + data.forEach(function(module) { + var versionString = ""; + module.versions.forEach(function(version) { + versionString += " - [" + version.version + "] : " + version.url + "\r\n" + }); + var description = module.description.replace(searchRegex,search.yellow.bold); + var author = " - [Author] ".cyan + module.author.cyan.bold + "\r\n"; + console.log(module.name.white.bold + "\r\n" + description.white + "\r\n" + author + versionString.cyan); + }); + console.log(""); + console.log("To install a module, use: ".white + "\r\n"); + console.log(" calipso modules download".cyan.bold + " ModuleName".green.bold + " [for latest version]".grey); + console.log(" calipso modules download".cyan.bold + " repo/project@version".green.bold + " [for specific version]".grey); + console.log(""); + next(); + }); + +} +exports.findModule = findModule; + + +/** + * Try to download a module + */ +function downloadModule(options, cli, next) { + var toPath = calipso.app.path + "/modules/downloaded/"; + var fromUrl = options[1]; + download('module', fromUrl, toPath, cli, function(err,moduleName,path) { + if(err) { + next(err); + } else { + installViaNpm(moduleName,path,next); + } + }); +} + /** * Show the list of currently installed modules - highlight those with issues / updates? */ @@ -95,7 +142,7 @@ function listModules(options,cli,next) { /** * Enable or disable a module */ -function toggleModule(enabled,options,cli,next) { +function toggleModule(enabled, options, cli, next) { // Get the module name var moduleName = options[1]; @@ -106,6 +153,7 @@ function toggleModule(enabled,options,cli,next) { // Split module / version var moduleVersion = moduleName.split('@')[1] || ""; var moduleName = moduleName.split('@')[0]; + var configKey = 'modules:' + moduleName; // Locate the module var installedModule = calipso.modules[moduleName]; @@ -114,30 +162,14 @@ function toggleModule(enabled,options,cli,next) { // Assume that we want to re-install the dependencies via NPM if(installed) { - // Re-retrieve our object - var AppConfig = calipso.lib.mongoose.model('AppConfig'); - AppConfig.findOne({}, function(err, c) { - - // Updates to mongo - var modules = []; - c.modules.forEach(function(value,key) { - if(value.name === moduleName) { - value.enabled = enabled; - } - modules.push(value); - }); - c.modules = modules; - - c.save(function(err) { - if (err) { - next(new Error("You must specify a module name.")); - } else { - console.log("Module " + moduleName.green.bold + " is now " + (enabled ? "enabled".green.bold : "disabled".red.bold) + "."); - next(); - } - }); - - }); + if (!calipso.config.get(configKey)) { + next(new Error("You must specify a valid module name, try 'modules list' first.")); + } else { + calipso.config.setSave(configKey + ':enabled', enabled, function(err) { + console.log("Module " + moduleName.green.bold + " is now " + (enabled ? "enabled".green.bold : "disabled".red.bold) + "."); + next(); + }); + } } else { diff --git a/lib/cli/RepoApi.js b/lib/cli/RepoApi.js index e2a96181d..e75e59e5d 100644 --- a/lib/cli/RepoApi.js +++ b/lib/cli/RepoApi.js @@ -20,34 +20,59 @@ var rootpath = process.cwd() + '/', function Api(url) { - this.url = url || "http://calip.so/repo/api/"; + + this.url = url || calipso.config.get('calipso:repo:url'); + this.url = this.url + (calipso.lib._.last(this.url) !== "/" ? "/" : ""); + +} + +/** + * Retrieve a specific item from the repo + * @options + * type : module, theme, profile + * name : name of module + */ +Api.prototype.get = function(options, next) { + + // Construct the url based on the options + var url = this.url + path.join('get', + (options.type ? options.type : "all"), + (options.name ? options.name : "*"), + (options.version ? options.version : "master")); + + console.log(""); + console.log("Asking repository for " + options.name.green.bold + "@".white + options.version.green.bold + " ..."); + + // Simple get request + get(url, next); + } + /** * Wrapper for Repo List * @options * type : module, theme, profile * name : name of module */ -Api.prototype.list = function(options,next) { +Api.prototype.find = function(type, options, next) { // Construct the url based on the options - var url = this.url - + (options.type ? options.type : "all") - + "/" + (options.name ? options.name : "*") - + "/" + (options.version ? options.version : "master"); + var searchString = options[1]; + var url = this.url + path.join('find', type, searchString); - console.dir(url); + console.log(""); + console.log("Searching repository for " + searchString.green.bold + " ..."); // Simple get request - get(url,next); + get(url, next); } /** * Simple wrapper for get requests */ -function get(url,cb) { +function get(url, cb) { // Parse the url to determine request type var parts = require('url').parse(url); @@ -72,7 +97,6 @@ function get(url,cb) { res.on('end', function() { try { - console.dir(data); var json = JSON.parse(data); cb(null,json); } catch(ex) { diff --git a/lib/client/Client.js b/lib/client/Client.js new file mode 100644 index 000000000..4fba3c701 --- /dev/null +++ b/lib/client/Client.js @@ -0,0 +1,206 @@ +/** + * This library provides a wrapper to enable modules to load javascript and styles into an + * array, that can then be rendered into a theme in the appropriate location. + * + * Styles and JS are all indexed by key, so you could write functions that over-wrote them in the theme as the + * last update will always stick. + * + */ + + var rootpath = process.cwd() + '/', + path = require('path'), + calipso = require(path.join(rootpath, 'lib/calipso')), + fs = require('fs'); + +/** + * Client Object - handle CSS and JS loading for modules out to themes + */ +var Client = module.exports = function Client(options) { + + this.options = options || { + 'minified-script': 'media/calipso-main' + }; + + this.scripts = []; + this.styles = []; + + // Shortcuts to core, must be included somewhere (module or theme) to be rendered + this.coreScripts = { + 'jquery': {key:'jquery', url:'jquery-1.6.4.min.js', weight: -100}, + 'calipso': {key:'calipso', url:'calipso.js', weight: -50} + } + +} + +Client.prototype.addScript = function(options) { + + var self = this; + + // Convert our options over with flexible defaults + if(typeof options === "string") { + if(this.coreScripts[options]) { + options = this.coreScripts[options]; + } else { + options = {name:options, url:options, weight: 0}; + } + } + if(!options.name) options.name = options.url; + + // Add the script + self._add('scripts',options.name,options); + +} + +/** + * Create simple list of all client JS + */ +Client.prototype.listScripts = function(next) { + + // TODO - this should be updated to use LABjs by default (?) + var self = this; + var output = "" + self.scripts.forEach(function(value) { + output += '\r\n'; + }); + output += "" + next(null,output); + +} + +Client.prototype.addStyle = function(options) { + + var self = this; + + // Convert our options over with flexible defaults + if(typeof options === "string") { + options = {name:options, url:options, weight: 0}; + } + if(!options.name) options.name = options.url; + + // Add the script + self._add('styles',options.name,options); + +} + +/** + * Compile together all of the client side scripts + */ +Client.prototype.listStyles = function(next) { + + // TODO - this should be updated to use LABjs by default (?) + var self = this; + var output = "" + + self.styles.forEach(function(value) { + output += '\r\n'; + }); + output += "" + next(null,output); + +} + + + +/** + * Helper to add unique elements to an array + */ +Client.prototype._add = function(arrName, name, options) { + + var self = this; + self[arrName] = self[arrName] || []; + + // Find first match + var found = calipso.lib._.find(self[arrName],function(context, value, index, list) { + return (value.name && value.name === name) ? true : false; + }); + + if(found) { + // Replace - this means we never get duplicates (e.g. of JQuery, JQueryUI) + self[arrName].splice(found, 1, options); + } else { + // Push + self[arrName].push(options); + } + + // Sort - TODO, this can probably be more efficient by placing the new item smarter + self[arrName].sort(function(a,b) { + return a.weight > b.weight; + }); + +} + + +/** + * Compile together all of the client side scripts + * TODO - this is currently not used, needs to be worked on and thought through. + * +Client.prototype.compile = function(next) { + + var self = this; + + try { + + var scriptFile = path.join(rootpath,self.options.script), + scriptStream = fs.createWriteStream(scriptFile, {'flags': 'a'}); + + } catch(ex) { + + console.dir(ex); + + } + + var grabFile = function(item, callback) { + + // TODO - allow referential + var filePath = path.join(rootpath, item.url); + + // Check to see if the file has changed + var stat = fs.lstatSync(filePath); + + fs.readFile(filePath, 'utf8', function(err, contents) { + + if(err) { + + return callback(new Error("Unable to locate file for ClientJS creation: " + filePath)); + + } else { + + var drain; + drain = scriptStream.write(contents); + callback(null, stat.mtime); + + } + }); + + } + + // Callback wrapper to close the streams + var done = function(err, data) { + scriptStream.end(); + next(err, data); + } + + // var contents = fs.readFileSync(config.out, 'utf8'); + calipso.lib.async.mapSeries(self.scripts, grabFile, function(err, scripts) { + + if(err) return done(err); + + var reduce = function(context, memo, value, index, list) { + return (value > memo) ? value : memo; + }; + + var maxmtime = calipso.lib._.reduce(scripts, reduce); + + console.dir(maxmtime); + + var script = '' + + done(null, script); + + }) + +} +**/ + + + diff --git a/lib/client/js/README b/lib/client/js/README new file mode 100644 index 000000000..e69de29bb diff --git a/lib/client/js/calipso.js b/lib/client/js/calipso.js new file mode 100644 index 000000000..b1071d424 --- /dev/null +++ b/lib/client/js/calipso.js @@ -0,0 +1,15 @@ +/** + * Calipso client side JS library + * + * This library is included in every page request, and should be used to manage all client side + * Javascript libraries. + * + */ +var Calipso = function () {}; + +/** + * Require a library + */ +Calipso.prototype.require(jsFile) { + +} diff --git a/lib/client/js/jquery-1.6.4.min.js b/lib/client/js/jquery-1.6.4.min.js new file mode 100644 index 000000000..628ed9b31 --- /dev/null +++ b/lib/client/js/jquery-1.6.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */ +(function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+"
"),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;it |