• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016-2023 The Khronos Group Inc.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
6
7include ::Asciidoctor
8
9module Asciidoctor
10
11require 'json'
12class ValidUsageToJsonTreeprocessor < Extensions::Treeprocessor
13  def process document
14    map = {}
15
16    map['version info'] = {
17      'schema version' => 2,
18      'api version' => document.attr('revnumber'),
19      'comment' => document.attr('revremark'),
20      'date' => document.attr('revdate')
21    }
22
23    map['validation'] = {}
24
25    parent = ''
26    error_found = false
27
28
29
30    # Iterate through all blocks and process valid usage blocks
31    # Use find_by so that sub-blocks that this script processes can be skipped
32    document.find_by do |block|
33
34      # Keep track of all attributes defined throughout the document, as Asciidoctor will not do this automatically.
35      # See https://discuss.asciidoctor.org/asciidoctorj-and-document-attributes-tp5960p6525.html
36      document.playback_attributes(block.attributes)
37
38      # Track the parent block for each subsequent valid usage block
39      if block.context == :open and block.attributes['refpage']
40        parent = block.attributes['refpage']
41      end
42
43      # Filter out anything that is not a refpage
44      if block.context == :sidebar && (block.title == "Valid Usage" || block.title == "Valid Usage (Implicit)")
45
46        # Iterate through all the VU lists in each block
47        block.blocks.each do |list|
48
49          # Play back list attributes
50          document.playback_attributes(list.attributes)
51
52          # Iterate through all the items in the block, tracking which extensions are enabled/disabled.
53          list.blocks.each do |item|
54
55            # Attribute definitions split lists, so no need to play back attributes between list items
56
57            # Look for converted anchors
58            match = /<a id=\"(VUID-[^"]+)\"[^>]*><\/a>(.*)/m.match(item.text)
59
60            if (match != nil)
61              vuid     = match[1]
62              text     = match[2]
63
64              # Remove newlines present in the asciidoctor source
65              text.gsub!("\n", ' ')
66
67              # Append text for all the subbullets
68              text += item.content
69
70              # Strip any excess leading/trailing whitespace
71              text.strip!
72
73              # Generate the table entry
74              entry = {'vuid' => vuid, 'text' => text, 'page' => 'vkspec' }
75
76              # Initialize the database if necessary
77              if map['validation'][parent] == nil
78                map['validation'][parent] = {}
79              end
80
81              # For legacy schema reasons, put everything in "core" entry section
82              entry_section = 'core'
83
84              # Initialize the entry section if necessary
85              if map['validation'][parent][entry_section] == nil
86                map['validation'][parent][entry_section] = []
87              end
88
89              # Check for duplicate entries
90              if map['validation'][parent][entry_section].include? entry
91                error_found = true
92                puts "VU Extraction Treeprocessor: ERROR - Valid Usage statement '#{entry}' is duplicated in the specification with VUID '#{vuid}'."
93              end
94
95              # Add the entry
96              map['validation'][parent][entry_section] << entry
97            else
98              puts "VU Extraction Treeprocessor: WARNING - Valid Usage statement without a VUID found: "
99              puts item.text
100            end
101          end
102        end
103        # This block's sub blocks have been handled through iteration, so return true to avoid the main loop re-processing them
104        true
105      else
106        # This block was not what we were looking for, so let asciidoctor continue iterating through its sub blocks
107        false
108      end
109    end
110
111
112    # Generate the json
113    json = JSON.pretty_generate(map)
114    outfile = document.attr('json_output')
115
116    # Verify the json against the schema, if the required gem is installed
117    begin
118      require 'json-schema'
119
120      # Read the schema in and validate against it
121      schema = IO.read(File.join(File.dirname(__FILE__), 'vu_schema.json'))
122      errors = JSON::Validator.fully_validate(schema, json, :errors_as_objects => true)
123
124      # Output errors if there were any
125      if errors != []
126        error_found = true
127        puts 'VU Extraction JSON Validator: ERROR - Validation of the json schema failed'
128        puts
129        puts 'It is likely that there is an invalid or malformed entry in the specification text,'
130        puts 'see below error messages for details, and use their VUIDs and text to correlate them to their location in the specification.'
131        puts
132
133        errors.each do |error|
134          puts error.to_s
135        end
136      end
137    rescue LoadError
138      puts 'VU Extraction JSON Validator: WARNING - "json-schema" gem missing - skipping verification of json output'
139      # error handling code here
140    end
141
142    # Write the file and exit - no further processing required.
143    IO.write(outfile, json)
144
145    if (error_found)
146      exit! 1
147    end
148    exit! 0
149  end
150end
151end
152