示例#1
0
def cli(context, host_pattern: str, inventory: str, quiet: bool,
        verbose: bool) -> None:
    log: Logger = Log("openbsd-run")

    context.ensure_object(dict)
    context.obj["host_pattern"] = host_pattern
    context.obj["inventory"] = inventory
    context.obj["quiet"] = quiet
    context.obj["verbose"] = verbose

    if "-h" in click.get_os_args() or "--help" in click.get_os_args():
        pass
    else:
        if not inventory:
            log.error("ansible inventory file not provided")
            exit(1)

        try:
            inventory_contents: dict = ReadConfig(inventory)
            context.obj["inventory_contents"] = inventory_contents
        except (FileNotFoundError, NotADirectoryError) as e:
            log.error("'{}'".format(e))
            exit(1)

        if not inventory_contents:
            log.error("inventory is invalid:", inventory_contents)
            exit(1)

    pass
示例#2
0
    def __init__(self, *args, **kwargs):
        self.playfile = kwargs.pop('playfile')
        self.use_template = kwargs.pop('use_template')
        # self.allow_interspersed_args = False
        # self.allow_extra_args = False
        kwargs.update(invoke_without_command=True)
        click.MultiCommand.__init__(self, *args, **kwargs)

        foo = click.get_os_args() or ['-f', self.playfile]
        ctx = self.make_context(__name__, foo, resilient_parsing=True)

        # for _on in self.get_help_option_names(ctx):
        #     if _on in click.get_os_args():
        #         ctx.exit(click.echo('{}\n'.format(ctx.get_help())))

        playfile = ctx.params.get('playfile', False)
        use_template = ctx.params.get('use_template', False) or PLAYTEMPLATE
        if playfile:
            self.playfile = playfile
        if use_template:
            if not use_template.startswith('playfile-'):
                use_template = 'playfile-{}'.format(use_template)
            self.playfile = os.path.join(
                VALID_TEMPLATES_DICT.get(use_template), playfile)

        try:
            ns = {}
            fn = os.path.join(self.playfile)
            # import sys
            # sys.argv.remove('-f')
            # sys.argv.remove('zplayfile.py')
            # click.echo(click.get_os_args())
            ctx = self.make_context(__name__, click.get_os_args())
            with open(fn) as f:
                code = compile(f.read(), fn, 'exec')
                eval(code, ns, ns)

            self.cli = ns['cli']
            self.commands = self.cli.list_commands(self.cli)
        except KeyError:
            abort('Not a valid playfile: %s' % self.playfile)
        except (NoSuchOption, BadOptionUsage, UsageError):
            click.echo('{}\n'.format(ctx.get_help()))
        except IOError:
            if ctx.params.get('init', None):
                return
            if ctx.params.get('list_templates', False):
                return
            if os.environ.get('PLAYFILE'):
                warn('env PLAYFILE: {}'.format(os.environ.get('PLAYFILE')))
            if os.environ.get('PLAYTEMPLATE'):
                warn('env PLAYTEMPLATE: {}'.format(
                    os.environ.get('PLAYTEMPLATE')))
            ctx.exit(
                "\nFatal error: Couldn't find any playfiles!\n\n" +
                "Remember that -f can be used to specify playfile path, and use --help for help"
            )
示例#3
0
def main(ctx: click.Context, interface: str, protocol: str, log_level: str, timing: float,
         serial_no: str, ip_addr: str, reset: bool) -> int:
    """NXP Debug Mailbox Tool."""
    logging.basicConfig(level=log_level.upper())
    logger.setLevel(level=log_level.upper())

    # Get the Debug probe object
    try:
        #TODO solve following parameters:
        # ip_addr
        # tool
        debug_probes = DebugProbeUtils.get_connected_probes(interface=interface, hardware_id=serial_no)
        selected_probe = debug_probes.select_probe()
        debug_probe = DebugProbeUtils.get_probe(interface=selected_probe.interface,
                                                hardware_id=selected_probe.hardware_id)
        debug_probe.open()

        ctx.obj = {
            'protocol': protocol,
            'debug_mailbox':
                DebugMailbox(
                    debug_probe=debug_probe, reset=reset, moredelay=timing
                ) if '--help' not in click.get_os_args() else None,
        }

    except DebugProbeError as exc:
        logger.error(str(exc))

    return 0
