diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2015-06-04 07:31:04 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2015-06-04 07:58:39 +0200 |
commit | f6d65e533c62f6deb21342d4901ece24497b433e (patch) | |
tree | f28adf0362d14bcd448f7b65a7aaf38650f923aa /vendor/oojs/oojs-ui/bin/docparser.rb | |
parent | c27b2e832fe25651ef2410fae85b41072aae7519 (diff) |
Update to MediaWiki 1.25.1
Diffstat (limited to 'vendor/oojs/oojs-ui/bin/docparser.rb')
-rw-r--r-- | vendor/oojs/oojs-ui/bin/docparser.rb | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/vendor/oojs/oojs-ui/bin/docparser.rb b/vendor/oojs/oojs-ui/bin/docparser.rb new file mode 100644 index 00000000..9f58549b --- /dev/null +++ b/vendor/oojs/oojs-ui/bin/docparser.rb @@ -0,0 +1,243 @@ +require 'pp' +require 'json' + +def parse_dir dirname + Dir.entries(dirname).map{|filename| + if filename == '.' || filename == '..' + nil + else + parse_any_path "#{dirname}/#{filename}" + end + }.compact.inject(:+) +end + +def cleanup_class_name class_name + class_name.sub(/OO\.ui\./, '') +end + +def parse_file filename + if filename !~ /\.(php|js)$/ + return nil + end + filetype = filename[/\.(php|js)$/, 1].to_sym + + text = File.read filename, encoding: 'utf-8' + + # ewwww + # some docblocks are missing and we really need them + text = text.sub(/(?<!\*\/\n)^class/, "/**\n*/\nclass") + # text = text.sub('public static $targetPropertyName', "/**\n*/\npublic static $targetPropertyName") + + # find all documentation blocks, together with the following line (unless it contains another docblock) + docblocks = text.scan(/\/\*\*[\s\S]+?\*\/\n[ \t]*(?:(?=\/\*\*)|.*)/) + + current_class = nil + output = [] + previous_item = {} # dummy + + docblocks.each{|d| + kind = nil + previous_item = data = { + name: nil, + description: '', + parent: nil, + mixins: [], + methods: [], + properties: [], + events: [], + params: [], + config: [], + visibility: :public, + type: nil, + } + valid_for_all = %w[name description].map(&:to_sym) + valid_per_kind = { + class: valid_for_all + %w[parent mixins methods properties events abstract].map(&:to_sym), + method: valid_for_all + %w[params config return visibility static].map(&:to_sym), + property: valid_for_all + %w[type static].map(&:to_sym), + event: valid_for_all + %w[params].map(&:to_sym), + } + + js_class_constructor = false + js_class_constructor_desc = '' + ignore = false + + comment, code_line = d.split '*/' + comment.split("\n").each{|c| + next if c.strip == '/**' + c.sub!(/^[ \t]*\*[ \t]?/, '') # strip leading * + + m = c.match(/^@(\w+)[ \t]*(.*)/) + unless m + previous_item[:description] << c + "\n" + next + end + + keyword, content = m.captures + + # handle JS class/constructor conundrum + if keyword == 'class' || keyword == 'constructor' + js_class_constructor = true + end + + case keyword + when 'constructor' + # handle JS class/constructor conundrum + js_class_constructor_desc = data[:description] + data[:description] = '' + kind = :method + when 'class' + kind = :class + when 'method' + kind = :method + when 'property', 'var' + kind = :property + m = content.match(/^\{?(.+?)\}?( .+)?$/) + if m.captures + type, description = m.captures + data[:type] = type + data[:description] = description if description + end + when 'event' + kind = :event + data[:name] = content.strip + when 'extends' + data[:parent] = cleanup_class_name(content.strip) + when 'mixins' + data[:mixins] << cleanup_class_name(content.strip) + when 'param' + case filetype + when :js + type, name, default, description = content.match(/^\{(.+?)\} \[?([\w.$]+?)(?:=(.+?))?\]?( .+)?$/).captures + next if type == 'Object' && name == 'config' + data[:params] << {name: name, type: cleanup_class_name(type), description: description || '', default: default} + previous_item = data[:params][-1] + when :php + type, name, config, description = content.match(/^(\S+) \&?\$(\w+)(?:\['(\w+)'\])?( .+)?$/).captures + next if type == 'array' && name == 'config' && !config + if config && name == 'config' + data[:config] << {name: config, type: cleanup_class_name(type), description: description || ''} + previous_item = data[:config][-1] + else + data[:params] << {name: name, type: cleanup_class_name(type), description: description || ''} + previous_item = data[:params][-1] + end + end + when 'cfg' # JS only + type, name, default, description = content.match(/^\{(.+?)\} \[?([\w.$]+?)(?:=(.+?))?\]?( .+)?$/).captures + data[:config] << {name: name, type: cleanup_class_name(type), description: description || '', default: default} + previous_item = data[:config][-1] + when 'return' + case filetype + when :js + type, description = content.match(/^\{(.+?)\}( .+)?$/).captures + data[:return] = {type: cleanup_class_name(type), description: description || ''} + previous_item = data[:return] + when :php + type, description = content.match(/^(\S+)( .+)?$/).captures + data[:return] = {type: cleanup_class_name(type), description: description || ''} + previous_item = data[:return] + end + when 'private' + data[:visibility] = :private + when 'protected' + data[:visibility] = :protected + when 'static' + data[:static] = true + when 'abstract' + data[:abstract] = true + when 'ignore' + ignore = true + when 'inheritable', 'deprecated', 'singleton', 'throws', + 'chainable', 'fires', 'localdoc', 'inheritdoc', 'member', + 'see' + # skip + else + fail "unrecognized keyword: #{keyword}" + end + } + + next if ignore + + if code_line && code_line.strip != '' + case filetype + when :js + m = code_line.match(/(?:(static|prototype)\.)?(\w+) =/) + kind_, name = m.captures + data[:static] = true if kind_ == 'static' + kind = {'static' => :property, 'prototype' => :method}[ kind_.strip ] if kind_ && !kind + data[:name] = cleanup_class_name(name) + when :php + m = code_line.match(/ + \s* + (?:(public|protected|private)\s)? + (?:(static)\s)?(function\s|class\s|\$) + (\w+) + (?:\sextends\s(\w+))? + /x) + visibility, static, kind_, name, parent = m.captures + kind = {'$' => :property, 'function' => :method, 'class' => :class}[ kind_.strip ] + data[:visibility] = {'private' => :private, 'protected' => :protected, 'public' => :public}[ visibility ] || :public + data[:static] = true if static + data[:parent] = cleanup_class_name(parent) if parent + data[:name] = cleanup_class_name(name) + end + end + + # handle JS class/constructor conundrum + if kind == :class || js_class_constructor + if current_class + output << current_class + end + current_class = data.select{|k, _v| valid_per_kind[:class].include? k } + current_class[:description] = js_class_constructor_desc if js_class_constructor_desc != '' + previous_item = current_class + end + + # standardize + if data[:name] == '__construct' || js_class_constructor + data[:name] = '#constructor' + end + + # put into the current class + if kind && kind != :class + keys = { + method: :methods, + property: :properties, + event: :events, + } + current_class[keys[kind]] << data.select{|k, _v| valid_per_kind[kind].include? k } + previous_item = current_class[keys[kind]] + end + } + + # this is evil, assumes we only have one class in a file, but we'd need a proper parser to do it better + if current_class + current_class[:mixins] += + text.scan(/\$this->mixin\( .*?new (\w+)\( \$this/).flatten.map(&method(:cleanup_class_name)) + end + + output << current_class if current_class + output +end + +def parse_any_path path + if File.directory? path + parse_dir path + else + parse_file path + end +end + +if __FILE__ == $PROGRAM_NAME + if ARGV.empty? || ARGV == ['-h'] || ARGV == ['--help'] + $stderr.puts "usage: ruby #{$PROGRAM_NAME} <files...>" + $stderr.puts " ruby #{$PROGRAM_NAME} src > docs-js.json" + $stderr.puts " ruby #{$PROGRAM_NAME} php > docs-php.json" + else + out = JSON.pretty_generate ARGV.map{|a| parse_any_path a }.inject(:+) + # ew + out = out.gsub(/\{\s+\}/, '{}').gsub(/\[\s+\]/, '[]') + puts out + end +end |