Skip to content

Commit

Permalink
Merge branch 'master' into custom-tag-msg
Browse files Browse the repository at this point in the history
  • Loading branch information
jiasli authored Dec 16, 2020
2 parents 5143a00 + e0fec24 commit 1a78232
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
arch:
- amd64
- ppc64le
sudo: false
language: python
python:
Expand Down
3 changes: 2 additions & 1 deletion knack/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .util import CLIError
from .config import CLIConfig
from .query import CLIQuery
from .events import EVENT_CLI_PRE_EXECUTE, EVENT_CLI_POST_EXECUTE
from .events import EVENT_CLI_PRE_EXECUTE, EVENT_CLI_SUCCESSFUL_EXECUTE, EVENT_CLI_POST_EXECUTE
from .parser import CLICommandParser
from .commands import CLICommandsLoader
from .help import CLIHelp
Expand Down Expand Up @@ -219,6 +219,7 @@ def invoke(self, args, initial_invocation_data=None, out_file=None):
if cmd_result and cmd_result.result is not None:
formatter = self.output.get_formatter(output_type)
self.output.out(cmd_result, formatter=formatter, out_file=out_file)
self.raise_event(EVENT_CLI_SUCCESSFUL_EXECUTE, result=cmd_result.result)
except KeyboardInterrupt as ex:
exit_code = 1
self.result = CommandResultItem(None, error=ex, exit_code=exit_code)
Expand Down
17 changes: 14 additions & 3 deletions knack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,20 @@ def sections(self):

def items(self, section):
import re
pattern = self.env_var_name(section, '.+')
candidates = [(k.split('_')[-1], os.environ[k], k) for k in os.environ if re.match(pattern, k)]
result = {c[0]: c for c in candidates}
# Only allow valid env vars, in all caps: CLI_SECTION_TEST_OPTION, CLI_SECTION__TEST_OPTION
pattern = self.env_var_name(section, '([0-9A-Z_]+)')
env_entries = []
for k in os.environ:
# Must be a full match, otherwise CLI_SECTION_T part in CLI_MYSECTION_Test_Option will match
matched = re.fullmatch(pattern, k)
if matched:
# (name, value, ENV_VAR_NAME)
item = (matched.group(1).lower(), os.environ[k], k)
env_entries.append(item)

# Prepare result with env entries first
result = {c[0]: c for c in env_entries}
# Add entries from config files if they do not exist yet
for config in self._config_file_chain if self.use_local_config else self._config_file_chain[-1:]:
try:
entries = config.items(section)
Expand Down
1 change: 1 addition & 0 deletions knack/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# --------------------------------------------------------------------------------------------

EVENT_CLI_PRE_EXECUTE = 'Cli.PreExecute'
EVENT_CLI_SUCCESSFUL_EXECUTE = 'Cli.SuccessfulExecute'
EVENT_CLI_POST_EXECUTE = 'Cli.PostExecute'

EVENT_INVOKER_PRE_CMD_TBL_CREATE = 'CommandInvoker.OnPreCommandTableCreate'
Expand Down
58 changes: 58 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,64 @@ def test_getboolean_error(self):
with self.assertRaises(ValueError):
self.cli_config.getboolean(section, option)

def test_items(self):
file_section = "MySection"
file_value = 'file_value'
env_value = 'env_value'

# Test file-only options are listed
file_only_option = 'file_only_option'
self.cli_config.set_value(file_section, file_only_option, file_value)
items_result = self.cli_config.items(file_section)
self.assertEqual(len(items_result), 1)
self.assertEqual(items_result[0]['name'], file_only_option)
self.assertEqual(items_result[0]['value'], file_value)
self.cli_config.remove_option(file_section, file_only_option)

# Test env-only options are listed
with mock.patch.dict('os.environ', {'CLI_MYSECTION_ENV_ONLY_OPTION': env_value}):
items_result = self.cli_config.items(file_section)
self.assertEqual(len(items_result), 1)
self.assertEqual(items_result[0]['name'], 'env_only_option')
self.assertEqual(items_result[0]['value'], env_value)

# Test file options are overridden by env options, for both single-word and multi-word options
test_options = [
# file_option, file_value, env_name, env_value
# Test single-word option
('optionsingle', 'file_value_single', 'CLI_MYSECTION_OPTIONSINGLE', 'env_value_single'),
# Test multi-word option
('option_multiple', 'file_value_multiple', 'CLI_MYSECTION_OPTION_MULTIPLE', 'env_value_multiple')
]
for file_option, file_value, env_name, env_value in test_options:
self.cli_config.set_value(file_section, file_option, file_value)
items_result = self.cli_config.items(file_section)
self.assertEqual(len(items_result), 1)
self.assertEqual(items_result[0]['value'], file_value)

with mock.patch.dict('os.environ', {env_name: env_value}):
items_result = self.cli_config.items(file_section)
self.assertEqual(len(items_result), 1)
self.assertEqual(items_result[0]['value'], env_value)

self.cli_config.remove_option(file_section, file_option)

# Test Invalid_Env_Var is not accepted on Linux
# Windows' env var is case-insensitive, so skip
import platform
if platform.system() != 'Windows':
# Not shown
with mock.patch.dict('os.environ', {'CLI_MYSECTION_Test_Option': env_value}):
items_result = self.cli_config.items(file_section)
self.assertEqual(len(items_result), 0)

# No overriding
self.cli_config.set_value(file_section, 'test_option', file_value)
with mock.patch.dict('os.environ', {'CLI_MYSECTION_Test_Option': env_value}):
items_result = self.cli_config.items(file_section)
self.assertEqual(len(items_result), 1)
self.assertEqual(items_result[0]['value'], file_value)

def test_set_config_value(self):
self.cli_config.set_value('test_section', 'test_option', 'a_value')
config = configparser.ConfigParser()
Expand Down

0 comments on commit 1a78232

Please sign in to comment.