Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
main.py352 linesDownload Raw Back to markdown_it
1from __future__ import annotations2 3from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping4from contextlib import contextmanager5from typing import Any, Literal, overload6 7from . import helpers, presets8from .common import normalize_url, utils9from .parser_block import ParserBlock10from .parser_core import ParserCore11from .parser_inline import ParserInline12from .renderer import RendererHTML, RendererProtocol13from .rules_core.state_core import StateCore14from .token import Token15from .utils import EnvType, OptionsDict, OptionsType, PresetType16 17try:18    import linkify_it19except ModuleNotFoundError:20    linkify_it = None21 22 23_PRESETS: dict[str, PresetType] = {24    "default": presets.default.make(),25    "js-default": presets.js_default.make(),26    "zero": presets.zero.make(),27    "commonmark": presets.commonmark.make(),28    "gfm-like": presets.gfm_like.make(),29    "gfm-like2": presets.gfm_like2.make(),30}31 32 33class MarkdownIt:34    def __init__(35        self,36        config: str | PresetType = "commonmark",37        options_update: Mapping[str, Any] | None = None,38        *,39        renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,40    ):41        """Main parser class42 43        :param config: name of configuration to load or a pre-defined dictionary44        :param options_update: dictionary that will be merged into ``config["options"]``45        :param renderer_cls: the class to load as the renderer:46            ``self.renderer = renderer_cls(self)47        """48        # add modules49        self.utils = utils50        self.helpers = helpers51 52        # initialise classes53        self.inline = ParserInline()54        self.block = ParserBlock()55        self.core = ParserCore()56        self.renderer = renderer_cls(self)57        self.linkify = linkify_it.LinkifyIt() if linkify_it else None58 59        # set the configuration60        if options_update and not isinstance(options_update, Mapping):61            # catch signature change where renderer_cls was not used as a key-word62            raise TypeError(63                f"options_update should be a mapping: {options_update}"64                "\n(Perhaps you intended this to be the renderer_cls?)"65            )66        self.configure(config, options_update=options_update)67 68    def __repr__(self) -> str:69        return f"{self.__class__.__module__}.{self.__class__.__name__}()"70 71    @overload72    def __getitem__(self, name: Literal["inline"]) -> ParserInline: ...73 74    @overload75    def __getitem__(self, name: Literal["block"]) -> ParserBlock: ...76 77    @overload78    def __getitem__(self, name: Literal["core"]) -> ParserCore: ...79 80    @overload81    def __getitem__(self, name: Literal["renderer"]) -> RendererProtocol: ...82 83    @overload84    def __getitem__(self, name: str) -> Any: ...85 86    def __getitem__(self, name: str) -> Any:87        return {88            "inline": self.inline,89            "block": self.block,90            "core": self.core,91            "renderer": self.renderer,92        }[name]93 94    def set(self, options: OptionsType) -> None:95        """Set parser options (in the same format as in constructor).96        Probably, you will never need it, but you can change options after constructor call.97 98        __Note:__ To achieve the best possible performance, don't modify a99        `markdown-it` instance options on the fly. If you need multiple configurations100        it's best to create multiple instances and initialize each with separate config.101        """102        self.options = OptionsDict(options)103 104    def configure(105        self, presets: str | PresetType, options_update: Mapping[str, Any] | None = None106    ) -> MarkdownIt:107        """Batch load of all options and component settings.108        This is an internal method, and you probably will not need it.109        But if you will - see available presets and data structure110        [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)111 112        We strongly recommend to use presets instead of direct config loads.113        That will give better compatibility with next versions.114        """115        if isinstance(presets, str):116            if presets not in _PRESETS:117                raise KeyError(f"Wrong `markdown-it` preset '{presets}', check name")118            config = _PRESETS[presets]119        else:120            config = presets121 122        if not config:123            raise ValueError("Wrong `markdown-it` config, can't be empty")124 125        options = config.get("options", {}) or {}126        if options_update:127            options = {**options, **options_update}  # type: ignore128 129        self.set(options)130 131        if "components" in config:132            for name, component in config["components"].items():133                rules = component.get("rules", None)134                if rules:135                    self[name].ruler.enableOnly(rules)136                rules2 = component.get("rules2", None)137                if rules2:138                    self[name].ruler2.enableOnly(rules2)139 140        return self141 142    def get_all_rules(self) -> dict[str, list[str]]:143        """Return the names of all active rules."""144        rules = {145            chain: self[chain].ruler.get_all_rules()146            for chain in ["core", "block", "inline"]147        }148        rules["inline2"] = self.inline.ruler2.get_all_rules()149        return rules150 151    def get_active_rules(self) -> dict[str, list[str]]:152        """Return the names of all active rules."""153        rules = {154            chain: self[chain].ruler.get_active_rules()155            for chain in ["core", "block", "inline"]156        }157        rules["inline2"] = self.inline.ruler2.get_active_rules()158        return rules159 160    def enable(161        self, names: str | Iterable[str], ignoreInvalid: bool = False162    ) -> MarkdownIt:163        """Enable list or rules. (chainable)164 165        :param names: rule name or list of rule names to enable.166        :param ignoreInvalid: set `true` to ignore errors when rule not found.167 168        It will automatically find appropriate components,169        containing rules with given names. If rule not found, and `ignoreInvalid`170        not set - throws exception.171 172        Example::173 174            md = MarkdownIt().enable(['sub', 'sup']).disable('smartquotes')175 176        """177        result = []178 179        if isinstance(names, str):180            names = [names]181 182        for chain in ["core", "block", "inline"]:183            result.extend(self[chain].ruler.enable(names, True))184        result.extend(self.inline.ruler2.enable(names, True))185 186        missed = [name for name in names if name not in result]187        if missed and not ignoreInvalid:188            raise ValueError(f"MarkdownIt. Failed to enable unknown rule(s): {missed}")189 190        return self191 192    def disable(193        self, names: str | Iterable[str], ignoreInvalid: bool = False194    ) -> MarkdownIt:195        """The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)196 197        :param names: rule name or list of rule names to disable.198        :param ignoreInvalid: set `true` to ignore errors when rule not found.199 200        """201        result = []202 203        if isinstance(names, str):204            names = [names]205 206        for chain in ["core", "block", "inline"]:207            result.extend(self[chain].ruler.disable(names, True))208        result.extend(self.inline.ruler2.disable(names, True))209 210        missed = [name for name in names if name not in result]211        if missed and not ignoreInvalid:212            raise ValueError(f"MarkdownIt. Failed to disable unknown rule(s): {missed}")213        return self214 215    @contextmanager216    def reset_rules(self) -> Generator[None, None, None]:217        """A context manager, that will reset the current enabled rules on exit."""218        chain_rules = self.get_active_rules()219        yield220        for chain, rules in chain_rules.items():221            if chain != "inline2":222                self[chain].ruler.enableOnly(rules)223        self.inline.ruler2.enableOnly(chain_rules["inline2"])224 225    def add_render_rule(226        self, name: str, function: Callable[..., Any], fmt: str = "html"227    ) -> None:228        """Add a rule for rendering a particular Token type.229 230        Only applied when ``renderer.__output__ == fmt``231        """232        if self.renderer.__output__ == fmt:233            self.renderer.rules[name] = function.__get__(self.renderer)  # type: ignore234 235    def use(236        self, plugin: Callable[..., None], *params: Any, **options: Any237    ) -> MarkdownIt:238        """Load specified plugin with given params into current parser instance. (chainable)239 240        It's just a sugar to call `plugin(md, params)` with curring.241 242        Example::243 244            def func(tokens, idx):245                tokens[idx].content = tokens[idx].content.replace('foo', 'bar')246            md = MarkdownIt().use(plugin, 'foo_replace', 'text', func)247 248        """249        plugin(self, *params, **options)250        return self251 252    def parse(self, src: str, env: EnvType | None = None) -> list[Token]:253        """Parse the source string to a token stream254 255        :param src: source string256        :param env: environment sandbox257 258        Parse input string and return list of block tokens (special token type259        "inline" will contain list of inline tokens).260 261        `env` is used to pass data between "distributed" rules and return additional262        metadata like reference info, needed for the renderer. It also can be used to263        inject data in specific cases. Usually, you will be ok to pass `{}`,264        and then pass updated object to renderer.265        """266        env = {} if env is None else env267        if not isinstance(env, MutableMapping):268            raise TypeError(f"Input data should be a MutableMapping, not {type(env)}")269        if not isinstance(src, str):270            raise TypeError(f"Input data should be a string, not {type(src)}")271        state = StateCore(src, self, env)272        self.core.process(state)273        return state.tokens274 275    def render(self, src: str, env: EnvType | None = None) -> Any:276        """Render markdown string into html. It does all magic for you :).277 278        :param src: source string279        :param env: environment sandbox280        :returns: The output of the loaded renderer281 282        `env` can be used to inject additional metadata (`{}` by default).283        But you will not need it with high probability. See also comment284        in [[MarkdownIt.parse]].285        """286        env = {} if env is None else env287        return self.renderer.render(self.parse(src, env), self.options, env)288 289    def parseInline(self, src: str, env: EnvType | None = None) -> list[Token]:290        """The same as [[MarkdownIt.parse]] but skip all block rules.291 292        :param src: source string293        :param env: environment sandbox294 295        It returns the296        block tokens list with the single `inline` element, containing parsed inline297        tokens in `children` property. Also updates `env` object.298        """299        env = {} if env is None else env300        if not isinstance(env, MutableMapping):301            raise TypeError(f"Input data should be an MutableMapping, not {type(env)}")302        if not isinstance(src, str):303            raise TypeError(f"Input data should be a string, not {type(src)}")304        state = StateCore(src, self, env)305        state.inlineMode = True306        self.core.process(state)307        return state.tokens308 309    def renderInline(self, src: str, env: EnvType | None = None) -> Any:310        """Similar to [[MarkdownIt.render]] but for single paragraph content.311 312        :param src: source string313        :param env: environment sandbox314 315        Similar to [[MarkdownIt.render]] but for single paragraph content. Result316        will NOT be wrapped into `<p>` tags.317        """318        env = {} if env is None else env319        return self.renderer.render(self.parseInline(src, env), self.options, env)320 321    # link methods322 323    def validateLink(self, url: str) -> bool:324        """Validate if the URL link is allowed in output.325 326        This validator can prohibit more than really needed to prevent XSS.327        It's a tradeoff to keep code simple and to be secure by default.328 329        Note: the url should be normalized at this point, and existing entities decoded.330        """331        return normalize_url.validateLink(url)332 333    def normalizeLink(self, url: str) -> str:334        """Normalize destination URLs in links335 336        ::337 338            [label]:   destination   'title'339                    ^^^^^^^^^^^340        """341        return normalize_url.normalizeLink(url)342 343    def normalizeLinkText(self, link: str) -> str:344        """Normalize autolink content345 346        ::347 348            <destination>349            ~~~~~~~~~~~350        """351        return normalize_url.normalizeLinkText(link)352 
codekingpro/portable-devtools · Team Ai