<commit_before>'use strict';

/**
@module ember-cli
*/

var existsSync   = require('exists-sync');
var path         = require('path');
var deprecate    = require('../utilities/deprecate');
var assign       = require('lodash/object/assign');
var SilentError  = require('silent-error');
var Reexporter   = require('../utilities/reexport');
var debug        = require('debug')('ember-cli:addon');

var nodeModulesPath = require('node-modules-path');

var p                   = require('ember-cli-preprocess-registry/preprocessors');
var preprocessJs        = p.preprocessJs;
var preprocessCss       = p.preprocessCss;
var preprocessTemplates = p.preprocessTemplates;

var AddonDiscovery  = require('../models/addon-discovery');
var AddonsFactory   = require('../models/addons-factory');
var CoreObject = require('core-object');
var Project = require('./project');

var upstreamMergeTrees = require('broccoli-merge-trees');
var Funnel     = require('broccoli-funnel');
var walkSync   = require('walk-sync');

var WatchedDir   = require('broccoli-source').WatchedDir;
var UnwatchedDir = require('broccoli-source').UnwatchedDir;

function mergeTrees(inputTree, options) {
  options = options || {};
  options.description = options.annotation;
  var tree = upstreamMergeTrees(inputTree, options);

  tree.description = options && options.description;

  return tree;
}


/**
  Root class for an Addon. If your addon module exports an Object this
  will be extended from this base class. If you export a constructor (function),
  it will **not** extend from this class.

  Hooks:

  - {{#crossLink "Addon/config:method"}}config{{/crossLink}}
  - {{#crossLink "Addon/blueprintsPath:method"}}blueprintsPath{{/crossLink}}
  - {{#crossLink "Addon/includedCommands:method"}}includedCommands{{/crossLink}}
  - {{#crossLink "Addon/serverMiddleware:method"}}serverMiddleware{{/crossLink}}
  - {{#crossLink "Addon/postBuild:method"}}postBuild{{/crossLink}}
  - {{#crossLink "Addon/outputReady:method"}}outputReady{{/crossLink}}
  - {{#crossLink "Addon/preBuild:method"}}preBuild{{/crossLink}}
  - {{#crossLink "Addon/buildError:method"}}buildError{{/crossLink}}
  - {{#crossLink "Addon/included:method"}}included{{/crossLink}}
  - {{#crossLink "Addon/postprocessTree:method"}}postprocessTree{{/crossLink}}
  - {{#crossLink "Addon/treeFor:method"}}treeFor{{/crossLink}}

  @class Addon
  @extends CoreObject
  @constructor
  @param {(Project|Addon)} parent The project or addon that directly depends on this addon
  @param {Project} project The current project (deprecated)
*/
function Addon(parent, project) {
  this.parent = parent;
  this.project = project;
  this.ui = project && project.ui;
  this.addonPackages = {};
  this.addons = [];
  this.addonDiscovery = new AddonDiscovery(this.ui);
  this.addonsFactory = new AddonsFactory(this, this.project);
  this.registry = p.defaultRegistry(this);
  this._didRequiredBuildPackages = false;

  if (!this.root) {
    throw new Error('Addon classes must be instantiated with the `root` property');
  }
  this.nodeModulesPath = nodeModulesPath(this.root);

  this.treePaths = {
    app:               'app',
    styles:            'app/styles',
    templates:         'app/templates',
    addon:             'addon',
    'addon-styles':    'addon/styles',
    'addon-templates': 'addon/templates',
    vendor:            'vendor',
    'test-support':    'test-support',
    public:            'public'
  };

  this.treeForMethods = {
    app:               'treeForApp',
    styles:            'treeForStyles',
    templates:         'treeForTemplates',
    'addon-templates': 'treeForAddonTemplates',
    addon:             'treeForAddon',
    vendor:            'treeForVendor',
    'test-support':    'treeForTestSupport',
    public:            'treeForPublic'
  };

  p.setupRegistry(this);

  if (!this.name) {
    throw new SilentError('An addon must define a `name` property.');
  }
}

Addon.__proto__ = CoreObject;
Addon.prototype.constructor = Addon;

