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

Commit 682c478

Browse files
committed
feat: adds rm command
License: MIT Signed-off-by: achingbrain <[email protected]>
1 parent 5d189a6 commit 682c478

File tree

10 files changed

+297
-14
lines changed

10 files changed

+297
-14
lines changed

src/cli/rm.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict'
2+
3+
module.exports = {
4+
command: 'rm <path>',
5+
6+
describe: 'Remove a file or directory',
7+
8+
builder: {
9+
recursive: {
10+
alias: 'r',
11+
type: 'boolean',
12+
default: false,
13+
describe: 'Remove directories recursively'
14+
}
15+
},
16+
17+
handler (argv) {
18+
let {
19+
path,
20+
ipfs,
21+
recursive
22+
} = argv
23+
24+
ipfs.mfs.rm(path, {
25+
recursive
26+
}, (error) => {
27+
if (error) {
28+
throw error
29+
}
30+
})
31+
}
32+
}

src/core/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
ls: require('./ls'),
66
mkdir: require('./mkdir'),
77
read: require('./read'),
8+
rm: require('./rm'),
89
stat: require('./stat'),
910
write: require('./write')
1011
}

src/core/rm.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict'
2+
3+
const promisify = require('promisify-es6')
4+
const UnixFs = require('ipfs-unixfs')
5+
const waterfall = require('async/waterfall')
6+
const {
7+
DAGNode
8+
} = require('ipld-dag-pb')
9+
const CID = require('cids')
10+
const {
11+
traverseTo,
12+
updateTree,
13+
updateMfsRoot,
14+
FILE_SEPARATOR
15+
} = require('./utils')
16+
17+
const defaultOptions = {
18+
recursive: false
19+
}
20+
21+
module.exports = function mfsRm (ipfs) {
22+
return promisify(function (path, options, callback) {
23+
if (typeof path === 'function') {
24+
return path(new Error('Please specify a path to remove'))
25+
}
26+
27+
if (!callback) {
28+
callback = options
29+
options = {}
30+
}
31+
32+
options = Object.assign({}, defaultOptions, options)
33+
34+
path = path.trim()
35+
36+
if (path === FILE_SEPARATOR) {
37+
return callback(new Error('Cannot delete root'))
38+
}
39+
40+
waterfall([
41+
(cb) => traverseTo(ipfs, path, {
42+
withCreateHint: false
43+
}, cb),
44+
(result, cb) => {
45+
const meta = UnixFs.unmarshal(result.node.data)
46+
47+
if (meta.type === 'directory' && !options.recursive) {
48+
return cb(new Error(`${path} is a directory, use -r to remove directories`))
49+
}
50+
51+
waterfall([
52+
(next) => DAGNode.rmLink(result.parent.node, result.name, next),
53+
(newParentNode, next) => {
54+
ipfs.dag.put(newParentNode, {
55+
cid: new CID(newParentNode.hash || newParentNode.multihash)
56+
}, (error) => next(error, newParentNode))
57+
},
58+
(newParentNode, next) => {
59+
result.parent.node = newParentNode
60+
61+
updateTree(ipfs, result.parent, next)
62+
},
63+
(newRoot, next) => updateMfsRoot(ipfs, newRoot.node.multihash, next)
64+
], cb)
65+
}
66+
], callback)
67+
})
68+
}

