This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch docs-code-block-improvements in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit bc82ce4bce90cad9e02f5d3053dc910ea006a5a1 Author: Yang Xia <[email protected]> AuthorDate: Wed Aug 12 16:51:39 2026 -0700 Add toy-graph dataset captions to rendered Gremlin examples Render an italic caption beneath each Gremlin example naming the toy graph it runs against, linking to that graph's section in the Sample Data book in the HTML output and emitting a machine-parseable `graph-dataset:` line in the Markdown mirror. Resolve the dataset from the AST walk rather than execution state. Introduce GraphCatalog as the single source of truth for toy-graph tokens, replacing the duplicated GRAPH_INIT and GRAPH_DOC maps so adding a dataset is a one-line change. Also simplify the console tab label from "console (groovy)" to "console" so it renders at the same size as the other single-word tabs. Assisted-by: Kiro:claude-opus-4.8 --- docs/sass/tabs.scss | 53 +++++++++++- docs/stylesheets/tinkerpop.css | 6 +- .../apache/tinkerpop/tinkeradoc/GraphCatalog.java | 94 ++++++++++++++++++++++ .../tinkerpop/tinkeradoc/GremlinTreeprocessor.java | 50 ++++++------ .../tinkerpop/tinkeradoc/HtmlTabRenderer.java | 16 +++- .../tinkerpop/tinkeradoc/MarkdownTabRenderer.java | 15 ++++ .../apache/tinkerpop/tinkeradoc/NeutralTab.java | 6 +- .../tinkerpop/tinkeradoc/TabbedHtmlBuilder.java | 47 ++++++++++- .../tinkeradoc/DualBackendIntegrationTest.java | 2 +- .../tinkerpop/tinkeradoc/GraphCatalogTest.java | 65 +++++++++++++++ .../tinkeradoc/GremlinTreeprocessorTest.java | 66 ++++++++++++++- .../tinkerpop/tinkeradoc/HtmlTabRendererTest.java | 4 +- .../tinkerpop/tinkeradoc/IntegrationTest.java | 2 +- .../tinkeradoc/MarkdownTabRendererTest.java | 18 ++++- .../tinkeradoc/TabbedHtmlBuilderTest.java | 33 +++++++- 15 files changed, 430 insertions(+), 47 deletions(-) diff --git a/docs/sass/tabs.scss b/docs/sass/tabs.scss index 1541fd19fc..13811e5bcf 100644 --- a/docs/sass/tabs.scss +++ b/docs/sass/tabs.scss @@ -114,6 +114,13 @@ $maxTabs: 7; } } +// At the tightest tab counts the columns get narrow, so trim horizontal padding to keep a long +// single-word label (e.g. "javascript") from overflowing its tab. +.tabs-6 label, +.tabs-7 label { + padding: 0 8px; +} + .tabs label:first-of-type { z-index: 4; } @@ -173,6 +180,50 @@ $maxTabs: 7; height: auto; width: 100%; height: auto; - padding-top: 30px; + // Uniform gray frame: equal gutter on all four sides around the code panel. The base + // stylesheet's side (pre margins) and bottom (listingblock margin) gutters are zeroed under + // .tabs below so this padding is the sole gutter, and the caption is overlaid in it. + padding: 30px; + } +} + +// Zero the base-stylesheet code-block gutters inside tabs so the uniform 30px panel padding is the +// only gray frame (otherwise the sides would be 30px + 2em and the bottom 30px + 1.25em). +.tabs .tabcontent .listingblock { + margin-bottom: 0; +} +.tabs .tabcontent .listingblock > .content > pre { + margin-left: 0; + margin-right: 0; +} + +// Graph-dataset caption for Gremlin examples: an italic line naming the toy graph the example runs +// against, with a link to its Sample Data book section. Absolutely positioned in the bottom gray +// gutter of the .tabcontent box: right-aligned to the code panel's right edge and vertically +// centered in the 30px gutter. Being out of flow, it never adds to the box height, so the gray +// frame stays uniform on all four sides. The position/top/left/width/height/opacity declarations +// override the `.tabcontent > div` rule that collapses and hides inactive panels. +.tabs .graph-dataset-note { + position: absolute; + top: auto; + left: auto; + right: 30px; + bottom: 0; + width: auto; + height: 30px; + line-height: 30px; + opacity: 1; + z-index: 101; + padding: 0; + font-size: 13px; + font-style: italic; + color: #555; + text-align: right; + + a { + // Intentionally darker than the tab accent $active (#609060) for legible contrast on the + // light-gray caption. This is caption-specific; do not replace it with $active. + color: #4a704a; + text-decoration: underline; } } diff --git a/docs/stylesheets/tinkerpop.css b/docs/stylesheets/tinkerpop.css index 71cc47eeec..f7495b2477 100644 --- a/docs/stylesheets/tinkerpop.css +++ b/docs/stylesheets/tinkerpop.css @@ -692,4 +692,8 @@ table.tableblock.grid-all th.tableblock, table.tableblock.grid-all td.tableblock #footer { background-color: #465158; padding: 2em; } #footer-text { color: #eee; font-size: 0.8em; text-align: center; } -.tabs{position:relative;margin:40px auto;width:1024px;max-width:100%;overflow:hidden;padding-top:10px;margin-bottom:60px}.tabs input{position:absolute;z-index:1000;height:50px;left:0;top:0;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);cursor:pointer;margin:0}.tabs input:hover+label{background:#e08f24}.tabs label{background:#e9ffe9;color:#1a1a1a;font-size:15px;line-height:50px;height:60px;position:relative;top:0;padding:0 20px;float:left [...] +.tabs{position:relative;margin:40px auto;width:1024px;max-width:100%;overflow:hidden;padding-top:10px;margin-bottom:60px}.tabs input{position:absolute;z-index:1000;height:50px;left:0;top:0;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);cursor:pointer;margin:0}.tabs input:hover+label{background:#e08f24}.tabs label{background:#e9ffe9;color:#1a1a1a;font-size:15px;line-height:50px;height:60px;position:relative;top:0;padding:0 20px;float:left [...] + +.tabs .graph-dataset-note{position:absolute;top:auto;left:auto;right:30px;bottom:0;width:auto;height:30px;line-height:30px;opacity:1;z-index:101;padding:0;font-size:13px;font-style:italic;color:#555;text-align:right}.tabs .graph-dataset-note a{color:#4a704a;text-decoration:underline}.tabs .tabcontent .listingblock{margin-bottom:0}.tabs .tabcontent .listingblock>.content>pre{margin-left:0;margin-right:0} + +.tabs-6 label,.tabs-7 label{padding:0 8px} diff --git a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GraphCatalog.java b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GraphCatalog.java new file mode 100644 index 0000000000..94db100ab3 --- /dev/null +++ b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GraphCatalog.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.tinkerpop.tinkeradoc; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Canonical registry of the toy graph datasets referenced by Gremlin documentation examples. + * <p> + * This is the single source of truth shared by two concerns that must never disagree about the set + * of datasets: + * <ul> + * <li><b>execution</b> — the Gremlin statement run to build the graph before an example + * ({@link GremlinTreeprocessor}); and</li> + * <li><b>rendering</b> — the dataset caption shown beneath an example and its link to the + * Sample Data book section ({@link TabbedHtmlBuilder}).</li> + * </ul> + * Adding a graph is therefore a one-line change here that feeds both, so a new dataset can never be + * executable-but-unlabeled (or vice versa). + */ +final class GraphCatalog { + + /** A dataset entry: how to construct it, how to name it, and where its Sample Data book section lives. */ + static final class Entry { + /** Gremlin statement that constructs the graph into the {@code graph} binding. */ + final String initStatement; + /** Human-readable name used in the rendered dataset caption. */ + final String displayName; + /** Sample Data book section anchor, or {@code null} if the graph has no dedicated section. */ + final String docAnchor; + + Entry(final String initStatement, final String displayName, final String docAnchor) { + this.initStatement = initStatement; + this.displayName = displayName; + this.docAnchor = docAnchor; + } + } + + /** Init statement for a bare block or any unrecognized token: a fresh, empty graph. */ + static final String DEFAULT_INIT = "graph = TinkerGraph.open()"; + + private static final Map<String, Entry> BY_TOKEN; + + static { + final Map<String, Entry> m = new HashMap<>(); + // "crew" and "theCrew" are aliases for the same dataset. + final Entry crew = new Entry("graph = TinkerFactory.createTheCrew()", "crew", "the-crew"); + m.put("modern", new Entry("graph = TinkerFactory.createModern()", "modern", "modern")); + m.put("classic", new Entry("graph = TinkerFactory.createClassic()", "classic", null)); + m.put("crew", crew); + m.put("theCrew", crew); + m.put("grateful", new Entry("graph = TinkerFactory.createGratefulDead()", "Grateful Dead", "grateful-dead")); + m.put("sink", new Entry("graph = TinkerFactory.createKitchenSink()", "kitchen sink", null)); + BY_TOKEN = Collections.unmodifiableMap(m); + } + + private GraphCatalog() { + } + + /** + * Returns the catalog entry for a dataset token, or {@code null} if the token is not a known + * dataset (including {@code null}, a bare block, or a misspelling). + */ + static Entry entry(final String token) { + return token == null ? null : BY_TOKEN.get(token); + } + + /** + * Returns the Gremlin init statement for a token, falling back to {@link #DEFAULT_INIT} (a fresh + * empty graph) for {@code null} or unrecognized tokens. + */ + static String initStatement(final String token) { + final Entry e = entry(token); + return e != null ? e.initStatement : DEFAULT_INIT; + } +} diff --git a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java index 0106f33dba..a39d2f4104 100644 --- a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java +++ b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java @@ -55,19 +55,6 @@ public class GremlinTreeprocessor extends Treeprocessor { static final Set<String> SUPPORTED_LANGUAGES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("groovy", "java", "csharp", "javascript", "python", "go"))); - static final Map<String, String> GRAPH_INIT; - - static { - final Map<String, String> m = new HashMap<>(); - m.put("modern", "graph = TinkerFactory.createModern()"); - m.put("classic", "graph = TinkerFactory.createClassic()"); - m.put("crew", "graph = TinkerFactory.createTheCrew()"); - m.put("theCrew", "graph = TinkerFactory.createTheCrew()"); - m.put("grateful", "graph = TinkerFactory.createGratefulDead()"); - m.put("sink", "graph = TinkerFactory.createKitchenSink()"); - GRAPH_INIT = Collections.unmodifiableMap(m); - } - private int gremlinBlockCount; private final StatementExecutor executor; private final TabbedHtmlBuilder tabBuilder; @@ -78,6 +65,11 @@ public class GremlinTreeprocessor extends Treeprocessor { private final ConsoleRestartHandler restartHandler; private ConsoleRestartHandler activeRestartHandler; private String currentGraph; + // Tracks the most recently declared graph dataset from the AST walk (independent of execution), + // used only to resolve the label for "existing" blocks. Unlike currentGraph, this is maintained + // on every backend pass — including passes that reuse the execution cache and skip graph init — + // so the rendered dataset label is identical across the HTML and Markdown backends. + private String labelGraphLineage; private String documentId; private List<String> currentExcludedPlugins; @@ -152,6 +144,7 @@ public class GremlinTreeprocessor extends Treeprocessor { public Document process(final Document document) { gremlinBlockCount = 0; currentGraph = null; + labelGraphLineage = null; documentId = resolveDocumentId(document); markdownMode = isMarkdownBackend(document); final Object dryRunAttr = document.getAttribute("gremlin-docs-dryrun"); @@ -317,6 +310,20 @@ public class GremlinTreeprocessor extends Treeprocessor { final List<StructuralNode> blocks = parent.getBlocks(); final Block gremlinBlock = (Block) blocks.get(startIndex); + // Resolve the graph dataset name for the label from the AST walk (not execution state), so + // it is identical across backend passes even when a later pass reuses the execution cache and + // skips graph initialization. An "existing" block carries forward the last declared dataset; + // any other block (including a bare block with no dataset) sets the lineage, mirroring how + // initGraphIfNeeded() re-initializes the graph for every non-"existing" block. + final String rawGraphName = extractGraphName(gremlinBlock); + final String resolvedGraphName; + if (EXISTING.equals(rawGraphName)) { + resolvedGraphName = labelGraphLineage; + } else { + resolvedGraphName = rawGraphName; + labelGraphLineage = rawGraphName; + } + // Walk consecutive [source,<lang>] sibling blocks first (FR-5). This is pure AST work with // no execution: it fixes the block range to replace and captures each sibling's language // and source, which are needed whether the console output comes fresh or from the cache. @@ -352,7 +359,7 @@ public class GremlinTreeprocessor extends Treeprocessor { executionCache.put(cacheKey, tabs); } - emitNeutralTabGroup(parent, startIndex, lastIndex, tabs); + emitNeutralTabGroup(parent, startIndex, lastIndex, tabs, resolvedGraphName); return startIndex; } @@ -377,7 +384,7 @@ public class GremlinTreeprocessor extends Treeprocessor { } } - emitNeutralTabGroup(parent, startIndex, lastIndex, tabs); + emitNeutralTabGroup(parent, startIndex, lastIndex, tabs, null); return startIndex; } @@ -389,12 +396,12 @@ public class GremlinTreeprocessor extends Treeprocessor { * into a neutral custom block for a separate render pass to consume. */ private void emitNeutralTabGroup(final StructuralNode parent, final int startIndex, - final int endIndex, final List<NeutralTab> tabs) { + final int endIndex, final List<NeutralTab> tabs, final String graphName) { final String json = NeutralTabCodec.serialize(tabs); final List<NeutralTab> resolvedTabs = NeutralTabCodec.parse(json); final String rendered = markdownMode - ? markdownTabRenderer.render(resolvedTabs) - : htmlTabRenderer.render(parent, resolvedTabs); + ? markdownTabRenderer.render(resolvedTabs, graphName) + : htmlTabRenderer.render(parent, resolvedTabs, graphName); replaceWithPassBlock(parent, startIndex, endIndex, rendered); } @@ -684,12 +691,7 @@ public class GremlinTreeprocessor extends Treeprocessor { executeSafely("['/tmp/neo4j', '/tmp/tinkergraph.kryo'].each { p -> " + "def f = new File(p); if (f.exists()) f.deleteDir() }"); - final String initStatement; - if (graphName == null) { - initStatement = "graph = TinkerGraph.open()"; - } else { - initStatement = GRAPH_INIT.getOrDefault(graphName, "graph = TinkerGraph.open()"); - } + final String initStatement = GraphCatalog.initStatement(graphName); executeSafely(initStatement); executeSafely("g = graph.traversal()"); diff --git a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java index 3f9f667d69..1373b870b1 100644 --- a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java +++ b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java @@ -45,11 +45,12 @@ class HtmlTabRenderer { /** * Highlights and renders the given neutral tabs into a complete tab-group HTML string. * - * @param parent the AST node used to locate the JRuby runtime for highlighting - * @param tabs the neutral tabs to render + * @param parent the AST node used to locate the JRuby runtime for highlighting + * @param tabs the neutral tabs to render + * @param graphName the resolved graph dataset name (e.g. "modern"), or null if not applicable * @return the tab-group HTML */ - String render(final StructuralNode parent, final List<NeutralTab> tabs) { + String render(final StructuralNode parent, final List<NeutralTab> tabs, final String graphName) { final List<TabbedHtmlBuilder.Tab> htmlTabs = new ArrayList<>(tabs.size()); for (final NeutralTab tab : tabs) { // The existing pipeline highlighted every tab (console and source) as groovy-flavored @@ -62,6 +63,13 @@ class HtmlTabRenderer { htmlTabs.add(TabbedHtmlBuilder.codeTabHighlighted(tab.getLanguage(), highlighted)); } } - return tabBuilder.build(htmlTabs); + return tabBuilder.build(htmlTabs, graphName); + } + + /** + * Highlights and renders the given neutral tabs into a complete tab-group HTML string (no graph label). + */ + String render(final StructuralNode parent, final List<NeutralTab> tabs) { + return render(parent, tabs, null); } } diff --git a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java index bb1685b956..cf583525a8 100644 --- a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java +++ b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java @@ -45,7 +45,22 @@ class MarkdownTabRenderer { * @return the Markdown for the tab group (no trailing newline) */ String render(final List<NeutralTab> tabs) { + return render(tabs, null); + } + + /** + * Renders the given neutral tabs as a sequence of labeled fenced code blocks with an optional + * graph-dataset label. + * + * @param tabs the neutral tabs to render + * @param graphName the resolved graph dataset name (e.g. "modern"), or null to omit + * @return the Markdown for the tab group (no trailing newline) + */ + String render(final List<NeutralTab> tabs, final String graphName) { final StringBuilder sb = new StringBuilder(); + if (graphName != null && !graphName.isEmpty()) { + sb.append("*graph-dataset: ").append(graphName).append("*\n\n"); + } for (int i = 0; i < tabs.size(); i++) { if (i > 0) sb.append("\n\n"); final NeutralTab tab = tabs.get(i); diff --git a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java index 2aa66b82f5..f97e6cf344 100644 --- a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java +++ b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java @@ -51,11 +51,11 @@ class NeutralTab { } /** - * Creates a console-transcript tab. Its label matches the existing HTML output, i.e. - * {@code console (<lang>)}. + * Creates a console-transcript tab. Its label is {@code console} (the transcript is always the + * Groovy Gremlin Console, so the language is not repeated in the label). */ static NeutralTab console(final String language, final String content) { - return new NeutralTab("console (" + language + ")", language, Kind.CONSOLE, content); + return new NeutralTab("console", language, Kind.CONSOLE, content); } /** diff --git a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java index 5098ecf5b0..925c583b9a 100644 --- a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java +++ b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java @@ -45,6 +45,28 @@ public class TabbedHtmlBuilder { private int groupCounter = 0; + /** + * Builds the italic caption line naming the toy graph an example runs against, linking the graph + * name to its section in the Sample Data book when one exists. Unknown tokens render as plain + * (unlinked) text. The display name and anchor come from the shared {@link GraphCatalog}. The link + * uses the {@code x.y.z} version placeholder, which {@code GremlinPostprocessor} replaces with the + * real version in the final HTML (the same mechanism used by every other cross-reference in the docs). + * + * @param token the resolved graph token (e.g. "modern", "theCrew") + * @return the caption HTML (a {@code <div class="graph-dataset-note">} element) + */ + static String graphNoteHtml(final String token) { + final GraphCatalog.Entry entry = GraphCatalog.entry(token); + final String display = (entry != null) ? entry.displayName : token; + final String anchor = (entry != null) ? entry.docAnchor : null; + final String name = (anchor != null) + ? "<a href=\"https://tinkerpop.apache.org/docs/x.y.z/data/#" + anchor + "\">" + + escapeHtml(display) + "</a>" + : escapeHtml(display); + return "<div class=\"graph-dataset-note\"><em>This example runs against the " + + name + " graph.</em></div>"; + } + /** * A single tab entry with a label, language, and source code content. */ @@ -103,6 +125,17 @@ public class TabbedHtmlBuilder { * @return the complete HTML string for the tab group */ String build(final List<Tab> tabs) { + return build(tabs, null); + } + + /** + * Builds tabbed HTML for a list of tabs with an optional graph-dataset caption. + * + * @param tabs the tabs to render + * @param graphName the resolved graph dataset name (e.g. "modern"), or null to omit the caption + * @return the complete HTML string for the tab group + */ + String build(final List<Tab> tabs, final String graphName) { if (tabs == null || tabs.isEmpty()) { return ""; } @@ -144,9 +177,17 @@ public class TabbedHtmlBuilder { html.append(" </div>\n"); } + // Graph-dataset caption: an italic line naming the toy graph the example runs against, + // rendered inside the gray .tabcontent box at the bottom-right. Emitted after the floated + // tab panels and cleared below them, so it sits within the gray frame rather than beneath + // it. It links to the graph's Sample Data book section. + if (graphName != null && !graphName.isEmpty()) { + html.append(" ").append(graphNoteHtml(graphName)).append("\n"); + } + html.append(" </div>\n"); - html.append("</section>"); + html.append("</section>"); return html.toString(); } @@ -158,14 +199,14 @@ public class TabbedHtmlBuilder { * @return a Tab instance for the console output */ static Tab consoleTab(final String lang, final String consoleOutput) { - return new Tab("console (" + lang + ")", lang, consoleOutput); + return new Tab("console", lang, consoleOutput); } /** * Creates a console tab with pre-highlighted HTML content. */ static Tab consoleTabHighlighted(final String lang, final String highlightedHtml) { - return new Tab("console (" + lang + ")", lang, highlightedHtml, true); + return new Tab("console", lang, highlightedHtml, true); } /** diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java index 48e634970a..dccddd3d7f 100644 --- a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java @@ -91,7 +91,7 @@ public class DualBackendIntegrationTest { // preserved verbatim (no highlighting). assertThat(markdown, containsString("## Basics")); assertThat(markdown, containsString("Some intro text.")); - assertThat(markdown, containsString("**console (groovy)**")); + assertThat(markdown, containsString("**console**")); assertThat(markdown, containsString("```text")); assertThat(markdown, containsString("gremlin> g.V(1)")); assertThat(markdown, containsString("==>v[1]")); diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GraphCatalogTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GraphCatalogTest.java new file mode 100644 index 0000000000..db3c5c3b5b --- /dev/null +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GraphCatalogTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.tinkerpop.tinkeradoc; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +public class GraphCatalogTest { + + @Test + public void shouldExposeInitStatementDisplayAndAnchorForKnownGraph() { + final GraphCatalog.Entry modern = GraphCatalog.entry("modern"); + assertThat(modern.initStatement, is("graph = TinkerFactory.createModern()")); + assertThat(modern.displayName, is("modern")); + assertThat(modern.docAnchor, is("modern")); + } + + @Test + public void shouldTreatCrewAndTheCrewAsTheSameDataset() { + final GraphCatalog.Entry crew = GraphCatalog.entry("crew"); + final GraphCatalog.Entry theCrew = GraphCatalog.entry("theCrew"); + assertThat(crew.initStatement, is("graph = TinkerFactory.createTheCrew()")); + assertThat(theCrew.initStatement, is(crew.initStatement)); + assertThat(theCrew.displayName, is(crew.displayName)); + assertThat(theCrew.docAnchor, is(crew.docAnchor)); + } + + @Test + public void shouldReportNullAnchorForGraphWithoutReferenceSection() { + assertThat(GraphCatalog.entry("classic").docAnchor, nullValue()); + assertThat(GraphCatalog.entry("sink").docAnchor, nullValue()); + } + + @Test + public void shouldReturnNoEntryForNullOrUnknownToken() { + assertThat(GraphCatalog.entry(null), nullValue()); + assertThat(GraphCatalog.entry("bogus"), nullValue()); + } + + @Test + public void shouldFallBackToEmptyGraphInitForNullOrUnknownToken() { + assertThat(GraphCatalog.initStatement(null), is(GraphCatalog.DEFAULT_INIT)); + assertThat(GraphCatalog.initStatement("bogus"), is(GraphCatalog.DEFAULT_INIT)); + assertThat(GraphCatalog.DEFAULT_INIT, is("graph = TinkerGraph.open()")); + } +} diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java index 209e73f66e..c1a2069993 100644 --- a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java @@ -87,6 +87,68 @@ public class GremlinTreeprocessorTest { } } + @Test + public void shouldRenderGraphBadgeForNamedGraph() { + final RecordingExecutor executor = new RecordingExecutor("==>v[1]"); + final GremlinTreeprocessor processor = new GremlinTreeprocessor(executor); + try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) { + asciidoctor.unregisterAllExtensions(); + asciidoctor.javaExtensionRegistry().treeprocessor(processor); + final String input = "= Test\n\n[gremlin-groovy,modern]\n----\ng.V(1)\n----\n"; + final String result = asciidoctor.convert(input, Options.builder().build()); + assertThat(result, containsString("class=\"graph-dataset-note\"")); + assertThat(result, containsString("/data/#modern\">modern</a> graph.")); + } + } + + @Test + public void shouldCarryForwardGraphLabelForExisting() { + final RecordingExecutor executor = new RecordingExecutor("==>result"); + final GremlinTreeprocessor processor = new GremlinTreeprocessor(executor); + try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) { + asciidoctor.unregisterAllExtensions(); + asciidoctor.javaExtensionRegistry().treeprocessor(processor); + final String input = "= Test\n\n" + + "[gremlin-groovy,modern]\n----\ng.V(1)\n----\n\n" + + "[gremlin-groovy,existing]\n----\ng.E()\n----\n"; + final String result = asciidoctor.convert(input, Options.builder().build()); + // Both blocks show the "modern" note; the existing block carries it forward. + final int badgeCount = result.split("/data/#modern\">modern</a>", -1).length - 1; + assertThat(badgeCount, is(2)); + } + } + + @Test + public void shouldLabelExistingConsistentlyAcrossCachedSecondPass() { + // Regression: the "existing" label must be resolved from the AST walk, not from + // execution-set state. A second backend pass reuses the shared execution cache and skips + // graph initialization, so a label derived from execution state would go missing on that + // pass. Both passes must produce the same "modern" label for the existing block. + final RecordingExecutor executor = new RecordingExecutor("==>result"); + final GremlinExecutionCache cache = new GremlinExecutionCache(); + final String input = "= Test\n\n" + + "[gremlin-groovy,modern]\n----\ng.V(1)\n----\n\n" + + "[gremlin-groovy,existing]\n----\ng.E()\n----\n"; + try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) { + asciidoctor.javaConverterRegistry().register(MarkdownConverter.class); + + asciidoctor.unregisterAllExtensions(); + asciidoctor.javaExtensionRegistry().treeprocessor(new GremlinTreeprocessor(executor, null, cache)); + final String html = asciidoctor.convert(input, Options.builder().backend("html5").build()); + + asciidoctor.unregisterAllExtensions(); + asciidoctor.javaExtensionRegistry().treeprocessor(new GremlinTreeprocessor(executor, null, cache)); + final String markdown = asciidoctor.convert(input, Options.builder().backend("tpmarkdown").build()); + + // HTML pass: two "modern" notes (named + existing carried forward). + final int htmlBadges = html.split("/data/#modern\">modern</a>", -1).length - 1; + assertThat(htmlBadges, is(2)); + // Markdown pass (cache reused, no execution): two "modern" dataset labels. + final int mdLabels = markdown.split("\\*graph-dataset: modern\\*", -1).length - 1; + assertThat(mdLabels, is(2)); + } + } + @Test public void shouldReuseGraphStateForExisting() { final RecordingExecutor executor = new RecordingExecutor("==>result"); @@ -356,7 +418,7 @@ public class GremlinTreeprocessorTest { final String input = "= Test\n\n[gremlin-groovy,modern]\n----\ng.V(1)\n----\n"; final String result = asciidoctor.convert(input, Options.builder().build()); assertThat(result, containsString("section class=\"tabs tabs-2\"")); - assertThat(result, containsString("console (groovy)")); + assertThat(result, containsString("console")); assertThat(result, containsString("tab-group-1")); } } @@ -375,7 +437,7 @@ public class GremlinTreeprocessorTest { "[source,python]\n----\ng.V(1)\n----\n"; final String result = asciidoctor.convert(input, Options.builder().build()); assertThat(result, containsString("tabs tabs-4")); - assertThat(result, containsString("console (groovy)")); + assertThat(result, containsString("console")); assertThat(result, containsString("tab-label-3\">java")); assertThat(result, containsString("tab-label-4\">python")); } diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java index d75da871ad..299327425e 100644 --- a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java @@ -48,7 +48,7 @@ public class HtmlTabRendererTest { @Test public void consoleFactorySetsLabelLanguageAndKind() { final NeutralTab tab = NeutralTab.console("groovy", "gremlin> g.V()"); - assertThat(tab.getLabel(), is("console (groovy)")); + assertThat(tab.getLabel(), is("console")); assertThat(tab.getLanguage(), is("groovy")); assertThat(tab.getKind(), is(NeutralTab.Kind.CONSOLE)); assertThat(tab.getContent(), is("gremlin> g.V()")); @@ -69,7 +69,7 @@ public class HtmlTabRendererTest { NeutralTab.source("groovy", "g.V(1)")); final String html = renderer.render(null, tabs); assertThat(html, containsString("<section class=\"tabs tabs-2\">")); - assertThat(html, containsString("class=\"tab-label-1\">console (groovy)</label>")); + assertThat(html, containsString("class=\"tab-label-1\">console</label>")); assertThat(html, containsString("class=\"tab-label-2\">groovy</label>")); } diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java index 3f767534ca..cda2bd4582 100644 --- a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java @@ -75,7 +75,7 @@ public class IntegrationTest { @Test public void shouldContainConsoleTabLabel() { - assertThat(html, containsString("console (groovy)")); + assertThat(html, containsString("console")); } @Test diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java index 34d8e61dd7..63eb611883 100644 --- a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java @@ -46,7 +46,7 @@ public class MarkdownTabRendererTest { public void consoleTabRendersAsTextFence() { final String md = renderer.render(Collections.singletonList( NeutralTab.console("groovy", "gremlin> g.V()\n==>v[1]"))); - assertThat(md, containsString("**console (groovy)**")); + assertThat(md, containsString("**console**")); assertThat(md, containsString("```text\ngremlin> g.V()\n==>v[1]\n```")); } @@ -65,7 +65,7 @@ public class MarkdownTabRendererTest { NeutralTab.source("groovy", "g.V(1)"), NeutralTab.source("python", "g.V(1)")); final String md = renderer.render(tabs); - final int console = md.indexOf("**console (groovy)**"); + final int console = md.indexOf("**console**"); final int groovy = md.indexOf("**groovy**"); final int python = md.indexOf("**python**"); assertThat(console >= 0 && groovy > console && python > groovy, is(true)); @@ -110,4 +110,18 @@ public class MarkdownTabRendererTest { NeutralTab.source("groovy", "a() <1>\nb()\nc() <2>"))); assertThat(md, containsString("a() // (1)\nb()\nc() // (2)")); } + + @Test + public void shouldIncludeGraphLabelWhenProvided() { + final String md = renderer.render(Collections.singletonList( + NeutralTab.source("groovy", "g.V()")), "modern"); + assertThat(md, containsString("*graph-dataset: modern*")); + } + + @Test + public void shouldOmitGraphLabelWhenNull() { + final String md = renderer.render(Collections.singletonList( + NeutralTab.source("groovy", "g.V()")), null); + assertThat(md, not(containsString("graph-dataset"))); + } } diff --git a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java index 998c4104e2..b8cd8930ba 100644 --- a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java +++ b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java @@ -101,7 +101,7 @@ public class TabbedHtmlBuilderTest { TabbedHtmlBuilder.consoleTab("groovy", "output"), TabbedHtmlBuilder.codeTab("java", "code")); final String html = builder.build(tabs); - assertThat(html, containsString("<label for=\"tab-1-1\" class=\"tab-label-1\">console (groovy)</label>")); + assertThat(html, containsString("<label for=\"tab-1-1\" class=\"tab-label-1\">console</label>")); assertThat(html, containsString("<label for=\"tab-1-2\" class=\"tab-label-2\">java</label>")); } @@ -176,7 +176,7 @@ public class TabbedHtmlBuilderTest { @Test public void shouldCreateConsoleTabWithCorrectLabel() { final TabbedHtmlBuilder.Tab tab = TabbedHtmlBuilder.consoleTab("groovy", "gremlin> g.V()"); - assertThat(tab.getLabel(), is("console (groovy)")); + assertThat(tab.getLabel(), is("console")); assertThat(tab.getLanguage(), is("groovy")); assertThat(tab.getContent(), is("gremlin> g.V()")); } @@ -258,7 +258,7 @@ public class TabbedHtmlBuilderTest { assertThat(html, containsString("id=\"tab-1-4\" class=\"tab-selector-4\"")); // Verify all 4 labels - assertThat(html, containsString("class=\"tab-label-1\">console (groovy)</label>")); + assertThat(html, containsString("class=\"tab-label-1\">console</label>")); assertThat(html, containsString("class=\"tab-label-2\">java</label>")); assertThat(html, containsString("class=\"tab-label-3\">python</label>")); assertThat(html, containsString("class=\"tab-label-4\">csharp</label>")); @@ -283,4 +283,31 @@ public class TabbedHtmlBuilderTest { assertThat(html2, containsString("id=\"tab-2-1\"")); assertThat(html3, containsString("id=\"tab-3-1\"")); } + + @Test + public void shouldIncludeGraphBadgeWhenGraphNameProvided() { + final List<TabbedHtmlBuilder.Tab> tabs = Collections.singletonList( + TabbedHtmlBuilder.codeTab("groovy", "g.V()")); + final String html = builder.build(tabs, "modern"); + assertThat(html, containsString("class=\"graph-dataset-note\"")); + assertThat(html, containsString("This example runs against the")); + assertThat(html, containsString("/data/#modern\">modern</a> graph.")); + } + + @Test + public void shouldOmitGraphBadgeWhenGraphNameIsNull() { + final List<TabbedHtmlBuilder.Tab> tabs = Collections.singletonList( + TabbedHtmlBuilder.codeTab("groovy", "g.V()")); + final String html = builder.build(tabs, null); + assertThat(html, not(containsString("graph-dataset-note"))); + } + + @Test + public void shouldEscapeHtmlInGraphBadge() { + final List<TabbedHtmlBuilder.Tab> tabs = Collections.singletonList( + TabbedHtmlBuilder.codeTab("groovy", "g.V()")); + final String html = builder.build(tabs, "<script>alert(1)</script>"); + assertThat(html, containsString("<script>alert(1)</script>")); + assertThat(html, not(containsString("<script>"))); + } }