Addon.prototype.hintingEnabled = function() {
  var isProduction = process.env.EMBER_ENV === 'production';
  var testsEnabledDefault = process.env.EMBER_CLI_TEST_COMMAND || !isProduction;

  return testsEnabledDefault;
};

/**
  Loads all required modules for a build

  @private
  @method _requireBuildPackages
 */

Addon.prototype._requireBuildPackages = function() {
  if (this._didRequiredBuildPackages === true) {
    return;
  } else {
    this._didRequiredBuildPackages = true;
  }

  this.transpileModules = deprecatedAddonFilters(this, 'this.transpileModules', 'broccoli-es6modules', function(tree, options) {
    return new (require('broccoli-es6modules'))(tree, options);
  });

  this.pickFiles = deprecatedAddonFilters(this, 'this.pickFiles', 'broccoli-funnel', function(tree, options) {
    return new Funnel(tree, options);
  });


  this.Funnel = deprecatedAddonFilters(this, 'new this.Funnel(..)', 'broccoli-funnel', function(tree, options) {
    return new Funnel(tree, options);
  });

  this.mergeTrees = deprecatedAddonFilters(this, 'this.mergeTrees', 'broccoli-merge-trees', mergeTrees);
  this.walkSync = deprecatedAddonFilters(this, 'this.walkSync', 'node-walk-sync', walkSync);
};

function deprecatedAddonFilters(addon, name, insteadUse, fn) {
  return function(tree, options) {
    var message = '[Deprecated] ' + name + ' is deprecated, please use ' + insteadUse + ' directly instead  [addon: ' + addon.name + ']';
    this.ui && this.ui.writeLine(message);

    if (!this.ui)  {
      console.warn(message);
    }

    return fn(tree, options);
  };
}

/**
  Shorthand method for [broccoli-sourcemap-concat](https://github.com/ef4/broccoli-sourcemap-concat)

  @private
  @method concatFiles
  @param {tree} tree Tree of files
  @param {Object} options Options for broccoli-sourcemap-concat
  @return {tree} Modified tree
*/
Addon.prototype.concatFiles = function(tree, options) {
  options.sourceMapConfig = this.app.options.sourcemaps;
  return require('broccoli-sourcemap-concat')(tree, options);
};

/**
  Returns whether or not this addon is running in development

  @public
  @method isDevelopingAddon
  @return {Boolean}
*/
Addon.prototype.isDevelopingAddon = function() {
  if (process.env.EMBER_ADDON_ENV === 'development' && (this.parent instanceof Project)) {
    return this.parent.name() === this.name;
  }
  return false;
};

/**
  Discovers all child addons of this addon and stores their names and
  package.json contents in this.addonPackages as key-value pairs

  @private
  @method discoverAddons
 */
Addon.prototype.discoverAddons = function() {
  var addonsList = this.addonDiscovery.discoverChildAddons(this);

  this.addonPackages = this.addonDiscovery.addonPackages(addonsList);
};

Addon.prototype.initializeAddons = function() {
  if (this._addonsInitialized) {
    return;
  }
  this._addonsInitialized = true;

  debug('initializeAddons for: %s', this.name);

  this.discoverAddons();
  this.addons = this.addonsFactory.initializeAddons(this.addonPackages);

  this.addons.forEach(function(addon) {
    debug('addon: %s', addon.name);
  });
};

/**
  Invoke the specified method for each enabled addon.

  @private
  @method eachAddonInvoke
  @param {String} methodName the method to invoke on each addon
  @param {Array} args the arguments to pass to the invoked method
*/
Addon.prototype.eachAddonInvoke = function eachAddonInvoke(methodName, args) {
  this.initializeAddons();

  var invokeArguments = args || [];

  return this.addons.map(function(addon) {
    if (addon[methodName]) {
      return addon[methodName].apply(addon, invokeArguments);
    }
  }).filter(Boolean);
};
/**
  Generates a tree for the specified path

  @private
  @method treeGenerator
  @return {tree}
*/
Addon.prototype.treeGenerator = function(dir) {
  var tree;
  if (this.project._watchmanInfo.canNestRoots || this.isDevelopingAddon()) {
    tree = new WatchedDir(dir);
  } else {
    tree = new UnwatchedDir(dir);
  }

  return tree;
};

