UNPKG

generator-fsharp

Version:
600 lines (509 loc) 19.5 kB
'use strict'; var yeoman = require('yeoman-generator'); var yosay = require('yosay'); var chalk = require('chalk'); var path = require('path'); var fs = require('fs'); var uuid = require('uuid'); var spawn = require('child_process').spawn; var spawnSync = require('child_process').spawnSync; var request = require('request'); var xmldom = require("xmldom"); var fse = require('fs-extra'); var _0777 = parseInt('0777', 8); var greeting = "\n ____ ____ __" + "\n /\\ _`\\ /\\ _`\\ /\\ \\ " + "\n \\ \\ \\L\\_\\ \\,\\L\\_\\ \\ \\___ __ _ __ _____ " + "\n \\ \\ _\\/\\/_\\__ \\\\ \\ _ `\\ /'__`\\ /\\`'__\\/\\ '__`\\ " + "\n \\ \\ \\/ /\\ \\L\\ \\ \\ \\ \\ \\/\\ \\L\\.\\_\\ \\ \\/ \\ \\ \\L\\ \\ " + "\n \\ \\_\\ \\ `\\____\\ \\_\\ \\_\\ \\__/.\\_\\\\ \\_\\ \\ \\ ,__/ " + "\n \\/_/ \\/_____/\\/_/\\/_/\\/__/\\/_/ \\/_/ \\ \\ \\/ " + "\n \\ \\_\\ " + "\n \\/_/ "; fs.mkdirParent = function(dirPath, mode, callback) { //Call the standard fs.mkdir fs.mkdir(dirPath, mode, function(error) { //When it fail in this way, do the custom steps if (error && error.errno === 34) { //Create all the parents recursively fs.mkdirParent(path.dirname(dirPath), mode, callback); //And then the directory fs.mkdirParent(dirPath, mode, callback); } //Manually run the callback since we used our own callback to do all these callback && callback(error); }); }; fs.mkdirParentSync = function sync (p, opts, made) { if (!opts || typeof opts !== 'object') { opts = { mode: opts }; } var mode = opts.mode; var xfs = opts.fs || fs; if (mode === undefined) { mode = _0777 & (~process.umask()); } if (!made) made = null; p = path.resolve(p); try { xfs.mkdirSync(p, mode); made = made || p; } catch (err0) { switch (err0.code) { case 'ENOENT' : made = sync(path.dirname(p), opts, made); sync(p, opts, made); break; // In the case of any other error, just see if there's a dir // there already. If so, then hooray! If not, then something // is borked. default: var stat; try { stat = xfs.statSync(p); } catch (err1) { throw err0; } if (!stat.isDirectory()) throw err0; break; } } return made; }; var FSharpGenerator = yeoman.generators.Base.extend({ username: 'fsprojects', repo: 'generator-fsharp', branch: 'templates', ACTION_CREATE_STANDALONE_PROJECT: 1, ACTION_ADD_PROJECT_TO_SOLUTION: 2, ACTION_CREATE_EMPTY_SOLUTION: 3, ACTION_ADD_REFERENCE_TO_PROJECT: 4, constructor: function() { yeoman.generators.Base.apply(this, arguments); }, _isOnWindows : function() { return /^win/.test(process.platform); }, _download : function(t, done, reload) { t.remote(t.username, t.repo, t.branch, function (err,r) { done(); }, reload) }, _getTemplateDirectory : function() { //return path.join(this.sourceRoot(), "..","..","templates"); return path.join(this.cacheRoot(), this.username, this.repo, this.branch); }, _execManaged : function(file, args, options) { if(this._isOnWindows()){ return spawn(file, args, options); } else { var monoArgs = [file]; monoArgs = monoArgs.concat(args); return spawn('mono', monoArgs, options); } }, _saveSHA : function (p, sha, old) { if (!fs.existsSync(p)){ fs.mkdirParentSync(path.dirname(p)); } if(old){ fs.unlinkSync(p); } fs.appendFileSync(p, sha); }, _checkSHA : function (t, p, sha, old, done) { var oldsha = ""; if(old) oldsha = fs.readFileSync(p, 'utf8'); if(old && sha != oldsha) { t._saveSHA(p, sha, true); t._download(t, done, true) } else if (old && sha == oldsha) { done(); } else { t._saveSHA(p, sha, false); t._download(t, done, true); } }, _getSHA : function(old, p, done) { var log = this.log; var t = this; var checkSHA = this._checkSHA; var options = { url: "https://api.github.com/repos/fsprojects/generator-fsharp/commits?sha=templates", headers: { 'User-Agent': 'request' } }; request(options, function (error, response, body) { if (!error && response.statusCode == 200) { var sha = JSON.parse(body)[0].sha; checkSHA(t, p, sha, old, done); } }); }, init: function() { this.log(greeting); this.log('Welcome to the perfect ' + chalk.red('FSharp') + ' generator!'); this.templatedata = {}; var done = this.async(); var p = path.join(this.cacheRoot(), "sha") var old = fs.existsSync(p); this._getSHA(old, p, done); }, askFor: function() { var done = this.async(); var prompts = [{ type: 'list', name: 'action', message: 'What do You want to do?', choices: [{"name": "Create standalone project", "value": this.ACTION_CREATE_STANDALONE_PROJECT}, {"name": "Add new project to solution", "value": this.ACTION_ADD_PROJECT_TO_SOLUTION}, {"name": "Create empty solution", "value": this.ACTION_CREATE_EMPTY_SOLUTION}, {"name": "Add reference to project", "value": this.ACTION_ADD_REFERENCE_TO_PROJECT} ] }]; this.prompt(prompts, function(props) { this.action = props.action; done(); }.bind(this)); }, askForProject: function() { if (this.action === this.ACTION_ADD_REFERENCE_TO_PROJECT) return; var done = this.async(); if(this.action !== this.ACTION_CREATE_EMPTY_SOLUTION) { var p = path.join(this._getTemplateDirectory(), 'templates.json') var choices = JSON.parse(fs.readFileSync(p, "utf8")); var prompts = [{ type: 'list', name: 'type', message: 'What type of application do you want to create?', choices: choices.Templates }]; this.prompt(prompts, function(props) { this.type = props.type; done(); }.bind(this)); } else { done(); } }, askForName: function() { if (this.action === this.ACTION_ADD_REFERENCE_TO_PROJECT) return; var done = this.async(); var prompts = [{ name: 'applicationName', validate: function(input) { if (input.match(/^[\u0000-\u0040]/) || input.match(/[\u0000-\u002f\u003a-\u0040]/)) { return "Invalid module name"; } else { return true; } }, message: 'What\'s the name of your application?', default: this.type }]; this.prompt(prompts, function(props) { this.templatedata.namespace = props.applicationName; this.templatedata.applicationname = props.applicationName; this.templatedata.guid = uuid.v4(); if(this.action === this.ACTION_ADD_PROJECT_TO_SOLUTION) { this.templatedata.packagesPath = "../packages" this.templatedata.paketPath = "../.paket" } else { this.templatedata.packagesPath = "packages" this.templatedata.paketPath = ".paket" } this.applicationName = props.applicationName; done(); }.bind(this)); }, askForPaket: function() { if (this.action === this.ACTION_ADD_REFERENCE_TO_PROJECT) return; var done = this.async(); var prompts = [{ type: 'list', name: 'paket', message: 'Do You want to use Paket?', choices: [{"name": "Yes", "value": true}, {"name": "No", "value": false}] }]; this.prompt(prompts, function(props) { this.paket = props.paket; done(); }.bind(this)); }, askForFake: function() { if (this.action === this.ACTION_ADD_REFERENCE_TO_PROJECT) return; var done = this.async(); var prompts = [{ type: 'list', name: 'fake', message: 'Do You want to use FAKE?', choices: [{"name": "Yes", "value": true}, {"name": "No", "value": false}] }]; if (this.paket) { this.prompt(prompts, function(props) { this.fake = props.fake; done(); }.bind(this)); return; } done(); }, _copy: function(dirPath, targetDirPath){ var files = fs.readdirSync(dirPath); for(var i in files) { var f = files[i]; var fp = path.join(dirPath, f); this.log(f); if(fs.statSync(fp).isDirectory()) { var newTargetPath = path.join(targetDirPath, f); this._copy(fp, newTargetPath); } else { var fn = path.join(targetDirPath.replace('ApplicationName', this.applicationName), f.replace('ApplicationName', this.applicationName)); this.template(fp,fn, this.templatedata); } } }, writing: function() { if (this.action === this.ACTION_ADD_REFERENCE_TO_PROJECT) return; var log = this.log; var p; if (this.action === this.ACTION_CREATE_EMPTY_SOLUTION){ p = path.join(this._getTemplateDirectory(), 'sln') } else { p = path.join(this._getTemplateDirectory(), this.type); } this._copy(p, this.applicationName); if(this.paket) { var bpath; if(this.action !== this.ACTION_ADD_PROJECT_TO_SOLUTION) { bpath = path.join(this.applicationName, ".paket", "paket.bootstrapper.exe" ); } else { bpath = path.join(".paket", "paket.bootstrapper.exe" ); } var p = path.join(this._getTemplateDirectory(), ".paket", "paket.bootstrapper.exe"); this.copy(p, bpath); } if(this.fake) { if (this.action !== this.ACTION_ADD_PROJECT_TO_SOLUTION){ var fakeSource = path.join(this._getTemplateDirectory(), ".fake"); this._copy(fakeSource, this.applicationName); } } }, _getProjectFile: function() { var dirPath = this.destinationRoot(); var files = fs.readdirSync(dirPath); var projectFile; for(var i in files) { var f = files[i]; var fp = path.join(dirPath, f); if (fp.endsWith(".fsproj")) { projectFile = fp; } } return projectFile; }, _getProjectFiles: function() { var result = []; var dirPath = path.join(this.destinationRoot(), ".."); var fsprojFilter = function(item){ path.extname(item.path) === '.fsproj' }; return new Promise(function(resolve, reject){ fse.walk(dirPath, {filter: fsprojFilter}) .on('data', function(item){ result.push(item.path); }) .on('end', function(){ resolve(result); }) }); }, _askForReference: function(projectFiles, onChoose) { var choices = projectFiles.map(function(s) { return {"name": s, "value": "./../" + s}; }); var prompts = [{ type: 'list', name: 'projectToReference', message: 'Which project should be referenced?', choices: choices }]; this.prompt(prompts, function(props) { onChoose(props.projectToReference); }.bind(this)); }, _addReference: function(done) { var log = this.log; var projectFile = this._getProjectFile(); var fs = this.fs; var self = this; if (projectFile === undefined) { this.log("No project file in local folder found"); done(); return; } this.log("Project file: " + projectFile); this._getProjectFiles().then(function(projectFiles){ self._askForReference(projectFiles, function(selectedFile) { var projectFileContent = fs.read(projectFile); var domParser = new xmldom.DOMParser(); var projectXml = domParser.parseFromString(projectFileContent, 'text/xml'); var projectReferenceItemGroup; var itemGroupNodes = projectXml.getElementsByTagName("ItemGroup"); for (var c in itemGroupNodes) { var node = itemGroupNodes[c]; for (var cc in node.childNodes) { var itemGroupNode = node.childNodes[cc]; if (itemGroupNode.nodeName == "ProjectReference") { projectReferenceItemGroup = node; break; } } } if (projectReferenceItemGroup === undefined) { var newItemGroup = projectXml.createElement("ItemGroup"); var lastItemGroup = itemGroupNodes[itemGroupNodes.length-1]; projectReferenceItemGroup = projectXml.insertBefore(newItemGroup, lastItemGroup); } var alreadyReferenced = false; for (var cc in projectReferenceItemGroup.childNodes) { var itemGroupNode = projectReferenceItemGroup.childNodes[cc]; if (itemGroupNode.nodeName == "ProjectReference") { if (itemGroupNode.getAttribute("Include") === selectedFile) { alreadyReferenced = true; } } } if (alreadyReferenced) { log(selectedFile + " is already referenced"); } else { var projectReferenceNode = projectXml.createElement("ProjectReference"); projectReferenceNode.setAttribute("Include", selectedFile); projectReferenceItemGroup.appendChild(projectReferenceNode); var xmlSerialzier = new xmldom.XMLSerializer() var xml = xmlSerialzier.serializeToString(projectXml); log("Please press Y for updating the existing file"); //log(xml); fs.write(projectFile, xml); } done(); }); }, function(){log("missing .fsproj")}); }, install: function() { var log = this.log var done = this.async(); var appName = this.applicationName; var action = this.action; var dest = this.destinationRoot(); var isfake = this.fake; var fs = this.fs; var generator = this; var isWin = this._isOnWindows(); if (action === this.ACTION_ADD_REFERENCE_TO_PROJECT) { this._addReference(done); return; } if(this.fake) { if (!this._isOnWindows()) { var buildShPath = path.join(dest, appName, 'build.sh'); var chmodProc = spawnSync('chmod', ['+x', buildShPath], {cwd: dest}); } } if(this.paket) { var bpath; if(this.action !== this.ACTION_ADD_PROJECT_TO_SOLUTION) { bpath = path.join(this.applicationName, ".paket", "paket.bootstrapper.exe" ); } else { bpath = path.join(".paket", "paket.bootstrapper.exe" ); } var bootstrapper = this._execManaged(bpath, [], {}); bootstrapper.stdout.on('data', function (data) { log(data.toString()); }); bootstrapper.on('close', function (code) { var ppath; var cpath; if(action !== this.ACTION_ADD_PROJECT_TO_SOLUTION) { ppath = path.join(dest, appName, ".paket", "paket.exe" ); cpath = path.join(dest, appName); } else { ppath = path.join(dest, ".paket", "paket.exe" ); cpath = dest; } try{ log(cpath); var paket = generator._execManaged(ppath, ['convert-from-nuget','-f'], {cwd: cpath}); paket.stdout.on('data', function (data) { log(data.toString()); }); paket.stdout.on('close', function (data) { var simplifiy = generator._execManaged(ppath, ['simplify'], {cwd: cpath}); simplifiy.stdout.on('data', function (data) { log(data.toString()); }); simplifiy.stdout.on('close', function (data) { if (isfake) { var addFake = generator._execManaged(ppath, ['add', 'nuget', 'FAKE'], {cwd: cpath}); addFake.stdout.on('close', function(data) { done(); }) } else { done(); } }); }); } catch(ex) { log(ex); } }); } else { done(); } }, end: function() { this.log('\r\n'); this.log('Your project is now created'); this.log('\r\n'); } }); module.exports = FSharpGenerator;