/** * Generates Magento 2 Observer * This command generates the necessary files and configuration to add * an event observer to a Magento 2 system. * * pestle.phar generate_observer Pulsestorm_Generate controller_action_predispatch pulsestorm_generate_listener3 'Pulsestorm\Generate\Model\Observer3' * * @command generate_observer * @argument module Full Module Name? [Pulsestorm_Generate] * @argument event_name Event Name? [controller_action_predispatch] * @argument observer_name Observer Name? [<$module$>_listener] * @argument model_name Class Name? [<$module$>\Model\Observer] */ function pestle_cli($argv) { $module = $argv['module']; $event_name = $argv['event_name']; $observer_name = $argv['observer_name']; $model_name = $argv['model_name']; $method_name = 'execute'; $path_xml_event = initilizeModuleConfig($module, 'events.xml', 'urn:magento:framework:Event/etc/events.xsd'); $xml = simplexml_load_file($path_xml_event); $nodes = $xml->xpath('//event[@name="' . $event_name . '"]'); $node = array_shift($nodes); $event = $node; if (!$node) { $event = $node ? $node : $xml->addChild('event'); $event->addAttribute('name', $event_name); } $observer = $event->addChild('observer'); $observer->addAttribute('name', $observer_name); $observer->addAttribute('instance', $model_name); // $observer->addAttribute('method', $method_name); output("Creating: {$path_xml_event}"); $path = writeStringToFile($path_xml_event, $xml->asXml()); output("Creating: {$model_name}"); $contents = createClassTemplate($model_name, false, '\\Magento\\Framework\\Event\\ObserverInterface'); $contents = str_replace('<$body$>', "\n" . ' public function execute(\\Magento\\Framework\\Event\\Observer $observer){exit(__FILE__);}' . "\n", $contents); createClassFile($model_name, $contents); }
/** * Generates bin/magento command files * This command generates the necessary files and configuration * for a new command for Magento 2's bin/magento command line program. * * pestle.phar Pulsestorm_Generate Example * * Creates * app/code/Pulsestorm/Generate/Command/Example.php * app/code/Pulsestorm/Generate/etc/di.xml * * @command generate_command * @argument module_name In which module? [Pulsestorm_Helloworld] * @argument command_name Command Name? [Testbed] */ function pestle_cli($argv) { $module_info = getModuleInformation($argv['module_name']); $namespace = $module_info->vendor; $module_name = $module_info->name; $module_shortname = $module_info->short_name; $module_dir = $module_info->folder; $command_name = $argv['command_name']; // $command_name = input("Command Name?", 'Testbed'); output($module_dir); createPhpClass($module_dir, $namespace, $module_shortname, $command_name); $path_di_xml = createDiIfNeeded($module_dir); $xml_di = simplexml_load_file($path_di_xml); //get commandlist node $nodes = $xml_di->xpath('/config/type[@name="Magento\\Framework\\Console\\CommandList"]'); $xml_type_commandlist = array_shift($nodes); if (!$xml_type_commandlist) { throw new Exception("Could not find CommandList node"); } $argument = simpleXmlAddNodesXpath($xml_type_commandlist, '/arguments/argument[@name=commands,@xsi:type=array]'); $full_class = $namespace . '\\' . $module_shortname . '\\Command\\' . $command_name; $item_name = str_replace('\\', '_', strToLower($full_class)); $item = $argument->addChild('item', $full_class); $item->addAttribute('name', $item_name); $item->addAttribute('xsi:type', 'object', 'http://www.w3.org/2001/XMLSchema-instance'); $xml_di = formatXmlString($xml_di->asXml()); writeStringToFile($path_di_xml, $xml_di); }
/** * Creates a Route XML * generate_route module area id * @command generate_route */ function pestle_cli($argv) { $module_info = askForModuleAndReturnInfo($argv); $module = $module_info->name; $legend = ['frontend' => 'standard', 'adminhtml' => 'admin']; $areas = array_keys($legend); $area = inputOrIndex('Which area? [frontend, adminhtml]', 'frontend', $argv, 1); $router_id = $legend[$area]; if (!in_array($area, $areas)) { throw new Exception("Invalid areas"); } $frontname = inputOrIndex('Frontname/Route ID?', null, $argv, 2); $route_id = $frontname; $path = $module_info->folder . '/etc/' . $area . '/routes.xml'; if (!file_exists($path)) { $xml = simplexml_load_string(getBlankXml('routes')); writeStringToFile($path, $xml->asXml()); } $xml = simplexml_load_file($path); simpleXmlAddNodesXpath($xml, "router[@id={$router_id}]/" . "route[@id={$route_id},@frontName={$frontname}]/" . "module[@name={$module}]"); writeStringToFile($path, formatXmlString($xml->asXml())); $class = str_replace('_', '\\', $module) . '\\Controller\\Index\\Index'; $controllerClass = createControllerClass($class, $area); $path_controller = getPathFromClass($class); writeStringToFile($path_controller, $controllerClass); output($path); output($path_controller); }
/** * Generates Magento 2 Observer * This command generates the necessary files and configuration to add * an event observer to a Magento 2 system. * * pestle.phar generate_observer Pulsestorm_Generate controller_action_predispatch pulsestorm_generate_listener3 'Pulsestorm\Generate\Model\Observer3' * * @command generate_observer * @argument module Full Module Name? [Pulsestorm_Generate] * @argument event_name Event Name? [controller_action_predispatch] * @argument observer_name Observer Name? [<$module$>_listener] * @argument model_name Class Name? [<$module$>\Model\Observer] */ function pestle_cli($argv) { // $module = input("Full Module Name?", 'Pulsestorm_Helloworld'); // $event_name = input('Event Name?', 'controller_action_predispatch'); // $observer_name = input('Observer Name?', strToLower($module . '_listener')); // $model_name = input('Class Name?', str_replace('_', '\\', $module) . '\\Model\\Observer'); // $method_name = input('Method Name?', 'myMethodName'); $module = $argv['module']; $event_name = $argv['event_name']; $observer_name = $argv['observer_name']; $model_name = $argv['model_name']; $method_name = 'execute'; $path_xml_event = initilizeModuleConfig($module, 'events.xml', '../../../../../lib/internal/Magento/Framework/Event/etc/events.xsd'); $xml = simplexml_load_file($path_xml_event); $nodes = $xml->xpath('//event[@name="' . $event_name . '"]'); $node = array_shift($nodes); $event = $node; if (!$node) { $event = $node ? $node : $xml->addChild('event'); $event->addAttribute('name', $event_name); } $observer = $event->addChild('observer'); $observer->addAttribute('name', $observer_name); $observer->addAttribute('instance', $model_name); // $observer->addAttribute('method', $method_name); output("Creating: {$path_xml_event}"); $path = writeStringToFile($path_xml_event, $xml->asXml()); output("Creating: {$model_name}"); $contents = createClassTemplate($model_name, false, '\\Magento\\Framework\\Event\\ObserverInterface'); $contents = str_replace('<$body$>', "\n" . ' public function execute(\\Magento\\Framework\\Event\\Observer $observer){exit(__FILE__);}' . "\n", $contents); // $contents = createBasicClassContents($model_name, $method_name); createClassFile($model_name, $contents); }
/** * Generates new module XML, adds to file system * This command generates the necessary files and configuration * to add a new module to a Magento 2 system. * * pestle.phar Pulsestorm TestingCreator 0.0.1 * * @argument namespace Vendor Namespace? [Pulsestorm] * @argument name Module Name? [Testbed] * @argument version Version? [0.0.1] * @command generate_module */ function pestle_cli($argv) { $namespace = $argv['namespace']; $name = $argv['name']; $version = $argv['version']; $full_module_name = implode('_', [$namespace, $name]); $config = simplexml_load_string(getBlankXml('module')); $module = $config->addChild('module'); $module->addAttribute('name', $full_module_name); $module->addAttribute('setup_version', $version); $xml = $config->asXml(); $base_dir = getBaseMagentoDir(); $module_dir = implode('/', [$base_dir, 'app/code', $namespace, $name]); $etc_dir = $module_dir . '/etc'; $reg_path = $module_dir . '/registration.php'; if (is_dir($etc_dir)) { output("Module directory [{$etc_dir}] already exists, bailing"); return; } mkdir($etc_dir, 0777, $etc_dir); writeStringToFile($etc_dir . '/module.xml', $xml); output("Created: " . $etc_dir . '/module.xml'); $register = templateRegistrationPhp($full_module_name); writeStringToFile($reg_path, $register); output("Created: " . $reg_path); }
/** * Generates plugin XML * This command generates the necessary files and configuration * to "plugin" to a preexisting Magento 2 object manager object. * * pestle.phar generate_plugin_xml Pulsestorm_Helloworld 'Magento\Framework\Logger\Monolog' 'Pulsestorm\Helloworld\Plugin\Magento\Framework\Logger\Monolog' * * @argument module_name Create in which module? [Pulsestorm_Helloworld] * @argument class Which class are you plugging into? [Magento\Framework\Logger\Monolog] * @argument class_plugin What's your plugin class name? [<$module_name$>\Plugin\<$class$>] * @command generate_plugin_xml */ function pestle_cli($argv) { // $module_info = askForModuleAndReturnInfo($argv); $module_info = getModuleInformation($argv['module_name']); $class = $argv['class']; $class_plugin = $argv['class_plugin']; $path_di = $module_info->folder . '/etc/di.xml'; if (!file_exists($path_di)) { $xml = simplexml_load_string(getBlankXml('di')); writeStringToFile($path_di, $xml->asXml()); output("Created new {$path_di}"); } $xml = simplexml_load_file($path_di); // $plugin_name = strToLower($module_info->name) . '_' . underscoreClass($class); // simpleXmlAddNodesXpath($xml, // "/type[@name=$class]/plugin[@name=$plugin_name,@type=$class_plugin]"); $type = $xml->addChild('type'); $type->addAttribute('name', $class); $plugin = $type->addChild('plugin'); $plugin->addAttribute('name', strToLower($module_info->name) . '_' . underscoreClass($class)); $plugin->addAttribute('type', $class_plugin); writeStringToFile($path_di, formatXmlString($xml->asXml())); output("Added nodes to {$path_di}"); $path_plugin = getPathFromClass($class_plugin); $body = implode("\n", [' //function beforeMETHOD($subject, $arg1, $arg2){}', ' //function aroundMETHOD($subject, $procede, $arg1, $arg2){return $proceed($arg1, $arg2);}', ' //function afterMETHOD($subject, $result){return $result;}']); $class_definition = str_replace('<$body$>', "\n{$body}\n", createClassTemplate($class_plugin)); writeStringToFile($path_plugin, $class_definition); output("Created file {$path_plugin}"); }
function createControllerClassForRoute($module, $area, $acl) { $class = createControllerClassName($module, $area, $acl); $controllerClass = createControllerClass($class, $area); $path_controller = getPathFromClass($class); writeStringToFile($path_controller, $controllerClass); output($path_controller); }
function createViewXmlFile($base_folder, $package, $theme, $area) { $path = $base_folder . '/etc/view.xml'; $xml = simplexml_load_string(getBlankXml('view')); $media = simpleXmlAddNodesXpath($xml, 'media'); output("Creating: {$path}"); writeStringToFile($path, formatXmlString($xml->asXml())); }
function createHandleFile($module_info, $area, $template, $class, $handle) { $xml = simplexml_load_string(getBlankXml('layout_handle')); $name = strToLower($module_info->name) . '_block_' . strToLower(getShortClassNameFromClass($class)); simpleXmlAddNodesXpath($xml, 'referenceBlock[@name=content]/block[' . '@template=' . $template . ',' . '@class=' . $class . ',' . '@name=' . $name . ']'); $path = $module_info->folder . '/view/' . $area . '/layout/' . $handle . '.xml'; output("Creating: {$path}"); writeStringToFile($path, $xml->asXml()); }
/** * Generates a Magento 2.1 ui grid listing and support classes. * * @command magento2:generate:ui:add-column-sections * @argument listing_file Which Listing File? [] * @argument column_name Column Name? [ids] * @argument index_field Index Field/Primary Key? [entity_id] */ function pestle_cli($argv) { $xml = simplexml_load_file($argv['listing_file']); validateAsListing($xml); $columns = getOrCreateColumnsNode($xml); $sectionsColumn = $columns->addChild('selectionsColumn'); $sectionsColumn->addAttribute('name', $argv['column_name']); $argument = addArgument($sectionsColumn, 'data', 'array'); $configItem = addItem($argument, 'config', 'array'); $indexField = addItem($configItem, 'indexField', 'string', $argv['index_field']); writeStringToFile($argv['listing_file'], formatXmlString($xml->asXml())); }
function initilizeModuleConfig($module, $file, $xsd) { $path = implode('/', [getModuleConfigDir($module), $file]); if (file_exists($path)) { return $path; } $xml = addSchemaToXmlString('<config></config>', $xsd); $xml = simplexml_load_string($xml); if (!is_dir(dirname($path))) { mkdir(dirname($path), 0777, true); } writeStringToFile($path, $xml->asXml()); return $path; }
function generateNewClass($argv) { $pathType = getPathFromClass($argv['type']); $typeGlobalNs = '\\' . trim($argv['for'], '\\'); $classContents = createClassTemplate($argv['type'], $typeGlobalNs); if (isTypeInterface($typeGlobalNs)) { $classContents = createClassTemplate($argv['type'], null, $typeGlobalNs); } $classContents = str_replace('<$body$>', '', $classContents); if (!file_exists($pathType)) { output("Creating {$pathType}"); writeStringToFile($pathType, $classContents); } else { output("{$pathType} already exists, skipping creation"); } }
/** * Generates pestle command boiler plate * This command creates the necessary files * for a pestle command * * pestle.phar generate_pestle_command command_name * * @command generate_pestle_command * @argument command_name New Command Name? [foo_bar] */ function pestle_cli($argv) { $command_name = $argv['command_name']; $namespace = createNamespaceFromCommandName($command_name); $command = '<' . '?php' . "\n" . 'namespace ' . $namespace . ';' . "\n" . 'use function Pulsestorm\\Pestle\\Importer\\pestle_import;' . "\n" . 'pestle_import(\'Pulsestorm\\Pestle\\Library\\output\');' . "\n\n" . '/**' . "\n" . '* One Line Description' . "\n" . '*' . "\n" . '* @command ' . $command_name . '' . "\n" . '*/' . "\n" . 'function pestle_cli($argv)' . "\n" . '{' . "\n" . ' output("Hello Sailor");' . "\n" . '}' . "\n"; output("Creating the following class"); output($command); $path_full = createPathFromNamespace($namespace); if (file_exists($path_full)) { output("{$path_full} already exists, bailing"); exit; } writeStringToFile($path_full, $command); output("bbedit {$path_full}"); output("sublime {$path_full}"); output("vi {$path_full}"); }
/** * One Line Description * * @command generate_acl * @argument module_name Which Module? [Pulsestorm_HelloWorld] * @argument rule_ids Rule IDs? [<$module_name$>::top,<$module_name$>::config,] */ function pestle_cli($argv) { extract($argv); $rule_ids = explode(',', $rule_ids); $rule_ids = array_filter($rule_ids); $xml = simplexml_load_string(getBlankXml('acl')); $nodes = $xml->xpath('//resource[@id="Magento_Backend::admin"]'); $node = array_shift($nodes); foreach ($rule_ids as $id) { $id = trim($id); $node = $node->addChild('resource'); $node->addAttribute('id', $id); $node->addAttribute('title', 'TITLE HERE FOR ' . $id); } $path = getBaseModuleDir($module_name) . '/etc/acl.xml'; writeStringToFile($path, formatXmlString($xml->asXml())); output("Created {$path}"); }
/** * One Line Description * * @command generate_acl * @argument module_name Which Module? [Pulsestorm_HelloWorld] * @argument rule_ids Rule IDs? [<$module_name$>::top,<$module_name$>::config,] */ function pestle_cli($argv) { extract($argv); $rule_ids = explode(',', $rule_ids); $rule_ids = array_filter($rule_ids); $path = getBaseModuleDir($module_name) . '/etc/acl.xml'; if (!file_exists($path)) { $xml = simplexml_load_string(getBlankXml('acl')); writeStringToFile($path, $xml->asXml()); } $xml = simplexml_load_file($path); $xpath = 'acl/resources/resource[@id=Magento_Backend::admin]'; foreach ($rule_ids as $id) { $id = trim($id); $xpath .= '/resource[@id=' . $id . ',@title=TITLE HERE FOR]'; } simpleXmlAddNodesXpath($xml, $xpath); writeStringToFile($path, formatXmlString($xml->asXml())); output("Created {$path}"); }
function generateDiXml($module_info) { $path_di = $module_info->folder . '/etc/adminhtml/di.xml'; if (!file_exists($path_di)) { $xml = simplexml_load_string(getBlankXml('di')); writeStringToFile($path_di, $xml->asXml()); output("Created new {$path_di}"); } $xml = simplexml_load_file($path_di); $item = simpleXmlAddNodesXpath($xml, 'type[@name=Magento\\Framework\\View\\Element\\UiComponent\\DataProvider\\CollectionFactory]/' . 'arguments/argument[@name=collections,@xsi:type=array]/' . 'item[@name=pulsestorm_commercebug_log_data_source,@xsi:type=string]'); $item[0] = 'Pulsestorm\\Commercebug\\Model\\ResourceModel\\Log\\Grid\\Collection'; $virtualType = addVirtualType($xml, 'Pulsestorm\\Commercebug\\Model\\ResourceModel\\Log\\Grid\\Collection', 'Magento\\Framework\\View\\Element\\UiComponent\\DataProvider\\SearchResult'); $arguments = $virtualType->addChild('arguments'); $argument = addArgument($arguments, 'mainTable', 'string', 'pulsestorm_commercebug_log'); $argument = addArgument($arguments, 'resourceModel', 'string', 'Pulsestorm\\Commercebug\\Model\\ResourceModel\\Log'); return $xml; }
function replaceObjectManager($file, $array, $tokens_all) { $indexAndPropNames = array_map(function ($result) { $item = new \stdClass(); $item->index = $result->previous_token->originalIndex; $item->propName = $result->newPropName; $item->method = $result->methodCalled; return $item; }, $array); $tokensNew = []; $state = TOKEN_BASELINE; $propName = ''; $method = ''; foreach ($tokens_all as $index => $token) { if ($state === TOKEN_BASELINE) { $thing = array_filter($indexAndPropNames, function ($item) use($index) { return $item->index === $index; }); $thing = array_shift($thing); //if we couldn't extract anything, add the token if (!$thing) { $tokensNew[] = $token; continue; } $state = TOKEN_REMOVING_OM; $propName = $thing->propName; $method = $thing->method; } if ($state === TOKEN_REMOVING_OM && $token->token_value === ')') { $tmp = new \stdClass(); $state = TOKEN_BASELINE; $tmp->token_value = $propName; if ($method === 'create') { $tmp->token_value .= '->create()'; } $tokensNew[] = $tmp; } } $tokenValues = array_map(function ($token) { return $token->token_value; }, $tokensNew); writeStringToFile($file, implode('', $tokenValues)); }
/** * Injects a dependency into a class constructor * This command modifies a preexisting class, adding the provided * dependency to that class's property list, `__construct` parameters * list, and assignment list. * * pestle.phar generate_di app/code/Pulsestorm/Generate/Command/Example.php 'Magento\Catalog\Model\ProductFactory' * * @command generate_di * @argument file Which PHP class file are we injecting into? * @argument class Which class to inject? [Magento\Catalog\Model\ProductFactory] * */ function pestle_cli($argv) { defineStates(); $file = realpath($argv['file']); if (!$file) { exit("Could not find {$file}.\n"); } $class = $argv['class']; $di_lines = (object) getDiLinesFromMage2ClassName($class); $di_lines->parameter = trim(trim($di_lines->parameter, ',')); $indent = getClassIndent(); $contents = file_get_contents($file); $tokens = pestle_token_get_all($contents); $has_constructor = arrayContainsConstructToken($tokens); if (!$has_constructor) { $tokens = insertConstrctorIntoPhpClassFileTokens($tokens); } $state = 0; $c = 0; $new_tokens = []; foreach ($tokens as $token) { $new_tokens[] = $token; if ($state === 0 && $token->token_name === 'T_CLASS') { $state = FOUND_CLASS_KEYWORD; } if ($state === FOUND_CLASS_KEYWORD && $token->token_value === '{') { $state = FOUND_OPENING_CLASS_BRACKET; $tmp = new stdClass(); //$tmp->token_value = "\n" . $indent . '#Property Here' . "\n"; $tmp->token_value = "\n" . $indent . $di_lines->property . "\n"; $new_tokens[] = $tmp; } if ($state === FOUND_OPENING_CLASS_BRACKET && $token->token_value === '__construct') { $state = FOUND_CONSTRUCT; } if ($state === FOUND_CONSTRUCT && $token->token_value === ')') { $state = FOUND_CONSTRUCT_CLOSE_PAREN; $tmp = new stdClass(); $tmp->token_value = "\n" . $indent . $indent . $di_lines->parameter; $current_token = array_pop($new_tokens); $new_tokens = trimWhitespaceFromEndOfTokenArray($new_tokens); $new_tokens = addCommaIfSpoolBackwardsRevealsConstructorParam($new_tokens); $new_tokens[] = $tmp; $new_tokens[] = $current_token; } if ($state === FOUND_CONSTRUCT_CLOSE_PAREN && $token->token_value === '{') { $state = FOUND_CONSTRUCT_OPEN_BRACKET; $tmp = new stdClass(); // $tmp->token_value = "\n" . $indent . '#Property Assignment Here' . "\n"; $tmp->token_value = "\n" . $indent . $indent . $di_lines->assignment; $new_tokens[] = $tmp; } $c++; } $contents = implodeTokensIntoContents($new_tokens); output("Injecting {$class} into {$file}"); writeStringToFile($file, $contents); }
function injectDependencyArgumentIntoFile($class, $file, $propName = false) { $di_lines = (object) getDiLinesFromMage2ClassName($class, $propName); $di_lines->parameter = trim(trim($di_lines->parameter, ',')); $indent = getClassIndent(); $contents = file_get_contents($file); $tokens = pestle_token_get_all($contents); $has_constructor = arrayContainsConstructToken($tokens); if (!$has_constructor) { $tokens = insertConstrctorIntoPhpClassFileTokens($tokens); } $state = 0; $c = 0; $new_tokens = []; foreach ($tokens as $token) { $new_tokens[] = $token; if ($state === 0 && $token->token_name === 'T_CLASS') { $state = FOUND_CLASS_KEYWORD; } if ($state === FOUND_CLASS_KEYWORD && $token->token_value === '{') { $state = FOUND_OPENING_CLASS_BRACKET; $tmp = new stdClass(); //$tmp->token_value = "\n" . $indent . '#Property Here' . "\n"; $comment = $indent . '/**' . "\n" . $indent . '* Injected Dependency Description' . "\n" . $indent . '* ' . "\n" . $indent . '* @var \\' . $class . '' . "\n" . $indent . '*/' . "\n"; $tmp->token_value = "\n" . $comment . $indent . $di_lines->property . "\n"; $new_tokens[] = $tmp; } if ($state === FOUND_OPENING_CLASS_BRACKET && $token->token_value === '__construct') { $state = FOUND_CONSTRUCT; } if ($state === FOUND_CONSTRUCT && $token->token_value === ')') { $state = FOUND_CONSTRUCT_CLOSE_PAREN; $tmp = new stdClass(); $tmp->token_value = "\n" . $indent . $indent . $di_lines->parameter; $current_token = array_pop($new_tokens); $new_tokens = trimWhitespaceFromEndOfTokenArray($new_tokens); $new_tokens = addCommaIfSpoolBackwardsRevealsConstructorParam($new_tokens); $new_tokens[] = $tmp; $new_tokens[] = $current_token; } if ($state === FOUND_CONSTRUCT_CLOSE_PAREN && $token->token_value === '{') { $state = FOUND_CONSTRUCT_OPEN_BRACKET; $tmp = new stdClass(); // $tmp->token_value = "\n" . $indent . '#Property Assignment Here' . "\n"; $tmp->token_value = "\n" . $indent . $indent . $di_lines->assignment; $new_tokens[] = $tmp; } $c++; } $contents = implodeTokensIntoContents($new_tokens); output("Injecting {$class} into {$file}"); writeStringToFile($file, $contents); }
function createDataClass($module_info, $model_name) { $className = str_replace('_', '\\', $module_info->name) . '\\Setup\\InstallData'; $path = getPathFromClass($className); $template = createClassTemplate($className, false, '\\Magento\\Framework\\Setup\\InstallDataInterface'); $contents = str_replace('<$body$>', templateInstallDataFunction(), $template); if (!file_exists($path)) { output("Creating: " . $path); writeStringToFile($path, $contents); } else { output("Data Installer Already Exists: " . $path); } }
/** * One Line Description * * @command generate_menu * @argument module_name Module Name? [Pulsestorm_HelloGenerate] * @argument parent @callback selectParentMenu * @argument id Menu Link ID [<$module_name$>::unique_identifier] * @argument resource ACL Resource [<$id$>] * @argument title Link Title [My Link Title] * @argument action Three Segment Action [frontname/index/index] * @argument sortOrder Sort Order? [10] */ function pestle_cli($argv) { extract($argv); $path = getModuleInformation($module_name)->folder . '/etc/adminhtml/menu.xml'; $xml = loadOrCreateMenuXml($path); $xml = addAttributesToXml($argv, $xml); writeStringToFile($path, $xml->asXml()); output("Writing: {$path}"); output("Done."); }