diff --git a/packages/app-cli/app/app.js b/packages/app-cli/app/app.js index 28472819d..d74f49622 100644 --- a/packages/app-cli/app/app.js +++ b/packages/app-cli/app/app.js @@ -9,7 +9,7 @@ const Tag = require('@joplin/lib/models/Tag').default; const Setting = require('@joplin/lib/models/Setting').default; const { reg } = require('@joplin/lib/registry.js'); const { fileExtension } = require('@joplin/lib/path-utils'); -const { splitCommandString } = require('@joplin/lib/string-utils'); +const { splitCommandString, splitCommandBatch } = require('@joplin/lib/string-utils'); const { _ } = require('@joplin/lib/locale'); const fs = require('fs-extra'); const { cliUtils } = require('./cli-utils.js'); @@ -390,7 +390,8 @@ class Application extends BaseApplication { async commandList(argv) { if (argv.length && argv[0] === 'batch') { const commands = []; - const commandLines = (await fs.readFile(argv[1], 'utf-8')).split('\n'); + const commandLines = splitCommandBatch(await fs.readFile(argv[1], 'utf-8')); + for (const commandLine of commandLines) { if (!commandLine.trim()) continue; const splitted = splitCommandString(commandLine.trim()); diff --git a/packages/lib/StringUtils.test.js b/packages/lib/StringUtils.test.js index 31c18eeda..2c29a601a 100644 --- a/packages/lib/StringUtils.test.js +++ b/packages/lib/StringUtils.test.js @@ -1,5 +1,6 @@ /* eslint-disable no-unused-vars */ +const { splitCommandBatch } = require('./string-utils'); const StringUtils = require('./string-utils'); describe('StringUtils', function() { @@ -53,4 +54,26 @@ describe('StringUtils', function() { }); })); + it('should split the command batch by newlines not inside quotes', (async () => { + const eol = '\n'; + const testCases = [ + ['', + ['']], + ['command1', + ['command1']], + ['command1 arg1 arg2 arg3', + ['command1 arg1 arg2 arg3']], + [`command1 arg1 'arg2${eol}continue' arg3`, + [`command1 arg1 'arg2${eol}continue' arg3`]], + [`command1 arg1 'arg2${eol}continue'${eol}command2${eol}command3 'arg1${eol}continue${eol}continue' arg2 arg3`, + [`command1 arg1 'arg2${eol}continue'`, 'command2', `command3 'arg1${eol}continue${eol}continue' arg2 arg3`]], + [`command1 arg\\1 'arg2${eol}continue\\'continue' arg3`, + [`command1 arg\\1 'arg2${eol}continue\\'continue' arg3`]], + ]; + + testCases.forEach((t) => { + expect(splitCommandBatch(t[0])).toEqual(t[1]); + }); + })); + }); diff --git a/packages/lib/string-utils.js b/packages/lib/string-utils.js index 28af8deed..6309a4031 100644 --- a/packages/lib/string-utils.js +++ b/packages/lib/string-utils.js @@ -210,6 +210,59 @@ function splitCommandString(command, options = null) { return args; } +function splitCommandBatch(commandBatch) { + const commandLines = []; + const eol = '\n'; + + let state = 'command'; + let current = ''; + let quote = ''; + for (let i = 0; i < commandBatch.length; i++) { + const c = commandBatch[i]; + + if (state === 'command') { + if (c === eol) { + commandLines.push(current); + current = ''; + } else if (c === '"' || c === '\'') { + quote = c; + current += c; + state = 'quoted'; + } else if (c === '\\') { + current += c; + if (i + 1 < commandBatch.length) { + current += commandBatch[i + 1]; + i++; + } + } else { + current += c; + } + } else if (state === 'quoted') { + if (c === quote) { + quote = ''; + current += c; + state = 'command'; + } else if (c === '\\') { + current += c; + if (i + 1 < commandBatch.length) { + current += commandBatch[i + 1]; + i++; + } + } else { + current += c; + } + } + } + if (current.length > 0) { + commandLines.push(current); + } + if (commandLines.length === 0) { + commandLines.push(''); + } + + return commandLines; +} + function padLeft(string, length, padString) { if (!string) return ''; @@ -307,4 +360,4 @@ function scriptType(s) { return 'en'; } -module.exports = Object.assign({ formatCssSize, camelCaseToDash, removeDiacritics, substrWithEllipsis, nextWhitespaceIndex, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase, urlDecode, escapeHtml, surroundKeywords, scriptType, commandArgumentsToString }, stringUtilsCommon); +module.exports = Object.assign({ formatCssSize, camelCaseToDash, removeDiacritics, substrWithEllipsis, nextWhitespaceIndex, escapeFilename, wrap, splitCommandString, splitCommandBatch, padLeft, toTitleCase, urlDecode, escapeHtml, surroundKeywords, scriptType, commandArgumentsToString }, stringUtilsCommon);