Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
main.py356 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}30 31 32class MarkdownIt:33    def __init__(34        self,35        config: str | PresetType = "commonmark",36        options_update: Mapping[str, Any] | None = None,37        *,38        renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,39    ):40        """Main parser class41 42        :param config: name of configuration to load or a pre-defined dictionary43        :param options_update: dictionary that will be merged into ``config["options"]``44        :param renderer_cls: the class to load as the renderer:45            ``self.renderer = renderer_cls(self)46        """47        # add modules48        self.utils = utils49        self.helpers = helpers50 51        # initialise classes52        self.inline = ParserInline()53        self.block = ParserBlock()54        self.core = ParserCore()55        self.renderer = renderer_cls(self)56        self.linkify = linkify_it.LinkifyIt() if linkify_it else None57 58        # set the configuration59        if options_update and not isinstance(options_update, Mapping):60            # catch signature change where renderer_cls was not used as a key-word61            raise TypeError(62                f"options_update should be a mapping: {options_update}"63                "\n(Perhaps you intended this to be the renderer_cls?)"64            )65        self.configure(config, options_update=options_update)66 67    def __repr__(self) -> str:68        return f"{self.__class__.__module__}.{self.__class__.__name__}()"69 70    @overload71    def __getitem__(self, name: Literal["inline"]) -> ParserInline:72        ...73 74    @overload75    def __getitem__(self, name: Literal["block"]) -> ParserBlock:76        ...77 78    @overload79    def __getitem__(self, name: Literal["core"]) -> ParserCore:80        ...81 82    @overload83    def __getitem__(self, name: Literal["renderer"]) -> RendererProtocol:84        ...85 86    @overload87    def __getitem__(self, name: str) -> Any:88        ...89 90    def __getitem__(self, name: str) -> Any:91        return {92            "inline": self.inline,93            "block": self.block,94            "core": self.core,95            "renderer": self.renderer,96        }[name]97 98    def set(self, options: OptionsType) -> None:99        """Set parser options (in the same format as in constructor).100        Probably, you will never need it, but you can change options after constructor call.101 102        __Note:__ To achieve the best possible performance, don't modify a103        `markdown-it` instance options on the fly. If you need multiple configurations104        it's best to create multiple instances and initialize each with separate config.105        """106        self.options = OptionsDict(options)107 108    def configure(109        self, presets: str | PresetType, options_update: Mapping[str, Any] | None = None110    ) -> MarkdownIt:111        """Batch load of all options and component settings.112        This is an internal method, and you probably will not need it.113        But if you will - see available presets and data structure114        [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)115 116        We strongly recommend to use presets instead of direct config loads.117        That will give better compatibility with next versions.118        """119        if isinstance(presets, str):120            if presets not in _PRESETS:121                raise KeyError(f"Wrong `markdown-it` preset '{presets}', check name")122            config = _PRESETS[presets]123        else:124            config = presets125 126        if not config:127            raise ValueError("Wrong `markdown-it` config, can't be empty")128 129        options = config.get("options", {}) or {}130        if options_update:131            options = {**options, **options_update}  # type: ignore132 133        self.set(options)  # type: ignore134 135        if "components" in config:136            for name, component in config["components"].items():137                rules = component.get("rules", None)138                if rules:139                    self[name].ruler.enableOnly(rules)140                rules2 = component.get("rules2", None)141                if rules2:142                    self[name].ruler2.enableOnly(rules2)143 144        return self145 146    def get_all_rules(self) -> dict[str, list[str]]:147        """Return the names of all active rules."""148        rules = {149            chain: self[chain].ruler.get_all_rules()150            for chain in ["core", "block", "inline"]151        }152        rules["inline2"] = self.inline.ruler2.get_all_rules()153        return rules154 155    def get_active_rules(self) -> dict[str, list[str]]:156        """Return the names of all active rules."""157        rules = {158            chain: self[chain].ruler.get_active_rules()159            for chain in ["core", "block", "inline"]160        }161        rules["inline2"] = self.inline.ruler2.get_active_rules()162        return rules163 164    def enable(165        self, names: str | Iterable[str], ignoreInvalid: bool = False166    ) -> MarkdownIt:167        """Enable list or rules. (chainable)168 169        :param names: rule name or list of rule names to enable.170        :param ignoreInvalid: set `true` to ignore errors when rule not found.171 172        It will automatically find appropriate components,173        containing rules with given names. If rule not found, and `ignoreInvalid`174        not set - throws exception.175 176        Example::177 178            md = MarkdownIt().enable(['sub', 'sup']).disable('smartquotes')179 180        """181        result = []182 183        if isinstance(names, str):184            names = [names]185 186        for chain in ["core", "block", "inline"]:187            result.extend(self[chain].ruler.enable(names, True))188        result.extend(self.inline.ruler2.enable(names, True))189 190        missed = [name for name in names if name not in result]191        if missed and not ignoreInvalid:192            raise ValueError(f"MarkdownIt. Failed to enable unknown rule(s): {missed}")193 194        return self195 196    def disable(197        self, names: str | Iterable[str], ignoreInvalid: bool = False198    ) -> MarkdownIt:199        """The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)200 201        :param names: rule name or list of rule names to disable.202        :param ignoreInvalid: set `true` to ignore errors when rule not found.203 204        """205        result = []206 207        if isinstance(names, str):208            names = [names]209 210        for chain in ["core", "block", "inline"]:211            result.extend(self[chain].ruler.disable(names, True))212        result.extend(self.inline.ruler2.disable(names, True))213 214        missed = [name for name in names if name not in result]215        if missed and not ignoreInvalid:216            raise ValueError(f"MarkdownIt. Failed to disable unknown rule(s): {missed}")217        return self218 219    @contextmanager220    def reset_rules(self) -> Generator[None, None, None]:221        """A context manager, that will reset the current enabled rules on exit."""222        chain_rules = self.get_active_rules()223        yield224        for chain, rules in chain_rules.items():225            if chain != "inline2":226                self[chain].ruler.enableOnly(rules)227        self.inline.ruler2.enableOnly(chain_rules["inline2"])228 229    def add_render_rule(230        self, name: str, function: Callable[..., Any], fmt: str = "html"231    ) -> None:232        """Add a rule for rendering a particular Token type.233 234        Only applied when ``renderer.__output__ == fmt``235        """236        if self.renderer.__output__ == fmt:237            self.renderer.rules[name] = function.__get__(self.renderer)  # type: ignore238 239    def use(240        self, plugin: Callable[..., None], *params: Any, **options: Any241    ) -> MarkdownIt:242        """Load specified plugin with given params into current parser instance. (chainable)243 244        It's just a sugar to call `plugin(md, params)` with curring.245 246        Example::247 248            def func(tokens, idx):249                tokens[idx].content = tokens[idx].content.replace('foo', 'bar')250            md = MarkdownIt().use(plugin, 'foo_replace', 'text', func)251 252        """253        plugin(self, *params, **options)254        return self255 256    def parse(self, src: str, env: EnvType | None = None) -> list[Token]:257        """Parse the source string to a token stream258 259        :param src: source string260        :param env: environment sandbox261 262        Parse input string and return list of block tokens (special token type263        "inline" will contain list of inline tokens).264 265        `env` is used to pass data between "distributed" rules and return additional266        metadata like reference info, needed for the renderer. It also can be used to267        inject data in specific cases. Usually, you will be ok to pass `{}`,268        and then pass updated object to renderer.269        """270        env = {} if env is None else env271        if not isinstance(env, MutableMapping):272            raise TypeError(f"Input data should be a MutableMapping, not {type(env)}")273        if not isinstance(src, str):274            raise TypeError(f"Input data should be a string, not {type(src)}")275        state = StateCore(src, self, env)276        self.core.process(state)277        return state.tokens278 279    def render(self, src: str, env: EnvType | None = None) -> Any:280        """Render markdown string into html. It does all magic for you :).281 282        :param src: source string283        :param env: environment sandbox284        :returns: The output of the loaded renderer285 286        `env` can be used to inject additional metadata (`{}` by default).287        But you will not need it with high probability. See also comment288        in [[MarkdownIt.parse]].289        """290        env = {} if env is None else env291        return self.renderer.render(self.parse(src, env), self.options, env)292 293    def parseInline(self, src: str, env: EnvType | None = None) -> list[Token]:294        """The same as [[MarkdownIt.parse]] but skip all block rules.295 296        :param src: source string297        :param env: environment sandbox298 299        It returns the300        block tokens list with the single `inline` element, containing parsed inline301        tokens in `children` property. Also updates `env` object.302        """303        env = {} if env is None else env304        if not isinstance(env, MutableMapping):305            raise TypeError(f"Input data should be an MutableMapping, not {type(env)}")306        if not isinstance(src, str):307            raise TypeError(f"Input data should be a string, not {type(src)}")308        state = StateCore(src, self, env)309        state.inlineMode = True310        self.core.process(state)311        return state.tokens312 313    def renderInline(self, src: str, env: EnvType | None = None) -> Any:314        """Similar to [[MarkdownIt.render]] but for single paragraph content.315 316        :param src: source string317        :param env: environment sandbox318 319        Similar to [[MarkdownIt.render]] but for single paragraph content. Result320        will NOT be wrapped into `<p>` tags.321        """322        env = {} if env is None else env323        return self.renderer.render(self.parseInline(src, env), self.options, env)324 325    # link methods326 327    def validateLink(self, url: str) -> bool:328        """Validate if the URL link is allowed in output.329 330        This validator can prohibit more than really needed to prevent XSS.331        It's a tradeoff to keep code simple and to be secure by default.332 333        Note: the url should be normalized at this point, and existing entities decoded.334        """335        return normalize_url.validateLink(url)336 337    def normalizeLink(self, url: str) -> str:338        """Normalize destination URLs in links339 340        ::341 342            [label]:   destination   'title'343                    ^^^^^^^^^^^344        """345        return normalize_url.normalizeLink(url)346 347    def normalizeLinkText(self, link: str) -> str:348        """Normalize autolink content349 350        ::351 352            <destination>353            ~~~~~~~~~~~354        """355        return normalize_url.normalizeLinkText(link)356 
codekingpro/portable-devtools · Team Ai