%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/vendor/oojs/oojs-ui/bin/ |
| Current File : /www/varak.net/wiki.varak.net/vendor/oojs/oojs-ui/bin/docparser.rb |
require 'pp'
require 'json'
$bad_input = false
def bad_input file, text
$bad_input = true
$stderr.puts "#{file}: unrecognized input: #{text}"
end
def parse_dir dirname
Dir.entries(dirname).sort.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\./, '').sub(/mixin\./, '')
end
def extract_default_from_description item
m = item[:description].match(/\(default: (.+?)\)\s*?$/i)
return if !m
# modify `item` in-place
item[:default] = m[1]
item[:description] = m.pre_match
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|trait)/, "/**\n*/\n\\1")
# 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 mixin].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 = ''
php_trait_constructor = false
ignore = false
comment, code_line = d.split '*/'
comment.split("\n").each{|comment_line|
next if comment_line.strip == '/**'
comment_line.sub!(/^[ \t]*\*[ \t]?/, '') # strip leading '*' and whitespace
m = comment_line.match(/^@([\w-]+)(?:[ \t]+(.+))?/)
if !m
# this is a continuation of previous item's description
previous_item[:description] << comment_line + "\n"
if filetype == :php
extract_default_from_description(previous_item)
end
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
data[:name] = cleanup_class_name(content.strip) if content && !content.strip.empty?
when 'method'
kind = :method
when 'property', 'var'
kind = :property
m = content.match(/^\{?(.+?)\}?( .+)?$/)
if !m
bad_input filename, comment_line
next
end
type, description = m.captures
data[:type] = type
data[:description] = description if description
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
if filetype == :php
extract_default_from_description(previous_item)
end
end
when 'cfg' # JS only
m = content.match(/^\{(.+?)\} \[?([\w.$]+?)(?:=(.+?))?\]?( .+)?$/)
if !m
bad_input filename, comment_line
next
end
type, name, default, description = m.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
m = content.match(/^\{(.+?)\}( .+)?$/)
when :php
m = content.match(/^(\S+)( .+)?$/)
end
if !m
bad_input filename, comment_line
next
end
type, description = m.captures
data[:return] = {type: cleanup_class_name(type), description: description || ''}
previous_item = data[:return]
when 'private'
data[:visibility] = :private
when 'protected'
data[:visibility] = :protected
when 'static'
data[:static] = true
when 'abstract'
data[:abstract] = true
when 'ignore', 'inheritdoc'
ignore = true
when 'inheritable', 'deprecated', 'singleton', 'throws',
'chainable', 'fires', 'localdoc', 'member',
'see', 'uses', 'param-taint'
# skip
else
bad_input filename, comment_line
next
end
}
next if ignore
if code_line && code_line.strip != ''
case filetype
when :js
m = code_line.match(/(?:(static|prototype|mixin)\.)?(\w+) =/)
if !m
bad_input filename, code_line.strip
next
end
kind_, name = m.captures
data[:static] = true if kind_ == 'static'
kind = {'static' => :property, 'prototype' => :method}[ kind_.strip ] if kind_ && !kind
data[:mixin] = true if kind_ == 'mixin'
data[:name] ||= cleanup_class_name(name)
when :php
m = code_line.match(/
\s*
(?:(public|protected|private)\s)?
(?:(static)\s)?(function\s|class\s|trait\s|\$)
(\w+)
(?:\sextends\s(\w+))?
/x)
if !m
bad_input filename, code_line.strip
next
end
visibility, static, kind_, name, parent = m.captures
kind = {'$' => :property, 'function' => :method, 'class' => :class, 'trait' => :class}[ kind_.strip ]
data[:visibility] = {'private' => :private, 'protected' => :protected, 'public' => :public}[ visibility ] || :public
data[:mixin] = true if kind_.strip == 'trait'
data[:static] = true if static
data[:parent] = cleanup_class_name(parent) if parent
data[:name] ||= cleanup_class_name(name)
php_trait_constructor = true if kind == :method && data[:name] == 'initialize' + current_class[: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
# (also handle fake constructors for traits)
if data[:name] == '__construct' || js_class_constructor || php_trait_constructor
data[:name] = '#constructor'
end
# put into the current class
if kind && kind != :class
keys = {
method: :methods,
property: :properties,
event: :events,
}
if current_class
current_class[keys[kind]] << data.select{|k, _v| valid_per_kind[kind].include? k }
previous_item = current_class[keys[kind]]
end
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(/^[ \t]*use (\w+)(?: ?\{|;)/).flatten.map(&method(:cleanup_class_name))
end
output << current_class if current_class
output
end
def parse_any_path path
if File.directory? path
result = parse_dir path
else
result = parse_file path
end
if $bad_input
$stderr.puts 'Unrecognized inputs encountered, stopping.'
exit 1
end
result
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