adonis命令模块学习笔记

来源:互联网 发布:网络教学英语作文优点 编辑:程序博客网 时间:2024/06/07 16:42

基本类(文件):

adonis命令脚本,bin目录下

Kernel类,用来暂时注册命令并且执行命令最外层

Command类,Kernel类直接包含的对象,adonis自己的命令类,包含各种执行命令相关方法

Commander类(在commander模块中也叫Command类):Command类的属性,是一个事件发射器

1.adonis脚本

const path = require('path')//cli模块下包含所有命令的对象const Commands = require('./src/Commands')//命令名称数组const commandNames = []//需要供应商的命令,应该在app.js文件中设置命令供应商,并且使用ace命令来调用这些命令const needProviders = ['repl', 'route:list', 'run:instructions']//获取ace模块const ace = require('./lib/ace')Object.keys(Commands).forEach((name) => {//将命令名称加入数组  commandNames.push(name)//ace加入命令模块导出的对象  ace.addCommand(Commands[name])})try {//获取需要执行的命令名称,如node adonis new中的newconst command = process.argv[2]//如果命令名称有效,且不需要供应商if (commandNames.indexOf(command) > -1 && needProviders.indexOf(command) <= -1) {//这一步将回调方法注册到commander上,对应的事件为commander:xxx,xxx为命令名称    ace.wireUpWithCommander()//这一步通过parse方法、parseArgs方法解析参数,根据命令在commander上触发指定事件    ace.invoke(require('./package'))  } else {//执行当前目录下ace文件    require(path.join(process.cwd(), 'ace'))  }} catch (error) {  if (error.code !== 'ENOENT' && error.code !== 'MODULE_NOT_FOUND') {    throw error  }//如果是上述两个错误码,继续执行命令  ace.wireUpWithCommander()  ace.invoke(require('./package'))}@adonisjs/cli/src/Commands/index.js包含了所有有效命令与对应的导出对象module.exports = {//new命令  new: require('./New'),//install命令  install: require('./Install'),//serve命令  serve: require('./Serve'),//各种帮助名命令    //生成应用程序密钥  'key:generate': require('./KeyGenerate'),    //生成控制器  'make:controller': require('./Make/Controller'),    //生成模型  'make:model': require('./Make/Model'),    //生成模型特性  'make:trait': require('./Make/Trait'),    //生成视图  'make:view': require('./Make/View'),    //生成中间件  'make:middleware': require('./Make/Middleware'),    //生成命令  'make:command': require('./Make/Command'),    //生成异常  'make:exception': require('./Make/Exception'),    //生成模型钩子  'make:hook': require('./Make/Hook'),    //生成数据库迁移类,即Schema类  'make:migration': require('./Make/Migration'),    //生成监听器  'make:listener': require('./Make/Listener'),    //在此环境下可以直接在命令行使用数据库查询构建器与数据库操作,不过必须在项目根目录下执行,且项目要启动  'repl': require('./Repl'),    //生成异常处理器  'make:ehandler': require('./Make/ExceptionHandler'),  'make:seed': require('./Make/Seed'),    //路由列表  'route:list': require('./RouteList'),  'run:instructions': require('./Instructions')}

2.@adonisjs/ace/src/Kernel/index.js中的Kernel类

//鲁大师const _ = require('lodash')//颜色输出const chalk = require('chalk')const isArrowFunction = require('is-arrow-function')//ace的Command类const Command = require('../Command')//原生命令模块const commander = require('../../lib/commander')//空白const WHITE_SPACE = ''//用来注册、调用命令的类class Kernel {  constructor () {   //注册的命令对象,键为命令名称,值为命令对象,包含handle处理方法    this.commands = {}  }//添加一个命令对象  addCommand (command) {//如果是字符串,从容器中获取,一般此时Ioc容器还未初始化,这个不是为一般情况准备的    if (typeof (command) === 'string' && global.use) {      command = global.use(command)    }//不是Command的子类,抛出异常    if (command.prototype instanceof Command === false) {      throw new Error(`Make sure ${command.name} extends the base command`)    }//调用引导方法    command.boot()//作为键值对存储    this.commands[command.commandName] = command  }//将每个Command命令链接到Commander模块对应的类上  wireUpWithCommander () {    _.each(this.commands, (command) => {//这一步会创建一个Commander实例,设置到Command实例中去,并且在Commander实例上为命令设置监听器      command.wireUpWithCommander()    })  }//调用当前Kernel中对应的Command  invoke (packageJson) {    process.env.NO_ANSI = 'false'//从下标为2的元素开始选择    const commandName = process.argv.slice(2)//如果这个数组不存在,或者原参数数组中下标为2的元素不存在,输出帮助信息    if (!commandName || !commandName[0]) {//如果不存在,使用底层命令模块输出帮助信息      return commander.outputHelp()    }//打印adonis版本    if (packageJson && packageJson.version) {      commander.version(packageJson.version)    }//使用commander来解析参数,这一步会触发Command对象包含的Commander(在commander模块中也叫Command类,为了区分)类上的相应事件    commander.parse(process.argv)  }}//为command:*命令事件注册回调函数,即当使用的命令无效时触发这个事件commander  .command('*')  .action(function (command) {    console.log(`\n  error: \`${command}\` is not a registered command \n`)    process.exit(1)  })