示例#4
0
文件: cli.py 项目: vmario89/vpype
def cli(ctx, verbose, include, history, seed, config):
    """Execute the vector processing pipeline passed as argument."""

    logging.basicConfig()
    if verbose == 0:
        logging.getLogger().setLevel(logging.WARNING)
    elif verbose == 1:
        logging.getLogger().setLevel(logging.INFO)
    elif verbose > 1:
        logging.getLogger().setLevel(logging.DEBUG)

    # We use the command string as context object, mainly for the purpose of the `write`
    # command. This is a bit of a hack, and will need to be updated if we ever need more state
    # to be passed around (probably VpypeState should go in there!)
    cmd_string = "vpype " + " ".join(
        shlex.quote(arg) for arg in get_os_args()) + "\n"
    ctx.obj = cmd_string

    if history:
        with open("vpype_history.txt", "a") as fp:
            fp.write(cmd_string)

    if seed is None:
        seed = np.random.randint(2**31)
        logging.info(f"vpype: no seed provided, using {seed}")
    np.random.seed(seed)
    random.seed(seed)

    if config is not None:
        vp.config_manager.load_config_file(config)
示例#5
0
def set_settings(ctx, instance=None):
    """Pick correct settings instance and set it to a global variable."""

    global settings

    settings = None

    if instance is not None:
        sys.path.insert(0, ".")
        settings = import_settings(instance)
    elif "FLASK_APP" in os.environ:  # pragma: no cover
        with suppress(ImportError, click.UsageError):
            from flask.cli import ScriptInfo

            flask_app = ScriptInfo().load_app()
            settings = flask_app.config
            click.echo(
                click.style(
                    "Flask app detected", fg="white", bg="bright_black"
                )
            )
    elif "DJANGO_SETTINGS_MODULE" in os.environ:  # pragma: no cover
        sys.path.insert(0, os.path.abspath(os.getcwd()))
        try:
            # Django extension v2
            from django.conf import settings

            settings.DYNACONF.configure()
        except (ImportError, AttributeError):
            # Backwards compatible with old django extension (pre 2.0.0)
            import dynaconf.contrib.django_dynaconf  # noqa
            from django.conf import settings as django_settings

            django_settings.configure()
            settings = django_settings

        if settings is not None:
            click.echo(
                click.style(
                    "Django app detected", fg="white", bg="bright_black"
                )
            )

    if settings is None:

        if instance is None and "--help" not in click.get_os_args():
            if ctx.invoked_subcommand and ctx.invoked_subcommand not in [
                "init",
            ]:
                warnings.warn(
                    "Starting on 3.x the param --instance/-i is now required. "
                    "try passing it `dynaconf -i path.to.settings <cmd>` "
                    "Example `dynaconf -i config.settings list` "
                )
                settings = legacy_settings
            else:
                settings = LazySettings(create_new_settings=True)
        else:
            settings = LazySettings()
示例#6
0
def not_in_project_warning():
    if '--help' in click.get_os_args():
        pass
    else:
        path, management, file = find_project_files(os.getcwd())
        if not management:
            log_error(PROJECT_DIRECTORY_NOT_FOUND_ERROR)
            raise click.Abort
