How can I dynamically generate a list of filenames for use in a task with Grunt?

≡放荡痞女 提交于 2019-12-12 08:02:21

问题


I'm using load-grunt-config and grunt-prompt, and I'm developing an init task, which copies some php templates between two folders.

Right now the template filenames are hardcoded, but I'd rather have grunt scan the right folder and provide the filenames dynamically.

I've tried using grunt.file.expand, but I'm unable to get it to work. Is it possible to scan a folder and return an array (or object, not sure what you'd call it) of filenames in the format that grunt-prompt expects?

// -------------------------------------
// Grunt prompt
// -------------------------------------

module.exports = {

  // ----- Initialization prompt ----- //

  init: {
    options: {
      questions: [{
        // Set the authors name
        config: 'init.author.name',
        type: 'input',
        message: 'What is your name?'
      }, {
        // Set the name of the project
        config: 'init.project.name',
        type: 'input',
        message: 'What is the name of your project?'
      }, {
        // Select templates to be used
        config: 'init.php.templates',
        type: 'checkbox',
        message: 'Which templates do you want to use?',
        choices: [{
          name: '404.php',
          checked: false
        }, {
          name: 'archive.php',
          checked: false
        }, {
          name: 'comments.php',
          checked: false
        }]
      }]
    }
  }
};

By the way, I have found this answer: https://stackoverflow.com/a/22270703/1694077, which relates to the problem. But it does not go into detail about how one would specifically address this problem. Also, I need more specific syntax than just an array of filenames:

[{
  name: '404.php'
}, {
  name: 'archive.php'
}]

回答1:


Basic Principle

Here's a way to do it that uses Grunt's file matching capabilities to get a list of files. The following code will seek templates in a subdirectory named templates. You just need to put your php files there and the script will find it. Note I've omitted the use of load-grunt-config since it is not a factor in the specific problem of getting a list of files.

The key is to use grunt.file.expand to get the files.

module.exports = function (grunt) {

    // List all files in the templates directory.
    var templates = grunt.file.expand({filter: "isFile", cwd: "templates"},
                                      ["*"]);

    // Make actual choices out of them that grunt-prompt can use.
    var choices = templates.map(function (t) {
        return { name: t, checked: false};
    });

    grunt.initConfig({
        prompt: {
            init: {
                options: {
                    questions: [{
                        // Set the authors name
                        config: 'init.author.name',
                        type: 'input',
                        message: 'What is your name?'
                    }, {
                        // Set the name of the project
                        config: 'init.project.name',
                        type: 'input',
                        message: 'What is the name of your project?'
                    }, {
                        // Select templates to be used
                        config: 'init.php.templates',
                        type: 'checkbox',
                        message: 'Which templates do you want to use?',
                        choices: choices
                    }]
                }
            }
        }
    });

    grunt.task.loadNpmTasks("grunt-prompt");
    grunt.registerTask("default", ["prompt"]);
};

You could use something more sophisticated than "*" as a pattern. For instance, if you are going to have other types of files there that you don't want to list "*.php" would be indicated. I also use isFile for the filter option to avoid listing directories. And I use cwd to change the working directory to templates before listing the files, which means the file names returned do not include templates/ in their name. It would also be possible to do this instead:

var templates = grunt.file.expand({filter: "isFile"}, ["templates/*"]);

and get a list of files that include the templates/ directory in their name.

With load-grunt-config

By default, load-grunt-config wants a package.json file (because it calls load-grunt-tasks). This is what I've used:

{
  "dependencies": {
    "load-grunt-config": "^0.8.0",
    "grunt-prompt": "^1.1.0",
    "grunt": "^0.4.4"
  }
}

The Gruntfile.js becomes:

module.exports = function (grunt) {

    grunt.registerTask("default", ["prompt"]);
    require('load-grunt-config')(grunt);
};

And then in grunt/prompt.js you need this:

module.exports = function(grunt) {
    // List all files in the templates directory.
    var templates = grunt.file.expand({filter: "isFile", cwd: "templates"},
                                      ["*"]);

    // Make actual choices out of them that grunt-prompt can use.
    var choices = templates.map(function (t) {
        return { name: t, checked: false};
    });

    return {
        init: {
            options: {
                questions: [{
                    // Set the authors name
                    config: 'init.author.name',
                    type: 'input',
                    message: 'What is your name?'
                }, {
                    // Set the name of the project
                    config: 'init.project.name',
                    type: 'input',
                    message: 'What is the name of your project?'
                }, {
                    // Select templates to be used
                    config: 'init.php.templates',
                    type: 'checkbox',
                    message: 'Which templates do you want to use?',
                    choices: choices
                }]
            }
        }
    };
};



回答2:


Here is a short code to list the files in a dir:

var fs = require("fs")
var files = [];
var list = function (path) {
  fs.readdirSync(path).forEach(function (file) {
    if(fs.lstatSync(path + '/' +file).isDirectory())
      list(path + '/' +file);
    else
      files.push({name: file});
  });
}
list(YOUR_PATH)
console.log(files)

In your example :

var fs = require("fs")
var files = [];
var list = function (path) {
  fs.readdirSync(path).forEach(function (file) {
    if(fs.lstatSync(path + '/' +file).isDirectory())
      list(path + '/' +file);
    else
      files.push({name: file});
  });
}
list(YOUR_PATH)
module.exports = {

  // ----- Initialization prompt ----- //

  init: {
    options: {
      questions: [{
        // Set the authors name
        config: 'init.author.name',
        type: 'input',
        message: 'What is your name?'
      }, {
        // Set the name of the project
        config: 'init.project.name',
        type: 'input',
        message: 'What is the name of your project?'
      }, {
        // Select templates to be used
        config: 'init.php.templates',
        type: 'checkbox',
        message: 'Which templates do you want to use?',
        choices: files
      }]
    }
  }
};



回答3:


There is another way to do this that uses a different Grunt utility method than what's listed in the accepted answer. Its worth mentioning because it exposes the options object for the "glob" npm package that Grunt uses internally.

See README.md for the version of glob used by your version of Grunt for more info (check its package.json file). I first looked at the most current glob and was puzzled by the fact that Grunt had no grunt.file.globSync() method. I eventually realized Grunt was using an earlier version of glob, after which {sync: true} was removed from the options object and replaced by the globSync() function I'd been looking for.

NOTE: grunt.file.glob() does not take a src array as grunt.file.expand() does, but you can use braces to effectively encode an array:

var options = { ... };
var results = 
  grunt.file.glob(
     '{first/path/**/*,second/path/**/*}', 
     options
  );

Here's an example block showing how I use this in jade processing, which I seem to recall does not handle expand blocks natively for some reason. The options:

  • allow wildcards to match dot prefixed files
  • disable unnecessary stat calls for non-wildcard glob pattern path nodes
  • disables sorting since I don't care about input order
  • disables de-duping because I know my pattern matches each file exactly once
  • requests an empty array rather than an array if no files match

    var filesObj = { }; var configObj = { build: { options: { pretty: true, data: { titleStr: 'This is a title from data' } }, files: filesObj } };

    // Get source and build output directory roots from config var srcPrefix = new RegExp('^' + grunt.config.get('sourceAssets')); var buildPrefix = grunt.config.get('buildAssets');

    // Call once per glob match to populate files object. function addJadeFile(srcPath) { // Handle glob output on Windows by denormlalizing path. srcPath = srcPath.replace(/\/g, '/'); // Extract common path suffix and change file extension var relPath = srcPath.replace(srcPrefix, '..').replace(/.jade$/, '.html'); // Prepend destination path prefix and a property // to files sub-object of config object. filesObj[buildPath + relPath] = srcPath; }

    grunt.file.glob( appConfig.source.client + '/**/*.jade', { sync: true, stat: false strict: true, dot: false, nonull: false, nodir: true, nosort: true, nounique: true } ).forEach(addJadeFile);

    return configObj;

P.S. Both grunt.file.glob() and grunt.file.expand() are generally preferable over traversal methods from the fs package because glob keeps a traversal cache.

It's also good to be aware of glob's traversal cache if you are looking for files that may have been created by a previous build step, but not before other tasks first traversed their creation location and populated that cache. I've not run into this yet, but be aware of it in case you need to find out how to purge or disregard the cache in such a corner case.



来源:https://stackoverflow.com/questions/23294849/how-can-i-dynamically-generate-a-list-of-filenames-for-use-in-a-task-with-grunt

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!