def test_set_formatter(): PygmentsBridge.html_formatter = MyFormatter try: bridge = PygmentsBridge('html') ret = bridge.highlight_block('foo\n', 'python') assert ret == 'foo\n' finally: PygmentsBridge.html_formatter = HtmlFormatter
def test_trim_doctest_flags(): PygmentsBridge.html_formatter = MyFormatter try: bridge = PygmentsBridge('html', trim_doctest_flags=True) ret = bridge.highlight_block('>>> 1+2 # doctest: SKIP\n3\n', 'pycon') assert ret == '>>> 1+2 \n3\n' finally: PygmentsBridge.html_formatter = HtmlFormatter
def __init__(self, builder, *args, **kwds): BaseTranslator.__init__(self, *args, **kwds) self.highlighter = PygmentsBridge('html', builder.config.pygments_style) self.no_smarty = 0 self.builder = builder self.highlightlang = 'python' self.highlightlinenothreshold = sys.maxint self.language.labels['warning'] = 'Caveat'
def _builder_inited(app: sphinx.application.Sphinx) -> None: if app.config.html_theme != "furo": return builder = app.builder assert builder.dark_highlighter is None, "this shouldn't happen." builder.dark_highlighter = PygmentsBridge("html", app.config.pygments_dark_style)
def __init__(self, builder, *args, **kwds): BaseTranslator.__init__(self, *args, **kwds) self.highlighter = PygmentsBridge('html', builder.config.pygments_style) self.no_smarty = 0 self.builder = builder self.highlightlang = 'python' self.highlightlinenothreshold = sys.maxint self.protect_literal_text = 0
def init_highlighter(self) -> None: # determine Pygments style and create the highlighter if self.config.pygments_style is not None: style = self.config.pygments_style elif self.theme: style = self.theme.get_config('theme', 'pygments_style', 'none') else: style = 'sphinx' self.highlighter = PygmentsBridge('html', style)
def init(self): # writer object is initialized in prepare_writing method self.writer = None # section numbers for headings in the currently visited document self.secnumbers = {} # figure numbers self.fignumbers = {} # currently written docname self.current_docname = None # type: unicode # sphinx highlighter, from StandaloneHTMLBuilder.init_highlighter() self.highlighter = PygmentsBridge( 'html', 'sphinx', self.config.trim_doctest_flags)
def _builder_inited(app: sphinx.application.Sphinx) -> None: if app.config.html_theme != "furo": return builders = [ "html", "singlehtml", "dirhtml", "readthedocs", "readthedocsdirhtml", "readthedocssinglehtml", "readthedocssinglehtmllocalmedia", "spelling", ] if not app.builder.format in builders: return builder = app.builder assert builder.dark_highlighter is None, "this shouldn't happen." builder.dark_highlighter = PygmentsBridge("html", app.config.pygments_dark_style)
def make_lexer(lang: str = None, code: str = None, language_lexer: Lexer = None): """Return a subclass of HighlightedCodeLexer and the language lexer class which is either deduced from lang (str) or code (str), or the class of language_lexer (pygments.lexer) instance. Params: language_lexer (pygments.Lexer): an instance of the lexer class to subclass lang (str): name of the language code (str): code to be lexed """ if not (lang or code or language_lexer): raise ArgumentError( "make_lexer() requires one of lang(str) and code(str) " "or language_lexer(pygments.Lexer) keyword arguments") # let sphinx figure out the language based on the content and/or language name if not language_lexer: language_lexer = PygmentsBridge().get_lexer(source=code, lang=lang or "guess") # get the lexer class to inherit from parent = language_lexer.__class__ # dynamically define metaclass for __repr__ meta = type(f"{parent.__name__.replace('Lexer', '')}Meta", (type(parent), ), {"__repr__": __meta_repr__}) # dynamically define lexer class klass = new_class( f"Highlighted{parent.__name__}", (HighlightedCodeLexer, parent), {"metaclass": meta}, ) klass.parent = parent return klass
def test_default_highlight(logger): bridge = PygmentsBridge('html') # default: highlights as python3 ret = bridge.highlight_block('print "Hello sphinx world"', 'default') assert ret == ('<div class="highlight"><pre><span></span><span class="nb">print</span> ' '<span class="s2">"Hello sphinx world"</span>\n</pre></div>\n') # default: fallbacks to none if highlighting failed ret = bridge.highlight_block('reST ``like`` text', 'default') assert ret == '<div class="highlight"><pre><span></span>reST ``like`` text\n</pre></div>\n' # python3: highlights as python3 ret = bridge.highlight_block('print "Hello sphinx world"', 'python3') assert ret == ('<div class="highlight"><pre><span></span><span class="nb">print</span> ' '<span class="s2">"Hello sphinx world"</span>\n</pre></div>\n') # python3: raises error if highlighting failed ret = bridge.highlight_block('reST ``like`` text', 'python3') logger.warning.assert_called_with('Could not lex literal_block as "%s". ' 'Highlighting skipped.', 'python3', type='misc', subtype='highlighting_failure', location=None)
def _get_dark_style(app: sphinx.application.Sphinx) -> Style: # number_of_hours_spent_figuring_this_out = 7 # # Hello human in the future! This next block of code needs a bit of a story, and # if you're going to touch it, remember to update the number above (or remove this # comment entirely). # # Hopefully, you know that Sphinx allows extensions and themes to add configuration # values via `app.add_config_value`. This usually lets users set those values from # `conf.py` while allowing the extension to read from it and utilise that information. # As any reasonable person who's written a Sphinx extension before, you would # expect the following to work: # # dark_style = app.config.pygments_dark_style # # Turns out, no. How dare you expect things to just work!? That stuff just returns # the default value provided when calling `app.add_config_value`. Yes, even if you # set it in `conf.py`. Why? Good question. :) # # The logic in Sphinx literally looks it up in the same mapping as what was # manipulated by `add_config_value`, and there's no other spot where that value # gets manipulated. I spent a bunch of time debugging how that class works, and... # yea, I can't figure it out. There's multiple mappings floating around and bunch # of manipulation being done for all kinds of things. # # The only place on the config object where I was able to find the user-provided # value from `conf.py` is a private variable `self._raw_config`. Those values are # supposed to get added to self.__dict__[...], and generally be accessible through # the object's custom `__getattr__`. # # Anyway, after giving up on figuring out how to file a PR to fix this upstream, I # started looking for hacky ways to get this without reaching into private # variables. That quest led to a very simple conclusion: no, you can't do that. # # So, here we are: with the only option being to reach into the guts of the beast, # and pull out the specific thing that's needed. This is obviously fragile though, # so this is written with the assumption that any changes to Sphinx's config # object's internals would correspond to the originally expected behaviour working. # This is so that when any of Sphinx's internals change, this logic would basically # fall back to the original behaviour and also print a warning, so that hopefully # someone will report this. Maybe it'll all be fixed, and I can remove this whole # hack and this giant comment. # HACK: begins here dark_style = None try: if (hasattr(app.config, "_raw_config") and isinstance(app.config._raw_config, dict) and "pygments_dark_style" in app.config._raw_config): dark_style = app.config._raw_config["pygments_dark_style"] except (AttributeError, KeyError) as e: logger.warn( ("Furo could not determine the value of `pygments_dark_style`. " "Falling back to using the value provided by Sphinx.\n" "Caused by %s"), e, ) if dark_style is None: dark_style = app.config.pygments_dark_style return PygmentsBridge("html", dark_style).formatter_args["style"]
def init_highlighter(self): self.highlighter = PygmentsBridge('html', 'sphinx', self.config.trim_doctest_flags)
def test_lexer_options(): bridge = PygmentsBridge('html') ret = bridge.highlight_block('//comment', 'php', opts={'startinline': True}) assert '<span class="c1">//comment</span>' in ret
def test_add_lexer(app, status, warning): app.add_lexer('test', MyLexer()) bridge = PygmentsBridge('html') ret = bridge.highlight_block('ab', 'test') assert '<span class="n">a</span>b' in ret
SASS_DIR = os.path.join(BASE_DIR, "sphinx_library", "scss") STATIC_DIR = CSS_DIR = os.path.join(BASE_DIR, "sphinx_library", "static") CSS_DIR = os.path.join(STATIC_DIR, "library") # -- COMPILE SCSS ------------------------------------------------------------- # Generate pygment themes into scss files. disclaimer = """/* * This file is auto-generated by ``build_sass.py``. * Edit pygments themes directly in ``sphinx_library.theme`` */ """ light_highlighter = PygmentsBridge("html", "sphinx_library.theme.LibraryLight") with open(os.path.join(SASS_DIR, "_pygments-light.scss"), "w") as f: f.write(disclaimer) f.write(light_highlighter.get_stylesheet()) dark_highlighter = PygmentsBridge("html", "sphinx_library.theme.LibraryDark") with open(os.path.join(SASS_DIR, "_pygments-dark.scss"), "w") as f: f.write(disclaimer) f.write(dark_highlighter.get_stylesheet()) # Compile theme scss. sass.compile( output_style="compressed", dirname=(SASS_DIR, CSS_DIR), )
def _builder_inited(app: sphinx.application.Sphinx) -> None: if app.config.html_theme != "furo": return # Our `main.js` file needs to be loaded as soon as possible. app.add_js_file("scripts/main.js", priority=200) # 500 is the default priority for extensions, we want this after this. app.add_css_file("styles/furo-extensions.css", priority=600) builder = app.builder assert builder.dark_highlighter is None, "this shouldn't happen." # number_of_hours_spent_figuring_this_out = 7 # # Hello human in the future! This next block of code needs a bit of a story, and # if you're going to touch it, remember to update the number above (or remove this # comment entirely). # # Hopefully, you know that Sphinx allows extensions and themes to add configuration # values via `app.add_config_value`. This usually lets users set those values from # `conf.py` while allowing the extension to read from it and utilise that information. # As any reasonable person who's written a Sphinx extension before, you would # expect the following to work: # # dark_style = app.config.pygments_dark_style # # Turns out, no. How dare you expect things to just work!? That stuff just returns # the default value provided when calling `app.add_config_value`. Yes, even if you # set it in `conf.py`. Why? Good question. :) # # The logic in Sphinx literally looks it up in the same mapping as what was # manipulated by `add_config_value`, and there's no other spot where that value # gets manipulated. I spent a bunch of time debugging how that class works, and... # yea, I can't figure it out. There's multiple mappings floating around and bunch # of manipulation being done for all kinds of things. # # The only place on the config object where I was able to find the user-provided # value from `conf.py` is a private variable `self._raw_config`. Those values are # supposed to get added to self.__dict__[...], and generally be accessible through # the object's custom `__getattr__`. # # Anyway, after giving up on figuring out how to file a PR to fix this upstream, I # started looking for hacky ways to get this without reaching into private # variables. That quest led to a very simple conclusion: no, you can't do that. # # So, here we are: with the only option being to reach into the guts of the beast, # and pull out the specific thing that's needed. This is obviously fragile though, # so this is written with the assumption that any changes to Sphinx's config # object's internals would correspond to the originally expected behaviour working. # This is so that when any of Sphinx's internals change, this logic would basically # fall back to the original behaviour and also print a warning, so that hopefully # someone will report this. Maybe it'll all be fixed, and I can remove this whole # hack and this giant comment. # # But wait, this hack actually has another layer to it. # # This whole setup depends on an internal implementation detail in Sphinx -- that # it "adds" the `pygments_dark.css` file for inclusion in output, at a different # point than where it is generates the file. The dark syntax highlighting mechanism # of this theme depends on that fact -- we don't actually set `pygments_dark_style` # in our theme.conf file. # # Instead, we stick our filthy monkey hands into Sphinx's builder, to patch the # builder to generate the `pygments_dark.css` file as if this theme actually sets # `pygments_dark_style`. This results in Sphinx generating the file without # injecting a custom CSS file for it. Then, we include that stylesheet in our HTML # via a hand-crafted <link> tag. There's 2 benefits to this approach: (1) it works, # (2) we can, at some point in the future, pivot to a different strategy for # including the dark mode syntax highlighting styles. # HACK: begins here dark_style = None try: if (hasattr(app.config, "_raw_config") and isinstance(app.config._raw_config, dict) and "pygments_dark_style" in app.config._raw_config): dark_style = app.config._raw_config["pygments_dark_style"] except (AttributeError, KeyError) as e: logger.warn( ("Furo could not determine the value of `pygments_dark_style`. " "Falling back to using the value provided by Sphinx.\n" "Caused by %s"), e, ) if dark_style is None: dark_style = app.config.pygments_dark_style builder.dark_highlighter = PygmentsBridge("html", dark_style)