mirror of
https://github.com/onyx-and-iris/vban-cli.git
synced 2026-04-16 02:23:30 +00:00
initial commit
CLI skeletal structure implemented --version flag implemented configuration via flag+env vars implemented strip command group implemented + some subcommands custom help formatter implemented.
This commit is contained in:
3
src/vmr_cli/__init__.py
Normal file
3
src/vmr_cli/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from importlib.metadata import version
|
||||
|
||||
__version__ = version('vmr-cli')
|
||||
52
src/vmr_cli/app.py
Normal file
52
src/vmr_cli/app.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
import vban_cmd
|
||||
from cyclopts import App, Parameter, config
|
||||
|
||||
from . import __version__ as version
|
||||
from . import console, strip
|
||||
from .context import Context
|
||||
|
||||
app = App(
|
||||
config=config.Env(
|
||||
'VMR_CLI_',
|
||||
), # Environment variable prefix for configuration parameters
|
||||
version=version,
|
||||
)
|
||||
app.command(strip.app.meta, name='strip')
|
||||
|
||||
|
||||
@Parameter(name='*')
|
||||
@dataclass
|
||||
class VBANConfig:
|
||||
kind: Annotated[str, Parameter(help='Kind of Voicemeeter')] = 'potato'
|
||||
host: Annotated[str, Parameter(help='VBAN host')] = 'localhost'
|
||||
port: Annotated[int, Parameter(help='VBAN port')] = 6980
|
||||
streamname: Annotated[str, Parameter(help='VBAN stream name')] = 'Command1'
|
||||
|
||||
|
||||
@app.meta.default
|
||||
def launcher(
|
||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||
vban_config: Annotated[VBANConfig, Parameter()] = VBANConfig(),
|
||||
):
|
||||
with vban_cmd.api(
|
||||
vban_config.kind,
|
||||
ip=vban_config.host,
|
||||
port=vban_config.port,
|
||||
streamname=vban_config.streamname,
|
||||
) as client:
|
||||
additional_kwargs = {}
|
||||
command, bound, _ = app.parse_args(tokens)
|
||||
additional_kwargs['ctx'] = Context(client=client)
|
||||
|
||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||
|
||||
|
||||
def run():
|
||||
try:
|
||||
app.meta()
|
||||
except Exception as e:
|
||||
console.err.print(f'Error: {e}')
|
||||
return e.code
|
||||
4
src/vmr_cli/console.py
Normal file
4
src/vmr_cli/console.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from rich.console import Console
|
||||
|
||||
out = Console()
|
||||
err = Console(stderr=True)
|
||||
8
src/vmr_cli/context.py
Normal file
8
src/vmr_cli/context.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from vban_cmd.vbancmd import VbanCmd
|
||||
|
||||
|
||||
@dataclass
|
||||
class Context:
|
||||
client: VbanCmd
|
||||
43
src/vmr_cli/help.py
Normal file
43
src/vmr_cli/help.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import re
|
||||
|
||||
from cyclopts.help import DefaultFormatter, HelpPanel
|
||||
from rich.console import Console, ConsoleOptions
|
||||
|
||||
|
||||
class CustomHelpFormatter(DefaultFormatter):
|
||||
"""Custom help formatter that injects an index argument into the usage line and filters it out from the parameters list.
|
||||
|
||||
This formatter modifies the usage line to include an <index> argument after the 'strip' command,
|
||||
and filters out any parameters related to 'index' from the Parameters section of the help output.
|
||||
"""
|
||||
|
||||
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
|
||||
"""Render the usage line with index argument injected."""
|
||||
if usage:
|
||||
modified_usage = re.sub(
|
||||
r'(\S+\s+strip)\s+(COMMAND)', r'\1 <index> \2', str(usage)
|
||||
)
|
||||
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||
|
||||
def __call__(
|
||||
self, console: Console, options: ConsoleOptions, panel: HelpPanel
|
||||
) -> None:
|
||||
"""Render a help panel, filtering out the index parameter from Parameters sections."""
|
||||
if panel.title == 'Parameters':
|
||||
filtered_entries = [
|
||||
entry
|
||||
for entry in panel.entries
|
||||
if not (
|
||||
entry.names and any('index' in name.lower() for name in entry.names)
|
||||
)
|
||||
]
|
||||
|
||||
filtered_panel = HelpPanel(
|
||||
title=panel.title,
|
||||
entries=filtered_entries,
|
||||
description=panel.description,
|
||||
format=panel.format,
|
||||
)
|
||||
super().__call__(console, options, filtered_panel)
|
||||
else:
|
||||
super().__call__(console, options, panel)
|
||||
266
src/vmr_cli/strip.py
Normal file
266
src/vmr_cli/strip.py
Normal file
@@ -0,0 +1,266 @@
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from cyclopts import App, Argument, Parameter
|
||||
|
||||
from . import console
|
||||
from .context import Context
|
||||
from .help import CustomHelpFormatter
|
||||
|
||||
app = App(name='strip', help_formatter=CustomHelpFormatter())
|
||||
|
||||
|
||||
@app.meta.default
|
||||
def launcher(
|
||||
index: Annotated[int, Argument()] = None,
|
||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Control the strip parameters."""
|
||||
additional_kwargs = {}
|
||||
command, bound, _ = app.parse_args(tokens)
|
||||
if index is not None:
|
||||
additional_kwargs['index'] = index
|
||||
if ctx is not None:
|
||||
additional_kwargs['ctx'] = ctx
|
||||
|
||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||
|
||||
|
||||
@app.command(name='mono')
|
||||
def mono(
|
||||
new_state: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the mono state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_state : bool, optional
|
||||
If provided, sets the mono state to this value. If not provided, the current mono state is printed.
|
||||
"""
|
||||
if new_state is None:
|
||||
console.out.print(ctx.client.strip[index].mono)
|
||||
return
|
||||
ctx.client.strip[index].mono = new_state
|
||||
|
||||
|
||||
@app.command(name='solo')
|
||||
def solo(
|
||||
new_state: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the solo state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_state : bool, optional
|
||||
If provided, sets the solo state to this value. If not provided, the current solo state is printed.
|
||||
"""
|
||||
if new_state is None:
|
||||
console.out.print(ctx.client.strip[index].solo)
|
||||
return
|
||||
ctx.client.strip[index].solo = new_state
|
||||
|
||||
|
||||
@app.command(name='mute')
|
||||
def mute(
|
||||
new_state: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the mute state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_state : bool, optional
|
||||
If provided, sets the mute state to this value. If not provided, the current mute state is printed.
|
||||
"""
|
||||
if new_state is None:
|
||||
console.out.print(ctx.client.strip[index].mute)
|
||||
return
|
||||
ctx.client.strip[index].mute = new_state
|
||||
|
||||
|
||||
@app.command(name='gain')
|
||||
def gain(
|
||||
new_value: Annotated[Optional[float], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the gain of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : float, optional
|
||||
If provided, sets the gain to this value. If not provided, the current gain is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].gain)
|
||||
return
|
||||
ctx.client.strip[index].gain = new_value
|
||||
|
||||
|
||||
@app.command(name='A1')
|
||||
def a1(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the A1 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the A1 state to this value. If not provided, the current A1 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].A1)
|
||||
return
|
||||
ctx.client.strip[index].A1 = new_value
|
||||
|
||||
|
||||
@app.command(name='A2')
|
||||
def a2(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the A2 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the A2 state to this value. If not provided, the current A2 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].A2)
|
||||
return
|
||||
ctx.client.strip[index].A2 = new_value
|
||||
|
||||
|
||||
@app.command(name='A3')
|
||||
def a3(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the A3 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the A3 state to this value. If not provided, the current A3 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].A3)
|
||||
return
|
||||
ctx.client.strip[index].A3 = new_value
|
||||
|
||||
|
||||
@app.command(name='A4')
|
||||
def a4(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the A4 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the A4 state to this value. If not provided, the current A4 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].A4)
|
||||
return
|
||||
ctx.client.strip[index].A4 = new_value
|
||||
|
||||
|
||||
@app.command(name='A5')
|
||||
def a5(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the A5 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the A5 state to this value. If not provided, the current A5 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].A5)
|
||||
return
|
||||
ctx.client.strip[index].A5 = new_value
|
||||
|
||||
|
||||
@app.command(name='B1')
|
||||
def b1(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the B1 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the B1 state to this value. If not provided, the current B1 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].B1)
|
||||
return
|
||||
ctx.client.strip[index].B1 = new_value
|
||||
|
||||
|
||||
@app.command(name='B2')
|
||||
def b2(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the B2 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the B2 state to this value. If not provided, the current B2 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].B2)
|
||||
return
|
||||
ctx.client.strip[index].B2 = new_value
|
||||
|
||||
|
||||
@app.command(name='B3')
|
||||
def b3(
|
||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||
*,
|
||||
index: Annotated[int, Parameter(show=False)] = None,
|
||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||
):
|
||||
"""Get or set the B3 state of the specified strip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_value : bool, optional
|
||||
If provided, sets the B3 state to this value. If not provided, the current B3 state is printed.
|
||||
"""
|
||||
if new_value is None:
|
||||
console.out.print(ctx.client.strip[index].B3)
|
||||
return
|
||||
ctx.client.strip[index].B3 = new_value
|
||||
Reference in New Issue
Block a user