# Copyright (c) 2024-2025 Karana Dynamics Pty Ltd. All rights reserved.
#
# NOTICE TO USER:
#
# This source code and/or documentation (the "Licensed Materials") is
# the confidential and proprietary information of Karana Dynamics Inc.
# Use of these Licensed Materials is governed by the terms and conditions
# of a separate software license agreement between Karana Dynamics and the
# Licensee ("License Agreement"). Unless expressly permitted under that
# agreement, any reproduction, modification, distribution, or disclosure
# of the Licensed Materials, in whole or in part, to any third party
# without the prior written consent of Karana Dynamics is strictly prohibited.
#
# THE LICENSED MATERIALS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# KARANA DYNAMICS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND
# FITNESS FOR A PARTICULAR PURPOSE.
#
# IN NO EVENT SHALL KARANA DYNAMICS BE LIABLE FOR ANY DAMAGES WHATSOEVER,
# INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, OR USE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, WHETHER IN CONTRACT, TORT,
# OR OTHERWISE ARISING OUT OF OR IN CONNECTION WITH THE LICENSED MATERIALS.
#
# U.S. Government End Users: The Licensed Materials are a "commercial item"
# as defined at 48 C.F.R. 2.101, and are provided to the U.S. Government
# only as a commercial end item under the terms of this license.
#
# Any use of the Licensed Materials in individual or commercial software must
# include, in the user documentation and internal source code comments,
# this Notice, Disclaimer, and U.S. Government Use Provision.
"""Classes and functions used to create a Kclick CLI application.
This module contains classes and functions used to create Kclick CLI
applications. Kclick is a thin wrapper around the third-party, open-source
click package, which is used to quickly generate high-quality CLI applications.
"""
import sys
from typing import Any, Optional
from copy import deepcopy
import click
kclick_options = {}
output_type = ""
cfg_file = ""
[docs]
def error(err: str):
"""Print out error text in red."""
click.secho(err, fg="red")
def _getDefault(cli) -> dict[Any, Any]:
"""Get default values from click."""
default_dict = {}
for cmd_name, cmd in cli.commands.items():
default_dict[cmd_name] = {}
for opt in cmd.params:
default_dict[cmd_name][opt.name] = opt.default
return default_dict
def _mergeDict(d1: dict[Any, Any], d2: dict[Any, Any], copy: bool = True) -> dict[Any, Any]:
"""Override the values in d1 with d2.
If copy=True, then we will modify a copy of d1 rather than
overwriting it.
"""
if copy:
d3 = deepcopy(d1)
else:
d3 = d1
for k, v in d2.items():
if k in d3:
if isinstance(v, dict):
d3[k] = _mergeDict(d3[k], d2[k], copy=False)
else:
d3[k] = v
else:
d3[k] = v
return d3
def _addKclickOptions(cmd, opts):
"""Add kclick_options to global kclick_options."""
kclick_options[cmd] = _mergeDict(kclick_options.get(cmd, {}), opts, copy=False)
[docs]
class KGroup(click.Group):
"""Thin wrapper around click.Group that simplifies command functions.
Command functions need only return the incoming `kwargs`. The logic used
here will automatically update the kclick_options with said kwargs.
"""
[docs]
def add_command(self, cmd: click.Command, name: Optional[str] = None):
"""Thin wrapper around click.Group.add_command that simplifies command functions.
Command functions need only return the incoming `kwargs`. The logic used
here will automatically update the kclick_options with said kwargs.
"""
super().add_command(cmd, name=name)
if name is None:
name = cmd.name
def _dec(f):
def _inner(*args, **kwargs):
ret = f(*args, **kwargs)
_addKclickOptions(name, ret)
return ret
return _inner
# Modify the command's function here so that we call _addKclickOptions for the **kwargs
cmd.callback = _dec(cmd.callback)
[docs]
@click.group(
name="cli",
cls=KGroup,
invoke_without_command=True,
chain=True,
context_settings={"help_option_names": ["-h", "--help"]},
)
@click.pass_context
def cli(ctx, **kwargs):
"""Major click group.
This is the main click group used for all Kclick CLI applications. Commands and
options can be added to this group, and they will show up in your Kclick CLI
application.
"""
return ctx
[docs]
@click.pass_context
@cli.result_callback()
def test(ctx, **kwargs):
"""Kclick cli group callback.
This callback merges all options from all commands.
"""
global kclick_options
default_opts = _getDefault(cli)
kclick_options = _mergeDict(default_opts, kclick_options)
return click.get_current_context(), kclick_options
[docs]
def runCli() -> tuple[click.core.Context, dict[str, Any]]:
"""Run the Kclick cli group and return the parsed results.
Run the Kclick cli group, which will parse the incoming CLI options.
This is then merged into a single dictionary with all the final option
values.
Returns
-------
tuple[click.core.Context, dict[str, Any]]
A tuple where the first element is the click context object and the second
is the CLI options.
"""
try:
run = cli(standalone_mode=False)
except click.exceptions.MissingParameter as e:
cmd = e.cmd.name
param = e.param.name
error(f"Missing parameter '{param}' on command '{cmd}'")
run = e.exit_code
if isinstance(run, int):
sys.exit(run)
else:
return run