Skip to content

Commit 0caf2be

Browse files
achingbrainhugomrdias
authored andcommitted
feat: support unixfs metadata and formatting it (#14) (#18)
Adds mode and mtime properties to normalised .add inputs, also adds functions for turning metadata into strings for CLI use. BREAKING CHANGE: In order to support metadata on intermediate directories, globSource in this module will now emit directories and files where previously it only emitted files.
1 parent 71a7bf5 commit 0caf2be

11 files changed

+368
-52
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333
"is-electron": "^2.2.0",
3434
"is-pull-stream": "0.0.0",
3535
"is-stream": "^2.0.0",
36-
"it-glob": "0.0.4",
36+
"it-glob": "0.0.7",
3737
"kind-of": "^6.0.2",
3838
"pull-stream-to-async-iterator": "^1.0.2",
3939
"readable-stream": "^3.4.0"
4040
},
4141
"devDependencies": {
4242
"aegir": "^20.3.0",
43-
"async-iterator-all": "^1.0.0",
43+
"it-all": "^1.0.1",
4444
"chai": "^4.2.0",
4545
"chai-as-promised": "^7.1.1",
4646
"dirty-chai": "^2.0.1",

src/files/format-mode.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict'
2+
3+
const S_ISUID = parseInt('4000', 8) // set UID bit
4+
const S_ISGID = parseInt('2000', 8) // set-group-ID bit (see below)
5+
const S_ISVTX = parseInt('1000', 8) // sticky bit (see below)
6+
// const S_IRWXU = parseInt('700', 8) // mask for file owner permissions
7+
const S_IRUSR = parseInt('400', 8) // owner has read permission
8+
const S_IWUSR = parseInt('200', 8) // owner has write permission
9+
const S_IXUSR = parseInt('100', 8) // owner has execute permission
10+
// const S_IRWXG = parseInt('70', 8) // mask for group permissions
11+
const S_IRGRP = parseInt('40', 8) // group has read permission
12+
const S_IWGRP = parseInt('20', 8) // group has write permission
13+
const S_IXGRP = parseInt('10', 8) // group has execute permission
14+
// const S_IRWXO = parseInt('7', 8) // mask for permissions for others (not in group)
15+
const S_IROTH = parseInt('4', 8) // others have read permission
16+
const S_IWOTH = parseInt('2', 8) // others have write permission
17+
const S_IXOTH = parseInt('1', 8) // others have execute permission
18+
19+
function checkPermission (mode, perm, type, output) {
20+
if ((mode & perm) === perm) {
21+
output.push(type)
22+
} else {
23+
output.push('-')
24+
}
25+
}
26+
27+
function formatMode (mode, isDirectory) {
28+
const output = []
29+
30+
if (isDirectory) {
31+
output.push('d')
32+
} else {
33+
output.push('-')
34+
}
35+
36+
checkPermission(mode, S_IRUSR, 'r', output)
37+
checkPermission(mode, S_IWUSR, 'w', output)
38+
39+
if ((mode & S_ISUID) === S_ISUID) {
40+
output.push('s')
41+
} else {
42+
checkPermission(mode, S_IXUSR, 'x', output)
43+
}
44+
45+
checkPermission(mode, S_IRGRP, 'r', output)
46+
checkPermission(mode, S_IWGRP, 'w', output)
47+
48+
if ((mode & S_ISGID) === S_ISGID) {
49+
output.push('s')
50+
} else {
51+
checkPermission(mode, S_IXGRP, 'x', output)
52+
}
53+
54+
checkPermission(mode, S_IROTH, 'r', output)
55+
checkPermission(mode, S_IWOTH, 'w', output)
56+
57+
if ((mode & S_ISVTX) === S_ISVTX) {
58+
output.push('t')
59+
} else {
60+
checkPermission(mode, S_IXOTH, 'x', output)
61+
}
62+
63+
return output.join('')
64+
}
65+
66+
module.exports = formatMode

src/files/format-mtime.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
function formatMtime (mtime) {
4+
if (mtime === undefined) {
5+
return '-'
6+
}
7+
8+
return new Date(mtime * 1000).toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, {
9+
year: 'numeric',
10+
month: 'short',
11+
day: 'numeric',
12+
hour: '2-digit',
13+
minute: '2-digit',
14+
second: '2-digit',
15+
timeZoneName: 'short'
16+
})
17+
}
18+
19+
module.exports = formatMtime

