├── .github ├── stale.yml └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── bin └── grunt-string-replace ├── package-lock.json ├── package.json ├── tasks ├── lib │ └── string-replace.js └── string-replace.js └── test ├── fixtures ├── bar.txt ├── baz.txt └── foo.txt └── string-replace_test.js /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '36 9 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | tmp_baz/ 3 | *.sublime-* 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | tmp_baz/ 3 | *.sublime-* 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "14" 4 | - "16" 5 | - "17" 6 | install: 7 | - npm update -g npm 8 | - npm install -g grunt-cli 9 | - npm install 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-string-replace 3 | * https://github.com/eruizdechavez/grunt-string-replace 4 | * 5 | * Copyright (c) 2016 Erick Ruiz de Chavez 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | module.exports = function(grunt) { 10 | 'use strict'; 11 | 12 | grunt.initConfig({ 13 | base: { 14 | env: { 15 | test: 'replaced!' 16 | } 17 | }, 18 | 19 | package: { 20 | replacement: 'replaced!' 21 | }, 22 | 23 | jshint: { 24 | options: { 25 | curly: true, 26 | eqeqeq: true, 27 | immed: true, 28 | latedef: true, 29 | newcap: true, 30 | noarg: true, 31 | sub: true, 32 | undef: true, 33 | boss: true, 34 | eqnull: true, 35 | node: true 36 | }, 37 | lint: ['Gruntfile.js', 'tasks/**/*.js', 'test/**/*.js'] 38 | }, 39 | 40 | clean: ['tmp/', 'tmp_baz/'], 41 | 42 | nodeunit: { 43 | files: ['test/**/*.js'] 44 | }, 45 | 46 | watch: { 47 | files: '<%= jshint.lint %>', 48 | tasks: ['jshint', 'test'] 49 | }, 50 | 51 | copy: { 52 | fixtures: { 53 | files: [{ 54 | dest: 'tmp/foo/1.txt', 55 | src: 'test/fixtures/foo.txt' 56 | }, { 57 | dest: 'tmp/foo/2.txt', 58 | src: 'test/fixtures/foo.txt' 59 | }, { 60 | dest: 'tmp/bar/1.txt', 61 | src: 'test/fixtures/foo.txt' 62 | }, { 63 | dest: 'tmp/bar/2.txt', 64 | src: 'test/fixtures/foo.txt' 65 | }] 66 | } 67 | }, 68 | 69 | 'string-replace': { 70 | single_file: { 71 | files: { 72 | 'tmp/foo.txt': 'test/fixtures/foo.txt', 73 | 'tmp/baz.txt': 'test/fixtures/baz.txt' 74 | }, 75 | options: { 76 | replacements: [{ 77 | pattern: '[test:string]', 78 | replacement: 'replaced!' 79 | }, { 80 | pattern: /\[test a:regex \d{3,}\]/, 81 | replacement: 'replaced!' 82 | }, { 83 | pattern: /\[test b:regex \d{3,}\]/g, 84 | replacement: 'replaced!' 85 | }, { 86 | pattern: /\[test c:regex \d{3,}\]/g, 87 | replacement: 'replaced!' 88 | }, { 89 | pattern: /\[test d:regex \d{3,}\]/ig, 90 | replacement: 'replaced!' 91 | }, { 92 | pattern: /\[test e:regex \d{3,}\]/ig, 93 | replacement: '<%= package.replacement %>' 94 | }, { 95 | pattern: /\[test f:regex \d{3,}\]/g, 96 | replacement: function(match, p1) { 97 | var env = grunt.option('env').toLowerCase(); 98 | return grunt.config.get(['base', 'env', env]); 99 | } 100 | }] 101 | } 102 | }, 103 | mutli_same_path: { 104 | files: { 105 | 'tmp/foo/': 'tmp/foo/*.txt' 106 | }, 107 | options: { 108 | replacements: [{ 109 | pattern: '[test:string]', 110 | replacement: 'replaced!' 111 | }, { 112 | pattern: /\[test a:regex \d{3,}\]/, 113 | replacement: 'replaced!' 114 | }, { 115 | pattern: /\[test b:regex \d{3,}\]/g, 116 | replacement: 'replaced!' 117 | }, { 118 | pattern: /\[test c:regex \d{3,}\]/g, 119 | replacement: 'replaced!' 120 | }, { 121 | pattern: /\[test d:regex \d{3,}\]/ig, 122 | replacement: 'replaced!' 123 | }, { 124 | pattern: /\[test e:regex \d{3,}\]/ig, 125 | replacement: '<%= package.replacement %>' 126 | }, { 127 | pattern: /\[test f:regex \d{3,}\]/g, 128 | replacement: function(match, p1) { 129 | var env = grunt.option('env').toLowerCase(); 130 | return grunt.config.get(['base', 'env', env]); 131 | } 132 | }] 133 | } 134 | }, 135 | mutli_diff_path: { 136 | files: { 137 | 'tmp_baz/': 'tmp/bar/*.txt' 138 | }, 139 | options: { 140 | replacements: [{ 141 | pattern: '[test:string]', 142 | replacement: 'replaced!' 143 | }, { 144 | pattern: /\[test a:regex \d{3,}\]/, 145 | replacement: 'replaced!' 146 | }, { 147 | pattern: /\[test b:regex \d{3,}\]/g, 148 | replacement: 'replaced!' 149 | }, { 150 | pattern: /\[test c:regex \d{3,}\]/g, 151 | replacement: 'replaced!' 152 | }, { 153 | pattern: /\[test d:regex \d{3,}\]/ig, 154 | replacement: 'replaced!' 155 | }, { 156 | pattern: /\[test e:regex \d{3,}\]/ig, 157 | replacement: '<%= package.replacement %>' 158 | }, { 159 | pattern: /\[test f:regex \d{3,}\]/g, 160 | replacement: function(match, p1) { 161 | var env = grunt.option('env').toLowerCase(); 162 | return grunt.config.get(['base', 'env', env]); 163 | } 164 | }] 165 | } 166 | } 167 | } 168 | }); 169 | 170 | // Load nom tasks. 171 | grunt.loadNpmTasks('grunt-contrib-jshint'); 172 | grunt.loadNpmTasks('grunt-contrib-clean'); 173 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 174 | grunt.loadNpmTasks('grunt-contrib-watch'); 175 | grunt.loadNpmTasks('grunt-contrib-copy'); 176 | 177 | // Load local tasks. 178 | grunt.loadTasks('tasks'); 179 | 180 | grunt.registerTask('test', ['clean', 'copy', 'string-replace', 'nodeunit']); 181 | grunt.registerTask('default', ['jshint', 'test']); 182 | }; 183 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Erick Ruiz de Chavez 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-string-replace [![Build Status](https://app.travis-ci.com/eruizdechavez/grunt-string-replace.svg?branch=master)](https://app.travis-ci.com/eruizdechavez/grunt-string-replace) 2 | 3 | Replaces strings on files by using string or regex patterns. Attempts to be a [String.prototype.replace](http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4.11) adapter task for your grunt project. 4 | 5 | ## Getting Started 6 | 7 | This plugin requires node `>= 0.10.0`, Grunt `>= 0.4.0` and npm `>= 1.4.15` (latest stable is recommended). 8 | 9 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 10 | 11 | ```shell 12 | npm install grunt-string-replace --save-dev 13 | ``` 14 | 15 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 16 | 17 | ```javascript 18 | grunt.loadNpmTasks("grunt-string-replace"); 19 | ``` 20 | 21 | _If you're still using grunt v0.3.x it's strongly recommended that [you upgrade](http://gruntjs.com/upgrading-from-0.3-to-0.4), but in case you can't please use [v0.1.1-1](https://github.com/eruizdechavez/grunt-string-replace/tree/0.1.1-1)._ 22 | 23 | ## Configuration 24 | 25 | Inside your `Gruntfile.js` file add a section named `string-replace`. This section specifies the files to edit, destinations, patterns and replacements. 26 | 27 | ### Parameters 28 | 29 | #### files `object` 30 | 31 | Defines what files this task will edit. Grunt itself has very powerful [abstractions](http://gruntjs.com/configuring-tasks#files), so it is **highly recommended** you understand the different ways to specify them. Learn more at [Gruntfile Files mapping](http://gruntjs.com/configuring-tasks#files), some options incude compact format, files object format and files array format. 32 | 33 | #### options `object` 34 | 35 | Controls how this task operates and should contain key:value pairs, see options below. 36 | 37 | ##### options.saveUnchanged `boolean` 38 | 39 | By default `true` this flag will instruct `grunt-string-replace` to copy the files on `options.replacements` patterns even if there are no replacing matches. 40 | 41 | By setting this flag to `false` files that have not changed (no replacements done) will not be saved on the new location. This will speed up the task if there is a large number of files. 42 | 43 | ##### options.replacements `array` 44 | 45 | This option will hold all your pattern/replacement pairs. A pattern/replacement pair should contain key:value pairs containing: 46 | 47 | - pattern `string` or `regex` 48 | - replacement `string` 49 | 50 | ```javascript 51 | options: { 52 | replacements: [ 53 | { 54 | pattern: /\/(asdf|qwer)\//gi, 55 | replacement: '"$1"', 56 | }, 57 | { 58 | pattern: ",", 59 | replacement: ";", 60 | }, 61 | ]; 62 | } 63 | ``` 64 | 65 | ### Notes 66 | 67 | - If the pattern is a string, only the first occurrence will be replaced, as stated on [String.prototype.replace](http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4.11). 68 | - When using Grunt templates, be aware that some security checks are implemented by LoDash and may alter your content (mainly to avoid XSS). To avoid this, see the advanced example below. 69 | 70 | ## Examples 71 | 72 | ### Multiple files and multiple replacements 73 | 74 | ```javascript 75 | 'string-replace': { 76 | dist: { 77 | files: { 78 | 'dest/': 'src/**', 79 | 'prod/': ['src/*.js', 'src/*.css'], 80 | }, 81 | options: { 82 | replacements: [{ 83 | pattern: /\/(asdf|qwer)\//ig, 84 | replacement: ''$1'' 85 | }, { 86 | pattern: ',', 87 | replacement: ';' 88 | }] 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | ### Simple inline content 95 | 96 | ```javascript 97 | 'string-replace': { 98 | inline: { 99 | files: { 100 | 'dest/': 'src/**', 101 | }, 102 | options: { 103 | replacements: [ 104 | // place files inline example 105 | { 106 | pattern: '', 107 | replacement: '' 108 | } 109 | ] 110 | } 111 | } 112 | } 113 | ``` 114 | 115 | ### Using files' expand options 116 | 117 | For more details, see Grunt's documentation about [dynamic files object](http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically). 118 | 119 | ```javascript 120 | 'string-replace': { 121 | dist: { 122 | files: [{ 123 | expand: true, 124 | cwd: 'src/', 125 | src: '**/*', 126 | dest: 'dist/' 127 | }], 128 | options: { 129 | replacements: [{ 130 | pattern: 'hello', 131 | replacement: 'howdy' 132 | }] 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | ### Advanced inline 139 | 140 | Since grunt-string-replace is basically a wrapper of [String.prototype.replace](http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4.11) you can also provide a function as a replacement pattern instead of a string or a template; as a nice added bonus to using a replacement function, grunt-string-replace will provide 2 extra arguments apart from the ones documented in the link below: `src` and `dest`. To get more details about how to use a function as replacement pattern I recommend you to read [Specifying a function as a parameter](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter). 141 | 142 | We will be reading file names from HTML comments and use the paths later to fetch the content and insert it inside a resulting HTML. Assuming the following setup: 143 | 144 | _src/index.html_ 145 | 146 | ```html 147 | 148 | content here 149 | 150 | ``` 151 | 152 | _src/partials/header.html_ 153 | 154 | ```html 155 | 156 | 157 | 158 | 159 | ``` 160 | 161 | _src/partials/footer.html_ 162 | 163 | ```html 164 | 165 | ``` 166 | 167 | _Gruntfile.js_ 168 | 169 | ```javascript 170 | 'use strict'; 171 | 172 | module.exports = function (grunt) { 173 | // Project configuration. 174 | grunt.initConfig({ 175 | config: { 176 | src: 'src/*.html' 177 | dist: 'dist/' 178 | }, 179 | 'string-replace': { 180 | dist: { 181 | files: { 182 | '<%= config.dist %>': '<%= config.src %>' 183 | }, 184 | options: { 185 | replacements: [{ 186 | pattern: //ig, 187 | replacement: function (match, p1) { 188 | return grunt.file.read(grunt.config.get('config.dist') + p1); 189 | } 190 | }] 191 | } 192 | } 193 | } 194 | }); 195 | 196 | // These plugins provide necessary tasks. 197 | grunt.loadNpmTasks('grunt-string-replace'); 198 | 199 | // Default task. 200 | grunt.registerTask('default', ['string-replace']); 201 | }; 202 | ``` 203 | 204 | After executing grunt we get the following: 205 | 206 | _dist/index.html_ 207 | 208 | ```html 209 | 210 | 211 | 212 | content here 213 | 214 | 215 | ``` 216 | 217 | ## Contributing 218 | 219 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt][grunt]. 220 | 221 | ## Release History 222 | 223 | ### 1.3.3 224 | 225 | - Sync docs, travis settings and code with npm (made a mess between GitHub and npm on 1.3.2). 226 | 227 | ### 1.3.2 228 | 229 | - Updated several dependencies. Some updates require node >= 8. If you need node < 8 stay on 1.3.1. Thanks to [anand-gopinath](https://github.com/anand-gopinath) for flagging some of them. 230 | 231 | ### 1.3.1 232 | 233 | - Misc updates. 234 | 235 | ### 1.3.0 236 | 237 | - Fix #39, a typo in the documentation 238 | - Inject src and dest into the callback function when replacement is a function; this allows the replacement function to use the old and/or new file paths/names for further processing 239 | - Add total files modified and update file created logging to verbose. Contributed by [tHBp](https://github.com/tHBp) 240 | 241 | ### 1.2.1 242 | 243 | - Update project URLs 244 | - Update dependencies 245 | 246 | ### 1.2.0 247 | 248 | - Add `saveUnchanged` option to control weather unmodified files are saved or not. 249 | - Add iojs to Travis CI. 250 | 251 | ### 1.1.1 252 | 253 | - Add Node.js v0.12 to Travis CI 254 | 255 | ### 1.1.0 256 | 257 | - Update dependencies 258 | - Add new log and debug messages 259 | - Improved file handling; grunt-string-replace will not copy files that are not modified (no replacements executed). Contributed by [iabw](https://github.com/iabw) 260 | 261 | ### 1.0.0 262 | 263 | - Update dependencies 264 | - Update README.md 265 | - Well deserved bump to 1.0.0 (its been stable for long enough now) 266 | 267 | ### 0.2.8 268 | 269 | - Added log message after file is succesfully created. Contributed by [donaldpipowitch](https://github.com/donaldpipowitch) 270 | - Do not report error if one of the replacements resolves to a folder 271 | 272 | ### 0.2.7 273 | 274 | - External libraries are deprecated on Grunt 0.4.2 275 | 276 | - Remove grunt.util.\_ as it is not really required 277 | - Replace grunt.util.async with async 278 | 279 | ### 0.2.6 280 | 281 | - Update Getting Started section 282 | - Fix broken link to Gruntfile's File section (#18) 283 | 284 | ### 0.2.5 285 | 286 | - Fix for #16 287 | - Fix for Travis CI config file 288 | - Added error handling to finish the task if something did not work as expected instead of just fail silently 289 | - Updated dev dependencies to latest stable versions 290 | 291 | ### 0.2.4 292 | 293 | - Asynchronously loop files. Original idea contributed by [maxnachlinger](https://github.com/maxnachlinger) 294 | - Inline replacing example on README.md. Contributed by [willfarrell](https://github.com/willfarrell) 295 | 296 | ### 0.2.3 297 | 298 | - Removed dependency with grunt-lib-contrib due to deprecation of 'options' method in favor of Grunt's 'options' util. 299 | - Updated grunt-contrib-jshint version in package.json to 0.3.0 300 | - Updated grunt-contrib-watch version in package.json to 0.3.1 301 | - Updated grunt version in package.json to 0.4.1 302 | - Added Node.js v0.10 to Travis CI config file 303 | 304 | ### 0.2.2 305 | 306 | - Added support to be used as npm module. Contributed by [thanpolas](https://github.com/thanpolas). 307 | 308 | ### 0.2.1 309 | 310 | - Updated dependencies for Grunt 0.4.0. 311 | 312 | ### 0.2.0 313 | 314 | - Added Support for grunt 0.4.0. This version will not support grunt 0.3.x, if you need to use it then `npm install grunt-string-replace@0.1`. 315 | 316 | ### 0.1.1-1 317 | 318 | - Added Clean task (and dev dependency) to remove test generated file before testing. 319 | - Added Sublime Text project files and test generated file to npm ignore list. 320 | 321 | ### 0.1.1 322 | 323 | - Fix dependency with grunt-lib-contrib. 324 | 325 | ### 0.1.0-1 326 | 327 | - Fixed a typo on package.json description. 328 | - Added a note about string pattern behavior. 329 | 330 | ### 0.1.0 331 | 332 | - Initial release. 333 | 334 | ## License 335 | 336 | Copyright (c) 2016 Erick Ruiz de Chavez. 337 | Licensed under the MIT license. 338 | 339 | [grunt]: http://gruntjs.com/ 340 | -------------------------------------------------------------------------------- /bin/grunt-string-replace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('grunt').npmTasks('grunt-string-replace').cli(); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-string-replace", 3 | "description": "Replaces strings on files by using string or regex patterns. Attempts to be a String.prototype.replace adapter task for your grunt project.", 4 | "version": "1.3.3", 5 | "homepage": "https://github.com/eruizdechavez/grunt-string-replace", 6 | "author": { 7 | "name": "Erick Ruiz de Chavez", 8 | "email": "eruizdechavez@fastmail.com", 9 | "url": "http://erickruizdechavez.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/eruizdechavez/grunt-string-replace.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/eruizdechavez/grunt-string-replace/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/eruizdechavez/grunt-string-replace/blob/master/LICENSE-MIT" 22 | } 23 | ], 24 | "main": "tasks/string-replace", 25 | "bin": "bin/grunt-string-replace", 26 | "engines": { 27 | "node": ">= 0.10.0", 28 | "npm": ">= 1.4.15" 29 | }, 30 | "scripts": { 31 | "test": "grunt --verbose --env=test", 32 | "preversion": "npm test", 33 | "postversion": "git push && git push --tags" 34 | }, 35 | "devDependencies": { 36 | "grunt": "^1.0.0", 37 | "grunt-contrib-clean": "^2.0.1", 38 | "grunt-contrib-copy": "^1.0.0", 39 | "grunt-contrib-jshint": "^3.2.0", 40 | "grunt-contrib-nodeunit": "^4.0.0", 41 | "grunt-contrib-watch": "^1.0.0" 42 | }, 43 | "keywords": [ 44 | "gruntplugin", 45 | "string", 46 | "replace", 47 | "regex" 48 | ], 49 | "dependencies": { 50 | "async": "^3.2.3", 51 | "chalk": "^4.1.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tasks/lib/string-replace.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-string-replace 3 | * https://github.com/eruizdechavez/grunt-string-replace 4 | * 5 | * Copyright (c) 2016 Erick Ruiz de Chavez 6 | * Licensed under the MIT license. 7 | */ 8 | var util = require('util'), 9 | async = require('async'), 10 | chalk = require('chalk'), 11 | counter = 0; 12 | 13 | exports.init = function(grunt) { 14 | 'use strict'; 15 | 16 | var path = require('path'); 17 | 18 | var detectDestType = function(dest) { 19 | if (dest[dest.length - 1] === '/') { 20 | return 'directory'; 21 | } else { 22 | return 'file'; 23 | } 24 | }; 25 | 26 | var unixifyPath = function(filepath) { 27 | var path = ''; 28 | if (process.platform === 'win32') { 29 | path = filepath.replace(/\\/g, '/'); 30 | } else { 31 | path = filepath; 32 | } 33 | return path; 34 | }; 35 | 36 | exports.replace = function(files, replacements, options, replace_done) { 37 | var content, newContent, dest; 38 | 39 | if (!replace_done) { 40 | replace_done = options; 41 | options = {}; 42 | } 43 | 44 | if (!options.hasOwnProperty("saveUnchanged")) { 45 | options.saveUnchanged = true; 46 | } else { 47 | options.saveUnchanged = !!options.saveUnchanged; 48 | } 49 | 50 | async.forEach(files, function(file, files_done) { 51 | async.forEach(file.src, function(src, src_done) { 52 | grunt.log.debug('working on file', src); 53 | 54 | if (!grunt.file.exists(src)) { 55 | grunt.log.debug('file not fount', src); 56 | return src_done(src + ' file not found'); 57 | } 58 | 59 | if (grunt.file.isDir(src)) { 60 | grunt.log.debug('source file is a directory', src); 61 | return src_done(); 62 | } 63 | 64 | if (detectDestType(file.dest) === 'directory') { 65 | grunt.log.debug('destination is a directory'); 66 | 67 | if (grunt.file.doesPathContain(file.dest, src)) { 68 | dest = path.join(file.dest, src.replace(file.dest, '')); 69 | } else { 70 | dest = path.join(file.dest, src); 71 | } 72 | } else { 73 | dest = file.dest; 74 | } 75 | 76 | dest = unixifyPath(dest); 77 | grunt.log.debug('unixified path is', dest); 78 | content = grunt.file.read(src); 79 | newContent = exports.multi_str_replace(content, replacements, src, dest); 80 | 81 | if (content !== newContent || options.saveUnchanged) { 82 | grunt.file.write(dest, newContent); 83 | counter+=1; 84 | grunt.verbose.writeln('File ' + chalk.cyan(dest) + ' created.'); 85 | } else { 86 | grunt.log.writeln('File ' + chalk.cyan(dest) + ' ' + chalk.red('not') + ' created; No replacements found.'); 87 | } 88 | 89 | return src_done(); 90 | 91 | }, files_done); 92 | }, function(err) { 93 | if (err) { 94 | grunt.log.error(err); 95 | replace_done(false); 96 | } 97 | grunt.log.writeln('\n'+ chalk.cyan(counter) + ' files created'); 98 | replace_done(); 99 | }); 100 | }; 101 | 102 | exports.normalize_replacements = function(replacements) { 103 | return replacements.map(function(replacement) { 104 | return [replacement.pattern, replacement.replacement]; 105 | }); 106 | }; 107 | 108 | var decorate_replace_function = function(replacement, src, dest) { 109 | grunt.log.debug('decorating replace function with extra arguments'); 110 | 111 | return function () { 112 | grunt.log.debug('running decorated replace function with extra arguments'); 113 | var args = Array.prototype.slice.apply(arguments); 114 | args.push(src, dest); 115 | return replacement.apply(null, args); 116 | }; 117 | }; 118 | 119 | exports.multi_str_replace = function(string, replacements, src, dest) { 120 | return replacements.reduce(function(content, replacements) { 121 | var pattern = replacements[0]; 122 | var replacement = replacements[1]; 123 | 124 | if (typeof replacement === 'function') { 125 | grunt.log.debug('replacing function with augmented one'); 126 | replacement = decorate_replace_function(replacement, src, dest); 127 | } 128 | 129 | return content.replace(pattern, replacement); 130 | }, string); 131 | }; 132 | 133 | return exports; 134 | }; 135 | -------------------------------------------------------------------------------- /tasks/string-replace.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-string-replace 3 | * https://github.com/eruizdechavez/grunt-string-replace 4 | * 5 | * Copyright (c) 2016 Erick Ruiz de Chavez 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | module.exports = function(grunt) { 10 | 'use strict'; 11 | 12 | // if grunt is not provided, then expose internal API 13 | if ('object' !== typeof(grunt)) { 14 | return require('./lib/string-replace').init(require('grunt')); 15 | } 16 | 17 | var string_replace = require('./lib/string-replace').init(grunt); 18 | 19 | grunt.registerMultiTask('string-replace', 'String Replace Task.', function() { 20 | var done = this.async(), 21 | options = this.options({ 22 | replacements: [] 23 | }), 24 | replacements; 25 | 26 | replacements = string_replace.normalize_replacements(options.replacements); 27 | 28 | string_replace.replace(this.files, replacements, options, done); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /test/fixtures/bar.txt: -------------------------------------------------------------------------------- 1 | Replace string 2 | replaced! === replaced! 3 | [test:string] === not replaced! 4 | 5 | Replace RegEx, not global 6 | replaced! === replaced! 7 | [test a:regex 002] === not replaced! 8 | 9 | Replace RegEx, global 10 | replaced! === replaced! 11 | replaced! === replaced! 12 | 13 | Replace RegEx, case sensitive 14 | replaced! === replaced! 15 | [test C:regex 006] === not replaced! 16 | 17 | Replace RegEx, case insensitive 18 | replaced! === replaced! 19 | replaced! === replaced! 20 | 21 | Replace RegEx, with template value 22 | replaced! === replaced! 23 | 24 | Replace RegEx, with function 25 | replaced! === replaced! 26 | -------------------------------------------------------------------------------- /test/fixtures/baz.txt: -------------------------------------------------------------------------------- 1 | Replace string 2 | There are no replacement patterns on this file. 3 | -------------------------------------------------------------------------------- /test/fixtures/foo.txt: -------------------------------------------------------------------------------- 1 | Replace string 2 | [test:string] === replaced! 3 | [test:string] === not replaced! 4 | 5 | Replace RegEx, not global 6 | [test a:regex 001] === replaced! 7 | [test a:regex 002] === not replaced! 8 | 9 | Replace RegEx, global 10 | [test b:regex 003] === replaced! 11 | [test b:regex 004] === replaced! 12 | 13 | Replace RegEx, case sensitive 14 | [test c:regex 005] === replaced! 15 | [test C:regex 006] === not replaced! 16 | 17 | Replace RegEx, case insensitive 18 | [test d:regex 007] === replaced! 19 | [test D:regex 008] === replaced! 20 | 21 | Replace RegEx, with template value 22 | [test e:regex 007] === replaced! 23 | 24 | Replace RegEx, with function 25 | [test f:regex 008] === replaced! 26 | -------------------------------------------------------------------------------- /test/string-replace_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-string-replace 3 | * https://github.com/eruizdechavez/grunt-string-replace 4 | * 5 | * Copyright (c) 2016 Erick Ruiz de Chavez 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | var grunt = require('grunt'), 10 | string_replace = require('../tasks/lib/string-replace').init(grunt); 11 | 12 | var Replacement = function(pattern, replacement) { 13 | return { 14 | pattern: pattern || '', 15 | replacement: replacement || '' 16 | }; 17 | }; 18 | 19 | exports['string-replace'] = { 20 | 'normalize_replacements': function(test) { 21 | test.expect(2); 22 | 23 | var replacements = [], i = 0; 24 | 25 | for (; i < 10; i++) { 26 | replacements.push(new Replacement()); 27 | } 28 | 29 | var normalized = string_replace.normalize_replacements(replacements); 30 | test.equal(Array.isArray(normalized), true, 'normalized should be an array'); 31 | 32 | var total = normalized.reduce(function(subtotal, item) { 33 | return subtotal + item.length; 34 | }, 0); 35 | test.equal(total, 20, 'normalized should have n * 2 items'); 36 | 37 | test.done(); 38 | }, 39 | 40 | 'multi_str_replace': function(test) { 41 | test.expect(1); 42 | test.equal(string_replace.multi_str_replace('ASDF QWER', [ 43 | ['ASDF', 'Hello'], 44 | [/qwer/i, 'World'] 45 | ]), 'Hello World', 'should replace a set of replacements'); 46 | test.done(); 47 | }, 48 | 49 | 'replace single file': function(test) { 50 | test.expect(1); 51 | 52 | var expected = grunt.file.read('test/fixtures/bar.txt'), 53 | actual = grunt.file.read('tmp/foo.txt'); 54 | 55 | test.equal(actual, expected, 'should execute replacements and save a new file'); 56 | test.done(); 57 | }, 58 | 59 | 'replace multi, same path': function(test) { 60 | test.expect(2); 61 | 62 | var expected = grunt.file.read('test/fixtures/bar.txt'), 63 | actual1 = grunt.file.read('tmp/foo/1.txt'), 64 | actual2 = grunt.file.read('tmp/foo/2.txt'); 65 | 66 | test.equal(actual1, expected, 'should execute replacements and replace src (1)'); 67 | test.equal(actual2, expected, 'should execute replacements and replace src (2)'); 68 | test.done(); 69 | }, 70 | 71 | 'replace multi, diff path': function(test) { 72 | test.expect(2); 73 | 74 | var expected = grunt.file.read('test/fixtures/bar.txt'), 75 | actual1 = grunt.file.read('tmp_baz/tmp/bar/1.txt'), 76 | actual2 = grunt.file.read('tmp_baz/tmp/bar/2.txt'); 77 | 78 | test.equal(actual1, expected, 'should execute replacements and save the file the new path (follows dest as base path and src as file path/name'); 79 | test.equal(actual2, expected, 'should execute replacements and save the file the new path (follows dest as base path and src as file path/name'); 80 | test.done(); 81 | } 82 | }; 83 | --------------------------------------------------------------------------------