PLWL/src/argv.js

85 lines
3 KiB
JavaScript

'use strict'
const path = require('path')
const fs = require('fs')
// Command-line parsing shared by boot and the single-instance handoff.
// Supported: --launch <id>, --file <path>, --open <path>, or a bare file path.
// Electron re-serializes handoff argv, so options/positionals match in order.
const VALUE_OPTIONS = ['--launch', '--file', '--open']
// Value options, the positionals that follow the switches, and inline
// `--name=value` pairs. Tokens before the first switch are the executable
// (matched against the running app path in dev mode and skipped).
function splitArgv(argv) {
const options = []
const positional = []
const inline = new Map()
const appPath = process.defaultApp && process.argv[1] ? path.resolve(process.argv[1]) : null
let switched = false
for (const arg of Array.isArray(argv) ? argv : []) {
if (typeof arg !== 'string' || !arg) continue
if (arg.startsWith('-')) {
switched = true
const eq = arg.indexOf('=')
const name = eq === -1 ? arg : arg.slice(0, eq)
if (!VALUE_OPTIONS.includes(name)) continue
if (eq === -1) options.push(name)
else inline.set(name, arg.slice(eq + 1))
continue
}
// absolute in the handed-over argv, possibly relative here
if (appPath && path.resolve(arg) === appPath) continue
if (switched) positional.push(arg)
}
return { options, positional, inline }
}
// An option's value: inline `--name=value`, or the positional at the same
// in-order position among the value options.
function optionValue(parsed, name) {
if (parsed.inline.has(name)) return parsed.inline.get(name)
const index = parsed.options.indexOf(name)
if (index === -1) return null
const value = parsed.positional[index]
return value === undefined ? null : value
}
// The file an app shortcut was asked to open; only an absolute path counts.
const FIELD_CODE_RE = /^%[fFuUdDnNickvm]?$/
function launchFileOf(parsed, launchId) {
const explicit = optionValue(parsed, '--file')
// a literal %f token is never a file path
if (explicit && !FIELD_CODE_RE.test(explicit) && path.isAbsolute(explicit)) return explicit
if (!launchId) return null
for (let i = parsed.positional.length - 1; i >= 0; i--) {
const arg = parsed.positional[i]
if (!path.isAbsolute(arg)) continue
try {
if (fs.statSync(arg).isFile()) return arg
} catch {}
}
return null
}
function parseArgv(argv) {
const args = Array.isArray(argv) ? argv : process.argv
const parsed = splitArgv(args)
const launchId = optionValue(parsed, '--launch')
const launchFile = launchFileOf(parsed, launchId)
let openPath = optionValue(parsed, '--open')
if (!openPath && !launchId) {
// Exec %f: the file path arrives as the only non-flag argument
const bare = args.find(
a =>
!a.startsWith('-') &&
/\.(exe|msi|bat|cmd|lnk)$/i.test(a) &&
!/\b(electron|olcxja\.plwl)(\.exe)?$/i.test(a)
)
if (bare) openPath = bare
}
return { launchId, openPath, launchFile }
}
module.exports = { parseArgv }