src/files/glob-source.js

+53-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const kindOf = require('kind-of')
1515
* @param {Boolean} [options.hidden] Include .dot files in matched paths
1616
* @param {Array<String>} [options.ignore] Glob paths to ignore
1717
* @param {Boolean} [options.followSymlinks] follow symlinks
18+
* @param {Boolean} [options.preserveMode] preserve mode
19+
* @param {Boolean} [options.preserveMtime] preserve mtime
20+
* @param {Boolean} [options.mode] mode to use - if preserveMode is true this will be ignored
21+
* @param {Boolean} [options.mtime] mtime to use - if preserveMtime is true this will be ignored
1822
* @yields {Object} File objects in the form `{ path: String, content: AsyncIterator<Buffer> }`
1923
*/
2024
module.exports = async function * globSource (paths, options) {
@@ -47,21 +51,49 @@ module.exports = async function * globSource (paths, options) {
4751
const stat = await fs.stat(absolutePath)
4852
const prefix = Path.dirname(absolutePath)
4953

50-
for await (const entry of toGlobSource({ path, type: stat.isDirectory() ? 'dir' : 'file', prefix }, globSourceOptions)) {
51-
yield entry
54+
let mode = options.mode
55+
56+
if (options.preserveMode) {
57+
mode = stat.mode
58+
}
59+
60+
let mtime = options.mtime
61+
62+
if (options.preserveMtime) {
63+
mtime = parseInt(stat.mtimeMs / 1000)
64+
}
65+
66+
if (stat.isDirectory()) {
67+
yield {
68+
path: `/${Path.basename(path)}`,
69+
mode,
70+
mtime
71+
}
5272
}
73+
74+
yield * toGlobSource({
75+
path,
76+
type: stat.isDirectory() ? 'dir' : 'file',
77+
prefix,
78+
mode,
79+
mtime,
80+
preserveMode: options.preserveMode,
81+
preserveMtime: options.preserveMtime
82+
}, globSourceOptions)
5383
}
5484
}
5585

56-
async function * toGlobSource ({ path, type, prefix }, options) {
86+
async function * toGlobSource ({ path, type, prefix, mode, mtime, preserveMode, preserveMtime }, options) {
5787
options = options || {}
5888

5989
const baseName = Path.basename(path)
6090

6191
if (type === 'file') {
6292
yield {
63-
path: baseName.replace(prefix, ''),
64-
content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path))
93+
path: `/${baseName.replace(prefix, '')}`,
94+
content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path)),
95+
mode,
96+
mtime
6597
}
6698

6799
return
@@ -77,15 +109,29 @@ async function * toGlobSource ({ path, type, prefix }, options) {
77109

78110
const globOptions = Object.assign({}, options.glob, {
79111
cwd: path,
80-
nodir: true,
112+
nodir: false,
81113
realpath: false,
82114
absolute: true
83115
})
84116

85117
for await (const p of glob(path, '**/*', globOptions)) {
118+
const stat = await fs.stat(p)
119+
120+
if (preserveMode || preserveMtime) {
121+
if (preserveMode) {
122+
mode = stat.mode
123+
}
124+
125+
if (preserveMtime) {
126+
mtime = parseInt(stat.mtimeMs / 1000)
127+
}
128+
}
129+
86130
yield {
87131
path: toPosix(p.replace(prefix, '')),
88-
content: fs.createReadStream(p)
132+
content: stat.isFile() ? fs.createReadStream(p) : undefined,
133+
mode,
134+
mtime
89135
}
90136
}
91137
}

src/files/normalise-input.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ module.exports = function normaliseInput (input) {
211211
}
212212