3.@adonisjs/ace/src/Command/index.js中的Command类

//命令基类class Command {  constructor () {    //可以在shell上输出颜色字体的方法    this.chalk = new chalk.constructor({ enabled: process.env.NO_ANSI === 'false' })//各种状态图标    this.iconsMain = {      info: this.chalk.cyan('ℹ'),      success: this.chalk.green('✔'),      warn: this.chalk.yellow('⚠'),      error: this.chalk.red('✖')    }//windows系统使用的状态图标    this.iconsWin = {      info: this.chalk.cyan('i'),      success: this.chalk.green('√'),      warn: this.chalk.yellow('‼'),      error: this.chalk.red('×')    }  }//初始化类属性  static _initiate () {    this._booted = false//未被引导    this._name = ''//commonder模块下的Command类实例    this.command = null//参数    this.args = []//选项    this.options = []  }//绑定commonder模块下的Commond类实例  static _bindCommander () {//参数为命令名称    this.command = commander.command(this.commandName).description(this.description)  }//注册所有参数到commander  static _registerArgsWithCommander () {    this.command.arguments(this._stringifyArgs().trim())  }//将选项注册到commonder实例  static _registerOptionsWithCommander () {    _.each(this.options, (option) => {      this.command.option(this._getArgOrOptionName(option), option.description)    })  }//命令名  static get commandName () {    return this._name  }//命令签名  static get signature () {    return ''  }//命令描述  static get description () {    return ''  }//添加一个参数对象,可以在引导方法里调用//参数与选项不是随意的,必须使用方法添加,或者在签名中说明才可以在处理方法中使用static addArgument (arg = {}) {//确保引导了    this._ensureIsBooted()    let mergedArg = {}    _.merge(mergedArg, defaults, arg)    this._validateName(mergedArg.name)    this.args.push(mergedArg)    return this  }//添加选项  static addOption (option = {}) {    this._ensureIsBooted()    let mergedOption = {}    _.merge(mergedOption, defaults, option)    this._validateName(mergedOption.name)    this.options.push(mergedOption)    return this  }//解析签名  static parseSignature () {    if (this.signature) {//获取命令名称、参数、选项的数组      const [name, ...tokens] = this.signature.trim().split(' ')//设置命令名称      this._name = name.trim()            const parsedTokens = parser.parseSignature(tokens.join(' '))//添加签名得到的参数      _.each(parsedTokens.args, this.addArgument.bind(this))//添加签名得到的选项      _.each(parsedTokens.options, this.addOption.bind(this))    }  }//打印帮助信息  static outputHelp (colorize = process.env.NO_ANSI === 'false') {    const ctx = new chalk.constructor({ enabled: colorize })    const stringifiedArgs = this._stringifyArgs()    const maxWidth = this.biggestArg()    const strings = []    strings.push(ctx.magenta.bold('Usage:'))    const args = stringifiedArgs.length ? ` ${stringifiedArgs}` : ''    strings.push(`  ${this.commandName}${args} [options]`)    if (this.args.length) {      strings.push(WHITE_SPACE)      strings.push(ctx.magenta.bold('Arguments:'))      _.each(this.args, (arg) => {        strings.push(`  ${ctx.blue(_.padEnd(arg.name, maxWidth))} ${arg.description}`)      })    }    if (this.options.length) {      strings.push(WHITE_SPACE)      strings.push(ctx.magenta.bold('Options:'))      _.each(this.options, (option) => {        strings.push(`  ${ctx.blue(_.padEnd(this._getArgOrOptionName(option), maxWidth))} ${option.description}`)      })    }    if (this.description) {      strings.push(WHITE_SPACE)      strings.push(ctx.magenta.bold('About:'))      strings.push(`  ${this.description}`)    }    strings.push(WHITE_SPACE)    return strings.join('\n')  }//引导方法  static boot () {    if (this._booted) {      return this    }//初始化    this._initiate()    this._booted = true//解析签名    this.parseSignature()    if (!this.commandName) {      throw new Error('Make sure to define the command name')    }    return this  }//将自身链接到commonder实例  static wireUpWithCommander () {//绑定一个comonder实例,这一步只是创建并且设为属性    this._bindCommander()//注册参数到commonder    this._registerArgsWithCommander()//注册选项到commonder    this._registerOptionsWithCommander()//在commander上注册回调函数,即commander:{commandName}事件的回调函数//被注册回调函数为Command类的commanderAction方法,间接调用了handle方法this.command.action(this.commanderAction.bind(this))    return this  }//命令动作,也是被注册的回调函数  static commanderAction (...input) {//    const command = _.last(input)    const args = _.transform(_.initial(input), (result, arg, index) => {      result[this.args[index].name] = arg || this.args[index].defaultValue || null      return result    }, {})    const options = _.transform(command.opts(), (result, option, name) => {      result[name] = result[name] = command.hasOwnProperty(name) ? command[name] : null      return result    }, {})    return this.exec(args, options, true)  }//执行命令对象的handle方法  static exec (args, options, viaAce) {//检测make是否是全局,并且新建一个Command实例    const commandInstance = typeof (global.make) === 'function' ? global.make(this) : new this()//是否通过ace执行    commandInstance.viaAce = viaAce//执行handle方法    return commandInstance.handle(args, options)  }//默认处理方法为空  handle () {    throw new Error(`Make sure to implement handle method for ${this.constructor.commandName} command`)  }//输出各种颜色信息  info (...input) {    console.log(this.chalk.cyan(...input))  }  warn (...input) {    console.warn(this.chalk.yellow(...input))  }  success (...input) {    console.log(this.chalk.green(...input))  }  error (...input) {    console.error(this.chalk.red(...input))  }  completed (action, message) {    console.log(`${this.chalk.green(action + ':')} ${message}`)  }  failed (action, message) {    console.error(`${this.chalk.red(action + ':')} ${message}`)  }//输出表信息  table (head, body, style = { head: ['cyan'] }) {    const table = new Table({head, style})    if (_.isArray(body)) {      _.each(body, (item) => {        table.push(item)      })    } else if (_.isPlainObject(body)) {      _.each(body, (value, key) => {        table.push([key, value])      })    }    console.log(table.toString())  }//获取图标  icon (type) {    return process.platform === 'win32' ? this.iconsWin[type] : this.iconsMain[type]  }//文件操作  writeFile (file, content, options) {    return fs.outputFile(file, content, options)  }  }}//为命令类注册了一些提问方法,可以与用户交互QUESTION_METHODS.forEach((method) => {  Command.prototype[method] = function (...input) {    return new Question()[method](...input)  }})
Command类提供了一些执行命令的工具方法,它将命令相关事件注册到了commander实例中去,并且将自己的handle方法作为回调,可以说完全委托commander来执行命令了


