import parser from 'yargs-parser' ; import { CliConfigType } from './CliConfigType' ; import { CliCommandType , CliProgramConfigType , CliProgramType , ParsedCommandType , ProcessedProgramType , } from './CliTypes' ; import { CommandManager } from '../Command' ; import { Output } from '../Output' ; export class CliService { private readonly config : CliConfigType ; private programs : { [ program ] : ProcessedProgramType ; } = { } ; /**
     * Lookup index linking to commands in programs.
     */ private commands : { [ token ] : CliCommandType ; } = { } ; private commandManager : CommandManager ; constructor ( commandManager , config = { } ) { this . config = config ; this . commandManager = commandManager ; if ( config . commands ) { this . registerPrograms ( config . commands ) ; } } resolveToken ( args ) { if ( args . help ) { return this . config . fallbackToken ; } return args . _ . shift ( ) || this . config . fallbackToken ; } public async execute ( argv ) { const output = await this . resolve ( argv ) ; output . send ( ) ; } private async resolve ( argv ) { const parsed = parser ( argv , { alias : { h : 'help' } } ) ; const token = this . resolveToken ( parsed ) ; const command = this . commands [ token ] as CliCommandType ; const output = new Output ( ) ; if ( token === argv [ 0 ] ) { argv . splice ( 0 , 1 ) ; } if ( ! command ) { return output . error ( `Unknown command "${ token }".` ) ; } const commandInstance = this . commandManager . getCommand ( command . Command ) as { [ key ] : Function ; } ; try { await commandInstance [ command . action ] ( output , this . validate ( argv , command ) ) ; } catch ( error ) { output . error ( error ) ; } return output ; } private validate ( argv , command ) { const alias = this . collectAliases ( command ) ; const parsed = parser ( argv , { alias } ) ; const args = { } ; if ( Array . isArray ( command . args ) ) { command . args . forEach ( ( { required , name } , index ) => { if ( required && ! parsed . _ [ index ] ) { throw `Missing required argument "${ name }".` ; } if ( parsed . _ [ index ] ) { args [ name ] = parsed . _ [ index ] ; } } ) ; } if ( ! command . config || ! command . config . options ) { return args ; } Object . keys ( command . config . options ) . forEach ( option => { if ( command . config . options [ option ] . required && ! parsed [ option ] ) { throw `Missing required option "${ option }".` ; } args [ option ] = parsed [ option ] ; } ) ; return args ; } private collectAliases ( { config } ) { if ( ! config || ! config . options ) { return { } ; } return Object . keys ( config . options ) . reduce ( ( aliases , name ) => { if ( config . options [ name ] . alias ) { aliases [ name ] = config . options [ name ] . alias ; } return aliases ; } , { } ) ; } public getPrograms ( ) { return this . programs ; } public getCommands ( ) { return this . commands ; } public getCommand ( command ) { return this . commands [ command ] ; } public getConfig ( ) { return this . config ; } private registerPrograms ( programs ) { programs . forEach ( ( programEntry ) => { const programName = ( programEntry as CliProgramType ) . program || this . config . defaultProgramName ; const programConfig = ( programEntry . config || { commands : [ programEntry ] } ) as CliProgramConfigType ; this . registerProgram ( programName , programConfig ) ; } ) ; } private registerProgram ( program , { commands , examples } ) { if ( ! this . programs [ program ] ) { this . programs [ program ] = { program , examples , commands : { } } ; } commands . forEach ( ( command ) => { const { token , args } = this . queParser ( command . commandLine ) ; command . args = args ; command . token = token ; this . programs [ program ] . commands [ token ] = command ; this . commands [ token ] = command ; } ) ; } private queParser ( commandLine ) { return commandLine . split ( ' ' ) . reduce ( ( parsed , value ) => { if ( value [ 0 ] === '<' && value [ value . length - 1 ] === '>' ) { parsed . args . push ( { required : true , name : value . slice ( 1 , - 1 ) } ) ; } else if ( value [ 0 ] === '[' && value [ value . length - 1 ] === ']' ) { parsed . args . push ( { required : false , name : value . slice ( 1 , - 1 ) } ) ; } else { parsed . token = value ; } return parsed ; } , { args : [ ] } ) as ParsedCommandType ; } }