示例#7
0
def extract(
    ctx: click.Context,
    state: State,
    image_id: Optional[int],
    movie_id: Optional[int],
    path: Optional[Path],
    with_drawing: bool,
    **kwargs,
) -> None:
    """Extract keypoints from image or movie.

    Either '--image-id' or '--movie-id' or '--path' is required.

    When using the '--with-drawing' option, you can use drawing options such as
    '--rule', '--bg-rule', and '--rule-file', and '--download / --no-download'.
    In addition, when downloading the drawn file, you can use download options
    such as '-o, --out', '--force' and '--open / --no-open'.
    """
    required_options = [image_id, movie_id, path]
    if required_options.count(None) != len(required_options) - 1:
        raise click.UsageError(
            "Either '--image-id' or '--movie-id' or '--path' is required"
        )
    if not with_drawing:
        args = click.get_os_args()
        options = check_draw_options(args) + check_download_options(args)
        echo_invalid_option_warning("using '--with-drawing'", options)

    client = get_client(state)

    result = None
    if path is not None:
        result = ctx.invoke(upload, path=path)._asdict()
        echo()
    data = result or {"image_id": image_id, "movie_id": movie_id}

    try:
        keypoint_id = client.extract_keypoint(data=data)
        echo(f"Keypoint extraction started. (keypoint id: {color_id(keypoint_id)})")

        if state.use_spinner:
            with yaspin(text="Processing..."):
                response = client.wait_for_extraction(keypoint_id)
        else:
            response = client.wait_for_extraction(keypoint_id)
    except RequestsError as e:
        raise ClickException(str(e))

    if response.status == "SUCCESS":
        echo_success("Keypoint extraction is complete.")
    elif response.status == "TIMEOUT":
        raise ClickException("Keypoint extraction is timed out.")
    else:
        raise ClickException(f"Keypoint extraction failed.\n{response.failure_detail}")

    if with_drawing:
        echo()
        ctx.invoke(draw, keypoint_id=keypoint_id, **kwargs)
示例#8
0
文件: cli.py 项目: gbiggs31/auto_quiz
def _get_command_line_as_string() -> Optional[str]:
    import subprocess

    parent = click.get_current_context().parent
    if parent is None:
        return None
    cmd_line_as_list = [parent.command_path]
    cmd_line_as_list.extend(click.get_os_args())
    return subprocess.list2cmdline(cmd_line_as_list)
示例#9
0
def __py2_win_argv():
    # `click` includes a fancy argv parser to handle unicode arguments on windows when running python2 (since python2
    # does not default to unicode). However, this argv gets the arguments directly from the windows commandline instead
    # of via python, so it includes the PEX binary path when taking the PEX based approach. We handle this here by
    # detecting if this happened, and chopping it off the args.
    unicode_argv = click.get_os_args()
    if len(unicode_argv) > 0 and os.path.basename(
            unicode_argv[0]) == os.path.basename(__file__):
        unicode_argv = unicode_argv[1:]
    return unicode_argv
示例#10
0
def es_group(ctx: click.Context, **kwargs):
    """Commands for integrating with Elasticsearch."""
    ctx.ensure_object(dict)

    # only initialize an es client if the subcommand is invoked without help (hacky)
    if click.get_os_args()[-1] in ctx.help_option_names:
        click.echo('Elasticsearch client:')
        click.echo(format_command_options(ctx))

    else:
        ctx.obj['es'] = get_elasticsearch_client(ctx=ctx, **kwargs)
示例#11
0
def kibana_group(ctx: click.Context, **kibana_kwargs):
    """Commands for integrating with Kibana."""
    ctx.ensure_object(dict)

    # only initialize an kibana client if the subcommand is invoked without help (hacky)
    if click.get_os_args()[-1] in ctx.help_option_names:
        click.echo('Kibana client:')
        click.echo(format_command_options(ctx))

    else:
        ctx.obj['kibana'] = get_kibana_client(**kibana_kwargs)
示例#12
0
def main(ctx: click.Context, port: str, usb: str, use_json: bool,
         log_level: int, timeout: int) -> int:
    """Utility for communication with bootloader on target."""
    logging.basicConfig(level=log_level or logging.WARNING)
    # if --help is provided anywhere on commandline, skip interface lookup and display help message
    if not '--help' in click.get_os_args():
        ctx.obj = {
            'interface':
            get_interface(module='mboot', port=port, usb=usb, timeout=timeout),
            'use_json':
            use_json
        }
    return 0
