Skip to content

Commit 02dd9e7

Browse files
committed
update
1 parent 021df50 commit 02dd9e7

File tree

4 files changed

+71
-50
lines changed

4 files changed

+71
-50
lines changed

src/sphinxnotes/snippet/cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ def main(argv: list[str] = sys.argv[1:]):
6060
formatter_class=HelpFormatter,
6161
epilog=dedent("""
6262
snippet tags:
63-
d (document) a reST document
64-
s (section) a reST section
65-
c (code) snippet with code blocks
63+
d (document) a document
64+
s (section) a section
65+
c (code) a code block
6666
* (any) wildcard for any snippet"""),
6767
)
6868
parser.add_argument(

src/sphinxnotes/snippet/ext.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from collections.abc import Iterator
2727

2828
from .config import Config
29-
from .snippets import Snippet, WithTitle, Document, Section
29+
from .snippets import Snippet, WithTitle, Document, Section, Code
3030
from .picker import pick
3131
from .cache import Cache, Item
3232
from .keyword import Extractor
@@ -45,6 +45,8 @@ def extract_tags(s: Snippet) -> str:
4545
tags += 'd'
4646
elif isinstance(s, Section):
4747
tags += 's'
48+
elif isinstance(s, Code):
49+
tags += 'c'
4850
return tags
4951

5052

@@ -53,14 +55,21 @@ def extract_excerpt(s: Snippet) -> str:
5355
return '<' + s.title.text + '>'
5456
elif isinstance(s, Section) and s.title is not None:
5557
return '[' + s.title.text + ']'
58+
elif isinstance(s, Code):
59+
excerpt = s.desc.astext() if s.desc else s.caption or '' # FIXME
60+
return '`' + s.language + ':' + excerpt + '`'
5661
return ''
5762

5863

5964
def extract_keywords(s: Snippet) -> list[str]:
6065
keywords = [s.docname]
61-
# TODO: Deal with more snippet
6266
if isinstance(s, WithTitle) and s.title is not None:
6367
keywords.extend(extractor.extract(s.title.text, strip_stopwords=False))
68+
if isinstance(s, Code):
69+
if s.desc:
70+
keywords.extend(extractor.extract(s.desc.astext(), strip_stopwords=False))
71+
if s.caption:
72+
keywords.extend(extractor.extract(s.caption, strip_stopwords=False))
6473
return keywords
6574

6675

@@ -76,7 +85,7 @@ def is_document_matched(
7685
return new_pats
7786

7887

79-
def is_snippet_matched(pats: dict[str, list[str]], s: [Snippet], docname: str) -> bool:
88+
def is_snippet_matched(pats: dict[str, list[str]], s: Snippet, docname: str) -> bool:
8089
"""Whether the snippet's tags and docname matched by given patterns pats"""
8190
if '*' in pats: # Wildcard
8291
for pat in pats['*']:
@@ -113,6 +122,7 @@ def on_env_get_outdated(
113122
removed: set[str],
114123
) -> list[str]:
115124
# Remove purged indexes and snippetes from db
125+
assert cache is not None
116126
for docname in removed:
117127
del cache[(app.config.project, docname)]
118128
return []
@@ -162,6 +172,7 @@ def on_doctree_resolved(app: Sphinx, doctree: nodes.document, docname: str) -> N
162172

163173

164174
def on_builder_finished(app: Sphinx, exception) -> None:
175+
assert cache is not None
165176
cache.dump()
166177

167178

src/sphinxnotes/snippet/picker.py

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from sphinx.util import logging
1717

18-
from .snippets import Snippet, Section, Document
18+
from .snippets import Snippet, Section, Document, Code
1919

2020
if TYPE_CHECKING:
2121
from sphinx.application import Sphinx
@@ -40,66 +40,53 @@ def pick(
4040
logger.debug('Skipped document with nosearch metadata')
4141
return []
4242

43-
snippets: list[tuple[Snippet, nodes.section]] = []
43+
# Walk doctree and pick snippets.
44+
picker = SnippetPicker(doctree)
45+
doctree.walkabout(picker)
4446

45-
# Pick document
46-
toplevel_section = doctree.next_node(nodes.section)
47-
if toplevel_section:
48-
snippets.append((Document(doctree), toplevel_section))
49-
else:
50-
logger.warning('can not pick document without child section: %s', doctree)
47+
return picker.snippets
5148

52-
# Pick sections
53-
section_picker = SectionPicker(doctree)
54-
doctree.walkabout(section_picker)
55-
snippets.extend(section_picker.sections)
5649

57-
return snippets
58-
59-
60-
class SectionPicker(nodes.SparseNodeVisitor):
50+
class SnippetPicker(nodes.SparseNodeVisitor):
6151
"""Node visitor for picking snippets from document."""
6252

63-
#: Constant list of unsupported languages (:class:`pygments.lexers.Lexer`)
64-
UNSUPPORTED_LANGUAGES: list[str] = ['default']
65-
66-
#: List of picked section snippets and the section it belongs to
67-
sections: list[tuple[Section, nodes.section]]
53+
#: List of picked snippets and the section it belongs to
54+
snippets: list[tuple[Snippet, nodes.section]]
6855

69-
_section_has_code_block: bool
70-
_section_level: int
56+
#: Stack of nested sections.
57+
_sections: list[nodes.section]
7158

72-
def __init__(self, document: nodes.document) -> None:
73-
super().__init__(document)
74-
self.sections = []
75-
self._section_has_code_block = False
76-
self._section_level = 0
59+
def __init__(self, doctree: nodes.document) -> None:
60+
super().__init__(doctree)
61+
self.snippets = []
62+
self._sections = []
7763

7864
###################
7965
# Visitor methods #
8066
###################
8167

8268
def visit_literal_block(self, node: nodes.literal_block) -> None:
83-
if node['language'] in self.UNSUPPORTED_LANGUAGES:
69+
try:
70+
code = Code(node)
71+
except ValueError as e:
72+
logger.debug(f'skip {node}: {e}')
8473
raise nodes.SkipNode
85-
self._has_code_block = True
74+
self.snippets.append((code, self._sections[-1]))
8675

8776
def visit_section(self, node: nodes.section) -> None:
88-
self._section_level += 1
77+
self._sections.append(node)
8978

9079
def depart_section(self, node: nodes.section) -> None:
91-
self._section_level -= 1
92-
self._has_code_block = False
80+
section = self._sections.pop()
81+
assert section == node
9382

9483
# Skip non-leaf section without content
9584
if self._is_empty_non_leaf_section(node):
9685
return
97-
# Skip toplevel section, we generate :class:`Document` for it
98-
if self._section_level == 0:
99-
return
100-
101-
# TODO: code block
102-
self.sections.append((Section(node), node))
86+
if len(self._sections) == 0:
87+
self.snippets.append((Document(self.document), node))
88+
else:
89+
self.snippets.append((Section(node), node))
10390

10491
def unknown_visit(self, node: nodes.Node) -> None:
10592
pass # Ignore any unknown node

src/sphinxnotes/snippet/snippets.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,38 @@ def __init__(self, node: nodes.Node) -> None:
9696

9797
class Code(Text):
9898
#: Language of code block
99-
language: str
100-
#: Caption of code block
101-
caption: str | None
99+
lang: str
100+
#: Description of code block, usually the text of preceding paragraph
101+
desc: nodes.paragraph | str
102102

103103
def __init__(self, node: nodes.literal_block) -> None:
104104
assert isinstance(node, nodes.literal_block)
105105
super().__init__(node)
106-
self.language = node['language']
107-
self.caption = node.get('caption')
106+
107+
self.lang = node['language']
108+
109+
if isinstance(para := node.previous_sibling(), nodes.paragraph):
110+
# Use the preceding paragraph as descritpion.
111+
#
112+
# We usually write some descritpions before a code block, for example,
113+
# The ``::`` syntax is a common way to create code block::
114+
#
115+
# | Foo:: | <paragraph>
116+
# | | Foo:
117+
# | Bar | <literal_block xml:space="preserve">
118+
# | | Bar
119+
#
120+
# In this case, the preceding paragraph "Foo:" is the descritpion
121+
# of the code block. This convention also applies to the code,
122+
# code-block, sourcecode directive.
123+
self.desc = para
124+
elif caption := node.get('caption'):
125+
# Use caption as descritpion.
126+
# In sphinx, code-block, sourcecode and code may have caption option.
127+
# https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block
128+
self.desc = caption
129+
else:
130+
raise ValueError('Lack of description: preceding paragraph or caption')
108131

109132

110133
class Title(Text):

0 commit comments

Comments
 (0)