/**
  Returns a given type of tree (if present), merged with the
  application tree. For each of the trees available using this
  method, you can also use a direct method called treeFor[Type] (eg. `treeForApp`).

  Available tree names:
  - app
  - styles
  - templates
  - {{#crossLink "Addon/treeForAddon:method"}}addon{{/crossLink}}
  - vendor
  - test-support
  - {{#crossLink "Addon/treeForPublic:method"}}public{{/crossLink}}

  @public
  @method treeFor
  @param {String} name
  @return {tree}
*/
Addon.prototype.treeFor = function treeFor(name) {
  this._requireBuildPackages();

  var tree;
  var trees = this.eachAddonInvoke('treeFor', [name]);

  if (tree = this._treeFor(name)) {
    trees.push(tree);
  }

  if (this.isDevelopingAddon() && this.hintingEnabled() && name === 'app') {
    trees.push(this.jshintAddonTree());
  }

  return mergeTrees(trees.filter(Boolean), {
    overwrite: true,
    annotation: 'Addon#treeFor (' + this.name + ' - ' + name + ')'
  });
};

/**
  @private
  @param {String} name
  @method _treeFor
  @return {tree}
*/
Addon.prototype._treeFor = function _treeFor(name) {
  var treePath = path.resolve(this.root, this.treePaths[name]);
  var treeForMethod = this.treeForMethods[name];
  var tree;

  if (existsSync(treePath)) {
    tree = this.treeGenerator(treePath);
  }

  if (this[treeForMethod]) {
    tree = this[treeForMethod](tree);
  }

  return tree;
};

/**
  This method is called when the addon is included in a build. You
  would typically use this hook to perform additional imports

  ```js
    included: function(app) {
      app.import(somePath);
    }
  ```

  @public
  @method included
  @param {EmberApp} app The application object
*/
Addon.prototype.included = function(/* app */) {
  if (!this._addonsInitialized) {
    // someone called `this._super.included` without `apply` (because of older
    // core-object issues that prevent a "real" super call from working properly)
    return;
  }

  this.eachAddonInvoke('included', [this]);
};

/**
  Returns the tree for all public files

  @public
  @method treeForPublic
  @param {Tree} tree
  @return {Tree} Public file tree
*/
Addon.prototype.treeForPublic = function(tree) {
  this._requireBuildPackages();

  if (!tree) {
    return tree;
  }

  return new Funnel(tree, {
    srcDir: '/',
    destDir: '/' + this.moduleName()
  });
};

/**
  Returns a tree for this addon

  @public
  @method treeForAddon
  @param {Tree} tree
  @return {Tree} Addon file tree
*/
Addon.prototype.treeForAddon = function(tree) {
  this._requireBuildPackages();

  if (!tree) {
    return tree;
  }

  var addonTree = this.compileAddon(tree);
  var stylesTree = this.compileStyles(this._treeFor('addon-styles'));

  return mergeTrees([addonTree, stylesTree].filter(Boolean), {
    annotation: 'Addon#treeForAddon(' + this.name + ')'
  });
};

/**
  @method treeForStyles
  @param {Tree} tree The tree to process, usually `app/styles/` in the addon.
  @returns {Tree} The return tree has the same contents as the input tree, but is moved so that the `app/styles/` path is preserved.
*/
Addon.prototype.treeForStyles = function(tree) {
  this._requireBuildPackages();

  if (!tree) {
    return tree;
  }

  return new this.Funnel(tree, {
    destDir: 'app/styles'
  });
};

/**
  Runs the styles tree through preprocessors.

  @private
  @method compileStyles
  @param {Tree} tree Styles file tree
  @return {Tree} Compiled styles tree
*/
Addon.prototype.compileStyles = function(tree) {
  this._requireBuildPackages();

  if (tree) {
    return preprocessCss(tree, '/', '/', {
      outputPaths: { 'addon': this.name + '.css' },
      registry: this.registry
    });
  }
};

/**
  Looks in the addon/ and addon/templates trees to determine if template files
  exists that need to be precompiled.

  This is executed once when building, but not on rebuilds.<commit_msg>[fixes #5092] remove long since deprecated this.Funnel usage from internals (keep around for older add ons though)<commit_after>'use strict';

/**
@module ember-cli
*/

