1# Copyright (c) 2016-2018 The Khronos Group Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal' 16 17include ::Asciidoctor 18 19module Asciidoctor 20 21class ValidUsageToJsonPreprocessorReader < PreprocessorReader 22 def process_line line 23 if line.start_with?( 'ifdef::VK_', 'ifndef::VK_', 'endif::VK_') 24 # Turn extension ifdefs into list items for when we're processing VU later. 25 return super('* ' + line) 26 else 27 return super(line) 28 end 29 end 30end 31 32# Preprocessor hook to iterate over ifdefs to prevent them from affecting asciidoctor's processing. 33class ValidUsageToJsonPreprocessor < Extensions::Preprocessor 34 35 def process document, reader 36 # Create a new reader to return, which handles turning the extension ifdefs into something else. 37 extension_preprocessor_reader = ValidUsageToJsonPreprocessorReader.new(document, reader.lines) 38 39 detected_vuid_list = [] 40 extension_stack = [] 41 in_validusage = :outside 42 43 # Despite replacing lines in the overridden preprocessor reader, a 44 # FIXME in Reader#peek_line suggests that this doesn't work, the new lines are simply discarded. 45 # So we just run over the new lines and do the replacement again. 46 new_lines = extension_preprocessor_reader.read_lines().flat_map do | line | 47 48 # Track whether we're in a VU block or not 49 if line.start_with?(".Valid Usage") 50 in_validusage = :about_to_enter # About to enter VU 51 elsif in_validusage == :about_to_enter and line == '****' 52 in_validusage = :inside # Entered VU block 53 extension_stack.each 54 elsif in_validusage == :inside and line == '****' 55 in_validusage = :outside # Exited VU block 56 end 57 58 # Track extensions outside of the VU 59 if in_validusage == :outside and line.start_with?( 'ifdef::VK_', 'ifndef::VK_') and line.end_with?( '[]') 60 extension_stack.push line 61 elsif in_validusage == :outside and line.start_with?( 'endif::VK_') 62 extension_stack.pop 63 end 64 65 if in_validusage == :inside and line == '****' 66 # Write out the extension stack as bullets after this line 67 returned_lines = [line] 68 extension_stack.each do | extension | 69 returned_lines << ('* ' + extension) 70 end 71 returned_lines 72 elsif in_validusage == :inside and line.start_with?( 'ifdef::VK_', 'ifndef::VK_', 'endif::VK_') and line.end_with?('[]') 73 # Turn extension ifdefs into list items for when we're processing VU later. 74 ['* ' + line] 75 elsif in_validusage == :outside and line.start_with?( 'ifdef::VK_', 'ifndef::VK_', 'endif::VK_') and line.end_with?('[]') 76 # Remove the extension defines from the new lines, as we've dealt with them 77 [] 78 elsif line.match(/\[\[(VUID-([^-]+)-[^\]]+)\]\]/) 79 # Add all the VUIDs into an array to guarantee they're all caught later. 80 detected_vuid_list << line.match(/(VUID-([^-]+)-[^\]]+)/)[0] 81 [line] 82 else 83 [line] 84 end 85 end 86 87 # Stash the detected vuids into a document attribute 88 document.set_attribute('detected_vuid_list', detected_vuid_list.join("\n")) 89 90 # Return a new reader after preprocessing 91 Reader.new(new_lines) 92 end 93end 94 95require 'json' 96class ValidUsageToJsonTreeprocessor < Extensions::Treeprocessor 97 def process document 98 map = {} 99 100 # Get the global vuid list 101 detected_vuid_list = document.attr('detected_vuid_list').split("\n") 102 103 map['version info'] = { 104 'schema version' => 2, 105 'api version' => document.attr('revnumber'), 106 'comment' => document.attr('revremark'), 107 'date' => document.attr('revdate') 108 } 109 110 map['validation'] = {} 111 112 # Need to find all valid usage blocks within a structure or function ref page section 113 114 # Find all the open blocks 115 (document.find_by context: :open).each do |openblock| 116 # Filter out anything that's not a refpage 117 if openblock.attributes['refpage'] 118 if openblock.attributes['type'] == 'structs' || openblock.attributes['type'] == 'protos' 119 parent = openblock.attributes['refpage'] 120 # Find all the sidebars 121 (openblock.find_by context: :sidebar).each do |sidebar| 122 # Filter only the valid usage sidebars 123 if sidebar.title == "Valid Usage" || sidebar.title == "Valid Usage (Implicit)" 124 # There should be only one block - but just in case... 125 sidebar.blocks.each do |list| 126 extensions = [] 127 # Iterate through all the items in the block, tracking which extensions are enabled/disabled. 128 list.blocks.each do |item| 129 if item.text.start_with?('ifdef::VK_') 130 extensions << '(' + item.text[('ifdef::'.length)..-3] + ')' # Look for "ifdef" directives and add them to the list of extensions 131 elsif item.text.start_with?('ifndef::VK_') 132 extensions << '!(' + item.text[('ifndef::'.length)..-3] + ')' # Ditto for "ifndef" directives 133 elsif item.text.start_with?('endif::VK_') 134 extensions.slice!(-1) # Remove the last element when encountering an endif 135 else 136 match = /<a id=\"(VUID-([^-]+)-[^"]+)\"[^>]*><\/a>(.*)/m.match(item.text) # Otherwise, look for the VUID. 137 if (match != nil) 138 vuid = match[1] 139 parentid = match[2] 140 text = match[3].gsub("\n", ' ') # Have to forcibly remove newline characters; for some reason they're translated to the literally '\n' when converting to json. 141 142 # Check parentid from VUID matches the parent - warn if not 143 if parentid != parent 144 puts "VU Extraction Treeprocessor: WARNING - Valid Usage statement VUID parent conflicts with parent ref page. Expected parent of '#{parent}' but VUID was '#{vuid}'." 145 end 146 147 # Delete the vuid from the detected vuid list, so we know it's been extracted successfully 148 detected_vuid_list.delete(vuid) 149 150 # Generate the table entry 151 entry = {'vuid' => vuid, 'text' => text} 152 153 # Initialize the database if necessary 154 if map['validation'][parent] == nil 155 map['validation'][parent] = {} 156 end 157 158 # Figure out the name of the section the entry will be added in 159 if extensions == [] 160 entry_section = 'core' 161 else 162 entry_section = extensions.join('+') 163 end 164 165 # Initialize the entry section if necessary 166 if map['validation'][parent][entry_section] == nil 167 map['validation'][parent][entry_section] = [] 168 end 169 170 # Check for duplicate entries 171 if map['validation'][parent][entry_section].include? entry 172 puts "VU Extraction Treeprocessor: WARNING - Valid Usage statement '#{entry}' is duplicated in the specification with VUID '#{vuid}'." 173 end 174 175 # Add the entry 176 map['validation'][parent][entry_section] << entry 177 178 else 179 puts "VU Extraction Treeprocessor: WARNING - Valid Usage statement without a VUID found: " 180 puts item.text 181 end 182 end 183 end 184 end 185 end 186 end 187 188 end 189 end 190 end 191 192 # Print out a list of VUIDs that were not extracted 193 if detected_vuid_list.length != 0 194 puts 'Some VUIDs were not successfully extracted from the specification.' 195 puts 'This is usually down to them appearing outside of a refpage (open)' 196 puts 'block; try checking where they are included.' 197 puts 'The following VUIDs were not extracted:' 198 detected_vuid_list.each do |vuid| 199 puts "\t * " + vuid 200 end 201 end 202 203 # Generate the json 204 json = JSON.pretty_generate(map) 205 outfile = document.attr('json_output') 206 207 # Verify the json against the schema, if the required gem is installed 208 begin 209 require 'json-schema' 210 211 # Read the schema in and validate against it 212 schema = IO.read(File.join(File.dirname(__FILE__), 'vu_schema.json')) 213 errors = JSON::Validator.fully_validate(schema, json, :errors_as_objects => true) 214 215 # Output errors if there were any 216 if errors != [] 217 puts 'VU Extraction JSON Validator: WARNING - Validation of the json schema failed' 218 puts 219 puts 'It is likely that there is an invalid or malformed entry in the specification text,' 220 puts 'see below error messages for details, and use their VUIDs and text to correlate them to their location in the specification.' 221 puts 222 223 errors.each do |error| 224 puts error.to_s 225 end 226 end 227 rescue LoadError 228 puts 'VU Extraction JSON Validator: WARNING - "json-schema" gem missing - skipping verification of json output' 229 # error handling code here 230 end 231 232 # Write the file and exit - no further processing required. 233 IO.write(outfile, json) 234 exit! 0 235 end 236end 237end 238