Grunt - resolving non-string (eg array) templates
Asked Answered
C

4

9

Say I have a variable in my grunt config with an array as a value. A real world example is grunt.regarde.changed from the grunt-regarde plugin, which lists all files that have changed.

I want to resolve that array using a template, so that I could (in this case) copy the changed files:

  copy: {
    staticWeb: {
      src: '<%= grunt.regarde.changed %>',
      dest: 'someDir'
    },

What src gets in this case is a is a single comma delimited string instead of an array. Grunt's file processor does not parse the string, and so it cannot find the src file.

I can't remove the single quotes around the template because then it's invalid javascript.

So how do I pass that grunt.regarde.changed array to the src variable?

Coagulate answered 12/4, 2013 at 20:34 Comment(1)
In the future, I hope grunt templates will natively handle non-string values. For now though, oligofren's answer is our best option. It should be the accepted answer.Stereopticon
I
10

The problem is very easy to fix once you know how, just a few lines of code, but it took me quite a while to dig up all the relevant info from the Grunt source code in order to understand what to do, so bear with me while I take you through the background ...


The general way of getting hold of a property on the configuration object is straight forward:

<%= some.property %> // fetches grunt.config.get('some.property')

That works for all properties that have been set on the grunt.config object, which (of course) include the config that is passed into grunt.initConfig(). This is why you can reference other tasks variables directly, such as in <%= concat.typescriptfiles.dest %>, as all the properties in the config object is in the template's own scope.

Technically this expansion happens when the (LoDash) template is passed along with either the options object (if defined) or the grunt.config object to the template processor (LoDash' template function).

So this works for values that have been set in the config itself, or by using dynamically assigned values through grunt.config.set(). See the API docs for more info.

What does not work in the same way is accessing values that are not availabe on the configuration object. It seems that for some reason I am not quite sure of, all other values always ends up as strings. This happens regardless of whether you access them directly or through method calls. For instance trying to get access to an array on the config through grunt.config.get() gets you a string.

A workaround for the problem that preserves file order

The accepted answer works in a way, but due to the globbing syntax it will be parsed by the glob() module which does not preserve file order. This was a no-no for my build.

A workaround, in case the array you want to use is not available on the config object, is to add it to the config via an intermediary task. Something like the following should work:

// This variable will be used twice to demonstrate the difference
// between directly setting an attribute on the grunt object
// and using the setter method on the grunt.config object
var myFiles = ['c/file1.txt', 'a/file2.txt', 'b/file3.txt']
module.exports = function(grunt){

    grunt.initConfig({

        debug : {
            using_attribute: {
                src : '<%= grunt.value_as_attribute %>' // will be a string
            },
            using_task: {
                src : '<%= value_by_setter %>' // will be an array
            },
            no_task_direct_setter: {
                src : '<%= value_by_setter_early %>' // will be an array
            }
        }        
    });

    grunt.registerTask('myValSetter', function() {
        grunt.config.set('value_by_setter', myFiles );
    });

    // a task that will report information on our set values
    grunt.registerMultiTask('debug', function(){
        grunt.log.writeln('data.src: ', this.data.src);
        grunt.log.writeln('type: ', Array.isArray(this.data.src)? "Array" : typeof this.data.src);
    });

    grunt.value_as_attribute = myFiles;

    grunt.config.set('value_by_setter_early', myFiles );

    grunt.registerTask('default',['myValSetter', 'debug']);
}

This will output

$ grunt
Running "myValSetter" task

Running "debug:using_attribute" (debug) task
data.src:  c/file1.txt,a/file2.txt,b/file3.txt
type:  string

Running "debug:using_task" (debug) task
data.src:  [ 'c/file1.txt', 'a/file2.txt', 'b/file3.txt' ]
type:  Array

Running "debug:no_task_direct_setter" (debug) task
data.src:  [ 'c/file1.txt', 'a/file2.txt', 'b/file3.txt' ]
type:  Array

Done, without errors.

This example is just meant to illustrate the concept, but you should be able to easily customize it to your instance :)

Industry answered 27/6, 2014 at 8:26 Comment(4)
This is brilliant. Thank you. One change I've made for my implementation is using grunt.config.set to create a single grunt "global" called grunt.values to contain all of the non-string values that are used more than once in the config. e.g. grunt.config.set('values', { a:[1,2], b:[2,3], etc:[3,4] }). Also, I don't see the need to create other grunt properties as shown in your example as grunt.somevalue. "Registering" the non-string values directly with grunt.config.set seems to be sufficient.Stereopticon
Good that someone made use of it - I remember using a whole night of hacking, including reading all of the relevant Grunt source code to understand what was going on under the hood! You are right about the grunt.somevalue thing. I replaced that with a normal variable in the answer now.Industry
Oh, now I remember. I use it to show the difference between reading directly set attribute values and using grunt.config.set. I have to revert my edit, otherwise the comments make no sense. //src : '<%= grunt.somevalue %>' // will be a stringIndustry
I have cleared up the answer, adding some tasks, and renamed the tasks so it is clearer what and why is happening.Industry
B
8

I had exactly the same problem as yours, solve it by surrounding your template with curly brackets.

So here is your code, modified

copy: {
  staticWeb: {
    src: '{<%= grunt.regarde.changed %>}', // added curly brackets
    dest: 'someDir'
  }
}

It will output the src as {source1,source2,source3}, which is the same as using an array. (See Globbing Patterns in Grunt Documentation)

Borghese answered 6/2, 2014 at 16:7 Comment(2)
Excellent! I was struggling with this for quite a few hours ... #24441363Industry
One problem is that it for some reason does not preserve file order ... :-(Industry
I
0
// assuming that regarde is a grunt plugin (haven't used—link is broken)

copy: {
  staticWeb: {
    src: '<% regarde.changed %>',
    dest: 'someDir'
  }
}

http://gruntjs.com/api/grunt.config#grunt.config.getraw

#ezpz

Incunabula answered 9/9, 2015 at 21:47 Comment(0)
D
-1

Have you tried:

copy: {
  staticWeb: {
    src: '<%= grunt.regarde.changed.split(",") %>',
    dest: 'someDir'
  }
}
Dulaney answered 12/4, 2013 at 20:45 Comment(1)
Have you tried this? It does not work for me with Grunt v0.4.5, which currently is the latest stable version.Hypso

© 2022 - 2024 — McMap. All rights reserved.