Skip to content

Commit

Permalink
feat: Windows support
Browse files Browse the repository at this point in the history
  • Loading branch information
q2s2t committed Nov 8, 2018
1 parent ee7af71 commit 4cea36d
Show file tree
Hide file tree
Showing 23 changed files with 375 additions and 215 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ coverage
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
7z.exe

# Dummy files for local testing
dummy.*
Expand Down
3 changes: 2 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ Maintenance:
✔ Spaces in achive name, file name or in switches @done(18-08-23 12:36)
☐ Keep a Promise and callback style interface on top (do not recommand using it)
☐ Check all issues from GitHub and test them
☐ fix: dual instance (see test-debug)
Features:
✔ Streams? Buffer or Object mode? @done(18-09-09 18:58)
✔ 7z path option @done(18-09-02 19:34)
☐ Exit codes
☐ TypeScript support
☐ Babel/ES5 etc support
☐ Close/end SevenZipStream
Full-named switches for cleaner API
Full-named switches for cleaner API @done(18-11-04 19:54)
✔ Stream methods returns stream @done(18-10-17 20:35)
☐ Esier to use progress
☐ Status (TU+R. and others?) as plain text
Expand Down
4 changes: 3 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ environment:
# Install scripts. (runs after repo cloning)
install:
# Download 7z Binary to _mock for testing $path special option
- curl -fsS https://www.7-zip.org/a/7z1805-x64.exe -o "./test/_mock/Seven Zip"
- curl -fsS https://www.7-zip.org/a/7z1805-x64.exe -o"7z.exe"
- copy "7z.exe" "./test/_mock/Seven Zip"
- dir test
- 7z # Display 7z version and help
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# install modules
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "lib/index.js",
"scripts": {
"test": "npm run test-ci ; npm run test-cleanup",
"test-ci": "npx mocha -r esm --reporter=spec \"test/**/*.spec.js\" --timeout=0",
"test-ci": "npx mocha -r esm --reporter=spec \"test/**/*.spec.js\"",
"test-docker": "circleci local execute --job build",
"test-cleanup": "rm -rf test/_tmp/* ; touch test/_tmp/.gitkeep",
"test-debug": "DEBUG=node-7z npm run test",
Expand Down Expand Up @@ -52,7 +52,8 @@
"dependencies": {
"cross-spawn": "^6.0.4",
"debug": "^4.1.0",
"esm": "^3.0.79"
"esm": "^3.0.79",
"normalize-path": "^3.0.0"
},
"optionalDependencies": {},
"devDependencies": {
Expand Down
8 changes: 5 additions & 3 deletions src/parser.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import normalizePath from 'normalize-path'
import { INFOS, BODY_PROGRESS, BODY_SYMBOL_FILE, BODY_HASH, INFOS_SPLIT, END_OF_STAGE_HYPHEN } from './regexp.js'
import { STAGE_BODY, STAGE_HEADERS } from './references.js'

Expand Down Expand Up @@ -62,7 +63,7 @@ export function matchBodyProgress (stream, line) {
return {
percent: Number.parseInt(match.groups.percent),
fileCount: Number.parseInt(match.groups.fileCount),
file: match.groups.file
file: normalizePath(match.groups.file)
}
}
return null
Expand All @@ -76,6 +77,7 @@ export function matchBodyProgress (stream, line) {
export function matchBodySymbol (stream, line) {
const match = line.match(BODY_SYMBOL_FILE)
if (match) {
match.groups.file = normalizePath(match.groups.file)
return match.groups
}
return null
Expand All @@ -101,7 +103,7 @@ export function matchBodyList (stream, line) {
const attributes = (!isEmpty(raw.attributes)) ? raw.attributes.trim() : undefined
const size = (!isEmpty(raw.size)) ? Number.parseInt(raw.size) : undefined
const sizeCompressed = (!isEmpty(raw.sizeCompressed)) ? Number.parseInt(raw.sizeCompressed) : undefined
const file = (!isEmpty(raw.file)) ? raw.file.trim() : undefined
const file = (!isEmpty(raw.file)) ? normalizePath(raw.file.trim()) : undefined
return { datetime, attributes, size, sizeCompressed, file }
}

Expand All @@ -119,7 +121,7 @@ export function matchBodyHash (stream, line) {
return {
hash: match.groups.hash,
size: Number.parseInt(match.groups.size),
file: match.groups.file
file: normalizePath(match.groups.file)
}
}
return null
Expand Down
47 changes: 47 additions & 0 deletions src/references.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
/**
* For less cryptic API
*/
export const swApiNames = {
// Booleans
recursive: 'r', // Recurse subdirectories. For `-r0` usage see `raw`
deleteFilesAfter: 'sdel', // Delete files after compression
largePageMode: 'spl', // Set Large Pages mode
storeNtSecurity: 'sni', // Store NT security
alternateStreamExtract: 'snc', // Extract file as alternate stream, if there is ':' character in name
alternateStreamReplace: 'snr', // Replace ':' character to '_' character in paths of alternate streams
storeHardLinks: 'snh', // Store hard links as links (WIM and TAR formats only)
storeSymLinks: 'snl', // Store symbolic links as links (WIM and TAR formats only)
toStdout: 'so', // Write data to stdout
noWildcards: 'spd', // Disable wildcard matching for file names
noRootDuplication: 'spe', // Eliminate duplication of root folder for extract command
fullyQualifiedPaths: 'spf', // Use fully qualified file paths
openFiles: 'ssw', // Compress files open for writing
latestTimeStamp: 'stl', // Set archive timestamp from the most recently modified file
yesToAll: 'y', // Assume Yes on all queries
// Context Booleans
alternateStreamStore: 'sns', // Store NTFS alternate Streams
caseSensitive: 'ssc', // Set Sensitive Case mode
// Arguments
overwrite: 'ao', // Overwrite mode
logLevel: 'bb', // Set output log level
outputDir: 'o', // Set Output directory
password: 'p', // Set Password
archiveNameMode: 'sa', // Set Archive name mode
hashMethod: 'scrc', // Set hash function
listFileCharset: 'scs', // Set charset for list files
sfx: 'sfx', // Create SFX archive
fromStdin: 'si', // Read data from StdIn
cpuAffinity: 'stm', // Set CPU thread affinity mask (hexadecimal number).
excludeArchiveType: 'stx', // Exclude archive type
archiveType: 't', // Type of archive
updateOptions: 'u', // Update options
workingDir: 'w', // Set Working directory
// Repeatings
includeArchive: 'ai', // Include archive filenames
excludeArchive: 'ax', // Exclude archive filenames
outputStreams: 'bs', // Set output stream for output/error/progress
include: 'i', // Include filenames
method: 'm', // Set Compression Method
volumes: 'v', // Create Volumes
exlude: 'x'// Exclude filenames
}

/**
* Switches that can be toggled on or off (boolean switches). Default values
Expand Down
6 changes: 3 additions & 3 deletions src/regexp.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const LINE_SPLIT = /\n|\x08+/
export const LINE_SPLIT = /\n|\r\n|\x08+|\r +\r/
export const BODY_PROGRESS = /^ *(?<percent>\d+)% ?(?<fileCount>\d+)? ?(?<file>.*)$/
export const BODY_SYMBOL_FILE = /^(?<symbol>[=TU+R.-]) (?<file>.+)$/
export const BODY_HASH = /^(?<hash>\S+)? +(?<size>\d*) +(?<file>.+)$/
export const END_OF_STAGE_HYPHEN = /^(-+ +)+-+$/
export const INFOS = /^(?<property>.+?)(?<separator>( = )|(: +))(?<value>.+)$/
export const INFOS_SPLIT = /, +# /
export const ERR_ONE_LINE = /ERROR: (?<message>.*)\n/
export const ERR_MULTIPLE_LINE = /(?<level>WARNING|ERROR): (?<message>.+)(\n(?<path>.+)\n)?/
export const ERR_ONE_LINE = /(?<level>WARNING|ERROR): (?<message>.*)\n(\r\n)?/i
export const ERR_MULTIPLE_LINE = /(?<level>WARNING|ERROR): (?<message>.+)(\n(?<path>.+)\n)?(\r\n)?/i
2 changes: 1 addition & 1 deletion src/special.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export function transformBinToString (options) {
if (isCustomBin) {
return options.$bin
} else {
return '7za'
return '7z'
}
}
48 changes: 36 additions & 12 deletions src/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ import {
ERR_MULTIPLE_LINE
} from './regexp.js'
import { transformBinToString, transformSpecialArrayToArgs } from './special.js'
import { transformSwitchesToArgs } from './switches.js'
import { transformApiToSwitch, transformSwitchesToArgs } from './switches.js'

const debug = debugModule('node-7z')

export class SevenZipStream extends Readable {
constructor (options) {
debug('stream: new SevenZipStream()')
// @TODO must be called with the `new` operator
options.highWaterMark = 16
options.objectMode = true
Expand All @@ -38,7 +37,7 @@ export class SevenZipStream extends Readable {
// Compose child_process args
const wildcards = transformSpecialArrayToArgs(options, '$wildcards')
const raw = transformSpecialArrayToArgs(options, '$raw')
const switches = transformSwitchesToArgs(options)
const switches = transformSwitchesToArgs(transformApiToSwitch(options))
const binSpawn = transformBinToString(options)
const args = options._commandArgs
.concat(wildcards)
Expand All @@ -50,6 +49,7 @@ export class SevenZipStream extends Readable {
this._progressSwitch = args.includes('-bsp1')
this._stage = STAGE_HEADERS
this.info = new Map()
debug('stream: new SevenZipStream()', binSpawn, args)

// When $defer option is specified the stream is constructed but the
// child_process is not spawned, nor the listeners are attached. It allows
Expand Down Expand Up @@ -93,6 +93,18 @@ export class SevenZipStream extends Readable {
stream._childProcess.on('error', function (err) {
stream._onSpawnError(stream, err)
})
// stream._childProcess.on('exit', function () {
// if (stream.err) {
// debug('err: %j', stream.err)
// stream.emit('error', stream.err)
// }
// })
stream._childProcess.on('close', function () {
if (stream.err) {
debug('err: %j', stream.err)
stream.emit('error', stream.err)
}
})
return this
}

Expand Down Expand Up @@ -241,20 +253,32 @@ export class SevenZipStream extends Readable {

_onSubprocessError (stream, chunk) {
const stderr = chunk.toString()
const inLineError = stderr.match(ERR_ONE_LINE)
const offLineError = stderr.match(ERR_MULTIPLE_LINE)
const err = new Error('unknown error')
let errProps = {}
errProps = (inLineError) ? inLineError.groups : errProps
errProps = (offLineError) ? offLineError.groups : errProps
Object.assign(err, errProps)
const matchOneLine = stderr.match(ERR_ONE_LINE)
const matchMultipleLine = stderr.match(ERR_MULTIPLE_LINE)
let err = new Error('unknown error')
if (matchOneLine) {
Object.assign(err, matchOneLine.groups)
}
if (matchMultipleLine) {
Object.assign(err, matchMultipleLine.groups)
}
err.stderr = stderr // @TODO doc: usage of raw stderr to get more info
stream.emit('error', err)
if (stream.err) {
Object.assign(stream.err, err)
} else {
stream.err = err
}
debug('error-stderr: %o', stream.err)
return stream
}

_onSpawnError (stream, err) {
stream.emit('error', err)
if (stream.err) {
Object.assign(stream.err, err)
} else {
stream.err = err
}
debug('error-childprocess: %o', err)
return stream
}
}
28 changes: 21 additions & 7 deletions src/switches.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { swDefaultBool, swContextBool, swRepeating, swArgs } from './references.js'
import { swApiNames, swDefaultBool, swContextBool, swRepeating, swArgs } from './references.js'

// Set deflaut values. This block is out of any function for performances (only
// executed once and code quality concerns (transformSwitchesToArgs to long)
const swApiNamesKeys = Object.keys(swApiNames)
const swDefaultBoolKeys = Object.keys(swDefaultBool)
const swContextBoolKeys = Object.keys(swContextBool)
const swRepeatingKeys = Object.keys(swRepeating)
Expand All @@ -12,12 +13,25 @@ const swDefaults = {
bs: []
}

/**
* Transform an object of options into an array that can be passed to the
* spawned child process. Only cares about the known switches from 7-zip
* @param {Object} options An object of options
* @return {array} Array to pass to the `run` function.
*/
// Transform readable switch API to 7zip cryptic switch API
export function transformApiToSwitch (options) {
Object.keys(options).forEach(function (swApiName) {
const isApiName = (swApiNamesKeys.includes(swApiName))
if (isApiName) {
const swName = swApiNames[swApiName]
options[swName] = options[swApiName]
}
})
if (options.$progress) {
let outputStreams = options.bs || options.outputStreams || []
outputStreams.push('p1')
options.bs = outputStreams
}
return options
}

// Transform an object of options into an array that can be passed to the
// spawned child process. Only cares about the known switches from 7-zip
export function transformSwitchesToArgs (options) {
const switches = Object.assign({}, swDefaultBool, swDefaults, options)

Expand Down
Loading

0 comments on commit 4cea36d

Please sign in to comment.