示例#13
0
def main(ctx: click.Context, port: str, usb: str, use_json: bool, log_level: int, timeout: int) -> int:
    """Utility for communication with bootloader on target."""
    logging.basicConfig(level=log_level or logging.WARNING)

    # print help for get-property if property tag is 0 or 'list-properties'
    if ctx.invoked_subcommand == 'get-property':
        args = click.get_os_args()
        # running this via pytest changes the args to a single arg, in that case skip
        if len(args) > 1 and 'get-property' in args:
            tag_str = args[args.index('get-property') + 1]
            if parse_property_tag(tag_str) == 0:
                click.echo(ctx.command.commands['get-property'].help)   # type: ignore
                ctx.exit(0)

    # if --help is provided anywhere on commandline, skip interface lookup and display help message
    if not '--help' in click.get_os_args():
        ctx.obj = {
            'interface': get_interface(
                module='mboot', port=port, usb=usb, timeout=timeout
            ),
            'use_json': use_json
        }
    return 0
示例#14
0
def main(email, inbox, search, folder, file_ext, mime_type):
    logging.basicConfig(level=logging.INFO, format="%(message)s")

    logger.info(
        Template("Script initialized with the arguments: $arguments").
        safe_substitute(arguments=click.get_os_args()))

    service_name = "gmail_attachment_downloader"
    passwd = keyring.get_password(service_name, email)
    if passwd:
        logger.info(
            Template("Retrieved password from keyring for email $address").
            safe_substitute(address=email))
    else:
        logger.info("Password not stored, asking user")
        passwd = getpass.getpass()
        keyring.set_password(service_name, email, passwd)

    with IMAPClient(host="imap.gmail.com", ssl=993) as imap_client:
        logger.info("Connected to IMAP server, attempting login")
        imap_client.login(email, passwd)
        logger.info(
            Template('Selecting inbox folder "$inbox"').safe_substitute(
                inbox=inbox))
        imap_client.select_folder(inbox, readonly=True)
        search_terms = Template("has:attachment ${search}").safe_substitute(
            search=search)
        logger.info(
            Template('Applying gmail search with the terms: "$terms"').
            safe_substitute(terms=search_terms))

        if not mime_type:
            made_up_fname = Template("${name}.${file_ext}").safe_substitute(
                name="name", file_ext=file_ext)
            mime_type, _ = mimetypes.guess_type(made_up_fname)
            logger.info(
                Template('Guessed mimetype from "${ext}" as "${mime}"').
                safe_substitute(ext=file_ext, mime=mime_type))

        for filename, attachment in fetch_attachments(imap_client, mime_type,
                                                      search_terms):
            filepath = pathlib.Path(folder) / pathlib.Path(
                find_unused_filename(filename, file_ext, folder))
            with open(filepath, "wb") as file:
                file.write(attachment)

            logger.info(
                Template('Saved file "${filename}" at "${filepath}"').
                safe_substitute(filename=filename, filepath=filepath))
    def main(self, *args, **kwargs):
        """Main function called by setuptools.

        :param list args: Passed to super().
        :param dict kwargs: Passed to super().

        :return: super() return value.
        """
        argv = kwargs.pop('args', click.get_os_args())
        if '--' in argv:
            pos = argv.index('--')
            argv, self.overflow = argv[:pos], tuple(argv[pos + 1:])
        else:
            argv, self.overflow = argv, tuple()
        return super(ClickGroup, self).main(args=argv, *args, **kwargs)
示例#16
0
    def main(self, *args, **kwargs):
        """Main function called by setuptools.

        :param list args: Passed to super().
        :param dict kwargs: Passed to super().

        :return: super() return value.
        """
        argv = kwargs.pop('args', click.get_os_args())
        if '--' in argv:
            pos = argv.index('--')
            argv, self.overflow = argv[:pos], tuple(argv[pos + 1:])
        else:
            argv, self.overflow = argv, tuple()
        return super(ClickGroup, self).main(args=argv, *args, **kwargs)
