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).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(/(? :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} " $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