You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/09/24 18:03:10 UTC

(grails-core) branch docs/fix-section-title-sequence created (now 384ec80859)

This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a change to branch docs/fix-section-title-sequence
in repository https://gitbox.apache.org/repos/asf/grails-core.git


      at 384ec80859 Fix section title out of sequence warnings in the documentation builds

This branch includes the following new commits:

     new 384ec80859 Fix section title out of sequence warnings in the documentation builds

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(grails-core) 01/01: Fix section title out of sequence warnings in the documentation builds

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch docs/fix-section-title-sequence
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 384ec80859b411c77c70a849fe7c4f3e3c1ebd3a
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Thu Sep 24 14:02:58 2026 -0400

    Fix section title out of sequence warnings in the documentation builds
    
    - Render user guide sections as Asciidoctor fragments. Each section is
      converted on its own, so its first heading sits at its table-of-contents
      depth; headings nested inside a section are still checked.
    - Convert the standalone Hibernate 5 pages as fragments too, while
      index.adoc opts back out so the assembled manual keeps full checking.
    - Correct the remaining heading levels in the Hibernate 5 manual: nest
      Rollback Scripts under Reference alongside the other script groups,
      promote subsections that were already numbered and listed as x.y, and
      mark the cascade scenarios as discrete headings to keep their rendering.
    
    Generated output was compared before and after: the user guide and all
    other pages are byte-identical, and the only visible change is the
    corrected Rollback Scripts and Update Scripts numbering.
    
    Fixes #14898
---
 .../grails/doc/asciidoc/AsciiDocEngine.groovy      |  6 +-
 .../grails/doc/asciidoc/AsciiDocEngineSpec.groovy  | 85 ++++++++++++++++++++++
 grails-data-hibernate5/docs/build.gradle           |  6 +-
 .../src/docs/asciidoc/databaseMigration/index.adoc |  2 +-
 .../docs/src/docs/asciidoc/index.adoc              |  1 +
 .../docs/src/docs/asciidoc/persistenceBasics.adoc  |  2 +-
 .../docs/asciidoc/persistenceBasics/cascades.adoc  |  5 ++
 .../docs/asciidoc/programmaticTransactions.adoc    |  4 +-
 .../docs/src/docs/asciidoc/querying.adoc           |  4 +-
 9 files changed, 107 insertions(+), 8 deletions(-)