示例#17
0
def _get_command_line_as_string() -> Optional[str]:
    import subprocess

    parent = click.get_current_context().parent
    if parent is None:
        return None

    if "streamlit.cli" in parent.command_path:
        raise RuntimeError(
            "Running streamlit via `python -m streamlit.cli <command>` is"
            " unsupported. Please use `python -m streamlit <command>` instead."
        )

    cmd_line_as_list = [parent.command_path]
    cmd_line_as_list.extend(click.get_os_args())
    return subprocess.list2cmdline(cmd_line_as_list)
示例#18
0
def compare(
    ctx: click.Context,
    state: State,
    source_id: int,
    target_id: int,
    with_drawing: bool,
    **kwargs,
) -> None:
    """Compare the two extracted keypoint data.

    SOURCE_ID and TARGET_ID are extracted keypoint ids.

    When using the '--with-drawing' option, you can use drawing options such as
    '--rule', '--bg-rule', and '--rule-file', and '--download / --no-download'.
    In addition, when downloading the drawn file, you can use download options
    such as '-o, --out', '--force' and '--open / --no-open'.
    """
    if not with_drawing:
        args = click.get_os_args()
        options = check_draw_options(args) + check_download_options(args)
        echo_invalid_option_warning("using '--with-drawing'", options)

    client = get_client(state)

    try:
        comparison_id = client.compare_keypoint(source_id, target_id)
        echo(f"Comparison started. (comparison id: {color_id(comparison_id)})")

        if state.use_spinner:
            with yaspin(text="Processing..."):
                response = client.wait_for_comparison(comparison_id)
        else:
            response = client.wait_for_comparison(comparison_id)
    except RequestsError as e:
        raise ClickException(str(e))

    if response.status == "SUCCESS":
        echo_success("Comparison is complete.")
    elif response.status == "TIMEOUT":
        raise ClickException("Comparison is timed out.")
    else:
        raise ClickException(f"Comparison failed.\n{response.failure_detail}")

    if with_drawing:
        echo()
        ctx.invoke(draw, comparison_id=comparison_id, **kwargs)
示例#19
0
def get_install_options():
    """combine general and provider specific options

    add provider in front of the provider specific options to prevent overlap"""
    install_options = general_install_options

    # extend install options with provider specific options
    if "install" in click.get_os_args():
        for provider in genomepy.ProviderBase.list_providers():
            p_dict = eval("genomepy.provider." + provider.capitalize() +
                          "Provider.provider_specific_install_options")
            for option in p_dict.keys():
                p_dict[option][
                    "long"] = provider + "-" + p_dict[option]["long"]
            install_options.update(p_dict)

    return install_options
示例#20
0
def main(ctx: click.Context, interface: str, protocol: str, log_level: str,
         timing: float, serial_no: str, debug_probe_option: List[str],
         reset: bool) -> int:
    """NXP Debug Mailbox Tool."""
    logging.basicConfig(level=log_level.upper())
    logger.setLevel(level=log_level.upper())

    if '--help' not in click.get_os_args():
        # Get the Debug probe object
        try:
            probe_user_params = {}
            for par in debug_probe_option:
                if par.count("=") != 1:
                    logger.warning(f"Invalid -o parameter {par}!")
                else:
                    par_splitted = par.split("=")
                    probe_user_params[par_splitted[0]] = par_splitted[1]

            debug_probes = DebugProbeUtils.get_connected_probes(
                interface=interface,
                hardware_id=serial_no,
                user_params=probe_user_params)
            selected_probe = debug_probes.select_probe()
            debug_probe = DebugProbeUtils.get_probe(
                interface=selected_probe.interface,
                hardware_id=selected_probe.hardware_id,
                user_params=probe_user_params)
            debug_probe.open()

            ctx.obj = {
                'protocol':
                protocol,
                'debug_mailbox':
                DebugMailbox(debug_probe=debug_probe,
                             reset=reset,
                             moredelay=timing),
            }

        except DebugProbeError as exc:
            logger.error(str(exc))

    return 0
