1- from __future__ import annotations
2-
31import json
42import logging
53import sys
6- from argparse import ArgumentParser
74from pathlib import Path
8- from typing import Protocol , cast
5+ from typing import Annotated , Union
6+
7+ import typer
98
109from streamdeck .manager import PluginManager
1110from streamdeck .models .configs import PyProjectConfigs
1615
1716
1817
19- class DirectoryNotFoundError (FileNotFoundError ):
20- """Custom exception to indicate that a specified directory was not found."""
21- def __init__ (self , * args : object , directory : Path ):
22- super ().__init__ (* args )
23- self .directory = directory
24-
25-
26- class CliArgsNamespace (Protocol ):
27- """Represents the command-line arguments namespace."""
28- plugin_dir : Path | None
29- action_scripts : list [str ] | None
30-
31- # Args always passed in by StreamDeck software
32- port : int
33- pluginUUID : str # noqa: N815
34- registerEvent : str # noqa: N815
35- info : str # Actually a string representation of json object
18+ plugin = typer .Typer ()
3619
3720
38- def setup_cli () -> ArgumentParser :
39- """Set up the command-line interface for the script.
21+ @plugin .command ()
22+ def main (
23+ port : Annotated [int , typer .Option ("-p" , "-port" )],
24+ plugin_registration_uuid : Annotated [str , typer .Option ("-pluginUUID" )],
25+ register_event : Annotated [str , typer .Option ("-registerEvent" )],
26+ info : Annotated [str , typer .Option ("-info" )],
27+ plugin_dir : Annotated [Path , typer .Option (file_okay = False , exists = True , readable = True )] = Path .cwd (), # noqa: B008
28+ action_scripts : Union [list [str ], None ] = None , # noqa: UP007
29+ ) -> None :
30+ """Start the Stream Deck plugin with the given configuration.
4031
41- Returns:
42- argparse.ArgumentParser: The argument parser for the CLI .
32+ NOTE: Single flag long-name options are extected & passed in by the Stream Deck software.
33+ Double flag long-name options are used during development and testing .
4334 """
44- parser = ArgumentParser (description = "CLI to load Actions from action scripts." )
45- group = parser .add_mutually_exclusive_group (required = False )
46- group .add_argument (
47- "plugin_dir" ,
48- type = Path ,
49- nargs = "?" ,
50- help = "The directory containing plugin files to load Actions from." ,
51- )
52- group .add_argument (
53- "--action-scripts" ,
54- type = str ,
55- nargs = "+" ,
56- help = "A list of action script file paths to load Actions from or a single value to be processed." ,
57- )
58-
59- # Options that will always be passed in by the StreamDeck software when running this plugin.
60- parser .add_argument ("-port" , dest = "port" , type = int , help = "Port" , required = True )
61- parser .add_argument (
62- "-pluginUUID" , dest = "pluginUUID" , type = str , help = "pluginUUID" , required = True
63- )
64- parser .add_argument (
65- "-registerEvent" , dest = "registerEvent" , type = str , help = "registerEvent" , required = True
66- )
67- parser .add_argument ("-info" , dest = "info" , type = str , help = "info" , required = True )
68-
69- return parser
70-
71-
72- def main () -> None :
73- """Main function to parse arguments, load actions, and execute them."""
74- parser = setup_cli ()
75- args = cast (CliArgsNamespace , parser .parse_args ())
76-
77- # If `plugin_dir` was not passed in as a cli option, then fall back to using the CWD.
78- if args .plugin_dir is None :
79- plugin_dir = Path .cwd ()
80- # Also validate the plugin_dir argument.
81- elif not args .plugin_dir .is_dir ():
82- msg = f"The provided plugin directory '{ args .plugin_dir } ' is not a directory."
83- raise NotADirectoryError (msg )
84- elif not args .plugin_dir .exists ():
85- msg = f"The provided plugin directory '{ args .plugin_dir } ' does not exist."
86- raise DirectoryNotFoundError (msg , directory = args .plugin_dir )
87- else :
88- plugin_dir = args .plugin_dir
89-
9035 # Ensure plugin_dir is in `sys.path`, so that import statements in the plugin module will work as expected.
9136 if str (plugin_dir ) not in sys .path :
9237 sys .path .insert (0 , str (plugin_dir ))
9338
94- info = json .loads (args . info )
95- plugin_uuid = info ["plugin" ]["uuid" ]
39+ info_data = json .loads (info )
40+ plugin_uuid = info_data ["plugin" ]["uuid" ]
9641
9742 # After configuring once here, we can grab the logger in any other module with `logging.getLogger("streamdeck")`, or
9843 # a child logger with `logging.getLogger("streamdeck.mycomponent")`, all with the same handler/formatter configuration.
9944 configure_streamdeck_logger (name = "streamdeck" , plugin_uuid = plugin_uuid )
10045
101- pyproject = PyProjectConfigs .validate_from_toml_file (plugin_dir / "pyproject.toml" )
102- actions = list ( pyproject .streamdeck_plugin_actions )
46+ pyproject = PyProjectConfigs .validate_from_toml_file (plugin_dir / "pyproject.toml" , action_scripts = action_scripts )
47+ actions = pyproject .streamdeck_plugin_actions
10348
10449 manager = PluginManager (
105- port = args . port ,
50+ port = port ,
10651 plugin_uuid = plugin_uuid ,
10752 # NOT the configured plugin UUID in the manifest.json,
10853 # which can be pulled out of `info["plugin"]["uuid"]`
109- plugin_registration_uuid = args . pluginUUID ,
110- register_event = args . registerEvent ,
111- info = info ,
54+ plugin_registration_uuid = plugin_registration_uuid ,
55+ register_event = register_event ,
56+ info = info_data ,
11257 )
11358
11459 for action in actions :
@@ -117,5 +62,9 @@ def main() -> None:
11762 manager .run ()
11863
11964
120- if __name__ == "__main__" :
121- main ()
65+ # Also run the plugin if this script is ran as a console script.
66+ if __name__ in ("__main__" , "streamdeck.__main__" ):
67+ plugin ()
68+
69+
70+
0 commit comments