Skip to content
This repository has been archived by the owner on Mar 10, 2020. It is now read-only.

Commit

Permalink
feat: adds rm command
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: achingbrain <[email protected]>
  • Loading branch information
achingbrain committed May 8, 2018
1 parent 5d189a6 commit 682c478
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 14 deletions.
32 changes: 32 additions & 0 deletions src/cli/rm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

module.exports = {
command: 'rm <path>',

describe: 'Remove a file or directory',

builder: {
recursive: {
alias: 'r',
type: 'boolean',
default: false,
describe: 'Remove directories recursively'
}
},

handler (argv) {
let {
path,
ipfs,
recursive
} = argv

ipfs.mfs.rm(path, {
recursive
}, (error) => {
if (error) {
throw error
}
})
}
}
1 change: 1 addition & 0 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
ls: require('./ls'),
mkdir: require('./mkdir'),
read: require('./read'),
rm: require('./rm'),
stat: require('./stat'),
write: require('./write')
}
68 changes: 68 additions & 0 deletions src/core/rm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict'

const promisify = require('promisify-es6')
const UnixFs = require('ipfs-unixfs')
const waterfall = require('async/waterfall')
const {
DAGNode
} = require('ipld-dag-pb')
const CID = require('cids')
const {
traverseTo,
updateTree,
updateMfsRoot,
FILE_SEPARATOR
} = require('./utils')

const defaultOptions = {
recursive: false
}

