Improve UX of selecting configuration file - additional table column with filename, echo message when defaulting to the lone configuration file.

pull/2617/head
derekpierre 2021-04-08 11:22:23 -04:00
parent 4b1a7fd07c
commit 0c660fc233
3 changed files with 36 additions and 28 deletions

View File

@ -14,14 +14,12 @@
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
import glob
import json import json
from json.decoder import JSONDecodeError
from typing import Optional, Type, List
import click import click
from json.decoder import JSONDecodeError
from nucypher.config.base import CharacterConfiguration
from typing import Optional, Type
from nucypher.characters.control.emitters import StdoutEmitter from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.characters.lawful import Ursula from nucypher.characters.lawful import Ursula
@ -38,7 +36,9 @@ from nucypher.cli.literature import (
CONFIRM_URSULA_IPV4_ADDRESS CONFIRM_URSULA_IPV4_ADDRESS
) )
from nucypher.cli.types import WORKER_IP from nucypher.cli.types import WORKER_IP
from nucypher.config.base import CharacterConfiguration
from nucypher.config.characters import StakeHolderConfiguration from nucypher.config.characters import StakeHolderConfiguration
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.utilities.networking import InvalidWorkerIP, validate_worker_ip from nucypher.utilities.networking import InvalidWorkerIP, validate_worker_ip
from nucypher.utilities.networking import determine_external_ip_address, UnknownIPAddress from nucypher.utilities.networking import determine_external_ip_address, UnknownIPAddress
@ -50,6 +50,23 @@ def forget(emitter: StdoutEmitter, configuration: CharacterConfiguration) -> Non
emitter.message(SUCCESSFUL_FORGET_NODES, color='red') emitter.message(SUCCESSFUL_FORGET_NODES, color='red')
def get_config_filepaths(config_class: Type[CharacterConfiguration], config_root: str = None) -> List:
#
# Scrape disk for configuration files
#
config_root = config_root or DEFAULT_CONFIG_ROOT
default_config_file = glob.glob(config_class.default_filepath(config_root=config_root))
# updated glob pattern for secondary configuration files accommodates for:
# 1. configuration files with "0x..." checksum address as suffix - including older ursula config files
# 2. newer (ursula) configuration files which use signing_pub_key[:8] as hex as the suffix
glob_pattern = f'{config_root}/{config_class.NAME}-[0-9a-fA-f]*.{config_class._CONFIG_FILE_EXTENSION}'
secondary_config_files = sorted(glob.glob(glob_pattern)) # sort list to make order deterministic
config_files = [*default_config_file, *secondary_config_files]
return config_files
def get_or_update_configuration(emitter: StdoutEmitter, def get_or_update_configuration(emitter: StdoutEmitter,
filepath: str, filepath: str,
config_class: Type[CharacterConfiguration], config_class: Type[CharacterConfiguration],

View File

@ -15,9 +15,8 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
import glob
import os import os
from pathlib import Path
from typing import Callable from typing import Callable
from typing import Optional, Tuple, Type from typing import Optional, Tuple, Type
@ -33,6 +32,7 @@ from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContr
from nucypher.blockchain.eth.signers.base import Signer from nucypher.blockchain.eth.signers.base import Signer
from nucypher.blockchain.eth.token import NU, Stake from nucypher.blockchain.eth.token import NU, Stake
from nucypher.characters.control.emitters import StdoutEmitter from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.cli.actions.configure import get_config_filepaths
from nucypher.cli.literature import ( from nucypher.cli.literature import (
GENERIC_SELECT_ACCOUNT, GENERIC_SELECT_ACCOUNT,
NO_CONFIGURATIONS_ON_DISK, NO_CONFIGURATIONS_ON_DISK,
@ -48,7 +48,7 @@ from nucypher.cli.literature import (
from nucypher.cli.painting.policies import paint_cards from nucypher.cli.painting.policies import paint_cards
from nucypher.cli.painting.staking import paint_stakes from nucypher.cli.painting.staking import paint_stakes
from nucypher.config.base import CharacterConfiguration from nucypher.config.base import CharacterConfiguration
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, NUCYPHER_ENVVAR_WORKER_ADDRESS from nucypher.config.constants import NUCYPHER_ENVVAR_WORKER_ADDRESS, DEFAULT_CONFIG_ROOT
from nucypher.policy.identity import Card from nucypher.policy.identity import Card
@ -228,20 +228,8 @@ def select_config_file(emitter: StdoutEmitter,
""" """
#
# Scrape Disk Configurations
#
config_root = config_root or DEFAULT_CONFIG_ROOT config_root = config_root or DEFAULT_CONFIG_ROOT
default_config_file = glob.glob(config_class.default_filepath(config_root=config_root)) config_files = get_config_filepaths(config_class=config_class, config_root=config_root)
# updated glob pattern for secondary configuration files accommodates for:
# 1. configuration files with "0x..." checksum address as suffix - including older ursula config files
# 2. newer (ursula) configuration files which use signing_pub_key[:8] as hex as the suffix
glob_pattern = f'{config_root}/{config_class.NAME}-[0-9a-fA-f]*.{config_class._CONFIG_FILE_EXTENSION}'
secondary_config_files = glob.glob(glob_pattern)
config_files = [*default_config_file, *secondary_config_files]
if not config_files: if not config_files:
emitter.message(NO_CONFIGURATIONS_ON_DISK.format(name=config_class.NAME.capitalize(), emitter.message(NO_CONFIGURATIONS_ON_DISK.format(name=config_class.NAME.capitalize(),
command=config_class.NAME), color='red') command=config_class.NAME), color='red')
@ -249,8 +237,8 @@ def select_config_file(emitter: StdoutEmitter,
checksum_address = checksum_address or os.environ.get(NUCYPHER_ENVVAR_WORKER_ADDRESS, None) # TODO: Deprecate worker_address in favor of checksum_address checksum_address = checksum_address or os.environ.get(NUCYPHER_ENVVAR_WORKER_ADDRESS, None) # TODO: Deprecate worker_address in favor of checksum_address
parsed_addresses = list()
parsed_config_files = list() parsed_config_files = list()
parsed_addresses_and_filenames = list()
# parse configuration files for checksum address values # parse configuration files for checksum address values
for fp in config_files: for fp in config_files:
try: try:
@ -259,8 +247,8 @@ def select_config_file(emitter: StdoutEmitter,
# matching configuration file found, no need to continue - return filepath # matching configuration file found, no need to continue - return filepath
return fp return fp
parsed_addresses.append([config_checksum_address])
parsed_config_files.append(fp) parsed_config_files.append(fp)
parsed_addresses_and_filenames.append([config_checksum_address, Path(fp).name]) # store checksum & filename
except config_class.OldVersion: except config_class.OldVersion:
# no use causing entire usage to crash if file can't be used anyway - inform the user; they can # no use causing entire usage to crash if file can't be used anyway - inform the user; they can
# decide for themself # decide for themself
@ -280,11 +268,13 @@ def select_config_file(emitter: StdoutEmitter,
# #
# Interactive # Interactive
# #
parsed_addresses = tuple(parsed_addresses) # must be tuple-of-iterables for tabulation emitter.echo(f"\nConfiguration Directory: {config_root}\n")
parsed_addresses_and_filenames = tuple(parsed_addresses_and_filenames) # must be tuple-of-iterables for tabulation
# Display account info # Display account info
headers = ['Account'] headers = ['Account', 'Configuration File']
emitter.echo(tabulate(parsed_addresses, headers=headers, showindex='always')) emitter.echo(tabulate(parsed_addresses_and_filenames, headers=headers, showindex='always'))
# Prompt the user for selection, and return # Prompt the user for selection, and return
prompt = f"Select {config_class.NAME} configuration" prompt = f"Select {config_class.NAME} configuration"
@ -295,6 +285,7 @@ def select_config_file(emitter: StdoutEmitter,
else: else:
# Default: Only one config file, use it. # Default: Only one config file, use it.
config_file = parsed_config_files[0] config_file = parsed_config_files[0]
emitter.echo(f"Defaulting to {config_class.NAME} configuration file: {config_file}")
return config_file return config_file

View File

@ -325,7 +325,7 @@ nucypher {init_command}
SELECT_NETWORK = "Select Network" SELECT_NETWORK = "Select Network"
NO_CONFIGURATIONS_ON_DISK = "No {name} configurations found. run 'nucypher {command} init' then try again." NO_CONFIGURATIONS_ON_DISK = "No {name} configurations found. Run 'nucypher {command} init' then try again."
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES = "Updated configuration values: {fields}" SUCCESSFUL_UPDATE_CONFIGURATION_VALUES = "Updated configuration values: {fields}"