diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 9ddc9e3a3..36c96c206 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -13,11 +13,11 @@ jobs: - 'ubuntu-latest' - 'macos-latest' python-version: - - '3.8' - '3.9' - '3.10' - '3.11' - '3.12' + - '3.13' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 000000000..5ca6081db --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [master] + +jobs: + pre-commit: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..10524f131 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.1 + hooks: + - id: ruff-format + - id: ruff + args: [--fix] diff --git a/conf.py b/conf.py index 991622a78..01719086c 100644 --- a/conf.py +++ b/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Zeek documentation build configuration file, created by sphinx-quickstart # @@ -10,40 +9,47 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys extensions = [] # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('ext')) +sys.path.insert(0, os.path.abspath("ext")) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions += ['zeek', 'sphinx.ext.todo', 'zeek_pygments', 'spicy-pygments', 'literal-emph'] -extensions += ['sphinx.ext.extlinks'] +extensions += [ + "zeek", + "sphinx.ext.todo", + "zeek_pygments", + "spicy-pygments", + "literal-emph", + "sphinx.ext.extlinks", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Zeek' -copyright = u'2019-2023, The Zeek Project' +project = "Zeek" +copyright = "2019-2023, The Zeek Project" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -52,25 +58,27 @@ # The short X.Y version. # -version = u"source" +version = "source" try: # Use the actual Zeek version if available - with open('../VERSION', 'r') as f: + with open("../VERSION") as f: version = f.readline().strip() except: try: - import git import re - repo = git.Repo(os.path.abspath('.')) - version = u"git/master" + import git + + repo = git.Repo(os.path.abspath(".")) + version = "git/master" - version_tag_re = r'v\d+\.\d+(\.\d+)?' - version_tags = [t for t in repo.tags if - t.commit == repo.head.commit and - re.match(version_tag_re, str(t)) - ] + version_tag_re = r"v\d+\.\d+(\.\d+)?" + version_tags = [ + t + for t in repo.tags + if t.commit == repo.head.commit and re.match(version_tag_re, str(t)) + ] # Note: sorting by tag date doesn't necessarily give correct # order in terms of version numbers, but doubtful that will ever be # a problem (if we ever do re-tag an old version number on a given @@ -99,49 +107,49 @@ # `release/.*`, and the tip of those branches will always be in sync with # auto-generated content by simply having `zeek/ci/update-zeekygen-docs.sh` # change this to `release/.*` when needed. -zeek_code_version = 'master' -zeek_code_url = f'https://github.com/zeek/zeek/blob/{zeek_code_version}' +zeek_code_version = "master" +zeek_code_url = f"https://github.com/zeek/zeek/blob/{zeek_code_version}" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -today_fmt = '%B %d, %Y' +today_fmt = "%B %d, %Y" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = [".#*", 'script-reference/autogenerated-*'] +exclude_patterns = [".#*", "script-reference/autogenerated-*"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = True # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" -highlight_language = 'none' +highlight_language = "none" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Set canonical URL from the Read the Docs Domain html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") @@ -152,148 +160,147 @@ html_context = {} html_context["READTHEDOCS"] = True -html_last_updated_fmt = '%B %d, %Y' +html_last_updated_fmt = "%B %d, %Y" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - 'analytics_id': 'UA-144186885-1', - 'collapse_navigation': False, - 'display_version': True, + "analytics_id": "UA-144186885-1", + "collapse_navigation": False, + "display_version": True, "style_external_links": True, } # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v Documentation". -html_title = f'Book of Zeek ({release})' +html_title = f"Book of Zeek ({release})" # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = 'images/zeek-logo-sidebar.png' +html_logo = "images/zeek-logo-sidebar.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = 'images/zeek-favicon.ico' +html_favicon = "images/zeek-favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] + def setup(app): app.add_css_file("theme_overrides.css") from sphinx.highlighting import lexers from zeek_pygments import ZeekLexer - lexers['zeek'] = ZeekLexer() - app.add_config_value('zeek-code-url', zeek_code_url, 'env') + + lexers["zeek"] = ZeekLexer() + app.add_config_value("zeek-code-url", zeek_code_url, "env") + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = { +# html_sidebars = { #'**': ['localtoc.html', 'sourcelink.html', 'searchbox.html'], -#} +# } # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'zeek-docs' +htmlhelp_basename = "zeek-docs" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Zeek.tex', u'Zeek Documentation', - u'The Zeek Project', 'manual'), + ("index", "Zeek.tex", "Zeek Documentation", "The Zeek Project", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'zeek', u'Zeek Documentation', - [u'The Zeek Project'], 1) -] +man_pages = [("index", "zeek", "Zeek Documentation", ["The Zeek Project"], 1)] # -- Options for todo plugin -------------------------------------------- -todo_include_todos=True +todo_include_todos = True extlinks = { - 'slacklink': ('https://zeek.org/slack%s', None), - 'discourselink': ('https://community.zeek.org/%s', None), - 'spicylink': ('https://docs.zeek.org/projects/spicy/en/latest/%s', None), + "slacklink": ("https://zeek.org/slack%s", None), + "discourselink": ("https://community.zeek.org/%s", None), + "spicylink": ("https://docs.zeek.org/projects/spicy/en/latest/%s", None), } extlinks_detect_hardcoded_links = True diff --git a/ext/literal-emph.py b/ext/literal-emph.py index 0df8a5823..0d8768174 100644 --- a/ext/literal-emph.py +++ b/ext/literal-emph.py @@ -1,5 +1,6 @@ -import sphinx import re + +import sphinx from docutils import nodes # This extension adds a 'literal-emph' directive that operates the same @@ -9,30 +10,32 @@ # Adding " (no-emph)" to the end of a line within the 'literal-emph' content # disables substitutions for that line. + class LiteralEmphNode(nodes.General, nodes.Element): pass + class LiteralEmph(sphinx.directives.code.CodeBlock): def run(self): node = LiteralEmphNode() node += super().run() return [node] + def visit_litemph_node(self, node): pass + def depart_litemph_node(self, node): text = self.body[-1] - text = re.sub(r'\*\*(.*?)\*\*(?!.* \(no-emph\)\n)', - r'\1', - text) - text = re.sub(r'(.*) \(no-emph\)\n', r'\1\n', text) + text = re.sub(r"\*\*(.*?)\*\*(?!.* \(no-emph\)\n)", r"\1", text) + text = re.sub(r"(.*) \(no-emph\)\n", r"\1\n", text) self.body[-1] = text + def setup(app): app.add_directive("literal-emph", LiteralEmph) - app.add_node(LiteralEmphNode, - html=(visit_litemph_node, depart_litemph_node)) + app.add_node(LiteralEmphNode, html=(visit_litemph_node, depart_litemph_node)) return { - 'parallel_read_safe': True, + "parallel_read_safe": True, } diff --git a/ext/spicy-pygments.py b/ext/spicy-pygments.py index 882833573..9822c4737 100644 --- a/ext/spicy-pygments.py +++ b/ext/spicy-pygments.py @@ -1,6 +1,6 @@ # Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. -from pygments.lexer import RegexLexer, include, words, bygroups +from pygments.lexer import RegexLexer, bygroups, include, words from pygments.token import ( Comment, Keyword, diff --git a/ext/zeek.py b/ext/zeek.py index cf1961943..4bceb6df1 100644 --- a/ext/zeek.py +++ b/ext/zeek.py @@ -1,52 +1,54 @@ """ - The Zeek domain for Sphinx. +The Zeek domain for Sphinx. """ + def setup(Sphinx): Sphinx.add_domain(ZeekDomain) Sphinx.add_node(see) - Sphinx.add_directive_to_domain('zeek', 'see', SeeDirective) - Sphinx.connect('doctree-resolved', process_see_nodes) + Sphinx.add_directive_to_domain("zeek", "see", SeeDirective) + Sphinx.connect("doctree-resolved", process_see_nodes) return { - 'parallel_read_safe': True, + "parallel_read_safe": True, } -from sphinx import addnodes -from sphinx.domains import Domain, ObjType, Index -from sphinx.locale import _ + +from sphinx import addnodes, version_info from sphinx.directives import ObjectDescription +from sphinx.domains import Domain, Index, ObjType +from sphinx.locale import _ from sphinx.roles import XRefRole -from sphinx.util import docfields +from sphinx.util import docfields, logging from sphinx.util.nodes import make_refnode -from sphinx import version_info -from sphinx.util import logging logger = logging.getLogger(__name__) from docutils import nodes -from docutils.parsers.rst import Directive -from docutils.parsers.rst import directives -from docutils.parsers.rst.roles import set_classes +from docutils.parsers.rst import Directive, directives + class see(nodes.General, nodes.Element): refs = [] + class SeeDirective(Directive): has_content = True def run(self): - n = see('') + n = see("") n.refs = " ".join(self.content).split() return [n] + # Wrapper for creating a tuple for index nodes, staying backwards # compatible to Sphinx < 1.4: def make_index_tuple(indextype, indexentry, targetname, targetname2): - if version_info >= (1, 4, 0, '', 0): + if version_info >= (1, 4, 0, "", 0): return (indextype, indexentry, targetname, targetname2, None) else: return (indextype, indexentry, targetname, targetname2) + def process_see_nodes(app, doctree, fromdocname): for node in doctree.traverse(see): content = [] @@ -55,24 +57,27 @@ def process_see_nodes(app, doctree, fromdocname): for name in node.refs: join_str = " " if name != node.refs[0]: - join_str = ", " - link_txt = join_str + name; - - if name not in app.env.domaindata['zeek']['idtypes']: + join_str = ", " + link_txt = join_str + name + if name not in app.env.domaindata["zeek"]["idtypes"]: # Just create the text and issue warning - logger.warning('%s: unknown target for ".. zeek:see:: %s"', fromdocname, name, location=node) + logger.warning( + '%s: unknown target for ".. zeek:see:: %s"', + fromdocname, + name, + location=node, + ) para += nodes.Text(link_txt, link_txt) else: # Create a reference - typ = app.env.domaindata['zeek']['idtypes'][name] - todocname = app.env.domaindata['zeek']['objects'][(typ, name)] - - newnode = nodes.reference('', '') - innernode = nodes.literal(_(name), _(name), classes=['xref']) - newnode['refdocname'] = todocname - newnode['refuri'] = app.builder.get_relative_uri( - fromdocname, todocname) - newnode['refuri'] += '#' + typ + '-' + name + typ = app.env.domaindata["zeek"]["idtypes"][name] + todocname = app.env.domaindata["zeek"]["objects"][(typ, name)] + + newnode = nodes.reference("", "") + innernode = nodes.literal(_(name), _(name), classes=["xref"]) + newnode["refdocname"] = todocname + newnode["refuri"] = app.builder.get_relative_uri(fromdocname, todocname) + newnode["refuri"] += "#" + typ + "-" + name newnode.append(innernode) para += nodes.Text(join_str, join_str) para += newnode @@ -80,56 +85,61 @@ def process_see_nodes(app, doctree, fromdocname): content.append(para) node.replace_self(content) + class ZeekGeneric(ObjectDescription): - option_spec = { - 'source-code': directives.unchanged - } + option_spec = {"source-code": directives.unchanged} def __init__(self, *args, **kwargs): super(ObjectDescription, self).__init__(*args, **kwargs) options = args[2] self.code_url = None - if 'source-code' in options and 'zeek-code-url' in self.env.config: - base_url = self.env.config['zeek-code-url'] - path, start, end = options['source-code'].split() - path_parts = path.split('/') + if "source-code" in options and "zeek-code-url" in self.env.config: + base_url = self.env.config["zeek-code-url"] + path, start, end = options["source-code"].split() + path_parts = path.split("/") file_name = path_parts[-1] # Don't have anything to link to for BIFs - if not file_name.endswith('.bif.zeek'): - self.code_url = f'{base_url}/scripts/{path}#L{start}-L{end}' + if not file_name.endswith(".bif.zeek"): + self.code_url = f"{base_url}/scripts/{path}#L{start}-L{end}" def get_obj_name(self): return self.objtype def update_type_map(self, idname): - if 'idtypes' not in self.env.domaindata['zeek']: - self.env.domaindata['zeek']['idtypes'] = {} - self.env.domaindata['zeek']['idtypes'][idname] = self.get_obj_name() + if "idtypes" not in self.env.domaindata["zeek"]: + self.env.domaindata["zeek"]["idtypes"] = {} + self.env.domaindata["zeek"]["idtypes"][idname] = self.get_obj_name() def process_signode(self, name, sig, signode, targetname): - signode['names'].append(targetname) - signode['ids'].append(targetname) - signode['first'] = (not self.names) + signode["names"].append(targetname) + signode["ids"].append(targetname) + signode["first"] = not self.names self.state.document.note_explicit_target(signode) def add_target_and_index(self, name, sig, signode): - targetname = self.get_obj_name() + '-' + name + targetname = self.get_obj_name() + "-" + name if targetname not in self.state.document.ids: self.process_signode(name, sig, signode, targetname) - objects = self.env.domaindata['zeek']['objects'] + objects = self.env.domaindata["zeek"]["objects"] key = (self.get_obj_name(), name) - if ( key in objects and self.get_obj_name() != "id" and - self.get_obj_name() != "type" ): - logger.warning('%s: duplicate description of %s %s, ' % - (self.env.docname, self.get_obj_name(), name) + - 'other instance in ' + - self.env.doc2path(objects[key]), - self.lineno) + if ( + key in objects + and self.get_obj_name() != "id" + and self.get_obj_name() != "type" + ): + logger.warning( + "%s: duplicate description of %s %s, other instance in %s", + self.env.docname, + self.get_obj_name(), + name, + self.env.doc2path(objects[key]), + self.lineno, + ) objects[key] = self.env.docname self.update_type_map(name) @@ -137,18 +147,18 @@ def add_target_and_index(self, name, sig, signode): indextext = self.get_index_text(name) if indextext: - self.indexnode['entries'].append(make_index_tuple('single', - indextext, targetname, - targetname)) + self.indexnode["entries"].append( + make_index_tuple("single", indextext, targetname, targetname) + ) def get_index_text(self, name): - return _('%s (%s)') % (name, self.get_obj_name()) + return _("%s (%s)") % (name, self.get_obj_name()) def handle_signature(self, sig, signode): if self.code_url: - signode += nodes.reference(sig, sig, - refuri=self.code_url, - reftitle='View Source Code') + signode += nodes.reference( + sig, sig, refuri=self.code_url, reftitle="View Source Code" + ) # Could embed snippets directly, but would probably want to clean # up how it's done: don't use an external script, figure out why @@ -175,65 +185,73 @@ def handle_signature(self, sig, signode): return sig + class ZeekNamespace(ZeekGeneric): def add_target_and_index(self, name, sig, signode): - targetname = self.get_obj_name() + '-' + name + targetname = self.get_obj_name() + "-" + name if targetname not in self.state.document.ids: - signode['names'].append(targetname) - signode['ids'].append(targetname) - signode['first'] = (not self.names) + signode["names"].append(targetname) + signode["ids"].append(targetname) + signode["first"] = not self.names self.state.document.note_explicit_target(signode) - objects = self.env.domaindata['zeek']['objects'] + objects = self.env.domaindata["zeek"]["objects"] key = (self.get_obj_name(), name) objects[key] = self.env.docname self.update_type_map(name) indextext = self.get_index_text(name) - self.indexnode['entries'].append(make_index_tuple('single', indextext, - targetname, targetname)) - self.indexnode['entries'].append(make_index_tuple('single', - "namespaces; %s" % (sig), - targetname, targetname)) + self.indexnode["entries"].append( + make_index_tuple("single", indextext, targetname, targetname) + ) + self.indexnode["entries"].append( + make_index_tuple("single", f"namespaces; {sig}", targetname, targetname) + ) def get_index_text(self, name): - return _('%s (namespace); %s') % (name, self.env.docname) + return _("%s (namespace); %s") % (name, self.env.docname) def handle_signature(self, sig, signode): signode += addnodes.desc_name("", sig) return sig + class ZeekEnum(ZeekGeneric): def add_target_and_index(self, name, sig, signode): - targetname = self.get_obj_name() + '-' + name + targetname = self.get_obj_name() + "-" + name if targetname not in self.state.document.ids: self.process_signode(name, sig, signode, targetname) - objects = self.env.domaindata['zeek']['objects'] + objects = self.env.domaindata["zeek"]["objects"] key = (self.get_obj_name(), name) objects[key] = self.env.docname self.update_type_map(name) - indextext = self.get_index_text(name) - #self.indexnode['entries'].append(make_index_tuple('single', indextext, + # indextext = self.get_index_text(name) + # self.indexnode['entries'].append(make_index_tuple('single', indextext, # targetname, targetname)) m = sig.split() if len(m) < 2: - logger.warning("%s: zeek:enum directive missing argument(s)", self.env.docname) + logger.warning( + "%s: zeek:enum directive missing argument(s)", self.env.docname + ) return if m[1] == "Notice::Type": - if 'notices' not in self.env.domaindata['zeek']: - self.env.domaindata['zeek']['notices'] = [] - self.env.domaindata['zeek']['notices'].append( - (m[0], self.env.docname, targetname)) - - self.indexnode['entries'].append(make_index_tuple('single', - "%s (enum values); %s" % (m[1], m[0]), - targetname, targetname)) + if "notices" not in self.env.domaindata["zeek"]: + self.env.domaindata["zeek"]["notices"] = [] + self.env.domaindata["zeek"]["notices"].append( + (m[0], self.env.docname, targetname) + ) + + self.indexnode["entries"].append( + make_index_tuple( + "single", f"{m[1]} (enum values); {m[0]}", targetname, targetname + ) + ) def handle_signature(self, sig, signode): m = sig.split() @@ -241,15 +259,15 @@ def handle_signature(self, sig, signode): signode += addnodes.desc_name("", name) return name + class ZeekParamField(docfields.GroupedField): has_arg = True is_typed = True + class ZeekIdentifier(ZeekGeneric): - zeek_param_field = ZeekParamField('param', label='Parameters', can_collapse=True) - field_type_map = { - 'param': (zeek_param_field, False) - } + zeek_param_field = ZeekParamField("param", label="Parameters", can_collapse=True) + field_type_map = {"param": (zeek_param_field, False)} def get_index_text(self, name): return name @@ -257,6 +275,7 @@ def get_index_text(self, name): def get_field_type_map(self): return self.field_type_map + class ZeekNative(ZeekGeneric): def handle_signature(self, sig, signode): # The run() method is overridden to drop signode anyway in favor of @@ -272,88 +291,93 @@ def process_signode(self, name, sig, signode, targetname): def run(self): ns = super().run() index_node = ns[0] - desc_sig_node = ns[1] - target_id = self.get_obj_name() + '-' + self.native_name - target_node = nodes.target('', '', ids=[target_id]) + target_id = self.get_obj_name() + "-" + self.native_name + target_node = nodes.target("", "", ids=[target_id]) self.state.document.note_explicit_target(target_node) # Replace the description node from Sphinx with a simple target node return [index_node, target_node] + class ZeekKeyword(ZeekNative): def get_index_text(self, name): - if name and name[0] == '@': - return _('%s (directive)') % (name) + if name and name[0] == "@": + return _("%s (directive)") % (name) else: - return _('%s (keyword)') % (name) + return _("%s (keyword)") % (name) + class ZeekAttribute(ZeekNative): def get_index_text(self, name): - return _('%s (attribute)') % (name) + return _("%s (attribute)") % (name) + class ZeekNativeType(ZeekNative): def get_obj_name(self): # As opposed to using 'native-type', just imitate 'type'. - return 'type' + return "type" + class ZeekNotices(Index): """ Index subclass to provide the Zeek notices index. """ - name = 'noticeindex' - localname = _('Zeek Notice Index') - shortname = _('notices') + name = "noticeindex" + localname = _("Zeek Notice Index") + shortname = _("notices") def generate(self, docnames=None): content = {} - if 'notices' not in self.domain.env.domaindata['zeek']: + if "notices" not in self.domain.env.domaindata["zeek"]: return content, False - for n in self.domain.env.domaindata['zeek']['notices']: + for n in self.domain.env.domaindata["zeek"]["notices"]: modname = n[0].split("::")[0] entries = content.setdefault(modname, []) - entries.append([n[0], 0, n[1], n[2], '', '', '']) + entries.append([n[0], 0, n[1], n[2], "", "", ""]) content = sorted(content.items()) return content, False + class ZeekDomain(Domain): """Zeek domain.""" - name = 'zeek' - label = 'Zeek' + + name = "zeek" + label = "Zeek" object_types = { - 'type': ObjType(_('type'), 'type'), - 'native-type': ObjType(_('type'), 'type'), - 'namespace': ObjType(_('namespace'), 'namespace'), - 'id': ObjType(_('id'), 'id'), - 'keyword': ObjType(_('keyword'), 'keyword'), - 'enum': ObjType(_('enum'), 'enum'), - 'attr': ObjType(_('attr'), 'attr'), + "type": ObjType(_("type"), "type"), + "native-type": ObjType(_("type"), "type"), + "namespace": ObjType(_("namespace"), "namespace"), + "id": ObjType(_("id"), "id"), + "keyword": ObjType(_("keyword"), "keyword"), + "enum": ObjType(_("enum"), "enum"), + "attr": ObjType(_("attr"), "attr"), } directives = { - 'type': ZeekGeneric, - 'native-type': ZeekNativeType, - 'namespace': ZeekNamespace, - 'id': ZeekIdentifier, - 'keyword': ZeekKeyword, - 'enum': ZeekEnum, - 'attr': ZeekAttribute, + "type": ZeekGeneric, + "native-type": ZeekNativeType, + "namespace": ZeekNamespace, + "id": ZeekIdentifier, + "keyword": ZeekKeyword, + "enum": ZeekEnum, + "attr": ZeekAttribute, } roles = { - 'type': XRefRole(), - 'namespace': XRefRole(), - 'id': XRefRole(), - 'keyword': XRefRole(), - 'enum': XRefRole(), - 'attr': XRefRole(), - 'see': XRefRole(), + "type": XRefRole(), + "namespace": XRefRole(), + "id": XRefRole(), + "keyword": XRefRole(), + "enum": XRefRole(), + "attr": XRefRole(), + "see": XRefRole(), } indices = [ @@ -361,47 +385,61 @@ class ZeekDomain(Domain): ] initial_data = { - 'objects': {}, # fullname -> docname, objtype + "objects": {}, # fullname -> docname, objtype } def clear_doc(self, docname): to_delete = [] - for (typ, name), doc in self.data['objects'].items(): + for (typ, name), doc in self.data["objects"].items(): if doc == docname: to_delete.append((typ, name)) - for (typ, name) in to_delete: - del self.data['objects'][typ, name] + for typ, name in to_delete: + del self.data["objects"][typ, name] - def resolve_xref(self, env, fromdocname, builder, typ, target, node, - contnode): - objects = self.data['objects'] + def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): + objects = self.data["objects"] if typ == "see": - if target not in self.data['idtypes']: - logger.warning('%s: unknown target for ":zeek:see:`%s`"', fromdocname, target) + if target not in self.data["idtypes"]: + logger.warning( + '%s: unknown target for ":zeek:see:`%s`"', fromdocname, target + ) return [] - objtype = self.data['idtypes'][target] - return make_refnode(builder, fromdocname, - objects[objtype, target], - objtype + '-' + target, - contnode, target + ' ' + objtype) + objtype = self.data["idtypes"][target] + return make_refnode( + builder, + fromdocname, + objects[objtype, target], + objtype + "-" + target, + contnode, + target + " " + objtype, + ) else: objtypes = self.objtypes_for_role(typ) for objtype in objtypes: if (objtype, target) in objects: - return make_refnode(builder, fromdocname, - objects[objtype, target], - objtype + '-' + target, - contnode, target + ' ' + objtype) + return make_refnode( + builder, + fromdocname, + objects[objtype, target], + objtype + "-" + target, + contnode, + target + " " + objtype, + ) else: - logger.warning('%s: unknown target for ":zeek:%s:`%s`"', fromdocname, typ, target) + logger.warning( + '%s: unknown target for ":zeek:%s:`%s`"', + fromdocname, + typ, + target, + ) def get_objects(self): - for (typ, name), docname in self.data['objects'].items(): - yield name, name, typ, docname, typ + '-' + name, 1 + for (typ, name), docname in self.data["objects"].items(): + yield name, name, typ, docname, typ + "-" + name, 1 def merge_domaindata(self, docnames, otherdata): """ @@ -420,12 +458,11 @@ def merge_domaindata(self, docnames, otherdata): Out[4]: True """ - zeek_data = self.env.domaindata['zeek'] for target, data in otherdata.items(): - if target == 'version': + if target == "version": continue - elif hasattr(data, 'items'): - target_data = self.env.domaindata['zeek'].setdefault(target, {}) + elif hasattr(data, "items"): + target_data = self.env.domaindata["zeek"].setdefault(target, {}) # Iterate manually over the elements for debugging for k, v in data.items(): @@ -433,9 +470,9 @@ def merge_domaindata(self, docnames, otherdata): # to filenames that sort higher. See comment above. if k not in target_data or v > target_data[k]: target_data[k] = v - elif hasattr(data, 'extend'): + elif hasattr(data, "extend"): # notices are a list - target_data = self.env.domaindata['zeek'].setdefault(target, []) + target_data = self.env.domaindata["zeek"].setdefault(target, []) target_data.extend(data) else: raise NotImplementedError(target, type(data)) diff --git a/ext/zeek_pygments.py b/ext/zeek_pygments.py index a85f0ec7e..aaa9f449f 100644 --- a/ext/zeek_pygments.py +++ b/ext/zeek_pygments.py @@ -1,165 +1,247 @@ -from pygments.lexer import RegexLexer, bygroups, include, words, bygroups -from pygments.token import * +from pygments.lexer import RegexLexer, bygroups, include, words +from pygments.token import ( + Comment, + Keyword, + Literal, + Name, + Number, + Operator, + Punctuation, + String, + Text, +) + def setup(Sphinx): return { - 'parallel_read_safe': True, + "parallel_read_safe": True, } + class ZeekLexer(RegexLexer): """ For `Zeek `_ scripts. .. versionadded:: 2.5 """ - name = 'Zeek' - aliases = ['zeek'] - filenames = ['*.zeek'] - _hex = r'[0-9a-fA-F]' - _float = r'((\d*\.?\d+)|(\d+\.?\d*))([eE][-+]?\d+)?' - _h = r'[A-Za-z0-9][-A-Za-z0-9]*' - - tokens = { - 'root': [ - include('whitespace'), - include('comments'), - include('directives'), - include('attributes'), - include('types'), - include('keywords'), - include('literals'), - include('operators'), - include('punctuation'), + name = "Zeek" + aliases = ["zeek"] + filenames = ["*.zeek"] - (r'\b((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)(?=\s*\()', - Name.Function), + _hex = r"[0-9a-fA-F]" + _float = r"((\d*\.?\d+)|(\d+\.?\d*))([eE][-+]?\d+)?" + _h = r"[A-Za-z0-9][-A-Za-z0-9]*" - include('identifiers'), + tokens = { + "root": [ + include("whitespace"), + include("comments"), + include("directives"), + include("attributes"), + include("types"), + include("keywords"), + include("literals"), + include("operators"), + include("punctuation"), + ( + r"\b((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)(?=\s*\()", + Name.Function, + ), + include("identifiers"), ], - - 'whitespace': [ - (r'\n', Text), - (r'\s+', Text), - (r'\\\n', Text), + "whitespace": [ + (r"\n", Text), + (r"\s+", Text), + (r"\\\n", Text), ], - - 'comments': [ - (r'#.*$', Comment), + "comments": [ + (r"#.*$", Comment), ], - - 'directives': [ - (r'(@(load-plugin|load-sigs|load|unload))\b.*$', Comment.Preproc), - (r'(@(DEBUG|DIR|FILENAME|deprecated|if|ifdef|ifndef|else|endif))\b', Comment.Preproc), - (r'(@prefixes)\s*(\+?=).*$', Comment.Preproc), + "directives": [ + (r"(@(load-plugin|load-sigs|load|unload))\b.*$", Comment.Preproc), + ( + r"(@(DEBUG|DIR|FILENAME|deprecated|if|ifdef|ifndef|else|endif))\b", + Comment.Preproc, + ), + (r"(@prefixes)\s*(\+?=).*$", Comment.Preproc), ], - - 'attributes': [ - (words(('redef', 'priority', 'log', 'optional', 'default', 'add_func', - 'delete_func', 'expire_func', 'read_expire', 'write_expire', - 'create_expire', 'synchronized', 'persistent', 'rotate_interval', - 'rotate_size', 'encrypt', 'raw_output', 'mergeable', 'error_handler', - 'broker_allow_complex_type', 'is_assigned', 'is_used', - 'type_column', 'deprecated', 'on_change', 'backend', 'broker_store'), - prefix=r'&', suffix=r'\b'), - Keyword.Pseudo), + "attributes": [ + ( + words( + ( + "redef", + "priority", + "log", + "optional", + "default", + "add_func", + "delete_func", + "expire_func", + "read_expire", + "write_expire", + "create_expire", + "synchronized", + "persistent", + "rotate_interval", + "rotate_size", + "encrypt", + "raw_output", + "mergeable", + "error_handler", + "broker_allow_complex_type", + "is_assigned", + "is_used", + "type_column", + "deprecated", + "on_change", + "backend", + "broker_store", + ), + prefix=r"&", + suffix=r"\b", + ), + Keyword.Pseudo, + ), ], - - 'types': [ - (words(('any', - 'enum', 'record', 'set', 'table', 'vector', - 'function', 'hook', 'event', - 'addr', 'bool', 'count', 'double', 'file', 'int', 'interval', - 'pattern', 'port', 'string', 'subnet', 'time'), - prefix=r'\b', suffix=r'\b'), - Keyword.Type), - - (r'\b(opaque)(\s+)(of)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)\b', - bygroups(Keyword.Type, Text, Operator.Word, Text, Keyword.Type)), - - (r'\b(type)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)(\s*)(:)(\s*)\b(record|enum)\b', - bygroups(Keyword, Text, Name.Class, Text, Operator, Text, Keyword.Type)), - - (r'\b(type)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)(\s*)(:)', - bygroups(Keyword, Text, Name, Text, Operator)), - - (r'\b(redef)(\s+)(record|enum)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)\b', - bygroups(Keyword, Text, Keyword.Type, Text, Name.Class)), + "types": [ + ( + words( + ( + "any", + "enum", + "record", + "set", + "table", + "vector", + "function", + "hook", + "event", + "addr", + "bool", + "count", + "double", + "file", + "int", + "interval", + "pattern", + "port", + "string", + "subnet", + "time", + ), + prefix=r"\b", + suffix=r"\b", + ), + Keyword.Type, + ), + ( + r"\b(opaque)(\s+)(of)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)\b", + bygroups(Keyword.Type, Text, Operator.Word, Text, Keyword.Type), + ), + ( + r"\b(type)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)(\s*)(:)(\s*)\b(record|enum)\b", + bygroups(Keyword, Text, Name.Class, Text, Operator, Text, Keyword.Type), + ), + ( + r"\b(type)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)(\s*)(:)", + bygroups(Keyword, Text, Name, Text, Operator), + ), + ( + r"\b(redef)(\s+)(record|enum)(\s+)((?:[A-Za-z_][A-Za-z_0-9]*)(?:::(?:[A-Za-z_][A-Za-z_0-9]*))*)\b", + bygroups(Keyword, Text, Keyword.Type, Text, Name.Class), + ), ], - - 'keywords': [ - (words(('redef', 'export', 'if', 'else', 'for', 'while', - 'return', 'break', 'next', 'continue', 'fallthrough', - 'switch', 'default', 'case', - 'add', 'delete', 'copy', - 'when', 'timeout', 'schedule'), - prefix=r'\b', suffix=r'\b'), - Keyword), - (r'\b(print)\b', Keyword), - (r'\b(global|local|const|option)\b', Keyword.Declaration), - (r'\b(module)(\s+)(([A-Za-z_][A-Za-z_0-9]*)(?:::([A-Za-z_][A-Za-z_0-9]*))*)\b', - bygroups(Keyword.Namespace, Text, Name.Namespace)), + "keywords": [ + ( + words( + ( + "redef", + "export", + "if", + "else", + "for", + "while", + "return", + "break", + "next", + "continue", + "fallthrough", + "switch", + "default", + "case", + "add", + "delete", + "copy", + "when", + "timeout", + "schedule", + ), + prefix=r"\b", + suffix=r"\b", + ), + Keyword, + ), + (r"\b(print)\b", Keyword), + (r"\b(global|local|const|option)\b", Keyword.Declaration), + ( + r"\b(module)(\s+)(([A-Za-z_][A-Za-z_0-9]*)(?:::([A-Za-z_][A-Za-z_0-9]*))*)\b", + bygroups(Keyword.Namespace, Text, Name.Namespace), + ), ], - - 'literals': [ - (r'"', String, 'string'), - + "literals": [ + (r'"', String, "string"), # Not the greatest match for patterns, but generally helps # disambiguate between start of a pattern and just a division # operator. - (r'/(?=.*/)', String.Regex, 'regex'), - - (r'\b(T|F)\b', Keyword.Constant), - + (r"/(?=.*/)", String.Regex, "regex"), + (r"\b(T|F)\b", Keyword.Constant), # Port - (r'\b\d{1,5}/(udp|tcp|icmp|unknown)\b', Number), - + (r"\b\d{1,5}/(udp|tcp|icmp|unknown)\b", Number), # IPv4 Address - (r'\b(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\b', Number), - + ( + r"\b(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\b", + Number, + ), # IPv6 Address (not 100% correct: that takes more effort) - (r'\[([0-9a-fA-F]{0,4}:){2,7}([0-9a-fA-F]{0,4})?((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2}))?\]', Number), - + ( + r"\[([0-9a-fA-F]{0,4}:){2,7}([0-9a-fA-F]{0,4})?((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[0-9]{1,2}))?\]", + Number, + ), # Numeric - (r'\b0[xX]' + _hex + r'+\b', Number.Hex), - (r'\b' + _float + r'\s*(day|hr|min|sec|msec|usec)s?\b', Literal.Date), - (r'\b' + _float + r'\b', Number.Float), - (r'\b(\d+)\b', Number.Integer), - + (r"\b0[xX]" + _hex + r"+\b", Number.Hex), + (r"\b" + _float + r"\s*(day|hr|min|sec|msec|usec)s?\b", Literal.Date), + (r"\b" + _float + r"\b", Number.Float), + (r"\b(\d+)\b", Number.Integer), # Hostnames - (_h + r'(\.' + _h + r')+', String), + (_h + r"(\." + _h + r")+", String), ], - - 'operators': [ - (r'[!%*/+<=>~|&^-]', Operator), - (r'([-+=&|]{2}|[+=!><-]=)', Operator), - (r'\b(in|as|is|of)\b', Operator.Word), - (r'\??\$', Operator), + "operators": [ + (r"[!%*/+<=>~|&^-]", Operator), + (r"([-+=&|]{2}|[+=!><-]=)", Operator), + (r"\b(in|as|is|of)\b", Operator.Word), + (r"\??\$", Operator), # Technically, colons are often used for punctuation/separation. # E.g. field name/type separation. - (r'[?:]', Operator), + (r"[?:]", Operator), ], - - 'punctuation': [ - (r'\?\$', Punctuation), - (r'[{}()\[\],;:.]', Punctuation), + "punctuation": [ + (r"\?\$", Punctuation), + (r"[{}()\[\],;:.]", Punctuation), ], - - 'identifiers': [ - (r'([a-zA-Z_]\w*)(::)', bygroups(Name, Punctuation)), - (r'[a-zA-Z_]\w*', Name) + "identifiers": [ + (r"([a-zA-Z_]\w*)(::)", bygroups(Name, Punctuation)), + (r"[a-zA-Z_]\w*", Name), ], - - 'string': [ - (r'\\.', String.Escape), - (r'%-?[0-9]*(\.[0-9]+)?[DTdxsefg]', String.Escape), - (r'"', String, '#pop'), - (r'.', String), + "string": [ + (r"\\.", String.Escape), + (r"%-?[0-9]*(\.[0-9]+)?[DTdxsefg]", String.Escape), + (r'"', String, "#pop"), + (r".", String), ], - - 'regex': [ - (r'\\.', String.Escape), - (r'/', String.Regex, '#pop'), - (r'.', String.Regex), + "regex": [ + (r"\\.", String.Escape), + (r"/", String.Regex, "#pop"), + (r".", String.Regex), ], } diff --git a/requirements.txt b/requirements.txt index ae8fc3c05..c0ca88903 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Jinja2==3.1.4 Pygments==2.17.2 -docutils==0.17.1 +docutils==0.18.1 sphinx_rtd_theme==2.0.0 -Sphinx==5.3.0 +Sphinx==6.2.0 GitPython==3.1.41 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..819c72681 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,4 @@ +target-version = "py39" + +[lint] +select = ["C4", "F", "I", "ISC", "UP"] \ No newline at end of file