From 70f54d20d4ec73c09dfaf97e1a4ddd48878be57c Mon Sep 17 00:00:00 2001 From: qinkaiwu Date: Wed, 15 May 2024 16:55:35 +0800 Subject: [PATCH 1/5] Add new command `command-change tree-export` to export command tree --- azdev/commands.py | 1 + azdev/operations/command_change/__init__.py | 69 ++++++++++++++++++++- azdev/operations/command_change/util.py | 5 ++ azdev/params.py | 4 ++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/azdev/commands.py b/azdev/commands.py index b646b744..e4cbf939 100644 --- a/azdev/commands.py +++ b/azdev/commands.py @@ -40,6 +40,7 @@ def operation_group(name): with CommandGroup(self, 'command-change', operation_group('command_change')) as g: g.command('meta-export', 'export_command_meta') g.command('meta-diff', 'cmp_command_meta') + g.command('tree-export', 'export_command_tree') with CommandGroup(self, 'cli', operation_group('pypi')) as g: g.command('check-versions', 'verify_versions') diff --git a/azdev/operations/command_change/__init__.py b/azdev/operations/command_change/__init__.py index 2b90ced8..f55f0c40 100644 --- a/azdev/operations/command_change/__init__.py +++ b/azdev/operations/command_change/__init__.py @@ -12,7 +12,7 @@ import azure_cli_diff_tool from azdev.utilities import display, require_azure_cli, heading, get_path_table, filter_by_git_diff from .custom import DiffExportFormat, get_commands_meta, STORED_DEPRECATION_KEY -from .util import export_commands_meta +from .util import export_commands_meta, dump_command_tree from ..statistics import _create_invoker_and_load_cmds, _get_command_source, \ _command_codegen_info # pylint: disable=protected-access from ..statistics.util import filter_modules @@ -129,3 +129,70 @@ def export_command_meta(modules=None, git_source=None, git_target=None, git_repo def cmp_command_meta(base_meta_file, diff_meta_file, only_break=False, output_type="text", output_file=None): return azure_cli_diff_tool.meta_diff(base_meta_file, diff_meta_file, only_break, output_type, output_file) + + +def export_command_tree(modules, output_file=None): + require_azure_cli() + + # allow user to run only on CLI or extensions + cli_only = modules == ['CLI'] + ext_only = modules == ['EXT'] + if cli_only or ext_only: + modules = None + + selected_modules = get_path_table(include_only=modules) + + if cli_only: + selected_modules['ext'] = {} + if ext_only: + selected_modules['core'] = {} + selected_modules['mod'] = {} + + if not any(selected_modules.values()): + logger.warning('No commands selected to check.') + + selected_mod_names = list(selected_modules['mod'].keys()) + selected_mod_names += list(selected_modules['ext'].keys()) + selected_mod_names += list(selected_modules['core'].keys()) + + if selected_mod_names: + display('Modules selected: {}\n'.format(', '.join(selected_mod_names))) + + heading('Export Command Table Meta') + start = time.time() + display('Initializing with loading command table...') + from azure.cli.core import get_default_cli # pylint: disable=import-error + az_cli = get_default_cli() + + # load commands, args, and help + _create_invoker_and_load_cmds(az_cli) + + stop = time.time() + logger.info('Commands loaded in %i sec', stop - start) + display('Commands loaded in {} sec'.format(stop - start)) + command_loader = az_cli.invocation.commands_loader + + # trim command table to selected_modules + command_loader = filter_modules(command_loader, modules=selected_mod_names) + + if not command_loader.command_table: + logger.warning('No commands selected to check.') + + command_tree = {} + + for command_name, command in command_loader.command_table.items(): + module_loader = command_loader.cmd_to_loader_map[command_name] + if not module_loader: + continue + module_loader = module_loader[0] + module_path = module_loader.__class__.__module__ + module_name = module_path.rsplit('.', maxsplit=1)[-1] + parts = command_name.split() + subtree = command_tree + for part in parts[:-1]: + if not subtree.get(part): + subtree[part] = {} + subtree = subtree[part] + subtree[parts[-1]] = module_name + + dump_command_tree(command_tree, output_file) diff --git a/azdev/operations/command_change/util.py b/azdev/operations/command_change/util.py index 03d09198..b4f69b9c 100644 --- a/azdev/operations/command_change/util.py +++ b/azdev/operations/command_change/util.py @@ -73,3 +73,8 @@ def export_commands_meta(commands_meta, meta_output_path=None): os.makedirs(file_folder) with open(file_name, "w") as f_out: f_out.write(jsbeautifier.beautify(json.dumps(module_info), options)) + + +def dump_command_tree(command_tree, output_file): + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(command_tree, f, indent=4) diff --git a/azdev/params.py b/azdev/params.py index 511dd1e5..b34763d8 100644 --- a/azdev/params.py +++ b/azdev/params.py @@ -129,6 +129,10 @@ def load_arguments(self, _): help='format to print diff and suggest message') c.argument('output_file', help='command meta diff json file path to store') + with ArgumentsContext(self, 'command-change tree-export') as c: + c.positional('modules', modules_type) + c.argument('output_file', help='command tree json file path to store') + # region cmdcov with ArgumentsContext(self, 'cmdcov') as c: c.positional('modules', modules_type) From 0c4b95488333a647572af1cc4b5360baead35578 Mon Sep 17 00:00:00 2001 From: qinkaiwu Date: Wed, 15 May 2024 17:07:18 +0800 Subject: [PATCH 2/5] Fix style --- azdev/operations/command_change/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azdev/operations/command_change/__init__.py b/azdev/operations/command_change/__init__.py index f55f0c40..ca3936d6 100644 --- a/azdev/operations/command_change/__init__.py +++ b/azdev/operations/command_change/__init__.py @@ -180,7 +180,7 @@ def export_command_tree(modules, output_file=None): command_tree = {} - for command_name, command in command_loader.command_table.items(): + for command_name, _ in command_loader.command_table.items(): module_loader = command_loader.cmd_to_loader_map[command_name] if not module_loader: continue From ef140d3ed79667091ab9d647d7acfc30ac9ef878 Mon Sep 17 00:00:00 2001 From: qinkaiwu Date: Thu, 16 May 2024 10:52:49 +0800 Subject: [PATCH 3/5] apply suggestions --- azdev/operations/command_change/__init__.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/azdev/operations/command_change/__init__.py b/azdev/operations/command_change/__init__.py index ca3936d6..c497d62d 100644 --- a/azdev/operations/command_change/__init__.py +++ b/azdev/operations/command_change/__init__.py @@ -158,7 +158,7 @@ def export_command_tree(modules, output_file=None): if selected_mod_names: display('Modules selected: {}\n'.format(', '.join(selected_mod_names))) - heading('Export Command Table Meta') + heading('Export Command Tree') start = time.time() display('Initializing with loading command table...') from azure.cli.core import get_default_cli # pylint: disable=import-error @@ -180,19 +180,15 @@ def export_command_tree(modules, output_file=None): command_tree = {} - for command_name, _ in command_loader.command_table.items(): - module_loader = command_loader.cmd_to_loader_map[command_name] - if not module_loader: - continue - module_loader = module_loader[0] - module_path = module_loader.__class__.__module__ - module_name = module_path.rsplit('.', maxsplit=1)[-1] + for command_name, command in command_loader.command_table.items(): + module_source = _get_command_source(command_name, command)['module'] parts = command_name.split() + # The command tree is a tree structure like our azExtCmdTree: https://aka.ms/azExtCmdTree subtree = command_tree for part in parts[:-1]: if not subtree.get(part): subtree[part] = {} subtree = subtree[part] - subtree[parts[-1]] = module_name + subtree[parts[-1]] = module_source dump_command_tree(command_tree, output_file) From d1746478ba4b243ecb77fa1e5a3d7919ad194d9f Mon Sep 17 00:00:00 2001 From: qinkaiwu Date: Thu, 16 May 2024 11:03:53 +0800 Subject: [PATCH 4/5] Update version --- HISTORY.rst | 4 ++++ azdev/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 2387d839..c957c0f5 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,10 @@ Release History =============== +0.1.68 +++++++ +* `azdev command-change tree-export`: Add new command to support export command tree of CLI modules. + 0.1.67 ++++++ * `azdev extension cal-next-version`: Justify preview/exp tag operation based on last version's tag and next version's stable/preview tag. diff --git a/azdev/__init__.py b/azdev/__init__.py index 5b8a3ab3..92f9060d 100644 --- a/azdev/__init__.py +++ b/azdev/__init__.py @@ -4,4 +4,4 @@ # license information. # ----------------------------------------------------------------------------- -__VERSION__ = '0.1.67' +__VERSION__ = '0.1.68' From 6ec111637049b9c9f5b68e0f6d94e0a50ed9f499 Mon Sep 17 00:00:00 2001 From: qinkaiwu Date: Thu, 16 May 2024 13:15:46 +0800 Subject: [PATCH 5/5] Extract tree build and add test --- azdev/operations/command_change/__init__.py | 10 ++-------- azdev/operations/command_change/util.py | 10 ++++++++++ azdev/operations/tests/test_break_change.py | 18 +++++++++++++++++- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/azdev/operations/command_change/__init__.py b/azdev/operations/command_change/__init__.py index c497d62d..07d14041 100644 --- a/azdev/operations/command_change/__init__.py +++ b/azdev/operations/command_change/__init__.py @@ -12,7 +12,7 @@ import azure_cli_diff_tool from azdev.utilities import display, require_azure_cli, heading, get_path_table, filter_by_git_diff from .custom import DiffExportFormat, get_commands_meta, STORED_DEPRECATION_KEY -from .util import export_commands_meta, dump_command_tree +from .util import export_commands_meta, dump_command_tree, add_to_command_tree from ..statistics import _create_invoker_and_load_cmds, _get_command_source, \ _command_codegen_info # pylint: disable=protected-access from ..statistics.util import filter_modules @@ -182,13 +182,7 @@ def export_command_tree(modules, output_file=None): for command_name, command in command_loader.command_table.items(): module_source = _get_command_source(command_name, command)['module'] - parts = command_name.split() # The command tree is a tree structure like our azExtCmdTree: https://aka.ms/azExtCmdTree - subtree = command_tree - for part in parts[:-1]: - if not subtree.get(part): - subtree[part] = {} - subtree = subtree[part] - subtree[parts[-1]] = module_source + add_to_command_tree(command_tree, command_name, module_source) dump_command_tree(command_tree, output_file) diff --git a/azdev/operations/command_change/util.py b/azdev/operations/command_change/util.py index b4f69b9c..cb198ac3 100644 --- a/azdev/operations/command_change/util.py +++ b/azdev/operations/command_change/util.py @@ -75,6 +75,16 @@ def export_commands_meta(commands_meta, meta_output_path=None): f_out.write(jsbeautifier.beautify(json.dumps(module_info), options)) +def add_to_command_tree(tree, key, value): + parts = key.split() + subtree = tree + for part in parts[:-1]: + if not subtree.get(part): + subtree[part] = {} + subtree = subtree[part] + subtree[parts[-1]] = value + + def dump_command_tree(command_tree, output_file): with open(output_file, 'w', encoding='utf-8') as f: json.dump(command_tree, f, indent=4) diff --git a/azdev/operations/tests/test_break_change.py b/azdev/operations/tests/test_break_change.py index 6a81837c..123a7a59 100644 --- a/azdev/operations/tests/test_break_change.py +++ b/azdev/operations/tests/test_break_change.py @@ -8,7 +8,7 @@ import unittest import os from azdev.operations.command_change import export_command_meta, cmp_command_meta -from azdev.operations.command_change.util import get_command_tree +from azdev.operations.command_change.util import get_command_tree, add_to_command_tree class MyTestCase(unittest.TestCase): @@ -57,6 +57,22 @@ def test_diff_meta(self): break self.assertTrue(ignored, "ignored message found") + def test_command_tree(self): + tree = {} + add_to_command_tree(tree, 'a b c', 'd') + add_to_command_tree(tree, 'a b foo', 'bar') + add_to_command_tree(tree, 'a foo', 'baz') + expected = { + 'a': { + 'b': { + 'c': 'd', + 'foo': 'bar' + }, + 'foo': 'baz' + } + } + self.assertDictEqual(tree, expected) + if __name__ == '__main__': unittest.main()