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
Beispiel #3
0
 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'
Beispiel #4
0
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)
Beispiel #5
0
 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
Beispiel #6
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)
Beispiel #7
0
 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)
Beispiel #8
0
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)
Beispiel #9
0
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
Beispiel #10
0
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">&quot;Hello sphinx world&quot;</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">&quot;Hello sphinx world&quot;</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)
Beispiel #11
0
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"]
Beispiel #12
0
 def init_highlighter(self):
     self.highlighter = PygmentsBridge('html', 'sphinx',
                                       self.config.trim_doctest_flags)
Beispiel #13
0
def test_lexer_options():
    bridge = PygmentsBridge('html')
    ret = bridge.highlight_block('//comment',
                                 'php',
                                 opts={'startinline': True})
    assert '<span class="c1">//comment</span>' in ret
Beispiel #14
0
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
Beispiel #15
0
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),
)
Beispiel #16
0
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)