diff --git a/.gitignore b/.gitignore
index 8395262..946edea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
+nsis/output/
# Installer logs
pip-log.txt
diff --git a/LICENSE b/LICENSE
index e72bfdd..a42f0f9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -132,7 +132,7 @@ implementation is available to the public in source code form. A
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
+the source code needed to preview, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
diff --git a/README.md b/README.md
index b63b1d0..6778af9 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
@@ -8,10 +8,15 @@
+
-
+
+
+
+
+
@@ -22,7 +27,7 @@ Skippy - Simple. Comfortable. Powerful. notepad for www.scpwiki.com
Installation
-If you have Python you can install Skippy using pip:
+If you have Python 3.7 or later you can install Skippy using pip:
```
$ python -m pip install skippy-pad
@@ -52,7 +57,7 @@ Features
* Support all wikidot sites
* Autoupdating system
* Multi language interface
-* Advanced autocomplete, suggestion and syntax checking engine to the Wikidot syntax
+* Advanced autocomplete, suggestion and syntax engine to the Wikidot syntax
* and many other features!
@@ -92,9 +97,19 @@ Author
Donating
+* Buy me a coffee:
+
+
+
* Cryptocurrencies:
* Binance Smart Chain (BSC) - 0x581cc7acbe921b1fa9fccbbfe008c86f68bc097d (BTC, ETH, DOGE and etc.)
+## Sponsors
+
+Skippy would like to thank the following organizations for graciously permitting us to use their services for free:
+
+* [JetBrains](https://www.jetbrains.com/pycharm/) ([info](https://www.jetbrains.com/community/opensource/#support))
+
License
diff --git a/img/banner.png b/img/banner.png
new file mode 100644
index 0000000..17fb6b3
Binary files /dev/null and b/img/banner.png differ
diff --git a/img/skippy.png b/img/skippy.png
deleted file mode 100644
index f4f8f2a..0000000
Binary files a/img/skippy.png and /dev/null differ
diff --git a/img/skippy.svg b/img/skippy.svg
new file mode 100644
index 0000000..73a23bf
--- /dev/null
+++ b/img/skippy.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nsis/compile.bat b/nsis/compile.bat
index 148624e..4b7176d 100644
--- a/nsis/compile.bat
+++ b/nsis/compile.bat
@@ -1,3 +1,5 @@
+@ECHO OFF
+
python -m venv venv
venv\Scripts\python -m pip install ../../pyscp
diff --git a/requirements.txt b/requirements.txt
index b768641..506c840 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,8 @@
pypresence==4.2.0
requests==2.25.1
prettytable==2.0.0
-pyscp>=1.1.19
-pyftml>=0.1.2
+pyscp==1.1.19
+pyftml==0.1.2
PyQt5==5.15.5
-PyQtWebEngine==5.15.4
+PyQtWebEngine==5.15.5
+toml==0.10.2
diff --git a/setup.py b/setup.py
index 557470a..fd2e21a 100644
--- a/setup.py
+++ b/setup.py
@@ -55,13 +55,15 @@ def _get_constant(name: str) -> Any:
url="https://github.com/skippy-dev/skippy/",
author=_get_constant("author"),
author_email=_get_constant("email"),
- python_requires=">=3.9",
+ python_requires=">=3.7",
license=_get_constant("license"),
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Other Audience",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
keywords=["scp", "wikidot", "pyscp", "skippy"],
@@ -72,7 +74,7 @@ def _get_constant(name: str) -> Any:
"skippy": [
"resources/*",
"resources/*/*",
- "lang/*.json",
+ "lang/*.toml"
]
},
entry_points={
diff --git a/skippy/__init__.py b/skippy/__init__.py
index 92d86ab..163542f 100644
--- a/skippy/__init__.py
+++ b/skippy/__init__.py
@@ -1,5 +1,5 @@
__author__ = "MrNereof"
__license__ = "GPLv3"
__email__ = "mrnereof@gmail.com"
-__version__ = "1.2.3"
+__version__ = "1.2.4"
__description__ = "Simple. Comfortable. Powerful. notepad for www.scpwiki.com."
diff --git a/skippy/api/types.py b/skippy/api/types.py
index 625f9a4..4a5b506 100644
--- a/skippy/api/types.py
+++ b/skippy/api/types.py
@@ -1,16 +1,22 @@
-from typing import NamedTuple, TypedDict, Callable, Optional, Tuple, Dict, List, Any
+from typing import MutableMapping, NamedTuple, TypedDict, Callable, Optional, Tuple, Dict, List, Any
class Language(NamedTuple):
code: str
- dictionary: Dict[str, str]
+ dictionary: MutableMapping[str, Any]
+
+
+class Field(NamedTuple):
+ tag: str
+ name: str
+ description: str
class Action(NamedTuple):
label: str
statusTip: str
func: Callable[..., Any]
- img: str
+ img: Optional[str] = None
class InlineSyntax(NamedTuple):
@@ -33,4 +39,4 @@ class PageData(TypedDict):
link: Optional[Tuple[str, str]]
-__all__ = ["Language", "Action", "InlineSyntax", "BlockSyntax", "PageData"]
+__all__ = ["Language", "Field", "Action", "InlineSyntax", "BlockSyntax", "PageData"]
diff --git a/skippy/app.py b/skippy/app.py
index f40e60f..d91ba87 100644
--- a/skippy/app.py
+++ b/skippy/app.py
@@ -91,8 +91,11 @@ def plugins(self):
print(table)
- def start_ui(self):
+ def start_ui(self) -> int:
"""Initialize everything and run the application.
+
+ Returns:
+ int: Exit code
"""
logger.log.setLevel(logger.LOG_LEVELS[self.args.logging_level])
@@ -108,10 +111,10 @@ def start_ui(self):
logger.log.info("Start plugins...")
pluginLoader.start_plugins()
- logger.log.info("Initializing Discord RPC..")
+ logger.log.info("Initializing Discord RPC...")
rpc = discord_rpc.DiscordRPC()
- logger.log.info("Connect Discord RPC..")
+ logger.log.info("Connect Discord RPC...")
rpc.connect()
logger.log.info("Initializing application...")
@@ -120,7 +123,7 @@ def start_ui(self):
logger.log.info("Stop plugins...")
pluginLoader.stop_plugins()
- logger.log.info("Stop Discord RPC..")
+ logger.log.info("Stop Discord RPC...")
rpc.close()
return exit_code
diff --git a/skippy/config.py b/skippy/config.py
index 59f4c42..b9fa786 100644
--- a/skippy/config.py
+++ b/skippy/config.py
@@ -35,3 +35,7 @@
PROPERTY_FOLDER = os.path.join(APPDATA_FOLDER, "property")
LOGS_FOLDER = os.path.join(APPDATA_FOLDER, "logs")
+
+ PLUGINS_SETTINGS_FOLDER = os.path.join(APPDATA_FOLDER, "plugins")
+
+PLUGINS_SETTINGS_FOLDER = os.path.join(PLUGINS_FOLDER, "settings")
diff --git a/skippy/core/elements.py b/skippy/core/elements.py
new file mode 100644
index 0000000..0bd1da5
--- /dev/null
+++ b/skippy/core/elements.py
@@ -0,0 +1,202 @@
+"""Elements core module
+
+Attributes:
+ elements (List[AbstractElement]): List of elements
+"""
+from skippy.api import Field
+
+from abc import ABCMeta, abstractmethod
+from typing import Dict, List
+
+
+class AbstractElement(metaclass=ABCMeta):
+
+ """Abstract element class
+
+ Attributes:
+ required_fields (List[Field]): List of element fields
+ """
+
+ __alias__: str
+ __description__: str
+
+ base: str
+
+ def __init__(self):
+ """Initializing element
+ """
+ self.required_fields: List[Field] = []
+ self._init_fields()
+
+ def check_args(self, args: Dict[str, str]):
+ """Check required arguments
+
+ Args:
+ args (Dict[str, str]): Arguments for fields
+
+ Raises:
+ TypeError: Don't received required argument
+ """
+ for field in self.required_fields:
+ if field.tag not in args:
+ raise TypeError(
+ f"{self.__class__.__name__}.preview() don't received required argument: {field.tag}"
+ )
+
+ @abstractmethod
+ def _init_fields(self):
+ """Abstract init fields method
+ """
+ pass
+
+ def add_field(self, tag: str, name: str = "", description: str = ""):
+ """Add field to element
+
+ Args:
+ tag (str): Field tag
+ name (str, optional): Field name
+ description (str, optional): Field description
+ """
+ self.required_fields.append(Field(tag, name, description))
+
+ def prepare_args(self, args: Dict[str, str]) -> Dict[str, str]:
+ """Prepare arguments
+
+ Args:
+ args (Dict[str, str]): Arguments for fields
+
+ Returns:
+ Dict[str, str]: Prepared arguments
+ """
+ return args
+
+ def preview(self, args: Dict[str, str]) -> str:
+ """Preview element using arguments
+
+ Args:
+ args (Dict[str, str]): Arguments for fields
+
+ Returns:
+ str: Description
+ """
+ self.check_args(args)
+ args = self.prepare_args(args)
+
+ data = self.base
+ for name in args:
+ data = data.replace(f"<<{name}>>", args[name])
+ return data
+
+
+class AbstractComponent(AbstractElement):
+
+ """Abstract included component
+ """
+
+ component: str
+ base: str = "[[include <> <>]]"
+
+ multiline: bool = True
+
+ def prepare_args(self, args: Dict[str, str]) -> Dict[str, str]:
+ """Prepare arguments
+
+ Args:
+ args (Dict[str, str]): Arguments for fields
+
+ Returns:
+ Dict[str, str]: Prepared arguments
+ """
+ next_line = "\n" if self.multiline else " "
+ return {
+ "component": self.component,
+ "args": f" |{next_line}".join([f"{arg}={args[arg]}" for arg in args if args[arg]]),
+ }
+
+
+class BaseImageBlock(AbstractComponent):
+
+ """Base image block
+ """
+
+ component: str = "component:image-block"
+
+ multiline: bool = False
+
+ align: str
+
+ def _init_fields(self):
+ """Initializing fields
+ """
+ self.add_field("name", "ELEMENTS.BASE_IMAGE.NAME.NAME", "ELEMENTS.BASE_IMAGE.NAME.DESC")
+ self.add_field("caption", "ELEMENTS.BASE_IMAGE.CAPTION.NAME", "ELEMENTS.BASE_IMAGE.CAPTION.DESC")
+ self.add_field("width", "ELEMENTS.BASE_IMAGE.WIDTH.NAME", "ELEMENTS.BASE_IMAGE.WIDTH.DESC")
+ self.add_field("link", "ELEMENTS.BASE_IMAGE.LINK.NAME", "ELEMENTS.BASE_IMAGE.LINK.DESC")
+
+ def prepare_args(self, args: Dict[str, str]) -> Dict[str, str]:
+ """Prepare arguments
+
+ Args:
+ args (Dict[str, str]): Arguments for fields
+
+ Returns:
+ Dict[str, str]: Prepared arguments
+ """
+ args["align"] = self.align
+ return super(BaseImageBlock, self).prepare_args(args)
+
+
+class RightImageBlock(BaseImageBlock):
+
+ """Right image block
+ """
+
+ __alias__: str = "ELEMENTS.RIGHT_IMAGE_BLOCK.ALIAS"
+ __description__: str = "ELEMENTS.RIGHT_IMAGE_BLOCK.DESC"
+
+ align: str = ""
+
+
+class LeftImageBlock(BaseImageBlock):
+
+ """Left image block
+ """
+
+ __alias__: str = "ELEMENTS.LEFT_IMAGE_BLOCK.ALIAS"
+ __description__: str = "ELEMENTS.LEFT_IMAGE_BLOCK.DESC"
+
+ align: str = "left"
+
+
+class CenterImageBlock(BaseImageBlock):
+
+ """Center image block
+ """
+
+ __alias__: str = "ELEMENTS.CENTER_IMAGE_BLOCK.ALIAS"
+ __description__: str = "ELEMENTS.CENTER_IMAGE_BLOCK.DESC"
+
+ align: str = "center"
+
+
+class ACSBarComponent(AbstractComponent):
+
+ """Anomaly Classification Bar component
+ """
+
+ __alias__: str = "ELEMENTS.ACS_BAR.ALIAS"
+ __description__: str = "ELEMENTS.ACS_BAR.DESC"
+
+ component: str = "component:anomaly-class-bar-source"
+
+ def _init_fields(self):
+ self.add_field("item-number", "ELEMENTS.ACS_BAR.ITEM.NAME", "ELEMENTS.ACS_BAR.ITEM.DESC")
+ self.add_field("clearance", "ELEMENTS.ACS_BAR.CLEARANCE.NAME", "ELEMENTS.ACS_BAR.CLEARANCE.DESC")
+ self.add_field("container-class", "ELEMENTS.ACS_BAR.CONTAINER.NAME", "ELEMENTS.ACS_BAR.CONTAINER.DESC")
+ self.add_field("disruption-class", "ELEMENTS.ACS_BAR.DISRUPTION.NAME", "ELEMENTS.ACS_BAR.DISRUPTION.DESC")
+ self.add_field("risk-class", "ELEMENTS.ACS_BAR.RISK.NAME", "ELEMENTS.ACS_BAR.RISK.DESC")
+ self.add_field("secondary-class", "ELEMENTS.ACS_BAR.SEC_CLASS.NAME", "ELEMENTS.ACS_BAR.SEC_CLASS.DESC")
+ self.add_field("secondary-icon", "ELEMENTS.ACS_BAR.SEC_ICON.NAME", "ELEMENTS.ACS_BAR.SEC_ICON.DESC")
+
+
+elements: List[AbstractElement] = [RightImageBlock, LeftImageBlock, CenterImageBlock, ACSBarComponent]
diff --git a/skippy/core/plugins.py b/skippy/core/plugins.py
index 9ff8b8e..c05705b 100644
--- a/skippy/core/plugins.py
+++ b/skippy/core/plugins.py
@@ -9,7 +9,7 @@
from typing import Iterator, Optional, Callable, Union, Dict, List, Any
from abc import ABCMeta, abstractmethod
import importlib
-import json
+import toml
import sys
import os
@@ -61,7 +61,9 @@ class AbstractPlugin(metaclass=ABCMeta):
__version__: str
def __init__(self):
- self._settingsPath: str = os.path.join(skippy.config.PLUGINS_FOLDER, f"{self.__alias__}.json")
+ self._settingsPath: str = os.path.join(
+ skippy.config.PLUGINS_SETTINGS_FOLDER, f"{self.__alias__}.toml"
+ )
self._settings: Dict[str, AbstractSetting] = {}
def addSetting(self, name: str, setting: AbstractSetting):
@@ -69,16 +71,22 @@ def addSetting(self, name: str, setting: AbstractSetting):
def load_settings(self):
if self._settings and os.path.exists(self._settingsPath):
- with open(self._settingsPath, "r") as f:
- settings = json.loads(f.read())
+ with open(self._settingsPath) as f:
+ settings = toml.load(f)
for setting in settings:
if setting in self._settings:
- self._settings[setting].fromJson(settings[setting])
+ self._settings[setting].from_toml(settings[setting])
def save_settings(self):
if self._settings:
with open(self._settingsPath, "w") as f:
- f.write(json.dumps({setting: self._settings[setting].toJson() for setting in self._settings}))
+ toml.dump(
+ {
+ setting: self._settings[setting].to_toml()
+ for setting in self._settings
+ },
+ f,
+ )
@abstractmethod
def start(self):
diff --git a/skippy/core/preview.py b/skippy/core/preview.py
index 84968ed..a0f897f 100644
--- a/skippy/core/preview.py
+++ b/skippy/core/preview.py
@@ -1,4 +1,4 @@
-"""Summary
+"""Wikidot syntax previewer
"""
from skippy.api import PageData
@@ -15,13 +15,13 @@
def render(pdata: PageData) -> str:
- """Summary
+ """Render page by page data
Args:
- pdata (PageData): Description
+ pdata (PageData): Page data (title, source, tags and files)
Returns:
- str: Description
+ str: Rendered HTML
"""
preprocess = PreProcessorsHandler(pdata).process()
htmlprocess = HTMLProcessorsHandler(preprocess, pdata).process()
@@ -33,26 +33,26 @@ def render(pdata: PageData) -> str:
#################################################
class AbstractProcessor(metaclass=ABCMeta):
- """Summary"""
+ """Abstract processor class"""
pattern: str
def __init__(self, source: str, pdata: PageData):
- """Summary
+ """Initializing Processor
Args:
- source (str): Description
- pdata (PageData): Description
+ source (str): Page source
+ pdata (PageData): Page data
"""
self.source: str = source
self.pdata: PageData = pdata
@property
def matches(self) -> List[Union[Tuple[str, ...], str]]:
- """Summary
+ """Get all matches in source by pattern
Returns:
- List[Union[Tuple[str, ...], str]]: Description
+ List[Union[Tuple[str, ...], str]]: List of matches
"""
return re.findall(
self.pattern,
@@ -61,42 +61,38 @@ def matches(self) -> List[Union[Tuple[str, ...], str]]:
@abstractmethod
def process(self):
- """Summary"""
+ """Abstract process method"""
pass
-class ProcessorsHandler(metaclass=ABCMeta):
+class ProcessorsHandlerBase:
- """Summary
-
- Attributes:
- source (str): Description
- """
+ """Abstract processors handler class"""
def __init__(self, source: str, pdata: PageData):
"""Summary
Args:
- source (str): Description
- pdata (PageData): Description
+ source (str): Page source
+ pdata (PageData): Page data
"""
self.source: str = source
self.pdata: PageData = pdata
self.processors: List[AbstractProcessor] = []
- def register(self, processor: AbstractProcessor):
- """Summary
+ def register(self, processor: AbstractProcessor) -> object:
+ """Register a processor
Args:
- processor (AbstractProcessor): Description
+ processor (AbstractProcessor): Processor class
"""
self.processors.append(processor)
def process(self) -> str:
- """Summary
+ """Run all processor
Returns:
- str: Description
+ str: Processed source
"""
for processor in self.processors:
try:
@@ -109,46 +105,42 @@ def process(self) -> str:
#################################################
# ProcessorsHandlers
#################################################
-class PreProcessorsHandler(ProcessorsHandler):
+class PreProcessorsHandler(ProcessorsHandlerBase):
- """Summary"""
+ """Handler of preprocessors"""
def __init__(self, pdata: PageData):
- """Summary
+ """Initializing preprocessors handler
Args:
- pdata (PageData): Description
+ pdata (PageData): Page data
"""
super(PreProcessorsHandler, self).__init__(pdata["source"], pdata)
self.register(IncludesProcessor)
self.register(IftagsProcessor)
-class HTMLProcessorsHandler(ProcessorsHandler):
+class HTMLProcessorsHandler(ProcessorsHandlerBase):
- """Summary
-
- Attributes:
- html (str): Description
- """
+ """Handler of HTML processors"""
def __init__(self, source: str, pdata: PageData):
- """Summary
+ """Initializing HTML processors handler
Args:
- source (str): Description
- pdata (PageData): Description
+ source (str): Page source
+ pdata (PageData): Page data
"""
super(HTMLProcessorsHandler, self).__init__(source, pdata)
self.register(MarkdownProcessor)
self.register(InsertDataProcessor)
self.register(ModuleCSSProcessor)
- def process(self):
- """Summary
+ def process(self) -> str:
+ """Run all processor
Returns:
- TYPE: Description
+ str: Processed source
"""
self.html = self.processors[0](self.source, self.pdata).process()
for processor in self.processors[1:]:
@@ -159,16 +151,16 @@ def process(self):
return self.html
-class PostProcessorsHandler(ProcessorsHandler):
+class PostProcessorsHandler(ProcessorsHandlerBase):
- """Summary"""
+ """Handler of postprocessors"""
def __init__(self, source: str, pdata: PageData):
- """Summary
+ """Initializing postprocessors handler
Args:
- source (str): Description
- pdata (PageData): Description
+ source (str): Page source
+ pdata (PageData): Page data
"""
super(PostProcessorsHandler, self).__init__(source, pdata)
self.register(LocalImagesProcessor)
@@ -180,22 +172,18 @@ def __init__(self, source: str, pdata: PageData):
#################################################
class IncludesProcessor(AbstractProcessor):
- """Summary
-
- Attributes:
- source (str): Description
- """
+ """Include processor"""
pattern: str = r"(\[\[include\s(?::.+?:|)(?:.+?:|)(?:.+)(?:\s((?:.|\n)+?)|)]])"
def process(self, iteration: int = 0) -> str:
- """Summary
-
- Returns:
- str: Description
+ """Replace all include tags with included page source
Args:
- iteration (int, optional): Description
+ iteration (int, optional): Include iteration (max=5)
+
+ Returns:
+ str: Processed source
"""
for include in self.matches:
path = re.findall(
@@ -234,19 +222,15 @@ def process(self, iteration: int = 0) -> str:
class IftagsProcessor(AbstractProcessor):
- """Summary
-
- Attributes:
- source (str): Description
- """
+ """Iftags processor"""
pattern: str = r"(\[\[iftags (.+?)]]((?:(?:.|\n|\s)+?))\[\[\/iftags]])"
def process(self) -> str:
- """Summary
+ """Remove iftags if it not matches with page tags
Returns:
- str: Description
+ str: Processed source
"""
for iftag in self.matches:
match = True
@@ -267,31 +251,31 @@ def process(self) -> str:
#################################################
class MarkdownProcessor(AbstractProcessor):
- """Summary"""
+ """Markdown processor"""
wiki: pyscp.wikidot.Wiki = pyscp.wikidot.Wiki("www.wikidot.com")
def _wikidot(self) -> str:
- """Summary
+ """If connected to internet, get previewed HTML from Wikidot
Returns:
- str: Description
+ str: Previewed source
"""
return self.wiki._module("edit/PagePreviewModule", source=self.source)["body"]
def _ftml(self) -> str:
- """Summary
+ """If don't have connection to internet, get previewed HTML from local FTML previewer
Returns:
- str: Description
+ str: Previewed source
"""
return pyftml.render_html(self.source)["body"]
def process(self) -> str:
- """Summary
+ """Get previewed page with online/offline methods
Returns:
- str: Description
+ str: Processed source
"""
try:
return self._wikidot()
@@ -301,7 +285,7 @@ def process(self) -> str:
class InsertDataProcessor(AbstractProcessor):
- """Summary"""
+ """InsertData processor"""
html_base: str = """
@@ -374,21 +358,21 @@ class InsertDataProcessor(AbstractProcessor):
"""
def __init__(self, source: str, pdata: PageData, html: str):
- """Summary
+ """Initializing InsertData processor
Args:
- source (str): Description
- pdata (PageData): Description
- html (str): Description
+ source (str): Page source
+ pdata (PageData): Page data
+ html (str): Previewed page html
"""
super(InsertDataProcessor, self).__init__(source, pdata)
self.html: str = html
def process(self) -> str:
- """Summary
+ """Insert page data to HTML template
Returns:
- str: Description
+ str: Processed HTML page
"""
self.source = self.html_base.replace(
"<>", html.escape(self.pdata["title"])
@@ -403,30 +387,26 @@ def process(self) -> str:
class ModuleCSSProcessor(AbstractProcessor):
- """Summary
-
- Attributes:
- html (str): Description
- """
+ """Module CSS processor"""
pattern: str = r"(?:(?:\[\[module) (?:CSS|css)(?:(.+?)|)\]\]\n)((.|\n)+?)(?:\n\[\[\/module\]\])"
def __init__(self, source: str, pdata: PageData, html: str):
- """Summary
+ """Initializing Module CSS processor
Args:
- source (str): Description
- pdata (PageData): Description
- html (str): Description
+ source (str): Page source
+ pdata (PageData): Page data
+ html (str): Previewed page html
"""
super(ModuleCSSProcessor, self).__init__(source, pdata)
self.html: str = html
def process(self) -> str:
- """Summary
+ """Convert [[module CSS]] block to