diff --git a/lib/cmds/files_cmds/mv.js b/lib/cmds/files_cmds/mv.js new file mode 100644 index 0000000..c231204 --- /dev/null +++ b/lib/cmds/files_cmds/mv.js @@ -0,0 +1,57 @@ +'use strict'; + +const chalk = require('chalk'); +const querystring = require('querystring'); +const { patch, list } = require('../../api'); + +exports.command = 'mv '; +exports.desc = 'Move/rename files from to within a project'; +exports.builder = yargs => { + yargs.positional('source', { + describe: 'Either the ID of the file to move or a project ID and prefix of the form / to move a set of files in a project.', + type: 'string' + }).positional('dest', { + describe: 'The new destination for the file within the project.', + type: 'string' + }).option('recursive', { + describe: + 'If is a project Id with an optional prefix, move all files under that prefix to the destination.', + alias: 'r', + type: 'boolean', + default: false + }).option('limit', { + describe: 'The maximum number of files to move when used with the --recursive option', + alias: 'l', + type: 'number', + default: 1000 + }); +}; + +exports.handler = async argv => { + if (argv.recursive) { + const [datasetId, prefix] = argv.source.split('/'); + + const opts = { + datasetId, + pageSize: argv.limit + }; + + if (prefix) { + opts.name = prefix; + } + + const response = await list(argv, `/v1/files?${querystring.stringify(opts)}`); + for (const item of response.data.items) { + const newName = [argv.dest, item.name.replace(prefix, '')].join('/').replace(/\/{1,}/g, '/'); + await patch(argv, `/v1/files/${item.id}`, { + name: newName + }); + console.log(chalk.green(`Moved: `) + item.name + chalk.green(` to `) + newName); + } + } else { + await patch(argv, `/v1/files/${argv.source}`, { + name: argv.dest + }); + console.log(chalk.green(`Moved: `) + argv.source + chalk.green(` to `) + argv.dest); + } +}; diff --git a/test/unit/commands/file.test.js b/test/unit/commands/file.test.js index 66093f7..fc28ede 100644 --- a/test/unit/commands/file.test.js +++ b/test/unit/commands/file.test.js @@ -10,6 +10,7 @@ const querystring = require('querystring'); const getStub = sinon.stub(); const postStub = sinon.stub(); +const patchStub = sinon.stub(); const delStub = sinon.stub(); const printSpy = sinon.spy(); const downloadSpy = sinon.spy(); @@ -37,6 +38,10 @@ const mocks = { list: function (options, url) { return listStub(options, url); }, + patch: (options, url, opts) => { + patchStub(options, url, opts); + callback(); + }, post: postStub, del: delStub, download: function (options, url, file, name) { @@ -69,6 +74,7 @@ const list = proxyquire('../../../lib/cmds/files_cmds/list', mocks); const download = proxyquire('../../../lib/cmds/files_cmds/download', mocks); const upload = proxyquire('../../../lib/cmds/files_cmds/upload', mocks); const ls = proxyquire('../../../lib/cmds/files_cmds/ls', mocks); +const mv = proxyquire('../../../lib/cmds/files_cmds/mv', mocks); test.beforeEach(t => { t.context.sandbox = sinon.createSandbox(); @@ -78,6 +84,7 @@ test.afterEach.always(t => { listStub.reset(); getStub.reset(); postStub.reset(); + patchStub.reset(); delStub.reset(); printSpy.resetHistory(); uploadSpy.resetHistory(); @@ -236,6 +243,57 @@ test.serial.cb('The "files-download" command should download a set of files from .parse('download projectId/prefix /dir -r'); }); +test.serial.cb('The "files-mv" command should move a set of files from a project', t => { + patchStub.returns({}); + listStub.onFirstCall().returns({ + data: { + items: [ + { + id: '1', + name: 'foo.txt' + } + ] + } + }); + + callback = () => { + t.is(listStub.callCount, 1); + t.is(listStub.getCall(0).args[1], '/v1/files?datasetId=projectId&pageSize=1000&name=prefix'); + + t.is(patchStub.callCount, 1); + t.is(patchStub.getCall(0).args[1], '/v1/files/1'); + t.deepEqual(patchStub.getCall(0).args[2], { + name: '/dir/foo.txt' + }); + t.end(); + }; + + t.context.deleteFileStub = t.context.sandbox.stub(fs, 'unlinkSync').callsFake(callback); + t.context.copyFileStub = t.context.sandbox.stub(fs, 'copyFileSync').callsFake(callback); + + yargs.command(mv) + .parse('mv projectId/prefix /dir/ -r'); +}); + +test.serial.cb('The "files-mv" command should move a file in a project', t => { + patchStub.returns({}); + + callback = () => { + t.is(patchStub.callCount, 1); + t.is(patchStub.getCall(0).args[1], '/v1/files/1234'); + t.deepEqual(patchStub.getCall(0).args[2], { + name: '/dir/bar.txt' + }); + t.end(); + }; + + t.context.deleteFileStub = t.context.sandbox.stub(fs, 'unlinkSync').callsFake(callback); + t.context.copyFileStub = t.context.sandbox.stub(fs, 'copyFileSync').callsFake(callback); + + yargs.command(mv) + .parse('mv 1234 /dir/bar.txt'); +}); + test.serial.cb('The "files-upload" command should upload a file', t => { const res = { data: { uploadUrl: 'https://host/upload' } }; postStub.onFirstCall().returns(res);