213213
function toFileObject (input) {
214-
const obj = { path: input.path || '' }
214+
const obj = {
215+
path: input.path || '',
216+
mode: input.mode,
217+
mtime: input.mtime
218+
}
215219

216220
if (input.content) {
217221
obj.content = toAsyncIterable(input.content)

test/files/format-mode.spec.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict'
2+
3+
/* eslint-env mocha */
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const formatMode = require('../../src/files/format-mode')
7+
8+
chai.use(dirtyChai)
9+
const expect = chai.expect
10+
11+
describe('format-mode', function () {
12+
it('formats mode for directories', function () {
13+
expect(formatMode(parseInt('0777', 8), true)).to.equal('drwxrwxrwx')
14+
})
15+
16+
it('formats mode for files', function () {
17+
expect(formatMode(parseInt('0777', 8), false)).to.equal('-rwxrwxrwx')
18+
})
19+
20+
it('setgid, setuid and stick bit', function () {
21+
expect(formatMode(parseInt('1777', 8), false)).to.equal('-rwxrwxrwt')
22+
expect(formatMode(parseInt('2777', 8), false)).to.equal('-rwxrwsrwx')
23+
expect(formatMode(parseInt('4777', 8), false)).to.equal('-rwsrwxrwx')
24+
expect(formatMode(parseInt('5777', 8), false)).to.equal('-rwsrwxrwt')
25+
expect(formatMode(parseInt('6777', 8), false)).to.equal('-rwsrwsrwx')
26+
expect(formatMode(parseInt('7777', 8), false)).to.equal('-rwsrwsrwt')
27+
})
28+
29+
it('formats user', function () {
30+
expect(formatMode(parseInt('0100', 8), false)).to.equal('---x------')
31+
expect(formatMode(parseInt('0200', 8), false)).to.equal('--w-------')
32+
expect(formatMode(parseInt('0300', 8), false)).to.equal('--wx------')
33+
expect(formatMode(parseInt('0400', 8), false)).to.equal('-r--------')
34+
expect(formatMode(parseInt('0500', 8), false)).to.equal('-r-x------')
35+
expect(formatMode(parseInt('0600', 8), false)).to.equal('-rw-------')
36+
expect(formatMode(parseInt('0700', 8), false)).to.equal('-rwx------')
37+
})
38+
39+
it('formats group', function () {
40+
expect(formatMode(parseInt('0010', 8), false)).to.equal('------x---')
41+
expect(formatMode(parseInt('0020', 8), false)).to.equal('-----w----')
42+
expect(formatMode(parseInt('0030', 8), false)).to.equal('-----wx---')
43+
expect(formatMode(parseInt('0040', 8), false)).to.equal('----r-----')
44+
expect(formatMode(parseInt('0050', 8), false)).to.equal('----r-x---')
45+
expect(formatMode(parseInt('0060', 8), false)).to.equal('----rw----')
46+
expect(formatMode(parseInt('0070', 8), false)).to.equal('----rwx---')
47+
})
48+
49+
it('formats other', function () {
50+
expect(formatMode(parseInt('0001', 8), false)).to.equal('---------x')
51+
expect(formatMode(parseInt('0002', 8), false)).to.equal('--------w-')
52+
expect(formatMode(parseInt('0003', 8), false)).to.equal('--------wx')
53+
expect(formatMode(parseInt('0004', 8), false)).to.equal('-------r--')
54+
expect(formatMode(parseInt('0005', 8), false)).to.equal('-------r-x')
55+
expect(formatMode(parseInt('0006', 8), false)).to.equal('-------rw-')
56+
expect(formatMode(parseInt('0007', 8), false)).to.equal('-------rwx')
57+
})
58+
})

test/files/format-mtime.spec.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
/* eslint-env mocha */
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const formatMtime = require('../../src/files/format-mtime')
7+
8+
chai.use(dirtyChai)
9+
const expect = chai.expect
10+
11+
describe('format-mtime', function () {
12+
it('formats mtime', function () {
13+
expect((new Date(formatMtime(0))).getTime()).to.equal(0)
14+
})
15+
})

0 commit comments

Comments
 (0)