module.exports = function mfsRm (ipfs) {
return promisify(function (path, options, callback) {
if (typeof path === 'function') {
return path(new Error('Please specify a path to remove'))
}

if (!callback) {
callback = options
options = {}
}

options = Object.assign({}, defaultOptions, options)

path = path.trim()

if (path === FILE_SEPARATOR) {
return callback(new Error('Cannot delete root'))
}

waterfall([
(cb) => traverseTo(ipfs, path, {
withCreateHint: false
}, cb),
(result, cb) => {
const meta = UnixFs.unmarshal(result.node.data)

if (meta.type === 'directory' && !options.recursive) {
return cb(new Error(`${path} is a directory, use -r to remove directories`))
}

waterfall([
(next) => DAGNode.rmLink(result.parent.node, result.name, next),
(newParentNode, next) => {
ipfs.dag.put(newParentNode, {
cid: new CID(newParentNode.hash || newParentNode.multihash)
}, (error) => next(error, newParentNode))
},
(newParentNode, next) => {
result.parent.node = newParentNode

updateTree(ipfs, result.parent, next)
},
(newRoot, next) => updateMfsRoot(ipfs, newRoot.node.multihash, next)
], cb)
}
], callback)
})
}
4 changes: 3 additions & 1 deletion src/core/stat.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ module.exports = function mfsStat (ipfs) {
log(`Fetching stats for ${path}`)

waterfall([
(done) => traverseTo(ipfs, path, options, done),
(done) => traverseTo(ipfs, path, {
withCreateHint: false
}, done),
({ node }, done) => {
if (options.hash) {
return done(null, {
Expand Down
11 changes: 9 additions & 2 deletions src/core/utils/traverse-to.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const createNode = require('./create-node')
const defaultOptions = {
parents: false,
flush: true,
createLastComponent: false
createLastComponent: false,
withCreateHint: true
}

const traverseTo = (ipfs, path, options, callback) => {
Expand Down Expand Up @@ -62,7 +63,13 @@ const traverseTo = (ipfs, path, options, callback) => {
}

if (!options.parents) {
return done(new Error(`Cannot find ${path} - '${pathSegment}' did not exist: Try again with the --parents flag to create it`))
let message = `Cannot find ${path} - ${pathSegment} did not exist`

if (options.withCreateHint) {
message += ': Try again with the --parents flag to create it'
}

return done(new Error(message))
}

log(`Adding empty directory '${pathSegment}' to parent ${parent.name}`)
Expand Down
3 changes: 2 additions & 1 deletion test/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ require('./cp.spec.js')
require('./ls.spec.js')
require('./mkdir.spec.js')
require('./read.spec.js')
require('./stat.spec.js')
require('./write.spec.js')
require('./rm.spec.js')
require('./write.spec.js')
16 changes: 12 additions & 4 deletions test/cp.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,39 @@ describe('cp', function () {

it('refuses to copy files without arguments', () => {
return mfs.cp()
.then(() => expect.fail('No error was thrown for missing files'))
.then(() => {
throw new Error('No error was thrown for missing files')
})
.catch(error => {
expect(error.message).to.contain('Please specify a source(s) and a destination')
})
})

it('refuses to copy files without files', () => {
return mfs.cp('destination')
.then(() => expect.fail('No error was thrown for missing files'))
.then(() => {
throw new Error('No error was thrown for missing files')
})
.catch(error => {
expect(error.message).to.contain('Please specify a source(s) and a destination')
})
})

it('refuses to copy files without files', () => {
return mfs.cp('destination', {})
.then(() => expect.fail('No error was thrown for missing files'))
.then(() => {
throw new Error('No error was thrown for missing files')
})
.catch(error => {
expect(error.message).to.contain('Please specify a path to copy')
})
})

it('refuses to copy a file to a non-existent directory', () => {
return mfs.cp('/i-do-not-exist', '/output')
.then(() => expect.fail('No error was thrown for a non-existent file'))
.then(() => {
throw new Error('No error was thrown for a non-existent file')
})
.catch(error => {
expect(error.message).to.contain('did not exist')
})
Expand Down
12 changes: 9 additions & 3 deletions test/ls.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@ describe('ls', function () {

it('refuses to lists files with an empty path', () => {
return mfs.ls('')
.then(() => expect.fail('No error was thrown for an empty path'))
.then(() => {
throw new Error('No error was thrown for an empty path')
})
.catch(error => {
expect(error.message).to.contain('paths must not be empty')
})
})

it('refuses to lists files with an invalid path', () => {
return mfs.ls('not-valid')
.then(() => expect.fail('No error was thrown for an empty path'))
.then(() => {
throw new Error('No error was thrown for an empty path')
})
.catch(error => {
expect(error.message).to.contain('paths must start with a leading /')
})
Expand All @@ -50,7 +54,9 @@ describe('ls', function () {

it('fails to list non-existent file', () => {
return mfs.ls('/i-do-not-exist')
.then(() => expect.fail('No error was thrown for a non-existent file'))
.then(() => {
throw new Error('No error was thrown for a non-existent file')
})
.catch(error => {
expect(error.message).to.contain('file does not exist')
})
Expand Down
152 changes: 152 additions & 0 deletions test/rm.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const bufferStream = require('./fixtures/buffer-stream')

const {
createMfs
} = require('./fixtures')

const {
FILE_SEPARATOR
} = require('../src/core/utils')

describe('rm', function () {
this.timeout(30000)

let mfs

before(() => {
return createMfs()
.then(instance => {
mfs = instance
})
})

after((done) => {
mfs.node.stop(done)
})

it('refuses to remove files without arguments', () => {
return mfs.rm()
.then(() => {
throw new Error('No error was thrown for missing paths')
})
.catch(error => {
expect(error.message).to.contain('Please specify a path to remove')
})
})

it('refuses to remove the root path', () => {
return mfs.rm(FILE_SEPARATOR)
.then(() => {
throw new Error('No error was thrown for missing paths')
})
.catch(error => {
expect(error.message).to.contain('Cannot delete root')
})
})

it('refuses to remove a directory without the recursive flag', () => {
const path = `/directory-${Math.random()}`

return mfs.mkdir(path)
.then(() => mfs.rm(path))
.then(() => {
throw new Error('No error was thrown for missing recursive flag')
})
.catch(error => {
expect(error.message).to.contain(`${path} is a directory, use -r to remove directories`)
})
})

it('removes a file', () => {
const file = `/some-file-${Math.random()}.txt`

return mfs.write(file, bufferStream(100), {
create: true,
parents: true
})
.then(() => mfs.rm(file, {
recursive: true
}))
.then(() => mfs.stat(file))
.then(() => {
throw new Error('File was not removed')
})
.catch(error => {
expect(error.message).to.contain(`Path ${file} did not exist`)
})
})

it('removes a directory', () => {
const directory = `/directory-${Math.random()}`

return mfs.mkdir(directory)
.then(() => mfs.rm(directory, {
recursive: true
}))
.then(() => mfs.stat(directory))
.then(() => {
throw new Error('Directory was not removed')
})
.catch(error => {
expect(error.message).to.contain(`Path ${directory} did not exist`)
})
})

it('recursively removes a directory', () => {
const directory = `/directory-${Math.random()}`
const subdirectory = `/directory-${Math.random()}`
const path = `${directory}${subdirectory}`

return mfs.mkdir(path)
.then(() => mfs.rm(directory, {
recursive: true
}))
.then(() => mfs.stat(subdirectory))
.then(() => {
throw new Error('File was not removed')
})
.catch(error => {
expect(error.message).to.contain(`Path ${subdirectory} did not exist`)
})
.then(() => mfs.stat(directory))
.then(() => {
throw new Error('Directory was not removed')
})
.catch(error => {
expect(error.message).to.contain(`Path ${directory} did not exist`)
})
})

it('recursively removes a directory with files in', () => {
const directory = `directory-${Math.random()}`
const file = `/${directory}/some-file-${Math.random()}.txt`

return mfs.write(file, bufferStream(100), {
create: true,
parents: true
})
.then(() => mfs.rm(`/${directory}`, {
recursive: true
}))
.then(() => mfs.stat(file))
.then(() => {
throw new Error('File was not removed')
})
.catch(error => {
expect(error.message).to.contain(`Path ${file} did not exist`)
})
.then(() => mfs.stat(`/${directory}`))
.then(() => {
throw new Error('Directory was not removed')
})
.catch(error => {
expect(error.message).to.contain(`${directory} did not exist`)
})
})
})
Loading

0 comments on commit 682c478

Please sign in to comment.