diff --git a/build-logic/docs-core/src/main/groovy/grails/doc/asciidoc/AsciiDocEngine.groovy b/build-logic/docs-core/src/main/groovy/grails/doc/asciidoc/AsciiDocEngine.groovy
index a47ed1c440..dc914b5da8 100644
--- a/build-logic/docs-core/src/main/groovy/grails/doc/asciidoc/AsciiDocEngine.groovy
+++ b/build-logic/docs-core/src/main/groovy/grails/doc/asciidoc/AsciiDocEngine.groovy
@@ -44,7 +44,11 @@ class AsciiDocEngine extends DocEngine {
     Map attributes = [
         'imagesdir': '../img',
         'source-highlighter': 'coderay',
-        'icons': 'font'
+        'icons': 'font',
+        // Each guide section is rendered on its own, so its first heading sits at the depth the section
+        // occupies in the table of contents rather than at level 1. Rendering it as a fragment stops
+        // Asciidoctor warning about that, while headings nested inside the section are still checked.
+        'fragment': ''
     ]
     @Override
     String render(String content, RenderContext context) {
diff --git a/build-logic/docs-core/src/test/groovy/grails/doc/asciidoc/AsciiDocEngineSpec.groovy b/build-logic/docs-core/src/test/groovy/grails/doc/asciidoc/AsciiDocEngineSpec.groovy
new file mode 100644
index 0000000000..5969aee82a
--- /dev/null
+++ b/build-logic/docs-core/src/test/groovy/grails/doc/asciidoc/AsciiDocEngineSpec.groovy
@@ -0,0 +1,85 @@
+/*
+ *  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
+ *
+ *    https://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 grails.doc.asciidoc
+
+import org.asciidoctor.log.LogHandler
+import org.asciidoctor.log.LogRecord
+import org.radeox.engine.context.BaseInitialRenderContext
+import spock.lang.Shared
+import spock.lang.Specification
+
+class AsciiDocEngineSpec extends Specification {
+
+    @Shared
+    AsciiDocEngine engine = new AsciiDocEngine(new BaseInitialRenderContext())
+
+    @Shared
+    List<String> messages = []
+
+    void setupSpec() {
+        engine.asciidoctor.registerLogHandler({ LogRecord record -> messages << record.message } as LogHandler)
+    }
+
+    void setup() {
+        messages.clear()
+    }
+
+    void 'a section whose headings start below level 1 renders without a sequence warning'() {
+        when:
+        String html = engine.render('''
+            |////
+            |License header, as at the top of every guide section
+            |////
+            |
+            |Introduction to the section.
+            |
+            |==== Deeply nested heading
+            |
+            |Content
+            |
+            |==== Sibling heading
+            |
+            |More content
+            |'''.stripMargin(), null)
+
+        then:
+        messages.empty
+
+        and: 'the headings keep the level they were written at'
+        html.contains('<div class="sect3">')
+        html.contains('<h4 id="_deeply_nested_heading">Deeply nested heading</h4>')
+        html.contains('<h4 id="_sibling_heading">Sibling heading</h4>')
+    }
+
+    void 'a heading that skips a level inside the section is still reported'() {
+        when:
+        engine.render('''
+            |Introduction to the section.
+            |
+            |=== Section heading
+            |
+            |===== Skipped a level
+            |
+            |Content
+            |'''.stripMargin(), null)
+
+        then:
+        messages == ['section title out of sequence: expected level 3, got level 4']
+    }
+}
diff --git a/grails-data-hibernate5/docs/build.gradle b/grails-data-hibernate5/docs/build.gradle
index cb8da1329e..5ec29c3f35 100644
--- a/grails-data-hibernate5/docs/build.gradle
+++ b/grails-data-hibernate5/docs/build.gradle
@@ -84,7 +84,11 @@ tasks.named('asciidoctor', AsciidoctorTask) { AsciidoctorTask it ->
             'migrationPluginExamplesDir': project.layout.projectDirectory.dir('src/docs/asciidoc/databaseMigration').asFile.relativePath(rootProject.findProject(':grails-data-hibernate5-dbmigration').layout.projectDirectory.asFile),
             'migrationPluginGroupId': rootProject.findProject(':grails-data-hibernate5-dbmigration').group,
             'migrationPluginArtifactId': rootProject.findProject(':grails-data-hibernate5-dbmigration').name,
-            'liquibaseHibernate5Version': liquibaseHibernate5Version
+            'liquibaseHibernate5Version': liquibaseHibernate5Version,
+            // Every file under the source directory is also converted on its own, including the ones that only
+            // exist to be included by index.adoc. Their headings start at the depth they are included at, so they
+            // are converted as fragments. The '@' lets index.adoc switch this back off to keep its own checks.
+            'fragment': '@'
     ]
 }
 
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc
index a97a16969f..18115be852 100644
--- a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc
@@ -105,7 +105,7 @@ include::ref/Maintenance Scripts/dbm-tag.adoc[]
 include::ref/Maintenance Scripts/dbm-validate.adoc[]
 
 [[ref-rollback-scripts]]
-=== Rollback Scripts
+==== Rollback Scripts
 
 [[ref-rollback-scripts-dbm-future-rollback-sql]]
 include::ref/Rollback Scripts/dbm-future-rollback-sql.adoc[]
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/index.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/index.adoc
index db14ad08ae..8d840c970e 100644
--- a/grails-data-hibernate5/docs/src/docs/asciidoc/index.adoc
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/index.adoc
@@ -24,6 +24,7 @@ under the License.
 :last-update-label!:
 :toc:
 :sectnums:
+:!fragment:
 
 [[introduction]]
 == Introduction
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics.adoc
index 3377ea7602..238e2b9096 100644
--- a/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics.adoc
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics.adoc
@@ -40,7 +40,7 @@ Book.withTransaction {
 ----
 
 
-==== Transactional Write-Behind
+=== Transactional Write-Behind
 
 
 A useful feature of Hibernate over direct JDBC calls and even other frameworks is that when you call link:../api/org/grails/datastore/gorm/GormEntity.html#save()[save()] or link:../api/org/grails/datastore/gorm/GormEntity.html#delete()[delete()] it does not necessarily perform any SQL operations *at that point*. Hibernate batches up SQL statements and executes them as late as possible, often at the end of the request when flushing and closing the session.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics/cascades.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics/cascades.adoc
index 03cec30bba..654f9e6607 100644
--- a/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics/cascades.adoc
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/persistenceBasics/cascades.adoc
@@ -61,6 +61,7 @@ airport.delete()
 
 The above examples are called transitive persistence and are controlled via the `belongsTo` and the cascade policy. If I were to remove `belongsTo` then the above cascading deletion code *would not work*.
 
+[discrete]
 ===== Unidirectional Many-To-One without belongsTo
 
 For example, consider the following domain model:
@@ -137,6 +138,7 @@ class Author {
 
 The above example will configure the cascade policy to cascade saves and updates, but not deletes.
 
+[discrete]
 ===== Bidirectional one-to-many with belongsTo
 
 
@@ -155,6 +157,7 @@ In the case of a bidirectional one-to-many where the many side defines a `belong
 What this means is that whenever an instance of `A` is saved or updated. So will any instances of `B`. And, critically, whenever any instance of `A` is *deleted* so will all the associated instances of `B`!
 
 
+[discrete]
 ===== Unidirectional One-to-Many
 
 
@@ -174,6 +177,7 @@ Since the `belongsTo` is not defined, this means that saves and updates will be
 
 Only when you define `belongsTo` in `B` or alter the cascading strategy of `A` will deletes be cascaded.
 
+[discrete]
 ===== Bidirectional One-to-Many, no belongsTo
 
 
@@ -202,6 +206,7 @@ class A {
 }
 ----
 
+[discrete]
 ===== Unidirectional Many-to-One with belongsTo
 
 
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/programmaticTransactions.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/programmaticTransactions.adoc
index c557829f3a..4373065830 100644
--- a/grails-data-hibernate5/docs/src/docs/asciidoc/programmaticTransactions.adoc
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/programmaticTransactions.adoc
@@ -21,7 +21,7 @@ GORM's transaction management is built on Spring and uses Spring's Transaction a
 
 However, GORM classes have been enhanced to make this simpler with the link:../api/org/grails/datastore/gorm/GormEntity.html#withTransaction(groovy.lang.Closure)[withTransaction(Closure)]  method. This method has a single parameter, a Closure, which has a single parameter which is a Spring https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/TransactionStatus.html[TransactionStatus] instance.
 
-==== Using the withTransaction Method
+=== Using the withTransaction Method
 
 Here's an example of using `withTransaction` in a controller methods:
 
@@ -56,7 +56,7 @@ You can also use "save points" to rollback a transaction to a particular point i
 
 The `withTransaction` method deals with the begin/commit/rollback logic for you within the scope of the block.
 
-==== Using TransactionService
+=== Using TransactionService
 
 Since GORM 6.1, if you need more flexibility then instead you can instead take advantage of the link:../api/grails/gorm/transactions/TransactionService.html[TransactionService], which can be obtained by looking it up from from the `HibernateDatastore`:
 
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/querying.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/querying.adoc
index c4c223e53a..78569bcfa4 100644
--- a/grails-data-hibernate5/docs/src/docs/asciidoc/querying.adoc
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/querying.adoc
@@ -29,7 +29,7 @@ In addition, Groovy's ability to manipulate collections with https://groovy.code
 However, let's start with the basics.
 
 
-==== Listing instances
+=== Listing instances
 
 
 Use the link:../api/org/grails/datastore/gorm/GormEntity.html#list()[list()] method to obtain all instances of a given class:
@@ -56,7 +56,7 @@ def books = Book.list(sort:"title", order:"asc")
 Here, the `sort` argument is the name of the domain class property that you wish to sort on, and the `order` argument is either `asc` for *asc*ending or `desc` for *desc*ending.
 
 
-==== Retrieval by Database  Identifier
+=== Retrieval by Database  Identifier
 
 
 The second basic form of retrieval is by database identifier using the link:../api/org/grails/datastore/gorm/GormEntity.html#get(java.io.Serializable)[get(id)] method: