• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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