publishmd 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- publishmd-0.1.0/PKG-INFO +79 -0
- publishmd-0.1.0/README.md +58 -0
- publishmd-0.1.0/pyproject.toml +49 -0
- publishmd-0.1.0/setup.cfg +4 -0
- publishmd-0.1.0/src/publishmd/__init__.py +3 -0
- publishmd-0.1.0/src/publishmd/base.py +67 -0
- publishmd-0.1.0/src/publishmd/cli.py +70 -0
- publishmd-0.1.0/src/publishmd/config.py +146 -0
- publishmd-0.1.0/src/publishmd/emitters/__init__.py +1 -0
- publishmd-0.1.0/src/publishmd/emitters/assets_emitter.py +271 -0
- publishmd-0.1.0/src/publishmd/emitters/qmd_emitter.py +128 -0
- publishmd-0.1.0/src/publishmd/filters/__init__.py +6 -0
- publishmd-0.1.0/src/publishmd/filters/frontmatter_filter.py +83 -0
- publishmd-0.1.0/src/publishmd/processor.py +118 -0
- publishmd-0.1.0/src/publishmd/transformers/__init__.py +1 -0
- publishmd-0.1.0/src/publishmd/transformers/stale_links_transformer.py +231 -0
- publishmd-0.1.0/src/publishmd/transformers/tags_to_categories_transformer.py +110 -0
- publishmd-0.1.0/src/publishmd/transformers/wikilink_transformer.py +241 -0
- publishmd-0.1.0/src/publishmd.egg-info/PKG-INFO +79 -0
- publishmd-0.1.0/src/publishmd.egg-info/SOURCES.txt +30 -0
- publishmd-0.1.0/src/publishmd.egg-info/dependency_links.txt +1 -0
- publishmd-0.1.0/src/publishmd.egg-info/entry_points.txt +2 -0
- publishmd-0.1.0/src/publishmd.egg-info/requires.txt +6 -0
- publishmd-0.1.0/src/publishmd.egg-info/top_level.txt +1 -0
- publishmd-0.1.0/tests/test_assets_emitter.py +447 -0
- publishmd-0.1.0/tests/test_config.py +133 -0
- publishmd-0.1.0/tests/test_filters.py +257 -0
- publishmd-0.1.0/tests/test_processor_filtering.py +207 -0
- publishmd-0.1.0/tests/test_qmd_emitter.py +132 -0
- publishmd-0.1.0/tests/test_stale_links_transformer.py +350 -0
- publishmd-0.1.0/tests/test_tags_to_categories_transformer.py +260 -0
- publishmd-0.1.0/tests/test_wikilink_transformer.py +389 -0
publishmd-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: publishmd
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Prepare markdown content for publication with configurable processing pipeline
|
|
5
|
+
Author: Mateus Molina
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MateusMolina/publishmd
|
|
8
|
+
Project-URL: Repository, https://github.com/MateusMolina/publishmd.git
|
|
9
|
+
Project-URL: Issues, https://github.com/MateusMolina/publishmd/issues
|
|
10
|
+
Keywords: quarto,markdown,publishing,second-brain,notes,vault,obsidian,journal
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Office/Business
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Requires-Dist: click>=8.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest; extra == "dev"
|
|
20
|
+
Requires-Dist: black; extra == "dev"
|
|
21
|
+
|
|
22
|
+
# publishmd
|
|
23
|
+
|
|
24
|
+
Prepare markdown content for publication with configurable processing pipeline.
|
|
25
|
+
|
|
26
|
+
***Use Case 1.*** Transform an Obsidian vault into publication-ready content for a Quarto blog. Convert wikilinks, filter content, copy assets, and apply transformations to prepare your notes for publishing.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### From Source
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
publishmd -c config.yaml -i /path/to/markdown -o /path/to/output
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Create a YAML configuration file to specify the processing pipeline, e.g.:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
filters:
|
|
48
|
+
- name: frontmatter_filter
|
|
49
|
+
type: publishmd.filters.frontmatter_filter.FrontmatterFilter
|
|
50
|
+
config:
|
|
51
|
+
publish: true
|
|
52
|
+
|
|
53
|
+
emitters:
|
|
54
|
+
- name: qmd_emitter
|
|
55
|
+
type: publishmd.emitters.qmd_emitter.QmdEmitter
|
|
56
|
+
- name: assets_emitter
|
|
57
|
+
type: publishmd.emitters.assets_emitter.AssetsEmitter
|
|
58
|
+
|
|
59
|
+
transformers:
|
|
60
|
+
- name: wikilink_transformer
|
|
61
|
+
type: publishmd.transformers.wikilink_transformer.WikilinkTransformer
|
|
62
|
+
config:
|
|
63
|
+
preserve_aliases: true
|
|
64
|
+
link_extension: ".qmd"
|
|
65
|
+
- name: stale_links_transformer
|
|
66
|
+
type: publishmd.transformers.stale_links_transformer.StaleLinksTransformer
|
|
67
|
+
config:
|
|
68
|
+
remove_stale_links: true
|
|
69
|
+
convert_to_text: true
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For more examples, please check the integration tests folder (tests/integration).
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install -e ".[dev]"
|
|
78
|
+
pytest
|
|
79
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# publishmd
|
|
2
|
+
|
|
3
|
+
Prepare markdown content for publication with configurable processing pipeline.
|
|
4
|
+
|
|
5
|
+
***Use Case 1.*** Transform an Obsidian vault into publication-ready content for a Quarto blog. Convert wikilinks, filter content, copy assets, and apply transformations to prepare your notes for publishing.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### From Source
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install -e .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
publishmd -c config.yaml -i /path/to/markdown -o /path/to/output
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
Create a YAML configuration file to specify the processing pipeline, e.g.:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
filters:
|
|
27
|
+
- name: frontmatter_filter
|
|
28
|
+
type: publishmd.filters.frontmatter_filter.FrontmatterFilter
|
|
29
|
+
config:
|
|
30
|
+
publish: true
|
|
31
|
+
|
|
32
|
+
emitters:
|
|
33
|
+
- name: qmd_emitter
|
|
34
|
+
type: publishmd.emitters.qmd_emitter.QmdEmitter
|
|
35
|
+
- name: assets_emitter
|
|
36
|
+
type: publishmd.emitters.assets_emitter.AssetsEmitter
|
|
37
|
+
|
|
38
|
+
transformers:
|
|
39
|
+
- name: wikilink_transformer
|
|
40
|
+
type: publishmd.transformers.wikilink_transformer.WikilinkTransformer
|
|
41
|
+
config:
|
|
42
|
+
preserve_aliases: true
|
|
43
|
+
link_extension: ".qmd"
|
|
44
|
+
- name: stale_links_transformer
|
|
45
|
+
type: publishmd.transformers.stale_links_transformer.StaleLinksTransformer
|
|
46
|
+
config:
|
|
47
|
+
remove_stale_links: true
|
|
48
|
+
convert_to_text: true
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For more examples, please check the integration tests folder (tests/integration).
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install -e ".[dev]"
|
|
57
|
+
pytest
|
|
58
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "publishmd"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Prepare markdown content for publication with configurable processing pipeline"
|
|
9
|
+
authors = [{name = "Mateus Molina"}]
|
|
10
|
+
license = "MIT"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Topic :: Office/Business",
|
|
15
|
+
]
|
|
16
|
+
keywords = ["quarto", "markdown", "publishing", "second-brain", "notes", "vault", "obsidian", "journal"]
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
requires-python = ">=3.10"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"pyyaml>=6.0",
|
|
21
|
+
"click>=8.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest",
|
|
27
|
+
"black"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/MateusMolina/publishmd"
|
|
32
|
+
Repository = "https://github.com/MateusMolina/publishmd.git"
|
|
33
|
+
Issues = "https://github.com/MateusMolina/publishmd/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
publishmd = "publishmd.cli:main"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-dir]
|
|
42
|
+
"" = "src"
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
markers = [
|
|
46
|
+
"unit: marks tests as unit tests (fast, isolated)",
|
|
47
|
+
"integration: marks tests as integration tests (slower, end-to-end)",
|
|
48
|
+
"slow: marks tests as slow running",
|
|
49
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Base classes for emitters, transformers, and filters."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Set
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Emitter(ABC):
|
|
9
|
+
"""Base class for all emitters."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, config: Dict[str, Any]):
|
|
12
|
+
"""Initialize the emitter with configuration."""
|
|
13
|
+
self.config = config
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def emit(self, files_to_process: List[Path], output_dir: Path) -> List[Path]:
|
|
17
|
+
"""
|
|
18
|
+
Emit files to output directory.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
files_to_process: List of files to process and emit
|
|
22
|
+
output_dir: Target directory for emitted files
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
List of paths to emitted files
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Transformer(ABC):
|
|
31
|
+
"""Base class for all transformers."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, config: Dict[str, Any]):
|
|
34
|
+
"""Initialize the transformer with configuration."""
|
|
35
|
+
self.config = config
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def transform(self, file_path: Path, emitted_files: List[Path]) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Transform a file in place.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
file_path: Path to the file to transform
|
|
44
|
+
emitted_files: List of all emitted files for reference
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Filter(ABC):
|
|
50
|
+
"""Base class for all filters."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, config: Dict[str, Any]):
|
|
53
|
+
"""Initialize the filter with configuration."""
|
|
54
|
+
self.config = config
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def should_include(self, file_path: Path) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Check if a file should be included based on filter criteria.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
file_path: Path to the file to check
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if the file should be included, False otherwise
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Command-line interface for publishmd."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
from .processor import Processor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.option(
|
|
12
|
+
"--config",
|
|
13
|
+
"-c",
|
|
14
|
+
type=click.Path(exists=True, path_type=Path),
|
|
15
|
+
required=True,
|
|
16
|
+
help="Path to the YAML configuration file",
|
|
17
|
+
)
|
|
18
|
+
@click.option(
|
|
19
|
+
"--input-dir",
|
|
20
|
+
"-i",
|
|
21
|
+
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
|
22
|
+
required=True,
|
|
23
|
+
help="Input directory containing markdown files",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--output-dir",
|
|
27
|
+
"-o",
|
|
28
|
+
type=click.Path(path_type=Path),
|
|
29
|
+
required=True,
|
|
30
|
+
help="Output directory for processed files",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--verbose",
|
|
34
|
+
"-v",
|
|
35
|
+
is_flag=True,
|
|
36
|
+
help="Enable verbose output",
|
|
37
|
+
)
|
|
38
|
+
def main(config: Path, input_dir: Path, output_dir: Path, verbose: bool) -> None:
|
|
39
|
+
"""Prepare markdown content for publication."""
|
|
40
|
+
|
|
41
|
+
if verbose:
|
|
42
|
+
click.echo(f"Loading configuration from: {config}")
|
|
43
|
+
click.echo(f"Input directory: {input_dir}")
|
|
44
|
+
click.echo(f"Output directory: {output_dir}")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Create CLI overrides dictionary
|
|
48
|
+
cli_overrides: Dict[str, Any] = {
|
|
49
|
+
"verbose": verbose,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Initialize processor
|
|
53
|
+
processor = Processor(config, cli_overrides)
|
|
54
|
+
|
|
55
|
+
if verbose:
|
|
56
|
+
click.echo(f"Loaded {len(processor.emitters)} emitters")
|
|
57
|
+
click.echo(f"Loaded {len(processor.transformers)} transformers")
|
|
58
|
+
|
|
59
|
+
# Process files
|
|
60
|
+
processor.process(input_dir, output_dir)
|
|
61
|
+
|
|
62
|
+
click.echo("Conversion completed successfully!")
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
click.echo(f"Error: {e}", err=True)
|
|
66
|
+
raise click.Abort()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
main()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Configuration loading and plugin management."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import yaml
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Union
|
|
7
|
+
|
|
8
|
+
from .base import Emitter, Transformer, Filter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConfigLoader:
|
|
12
|
+
"""Loads and validates configuration from YAML files."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def load_config(config_path: Union[str, Path]) -> Dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Load configuration from a YAML file.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config_path: Path to the YAML configuration file
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dictionary containing the configuration
|
|
24
|
+
"""
|
|
25
|
+
config_path = Path(config_path)
|
|
26
|
+
if not config_path.exists():
|
|
27
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
28
|
+
|
|
29
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
30
|
+
config = yaml.safe_load(f)
|
|
31
|
+
|
|
32
|
+
return config or {}
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def validate_config(config: Dict[str, Any]) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Validate the configuration structure.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
config: Configuration dictionary to validate
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If configuration is invalid
|
|
44
|
+
"""
|
|
45
|
+
if not isinstance(config, dict):
|
|
46
|
+
raise ValueError("Configuration must be a dictionary")
|
|
47
|
+
|
|
48
|
+
# At least one of these sections must be present
|
|
49
|
+
required_sections = ["emitters", "transformers", "filters"]
|
|
50
|
+
if not any(section in config for section in required_sections):
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Configuration must contain at least one of: {', '.join(required_sections)}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
for section in ["emitters", "transformers", "filters"]:
|
|
56
|
+
if section in config:
|
|
57
|
+
if not isinstance(config[section], list):
|
|
58
|
+
raise ValueError(f"'{section}' must be a list")
|
|
59
|
+
|
|
60
|
+
for item in config[section]:
|
|
61
|
+
if not isinstance(item, dict):
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Each item in '{section}' must be a dictionary"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if "name" not in item:
|
|
67
|
+
raise ValueError(f"Each item in '{section}' must have a 'name'")
|
|
68
|
+
|
|
69
|
+
if "type" not in item:
|
|
70
|
+
raise ValueError(f"Each item in '{section}' must have a 'type'")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PluginLoader:
|
|
74
|
+
"""Loads and instantiates plugins from configuration."""
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def load_plugin_class(class_path: str) -> type:
|
|
78
|
+
"""
|
|
79
|
+
Load a plugin class from a module path.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
class_path: Full module path to the class (e.g., 'module.submodule.ClassName')
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The loaded class
|
|
86
|
+
"""
|
|
87
|
+
module_path, class_name = class_path.rsplit(".", 1)
|
|
88
|
+
module = importlib.import_module(module_path)
|
|
89
|
+
return getattr(module, class_name)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def load_emitters(config: List[Dict[str, Any]]) -> List[Emitter]:
|
|
93
|
+
"""
|
|
94
|
+
Load emitter instances from configuration.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
config: List of emitter configurations
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List of instantiated emitters
|
|
101
|
+
"""
|
|
102
|
+
emitters = []
|
|
103
|
+
for emitter_config in config:
|
|
104
|
+
plugin_class = PluginLoader.load_plugin_class(emitter_config["type"])
|
|
105
|
+
plugin_config = emitter_config.get("config", {})
|
|
106
|
+
emitter = plugin_class(plugin_config)
|
|
107
|
+
emitters.append(emitter)
|
|
108
|
+
return emitters
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def load_transformers(config: List[Dict[str, Any]]) -> List[Transformer]:
|
|
112
|
+
"""
|
|
113
|
+
Load transformer instances from configuration.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
config: List of transformer configurations
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of instantiated transformers
|
|
120
|
+
"""
|
|
121
|
+
transformers = []
|
|
122
|
+
for transformer_config in config:
|
|
123
|
+
plugin_class = PluginLoader.load_plugin_class(transformer_config["type"])
|
|
124
|
+
plugin_config = transformer_config.get("config", {})
|
|
125
|
+
transformer = plugin_class(plugin_config)
|
|
126
|
+
transformers.append(transformer)
|
|
127
|
+
return transformers
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def load_filters(config: List[Dict[str, Any]]) -> List[Filter]:
|
|
131
|
+
"""
|
|
132
|
+
Load filter instances from configuration.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
config: List of filter configurations
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of instantiated filters
|
|
139
|
+
"""
|
|
140
|
+
filters = []
|
|
141
|
+
for filter_config in config:
|
|
142
|
+
plugin_class = PluginLoader.load_plugin_class(filter_config["type"])
|
|
143
|
+
plugin_config = filter_config.get("config", {})
|
|
144
|
+
filter_instance = plugin_class(plugin_config)
|
|
145
|
+
filters.append(filter_instance)
|
|
146
|
+
return filters
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Emitters package."""
|