/* eslint-disable no-useless-escape */
/* eslint-disable prefer-promise-reject-errors */
/**
* Builds the command necessary to run the reasoner.
*
* Original author: Cristian Vasquez
*
* Source: https://github.com/cristianvasquez/HES-Agent/blob/master/readme.md
* @module Services/Reasoning
*/
const config = require('../config')
const exec = require('child-process-promise').exec
const Glob = require('glob').Glob
/**
* @typedef {Object} module:Reasoning.Inference
* @property {String} query - Path to N3 file containing the query to be passed to the reasoner
* @property {String[]} data - Array of paths to N3 files containing the auxiliary data to be used by the reasoner
*/
/**
* Wrapper for "invokeEye" that builds the necessary command for you.
*
* @param {Inference} inference - Object containing data to be used by the reasoner
* @returns {Promise} - Promise resolving to the output of the reasoner
* @see {@link invokeEye}
*/
function eyePromise (inference) {
return invokeEye(getEyeCommand(inference), false)
}
/**
* Build up a command for eye, from an expanded inference description
*
* @param {Inference} inference - Object containing data to be used by the reasoner
* @returns {String} - command string to be passed to {@link Reasoning.invokeEye}
*/
function getEyeCommand (inference) {
if (config.serverOptions.verbose) {
console.log(JSON.stringify(inference, null, 2))
}
let command = '"' + config.eyeOptions.eyePath + '"'
/**
* Handle flags
*/
let flags = config.eyeOptions.defaultFlags.join(' ')
if (inference.options) {
if (inference.options.proof) {
flags = ''
}
} else if (inference['eye:flags']) {
if (Array.isArray(inference['eye:flags'])) {
flags = flags + ' ' + inference['eye:flags'].join(' ')
} else {
flags = flags + ' ' + inference['eye:flags']
}
}
command = command + ' ' + flags
/**
* Handle data
*/
if (inference.data) {
const dataLocations = []
if (Array.isArray(inference.data)) {
for (const key in inference.data) {
// trace the location with glob, if the path is relative and refers to multiple files it is handled
const files = new Glob(inference.data[key], { nodir: true, mark: true, sync: true })
if (files.found && files.found.length > 0) {
files.found.forEach(x => dataLocations.push(x))
}
}
} else {
const files = new Glob(inference.data, { nodir: true, mark: true, sync: true })
if (files.found && files.found.length > 0) {
files.found.forEach(x => dataLocations.push(x))
}
}
// replace old date locations with the new ones, so it contains paths to /* locations
inference.data = dataLocations
// build the data part of the command
command = command + ' ' + inference.data.join(' ')
}
/**
* Handle query
*/
if (inference.query) {
if (Array.isArray(inference.query)) {
if (inference.query.length === 1) {
command = command + ' --query ' + inference.query[0]
} else {
throw new Error('cannot handle multiple queries')
}
} else {
command = command + ' --query ' + inference.query
}
}
/**
* Handle proof
*/
// proof only supports urls by the moment
if (inference.proof) {
if (!inference.proof) {
throw new Error('href for proof not specified')
}
if (Array.isArray(inference.proof)) {
command = command + ' --proof ' + inference.proof.join(' --proof ')
} else {
command = command + ' --proof ' + inference.proof
}
}
console.log(command)
return command
}
/**
* Executes the reasoner program using both the command string and the options set inside the server config
*
* @param {String} command - Command to be run
* @param {Boolean} fullOutput - Display full output or not, defaults to true
* @returns {Promise} - Promise resolving to an object containing the stdout and stderr streams of the reasoner
*/
function invokeEye (command, fullOutput = true) {
return new Promise(function (resolve, reject) {
if (config.serverOptions.verbose) {
console.log(command)
}
exec(command, config.eyeOptions.command_arguments)
.then(function (result) {
const stdout = result.stdout
const stderr = result.stderr
// EYE do not show signature as usual.
if (!stderr.match(eyeSignatureRegex)) {
reject({
stdout: result.stdout,
stderr: result.stderr,
error: 'No match for EYE signature'
})
}
// An error detected
const errorMatch = stderr.match(errorRegex)
if (errorMatch) {
reject({
stdout: result.stdout,
stderr: result.stderr,
error: errorMatch
})
}
if (fullOutput) {
resolve({
stdout: result.stdout,
stderr: result.stderr
})
} else {
if (config.eyeOptions.consoleLogging) { console.log(stdout) }
resolve(clean(stdout))
}
})
.catch(function (err) {
console.error('Command line exception :' + err)
reject({
error: err
})
})
})
}
// taken from https://github.com/RubenVerborgh/EyeServer
const commentRegex = /^#.*$\n/mg
const prefixDeclarationRegex = /^@prefix|PREFIX ([\w\-]*:) <([^>]+)>\.?\n/g
const eyeSignatureRegex = /^(Id: euler\.yap|EYE)/m
const errorRegex = /^\*\* ERROR \*\*\s*(.*)$/m
// taken from https://github.com/RubenVerborgh/EyeServer
function clean (n3) {
// remove comments
n3 = n3.replace(commentRegex, '')
// remove prefix declarations from the document, storing them in an object
const prefixes = {}
n3 = n3.replace(prefixDeclarationRegex, function (match, prefix, namespace) {
prefixes[prefix] = namespace.replace(/^file:\/\/.*?([^\/]+)$/, '$1')
return ''
})
// remove unnecessary whitespace from the document
n3 = n3.trim()
// find the used prefixes
const prefixLines = []
for (const prefix in prefixes) {
const namespace = prefixes[prefix]
// EYE does not use prefixes of namespaces ending in a slash (instead of a hash),
// so we apply them manually
if (namespace.match(/\/$/)) {
// warning: this could wreck havoc inside string literals
n3 = n3.replace(new RegExp('<' + escapeForRegExp(namespace) + '(\\w+)>', 'gm'), prefix + '$1')
}
// add the prefix if it's used
// (we conservatively employ a wide definition of "used")
if (n3.match(prefix)) { prefixLines.push('PREFIX ', prefix, ' <', namespace, '>\n') }
}
// join the used prefixes and the rest of the N3
return !prefixLines.length ? n3 : (prefixLines.join('') + '\n' + n3)
}
function escapeForRegExp (text) {
return text.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
}
module.exports = {
getEyeCommand: getEyeCommand,
eyePromise: eyePromise,
invokeEye: invokeEye
}