Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'azdev format' #358

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,19 @@ For instructions on manually writing the commands and tests, see more in
- [Authoring Tests](https://github.com/Azure/azure-cli/blob/dev/doc/authoring_tests.md)

## Style, linter check and testing
1. Check code style (Pylint and PEP8):
1. Auto format code (Black):
```
azdev format <extension-name/module-name>
```
2. Check code style (Pylint and PEP8):
```
azdev style <extension-name/module-name>
```
2. Run static code checks of the CLI command table:
3. Run static code checks of the CLI command table:
```
azdev linter <extension-name/module-name>
```
3. Record or replay CLI tests:
4. Record or replay CLI tests:
```
azdev test <extension-name/module-name>
```
Expand Down
2 changes: 1 addition & 1 deletion azdev/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# license information.
# -----------------------------------------------------------------------------

__VERSION__ = '0.1.40'
__VERSION__ = '0.1.41'
3 changes: 3 additions & 0 deletions azdev/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ def operation_group(name):
with CommandGroup(self, '', operation_group('testtool')) as g:
g.command('test', 'run_tests')

with CommandGroup(self, '', operation_group('format')) as g:
g.command('format', 'auto_format')

with CommandGroup(self, '', operation_group('style')) as g:
g.command('style', 'check_style')

Expand Down
8 changes: 8 additions & 0 deletions azdev/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@
"""


helps['format'] = """
short-summary: Autoformat Python code (black).
examples:
- name: Autoformat Python code using Black.
text: azdev format
"""


helps['style'] = """
short-summary: Check code style (pylint and PEP8).
examples:
Expand Down
119 changes: 119 additions & 0 deletions azdev/operations/format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# -----------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -----------------------------------------------------------------------------

from glob import glob
import multiprocessing
import os
import sys

from knack.log import get_logger
from knack.util import CLIError, CommandResultItem

from azdev.utilities import (
display, heading, py_cmd, get_path_table, filter_by_git_diff)


logger = get_logger(__name__)


# pylint: disable=too-many-statements
def auto_format(modules=None, git_source=None, git_target=None, git_repo=None):

heading('Autoformat')

# 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)

# remove these two non-modules
selected_modules['core'].pop('azure-cli-nspkg', None)
selected_modules['core'].pop('azure-cli-command_modules-nspkg', None)

black_result = None

if cli_only:
ext_names = None
selected_modules['ext'] = {}
if ext_only:
mod_names = None
selected_modules['mod'] = {}
selected_modules['core'] = {}

# filter down to only modules that have changed based on git diff
selected_modules = filter_by_git_diff(selected_modules, git_source, git_target, git_repo)

if not any(selected_modules.values()):
raise CLIError('No modules selected.')

mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['core'].keys())
ext_names = list(selected_modules['ext'].keys())

if mod_names:
display('Modules: {}\n'.format(', '.join(mod_names)))
if ext_names:
display('Extensions: {}\n'.format(', '.join(ext_names)))

exit_code_sum = 0
black_result = _run_black(selected_modules)
exit_code_sum += black_result.exit_code

if black_result.error:
logger.error(black_result.error.output.decode('utf-8'))
logger.error('Black: FAILED\n')
else:
display('Black: COMPLETE\n')

sys.exit(exit_code_sum)


def _combine_command_result(cli_result, ext_result):

final_result = CommandResultItem(None)

def apply_result(item):
if item:
final_result.exit_code += item.exit_code
if item.error:
if final_result.error:
try:
final_result.error.message += item.error.message
except AttributeError:
final_result.error.message += str(item.error)
else:
final_result.error = item.error
setattr(final_result.error, 'message', '')
if item.result:
if final_result.result:
final_result.result += item.result
else:
final_result.result = item.result

apply_result(cli_result)
apply_result(ext_result)
return final_result


def _run_black(modules):

cli_paths = list(modules["core"].values()) + list(modules["mod"].values())
ext_paths = list(modules["ext"].values())

def run(paths, desc):
if not paths:
return None
logger.debug("Running on %s:\n%s", desc, "\n".join(paths))
command = "black -l 120 {}".format(
" ".join(paths)
)
return py_cmd(command, message="Running black on {}...".format(desc))

cli_result = run(cli_paths, "modules")
ext_result = run(ext_paths, "extensions")
return _combine_command_result(cli_result, ext_result)
36 changes: 36 additions & 0 deletions azdev/operations/tests/test_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -----------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -----------------------------------------------------------------------------

import configparser
import unittest
from unittest import mock


class TestConfigFilePath(unittest.TestCase):
def test_black_config_without_setup(self):
mocked_config = configparser.ConfigParser()
mocked_config.add_section("cli")
mocked_config.set("cli", "repo_path", "")
mocked_config.add_section("ext")
mocked_config.set("ext", "repo_paths", "")


def test_black_config_with_partially_setup(self):
cli_repo_path = "~/Azure/azure-cli"
mocked_config = configparser.ConfigParser()
mocked_config.add_section("cli")
mocked_config.set("cli", "repo_path", cli_repo_path)
mocked_config.add_section("ext")
mocked_config.set("ext", "repo_paths", "")

def test_black_config_with_all_setup(self):
cli_repo_path = "~/Azure/azure-cli"
ext_repo_path = "~/Azure/azure-cli-extensions"
mocked_config = configparser.ConfigParser()
mocked_config.add_section("cli")
mocked_config.set("cli", "repo_path", cli_repo_path)
mocked_config.add_section("ext")
mocked_config.set("ext", "repo_paths", ext_repo_path)
3 changes: 3 additions & 0 deletions azdev/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def load_arguments(self, _):
c.argument('report', action='store_true', help='Display results as a report.')
c.argument('untested_params', nargs='+', help='Space-separated list of param dest values to search for (OR logic)')

with ArgumentsContext(self, 'format') as c:
c.positional('modules', modules_type)

with ArgumentsContext(self, 'style') as c:
c.positional('modules', modules_type)
c.argument('pylint', action='store_true', help='Run pylint.')
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
],
install_requires=[
'azure-multiapi-storage',
'black',
'docutils',
'flake8',
'gitpython',
Expand Down