# Plugin System NeXusCreator automatically discovers generator and parser plugins that live under `plugins/`. Each plugin can participate in generation (producing `.nxd`/YAML definitions) and/or parsing (building variable libraries from inputs). ## Built-in Plugins - **SPEC** (`plugins/spec_plugin.py`) — generator + parser for SPEC scans. - **DTA/DAT** (`plugins/dta_plugin.py`) — generator + parser for batteries workflows (RAW, non-RAW, temperature datasets). Use `-b batteries` to activate the folder generator. - **HDF5/NeXus** (`plugins/hdf5_plugin.py`) — generate definitions and variable libraries from existing NeXus/HDF5 files (`--hdf5-option links` or `extract`). - **TIFF** (`plugins/tiff_plugin.py`) — create detector/data placeholders and NXdata links from TIFF images. - **PEAXIS** (`plugins/peaxis_plugin.py`) — folder generator and parsers for SIF/XAS/DAT inputs. Use `-b peaxis` to activate. - **YAML** (`plugins/yaml_plugin.py`) — generator + parser for YAML-format data files (`.yaml`, `.yml`). Supports SchemaPlacer-guided placement when `--nxdl-root` is provided. - **MPES** (`plugins/mpes_plugin.py`) — generator + parser for MPES HDF5 inputs. Activated by `-b mpes`; uses `libraries/mpes_utils.py` to locate the HDF5 file. - **JSON-LD** (`plugins/jsonld_plugin.py`) — generator + parser for fixed-width/delimited text files described via a CDI/Schema.org JSON-LD document (`--jsonld-structure FILE`). Pairs with SchemaPlacer for NXDL-guided placement. - **Diamond B18 XAFS** (`plugins/diamond_ascii_plugin.py`) — detects either the ASCII exports under `data/cdi-ddi/.../ascii` or the matching NeXus files under `.../nexus`, parses both into a single library, and emits an `NXxas`-compliant definition referencing every parsed variable. ## Parser and Generator System In addition to the plugin system, NeXusCreator includes a parser and generator system that provides a consistent and extensible way to handle different input formats and generate NeXus definitions. ### Base Classes - **BaseParser**: Defines the interface for all parsers. Each parser must implement: - `can_parse(input_path: str) -> bool`: Checks if the parser can handle the given input path. - `parse(input_path: str) -> Dict[str, object]`: Parses the input file and returns a flat library. - **BaseGenerator**: Defines the interface for all generators. Each generator must implement: - `can_generate(input_path: str) -> bool`: Checks if the generator can handle the given input path. - `generate(input_path: str) -> dict`: Generates a NeXus-definition object from the input. ### Managers - **ParserManager**: Manages the discovery and registration of parsers. - Discovers all parsers in the `parsers` package. - Provides a method to get a parser for a specific file type. - **GeneratorManager**: Manages the discovery and registration of generators. - Discovers all generators in the `generators` package. - Provides a method to get a generator for a specific file type. ### Discovery and Registration The system automatically discovers and registers parsers and generators by: 1. Importing all modules in the `parsers` or `generators` package. 2. Finding all classes that inherit from `BaseParser` or `BaseGenerator`. 3. Instantiating these classes and sorting them by priority. ### Usage To use the parser and generator system: ```python from nexuscreator.parsers import get_parser_manager from nexuscreator.generators import get_generator_manager # Get a parser for a specific file type parser_manager = get_parser_manager() parser = parser_manager.get_parser("test.dta") if parser: library = parser.parse("test.dta") # Get a generator for a specific file type generator_manager = get_generator_manager() generator = generator_manager.get_generator("test.dta") if generator: nexus_object = generator.generate("test.dta") ``` ### Creating a New Parser or Generator To create a new parser or generator: 1. **Create a new parser**: ```python from nexuscreator.parsers.base import BaseParser class MyParser(BaseParser): id: str = 'my-parser' priority: int = 10 def can_parse(self, input_path: str) -> bool: return input_path.lower().endswith('.myformat') def parse(self, input_path: str) -> Dict[str, object]: # Parse the file and return a flat library return {"key": "value"} ``` 2. **Create a new generator**: ```python from nexuscreator.generators.base import BaseGenerator class MyGenerator(BaseGenerator): id: str = 'my-generator' priority: int = 10 def can_generate(self, input_path: str) -> bool: return input_path.lower().endswith('.myformat') def generate(self, input_path: str) -> dict: # Generate a NeXus-definition object from the input return {"entry": {"@NX_class": "NXentry"}} ``` ### Benefits - **Consistency**: All parsers and generators follow a consistent interface. - **Extensibility**: New parsers and generators can be easily added by inheriting from the base classes. - **Discoverability**: The system automatically discovers and registers parsers and generators. - **Testability**: The new system is well-tested, ensuring reliability. ## How Discovery Works - `DefinitionGeneratorPlugin` implements `can_generate()` and `generate_definition()`. - `DataParserPlugin` implements `can_parse()` and `parse_to_library()`. - Modules in `plugins/` are imported automatically; no explicit registration is required. - Plugins declare an `id` and optional `priority` to influence selection order. - Optional external plugins can be imported from filesystem paths via `NEXUSCREATOR_PLUGIN_PATHS` (path-separated list of `.py` files or directories). ## Create Your Own Plugin 1. Subclass `DefinitionGeneratorPlugin` and/or `DataParserPlugin`. 2. Implement `can_*` narrowly (extension, beamline, folder shape, etc.). 3. Return: - generator: a valid `nexus_object` dict - parser: a flat library dict compatible with placeholder injection 4. Set `priority` (lower value wins) when multiple plugins could match. ### Minimal parser + generator ```python from nexuscreator.plugins.base import DefinitionGeneratorPlugin, DataParserPlugin class MyGenerator(DefinitionGeneratorPlugin): id = "my-generator" priority = 40 def can_generate(self, input_path, beamline, flags): return input_path.lower().endswith(".abc") def generate_definition(self, input_path, beamline, flags): return { "entry": { "@NX_class": "NXentry", "data": { "@NX_class": "NXdata", "@signal": "counts", "@axes": "energy", "energy": {"@dtype": "NX_FLOAT64[]", "@value": "energy"}, "counts": {"@dtype": "NX_FLOAT64[]", "@value": "counts"}, }, } } class MyParser(DataParserPlugin): id = "my-parser" priority = 40 def can_parse(self, input_path, beamline, flags): return input_path.lower().endswith(".abc") def parse_to_library(self, input_path, beamline, flags): return {"energy": [1.0, 2.0, 3.0], "counts": [10.0, 20.0, 30.0]} ``` ### External plugin loading You can keep plugins outside this repository and point NeXusCreator to them: ```bash export NEXUSCREATOR_PLUGIN_PATHS="/path/to/my_plugins:/path/to/custom_plugin.py" ``` For plugin import diagnostics: ```bash export NEXUSCREATOR_PLUGIN_DEBUG=1 ``` ## Minimal Example ```python from nexuscreator.plugins.base import DefinitionGeneratorPlugin class MyGenerator(DefinitionGeneratorPlugin): id = "my-generator" def can_generate(self, input_path, beamline, flags): return input_path.endswith(".abc") def generate_definition(self, input_path, beamline, flags): # Return a NeXus definition object (dict-like) return {"entry": {"@NX_class": "NXentry"}} ``` Tip: combine your plugin with [SchemaPlacer](schema-placer.md) to map variables into canonical NXDL paths automatically.