var existsSync   = require('exists-sync');
var path         = require('path');
var deprecate    = require('../utilities/deprecate');
var assign       = require('lodash/object/assign');
var SilentError  = require('silent-error');
var Reexporter   = require('../utilities/reexport');
var debug        = require('debug')('ember-cli:addon');

var nodeModulesPath = require('node-modules-path');

var p                   = require('ember-cli-preprocess-registry/preprocessors');
var preprocessJs        = p.preprocessJs;
var preprocessCss       = p.preprocessCss;
var preprocessTemplates = p.preprocessTemplates;

var AddonDiscovery  = require('../models/addon-discovery');
var AddonsFactory   = require('../models/addons-factory');
var CoreObject = require('core-object');
var Project = require('./project');

var upstreamMergeTrees = require('broccoli-merge-trees');
var Funnel     = require('broccoli-funnel');
var walkSync   = require('walk-sync');

var WatchedDir   = require('broccoli-source').WatchedDir;
var UnwatchedDir = require('broccoli-source').UnwatchedDir;

function mergeTrees(inputTree, options) {
  options = options || {};
  options.description = options.annotation;
  var tree = upstreamMergeTrees(inputTree, options);

  tree.description = options && options.description;

  return tree;
}


/**
  Root class for an Addon. If your addon module exports an Object this
  will be extended from this base class. If you export a constructor (function),
  it will **not** extend from this class.

  Hooks:

  - {{#crossLink "Addon/config:method"}}config{{/crossLink}}
  - {{#crossLink "Addon/blueprintsPath:method"}}blueprintsPath{{/crossLink}}
  - {{#crossLink "Addon/includedCommands:method"}}includedCommands{{/crossLink}}
  - {{#crossLink "Addon/serverMiddleware:method"}}serverMiddleware{{/crossLink}}
  - {{#crossLink "Addon/postBuild:method"}}postBuild{{/crossLink}}
  - {{#crossLink "Addon/outputReady:method"}}outputReady{{/crossLink}}
  - {{#crossLink "Addon/preBuild:method"}}preBuild{{/crossLink}}
  - {{#crossLink "Addon/buildError:method"}}buildError{{/crossLink}}
  - {{#crossLink "Addon/included:method"}}included{{/crossLink}}
  - {{#crossLink "Addon/postprocessTree:method"}}postprocessTree{{/crossLink}}
  - {{#crossLink "Addon/treeFor:method"}}treeFor{{/crossLink}}

  @class Addon
  @extends CoreObject
  @constructor
  @param {(Project|Addon)} parent The project or addon that directly depends on this addon
  @param {Project} project The current project (deprecated)
*/
function Addon(parent, project) {
  this.parent = parent;
  this.project = project;
  this.ui = project && project.ui;
  this.addonPackages = {};
  this.addons = [];
  this.addonDiscovery = new AddonDiscovery(this.ui);
  this.addonsFactory = new AddonsFactory(this, this.project);
  this.registry = p.defaultRegistry(this);
  this._didRequiredBuildPackages = false;

  if (!this.root) {
    throw new Error('Addon classes must be instantiated with the `root` property');
  }
  this.nodeModulesPath = nodeModulesPath(this.root);

  this.treePaths = {
    app:               'app',
    styles:            'app/styles',
    templates:         'app/templates',
    addon:             'addon',
    'addon-styles':    'addon/styles',
    'addon-templates': 'addon/templates',
    vendor:            'vendor',
    'test-support':    'test-support',
    public:            'public'
  };

  this.treeForMethods = {
    app:               'treeForApp',
    styles:            'treeForStyles',
    templates:         'treeForTemplates',
    'addon-templates': 'treeForAddonTemplates',
    addon:             'treeForAddon',
    vendor:            'treeForVendor',
    'test-support':    'treeForTestSupport',
    public:            'treeForPublic'
  };

  p.setupRegistry(this);

  if (!this.name) {
    throw new SilentError('An addon must define a `name` property.');
  }
}

Addon.__proto__ = CoreObject;
Addon.prototype.constructor = Addon;

Addon.prototype.hintingEnabled = function() {
  var isProduction = process.env.EMBER_ENV === 'production';
  var testsEnabledDefault = process.env.EMBER_CLI_TEST_COMMAND || !isProduction;

  return testsEnabledDefault;
};

