diff --git a/tabcmd/__main__.py b/tabcmd/__main__.py index 29886963..0c800dea 100644 --- a/tabcmd/__main__.py +++ b/tabcmd/__main__.py @@ -3,7 +3,8 @@ try: from tabcmd.tabcmd import main except ImportError: - print("Tabcmd needs to be run as a module, it cannot be run as a script") + print("Error importing dependencies.") + print("Possible cause: Tabcmd needs to be run as a module, it cannot be run as a script") print("Try running python -m tabcmd") sys.exit(1) diff --git a/tabcmd/commands/datasources_and_workbooks/delete_command.py b/tabcmd/commands/datasources_and_workbooks/delete_command.py index 8e68cb41..3815b86b 100644 --- a/tabcmd/commands/datasources_and_workbooks/delete_command.py +++ b/tabcmd/commands/datasources_and_workbooks/delete_command.py @@ -21,10 +21,11 @@ class DeleteCommand(DatasourcesAndWorkbooks): @staticmethod def define_args(delete_parser): - delete_parser.add_argument("name", help=_("content_type.workbook") + "/" + _("content_type.datasource")) - set_ds_xor_wb_options(delete_parser) - set_project_r_arg(delete_parser) - set_parent_project_arg(delete_parser) + group = delete_parser.add_argument_group(title=DeleteCommand.name) + group.add_argument("name", help=_("content_type.workbook") + "/" + _("content_type.datasource")) + set_ds_xor_wb_options(group) + set_project_r_arg(group) + set_parent_project_arg(group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/datasources_and_workbooks/export_command.py b/tabcmd/commands/datasources_and_workbooks/export_command.py index 27d5d619..6775181a 100644 --- a/tabcmd/commands/datasources_and_workbooks/export_command.py +++ b/tabcmd/commands/datasources_and_workbooks/export_command.py @@ -16,20 +16,22 @@ class ExportCommand(DatasourcesAndWorkbooks): @staticmethod def define_args(export_parser): - export_parser.add_argument("url", help="url of the workbook or view to export") - export_parser_group = export_parser.add_mutually_exclusive_group(required=True) + group = export_parser.add_argument_group(title=ExportCommand.name) + group.add_argument("url", help="url of the workbook or view to export") + export_parser_group = group.add_mutually_exclusive_group(required=True) export_parser_group.add_argument("--pdf", action="store_true", help=_("export.options.pdf")) export_parser_group.add_argument("--fullpdf", action="store_true", help=_("export.options.fullpdf")) export_parser_group.add_argument("--png", action="store_true", help=_("export.options.png")) export_parser_group.add_argument("--csv", action="store_true", help=_("export.options.csv")) - export_parser.add_argument( + group.add_argument( "--pagelayout", choices=["landscape", "portrait"], + type=str.lower, default=None, help="page orientation (landscape or portrait) of the exported PDF", ) - export_parser.add_argument( + group.add_argument( "--pagesize", choices=[ pagesize.A3, @@ -47,16 +49,15 @@ def define_args(export_parser): pagesize.Tabloid, pagesize.Unspecified, ], + type=str.lower, default="letter", help="Set the page size of the exported PDF", ) - export_parser.add_argument( - "--width", default=800, help="Set the width of the image in pixels. Default is 800 px" - ) - export_parser.add_argument("--filename", "-f", help="filename to store the exported data") - export_parser.add_argument("--height", default=600, help=_("export.options.height")) - export_parser.add_argument( + group.add_argument("--width", default=800, help="Set the width of the image in pixels. Default is 800 px") + group.add_argument("--filename", "-f", help="filename to store the exported data") + group.add_argument("--height", default=600, help=_("export.options.height")) + group.add_argument( "--filter", metavar="COLUMN:VALUE", help="View filter to apply to the view", diff --git a/tabcmd/commands/datasources_and_workbooks/get_url_command.py b/tabcmd/commands/datasources_and_workbooks/get_url_command.py index 688426d9..c4ec13ec 100644 --- a/tabcmd/commands/datasources_and_workbooks/get_url_command.py +++ b/tabcmd/commands/datasources_and_workbooks/get_url_command.py @@ -19,8 +19,9 @@ class GetUrl(DatasourcesAndWorkbooks): @staticmethod def define_args(get_url_parser): - get_url_parser.add_argument("url", help=_("refreshextracts.options.url")) - set_filename_arg(get_url_parser) + group = get_url_parser.add_argument_group(title=GetUrl.name) + group.add_argument("url", help=_("refreshextracts.options.url")) + set_filename_arg(group) # these don't need arguments, although that would be a good future addition # tabcmd get "/views/Finance/InvestmentGrowth.png?:size=640,480" -f growth.png # tabcmd get "/views/Finance/InvestmentGrowth.png?:refresh=yes" -f growth.png diff --git a/tabcmd/commands/datasources_and_workbooks/publish_command.py b/tabcmd/commands/datasources_and_workbooks/publish_command.py index 59432ee1..c529604b 100644 --- a/tabcmd/commands/datasources_and_workbooks/publish_command.py +++ b/tabcmd/commands/datasources_and_workbooks/publish_command.py @@ -20,14 +20,15 @@ class PublishCommand(DatasourcesAndWorkbooks): @staticmethod def define_args(publish_parser): - publish_parser.add_argument( + group = publish_parser.add_argument_group(title=PublishCommand.name) + group.add_argument( "filename", metavar="filename.twbx|tdsx|hyper", # this is not actually a File type because we just pass the path to tsc ) - set_publish_args(publish_parser) - set_project_r_arg(publish_parser) - set_parent_project_arg(publish_parser) + set_publish_args(group) + set_project_r_arg(group) + set_parent_project_arg(group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/datasources_and_workbooks/runschedule_command.py b/tabcmd/commands/datasources_and_workbooks/runschedule_command.py index f4eceb29..9661135b 100644 --- a/tabcmd/commands/datasources_and_workbooks/runschedule_command.py +++ b/tabcmd/commands/datasources_and_workbooks/runschedule_command.py @@ -15,7 +15,8 @@ class RunSchedule(DatasourcesAndWorkbooks): @staticmethod def define_args(runschedule_parser): - runschedule_parser.add_argument("schedule", help=_("tabcmd.run_schedule.options.schedule")) + group = runschedule_parser.add_argument_group(title=RunSchedule.name) + group.add_argument("schedule", help=_("tabcmd.run_schedule.options.schedule")) @staticmethod def run_command(args): diff --git a/tabcmd/commands/extracts/create_extracts_command.py b/tabcmd/commands/extracts/create_extracts_command.py index f7f93b72..8f09ba90 100644 --- a/tabcmd/commands/extracts/create_extracts_command.py +++ b/tabcmd/commands/extracts/create_extracts_command.py @@ -18,12 +18,13 @@ class CreateExtracts(Server): @staticmethod def define_args(create_extract_parser): - set_ds_xor_wb_args(create_extract_parser) - set_embedded_datasources_options(create_extract_parser) - set_encryption_option(create_extract_parser) - set_project_arg(create_extract_parser) - set_parent_project_arg(create_extract_parser) - set_site_url_arg(create_extract_parser) + group = create_extract_parser.add_argument_group(title=CreateExtracts.name) + set_ds_xor_wb_args(group) + set_embedded_datasources_options(group) + set_encryption_option(group) + set_project_arg(group) + set_parent_project_arg(group) + set_site_url_arg(group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/extracts/decrypt_extracts_command.py b/tabcmd/commands/extracts/decrypt_extracts_command.py index 6a983a06..0fd0b1e3 100644 --- a/tabcmd/commands/extracts/decrypt_extracts_command.py +++ b/tabcmd/commands/extracts/decrypt_extracts_command.py @@ -16,7 +16,8 @@ class DecryptExtracts(Server): @staticmethod def define_args(decrypt_extract_parser): - decrypt_extract_parser.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) + group = decrypt_extract_parser.add_argument_group(title=DecryptExtracts.name) + group.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) @staticmethod def run_command(args): diff --git a/tabcmd/commands/extracts/delete_extracts_command.py b/tabcmd/commands/extracts/delete_extracts_command.py index 400d7c7a..e4e3dcc6 100644 --- a/tabcmd/commands/extracts/delete_extracts_command.py +++ b/tabcmd/commands/extracts/delete_extracts_command.py @@ -18,12 +18,13 @@ class DeleteExtracts(Server): @staticmethod def define_args(delete_extract_parser): - set_ds_xor_wb_args(delete_extract_parser) - set_embedded_datasources_options(delete_extract_parser) - # set_encryption_option(delete_extract_parser) - set_project_arg(delete_extract_parser) - set_parent_project_arg(delete_extract_parser) - delete_extract_parser.add_argument("--url", help=_("createextracts.options.url")) + group = delete_extract_parser.add_argument_group(title=DeleteExtracts.name) + set_ds_xor_wb_args(group) + set_embedded_datasources_options(group) + # set_encryption_option(group) + set_project_arg(group) + set_parent_project_arg(group) + group.add_argument("--url", help=_("createextracts.options.url")) @staticmethod def run_command(args): diff --git a/tabcmd/commands/extracts/encrypt_extracts_command.py b/tabcmd/commands/extracts/encrypt_extracts_command.py index 2d3b7ae4..a3507624 100644 --- a/tabcmd/commands/extracts/encrypt_extracts_command.py +++ b/tabcmd/commands/extracts/encrypt_extracts_command.py @@ -18,7 +18,8 @@ class EncryptExtracts(Server): @staticmethod def define_args(encrypt_extract_parser): - encrypt_extract_parser.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) + group = encrypt_extract_parser.add_argument_group(title=EncryptExtracts.name) + group.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) @staticmethod def run_command(args): diff --git a/tabcmd/commands/extracts/reencrypt_extracts_command.py b/tabcmd/commands/extracts/reencrypt_extracts_command.py index 66785bfe..37c1c121 100644 --- a/tabcmd/commands/extracts/reencrypt_extracts_command.py +++ b/tabcmd/commands/extracts/reencrypt_extracts_command.py @@ -18,7 +18,8 @@ class ReencryptExtracts(Server): @staticmethod def define_args(reencrypt_extract_parser): - reencrypt_extract_parser.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) + group = reencrypt_extract_parser.add_argument_group(title=ReencryptExtracts.name) + group.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) @staticmethod def run_command(args): diff --git a/tabcmd/commands/extracts/refresh_extracts_command.py b/tabcmd/commands/extracts/refresh_extracts_command.py index 562554ba..290e6cf3 100644 --- a/tabcmd/commands/extracts/refresh_extracts_command.py +++ b/tabcmd/commands/extracts/refresh_extracts_command.py @@ -16,15 +16,17 @@ class RefreshExtracts(Server): @staticmethod def define_args(refresh_extract_parser): - possible_targets = set_ds_xor_wb_args(refresh_extract_parser) + group = refresh_extract_parser.add_argument_group(title=RefreshExtracts.name) + possible_targets = set_ds_xor_wb_args(group) + # hm, why did I do this instead of group.add_arg? possible_targets.add_argument( "--url", help=_("createextracts.options.url"), ) - set_incremental_options(refresh_extract_parser) - set_calculations_options(refresh_extract_parser) - set_project_arg(refresh_extract_parser) - set_parent_project_arg(refresh_extract_parser) + set_incremental_options(group) + set_calculations_options(group) + set_project_arg(group) + set_parent_project_arg(group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/group/create_group_command.py b/tabcmd/commands/group/create_group_command.py index 489bf4db..8eea4198 100644 --- a/tabcmd/commands/group/create_group_command.py +++ b/tabcmd/commands/group/create_group_command.py @@ -17,7 +17,8 @@ class CreateGroupCommand(Server): @staticmethod def define_args(create_group_parser): - create_group_parser.add_argument("name") + args_group = create_group_parser.add_argument_group(title=CreateGroupCommand.name) + args_group.add_argument("name") @staticmethod def run_command(args): diff --git a/tabcmd/commands/group/delete_group_command.py b/tabcmd/commands/group/delete_group_command.py index 6d2741f1..d2c8ccf0 100644 --- a/tabcmd/commands/group/delete_group_command.py +++ b/tabcmd/commands/group/delete_group_command.py @@ -17,7 +17,8 @@ class DeleteGroupCommand(Server): @staticmethod def define_args(delete_group_parser): - delete_group_parser.add_argument("name") + args_group = delete_group_parser.add_argument_group(title=DeleteGroupCommand.name) + args_group.add_argument("name") @staticmethod def run_command(args): diff --git a/tabcmd/commands/help/__init__.py b/tabcmd/commands/help/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tabcmd/commands/help/help_command.py b/tabcmd/commands/help/help_command.py deleted file mode 100644 index 6c5409d8..00000000 --- a/tabcmd/commands/help/help_command.py +++ /dev/null @@ -1,74 +0,0 @@ -import argparse -from typing import Any, List - -from tabcmd.execution.localize import _ -from tabcmd.execution.logger_config import log - - -class HelpCommand: - """ - Command to show user help options - """ - - name: str = "help" - description: str = "Show Help and exit" - - @staticmethod - def define_args(parser): - parser.add_argument("help_option", nargs="?") - - @staticmethod - def run_command(args: argparse.Namespace): - - # whaddya mean, '__class__' is not defined ??!?!!? - logger = log(__class__.__name__, args.logging_level) # type: ignore[name-defined] - logger.debug(_("tabcmd.launching")) - - # delayed import, TODO fix cyclic imports - from tabcmd.execution.map_of_commands import CommandsMap - - all_commands: List[Any] = CommandsMap.commands_hash_map - - description: str = ( - "tabcmd - Tableau Server Command Line Utility 2.0 \n \n" - "tabcmd help -- List all available commands and global options \n" - "tabcmd help -- Show Help for a specific command\n\n" - ) - - if args.help_option: - - if args.help_option in map(lambda command: command.name, all_commands): - command_objects = filter(lambda command: command.name == args.help_option, all_commands) - cli_cmd = list(command_objects)[0] - logger.info(cli_cmd.name.ljust(25) + cli_cmd.description + "\n") - command_parser = argparse.ArgumentParser(parents=[]) - cli_cmd.define_args(command_parser) - - positionals = [] - optionals = [] - for option in command_parser._actions: - if option.option_strings: - optionals.append(option) - else: - positionals.append(option) - - if positionals: - logger.info("Required arguments") - for option in positionals: - logger.info("{0} {1}{2}{3}".format(option.dest.ljust(25), "{", option.help, "}")) - if optionals: - logger.info("\nOptional arguments") - for option in optionals: - logger.info("{0} {1} ".format(option.option_strings, option.help)) - - logger.info("\nUsage") - usage = cli_cmd.name + " " - for opt in positionals: - usage = usage + opt.dest - if len(positionals) < len(command_parser._actions): - usage = usage + " [--optional arguments]" - logger.info(usage) - - else: - for cmd in all_commands: - logger.info(cmd.name + ": " + cmd.description) diff --git a/tabcmd/commands/project/create_project_command.py b/tabcmd/commands/project/create_project_command.py index b1838b48..be5d2507 100644 --- a/tabcmd/commands/project/create_project_command.py +++ b/tabcmd/commands/project/create_project_command.py @@ -18,7 +18,8 @@ class CreateProjectCommand(Server): @staticmethod def define_args(create_project_parser): - create_project_parser.add_argument( + args_group = create_project_parser.add_argument_group(title=CreateProjectCommand.name) + args_group.add_argument( "--name", "-n", dest="project_name", required=True, help=_("createproject.options.name") ) set_parent_project_arg(create_project_parser) diff --git a/tabcmd/commands/project/delete_project_command.py b/tabcmd/commands/project/delete_project_command.py index 9ea30d8d..4449677d 100644 --- a/tabcmd/commands/project/delete_project_command.py +++ b/tabcmd/commands/project/delete_project_command.py @@ -18,8 +18,9 @@ class DeleteProjectCommand(Server): @staticmethod def define_args(delete_project_parser): - delete_project_parser.add_argument("project_name", metavar="project-name", help=_("createproject.options.name")) - set_parent_project_arg(delete_project_parser) + args_group = delete_project_parser.add_argument_group(title=DeleteProjectCommand.name) + args_group.add_argument("project_name", metavar="project-name", help=_("createproject.options.name")) + set_parent_project_arg(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/project/publish_samples_command.py b/tabcmd/commands/project/publish_samples_command.py index 65f47358..6d8a54c4 100644 --- a/tabcmd/commands/project/publish_samples_command.py +++ b/tabcmd/commands/project/publish_samples_command.py @@ -17,13 +17,14 @@ class PublishSamplesCommand(Server): @staticmethod def define_args(publish_samples_parser): - publish_samples_parser.add_argument( + args_group = publish_samples_parser.add_argument_group(title=PublishSamplesCommand.name) + args_group.add_argument( "--name", "-n", dest="project_name", required=True, ) - set_parent_project_arg(publish_samples_parser) # args.parent_project_name + set_parent_project_arg(args_group) # args.parent_project_name @staticmethod def run_command(args): diff --git a/tabcmd/commands/site/create_site_command.py b/tabcmd/commands/site/create_site_command.py index e8ca8c03..b54d57d0 100644 --- a/tabcmd/commands/site/create_site_command.py +++ b/tabcmd/commands/site/create_site_command.py @@ -18,8 +18,9 @@ class CreateSiteCommand(Server): @staticmethod def define_args(create_site_parser): - create_site_parser.add_argument("new_site_name", metavar="site-name", help=_("editsite.options.site-name")) - set_common_site_args(create_site_parser) + args_group = create_site_parser.add_argument_group(title=CreateSiteCommand.name) + args_group.add_argument("new_site_name", metavar="site-name", help=_("editsite.options.site-name")) + set_common_site_args(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/site/delete_site_command.py b/tabcmd/commands/site/delete_site_command.py index babc5f41..2d7599da 100644 --- a/tabcmd/commands/site/delete_site_command.py +++ b/tabcmd/commands/site/delete_site_command.py @@ -17,7 +17,8 @@ class DeleteSiteCommand(Server): @staticmethod def define_args(delete_site_parser): - delete_site_parser.add_argument("site_name_to_delete", metavar="site-name", help=strings[2]) + args_group = delete_site_parser.add_argument_group(title=DeleteSiteCommand.name) + args_group.add_argument("site_name_to_delete", metavar="site-name", help=strings[2]) @staticmethod def run_command(args): diff --git a/tabcmd/commands/site/edit_site_command.py b/tabcmd/commands/site/edit_site_command.py index cbd58f31..e47f246f 100644 --- a/tabcmd/commands/site/edit_site_command.py +++ b/tabcmd/commands/site/edit_site_command.py @@ -19,13 +19,11 @@ class EditSiteCommand(Server): @staticmethod def define_args(edit_site_parser): - edit_site_parser.add_argument("site_name", metavar="site-name", help="editsite.options.site-name") - edit_site_parser.add_argument( - "--site-name", default=None, dest="new_site_name", help=_("editsite.options.site-name") - ) - - set_common_site_args(edit_site_parser) - set_site_status_arg(edit_site_parser) + args_group = edit_site_parser.add_argument_group(title=EditSiteCommand.name) + args_group.add_argument("site_name", metavar="site-name", help="editsite.options.site-name") + args_group.add_argument("--site-name", default=None, dest="new_site_name", help=_("editsite.options.site-name")) + set_common_site_args(args_group) + set_site_status_arg(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/site/list_command.py b/tabcmd/commands/site/list_command.py index 2eff8ec5..f2af2a72 100644 --- a/tabcmd/commands/site/list_command.py +++ b/tabcmd/commands/site/list_command.py @@ -18,7 +18,8 @@ class ListCommand(Server): @staticmethod def define_args(list_parser): - list_parser.add_argument("content", choices=["projects", "workbooks", "datasources"], help="View content") + args_group = list_parser.add_argument_group(title=ListCommand.name) + args_group.add_argument("content", choices=["projects", "workbooks", "datasources"], help="View content") @staticmethod def run_command(args): diff --git a/tabcmd/commands/site/list_sites_command.py b/tabcmd/commands/site/list_sites_command.py index 55e69f85..be532584 100644 --- a/tabcmd/commands/site/list_sites_command.py +++ b/tabcmd/commands/site/list_sites_command.py @@ -18,7 +18,8 @@ class ListSiteCommand(Server): @staticmethod def define_args(list_site_parser): - set_site_detail_option(list_site_parser) + group = list_site_parser.add_argument_group(title=ListSiteCommand.name) + set_site_detail_option(group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/user/add_users_command.py b/tabcmd/commands/user/add_users_command.py index 2d580ac3..801f0253 100644 --- a/tabcmd/commands/user/add_users_command.py +++ b/tabcmd/commands/user/add_users_command.py @@ -15,9 +15,10 @@ class AddUserCommand(UserCommand): @staticmethod def define_args(add_user_parser): - add_user_parser.add_argument("name", help="name of group to add users to") - set_users_file_arg(add_user_parser) - set_completeness_options(add_user_parser) + args_group = add_user_parser.add_argument_group(title=AddUserCommand.name) + args_group.add_argument("name", help="name of group to add users to") + set_users_file_arg(args_group) + set_completeness_options(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/user/create_site_users.py b/tabcmd/commands/user/create_site_users.py index 37b7eaaa..2e470544 100644 --- a/tabcmd/commands/user/create_site_users.py +++ b/tabcmd/commands/user/create_site_users.py @@ -19,9 +19,10 @@ class CreateSiteUsersCommand(UserCommand): @staticmethod def define_args(create_site_users_parser): - set_role_arg(create_site_users_parser) - set_users_file_positional(create_site_users_parser) - set_completeness_options(create_site_users_parser) + args_group = create_site_users_parser.add_argument_group(title=CreateSiteUsersCommand.name) + set_role_arg(args_group) + set_users_file_positional(args_group) + set_completeness_options(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/user/create_users_command.py b/tabcmd/commands/user/create_users_command.py index 2a44854f..66414427 100644 --- a/tabcmd/commands/user/create_users_command.py +++ b/tabcmd/commands/user/create_users_command.py @@ -20,9 +20,10 @@ class CreateUsersCommand(UserCommand): @staticmethod def define_args(create_users_parser): - set_role_arg(create_users_parser) - set_users_file_positional(create_users_parser) - set_completeness_options(create_users_parser) + args_group = create_users_parser.add_argument_group(title=CreateUsersCommand.name) + set_role_arg(args_group) + set_users_file_positional(args_group) + set_completeness_options(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/user/delete_site_users_command.py b/tabcmd/commands/user/delete_site_users_command.py index aa749a5a..42f088fd 100644 --- a/tabcmd/commands/user/delete_site_users_command.py +++ b/tabcmd/commands/user/delete_site_users_command.py @@ -19,8 +19,9 @@ class DeleteSiteUsersCommand(Server): @staticmethod def define_args(delete_site_users_parser): - set_users_file_positional(delete_site_users_parser) - set_completeness_options(delete_site_users_parser) + args_group = delete_site_users_parser.add_argument_group(title=DeleteSiteUsersCommand.name) + set_users_file_positional(args_group) + set_completeness_options(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/commands/user/remove_users_command.py b/tabcmd/commands/user/remove_users_command.py index bedbd394..722ce3d5 100644 --- a/tabcmd/commands/user/remove_users_command.py +++ b/tabcmd/commands/user/remove_users_command.py @@ -15,9 +15,10 @@ class RemoveUserCommand(UserCommand): @staticmethod def define_args(remove_users_parser): - remove_users_parser.add_argument("name", help="The group to remove users from.") - set_users_file_arg(remove_users_parser) - set_completeness_options(remove_users_parser) + args_group = remove_users_parser.add_argument_group(title=RemoveUserCommand.name) + args_group.add_argument("name", help="The group to remove users from.") + set_users_file_arg(args_group) + set_completeness_options(args_group) @staticmethod def run_command(args): diff --git a/tabcmd/execution/global_options.py b/tabcmd/execution/global_options.py index 61467455..0e66cd3c 100644 --- a/tabcmd/execution/global_options.py +++ b/tabcmd/execution/global_options.py @@ -65,25 +65,29 @@ def set_no_wait_option(parser): return parser -# TODO make this lower case? +site_roles = [ + "ServerAdministrator", + "SiteAdministratorCreator", + "SiteAdministratorExplorer", + "SiteAdministrator", + "Creator", + "ExplorerCanPublish", + "Publisher", + "Explorer", + "Interactor", + "Viewer", + "Unlicensed", +] + + def set_role_arg(parser): parser.add_argument( "-r", "--role", - choices=[ - "ServerAdministrator", - "SiteAdministratorCreator", - "SiteAdministratorExplorer", - "SiteAdministrator", - "Creator", - "ExplorerCanPublish", - "Publisher", - "Explorer", - "Interactor", - "Viewer", - "Unlicensed", - ], - help="Specifies a site role for all users in the .csv file.", + choices=list(map(lambda x: x.lower(), site_roles)), + type=str.lower, + help="Specifies a site role for all users in the .csv file. Possible roles: " + ", ".join(site_roles), + metavar="SITE_ROLE", ) return parser @@ -202,6 +206,7 @@ def set_site_status_arg(parser): parser.add_argument( "--status", choices=["ACTIVE", "SUSPENDED"], + type=str.upper, help="Set to ACTIVE to activate a site, or to SUSPENDED to suspend a site.", ) return parser @@ -244,8 +249,9 @@ def set_common_site_args(parser): parser.add_argument( "--run-now-enabled", - help="Allow or deny users from running extract refreshes, flows, or schedules manually. \ - true to allow users to run tasks manually or false to prevent users from running tasks manually.", + choices=["true", "false"], + type=str.lower, + help="Allow or deny users from running extract refreshes, flows, or schedules manually.", ) return parser diff --git a/tabcmd/execution/logger_config.py b/tabcmd/execution/logger_config.py index 8a42e2cb..17fe6907 100644 --- a/tabcmd/execution/logger_config.py +++ b/tabcmd/execution/logger_config.py @@ -6,7 +6,7 @@ FORMATS = { logging.ERROR: "%(asctime)s %(levelname)-5s:(%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", logging.WARN: "%(asctime)s %(levelname)-5s: (%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", - logging.INFO: "%(filename)-10s: %(message)-30s", + logging.INFO: "%(message)-30s", logging.DEBUG: "%(asctime)s %(levelname)-5s: (%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", } @@ -45,6 +45,9 @@ def configure_log(name: str, logging_level_input: str): """function for logging statements to console and logfile""" logging_level = getattr(logging, logging_level_input.upper()) log_format = FORMATS[logging_level] + if logging_level is not logging.INFO: + FORMATS[logging.INFO] = "%(filename)-10s: %(message)-30s" + logging.basicConfig( level=logging_level, format=log_format, filename="tabcmd.log", filemode="a", datefmt="%Y-%m" "-%d " "%H:%M:%S" ) diff --git a/tabcmd/execution/map_of_commands.py b/tabcmd/execution/map_of_commands.py index c6f7a679..4b60e854 100644 --- a/tabcmd/execution/map_of_commands.py +++ b/tabcmd/execution/map_of_commands.py @@ -12,7 +12,6 @@ from tabcmd.commands.extracts.refresh_extracts_command import * from tabcmd.commands.group.create_group_command import * from tabcmd.commands.group.delete_group_command import * -from tabcmd.commands.help.help_command import * from tabcmd.commands.project.create_project_command import * from tabcmd.commands.project.delete_project_command import * from tabcmd.commands.project.publish_samples_command import * @@ -50,7 +49,6 @@ class CommandsMap: EncryptExtracts, ExportCommand, GetUrl, - HelpCommand, ListSiteCommand, ListCommand, LoginCommand, diff --git a/tabcmd/execution/parent_parser.py b/tabcmd/execution/parent_parser.py index 57b6d6b2..a2495792 100644 --- a/tabcmd/execution/parent_parser.py +++ b/tabcmd/execution/parent_parser.py @@ -1,5 +1,10 @@ import argparse +import logging + from .localize import _ +from .logger_config import log +from .map_of_commands import CommandsMap + # when we drop python 3.8, this could be replaced with this lighter weight option # from importlib.metadata import version, PackageNotFoundError @@ -12,143 +17,212 @@ pass +""" +Note: output order is influenced first by grouping, then by order they are added in here +Most of this function is about making the help output look nice. +Argparse uses argument groups to separate arguments in the help output - but that doesn't +work quite as documented when in nested parsers, like we have. +Everything that is just added directly to the parser will be in the default set of +'optional arguments' directly on the newly created parser, which is renamed 'behavior arguments' to +differentiate from the signin/connection options. +Everything we add to a mutually-exclusive-group in here will be in the default set of +'optional arguments' on the *parent* parser, which is displayed all together. To make this a nice set, +many arguments are added to a mutually-exclusive-group of one argument. They are named things like +'formatting_group1' to make it clear this is a formatting choice, not functional. +The arguments for each command must be added to a group for that command. +""" + + +def parent_parser_with_global_options(): + parser = argparse.ArgumentParser(usage=argparse.SUPPRESS, add_help=False) + parser._optionals.title = strings[0] + + formatting_group1 = parser.add_mutually_exclusive_group() + formatting_group1.add_argument( + "-s", + "--server", + default=None, # default is handled in Session class + metavar="", + help=_("session.options.server"), + ) + + formatting_group2 = parser.add_mutually_exclusive_group() + formatting_group2.add_argument( + "-t", "--site", default="", dest="site_name", metavar="SITEID", help=_("session.options.site") + ) + + auth_options = parser.add_mutually_exclusive_group() + auth_options.add_argument("--token-name", default=None, metavar="", help=strings[13]) + auth_options.add_argument("-u", "--username", default=None, metavar="", help=_("session.options.username")) + + secret_values = parser.add_mutually_exclusive_group() + secret_values.add_argument( + "--token-value", + default=None, + metavar="", + help=strings[12], + ) + secret_values.add_argument( + "-p", "--password", default=None, metavar="", help=_("session.options.password") + ) + secret_values.add_argument( + "--password-file", default=None, metavar="", help=_("session.options.password-file") + ) + secret_values.add_argument("--token-file", default=None, metavar="", help=strings[11]) + + formatting_group3 = parser.add_mutually_exclusive_group() + formatting_group3.add_argument("--no-prompt", action="store_true", help=_("session.options.no-prompt")) + + certificates = parser.add_mutually_exclusive_group() + certificates.add_argument( + "-c", + "--use-certificate", + dest="certificate", + default=None, + metavar="", + help=_("session.options.use-certificate"), + ) + certificates.add_argument( + "--no-certcheck", + action="store_true", + help=_("session.options.no-certcheck"), + ) + + formatting_group4 = parser.add_mutually_exclusive_group() + formatting_group4.add_argument("--no-cookie", action="store_true", help=_("session.options.no-cookie")) + + proxy_group = parser.add_mutually_exclusive_group() + proxy_group.add_argument( + "-x", "--proxy", dest="proxy", default=None, metavar="", help=_("session.options.proxy") + ) + proxy_group.add_argument( + "--no-proxy", + action="store_false", + help=_("session.options.no-proxy"), + ) + + formatting_group5 = parser.add_mutually_exclusive_group() + formatting_group5.add_argument( + "--timeout", + default=None, # default is handled in Session class + metavar="", # can't use -t, it's already used for --site + help=_("session.options.timeout"), + ) + + # general behavioral options + parser.add_argument( + "--continue-if-exists", + action="store_false", + help=strings[9], + ) + + parser.add_argument( + "--country", + choices=["de", "en", "es", "fr", "it", "ja", "ko", "pt", "sv", "zh"], + type=str.lower, # coerce input to lowercase to act case insensitive + help=_("export.options.country"), + ) + + parser.add_argument( + "--language", + choices=["de", "en", "es", "fr", "it", "ja", "ko", "pt", "sv", "zh"], + type=str.lower, # coerce input to lowercase to act case insensitive + help=strings[10], + ) + + parser.add_argument( + "-l", + "--logging-level", + choices=["TRACE", "DEBUG", "INFO", "ERROR"], + type=str.upper, # coerce input to uppercase to act case insensitive + default="info", + help=strings[8], + ) + + parser.add_argument( + "-v", + "--version", + action="version", + version=strings[6] + "v" + version + "\n \n", + help=strings[7], + ) + return parser + + class ParentParser: # Ref https://docs.python.org/3/library/argparse.html """Parser that will be inherited by all commands. Contains authentication and logging level setting""" def __init__(self): - self.global_options = self.parent_parser_with_global_options() - self.root = argparse.ArgumentParser(parents=[self.global_options]) + self.global_options = parent_parser_with_global_options() + self.root = argparse.ArgumentParser( + prog="tabcmd", description=strings[15], parents=[self.global_options], epilog=strings[2] + ) + self.root._optionals.title = strings[1] # https://stackoverflow.com/questions/7498595/python-argparse-add-argument-to-multiple-subparsers - self.subparsers = self.root.add_subparsers() + self.subparsers = self.root.add_subparsers( + title=strings[3], + description=strings[4], + metavar=strings[5], # instead of printing the list of choices + ) def get_root_parser(self): return self.root + def connect_commands(self): + commands = CommandsMap.commands_hash_map + for command in commands: + self.include(command) + return self.root + def include(self, command): additional_parser = self.subparsers.add_parser( command.name, help=command.description, parents=[self.global_options] ) + additional_parser._optionals.title = strings[1] # This line is where we actually set each parser to call the correct command additional_parser.set_defaults(func=command) command.define_args(additional_parser) return additional_parser - # ordered alphabetically by short option - this is reflected directly in help output - def parent_parser_with_global_options(self): - parser = argparse.ArgumentParser(usage=argparse.SUPPRESS, add_help=False) - - certificates = parser.add_mutually_exclusive_group() - certificates.add_argument( - "-c", - "--use-certificate", - dest="certificate", - default=None, - metavar="", - help=_("session.options.use-certificate"), - ) - certificates.add_argument( - "--no-certcheck", - action="store_true", - help=_("session.options.no-certcheck"), - ) - - parser.add_argument( - "--continue-if-exists", - action="store_true", - help="Treat resource conflicts as item creation success e.g project already exists", - ) - - parser.add_argument("--no-cookie", action="store_true", help=_("session.options.no-cookie")) - - parser.add_argument( - "-l", - "--logging-level", - choices=["TRACE", "DEBUG", "INFO", "ERROR"], - type=str.upper, # coerce input to uppercase to act case insensitive - default="info", - help="Use the specified logging level. The default level is INFO.", - ) - - parser.add_argument("--no-prompt", action="store_true", help=_("session.options.no-prompt")) - - auth_options = parser.add_mutually_exclusive_group() - auth_options.add_argument( - "--token-name", - default=None, - metavar="", - help="The name of the Tableau Server Personal Access Token. If using a token to sign in,\ - this is required at least once to begin session.", - ) - auth_options.add_argument( - "-u", "--username", default=None, metavar="", help=_("session.options.username") - ) - - secret_values = parser.add_mutually_exclusive_group() - secret_values.add_argument( - "--token-value", - default=None, - metavar="", - help="Use the specified Tableau Server Personal Access Token. Requires --token-name to be set.", - ) - secret_values.add_argument( - "-p", "--password", default=None, metavar="", help=_("session.options.password") - ) - secret_values.add_argument( - "--password-file", default=None, metavar="", help=_("session.options.password-file") - ) - - proxy_group = parser.add_mutually_exclusive_group() - proxy_group.add_argument( - "-x", "--proxy", dest="proxy", default=None, metavar="", help=_("session.options.proxy") - ) - proxy_group.add_argument( - "--no-proxy", - action="store_false", - help=_("session.options.no-proxy"), - ) - - parser.add_argument( - "-s", - "--server", - default=None, # default is handled in Session class - metavar="", - help=_("session.options.server"), - ) - parser.add_argument( - "-t", "--site", default="", dest="site_name", metavar="SITEID", help=_("session.options.site") - ) - - parser.add_argument( - "--timeout", - default=None, # default is handled in Session class - metavar="", # can't use -t, it's already used for --site - help=_("session.options.timeout"), - ) - - # TODO get the list of choices dynamically? - parser.add_argument( - "--language", - choices=["de", "en", "es", "fr", "it", "ja", "ko", "pt", "sv", "zh"], - help="Set the language to use. Exported data will be returned in this lang/locale." - "If not set, the client will use your computer locale, and the server will use your user account locale", - ) - - parser.add_argument( - "--country", - choices=["de", "en", "es", "fr", "it", "ja", "ko", "pt", "sv", "zh"], - help=_("export.options.country"), - ) - - # -h goes to argparse default help - - parser.add_argument( - "-v", - "--version", - action="version", - version="Tableau Server Command Line Utility v" + version + "\n \n", - help="Show version information and exit.", - ) - - return parser + def include_help(self): + additional_parser = self.subparsers.add_parser("help", help=strings[14], parents=[self.global_options]) + additional_parser._optionals.title = strings[1] + additional_parser.set_defaults(func=Help(self)) + + +class Help: + + parser = None + # This needs to have access to the parser when it gets called + def __init__(self, _parser: ParentParser): + self.parser = _parser + + def run_command(self, args): + logger = log(__name__, "info") + logger.info(strings[6] + " " + version + "\n") + logger.info(self.parser.root.format_help()) + + +strings = [ + "global behavioral arguments", # 0 - global_behavior_args + "global connection arguments", # 1 - global_conn_args + "For more help see https://tableau.github.io/tabcmd/", # 2 - for_more_help + "list of tabcmd commands", # 3 + "For help on a specific command use 'tabcmd -h'.", # 4 + "{ [command args]}", # 5 + "Tableau Server Command Line Utility", # 6 + "Show version information and exit.", # 7 + "Use the specified logging level. The default level is INFO.", # 8 + "Treat resource conflicts as item creation success e.g project already exists", # 9 + "Set the language to use. Exported data will be returned in this lang/locale.\n \ + If not set, the client will use your computer locale, and the server will use \ + your user account locale", # 10 + "Read the Personal Access Token from a file.", # 11 + "Use the specified Tableau Server Personal Access Token. Requires --token-name to be set.", # 12 + "The name of the Tableau Server Personal Access Token. If using a token to sign in,\ + this is required at least once to begin session.", # 13 + "Show message listing commands and global options, then exit", # 14 + "tabcmd -- Run a specific command", # 15 +] diff --git a/tabcmd/execution/tabcmd_controller.py b/tabcmd/execution/tabcmd_controller.py index 0dee0ad0..001b2b5b 100644 --- a/tabcmd/execution/tabcmd_controller.py +++ b/tabcmd/execution/tabcmd_controller.py @@ -2,7 +2,7 @@ import sys from .localize import set_client_locale -from .map_of_commands import * +from .logger_config import log from .parent_parser import ParentParser @@ -10,10 +10,8 @@ class TabcmdController: @staticmethod def initialize(): manager = ParentParser() - parent = manager.get_root_parser() - commands = CommandsMap.commands_hash_map - for command in commands: - manager.include(command) + parent = manager.connect_commands() + manager.include_help() return parent # during normal execution, leaving input as none will default to sys.argv @@ -29,7 +27,7 @@ def run(parser, user_input=None): print("logging:", namespace.logging_level) logger = log(__name__, namespace.logging_level or logging.INFO) - if namespace.password: + if namespace.password or namespace.token_value: logger.trace(namespace.func) else: logger.trace(namespace) diff --git a/tests/commands/test_run_commands.py b/tests/commands/test_run_commands.py index 22b9dda0..a7e03597 100644 --- a/tests/commands/test_run_commands.py +++ b/tests/commands/test_run_commands.py @@ -20,7 +20,6 @@ refresh_extracts_command, ) from tabcmd.commands.group import create_group_command, delete_group_command -from tabcmd.commands.help import help_command from tabcmd.commands.project import create_project_command, delete_project_command, publish_samples_command from tabcmd.commands.site import ( create_site_command, @@ -252,13 +251,6 @@ def test_delete_group(self, mock_session, mock_server): delete_group_command.DeleteGroupCommand.run_command(mock_args) mock_session.assert_called() - # help - def test_help(self, mock_session, mock_server): - RunCommandsTest._set_up_session(mock_session, mock_server) - mock_args.help_option = "boo" - help_command.HelpCommand.run_command(mock_args) - mock_session.assert_not_called() - # project def test_create_project(self, mock_session, mock_server): RunCommandsTest._set_up_session(mock_session, mock_server) diff --git a/tests/parsers/test_parser_create_user.py b/tests/parsers/test_parser_create_user.py index 440d430d..7b67f24f 100644 --- a/tests/parsers/test_parser_create_user.py +++ b/tests/parsers/test_parser_create_user.py @@ -27,4 +27,4 @@ def test_create_user_parser_role(self): with mock.patch("builtins.open", mock.mock_open(read_data="test")): mock_args = [commandname, "users.csv", "-r", "SiteAdministrator"] args = self.parser_under_test.parse_args(mock_args) - assert args.role == "SiteAdministrator", args + assert args.role.lower() == "SiteAdministrator".lower(), args