Skip to content

Commit

Permalink
DRY all commands, correct overload signature types
Browse files Browse the repository at this point in the history
This moves the repetitive overload definitions into a single type created
by a makeCommand method. This also means that all of the repetitive setup
and validation can be moved to one place as well.

Corrects a bug where TS thought that a `Promise<void>` would be
returned, which actually it was an instance of Unpack (or whatever).

The type calculus is rather baroque. The next major will replace the
overloaded function signatures with separately named functions, so
there'll be `createFile`, `createSync`, `createFileSync`, etc.
  • Loading branch information
isaacs committed May 30, 2024
1 parent ce612d0 commit 3062616
Show file tree
Hide file tree
Showing 20 changed files with 743 additions and 540 deletions.
100 changes: 20 additions & 80 deletions src/create.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,16 @@
import { WriteStream, WriteStreamSync } from '@isaacs/fs-minipass'
import { Minipass } from 'minipass'
import path from 'node:path'
import { list } from './list.js'
import { makeCommand } from './make-command.js'
import {
dealias,
isFile,
isSync,
isSyncFile,
TarOptions,
TarOptionsFile,
TarOptionsSync,
TarOptionsSyncFile,
TarOptionsWithAliases,
TarOptionsWithAliasesFile,
TarOptionsWithAliasesSync,
TarOptionsWithAliasesSyncFile,
} from './options.js'

import { WriteStream, WriteStreamSync } from '@isaacs/fs-minipass'
import { Minipass } from 'minipass'
import path from 'node:path'
import { list } from './list.js'
import { Pack, PackSync } from './pack.js'

export function create(
opt: TarOptionsWithAliasesSyncFile,
files?: string[],
): void
export function create(
opt: TarOptionsWithAliasesSync,
files?: string[],
): void
export function create(
opt: TarOptionsWithAliasesFile,
files?: string[],
cb?: () => any,
): Promise<void>
export function create(
opt: TarOptionsWithAliasesFile,
cb: () => any,
): Promise<void>
export function create(
opt: TarOptionsWithAliases,
files?: string[],
): Pack
export function create(
opt_: TarOptionsWithAliases,
files?: string[] | (() => any),
cb?: () => any,
): void | Promise<void> | Pack {
if (typeof files === 'function') {
cb = files
}

if (Array.isArray(opt_)) {
;(files = opt_), (opt_ = {})
}

if (!files || !Array.isArray(files) || !files.length) {
throw new TypeError('no files or directories specified')
}

files = Array.from(files)

const opt = dealias(opt_)

if (opt.sync && typeof cb === 'function') {
throw new TypeError(
'callback not supported for sync tar functions',
)
}

if (!opt.file && typeof cb === 'function') {
throw new TypeError('callback only supported with file option')
}

return (
isSyncFile(opt) ? createFileSync(opt, files)
: isFile(opt) ? createFile(opt, files, cb)
: isSync(opt) ? createSync(opt, files)
: create_(opt, files)
)
}

