import * as path from 'path' ; import * as fs from 'fs' ; import { IPathUtils } from '../types' ; import { EnvironmentVariables , IEnvironmentVariablesService } from './types' ; import { promisify } from 'util' ; const access = promisify ( fs . access ) ; const readFile = promisify ( fs . readFile ) ; param path function pathExists ( path ) { return access ( path ) . then ( ( ) => true ) . catch ( ( ) => false ) ; } export class EnvironmentVariablesService implements IEnvironmentVariablesService { private readonly pathVariable : 'PATH' | 'Path' ; constructor ( pathUtils ) { this . pathVariable = pathUtils . getPathVariableName ( ) ; } public async parseFile ( filePath ? , baseVars ? ) { if ( ! filePath || ! await pathExists ( filePath ) ) { return ; } if ( ! fs . lstatSync ( filePath ) . isFile ( ) ) { return ; } return parseEnvFile ( await readFile ( filePath , { encoding : null , flag : null } ) , baseVars ) ; } public mergeVariables ( source , target ) { if ( ! target ) { return ; } const settingsNotToMerge = [ 'PYTHONPATH' , this . pathVariable ] ; Object . keys ( source ) . forEach ( setting => { if ( settingsNotToMerge . indexOf ( setting ) >= 0 ) { return ; } if ( target [ setting ] === undefined ) { target [ setting ] = source [ setting ] ; } } ) ; } public appendPythonPath ( vars , ... pythonPaths ) { return this . appendPaths ( vars , 'PYTHONPATH' , ... pythonPaths ) ; } public appendPath ( vars , ... paths ) { return this . appendPaths ( vars , this . pathVariable , ... paths ) ; } private appendPaths ( vars , variableName , ... pathsToAppend ) { const valueToAppend = pathsToAppend . filter ( item => typeof item === 'string' && item . trim ( ) . length > 0 ) . map ( item => item . trim ( ) ) . join ( path . delimiter ) ; if ( valueToAppend . length === 0 ) { return vars ; } const variable = vars ? vars [ variableName ] : undefined ; if ( variable && typeof variable === 'string' && variable . length > 0 ) { vars [ variableName ] = variable + path . delimiter + valueToAppend ; } else { vars [ variableName ] = valueToAppend ; } return vars ; } } export function parseEnvFile ( lines , baseVars ? ) { const globalVars = baseVars ? baseVars : { } ; const vars = { } ; lines . toString ( ) . split ( '\n' ) . forEach ( ( line , _idx ) => { const [ name , value ] = parseEnvLine ( line ) ; if ( name === '' ) { return ; } vars [ name ] = substituteEnvVars ( value , vars , globalVars ) ; } ) ; return vars ; } function parseEnvLine ( line ) { const match = line . match ( /^\s*([a-zA-Z]\w*)\s*=\s*(.*?)?\s*$/ ) ; if ( ! match ) { return [ '' , '' ] ; } const name = match [ 1 ] ; let value = match [ 2 ] ; if ( value && value !== '' ) { if ( value [ 0 ] === '\'' && value [ value . length - 1 ] === '\'' ) { value = value . substring ( 1 , value . length - 1 ) ; value = value . replace ( /\\n/gm , '\n' ) ; } else if ( value [ 0 ] === '"' && value [ value . length - 1 ] === '"' ) { value = value . substring ( 1 , value . length - 1 ) ; value = value . replace ( /\\n/gm , '\n' ) ; } } else { value = '' ; } return [ name , value ] ; } const SUBST_REGEX = /\${([a-zA-Z]\w*)?([^}\w].*)?}/g ; function substituteEnvVars ( value , localVars , globalVars , missing = '' ) { let invalid = false ; let replacement = value ; replacement = replacement . replace ( SUBST_REGEX , ( match , substName , bogus , offset , orig ) => { if ( offset > 0 && orig [ offset - 1 ] === '\\' ) { return match ; } if ( ( bogus && bogus !== '' ) || ! substName || substName === '' ) { invalid = true ; return match ; } return localVars [ substName ] || globalVars [ substName ] || missing ; } ) ; if ( ! invalid && replacement !== value ) { value = replacement ; } return value . replace ( /\\\$/g , '$' ) ; }