示例#21
0
def main(ctx: click.Context, interface: str, protocol: str, log_level: str,
         timing: float, serial_no: int, ip_addr: str, hardware_id: str,
         reset: bool) -> int:
    """NXP Debug Mailbox Tool."""
    logging.basicConfig(level=log_level.upper())
    logger.setLevel(level=log_level.upper())

    ctx.obj = {
        'protocol':
        protocol,
        'debug_mailbox':
        DebugMailbox(debug_interface=interface,
                     reset=reset,
                     moredelay=timing,
                     serial_no=serial_no,
                     ip_addr=ip_addr,
                     tool=None,
                     hardware_id=hardware_id)
        if '--help' not in click.get_os_args() else None,
    }
    return 0
示例#22
0
def ssh(instance, role_dir):
    role_name = Path(role_dir).name
    inventory = _read_molecule_inventory(role_name)

    if len(inventory) == 1:
        instance = list(inventory.keys())[0]

    if not instance:
        try:
            instance = FzfPrompt().prompt(inventory.keys())[0]
        # Handle Ctrl+C
        except plumbum.commands.processes.ProcessExecutionError:
            return

    entry = inventory[instance]
    subprocess.Popen(_ssh_cmd(entry)).communicate()

    history_cmd = " ".join(
        ["dht"] + click.get_os_args() + ["--instance", _entry_repr(entry)]
    )
    _write_zsh_history(history_cmd)
示例#23
0
def load_config():
    """Load the config and instantiate an API client"""
    if trello.initialized or (
            # TODO: Find a  better way to determine the CliRunner invoked args
            # This is caused the help tests to fail when no config file or api
            # vars were present because get_os_args is diff from the invoked args
            set(["-h", "--help"]) & set(click.get_os_args())):
        return
    try:
        data = Config.load()
        if not data:
            raise Exception(
                "No api key or token found. \n"
                f"  Run: [ {PurePath(argv[0]).name} config --help ]"
                f" for more information.")
        trello.auth(**data)
    except TypeError as e:
        raise click.ClickException("Unable to load config."
                                   f"\n  Check {Config.config_file}:\n  {e}")
    except Exception as e:
        raise click.ClickException(
            f"Unable to load config information:\n  {e}")
示例#24
0
def not_an_app_directory_warning():
    if '--help' in click.get_os_args():
        pass
    elif not ('apps.py' in os.listdir('.')):
        log_error("Not inside an app directory")
        raise click.Abort
示例#25
0
    encoder = embiggen.text_encoder.TextEncoder(local_file)
    data, count, dictionary, reverse_dictionary = encoder.build_dataset()
    #print("Extracted a dataset with %d words" % len(data))
    if algorithm == 'cbow':
        logging.warning('Using cbow')
        model = embiggen.word2vec.ContinuousBagOfWordsWord2Vec(
            data,
            worddictionary=dictionary,
            reverse_worddictionary=reverse_dictionary,
            num_epochs=num_epochs)
    else:
        logging.warning('Using skipgram')
        model = SkipGramWord2Vec(data,
                                 worddictionary=dictionary,
                                 reverse_worddictionary=reverse_dictionary,
                                 num_epochs=num_epochs)
    model.add_display_words(count)
    model.train()
    write_embeddings(embed_text, model.embedding, reverse_dictionary)


if __name__ == "__main__":
    try:
        cli()
    except SystemExit as e:
        args = get_os_args()
        commands = list(cli.commands.keys())
        if args and args[0] not in commands:
            sys.exit("\nFirst argument should be a command: " + str(commands))