const createFileSync = (opt: TarOptionsSyncFile, files: string[]) => {
const p = new PackSync(opt)
const stream = new WriteStreamSync(opt.file, {
Expand All @@ -88,11 +20,7 @@ const createFileSync = (opt: TarOptionsSyncFile, files: string[]) => {
addFilesSync(p, files)
}

const createFile = (
opt: TarOptionsFile,
files: string[],
cb?: () => any,
) => {
const createFile = (opt: TarOptionsFile, files: string[]) => {
const p = new Pack(opt)
const stream = new WriteStream(opt.file, {
mode: opt.mode || 0o666,
Expand All @@ -107,7 +35,7 @@ const createFile = (

addFilesAsync(p, files)

return cb ? promise.then(cb, cb) : promise
return promise
}

const addFilesSync = (p: PackSync, files: string[]) => {
Expand Down Expand Up @@ -153,8 +81,20 @@ const createSync = (opt: TarOptionsSync, files: string[]) => {
return p
}

const create_ = (opt: TarOptions, files: string[]) => {
const createAsync = (opt: TarOptions, files: string[]) => {
const p = new Pack(opt)
addFilesAsync(p, files)
return p
}

export const create = makeCommand(
createFileSync,
createFile,
createSync,
createAsync,
(_opt, files) => {
if (!files?.length) {
throw new TypeError('no paths specified to add to archive')
}
},
)
132 changes: 14 additions & 118 deletions src/extract.ts
Original file line number Diff line number Diff line change
@@ -1,123 +1,13 @@
// tar -x
import * as fsm from '@isaacs/fs-minipass'
import fs from 'node:fs'
import { dirname, parse } from 'node:path'
import {
dealias,
isFile,
isSync,
isSyncFile,
TarOptions,
TarOptionsFile,
TarOptionsSync,
TarOptionsSyncFile,
TarOptionsWithAliases,
TarOptionsWithAliasesFile,
TarOptionsWithAliasesSync,
TarOptionsWithAliasesSyncFile,
} from './options.js'
import { stripTrailingSlashes } from './strip-trailing-slashes.js'
import { filesFilter } from './list.js'
import { makeCommand } from './make-command.js'
import { TarOptionsFile, TarOptionsSyncFile } from './options.js'
import { Unpack, UnpackSync } from './unpack.js'

export function extract(
opt: TarOptionsWithAliasesSyncFile,
files?: string[],
): void
export function extract(
opt: TarOptionsWithAliasesSync,
files?: string[],
): void
export function extract(
opt: TarOptionsWithAliasesFile,
files?: string[],
cb?: () => any,
): Promise<void>
export function extract(
opt: TarOptionsWithAliasesFile,
cb: () => any,
): Promise<void>
export function extract(
opt: TarOptionsWithAliases,
files?: string[],
): Unpack
export function extract(
opt_: TarOptionsWithAliases,
files?: string[] | (() => any),
cb?: () => any,
): void | Promise<void> | Unpack {
if (typeof opt_ === 'function') {
;(cb = opt_), (files = undefined), (opt_ = {})
} else if (Array.isArray(opt_)) {
;(files = opt_), (opt_ = {})
}

if (typeof files === 'function') {
;(cb = files), (files = undefined)
}

if (!files) {
files = []
} else {
files = Array.from(files)
}

const opt = dealias(opt_)

if (opt.sync && typeof cb === 'function') {
throw new TypeError(
'callback not supported for sync tar functions',
)
}

if (!opt.file && typeof cb === 'function') {
throw new TypeError('callback only supported with file option')
}

if (files.length) {
filesFilter(opt, files)
}

return (
isSyncFile(opt) ? extractFileSync(opt)
: isFile(opt) ? extractFile(opt, cb)
: isSync(opt) ? extractSync(opt)
: extract_(opt)
)
}

// construct a filter that limits the file entries listed
// include child entries if a dir is included
const filesFilter = (opt: TarOptions, files: string[]) => {
const map = new Map(files.map(f => [stripTrailingSlashes(f), true]))
const filter = opt.filter

const mapHas = (file: string, r: string = ''): boolean => {
const root = r || parse(file).root || '.'
let ret: boolean
if (file === root) ret = false
else {
const m = map.get(file)
if (m !== undefined) {
ret = m
} else {
ret = mapHas(dirname(file), root)
}
}

map.set(file, ret)
return ret
}

opt.filter =
filter ?
(file, entry) =>
filter(file, entry) && mapHas(stripTrailingSlashes(file))
: file => mapHas(stripTrailingSlashes(file))
}

const extractFileSync = (opt: TarOptionsSyncFile) => {
const u = new UnpackSync(opt)

const file = opt.file
const stat = fs.statSync(file)
// This trades a zero-byte read() syscall for a stat
Expand All @@ -130,7 +20,7 @@ const extractFileSync = (opt: TarOptionsSyncFile) => {
stream.pipe(u)
}

const extractFile = (opt: TarOptionsFile, cb?: () => void) => {
const extractFile = (opt: TarOptionsFile, _?: string[]) => {
const u = new Unpack(opt)
const readSize = opt.maxReadSize || 16 * 1024 * 1024

Expand All @@ -154,9 +44,15 @@ const extractFile = (opt: TarOptionsFile, cb?: () => void) => {
}
})
})
return cb ? p.then(cb, cb) : p
return p
}

const extractSync = (opt: TarOptionsSync) => new UnpackSync(opt)

const extract_ = (opt: TarOptions) => new Unpack(opt)
export const extract = makeCommand<Unpack, UnpackSync>(
extractFileSync,
extractFile,
opt => new UnpackSync(opt),
opt => new Unpack(opt),
(opt, files) => {
if (files?.length) filesFilter(opt, files)
},
)
35 changes: 22 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
export * from './create.js'
export * from './replace.js'
export * from './list.js'
export * from './update.js'
export * from './extract.js'
export {
type TarOptionsWithAliasesAsync,
type TarOptionsWithAliasesAsyncFile,
type TarOptionsWithAliasesAsyncNoFile,
type TarOptionsWithAliasesSyncNoFile,
type TarOptionsWithAliases,
type TarOptionsWithAliasesFile,
type TarOptionsWithAliasesSync,
type TarOptionsWithAliasesSyncFile,
} from './options.js'

export * from './create.js'
export { create as c } from './create.js'
export { replace as r } from './replace.js'
export { list as t } from './list.js'
export { update as u } from './update.js'
export * from './extract.js'
export { extract as x } from './extract.js'

export * from './header.js'
export * from './list.js'
export { list as t } from './list.js'
// classes
export * from './pack.js'
export * from './unpack.js'
export * from './parse.js'
export * from './read-entry.js'
export * from './write-entry.js'
export * from './header.js'
export * from './pax.js'
export * from './read-entry.js'
export * from './replace.js'
export { replace as r } from './replace.js'
export * as types from './types.js'
export * from './unpack.js'
export * from './update.js'
export { update as u } from './update.js'
export * from './write-entry.js'
Loading

0 comments on commit 3062616

Please sign in to comment.