4.commander模块

//Commond类function Command(name) {//子对象数组  this.commands = [];//选项数组  this.options = [];//可执行命令名称  this._execs = {};//允许的未知选项  this._allowUnknownOption = false;  this._args = [];  this._name = name || '';}/继承了事件发射器Command.prototype.__proto__ = EventEmitter.prototype;//添加一个name代表的命令并返回它Command.prototype.command = function(name, desc, opts) {  if(typeof desc === 'object' && desc !== null){    opts = desc;    desc = null;  }  opts = opts || {};  var args = name.split(/ +/);//使用命令名称创建命令对象  var cmd = new Command(args.shift());//有描述,执行描述方法  if (desc) {    cmd.description(desc);//可执行的    this.executables = true;//将执行命令名称添加到数组    this._execs[cmd._name] = true;    if (opts.isDefault) this.defaultExecutable = cmd._name;  }  cmd._noHelp = !!opts.noHelp;//将自身添加到数组  this.commands.push(cmd);  cmd.parseExpectedArgs(args);//父对象  cmd.parent = this;  if (desc) return this;  return cmd;};//注册回调函数Command.prototype.action = function(fn) {  var self = this;//定义监听器方法  var listener = function(args, unknown) {    // Parse any so-far unknown options    args = args || [];    unknown = unknown || [];//解析选项    var parsed = self.parseOptions(unknown);//必要时输出帮助信息    outputHelpIfNecessary(self, parsed.unknown);//如果有未知选项,停止进程    if (parsed.unknown.length > 0) {      self.unknownOption(parsed.unknown[0]);    }    // Leftover arguments need to be pushed back. Fixes issue #56    if (parsed.args.length) args = parsed.args.concat(args);    self._args.forEach(function(arg, i) {      if (arg.required && null == args[i]) {        self.missingArgument(arg.name);      } else if (arg.variadic) {        if (i !== self._args.length - 1) {          self.variadicArgNotLast(arg.name);        }        args[i] = args.splice(i);      }    });    if (self._args.length) {      args[self._args.length] = self;    } else {      args.push(self);    }//!!!!!!!!!!!!//给回调函数绑定commander实例,与参数    fn.apply(self, args);  };//有没有父命令  var parent = this.parent || this;  var name = parent === this ? '*' : this._name;//当发生command:xxxx事件时激活监听器  parent.on('command:' + name, listener);//需要时注册别名  if (this._alias) parent.on('command:' + this._alias, listener);  return this;};/解析参数,设置选项,当定义之后调用命令Command.prototype.parse = function(argv) {  // implicit help//如果可执行  if (this.executables) this.addImplicitHelpCommand();//存储原始参数  // store raw args  this.rawArgs = argv;  // guess name//猜测命令名词  this._name = this._name || basename(argv[1], '.js');  // github-style sub-commands with no sub-command  if (this.executables && argv.length < 3 && !this.defaultExecutable) {    // this user needs help//添加帮助参数    argv.push('--help');  }  // process argv//解析格式化后的选项  var parsed = this.parseOptions(this.normalize(argv.slice(2)));  var args = this.args = parsed.args;//解析参数,返回结果//!!!!!!!!!!!!!!注意在这一步中会根据命令触发相应的command:事件,然后调用相应的回调方法  var result = this.parseArgs(this.args, parsed.unknown);  // executable sub-commands  var name = result.args[0];  var aliasCommand = null;  // check alias of sub commands  if (name) {    aliasCommand = this.commands.filter(function(command) {      return command.alias() === name;    })[0];  }//执行子命令  if (this._execs[name] && typeof this._execs[name] != "function") {    return this.executeSubCommand(argv, args, parsed.unknown);  } else if (aliasCommand) {    // is alias of a subCommand    args[0] = aliasCommand._name;    return this.executeSubCommand(argv, args, parsed.unknown);  } else if (this.defaultExecutable) {    // use the default subcommand    args.unshift(this.defaultExecutable);    return this.executeSubCommand(argv, args, parsed.unknown);  }  return result;};//解析命令参数Command.prototype.parseArgs = function(args, unknown) {  var name;//参数长度存在  if (args.length) {    name = args[0];//如果命令对应的监听器存在,发射指定命令的事件    if (this.listeners('command:' + name).length) {      this.emit('command:' + args.shift(), args, unknown);    } else {//如果监听器不存在发射command:*的事件,在Kernel中已经注册      this.emit('command:*', args);    }  } else {//如果参数长度部队,输出帮助信息    outputHelpIfNecessary(this, unknown);    // If there were no args and we have unknown options,    // then they are extraneous and we need to error.    if (unknown.length > 0) {      this.unknownOption(unknown[0]);    }  }  return this;};

可见主要的路线为:

Kernel.wireUpWithCommander()->

Command.wireUpWithCommander()->

Commander(这里是模块名称,其实类名还是Command).action(fn)

注册了Command.commanderAction()->exec()->handle()回调方法到command:+name命名空间

Kernel.invoke()->

Commander.parse()->

parseArgs()通过参数激发回调