/**
  Loads all required modules for a build

  @private
  @method _requireBuildPackages
 */

Addon.prototype._requireBuildPackages = function() {
  if (this._didRequiredBuildPackages === true) {
    return;
  } else {
    this._didRequiredBuildPackages = true;
  }

  this.transpileModules = deprecatedAddonFilters(this, 'this.transpileModules', 'broccoli-es6modules', function(tree, options) {
    return new (require('broccoli-es6modules'))(tree, options);
  });

  this.pickFiles = deprecatedAddonFilters(this, 'this.pickFiles', 'broccoli-funnel', function(tree, options) {
    return new Funnel(tree, options);
  });


  this.Funnel = deprecatedAddonFilters(this, 'new this.Funnel(..)', 'broccoli-funnel', function(tree, options) {
    return new Funnel(tree, options);
  });

  this.mergeTrees = deprecatedAddonFilters(this, 'this.mergeTrees', 'broccoli-merge-trees', mergeTrees);
  this.walkSync = deprecatedAddonFilters(this, 'this.walkSync', 'node-walk-sync', walkSync);
};

function deprecatedAddonFilters(addon, name, insteadUse, fn) {
  return function(tree, options) {
    var message = '[Deprecated] ' + name + ' is deprecated, please use ' + insteadUse + ' directly instead  [addon: ' + addon.name + ']';
    this.ui && this.ui.writeLine(message);

    if (!this.ui)  {
      console.warn(message);
    }

    return fn(tree, options);
  };
}

/**
  Shorthand method for [broccoli-sourcemap-concat](https://github.com/ef4/broccoli-sourcemap-concat)

  @private
  @method concatFiles
  @param {tree} tree Tree of files
  @param {Object} options Options for broccoli-sourcemap-concat
  @return {tree} Modified tree
*/
Addon.prototype.concatFiles = function(tree, options) {
  options.sourceMapConfig = this.app.options.sourcemaps;
  return require('broccoli-sourcemap-concat')(tree, options);
};

/**
  Returns whether or not this addon is running in development

  @public
  @method isDevelopingAddon
  @return {Boolean}
*/
Addon.prototype.isDevelopingAddon = function() {
  if (process.env.EMBER_ADDON_ENV === 'development' && (this.parent instanceof Project)) {
    return this.parent.name() === this.name;
  }
  return false;
};

/**
  Discovers all child addons of this addon and stores their names and
  package.json contents in this.addonPackages as key-value pairs

  @private
  @method discoverAddons
 */
Addon.prototype.discoverAddons = function() {
  var addonsList = this.addonDiscovery.discoverChildAddons(this);

  this.addonPackages = this.addonDiscovery.addonPackages(addonsList);
};

Addon.prototype.initializeAddons = function() {
  if (this._addonsInitialized) {
    return;
  }
  this._addonsInitialized = true;

  debug('initializeAddons for: %s', this.name);

  this.discoverAddons();
  this.addons = this.addonsFactory.initializeAddons(this.addonPackages);

  this.addons.forEach(function(addon) {
    debug('addon: %s', addon.name);
  });
};

/**
  Invoke the specified method for each enabled addon.

  @private
  @method eachAddonInvoke
  @param {String} methodName the method to invoke on each addon
  @param {Array} args the arguments to pass to the invoked method
*/
Addon.prototype.eachAddonInvoke = function eachAddonInvoke(methodName, args) {
  this.initializeAddons();

  var invokeArguments = args || [];

  return this.addons.map(function(addon) {
    if (addon[methodName]) {
      return addon[methodName].apply(addon, invokeArguments);
    }
  }).filter(Boolean);
};
/**
  Generates a tree for the specified path

  @private
  @method treeGenerator
  @return {tree}
*/
Addon.prototype.treeGenerator = function(dir) {
  var tree;
  if (this.project._watchmanInfo.canNestRoots || this.isDevelopingAddon()) {
    tree = new WatchedDir(dir);
  } else {
    tree = new UnwatchedDir(dir);
  }

  return tree;
};

