UNPKG

parallel

Version:

CLI tool to execute shell commands in parallel, based on GNU parallel command

203 lines (194 loc) 5.38 kB
const opts = require('./opts') const crypto = require('crypto') const util = require('./util') // Placeholder definitions with description and GNU compatibility info const placeholders = { // GNU parallel basic placeholders '': { handler: (val) => val, desc: 'the input line', gnu: true }, '.': { handler: (val) => val.replace(/\.[^.]*$/, ''), desc: 'the input line without extension', gnu: true }, '/': { handler: (val) => val.split(/[\/\\]/).pop(), desc: 'the basename of the input line', gnu: true }, '//': { handler: (val) => val.split(/([\/\\])/).slice(0, -2).join(''), desc: 'the dirname of the input line', gnu: true }, '/.': { handler: (val) => placeholders['.'].handler(placeholders['/'].handler(val)), desc: 'the basename of the input line without extension', gnu: true }, '#': { handler: (_, jobId) => jobId, desc: 'the sequence number of the job to run, [1,]', gnu: true }, '%': { handler: (_, jobId) => opts.jobs ? ((jobId - 1) % opts.jobs) + 1 : jobId, desc: 'the job slot number [1, --jobs]', gnu: true }, '##': { handler: () => opts._totalJobs, desc: 'total number of jobs to be run', gnu: true }, // GNU parallel --plus placeholders '..': { handler: (val) => val.replace(/\.[^.]*\.[^.]*$/, ''), desc: 'the input line without two extensions (e.g., file.tar.gz → file)', gnu: true }, '...': { handler: (val) => val.replace(/(\.[^.]*){1,3}$/, ''), desc: 'the input line without up to three extensions (e.g., file.tar.gz.backup → file)', gnu: true }, '/..': { handler: (val) => placeholders['..'].handler(placeholders['/'].handler(val)), desc: 'the basename without two extensions (e.g., path/file.tar.gz → file)', gnu: true }, '/...': { handler: (val) => placeholders['...'].handler(placeholders['/'].handler(val)), desc: 'the basename without three extensions (e.g., path/file.tar.gz.backup → file)', gnu: true }, '+/': { handler: (val) => (val.match(/\//g) || []).length, desc: 'the number of "/" in the input line', gnu: true }, '+.': { handler: (val) => (val.match(/\./g) || []).length, desc: 'the number of "." in the input line', gnu: true }, '+..': { handler: (val) => { const match = val.match(/(\.[^.]*\.[^.]*)$/) return match ? match[1] : '' }, desc: 'the extensions removed by {..} (e.g., file.tar.gz → .tar.gz)', gnu: true }, '+...': { handler: (val) => { const match = val.match(/((\.[^.]*){1,3})$/) return match ? match[1] : '' }, desc: 'the extensions removed by {...} (e.g., file.tar.gz.bak → .tar.gz.bak)', gnu: true }, // Non-GNU extensions 'ext': { handler: (val) => val.split('.').pop(), desc: 'the extension of the input line', gnu: false }, 'trim': { handler: (val) => val.trim(), desc: 'the input line with leading/trailing whitespace removed', gnu: false }, 'v': { handler: (val) => val.toLowerCase(), desc: 'lower case the value', gnu: false }, '^': { handler: (val) => val.toUpperCase(), desc: 'upper case the value', gnu: false }, 't': { handler: () => Date.now(), desc: 'current date-time as a number', gnu: false }, 'T': { handler: () => new Date().toISOString(), desc: 'current date-time in ISO format', gnu: false }, 'd': { handler: () => new Date().toISOString().split('T')[0], desc: 'current date in ISO format', gnu: false }, 'r': { handler: () => 1e5 + Math.floor(Math.random() * 9e5), desc: 'random number between 100000 and 999999', gnu: false }, 'md5': { handler: (val) => crypto.createHash('md5').update(val).digest('hex'), desc: 'MD5 hash of the input line', gnu: false }, 'len': { handler: (val) => val.length, desc: 'the length of the input line in characters', gnu: false }, 'wc': { handler: (val) => val.trim().split(/\s+/).filter(Boolean).length, desc: 'the word count of the input line', gnu: false }, } // Matches any operator above, optionally preceeded by a number, enclosed in curly brackets const REGEX = new RegExp('\\{(-?\\d+)?(' + Object.keys(placeholders).map(util.escapeRegex).join('|') + ')}', 'g') exports.parse = function(jobId, lines) { let any = false const out = [] opts._.forEach(function(arg) { // No placeholder in this one if (!REGEX.test(arg)) { return out.push(arg) } any = true lines.forEach(function(line) { out.push(replace(arg, line, jobId)) }) }) // No placeholder, append input to end if (!any) out.push.apply(out, lines) return out } function replace(arg, line, jobId) { return arg.replace(REGEX, function(_, num, op) { const val = extract(line, num) return placeholders[op].handler(val, jobId) }) } function extract(line, num) { num = parseInt(num, 10) if (!num) return line const cols = split(line) if (num < 0) { num = cols.length + num } else { // They are 1-based num-- } return cols[num] || '' } let sep function split(line) { sep = sep || new RegExp(opts.colsep) return line.replace(/^"|"$/g, '').split(sep) } // Export the placeholders object for help generation exports.placeholders = placeholders