示例#26
0
def start_celery_worker():
    app.worker_main(argv=click.get_os_args())
    app.worker_main.loglevel = logging.DEBUG
示例#27
0
文件: cli.py 项目: Angel-RC/PuLink
def _get_command_line_as_string():
    import subprocess

    cmd_line_as_list = [click.get_current_context().parent.command_path]
    cmd_line_as_list.extend(click.get_os_args())
    return subprocess.list2cmdline(cmd_line_as_list)
示例#28
0
    Will connect to the board and perform a soft reset.  No arguments are
    necessary:

      ampy --port /board/serial/port reset
    """
    # Enter then exit the raw REPL, in the process the board will be soft reset
    # (part of enter raw REPL).
    _board.enter_raw_repl()
    _board.exit_raw_repl()


if __name__ == '__main__':
    try:
        cli()
    except BaseException as e:
        ctx = cli.make_context(__file__, click.get_os_args())
        port = ctx.params["port"]
        baud = ctx.params["baud"]
        text = "{}: {} (Port:{}\tBaud:{})".format(
            type(e).__name__, e, port, baud)
        termcolor.cprint(text,
                         color="red",
                         attrs=['bold', 'underline', 'dark', 'concealed'])
    finally:
        # Try to ensure the board serial connection is always gracefully closed.
        if _board is not None:
            try:
                _board.close()
            except:
                # Swallow errors when attempting to close as it's just a best effort
                # and shouldn't cause a new error or problem if the connection can't
示例#29
0
文件: cli.py 项目: vmario89/vpype
 def main(self, args=None, **extra):
     """Let's get a chance to pre-process the argument list for include options."""
     if args is None:
         args = get_os_args()
     return super().main(args=preprocess_argument_list(args), **extra)
示例#30
0
def draw(
    ctx: click.Context,
    state: State,
    keypoint_id: Optional[int],
    comparison_id: Optional[int],
    rule_str: Optional[str],
    rule_file: Optional[io.TextIOWrapper],
    bg_rule_str: Optional[str],
    is_download: Optional[bool],
    **kwargs,
) -> None:
    """Draw points and/or lines based on the extracted keypoints or comparison results.

    Either "--keypoint-id" or "--comparison-id" is required.

    See below for the format of the rule that can be specified with
    "--rule", "--bg-rule", and "--rule-file":
    https://docs.anymotion.jp/drawing.html
    """
    required_options = [keypoint_id, comparison_id]
    if required_options.count(None) != len(required_options) - 1:
        raise click.UsageError(
            "Either '--keypoint-id' or '--comparison-id' is required"
        )

    rule, background_rule = _parse_rule_and_bg_rule(rule_str, bg_rule_str, rule_file)
    client = get_client(state)

    if is_download is None:
        is_download = state.is_download
    if is_download is None:
        is_download = click.confirm("Download the drawn file?")
    if not is_download:
        args = click.get_os_args()
        options = check_download_options(args)
        echo_invalid_option_warning("downloading the file", options)

    try:
        drawing_id = client.draw_keypoint(
            keypoint_id=keypoint_id,
            comparison_id=comparison_id,
            rule=rule,
            background_rule=background_rule,
        )
        echo(f"Drawing started. (drawing id: {color_id(drawing_id)})")

        if state.use_spinner:
            with yaspin(text="Processing..."):
                response = client.wait_for_drawing(drawing_id)
        else:
            response = client.wait_for_drawing(drawing_id)
    except RequestsError as e:
        raise ClickException(str(e))

    if response.status == "SUCCESS":
        echo_success("Drawing is complete.")
    elif response.status == "TIMEOUT":
        raise ClickException("Drawing is timed out.")
    else:
        raise ClickException("Drawing failed.")

    echo()
    if is_download:
        ctx.invoke(download, drawing_id=drawing_id, **kwargs)
    else:
        message = dedent(
            f"""\
            Skip download. To download it, run the following command.

            "{state.cli_name} download {drawing_id}"
            """
        )
        echo(message)