Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/azure-cli-core/azure/cli/core/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,41 @@ def format_styled_text(styled_text, theme=None):
return ''.join(formatted_parts)


def highlight_command(raw_command):
"""Highlight a command with colors.

For example, for

az group create --name myrg --location westus

The command name 'az group create', argument name '--name', '--location' are marked as ACTION style.
The argument value 'myrg' and 'westus' are marked as PRIMARY style.
If the argument is provided as '--location=westus', it will be marked as PRIMARY style.

:param raw_command: The command that needs to be highlighted.
:type raw_command: str
:return: The styled command text.
:rtype: list
"""

styled_command = []
argument_begins = False

for index, arg in enumerate(raw_command.split()):
spaced_arg = ' {}'.format(arg) if index > 0 else arg
style = Style.PRIMARY

if arg.startswith('-') and '=' not in arg:
style = Style.ACTION
argument_begins = True
elif not argument_begins and '=' not in arg:
style = Style.ACTION
Comment on lines +191 to +195
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add comment about the rule used?

Copy link
Member

@jiasli jiasli Dec 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some comments at #16257 (comment)

The logic of = handling is for commands with positional argument. However, it works for az config set a=b, but it doesn't work for az config unset output. It is the nature of positional argument that we can NOT decide whether an arg (like output) is

  • part of a command, or
  • a positional argument

until semantic analysis is involved.

Let's keep it simple and treat it as part of the command by now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possible solution is to utilize linter_exclusions.yml which contains the full list of commands with positional arguments:

config set:
parameters:
key_value:
rule_exclusions:
- no_positional_parameters

Copy link
Contributor Author

@houk-ms houk-ms Dec 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good proposal, but also would make the function much more complex and harder to maintain.

I prefer we keep it simple for now, until we really need to consider the case.


styled_command.append((style, spaced_arg))

return styled_command


def _is_modern_terminal():
# Windows Terminal: https://github.com/microsoft/terminal/issues/1040
if 'WT_SESSION' in os.environ:
Expand Down
36 changes: 36 additions & 0 deletions src/azure-cli-core/azure/cli/core/tests/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,42 @@ def test_format_styled_text_on_error(self):
with self.assertRaisesRegex(CLIInternalError, "Invalid styled text."):
format_styled_text(["dummy text"])

def test_highlight_command(self):
from azure.cli.core.style import Style, highlight_command, format_styled_text

raw_commands = [
'az cmd',
'az cmd sub-cmd',
'az cmd --arg val',
'az cmd --arg=val',
'az cmd --arg "content with space"',
'az cmd --arg val1 val2',
'az cmd sub-cmd --arg1 val1 --arg2=" " --arg3 "val2 val3" --arg4 val4'
]

expected = [
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd')],
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd'), (Style.ACTION, ' sub-cmd')],
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd'), (Style.ACTION, ' --arg'), (Style.PRIMARY, ' val')],
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd'), (Style.PRIMARY, ' --arg=val')],
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd'), (Style.ACTION, ' --arg'), (Style.PRIMARY, ' "content'),
(Style.PRIMARY, ' with'), (Style.PRIMARY, ' space"')],
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd'), (Style.ACTION, ' --arg'),
(Style.PRIMARY, ' val1'), (Style.PRIMARY, ' val2')],
[(Style.ACTION, 'az'), (Style.ACTION, ' cmd'), (Style.ACTION, ' sub-cmd'), (Style.ACTION, ' --arg1'),
(Style.PRIMARY, ' val1'), (Style.PRIMARY, ' --arg2="'), (Style.PRIMARY, ' "'), (Style.ACTION, ' --arg3'),
(Style.PRIMARY, ' "val2'), (Style.PRIMARY, ' val3"'), (Style.ACTION, ' --arg4'), (Style.PRIMARY, ' val4')]
]

for cmd_index, command in enumerate(raw_commands):
styled_command = highlight_command(command)
expected_style = expected[cmd_index]

for tpl_index, style_tuple in enumerate(styled_command):
expected_tuple = expected_style[tpl_index]
self.assertEqual(style_tuple[0], expected_tuple[0])
self.assertEqual(style_tuple[1], expected_tuple[1])

def test_format_styled_text_theme(self):
from azure.cli.core.style import Style, format_styled_text
styled_text = [
Expand Down