tools/logstash-docgen/lib/logstash/docgen/static_parser.rb (151 lines of code) (raw):

# Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright # ownership. Elasticsearch B.V. licenses this file to you under # the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. module LogStash::Docgen # This class is parsing static content of the main class and # his ancestors, the result would be the description of the plugin and the # actual documentation for each of the option. class StaticParser COMMENTS_IGNORE = Regexp.union( Regexp.new(/encoding: utf-8/i), Regexp.new(/TODO:?/) ) VALID_CLASS_NAME = /^LogStash::(Codecs|Inputs|Filters|Outputs)::(\w+)/ COMMENT_RE = /^ *#(?: (.*)| *$)/ MULTILINE_RE = /(, *$)|(\\$)|(\[ *$)/ ENDLINES_RE = /\r\n|\n/ CLASS_DEFINITION_RE = /^ *class\s(.*) < *(::)?LogStash::(Outputs|Filters|Inputs|Codecs)::(\w+)/ NEW_CLASS_DEFINITION_RE = /module (\w+) module (\w+) class\s(.*) < *(::)?LogStash::(Outputs|Filters|Inputs|Codecs)::(\w+)/ NEW_CLASS_DEFINITION_RE_ML = /\s*class\s(.*) < *(::)?LogStash::(Outputs|Filters|Inputs|Codecs)::(\w+)/ CONFIG_OPTION_RE = /^\s*((mod|base).)?config +[^=].*/ CONFIG_NAME_RE = /^ *config_name .*/ RESET_BUFFER_RE = /^require\s("|')\w+("|')/ def initialize(context) @rules = [ [COMMENT_RE, :parse_comment], [CLASS_DEFINITION_RE, :parse_class_description], [NEW_CLASS_DEFINITION_RE_ML, :parse_new_class_description], [CONFIG_OPTION_RE, :parse_config], [CONFIG_NAME_RE, :parse_config_name], [RESET_BUFFER_RE, :reset_buffer] ] @context = context # Extracting the class name and parsing file # work on the same raw content of the file, we use this cache to make sure # we dont waste resources on reading the content @cached_read = {} reset_buffer end def parse_class_description(class_definition) @context.section = class_definition[3].downcase.gsub(/s$/, '') @context.name = class_definition[1] update_description end def parse_new_class_description(class_definition) @context.section = class_definition[3].downcase.gsub(/s$/, '') @context.name = "LogStash::#{class_definition[3]}::#{class_definition[2]}" update_description end # This is not obvious, but if the plugin define a class before the main class it can trip the buffer def update_description(match = nil) return unless reading_header? description = flush_buffer # can only be change by the main file @context.description = description if !@context.has_description? && main? transition_to_reading_attributes end def parse_config_name(match) if main? name = match[0].match(/config_name\s++["'](\w+)['"]/)[1] @context.config_name = name @context.name = name end end def parse_comment(match) comment = match[1] return if ignore_comment?(comment) @buffer << comment end def parse_config(match) field = match[0] field_name = field.match(/config\s+:(\w+)/)[1] @context.add_config_description(field_name, flush_buffer) end def parse(file, main = false) @main = main main ? transition_to_reading_header() : transition_to_reading_attributes() reset_buffer string = read_file(file) extract_lines(string).each do |line| parse_line(line) end end def transition_to_reading_attributes @state = :reading_attributes end def transition_to_reading_header @state = :reading_header end def reading_header? @state == :reading_header end def main? @main end def parse_line(line) @rules.each do |rule| re, action = rule if match = re.match(line) send(action, match) break end end end def extract_lines(string) buffer = "" string.split(ENDLINES_RE).collect do |line| # Join extended lines if !comment?(line) && multiline?(line) buffer += line.chomp next end line = buffer + line buffer = "" line end end def ignore_comment?(comment) COMMENTS_IGNORE.match(comment) end def comment?(line) line =~ COMMENT_RE end def multiline?(line) line =~ MULTILINE_RE end def flush_buffer content = @buffer.join("\n") reset_buffer content end def reset_buffer(match = nil) @buffer = [] end def read_file(file) @cached_read[file] ||= File.read(file) end # Let's try to extract a meaningful name for the classes # We need to support theses format: # # class LogStash::Inputs::File # legacy # module LogStash module inputs File # module LogStash # .... # module Inputs # ... # class File # new kid on the block def extract_class_name(file) content = read_file(file) legacy_definition = content.match(CLASS_DEFINITION_RE) if legacy_definition.nil? match_data = content.match(NEW_CLASS_DEFINITION_RE) "#{match_data[1]}::#{match_data[2]}::#{match_data[3]}" else if valid_class_name(legacy_definition[1]) legacy_definition[1] else m = content.match(NEW_CLASS_DEFINITION_RE_ML) "LogStash::#{m[3]}::#{m[1]}" end end end def valid_class_name(klass_name) klass_name =~ VALID_CLASS_NAME end end end