Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add edb::env-switcher and edb::split-section sphinx directives #8218

Merged
merged 5 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions edb/tools/docs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from . import js
from . import sdl
from . import graphql
from . import go
from . import shared


Expand Down Expand Up @@ -119,6 +120,7 @@ def setup(app):
js.setup_domain(app)
sdl.setup_domain(app)
graphql.setup_domain(app)
go.setup_domain(app)

app.add_directive('versionadded', VersionAdded, True)
app.add_directive('versionchanged', VersionChanged, True)
Expand Down
83 changes: 82 additions & 1 deletion edb/tools/docs/edb.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from docutils.parsers import rst as d_rst
from docutils.parsers.rst import directives as d_directives # type: ignore

from sphinx_code_tabs import TabsNode


class EDBYoutubeEmbed(d_rst.Directive):

Expand Down Expand Up @@ -56,13 +58,92 @@ def run(self):
return [node]


class EDBEnvironmentSwitcher(d_rst.Directive):

has_content = False
optional_arguments = 0
required_arguments = 0

def run(self):
node = d_nodes.container()
node['env-switcher'] = True
return [node]


class EDBSplitSection(d_rst.Directive):

has_content = True
optional_arguments = 0
required_arguments = 0

def run(self):
node = d_nodes.container()
node['split-section'] = True
self.state.nested_parse(self.content, self.content_offset, node)

split_indexes = [
index for index, child in enumerate(node.children)
if isinstance(child, d_nodes.container) and child.get('split-point')
]
if len(split_indexes) > 1:
raise Exception(
f'cannot have multiple edb:split-point\'s in edb:split-section'
)
blocks = (
node.children[:split_indexes[0]] if
node.children[split_indexes[0]].get('code-above')
else node.children[split_indexes[0] + 1:]
) if len(split_indexes) == 1 else [node.children[-1]]
if len(blocks) < 1:
raise Exception(
f'no content found at end of edb:split-section block, '
f'or before/after the edb:split-point in the edb:split-section'
)
for block in blocks:
if (
not isinstance(block, d_nodes.literal_block)
and not isinstance(block, TabsNode)
and not isinstance(block, d_nodes.image)
and not isinstance(block, d_nodes.figure)
):
raise Exception(
f'expected all content before/after the edb:split-point or '
f'at the end of the edb:split-section to be either a '
f'code block, code tabs, or image/figure'
)
return [node]


class EDBSplitPoint(d_rst.Directive):

has_content = False
optional_arguments = 1
required_arguments = 0

def run(self):
node = d_nodes.container()
node['split-point'] = True
if len(self.arguments) > 0:
if self.arguments[0] not in ['above', 'below']:
raise Exception(
f"expected edb:split-point arg to be 'above', 'below' "
f"or empty (defaults to 'below')"
)
if self.arguments[0] == 'above':
node['code-above'] = True
return [node]


class GelDomain(s_domains.Domain):
name = "edb"
label = "Gel"

directives = {
'collapsed': EDBCollapsed,
'youtube-embed': EDBYoutubeEmbed
'youtube-embed': EDBYoutubeEmbed,
'env-switcher': EDBEnvironmentSwitcher,
'split-section': EDBSplitSection,
'split-point': EDBSplitPoint
}


Expand Down
155 changes: 155 additions & 0 deletions edb/tools/docs/go.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#
# This source file is part of the EdgeDB open source project.
#
# Copyright 2018-present MagicStack Inc. and the EdgeDB authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


from __future__ import annotations

import re

from typing import Any, Dict

from sphinx import addnodes as s_nodes
from sphinx import directives as s_directives
from sphinx import domains as s_domains


class BaseGoDirective(s_directives.ObjectDescription):

def get_signatures(self):
return [re.compile(r'\\\s*\n').sub('\n', self.arguments[0])]

def add_target_and_index(self, name, sig, signode):
target = name.replace(' ', '-')

if target in self.state.document.ids:
raise self.error(
f'duplicate {self.objtype} {name} description')

signode['names'].append(target)
signode['ids'].append(target)
self.state.document.note_explicit_target(signode)

objects = self.env.domaindata['go']['objects']

if target in objects:
raise self.error(
f'duplicate {self.objtype} {name} description')
objects[target] = (self.env.docname, self.objtype)


class GoTypeDirective(BaseGoDirective):

def handle_signature(self, sig, signode):
name = re.split(r'\s+', sig)[1].strip()

signode['name'] = name
signode['fullname'] = name

signode['is_multiline'] = True
signode += [
s_nodes.desc_signature_line(sig, line)
for line in sig.split('\n')
]

return name

def add_target_and_index(self, name, sig, signode):
return super().add_target_and_index(name, sig, signode)


goFuncRegex = re.compile(
r"func\s+(?:\(.+?\s+\*?(?P<receiver>.+?)\)\s+)?(?P<name>.+?)\s*\(")


class GoFunctionDirective(BaseGoDirective):

def handle_signature(self, sig, signode):
match = goFuncRegex.match(sig)
if match is None:
raise self.error(f'could not parse go func signature: {sig!r}')

signode['fullname'] = fullname = (
f"{match.group('receiver')}.{match.group('name')}"
if match.group('receiver')
else match.group('name')
)
signode['name'] = match.group('name')

signode['is_multiline'] = True
signode += [
s_nodes.desc_signature_line(sig, line)
for line in sig.split('\n')
]

return fullname

def add_target_and_index(self, name, sig, signode):
return super().add_target_and_index(name, sig, signode)


class GoMethodDirective(GoFunctionDirective):
pass


class GolangDomain(s_domains.Domain):

name = "go"
label = "Golang"

object_types = {
'function': s_domains.ObjType('function'),
'type': s_domains.ObjType('type'),
}

directives = {
'function': GoFunctionDirective,
'type': GoTypeDirective,
'method': GoMethodDirective,
}

initial_data: Dict[str, Dict[str, Any]] = {
'objects': {} # fullname -> docname, objtype
}

def clear_doc(self, docname):
for fullname, (fn, _l) in list(self.data['objects'].items()):
if fn == docname:
del self.data['objects'][fullname]

def merge_domaindata(self, docnames, otherdata):
for fullname, (fn, objtype) in otherdata['objects'].items():
if fn in docnames:
self.data['objects'][fullname] = (fn, objtype)

def get_objects(self):
for refname, (docname, type) in self.data['objects'].items():
yield (refname, refname, type, docname, refname, 1)

def get_full_qualified_name(self, node):
fn = node.get('fullname')
if not fn:
raise self.error('no fullname attribute')
return fn


def setup_domain(app):
app.add_domain(GolangDomain)


def setup(app):
setup_domain(app)