# Copyright 2016-2023 The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal' include ::Asciidoctor module Asciidoctor require 'json' class ValidUsageToJsonTreeprocessor < Extensions::Treeprocessor def process document map = {} map['version info'] = { 'schema version' => 2, 'api version' => document.attr('revnumber'), 'comment' => document.attr('revremark'), 'date' => document.attr('revdate') } map['validation'] = {} parent = '' error_found = false # Iterate through all blocks and process valid usage blocks # Use find_by so that sub-blocks that this script processes can be skipped document.find_by do |block| # Keep track of all attributes defined throughout the document, as Asciidoctor will not do this automatically. # See https://discuss.asciidoctor.org/asciidoctorj-and-document-attributes-tp5960p6525.html document.playback_attributes(block.attributes) # Track the parent block for each subsequent valid usage block if block.context == :open and block.attributes['refpage'] parent = block.attributes['refpage'] end # Filter out anything that is not a refpage if block.context == :sidebar && (block.title == "Valid Usage" || block.title == "Valid Usage (Implicit)") # Iterate through all the VU lists in each block block.blocks.each do |list| # Play back list attributes document.playback_attributes(list.attributes) # Iterate through all the items in the block, tracking which extensions are enabled/disabled. list.blocks.each do |item| # Attribute definitions split lists, so no need to play back attributes between list items # Look for converted anchors match = /]*><\/a>(.*)/m.match(item.text) if (match != nil) vuid = match[1] text = match[2] # Remove newlines present in the asciidoctor source text.gsub!("\n", ' ') # Append text for all the subbullets text += item.content # Strip any excess leading/trailing whitespace text.strip! # Generate the table entry entry = {'vuid' => vuid, 'text' => text, 'page' => 'vkspec' } # Initialize the database if necessary if map['validation'][parent] == nil map['validation'][parent] = {} end # For legacy schema reasons, put everything in "core" entry section entry_section = 'core' # Initialize the entry section if necessary if map['validation'][parent][entry_section] == nil map['validation'][parent][entry_section] = [] end # Check for duplicate entries if map['validation'][parent][entry_section].include? entry error_found = true puts "VU Extraction Treeprocessor: ERROR - Valid Usage statement '#{entry}' is duplicated in the specification with VUID '#{vuid}'." end # Add the entry map['validation'][parent][entry_section] << entry else puts "VU Extraction Treeprocessor: WARNING - Valid Usage statement without a VUID found: " puts item.text end end end # This block's sub blocks have been handled through iteration, so return true to avoid the main loop re-processing them true else # This block was not what we were looking for, so let asciidoctor continue iterating through its sub blocks false end end # Generate the json json = JSON.pretty_generate(map) outfile = document.attr('json_output') # Verify the json against the schema, if the required gem is installed begin require 'json-schema' # Read the schema in and validate against it schema = IO.read(File.join(File.dirname(__FILE__), 'vu_schema.json')) errors = JSON::Validator.fully_validate(schema, json, :errors_as_objects => true) # Output errors if there were any if errors != [] error_found = true puts 'VU Extraction JSON Validator: ERROR - Validation of the json schema failed' puts puts 'It is likely that there is an invalid or malformed entry in the specification text,' puts 'see below error messages for details, and use their VUIDs and text to correlate them to their location in the specification.' puts errors.each do |error| puts error.to_s end end rescue LoadError puts 'VU Extraction JSON Validator: WARNING - "json-schema" gem missing - skipping verification of json output' # error handling code here end # Write the file and exit - no further processing required. IO.write(outfile, json) if (error_found) exit! 1 end exit! 0 end end end