lib/plugins/print/print.js/Print/print
class Print:  print() {
    // #####################################################################
    // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Variables.js ##
    // ##   there, see `populateService`                                  ##
    // ##   here, see below                                               ##
    // #####################################################################
    // ###################################################################
    // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Service.js ##
    // ##   there, see `constructor` and `loadServiceFileParam`         ##
    // ##   here, see `strip` and `adorn`                               ##
    // ###################################################################
    // The *already loaded* Service (i.e. serverless.yml) is adorned with extras for use by the
    // framework and throughout its codebase.  We could try using the populated service but that
    // would require playing whack-a-mole on issues as changes are made to the service anywhere in
    // the codebase.  Avoiding that, this method must read the serverless.yml file itself, adorn it
    // as the Service class would and then populate it, reversing the adornments thereafter in
    // preparation for printing the service for the user.
    return getServerlessConfigFile(this.serverless).then(svc => {
      const service = svc;
      this.adorn(service);
      // Need to delete variableSyntax to avoid self-matching errors
      this.serverless.variables.loadVariableSyntax();
      delete service.provider.variableSyntax; // cached by adorn, restored by strip
      this.serverless.variables.service = service;
      return this.serverless.variables.populateObject(service).then(populated => {
        let conf = populated;
        this.strip(conf);

        // dig into the object
        if (this.options.path) {
          const steps = this.options.path.split('.');
          for (const step of steps) {
            conf = conf[step];

            if (conf === undefined) {
              return BbPromise.reject(
                new this.serverless.classes.Error(`Path "${this.options.path}" not found`)
              );
            }
          }
        }

        // apply an optional filter
        if (this.options.transform) {
          if (this.options.transform === 'keys') {
            conf = Object.keys(conf);
          } else {
            return BbPromise.reject(
              new this.serverless.classes.Error('Transform can only be "keys"')
            );
          }
        }

        // print configuration in the specified format
        const format = this.options.format || 'yaml';
        let out;

        if (format === 'text') {
          if (_.isArray(conf)) {
            out = conf.join(os.EOL);
          } else {
            if (_.isObject(conf)) {
              return BbPromise.reject(
                new this.serverless.classes.Error('Cannot print an object as "text"')
              );
            }

            out = String(conf);
          }
        } else if (format === 'json') {
          out = jc.stringify(conf, null, '  ');
        } else if (format === 'yaml') {
          out = YAML.dump(JSON.parse(jc.stringify(conf)), { noRefs: true });
        } else {
          return BbPromise.reject(
            new this.serverless.classes.Error('Format must be "yaml", "json" or "text"')
          );
        }

        this.serverless.cli.consoleLog(out);
        return BbPromise.resolve();
      });
    });
  }