diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..d6e7b46
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,735 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ afterEach
+ runCli
+ tmpPrefix
+ a
+ async (ct)
+ async
+ async (t)
+ outputFileE
+ .input
+ tmpPRefix
+ .output
+ tmp(
+ tmp.fil
+ .file(
+ filename
+ tmpname
+ replaceFnPath
+ replaceFn
+ beforeEach
+ contentIndex
+ -
+ ^\s*?(‑.*?)\s\s\s*?(.*?)\s*\[(.*)\]$
+ ^\s*?(‑.*?)\s\s\s*(.*?)\s*\[(.*)\]$
+ ^\s*?(‑.*?)\s\s*(.*?)\s*\[(.*)\]$
+ ^\s*?(‑(?:(?:\S\s)|(?:\s\S)|(?:\S))*?)\s{3,}?(\S[\s\S]*?)\s{2,}\[(.+)\]$
+ nbsp
+ "
+ output
+ input
+ readFileSync
+
+
+ checkSyncAsync
+ ct, result
+ async ct
+ async t
+ ‑
+ | $1 | $3 | $2 |
+ `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+ true
+ true
+
+
+ true
+ DEFINITION_ORDER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $PROJECT_DIR$/README.md
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1539295920729
+
+
+ 1539295920729
+
+
+
+
+
+ 1539577933627
+
+
+
+ 1539577933627
+
+
+ 1539578507344
+
+
+
+ 1539578507344
+
+
+ 1539578554742
+
+
+
+ 1539578554742
+
+
+ 1539578744158
+
+
+
+ 1539578744158
+
+
+ 1539578861970
+
+
+
+ 1539578861970
+
+
+ 1539605480617
+
+
+
+ 1539605480617
+
+
+ 1539605653126
+
+
+
+ 1539605653126
+
+
+ 1539606242877
+
+
+
+ 1539606242877
+
+
+ 1539606700835
+
+
+
+ 1539606700835
+
+
+ 1539607341856
+
+
+
+ 1539607341856
+
+
+ 1539734798020
+
+
+
+ 1539734798020
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 840c039..2610fb5 100644
--- a/README.md
+++ b/README.md
@@ -61,10 +61,16 @@ FRS-replace [options]
| \ | string | String or path to replacement function file (see ‑‑replace‑fn switch for details) |
#### Options:
+> Note: Every boolean option can be negated with use of `--no-` prefix, e.g. `--stdout` or `--no-stdout` turn stdout output on or off, respectively.
+
+> Note: Object types can be set using [dot notation](https://github.com/yargs/yargs-parser#dot-notation). So, e.g. if you want to pass `utf8` value under in-opts encoding field you should write `--in-opts.encoding utf8`.
+
| Option | Type | Default | Description |
| --- | --- | --- | --- |
|‑i, ‑‑input | string | *-* | File to read & replace from |
+ | ‑‑in-opts | string or object | utf8 | Passed to [readFileSync](https://nodejs.org/api/fs.html#fs_fs_readfilesync_path_options) when reading input file |
| ‑o, ‑‑output | string | *-* | Output file name/path (replaces the file if it already exists and creates any intermediate directories if they don't already exist) |
+ | ‑‑out-opts | string or object | utf8 | Passed as options argument of [write's .sync](https://www.npmjs.com/package/write#sync) |
| ‑f, ‑‑flags | combination of *gim* flags | g | [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Syntax) flags |
| ‑c, ‑‑content | string | *-* | Content to be replaced (takes precedence over stream & file input) |
| ‑‑stdout | boolean | true if piped input present, false otherwise | Force sending output on stdout |
diff --git a/package.json b/package.json
index 11801c6..fc0c74e 100644
--- a/package.json
+++ b/package.json
@@ -11,15 +11,19 @@
"prerelease": "standard --fix && yarn test",
"release": "standard-version",
"postrelease": "git push --follow-tags origin master && yarn publish",
+ "pretest": "standard",
"test": "yarn test:unit --100",
- "pretest:unit": "standard",
- "test:unit": "tap ./src/*.spec.js -J",
- "posttest:unit": "tap --coverage-report=html"
+ "posttest": "tap --coverage-report=html",
+ "pretest:unit": "standard --fix",
+ "test:unit": "tap ./src/*.spec.js -J"
},
"nyc": {
"exclude": "**/*.spec.js",
"include": "src/**"
},
+ "yargs": {
+ "camel-case-expansion": false
+ },
"devDependencies": {
"standard": "^12.0.1",
"standard-version": "^4.4.0",
diff --git a/src/cli.js b/src/cli.js
index 5fbfc7f..13cc2ae 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -43,12 +43,22 @@ require('get-stdin')().then((stdin) => {
.string('i')
.nargs('i', 1)
+ .option('in-opts')
+ .describe('in-opts', 'Passed to fs.readFileSync when reading input file')
+ .default('in-opts', void 0, 'utf8') // will use node's default value
+ .implies('in-opts', 'i')
+
.option('o')
.alias('o', 'output')
.describe('o', 'Output file name/path (replaces the file if it already exists and creates any intermediate directories if they don\'t already exist)')
.string('o')
.nargs('o', 1)
+ .option('out-opts')
+ .describe('out-opts', 'Passed as options argument of write\'s .sync method')
+ .default('out-opts', void 0, 'utf8') // will use node's default value
+ .implies('out-opts', 'o')
+
.option('f')
.alias('f', 'flags')
.describe('f', 'RegExp flags (supporting gim)')
@@ -88,9 +98,11 @@ require('get-stdin')().then((stdin) => {
result = require('./replace').sync({
content: argv.c,
input: argv.i,
+ inputOptions: argv['in-opts'],
regex: new RegExp(argv.regex, argv.f),
replacement: argv.r ? require(argv.replacement) : argv.replacement,
- output: argv.o
+ output: argv.o,
+ outputOptions: argv['out-opts']
})
} catch (e) /* istanbul ignore next */ {
process.stderr.write(e.toString())
diff --git a/src/cli.spec.js b/src/cli.spec.js
index 016d0b7..952f2ea 100644
--- a/src/cli.spec.js
+++ b/src/cli.spec.js
@@ -18,7 +18,7 @@ const replacement = 'ą|'
const replaceFn = () => replacement
const expectedOutput = content.replace(new RegExp(regex, defaultFlags), replacement)
const defaultEncoding = 'utf8'
-let input, output
+let output
tap.afterEach((done) => {
fs.existsSync(output) && fs.unlinkSync(output)
@@ -89,6 +89,8 @@ tap.test('stdout argument', (t) => {
})
tap.test('input argument', async (t) => {
+ let input
+
t.beforeEach(
async () => tmp.file({ prefix: tmpPrefix, keep: true }).then(
async f => {
@@ -122,6 +124,65 @@ tap.test('input argument', async (t) => {
t.end()
})
+tap.test('input options argument', async (t) => {
+ let input
+
+ t.beforeEach(
+ async () => tmp.file({ prefix: tmpPrefix, keep: true }).then(
+ async f => {
+ input = f
+ return new Promise(
+ (resolve) => fs.appendFile(f.path, content, { encoding: defaultEncoding }, resolve)
+ )
+ })
+ )
+
+ t.afterEach(done => {
+ input.cleanup()
+ input = void 0
+ done()
+ })
+
+ t.test('without input argument', async (ct) => {
+ const result = runCli(
+ [regex, replacement, '--in-opts.encoding', defaultEncoding, '--stdout'],
+ { input: content }
+ )
+
+ ct.is(result.status, 1, 'process should send error status (1)')
+ ct.is(result.parsedOutput, '', 'stdout should be empty')
+ ct.is(result.parsedError, 'in-opts -> i', 'stderr contain error about missing in-opts dependency: i argument')
+
+ ct.end()
+ })
+
+ t.test('wrong with input argument', async (ct) => {
+ const result = runCli(
+ [regex, replacement, '-i', input.path, '--in-opts.encoding', 'incorrect-encoding', '--stdout']
+ )
+
+ ct.is(result.status, 1, 'process should send error status (1)')
+ ct.is(result.parsedOutput, '', 'stdout should be empty')
+ ct.contains(result.parsedError, 'incorrect-encoding', 'stderr should complain wrong encoding argument')
+
+ ct.end()
+ })
+
+ t.test('correct with input argument', async (ct) => {
+ const result = runCli(
+ [regex, replacement, '-i', input.path, '--in-opts.encoding', defaultEncoding, '--stdout']
+ )
+
+ ct.is(result.status, 0, 'process should send success status (0)')
+ ct.is(result.parsedOutput, expectedOutput, 'stdout should contain replaced string')
+ ct.is(result.parsedError, '', 'stderr should be empty')
+
+ ct.end()
+ })
+
+ t.end()
+})
+
tap.test('output argument', async (t) => {
const outputPath = output = tmp.tmpNameSync({ prefix: tmpPrefix })
await checkEachArgCombinations(
@@ -144,6 +205,41 @@ tap.test('output argument', async (t) => {
t.end()
})
+tap.test('input options argument', async (t) => {
+ const outputPath = output = tmp.tmpNameSync({ prefix: tmpPrefix })
+
+ t.test('without output argument', async (ct) => {
+ const result = runCli(
+ [regex, replacement, '--out-opts.encoding', defaultEncoding, '--stdout'],
+ { input: content }
+ )
+
+ ct.is(result.status, 1, 'process should send error status (1)')
+ ct.is(result.parsedOutput, '', 'stdout should be empty')
+ ct.is(result.parsedError, 'out-opts -> o', 'stderr contain error about missing in-opts dependency: i argument')
+
+ ct.end()
+ })
+
+ t.test('correct with input argument', async (ct) => {
+ const result = runCli(
+ [regex, replacement, '-o', outputPath, '--out-opts.encoding', defaultEncoding, '--no-stdout'],
+ { input: content }
+ )
+
+ ct.is(result.status, 0, 'process should send success status (0)')
+ ct.is(result.parsedOutput, '', 'stdout should be empty')
+ ct.is(result.parsedError, '', 'stderr should be empty')
+
+ const outputFileContent = fs.readFileSync(outputPath).toString()
+ ct.is(outputFileContent, expectedOutput, 'expected output saved to file')
+
+ ct.end()
+ })
+
+ t.end()
+})
+
tap.test('stdin && output argument', async (t) => {
const outputPath = output = tmp.tmpNameSync({ prefix: tmpPrefix })
@@ -273,7 +369,7 @@ function runCli (_args, _options) {
const result = childProcess.spawnSync('node', ['./src/cli'].concat(_args || []), _options)
result.parsedOutput = result.stdout.toString().trim()
- result.parsedError = result.stderr.toString().trim().split('\n').pop()
+ result.parsedError = result.stderr.toString().trim().split('\n').pop().trim()
return result
}