private void processBlocks(Document document, boolean listings) {
          document
            // find all listing or literal blocks in the document tree
            .findBy(Map.of(
              "traverse_documents", true,
              "context", listings ? LISTING : LITERAL
            ))
            .stream()
            .map(Block.class::cast)
            .forEach(block -> {
              List<String> lines = block.getLines();

              // trim the remainders of the padding that could have been shortened by indent handling
              // and search for the first line that then starts and ends with the include source marker
              Optional<String> includeSourceMarkerLine = lines
                .stream()
                .filter(line -> line.trim().startsWith(includeSourceMarker) && line.endsWith(includeSourceMarker))
                .findFirst();

              // if a literal block gets source style, it is transformed to a listing block
              // but this attribute still preserves the information that it originally was a literal block
              boolean cloakedLiteral = block.hasAttribute("cloaked-context") && "literal".equals(block.getAttribute("cloaked-context").toString());

              // if no include source marker lines are found, and we are at a listing block,
              // complain that there is only inline code instead of including tested code;
              // if we are at a literal block, just do nothing and don't complain;
              // be aware that a literal block with `source` style in the sources ends up as listing block here
              // but can be identified via the cloaked-context attribute read above.
              if (includeSourceMarkerLine.isEmpty()) {
                if (listings && !cloakedLiteral && !lines.isEmpty()) {
                  log(new LogRecord(WARN, block.getSourceLocation(), "listing with only inline code found; " +
                    "if this is not source from a file, consider using a literal block instead; " +
                    // work-around for https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/752
                    // once it is fixed, check whether the Gradle output contains the source location,
                    // otherwise add the source location instead of the first line here, it is just to find the spot
                    ((block.getSourceLocation() == null)
                      ? "first line: " + lines.get(0)
                      : "")));
                }
                return;
              }

              // remove all include source marker lines from the content
              lines.removeIf(line -> line.trim().startsWith(includeSourceMarker) && line.endsWith(includeSourceMarker));
              block.setLines(lines);

              // construct an AsciiDoc table programmatically that wraps
              // the current block and in AsciiDoc would be looking like
              //
              // [.includeSourceLinkerTable,cols="a,0",frame=none,grid=none]
              // |===
              // |
              // <the current block source>
              // |
              // ^icon:github[2x,link=<GitHub file link>,window=_blank]^
              // ^icon:play[2x,link=<gwcBase>?github=<canonicalPath>,window=_blank]^
              // |===

              // construct the table
              Table table = createTable((StructuralNode) block.getParent());
              table.addRole("includeSourceLinkerTable");
              table.setFrame("none");
              table.setGrid("none");

              // construct the content column
              Column contentColumn = createTableColumn(table, 0);
              contentColumn.setWidth(1);
              // work-around for https://github.com/asciidoctor/asciidoctorj/issues/1265
              contentColumn.setAttribute("colpcwidth", 100, true);
              table.getColumns().add(contentColumn);

              // construct the links column
              Column linksColumn = createTableColumn(table, 1);
              linksColumn.setWidth(0);
              // work-around for https://github.com/asciidoctor/asciidoctorj/issues/1265
              linksColumn.setAttribute("colpcwidth", 0, true);
              table.getColumns().add(linksColumn);

              // add the current block to a cell for the content column
              Document contentCellDocument = createDocument(document);
              contentCellDocument.getBlocks().add(block);
              Cell contentCell = createTableCell(contentColumn, contentCellDocument);

              // construct the cell for the links column
              // first extract the GitHub link from the first include source marker line
              String sourceLink = includeSourceMarkerLine.get().substring(
                includeSourceMarkerLine.get().indexOf(includeSourceMarker) + includeSourceMarkerLength,
                includeSourceMarkerLine.get().length() - includeSourceMarkerLength);
              StringBuilder linksBuilder = new StringBuilder();
              // add the GitHub icon that links to the source file and line on GitHub
              linksBuilder
                .append("^icon:github[2x,link=")
                .append(sourceLink)
                .append(",window=_blank]^");
              // if we are at a literal block, never show the play icon
              // if we are at a listing block, but the `executable` attribute was set to non-`true`, never show the play icon
              // if the `gwc-base` document attribute is not set, never show the play icon
              // if the included file's name does not end with `.groovy`, never show the play icon
              // otherwise show the play icon
              // but if the source link contains `/master/` instead of a commit ID,
              // do not link the play icon, as the gwc cannot handle branch names
              if (listings && !cloakedLiteral && parseBoolean(block.getAttribute("executable", "true").toString())) {
                Object gwcBase = document.getAttribute("gwc-base");
                if (gwcBase != null) {
                  String canonicalPath = sourceLink.substring(sourceLink.indexOf("github.com/") + "github.com/".length());
                  canonicalPath = canonicalPath.substring(0, canonicalPath.lastIndexOf('#'));
                  if (canonicalPath.endsWith(".groovy")) {
                    linksBuilder.append("\n^icon:play[2x");
                    if (!sourceLink.contains("/master/")) {
                      linksBuilder
                        .append(",link=")
                        .append(gwcBase)
                        .append("?github=")
                        .append(canonicalPath)
                        .append(",window=_blank");
                    }
                    linksBuilder.append("]^");
                  }
                }
              }
              Cell linksCell = createTableCell(linksColumn, linksBuilder.toString());

              // add the cells in a row to the table
              Row row = createTableRow(table);
              row.getCells().add(contentCell);
              row.getCells().add(linksCell);
              table.getBody().add(row);

              // replace the current block in the parent by the table-wrapped version
              List<StructuralNode> blocks = ((StructuralNode) block.getParent()).getBlocks();
              blocks.set(blocks.indexOf(block), table);
            });
        }
As we do custom include handling here, we have to reimplement all attribute handling we need.
So verify that only currently supported attributes are used.

@param reader The current reader
@param attributes The attributes used on the include directive
build-logic/asciidoc-extensions/src/main/java/org/spockframework/plugins/asciidoctor/IncludedSourceLinker.java