src/core/stat.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ module.exports = function mfsStat (ipfs) {
3434
log(`Fetching stats for ${path}`)
3535

3636
waterfall([
37-
(done) => traverseTo(ipfs, path, options, done),
37+
(done) => traverseTo(ipfs, path, {
38+
withCreateHint: false
39+
}, done),
3840
({ node }, done) => {
3941
if (options.hash) {
4042
return done(null, {

src/core/utils/traverse-to.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const createNode = require('./create-node')
1717
const defaultOptions = {
1818
parents: false,
1919
flush: true,
20-
createLastComponent: false
20+
createLastComponent: false,
21+
withCreateHint: true
2122
}
2223

2324
const traverseTo = (ipfs, path, options, callback) => {
@@ -62,7 +63,13 @@ const traverseTo = (ipfs, path, options, callback) => {
6263
}
6364

6465
if (!options.parents) {
65-
return done(new Error(`Cannot find ${path} - '${pathSegment}' did not exist: Try again with the --parents flag to create it`))
66+
let message = `Cannot find ${path} - ${pathSegment} did not exist`
67+
68+
if (options.withCreateHint) {
69+
message += ': Try again with the --parents flag to create it'
70+
}
71+
72+
return done(new Error(message))
6673
}
6774

6875
log(`Adding empty directory '${pathSegment}' to parent ${parent.name}`)

test/browser.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ require('./cp.spec.js')
44
require('./ls.spec.js')
55
require('./mkdir.spec.js')
66
require('./read.spec.js')
7-
require('./stat.spec.js')
7+
require('./write.spec.js')
8+
require('./rm.spec.js')
89
require('./write.spec.js')

test/cp.spec.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,39 @@ describe('cp', function () {
2828

2929
it('refuses to copy files without arguments', () => {
3030
return mfs.cp()
31-
.then(() => expect.fail('No error was thrown for missing files'))
31+
.then(() => {
32+
throw new Error('No error was thrown for missing files')
33+
})
3234
.catch(error => {
3335
expect(error.message).to.contain('Please specify a source(s) and a destination')
3436
})
3537
})
3638

3739
it('refuses to copy files without files', () => {
3840
return mfs.cp('destination')
39-
.then(() => expect.fail('No error was thrown for missing files'))
41+
.then(() => {
42+
throw new Error('No error was thrown for missing files')
43+
})
4044
.catch(error => {
4145
expect(error.message).to.contain('Please specify a source(s) and a destination')
4246
})
4347
})
4448

4549
it('refuses to copy files without files', () => {
4650
return mfs.cp('destination', {})
47-
.then(() => expect.fail('No error was thrown for missing files'))
51+
.then(() => {
52+
throw new Error('No error was thrown for missing files')
53+
})
4854
.catch(error => {
4955
expect(error.message).to.contain('Please specify a path to copy')
5056
})
5157
})
5258

5359
it('refuses to copy a file to a non-existent directory', () => {
5460
return mfs.cp('/i-do-not-exist', '/output')
55-
.then(() => expect.fail('No error was thrown for a non-existent file'))
61+
.then(() => {
62+
throw new Error('No error was thrown for a non-existent file')
63+
})
5664
.catch(error => {
5765
expect(error.message).to.contain('did not exist')
5866
})

test/ls.spec.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,19 @@ describe('ls', function () {
2727

2828
it('refuses to lists files with an empty path', () => {
2929
return mfs.ls('')
30-
.then(() => expect.fail('No error was thrown for an empty path'))
30+
.then(() => {
31+
throw new Error('No error was thrown for an empty path')
32+
})
3133
.catch(error => {
3234
expect(error.message).to.contain('paths must not be empty')
3335
})
3436
})
3537

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

5155
it('fails to list non-existent file', () => {
5256
return mfs.ls('/i-do-not-exist')
53-
.then(() => expect.fail('No error was thrown for a non-existent file'))
57+
.then(() => {
58+
throw new Error('No error was thrown for a non-existent file')
59+
})
5460
.catch(error => {
5561
expect(error.message).to.contain('file does not exist')
5662
})

test/rm.spec.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const chai = require('chai')
5+
chai.use(require('dirty-chai'))
6+
const expect = chai.expect
7+
const bufferStream = require('./fixtures/buffer-stream')
8+
9+
const {
10+
createMfs
11+
} = require('./fixtures')
12+
13+
const {
14+
FILE_SEPARATOR
15+
} = require('../src/core/utils')
16+
17+
describe('rm', function () {
18+
this.timeout(30000)
19+
20+
let mfs
21+
22+
before(() => {
23+
return createMfs()
24+
.then(instance => {
25+
mfs = instance
26+
})
27+
})
28+
29+
after((done) => {
30+
mfs.node.stop(done)
31+
})
32+
33+
it('refuses to remove files without arguments', () => {
34+
return mfs.rm()
35+
.then(() => {
36+
throw new Error('No error was thrown for missing paths')
37+
})
38+
.catch(error => {
39+
expect(error.message).to.contain('Please specify a path to remove')
40+
})
41+
})
42+
43+
it('refuses to remove the root path', () => {
44+
return mfs.rm(FILE_SEPARATOR)
45+
.then(() => {
46+
throw new Error('No error was thrown for missing paths')
47+
})
48+
.catch(error => {
49+
expect(error.message).to.contain('Cannot delete root')
50+
})
51+
})
52+
53+
it('refuses to remove a directory without the recursive flag', () => {
54+
const path = `/directory-${Math.random()}`
55+
56+
return mfs.mkdir(path)
57+
.then(() => mfs.rm(path))
58+
.then(() => {
59+
throw new Error('No error was thrown for missing recursive flag')
60+
})
61+
.catch(error => {
62+
expect(error.message).to.contain(`${path} is a directory, use -r to remove directories`)
63+
})
64+
})
65+
66+
it('removes a file', () => {
67+
const file = `/some-file-${Math.random()}.txt`
68+
69+
return mfs.write(file, bufferStream(100), {
70+
create: true,
71+
parents: true
72+
})
73+
.then(() => mfs.rm(file, {
74+
recursive: true
75+
}))
76+
.then(() => mfs.stat(file))
77+
.then(() => {
78+
throw new Error('File was not removed')
79+
})
80+
.catch(error => {
81+
expect(error.message).to.contain(`Path ${file} did not exist`)
82+
})
83+
})
84+
85+
it('removes a directory', () => {
86+
const directory = `/directory-${Math.random()}`
87+
88+
return mfs.mkdir(directory)
89+
.then(() => mfs.rm(directory, {
90+
recursive: true
91+
}))
92+
.then(() => mfs.stat(directory))
93+
.then(() => {
94+
throw new Error('Directory was not removed')
95+
})
96+
.catch(error => {
97+
expect(error.message).to.contain(`Path ${directory} did not exist`)
98+
})
99+
})
100+
101+
it('recursively removes a directory', () => {
102+
const directory = `/directory-${Math.random()}`
103+
const subdirectory = `/directory-${Math.random()}`
104+
const path = `${directory}${subdirectory}`
105+
106+
return mfs.mkdir(path)
107+
.then(() => mfs.rm(directory, {
108+
recursive: true
109+
}))
110+
.then(() => mfs.stat(subdirectory))
111+
.then(() => {
112+
throw new Error('File was not removed')
113+
})
114+
.catch(error => {
115+
expect(error.message).to.contain(`Path ${subdirectory} did not exist`)
116+
})
117+
.then(() => mfs.stat(directory))
118+
.then(() => {
119+
throw new Error('Directory was not removed')
120+
})
121+
.catch(error => {
122+
expect(error.message).to.contain(`Path ${directory} did not exist`)
123+
})
124+
})
125+
126+
it('recursively removes a directory with files in', () => {
127+
const directory = `directory-${Math.random()}`
128+
const file = `/${directory}/some-file-${Math.random()}.txt`
129+
130+
return mfs.write(file, bufferStream(100), {
131+
create: true,
132+
parents: true
133+
})
134+
.then(() => mfs.rm(`/${directory}`, {
135+
recursive: true
136+
}))
137+
.then(() => mfs.stat(file))
138+
.then(() => {
139+
throw new Error('File was not removed')
140+
})
141+
.catch(error => {
142+
expect(error.message).to.contain(`Path ${file} did not exist`)
143+
})
144+
.then(() => mfs.stat(`/${directory}`))
145+
.then(() => {
146+
throw new Error('Directory was not removed')
147+
})
148+
.catch(error => {
149+
expect(error.message).to.contain(`${directory} did not exist`)
150+
})
151+
})
152+
})

0 commit comments

Comments
 (0)