/**
  Returns a given type of tree (if present), merged with the
  application tree. For each of the trees available using this
  method, you can also use a direct method called treeFor[Type] (eg. `treeForApp`).

  Available tree names:
  - app
  - styles
  - templates
  - {{#crossLink "Addon/treeForAddon:method"}}addon{{/crossLink}}
  - vendor
  - test-support
  - {{#crossLink "Addon/treeForPublic:method"}}public{{/crossLink}}

  @public
  @method treeFor
  @param {String} name
  @return {tree}
*/
Addon.prototype.treeFor = function treeFor(name) {
  this._requireBuildPackages();

  var tree;
  var trees = this.eachAddonInvoke('treeFor', [name]);

  if (tree = this._treeFor(name)) {
    trees.push(tree);
  }

  if (this.isDevelopingAddon() && this.hintingEnabled() && name === 'app') {
    trees.push(this.jshintAddonTree());
  }

  return mergeTrees(trees.filter(Boolean), {
    overwrite: true,
    annotation: 'Addon#treeFor (' + this.name + ' - ' + name + ')'
  });
};

/**
  @private
  @param {String} name
  @method _treeFor
  @return {tree}
*/
Addon.prototype._treeFor = function _treeFor(name) {
  var treePath = path.resolve(this.root, this.treePaths[name]);
  var treeForMethod = this.treeForMethods[name];
  var tree;

  if (existsSync(treePath)) {
    tree = this.treeGenerator(treePath);
  }

  if (this[treeForMethod]) {
    tree = this[treeForMethod](tree);
  }

  return tree;
};

/**
  This method is called when the addon is included in a build. You
  would typically use this hook to perform additional imports

  ```js
    included: function(app) {
      app.import(somePath);
    }
  ```

  @public
  @method included
  @param {EmberApp} app The application object
*/
Addon.prototype.included = function(/* app */) {
  if (!this._addonsInitialized) {
    // someone called `this._super.included` without `apply` (because of older
    // core-object issues that prevent a "real" super call from working properly)
    return;
  }

  this.eachAddonInvoke('included', [this]);
};

/**
  Returns the tree for all public files

  @public
  @method treeForPublic
  @param {Tree} tree
  @return {Tree} Public file tree
*/
Addon.prototype.treeForPublic = function(tree) {
  this._requireBuildPackages();

  if (!tree) {
    return tree;
  }

  return new Funnel(tree, {
    srcDir: '/',
    destDir: '/' + this.moduleName()
  });
};

/**
  Returns a tree for this addon

  @public
  @method treeForAddon
  @param {Tree} tree
  @return {Tree} Addon file tree
*/
Addon.prototype.treeForAddon = function(tree) {
  this._requireBuildPackages();

  if (!tree) {
    return tree;
  }

  var addonTree = this.compileAddon(tree);
  var stylesTree = this.compileStyles(this._treeFor('addon-styles'));

  return mergeTrees([addonTree, stylesTree].filter(Boolean), {
    annotation: 'Addon#treeForAddon(' + this.name + ')'
  });
};

/**
  @method treeForStyles
  @param {Tree} tree The tree to process, usually `app/styles/` in the addon.
  @returns {Tree} The return tree has the same contents as the input tree, but is moved so that the `app/styles/` path is preserved.
*/
Addon.prototype.treeForStyles = function(tree) {
  this._requireBuildPackages();

  if (!tree) {
    return tree;
  }

  return new Funnel(tree, {
    destDir: 'app/styles'
  });
};

/**
  Runs the styles tree through preprocessors.

  @private
  @method compileStyles
  @param {Tree} tree Styles file tree
  @return {Tree} Compiled styles tree
*/
Addon.prototype.compileStyles = function(tree) {
  this._requireBuildPackages();

  if (tree) {
    return preprocessCss(tree, '/', '/', {
      outputPaths: { 'addon': this.name + '.css' },
      registry: this.registry
    });
  }
};

/**
  Looks in the addon/ and addon/templates trees to determine if template files
  exists that need to be precompiled.

  This is executed once when building, but not on rebuilds.
{'fraction_non_alphanumeric': 0.13611134471448996, 'fraction_numerical': 0.00042048608191068876, 'mean_word_length': 3.823159602514703, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6675850', 'n_tokens_mistral': 7677, 'n_tokens_neox': 7508, 'n_words': 1789}