├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── prepare-commit-msg ├── .npmrc ├── README.md ├── commitlint.config.js ├── gruntfile.js ├── license ├── package.json ├── tasks └── replace.js └── test ├── fixtures ├── built-in_source_file.txt ├── built-in_source_filename.txt ├── built-in_source_path.txt ├── built-in_target_file.txt ├── built-in_target_filename.txt ├── built-in_target_path.txt ├── fail.txt ├── simple.txt ├── verbose.txt └── warning.txt └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 14 14 | - 12 15 | - 10 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && git cz --hook || true 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-replace 2 | 3 | [![Build Status](https://img.shields.io/github/workflow/status/outaTiME/grunt-replace/CI)](https://github.com/outaTiME/grunt-replace/actions/workflows/main.yml) 4 | [![Version](https://img.shields.io/npm/v/grunt-replace.svg)](https://www.npmjs.com/package/grunt-replace) 5 | ![Prerequisite](https://img.shields.io/badge/node-%3E%3D10-blue.svg) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](#) 7 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 8 | [![Twitter: outa7iME](https://img.shields.io/twitter/follow/outa7iME.svg?style=social)](https://twitter.com/outa7iME) 9 | 10 | > Replace text patterns with [applause](https://github.com/outaTiME/applause). 11 | 12 | ## Install 13 | 14 | From NPM: 15 | 16 | ```shell 17 | npm install grunt-replace --save-dev 18 | ``` 19 | 20 | ## Usage 21 | 22 | Assuming installation via NPM, you can use `grunt-replace` in your gruntfile like this: 23 | 24 | ```javascript 25 | module.exports = function (grunt) { 26 | grunt.initConfig({ 27 | replace: { 28 | dist: { 29 | options: { 30 | patterns: [ 31 | { 32 | match: 'foo', 33 | replacement: 'bar' 34 | } 35 | ] 36 | }, 37 | files: [ 38 | { 39 | expand: true, flatten: true, src: ['src/index.html'], dest: 'build/' 40 | } 41 | ] 42 | } 43 | } 44 | }); 45 | grunt.loadNpmTasks('grunt-replace'); 46 | grunt.registerTask('default', ['replace']); 47 | }; 48 | ``` 49 | 50 | ## Options 51 | 52 | Supports all the applause [options](https://github.com/outaTiME/applause#options) in addition to the ones below. 53 | 54 | ### excludeBuiltins 55 | Type: `Boolean` 56 | Default: `false` 57 | 58 | If set to `true`, the built-in matching rules are excluded. 59 | 60 | ### force 61 | Type: `Boolean` 62 | Default: `true` 63 | 64 | Force the copy of files even when those files don't have any match found for replacement. 65 | 66 | ### noProcess 67 | Type: `String` 68 | 69 | This option is an advanced way to control which file contents are processed. 70 | 71 | > `processContentExclude` has been renamed to `noProcess` and the option name will be removed in the future. 72 | 73 | ### encoding 74 | Type: `String` 75 | Default: `grunt.file.defaultEncoding` 76 | 77 | The file encoding to copy files with. 78 | 79 | ### mode 80 | Type: `Boolean` or `Number` 81 | Default: `false` 82 | 83 | Whether to copy or set the existing file permissions. Set to `true` to copy the existing file permissions. Or set to the mode, i.e.: `0644`, that copied files will be set to. 84 | 85 | ### timestamp 86 | Type: `Boolean` 87 | Default: `false` 88 | 89 | Whether to preserve the timestamp attributes (atime and mtime) when copying files. Set to true to preserve files timestamp. But timestamp will not be preserved when the file contents or name are changed during copying. 90 | 91 | ### silent 92 | Type: `Boolean` 93 | Default: `false` 94 | 95 | If set to `true`, removes the output from stdout. 96 | 97 | ### pedantic 98 | Type: `Boolean` 99 | Default: `false` 100 | 101 | If set to `true`, the task will fail with a `grunt.fail.warn` when no matches are present. 102 | 103 | ## Built-in replacements 104 | 105 | Few matching rules are provided by default and can be used anytime (these will be affected by the `options` given): 106 | 107 | * `__SOURCE_FILE__`: 108 | 109 | Replace match with the source file. 110 | 111 | * `__SOURCE_PATH__`: 112 | 113 | Replace match with the path of source file. 114 | 115 | * `__SOURCE_FILENAME__`: 116 | 117 | Replace match with the filename of source file. 118 | 119 | * `__TARGET_FILE__`: 120 | 121 | Replace match with the target file. 122 | 123 | * `__TARGET_PATH__`: 124 | 125 | Replace match with the path of target file. 126 | 127 | * `__TARGET_FILENAME__`: 128 | 129 | Replace match with the filename of target file. 130 | 131 | > If you are looking how to use an `built-in` replacements, check out the [How to insert filename in target](#how-to-insert-filename-in-target) usage. 132 | 133 | ## Examples 134 | 135 | ### Basic 136 | 137 | File `src/manifest.appcache`: 138 | 139 | ``` 140 | CACHE MANIFEST 141 | # @@timestamp 142 | 143 | CACHE: 144 | 145 | favicon.ico 146 | index.html 147 | 148 | NETWORK: 149 | * 150 | ``` 151 | 152 | Task configuration on gruntfile: 153 | 154 | ```javascript 155 | { 156 | options: { 157 | patterns: [ 158 | { 159 | match: 'timestamp', 160 | replacement: '<%= Date.now() %>' 161 | } 162 | ] 163 | }, 164 | files: [ 165 | { 166 | expand: true, flatten: true, src: ['src/manifest.appcache'], dest: 'build/' 167 | } 168 | ] 169 | } 170 | ``` 171 | 172 | ### Multiple matching 173 | 174 | File `src/manifest.appcache`: 175 | 176 | ``` 177 | CACHE MANIFEST 178 | # @@timestamp 179 | 180 | CACHE: 181 | 182 | favicon.ico 183 | index.html 184 | 185 | NETWORK: 186 | * 187 | ``` 188 | 189 | File `src/humans.txt`: 190 | 191 | ``` 192 | __ _ 193 | _ _/__ /./|,//_` 194 | /_//_// /_|/// //_, outaTiME v.@@version 195 | 196 | /* TEAM */ 197 | Web Developer / Graphic Designer: Ariel Oscar Falduto 198 | Site: https://www.outa.im 199 | Twitter: @outa7iME 200 | Contact: afalduto at gmail dot com 201 | From: Buenos Aires, Argentina 202 | 203 | /* SITE */ 204 | Last update: @@timestamp 205 | Standards: HTML5, CSS3, robotstxt.org, humanstxt.org 206 | Components: H5BP, Modernizr, jQuery, Bootstrap, LESS, Jade, Grunt 207 | Software: Sublime Text, Photoshop, LiveReload 208 | ``` 209 | 210 | Task configuration on gruntfile: 211 | 212 | ```javascript 213 | { 214 | options: { 215 | patterns: [ 216 | { 217 | match: 'version', 218 | replacement: '<%= pkg.version %>' 219 | }, 220 | { 221 | match: 'timestamp', 222 | replacement: '<%= Date.now() %>' 223 | } 224 | ] 225 | }, 226 | files: [ 227 | { 228 | expand: true, flatten: true, src: ['src/manifest.appcache', 'src/humans.txt'], dest: 'build/' 229 | } 230 | ] 231 | } 232 | ``` 233 | 234 | ### Cache busting 235 | 236 | File `src/index.html`: 237 | 238 | ```html 239 | 240 | 241 | 242 | 243 | ``` 244 | 245 | Task configuration on gruntfile: 246 | 247 | ```javascript 248 | { 249 | options: { 250 | patterns: [ 251 | { 252 | match: 'timestamp', 253 | replacement: '<%= Date.now() %>' 254 | } 255 | ] 256 | }, 257 | files: [ 258 | { 259 | src: ['src/index.html'], dest: 'build/index.html' 260 | } 261 | ] 262 | } 263 | ``` 264 | 265 | ### Include file 266 | 267 | File `src/index.html`: 268 | 269 | ```html 270 | 271 | @@include 272 | 273 | ``` 274 | 275 | Task configuration on gruntfile: 276 | 277 | ```javascript 278 | { 279 | options: { 280 | patterns: [ 281 | { 282 | match: 'include', 283 | replacement: '<%= grunt.file.read("includes/content.html") %>' 284 | } 285 | ] 286 | }, 287 | files: [ 288 | { 289 | expand: true, flatten: true, src: ['src/index.html'], dest: 'build/' 290 | } 291 | ] 292 | } 293 | ``` 294 | 295 | ### Regular expression 296 | 297 | File `src/username.txt`: 298 | 299 | ``` 300 | John Smith 301 | ``` 302 | 303 | Task configuration on gruntfile: 304 | 305 | ```javascript 306 | { 307 | options: { 308 | patterns: [ 309 | { 310 | match: /(\w+)\s(\w+)/, 311 | replacement: '$2, $1' // Replaces "John Smith" with "Smith, John" 312 | } 313 | ] 314 | }, 315 | files: [ 316 | { 317 | expand: true, flatten: true, src: ['src/username.txt'], dest: 'build/' 318 | } 319 | ] 320 | } 321 | ``` 322 | 323 | ### Lookup for `foo` instead of `@@foo` 324 | 325 | Task configuration on gruntfile: 326 | 327 | ```javascript 328 | { 329 | 'opt-1': { 330 | options: { 331 | patterns: [ 332 | { 333 | match: /foo/g, // Explicitly using a regexp 334 | replacement: 'bar' 335 | } 336 | ] 337 | }, 338 | files: [ 339 | { 340 | expand: true, flatten: true, src: ['src/foo.txt'], dest: 'build/' 341 | } 342 | ] 343 | }, 344 | 'opt-2': { 345 | options: { 346 | patterns: [ 347 | { 348 | match: 'foo', 349 | replacement: 'bar' 350 | } 351 | ], 352 | usePrefix: false // Using the option provided 353 | }, 354 | files: [ 355 | { 356 | expand: true, flatten: true, src: ['src/foo.txt'], dest: 'build/' 357 | } 358 | ] 359 | }, 360 | 'opt-3': { 361 | options: { 362 | patterns: [ 363 | { 364 | match: 'foo', 365 | replacement: 'bar' 366 | } 367 | ], 368 | prefix: '' // Removing the prefix manually 369 | }, 370 | files: [ 371 | { 372 | expand: true, flatten: true, src: ['src/foo.txt'], dest: 'build/' 373 | } 374 | ] 375 | } 376 | } 377 | ``` 378 | 379 | ### How to insert filename in target 380 | 381 | File `src/app.js`: 382 | 383 | ```javascript 384 | // Filename: @@__SOURCE_FILENAME__ 385 | 386 | var App = App || (function () { 387 | return { 388 | // App contents 389 | }; 390 | })(); 391 | 392 | window.App = App; 393 | ``` 394 | 395 | Task configuration on gruntfile: 396 | 397 | ```javascript 398 | { 399 | options: { 400 | // Pass, we use built-in replacements 401 | }, 402 | files: [ 403 | { 404 | expand: true, flatten: true, src: ['src/**/*.js'], dest: 'build/' 405 | } 406 | ] 407 | } 408 | ``` 409 | 410 | ## Related 411 | 412 | - [applause](https://github.com/outaTiME/applause) - Human-friendly replacements 413 | 414 | ## License 415 | 416 | MIT © [outaTiME](https://outa.im) 417 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | grunt.initConfig({ 5 | replace: { 6 | simple: { 7 | options: { 8 | variables: { 9 | key: 'value' 10 | } 11 | }, 12 | files: [ 13 | { 14 | expand: true, flatten: true, src: ['test/fixtures/simple.txt'], dest: 'temp' 15 | } 16 | ] 17 | }, 18 | verbose: { 19 | options: { 20 | variables: { 21 | key: 'value' 22 | } 23 | }, 24 | files: [ 25 | { 26 | expand: true, flatten: true, src: ['test/fixtures/verbose.txt'], dest: 'temp' 27 | } 28 | ] 29 | }, 30 | warning: { 31 | options: { 32 | patterns: [ 33 | { 34 | match: 'key', 35 | replacement: 'value' 36 | }, 37 | { 38 | match: 'undefined-key', 39 | replacement: 'value' 40 | } 41 | ] 42 | }, 43 | files: [ 44 | { 45 | expand: true, flatten: true, src: ['test/fixtures/warning.txt'], dest: 'temp' 46 | } 47 | ] 48 | }, 49 | fail: { 50 | options: { 51 | pedantic: true, 52 | patterns: [ 53 | { 54 | match: 'key', 55 | replacement: 'value' 56 | }, 57 | { 58 | match: 'undefined-key', 59 | replacement: 'value' 60 | } 61 | ] 62 | }, 63 | files: [ 64 | { 65 | expand: true, flatten: true, src: ['test/fixtures/fail.txt'], dest: 'temp' 66 | } 67 | ] 68 | }, 69 | 'built-in': { 70 | options: { 71 | // Pass 72 | }, 73 | files: [ 74 | { 75 | expand: true, flatten: true, src: ['test/fixtures/built-in_*.txt'], dest: 'temp' 76 | } 77 | ] 78 | } 79 | } 80 | }); 81 | grunt.loadTasks('tasks'); 82 | grunt.registerTask('default', ['replace:simple', 'replace:built-in']); 83 | }; 84 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) outaTiME (https://outa.im) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-replace", 3 | "version": "2.0.2", 4 | "description": "Replace text patterns with applause.", 5 | "license": "MIT", 6 | "repository": "outaTiME/grunt-replace", 7 | "author": { 8 | "name": "outaTiME", 9 | "email": "afalduto@gmail.com", 10 | "url": "https://outa.im" 11 | }, 12 | "engines": { 13 | "node": ">=10" 14 | }, 15 | "scripts": { 16 | "test": "eslint . && grunt && ava", 17 | "release": "release-it", 18 | "prepare": "husky install" 19 | }, 20 | "files": [ 21 | "tasks" 22 | ], 23 | "keywords": [ 24 | "gruntplugin", 25 | "replace", 26 | "replacement", 27 | "pattern", 28 | "patterns", 29 | "match", 30 | "text", 31 | "string", 32 | "regex", 33 | "regexp", 34 | "json", 35 | "yaml", 36 | "cson", 37 | "flatten", 38 | "applause" 39 | ], 40 | "dependencies": { 41 | "applause": "^2.0.0", 42 | "chalk": "^4.1.0", 43 | "file-sync-cmp": "^0.1.0", 44 | "lodash": "^4.17.21" 45 | }, 46 | "devDependencies": { 47 | "@commitlint/cli": "^12.1.1", 48 | "@commitlint/config-conventional": "^12.1.1", 49 | "@release-it/conventional-changelog": "^2.0.1", 50 | "ava": "^3.15.0", 51 | "cz-conventional-changelog": "^3.3.0", 52 | "eslint": "^7.20.0", 53 | "eslint-config-xo": "^0.35.0", 54 | "eslint-config-xo-space": "^0.27.0", 55 | "grunt": "^1.0.0", 56 | "husky": "^6.0.0", 57 | "release-it": "^14.4.1", 58 | "rimraf": "^3.0.2" 59 | }, 60 | "eslintConfig": { 61 | "extends": [ 62 | "xo", 63 | "xo-space" 64 | ] 65 | }, 66 | "config": { 67 | "commitizen": { 68 | "path": "./node_modules/cz-conventional-changelog" 69 | } 70 | }, 71 | "release-it": { 72 | "hooks": { 73 | "after:init": [ 74 | "npm test" 75 | ] 76 | }, 77 | "git": { 78 | "commitMessage": "chore: release v${version}" 79 | }, 80 | "github": { 81 | "release": true 82 | }, 83 | "plugins": { 84 | "@release-it/conventional-changelog": { 85 | "preset": "angular", 86 | "infile": "CHANGELOG.md" 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tasks/replace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var chalk = require('chalk'); 6 | var _ = require('lodash'); 7 | var Applause = require('applause'); 8 | var fileSyncCmp = require('file-sync-cmp'); 9 | var isWindows = process.platform === 'win32'; 10 | 11 | var detectDestType = function (dest) { 12 | if (_.endsWith(dest, '/')) { 13 | return 'directory'; 14 | } 15 | 16 | return 'file'; 17 | }; 18 | 19 | var unixifyPath = function (filepath) { 20 | if (isWindows) { 21 | return filepath.replace(/\\/g, '/'); 22 | } 23 | 24 | return filepath; 25 | }; 26 | 27 | var syncTimestamp = function (src, dest) { 28 | var stat = fs.lstatSync(src); 29 | if (path.basename(src) !== path.basename(dest)) { 30 | return; 31 | } 32 | 33 | if (stat.isFile() && !fileSyncCmp.equalFiles(src, dest)) { 34 | return; 35 | } 36 | 37 | var fd = fs.openSync(dest, isWindows ? 'r+' : 'r'); 38 | fs.futimesSync(fd, stat.atime, stat.mtime); 39 | fs.closeSync(fd); 40 | }; 41 | 42 | module.exports = function (grunt) { 43 | var replace = function (source, target, options, applause) { 44 | var res; 45 | grunt.file.copy(source, target, { 46 | encoding: options.encoding, 47 | process: function (content) { 48 | res = applause.replace(content, [source, target]); 49 | var result = res.content; 50 | // Force contents 51 | if (result === false && options.force === true) { 52 | result = content; 53 | } 54 | 55 | if (result !== false) { 56 | grunt.verbose.writeln('Replace ' + chalk.cyan(source) + ' → ' + 57 | chalk.green(target)); 58 | } 59 | 60 | return result; 61 | }, 62 | noProcess: options.noProcess || options.processContentExclude 63 | }); 64 | return res; 65 | }; 66 | 67 | grunt.registerMultiTask( 68 | 'replace', 69 | 'Replace text patterns with applause.', 70 | function () { 71 | // Took options 72 | var options = this.options({ 73 | encoding: grunt.file.defaultEncoding, 74 | // ProcessContent/processContentExclude deprecated renamed to process/noProcess 75 | processContentExclude: [], 76 | mode: false, 77 | timestamp: false, 78 | patterns: [], 79 | excludeBuiltins: false, 80 | force: true, 81 | silent: false, 82 | pedantic: false 83 | }); 84 | // Attach builtins 85 | var patterns = options.patterns; 86 | if (options.excludeBuiltins !== true) { 87 | patterns.push({ 88 | match: '__SOURCE_FILE__', 89 | replacement: function (match, offset, string, source) { 90 | return source; 91 | }, 92 | builtin: true 93 | }, { 94 | match: '__SOURCE_PATH__', 95 | replacement: function (match, offset, string, source) { 96 | return path.dirname(source); 97 | }, 98 | builtin: true 99 | }, { 100 | match: '__SOURCE_FILENAME__', 101 | replacement: function (match, offset, string, source) { 102 | return path.basename(source); 103 | }, 104 | builtin: true 105 | }, { 106 | match: '__TARGET_FILE__', 107 | // eslint-disable-next-line max-params 108 | replacement: function (match, offset, string, source, target) { 109 | return target; 110 | }, 111 | builtin: true 112 | }, { 113 | match: '__TARGET_PATH__', 114 | // eslint-disable-next-line max-params 115 | replacement: function (match, offset, string, source, target) { 116 | return path.dirname(target); 117 | }, 118 | builtin: true 119 | }, { 120 | match: '__TARGET_FILENAME__', 121 | // eslint-disable-next-line max-params 122 | replacement: function (match, offset, string, source, target) { 123 | return path.basename(target); 124 | }, 125 | builtin: true 126 | }); 127 | } 128 | 129 | // Create applause instance 130 | var applause = Applause.create(_.extend({}, options, { 131 | // Pass 132 | })); 133 | // Took code from copy task 134 | var isExpandedPair; 135 | var dirs = {}; 136 | var tally = { 137 | dirs: 0, 138 | files: 0, 139 | replacements: 0, 140 | details: [] 141 | }; 142 | this.files.forEach(function (filePair) { 143 | isExpandedPair = filePair.orig.expand || false; 144 | filePair.src.forEach(function (src) { 145 | src = unixifyPath(src); 146 | var dest = unixifyPath(filePair.dest); 147 | if (detectDestType(dest) === 'directory') { 148 | dest = (isExpandedPair) ? dest : path.join(dest, src); 149 | } 150 | 151 | if (grunt.file.isDir(src)) { 152 | grunt.file.mkdir(dest); 153 | if (options.mode !== false) { 154 | fs.chmodSync(dest, (options.mode === true) ? 155 | fs.lstatSync(src).mode : options.mode); 156 | } 157 | 158 | if (options.timestamp) { 159 | dirs[dest] = src; 160 | } 161 | 162 | tally.dirs++; 163 | } else { 164 | var res = replace(src, dest, options, applause); 165 | tally.details = tally.details.concat(res.detail); 166 | tally.replacements += res.count; 167 | syncTimestamp(src, dest); 168 | if (options.mode !== false) { 169 | fs.chmodSync(dest, (options.mode === true) ? 170 | fs.lstatSync(src).mode : options.mode); 171 | } 172 | 173 | tally.files++; 174 | } 175 | 176 | if (options.mode !== false) { 177 | fs.chmodSync(dest, (options.mode === true) ? 178 | fs.lstatSync(src).mode : options.mode); 179 | } 180 | }); 181 | }); 182 | if (options.timestamp) { 183 | Object.keys(dirs).sort(function (a, b) { 184 | return b.length - a.length; 185 | }).forEach(function (dest) { 186 | syncTimestamp(dirs[dest], dest); 187 | }); 188 | } 189 | 190 | // Warn for unmatched patterns in the file list 191 | if (options.silent !== true) { 192 | var count = 0; 193 | patterns.forEach(function (pattern) { 194 | if (pattern.builtin !== true) { // Exclude builtins 195 | var found = _.find(tally.details, ['source', pattern]); 196 | if (!found) { 197 | count++; 198 | } 199 | } 200 | }); 201 | if (count > 0) { 202 | var strWarn = [ 203 | 'Unable to match ', 204 | count, 205 | count === 1 ? ' pattern' : ' patterns' 206 | ]; 207 | if (applause.options.usePrefix === true) { 208 | strWarn.push( 209 | ' using ', 210 | applause.options.prefix, 211 | ' as a prefix' 212 | ); 213 | } 214 | 215 | strWarn.push( 216 | '.' 217 | ); 218 | if (options.pedantic === true) { 219 | grunt.fail.warn(strWarn.join('')); 220 | } else { 221 | grunt.log.warn(strWarn.join('')); 222 | } 223 | } 224 | 225 | var str = [ 226 | tally.replacements, 227 | tally.replacements === 1 ? ' replacement' : ' replacements', 228 | ' in ', 229 | tally.files, 230 | tally.files === 1 ? ' file' : ' files', 231 | '.' 232 | ]; 233 | grunt.log.ok(str.join('')); 234 | } 235 | } 236 | ); 237 | }; 238 | -------------------------------------------------------------------------------- /test/fixtures/built-in_source_file.txt: -------------------------------------------------------------------------------- 1 | @@__SOURCE_FILE__ 2 | -------------------------------------------------------------------------------- /test/fixtures/built-in_source_filename.txt: -------------------------------------------------------------------------------- 1 | @@__SOURCE_FILENAME__ 2 | -------------------------------------------------------------------------------- /test/fixtures/built-in_source_path.txt: -------------------------------------------------------------------------------- 1 | @@__SOURCE_PATH__ 2 | -------------------------------------------------------------------------------- /test/fixtures/built-in_target_file.txt: -------------------------------------------------------------------------------- 1 | @@__TARGET_FILE__ 2 | -------------------------------------------------------------------------------- /test/fixtures/built-in_target_filename.txt: -------------------------------------------------------------------------------- 1 | @@__TARGET_FILENAME__ 2 | -------------------------------------------------------------------------------- /test/fixtures/built-in_target_path.txt: -------------------------------------------------------------------------------- 1 | @@__TARGET_PATH__ 2 | -------------------------------------------------------------------------------- /test/fixtures/fail.txt: -------------------------------------------------------------------------------- 1 | @@key 2 | -------------------------------------------------------------------------------- /test/fixtures/simple.txt: -------------------------------------------------------------------------------- 1 | @@key 2 | -------------------------------------------------------------------------------- /test/fixtures/verbose.txt: -------------------------------------------------------------------------------- 1 | @@key 2 | -------------------------------------------------------------------------------- /test/fixtures/warning.txt: -------------------------------------------------------------------------------- 1 | @@key 2 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var test = require('ava'); 2 | var grunt = require('grunt'); 3 | var path = require('path'); 4 | var exec = require('child_process').exec; 5 | var rimraf = require('rimraf'); 6 | 7 | test('should replace simple key with value', function (t) { 8 | var result = grunt.file.read('temp/simple.txt'); 9 | t.is(result, 'value\n'); 10 | }); 11 | 12 | test.cb('should verbose when "silent" is false', function (t) { 13 | exec('grunt replace:verbose', { 14 | cwd: path.join(__dirname, '..') 15 | }, function (error, stdout) { 16 | t.not(stdout.indexOf('1 replacement in 1 file.'), -1); 17 | t.end(); 18 | }); 19 | }); 20 | 21 | test.cb('should warn when no matches exist', function (t) { 22 | exec('grunt replace:warning', { 23 | cwd: path.join(__dirname, '..') 24 | }, function (error, stdout) { 25 | t.is(stdout.indexOf('Warning: Unable to match 1 pattern'), -1); 26 | t.end(); 27 | }); 28 | }); 29 | 30 | test.cb('should fail when no matches exist and "pedantic" is true', function (t) { 31 | exec('grunt replace:fail', { 32 | cwd: path.join(__dirname, '..') 33 | }, function (error, stdout) { 34 | t.is(error.code, 6); 35 | t.not(stdout.indexOf('Warning: Unable to match 1 pattern'), -1); 36 | t.end(); 37 | }); 38 | }); 39 | 40 | // Built-in 41 | 42 | test('should replace using built-in replacement (__SOURCE_FILE__)', function (t) { 43 | var result = grunt.file.read('temp/built-in_source_file.txt'); 44 | t.is(result, 'test/fixtures/built-in_source_file.txt\n'); 45 | }); 46 | 47 | test('should replace using built-in replacement (__SOURCE_PATH__)', function (t) { 48 | var result = grunt.file.read('temp/built-in_source_path.txt'); 49 | t.is(result, 'test/fixtures\n'); 50 | }); 51 | 52 | test('should replace using built-in replacement (__SOURCE_FILENAME__)', function (t) { 53 | var result = grunt.file.read('temp/built-in_source_filename.txt'); 54 | t.is(result, 'built-in_source_filename.txt\n'); 55 | }); 56 | 57 | test('should replace using built-in replacement (__TARGET_FILE__)', function (t) { 58 | var result = grunt.file.read('temp/built-in_target_file.txt'); 59 | t.is(result, 'temp/built-in_target_file.txt\n'); 60 | }); 61 | 62 | test('should replace using built-in replacement (__TARGET_PATH__)', function (t) { 63 | var result = grunt.file.read('temp/built-in_target_path.txt'); 64 | t.is(result, 'temp\n'); 65 | }); 66 | 67 | test('should replace using built-in replacement (__TARGET_FILENAME__)', function (t) { 68 | var result = grunt.file.read('temp/built-in_target_filename.txt'); 69 | t.is(result, 'built-in_target_filename.txt\n'); 70 | }); 71 | 72 | test.after.always.cb(function (t) { 73 | rimraf('temp', t.end); 74 | }); 75 | --------------------------------------------------------------------------------