# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.
###
# wxRuby3 wxWidgets interface generation templates
###
require 'set'
require 'yaml'
require_relative './base'
require_relative './analyzer'
module WXRuby3
class DocGenerator < Generator
class << self
private
def get_constants_db
script = <<~__SCRIPT
require 'json'
WX_GLOBAL_CONSTANTS=false
require 'wx'
def handle_module(mod, table)
mod.constants.each do |c|
a_const = mod.const_get(c)
if (::Module === a_const || ::Class === a_const) && a_const.name.start_with?('Wx::') # Wx:: Package submodule or Class (possibly Enum)
handle_module(a_const, table[c.to_s] = {})
elsif Wx::Enum === a_const
table[c.to_s] = { type: a_const.class.name.split('::').last, value: "\#{a_const.class}.new(\#{a_const.to_i})" }
elsif !(::Hash === a_const || ::Array === a_const)
table[c.to_s] = { type: a_const.class.name.split('::').last, value: a_const } unless c == :THE_APP
end
end
end
Wx::App.run do
table = { 'Wx' => {}}
handle_module(Wx, table['Wx'])
STDOUT.puts JSON.dump(table)
end
__SCRIPT
STDERR.puts "* executing constants collection script:\n#{script}" if Director.trace?
begin
tmpfile = Tempfile.new('script')
ftmp_name = tmpfile.path.dup
tmpfile << script
tmpfile.close(false)
result = if Director.trace?
Config.instance.run(ftmp_name, capture: :out, verbose: false)
else
Config.instance.run(ftmp_name, capture: :no_err, verbose: false)
end
STDERR.puts "* got constants collection output:\n#{result}" if Director.trace?
begin
db = JSON.load(result)
rescue Exception
File.open('constants_raw.json', "w") { |f| f << result } if Director.verbose?
::Kernel.raise RuntimeError, "Exception loading constants collection result: #{$!.message.slice(0, 512)}", cause: nil
end
File.open('constants.json', "w") { |f| f << JSON.pretty_generate(db) } if Director.verbose?
return db
ensure
File.unlink(ftmp_name)
end
end
def get_constants_xref_db(const_tbl = nil, mods = [])
xref_tbl = {}
(const_tbl || constants_db).each_pair do |constnm, constspec|
unless constspec.has_key?('type')
xref_tbl[constnm] = { 'mod' => mods.join('::'), 'table' => constspec }
xref_tbl.merge!(get_constants_xref_db(constspec, mods + [constnm]))
else
xref_tbl[constnm] = constspec.merge({'mod' => mods.join('::') })
end
end
File.open('constants_xrefs.json', "w") { |f| f << JSON.pretty_generate(xref_tbl) } if Director.verbose?
xref_tbl
end
public
def constants_db
@constants_db ||= get_constants_db
end
def constants_xref_db
@constants_xref_db ||= get_constants_xref_db
end
end
class XMLTransformer
include DirectorSpecsHelper
private
def event_section(f = true)
@event_section = !!f
end
def event_section?
!!@event_section
end
def event_list(f = true)
@event_list = !!f
end
def event_list?
!!@event_list
end
def no_ref(&block)
was_no_ref = @no_ref
@no_ref = true
begin
return block.call
ensure
@no_ref = was_no_ref
end
end
def no_ref?
!!@no_ref
end
def no_idents(&block)
was_no_idents = @no_idents
@no_idents = true
begin
return block.call
ensure
@no_idents = was_no_idents
end
end
def no_idents?
!!@no_idents
end
def _ident_to_ref(idstr)
idstr.sub(/(.*)(\(.*\))?/) { |_| "{#{$1}}#{$2}" }
end
def text_to_doc(node)
text = node.text
# handle left-over doxygen tags
text.gsub!(/#(\w)/, '\#\1')
text.gsub!(/@(end)?code/, '')
text.gsub!('@subsection', '##')
text = '' if text.strip == '##' # no empty headings
text.gsub!('@remarks', '')
text.gsub!(/@see.*\n/, '')
text.gsub!('@ref', '')
text.gsub!(/(\W|\A)nullptr(\W|\Z)/, '\1nil\2')
unless no_ref?
# auto create references for any ids explicitly declared such
text.gsub!(/\W?(wx\w+(::\w+)?(\(.*\))?)/) do |s|
if $1 == 'wxWidgets'
s
else
if s==$1
doc_id, known_id = _ident_str_to_doc($1)
known_id ? _ident_to_ref(doc_id) : doc_id
else
doc_id, known_id = _ident_str_to_doc($1)
"#{s[0]}#{known_id ? _ident_to_ref(doc_id) : doc_id}"
end
end
end
text.gsub!(/WX(K_[A-Z]+)/) { "{Wx::KeyCode::#{$1}}"}
end
if event_section?
case text
when /Event macros for events emitted by this class:/
event_list(true)
'Event handler methods for events emitted by this class:'
when /Event macros:/
event_list(true)
'Event handler methods:'
else
text
end
else
text
end
end
def computeroutput_to_doc(node)
if event_section?
node_to_doc(node)
elsif /\A[\w:\.]+\Z/ =~ node.text # only a single word/identifier?
node_to_doc(node)
else
no_ref do
"#{node_to_doc(node)}
"
end
end
end
def bold_to_doc(node)
"#{node_to_doc(node)}"
end
def sp_to_doc(node)
" #{node_to_doc(node)}"
end
def nonbreakablespace_to_doc(node)
sp_to_doc(node)
end
def linebreak_to_doc(node)
"#{node_to_doc(node)}\n"
end
def programlisting_to_doc(node)
no_idents do
"\n```\n #{node_to_doc(node).split("\n").join("\n ")}\n```\n"
end
end
def onlyfor_to_doc(node)
'' # handled elsewhere
end
def simplesect_to_doc(node)
case node['kind']
when 'since' # get rid of 'Since' notes
''
when 'see'
no_ref do
@see_list.concat node_to_doc(node).split(',')
end
''
when 'note'
<<~__NOTE
Note:
#{node_to_doc(node)}
__NOTE
when 'remark'
<<~__NOTE
__NOTE
else
node_to_doc(node)
end
end
def _arglist_to_doc(args)
args.split(',').collect do |a|
a = a.gsub(/const\s+/, '')
a.tr!('*&[]', '')
a.split(' ').last
end.join(',').strip
end
private :_arglist_to_doc
def _is_method?(itmname, clsnm=nil)
spec = clsnm ? Director::Spec.class_index[clsnm] : self
if spec
if clsnm
if clsdef = spec.def_item(clsnm)
if itmdef = clsdef.find_item(itmname)
return Extractor::FunctionDef === itmdef
end
end
else
if itmdef = spec.def_item(itmname)
return Extractor::FunctionDef === itmdef
end
end
end
false
end
private :_is_method?
def _is_static_method?(clsnm, mtdname)
if clsspec = Director::Spec.class_index[clsnm]
if clsdef = clsspec.def_item(clsnm)
if mtdef = clsdef.find_item(mtdname)
return Extractor::MethodDef === mtdef && mtdef.is_static
end
end
end
false
end
private :_is_static_method?
def _ident_str_to_doc(s, ref_scope = nil)
return s if no_idents?
return s if s.start_with?('wxRuby')
return 'WXOSX' if s.start_with?('wxMac')
return s.sub(/\Awx/, 'WX') if %w[wxMSW wxOSX wxGTK wxX11 wxUNIVERSAL].any? { |w| s.start_with?(w) }
nmlist = s.split('::')
nm_str = nmlist.shift.to_s
constnm = rb_wx_name(nm_str)
if nmlist.empty? # unscoped id?
if /(\w+)\s*\(([^\)]*)\)/ =~ nm_str # method with arglist?
fn = $1
args = _arglist_to_doc($2)
if ref_scope
mtdsig = if ref_scope == fn
args.empty? ? 'initialize' : "initialize(#{args})"
else
args.empty? ? "#{rb_method_name(fn)}" : "#{rb_method_name(fn)}(#{args})"
end
sep = _is_static_method?(ref_scope, fn) ? '.' : '#'
constnm = rb_wx_name(ref_scope)
if DocGenerator.constants_xref_db.has_key?(constnm)
["#{DocGenerator.constants_xref_db[constnm]['mod']}::#{constnm}#{sep}#{mtdsig}", true]
else
"Wx::#{constnm}#{sep}#{mtdsig}"
end
else
mtdsig = args.empty? ? "#{rb_method_name(fn)}" : "#{rb_method_name(fn)}(#{args})"
[mtdsig, true]
end
else # constant or method name only
if DocGenerator.constants_xref_db.has_key?(constnm)
["#{DocGenerator.constants_xref_db[constnm]['mod']}::#{constnm}", true]
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str))
["Wx::#{rb_constant_name(nm_str)}", true]
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str, false))
["Wx::#{rb_constant_name(nm_str, false)}", true]
elsif !_is_method?(nm_str, ref_scope)
["Wx::#{constnm}", true]
else
if ref_scope
mtdnm = if ref_scope == nm_str
'initialize'
else
rb_method_name(nm_str)
end
sep = _is_static_method?(ref_scope, nm_str) ? '.' : '#'
constnm = rb_wx_name(ref_scope)
if DocGenerator.constants_xref_db.has_key?(constnm)
["#{DocGenerator.constants_xref_db[constnm]['mod']}::#{constnm}#{sep}#{mtdnm}", true]
else
"Wx::#{constnm}#{sep}#{mtdnm}"
end
else
mtdnm = rb_method_name(nm_str)
[mtdnm, true]
end
end
end
else # scoped id
itmnm = nmlist.shift.to_s
mtd = nil
args = nil
known = true
if /(\w+)\s*\(([^\)]*)\)/ =~ itmnm
mtd = $1
args = _arglist_to_doc($2)
end
# transform the scope prefix
if DocGenerator.constants_xref_db.has_key?(constnm)
constnm = "#{DocGenerator.constants_xref_db[constnm]['mod']}::#{constnm}"
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str))
cnm = rb_constant_name(nm_str)
constnm = "#{DocGenerator.constants_xref_db[cnm]['mod']}::#{cnm}"
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str, false))
cnm = rb_constant_name(nm_str, false)
constnm = "#{DocGenerator.constants_xref_db[cnm]['mod']}::#{cnm}"
elsif nm_str.start_with?('wx')
known = false
constnm = "Wx::#{constnm}"
end
# transform and append the element id
if mtd.nil?
if DocGenerator.constants_xref_db.has_key?(rb_wx_name(itmnm))
itmnm = rb_wx_name(itmnm)
# in case of enum constants the documented scope most likely omits the enum class
# which we want for correct linking for wxRuby
if DocGenerator.constants_xref_db[itmnm]['mod'].start_with?("#{constnm}::")
["#{DocGenerator.constants_xref_db[itmnm]['mod']}::#{rb_wx_name(itmnm)}", known]
else
["#{constnm}::#{rb_wx_name(itmnm)}", known]
end
elsif !_is_method?(itmnm, nm_str)
["#{constnm}::#{rb_wx_name(itmnm)}", known]
else
sep = _is_static_method?(nm_str, itmnm) ? '.' : '#'
["#{constnm}#{sep}#{rb_method_name(itmnm)}", known]
end
elsif nm_str == mtd # ctor?
[args.empty? ? "#{constnm}\#initialize" : "#{constnm}\#initialize(#{args})", known]
else
sep = _is_static_method?(nm_str, mtd) ? '.' : '#'
[args.empty? ? "#{constnm}#{sep}#{rb_method_name(mtd)}" : "#{constnm}#{sep}#{rb_method_name(mtd)}(#{args})", known]
end
end
end
private :_ident_str_to_doc
# transform all cross references
def ref_to_doc(node)
return node.text if no_idents?
if @classdef
ref = @classdef.crossref_table[node['refid']]
end
ref ||= {}
return node.text if /\s/ =~ node.text # no crossref transforming if text contains whitespace; return plain text
if no_ref?
doc_id, _ = _ident_str_to_doc(node.text, ref[:scope])
doc_id
else
doc_id, id_known = _ident_str_to_doc(node.text, ref[:scope])
id_known ? _ident_to_ref(doc_id) : doc_id
end
end
# transform all titles
def title_to_doc(node)
"## #{node.text}\n"
end
def heading_to_doc(node)
lvl = 1+(node['level'] || '1').to_i
txt = node_to_doc(node)
event_section(/Events emitted by this class|Events using this class/i =~ txt)
txt.strip!
txt.empty? ? txt : "#{'#' * lvl} #{txt}"
end
# transform all itemizedlist
def itemizedlist_to_doc(node)
doc = node_to_doc(node)
if event_list?
# event emitter block ended
event_list(false)
event_section(false)
end
doc
end
# transform all listitem
def listitem_to_doc(node)
itm_text = node_to_doc(node)
# fix possible unwanted leading spaces resulting in verbatim blocks
itm_text = itm_text.split("\n").collect {|s|s.lstrip}.join("\n") if itm_text.index("\n")
"\n- #{itm_text}"
end
def node_to_doc(xmlnode)
xmlnode.children.inject('') do |docstr, node|
node = preprocess_node(node)
docstr << (node.is_a?(Nokogiri::XML::Node) ? self.__send__("#{node.name}_to_doc", node) : node.to_s)
end
end
def get_event_override(evt)
if ifspec.event_overrides.has_key?(@classdef.name)
return ifspec.event_overrides[@classdef.name][evt] || ifspec.event_overrides[@classdef.name][evt.upcase]
end
nil
end
private :get_event_override
def para_to_doc(node)
para = node_to_doc(node)
# loose specific notes paragraphs
case para
when /\A(\<(b)\>)?(wxPerl|\{Wx::Perl\}) Note:/, # wxPerl note
/\A\s*Library:/, # Library note
/\A\s*Include\s+file:/ # Include file note
''
else
para.sub!(/Include\s+file:\s+\\?#include\s+<[^>]+> */, '')
if event_section?
case para
when /The following event handler macros redirect.*(\{.*})/
event_ref = $1
"The following event-handler methods redirect the events to member method or handler blocks for #{event_ref} events."
when /\AEVT_[A-Z]+/
if event_list? && /\A(EVT_[_A-Z]+)\((.*,)\s+\w+\):(.*)/ =~ para
evthnd_name = $1.downcase
if override_spec = get_event_override(evthnd_name)
evthnd_name, evt_type, evt_arity, evt_klass = override_spec
idarg = case evt_arity
when 0
''
when 1
'id, '
when 2
'first_id, last_id, '
end
arglist = "#{idarg}meth = nil, &block"
else
arglist = "#{$2} meth = nil, &block"
end
docstr = $3.lstrip
package.event_docs[evthnd_name] = [arglist, docstr.dup] # register for eventlist doc gen
"{Wx::EvtHandler\##{evthnd_name}}(#{arglist}): #{docstr}"
elsif event_list? && /\A(EVT_[_A-Z]+)(\*)?\(\w+\):(.*)/ =~ para
wildcard = ($2 == '*')
evthnd_name = $1.downcase
arglist = "meth = nil, &block"
docstr = $3.lstrip
package.event_docs[wildcard ? /\A#{evthnd_name}/ : evthnd_name] = [arglist, docstr.dup] # register for eventlist doc gen
if wildcard
"{Wx::EvtHandler}#{evthnd_name}*(#{arglist}): #{docstr}"
else
"{Wx::EvtHandler\##{evthnd_name}}(#{arglist}): #{docstr}"
end
else
para
end
else
para
end
else
para
end
end
end
def preprocess_node(node)
if @item_doc_ovr[:pre] && @item_doc_ovr[:pre][node.name.to_sym]
@item_doc_ovr[:pre][node.name.to_sym].each do |ovr|
if ovr[:replace]
return ovr[:replace] if ovr[:pattern] =~ node.inner_html
elsif ovr[:subst]
if ovr[:global]
node.inner_html = node.inner_html.gsub(ovr[:pattern], ovr[:subst])
else
node.inner_html = node.inner_html.sub(ovr[:pattern], ovr[:subst])
end
end
end
end
node
end
private :preprocess_node
def method_missing(mtd, *args, &block)
if /\A\w+_to_doc\Z/ =~ mtd.to_s && args.size==1
node_to_doc(*args)
else
super
end
end
public
def initialize(director)
@director = director
@doc_overrides = if File.exist?(doc_override_file = File.join(__dir__, 'doc', underscore(name)+ '.yaml'))
if ::Psych::VERSION >= '3.1.0'
::Psych.safe_load(File.read(doc_override_file), permitted_classes: [::Regexp, ::Symbol])
else
::Psych.safe_load(File.read(doc_override_file), [::Regexp, ::Symbol])
end
else
{}
end
@classdef = nil
@see_list = []
end
attr_reader :director
def for_class(clsdef, &block)
prevcls = @classdef
@classdef = clsdef
begin
block.call
ensure
@classdef = prevcls
end
end
def to_doc(xmlnode_or_set, item: nil, desc: :brief)
return '' unless xmlnode_or_set
@see_list.clear
@item_doc_ovr = if item
item_key = if item.is_a?(Extractor::BaseDef)
if item.is_a?(Extractor::ClassDef) || @classdef.nil?
item.name
else
"#{@classdef.name}.#{item.name}"
end
else
@classdef ? "#{@classdef.name}.#{item}" : item.to_s
end
item_key = item_key.to_sym
@doc_overrides[item_key] ? @doc_overrides[item_key][desc] || {} : {}
else
{}
end
doc = if Nokogiri::XML::NodeSet === xmlnode_or_set
xmlnode_or_set.inject('') do |s, n|
n = preprocess_node(n)
s << (n.is_a?(Nokogiri::XML::Node) ? node_to_doc(n) : n.to_s)
end
else
xmlnode_or_set = preprocess_node(xmlnode_or_set)
xmlnode_or_set.is_a?(Nokogiri::XML::Node) ? node_to_doc(xmlnode_or_set) : xmlnode_or_set.to_s
end
event_section(false)
if @item_doc_ovr.has_key?(:post)
@item_doc_ovr[:post].each do |ovr|
if ovr[:global]
doc.gsub!(ovr[:pattern], ovr[:subst])
else
doc.sub!(ovr[:pattern], ovr[:subst])
end
end
end
if @item_doc_ovr.has_key?(:replace)
doc = @item_doc_ovr[:replace][:text]
end
doc.sub!(/\\{Wx::Perl.*/, '')
doc.strip!
# reduce triple(or more) newlines to max 2
doc << "\n" # always end with a NL without following whitespace
doc.gsub!(/\n *\n *\n+/, "\n\n")
# add crossref tags
@see_list.each { |s| doc << "@see #{s}\n" }
doc
end
def constants_db
DocGenerator.constants_db
end
def constants_xref_db
DocGenerator.constants_xref_db
end
def type_to_doc(ctype_decl)
return ctype_decl if ctype_decl == 'void'
ctype = Typemap.strip_type_decl(ctype_decl)
nmlist = ctype.split('::')
nm_str = nmlist.shift.to_s
constnm = rb_wx_name(nm_str)
if nmlist.empty? # unscoped id?
if DocGenerator.constants_xref_db.has_key?(constnm)
"#{DocGenerator.constants_xref_db[constnm]['mod']}::#{constnm}"
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str))
"Wx::#{rb_constant_name(nm_str)}"
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str, false))
"Wx::#{rb_constant_name(nm_str, false)}"
else
Typemap.wx_type_to_rb(ctype)
end
else
itmnm = nmlist.shift.to_s
if DocGenerator.constants_xref_db.has_key?(constnm)
constnm = "#{DocGenerator.constants_xref_db[constnm]['mod']}::#{constnm}"
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str))
cnm = rb_constant_name(nm_str)
constnm = "#{DocGenerator.constants_xref_db[cnm]['mod']}::#{cnm}"
elsif DocGenerator.constants_xref_db.has_key?(rb_constant_name(nm_str, false))
cnm = rb_constant_name(nm_str, false)
constnm = "#{DocGenerator.constants_xref_db[cnm]['mod']}::#{cnm}"
elsif nm_str.start_with?('wx')
constnm = "Wx::#{constnm}"
end
"#{constnm}::#{rb_wx_name(itmnm)}"
end
end
end
def run
# run an analysis comparing inherited generated methods with this class's own generated methods
InterfaceAnalyzer.check_interface_methods(@director, doc_gen: true)
@xml_trans = DocGenerator::XMLTransformer.new(@director)
Stream.transaction do
fdoc = CodeStream.new(File.join(package.ruby_doc_path, underscore(name)+'.rb'))
fdoc << <<~__HEREDOC
# :stopdoc:
# This file is automatically generated by the WXRuby3 documentation
# generator. Do not alter this file.
# :startdoc:
__HEREDOC
# at least 2 newlines to make Yard skip/forget the header comment
fdoc.puts
fdoc.puts
fdoc.puts "module #{package.fullname}"
fdoc.puts
fdoc.indent do
gen_constants_doc(fdoc)
gen_functions_doc(fdoc) unless no_gen?(:functions)
gen_class_doc(fdoc) unless no_gen?(:classes)
end
fdoc.puts
fdoc.puts 'end'
end
end
protected
def to_feature_text(feat)
if Config::AnyOf === feat
feat.features.collect { |f| f.is_a?(::Array) ? f.join('&') : f }.join('|')
else
feat
end
end
private :to_feature_text
def gen_item_requirements(fdoc, item)
if item.required_features_doc
fdoc.doc.puts '@wxrb_require ' + item.required_features_doc.collect(&->(feat){ to_feature_text(feat) }).join(',')
end
end
def has_class_requirements?
!(ifspec.requirements.empty? && ifspec.package.required_features.empty?)
end
def get_class_requirements
ifspec.requirements + ifspec.package.required_features.to_a
end
private :get_class_requirements
def gen_class_requirements(fdoc)
if has_class_requirements?
fdoc.doc.puts '@wxrb_require ' + get_class_requirements.collect(&->(feat){ to_feature_text(feat) }).join(',')
end
end
def get_constant_doc(const)
@xml_trans.to_doc(const.brief_doc, item: const)
end
def gen_constant_value(val)
if ::String === val && /\A(#<(.*)>|[\w:]+\.new\(.*\))\Z/ =~ val
if $2
valstr = $2
if /\Awx/ =~ valstr
valstr.sub(/\Awx/, '')
else
'nil'
end
else
$1
end
else
val.inspect
end
end
def gen_constant_doc(fdoc, name, spec, doc)
fdoc.doc.puts doc
fdoc.puts "#{name} = #{gen_constant_value(spec['value'])}"
fdoc.puts
end
def get_enum_doc(enumdef)
doc = @xml_trans.to_doc(enumdef.brief_doc, item: enumdef)
doc << "\n" if enumdef.detailed_doc
doc << @xml_trans.to_doc(enumdef.detailed_doc, item: enumdef, desc: :detail) if enumdef.detailed_doc
doc
end
def gen_enum_doc(fdoc, enumname, enumdef, enum_table)
fdoc.doc.puts get_enum_doc(enumdef)
gen_class_requirements(fdoc)
fdoc.puts "class #{enumname} < Wx::Enum"
fdoc.puts
fdoc.indent do
enumdef.items.each do |e|
const_name = rb_wx_name(e.name)
if enum_table.has_key?(const_name)
gen_constant_doc(fdoc, const_name, enum_table[const_name], get_constant_doc(e))
end
end
end
fdoc.puts "end # #{enumname}"
fdoc.puts
end
def gen_constants_doc(fdoc)
xref_table = package.all_modules.reduce(DocGenerator.constants_db) { |db, mod| db[mod] }
def_items.select {|itm| !itm.docs_ignored(Director::Package.full_docs?) }.each do |item|
case item
when Extractor::GlobalVarDef
unless no_gen?(:variables)
const_name = rb_constant_name(item.name)
if xref_table.has_key?(const_name)
gen_constant_doc(fdoc, const_name, xref_table[const_name], get_constant_doc(item))
end
end
when Extractor::EnumDef
unless no_gen?(:enums)
if item.is_type
enum_name = rb_wx_name(item.name)
if xref_table.has_key?(enum_name)
gen_enum_doc(fdoc, enum_name, item, xref_table[enum_name] || {})
end
else
item.items.each do |e|
const_name = rb_constant_name(e.name, false)
if xref_table.has_key?(const_name)
gen_constant_doc(fdoc, const_name, xref_table[const_name], get_constant_doc(e))
end
end
end
end
when Extractor::DefineDef
unless no_gen?(:defines)
if !item.is_macro? && item.value && !item.value.empty?
const_name = rb_constant_name(item.name)
if xref_table.has_key?(const_name)
gen_constant_doc(fdoc, const_name, xref_table[const_name], get_constant_doc(item))
end
end
end
end
end
end
def get_function_doc(func)
func.rb_doc(@xml_trans, type_maps, Director::Package.full_docs?)
end
def gen_functions_doc(fdoc)
def_items.select {|itm| !itm.docs_ignored(Director::Package.full_docs?) }.each do |item|
if Extractor::FunctionDef === item
get_method_doc(item).each_pair do |name, docs|
if docs.size>1 # method with overloads?
docs.each do |ovl, params, ovl_doc|
fdoc.doc.puts "@overload #{name}(#{params})"
fdoc.doc.indent { fdoc.doc.puts ovl_doc }
fdoc.doc.indent { gen_item_requirements(fdoc, ovl) }
end
fdoc.puts "def #{name}(*args) end"
else
mtd, params, doc = docs.shift
fdoc.doc.puts doc
gen_item_requirements(fdoc, mtd)
if params.empty?
fdoc.puts "def #{name}; end"
else
fdoc.puts "def #{name}(#{params}) end"
end
end
fdoc.puts
end
end
end
end
def get_class_doc(cls)
doc = @xml_trans.to_doc(cls.brief_doc, item: cls)
doc << "\n" if cls.detailed_doc # force empty line (paragraph break) between summary and detail
doc << @xml_trans.to_doc(cls.detailed_doc, item: cls, desc: :detail) if cls.detailed_doc
doc
end
def get_method_doc(mtd)
mtd.rb_doc(@xml_trans, type_maps, Director::Package.full_docs?)
end
def get_method_head(clsdef, mtdef)
if mtdef.class_name == clsdef.name
# get method head item
clsdef.items.find { |m| Extractor::MethodDef === m && m.name == mtdef.name }
else
# check folded bases
base = folded_bases(clsdef.name).find { |bc| bc == mtdef.class_name }
base = def_classes.find { |c| base == c.name }
base ? base.items.find { |m| Extractor::MethodDef === m && m.name == mtdef.name } : nil
end
end
def gen_class_doc_members(fdoc, clsdef, cls_members, alias_methods)
# generate method and member variable documentation
mtd_done = ::Set.new
cls_members.each do |cm|
case cm
when Extractor::MethodDef
# overloads are flattened out by the Analyzer keeping only the non-ignored items
# but for doc gen we need the head item (and only that, so keep track and skip the rest)
unless cm.is_dtor || mtd_done.include?(cm.name)
# get method head item
mtd_head = get_method_head(clsdef, cm)
get_method_doc(mtd_head).each_pair do |name, docs|
if docs.size>1 # method with overloads?
docs.each do |ovl, params, ovl_doc|
fdoc.doc.puts "@overload #{name}(#{params})"
fdoc.doc.indent { fdoc.doc.puts ovl_doc }
fdoc.doc.indent { gen_item_requirements(fdoc, ovl) }
end
fdoc.puts "def #{name}(*args) end"
# in case all overloads were ignored there will be no SWIG generated alias
no_gen_alias = docs.all? { |ovl, _, _| ovl.ignored }
else
mtd, params, doc = docs.shift
fdoc.doc.puts doc
gen_item_requirements(fdoc, mtd)
if params.empty?
fdoc.puts "def #{name}; end"
else
fdoc.puts "def #{name}(#{params}) end"
end
# in case the documented method was ignored there will be no SWIG generated alias
no_gen_alias = mtd.ignored
end
# check for SWIG generated aliases (skip ignored method defs as these will not have SWIG generated aliases)
if !no_gen_alias && alias_methods.has_key?(cm.name)
fdoc.puts "alias_method :#{alias_methods[cm.name]}, :#{name}"
else
# check for aliases that will be available from WxRubyStyleAccessors at runtime
# and document these as well
alias_name = case name
when /\Aget_(\w+)/
$1
when /\Aset_(\w+)/
# only document alias if at least 1 method overload has a single required argument
if docs.any? { |ovl, _, _| ovl.parameter_count > 0 && ovl.required_param_count < 2 }
"#{$1}="
else
nil
end
when /\Ais_(\w+)/
"#{$1}?"
when /\A(has_|can_)/
"#{name}?"
else
nil
end
# only consider alias if no other method matching alias_name exists as WxRubyStyleAccessors
# aliases rely on method_missing being called
if alias_name &&
!cls_members.any? { |m| Extractor::MethodDef === m && !m.ignored && m.rb_decl_name == alias_name }
fdoc.puts "alias_method :#{alias_name}, :#{name}"
end
end
fdoc.puts
end
mtd_done << cm.name
end
when Extractor::MemberVarDef
rd_doc, rd_decl, wr_doc, wr_decl = cm.rb_doc(@xml_trans, type_maps)
rd_doc.each { |s| fdoc.doc.puts s }
fdoc.puts rd_decl
if wr_doc
wr_doc.each { |s| fdoc.doc.puts s }
fdoc.puts wr_decl
end
fdoc.puts
end
end
end
def gen_class_doc(fdoc)
const_table = package.all_modules.reduce(DocGenerator.constants_db) { |db, mod| db[mod] }
def_items.select {|itm| !itm.docs_ignored(Director::Package.full_docs?) && Extractor::ClassDef === itm && !is_folded_base?(itm.name) }.each do |item|
if !item.is_template? || template_as_class?(item.name)
@xml_trans.for_class(item) do
intf_class_name = if (item.is_template? && template_as_class?(item.name))
template_class_name(item.name)
else
item.name
end
clsnm = rb_wx_name(intf_class_name)
xref_table = const_table[clsnm] || {}
fdoc.doc.puts get_class_doc(item)
if is_mixin?(item)
fdoc.doc.puts "\n@note In wxRuby this is a mixin module instead of a (base) class."
gen_class_requirements(fdoc)
fdoc.puts "module #{clsnm}"
else
fdoc.doc.puts "\n@note This class is untracked and should not be derived from nor instances extended!" unless is_tracked?(item)
gen_class_requirements(fdoc)
basecls = ifspec.classdef_name(base_class(item, doc: true))
fdoc.puts "class #{clsnm} < #{basecls ? basecls.sub(/\Awx/, '') : '::Object'}"
end
fdoc.puts
# mixin includes
if included_mixins.has_key?(item.name)
included_mixins[item.name].keys.each { |mod| fdoc.iputs "include #{mod}" }
fdoc.puts
end
# collect possible aliases
alias_methods = item.aliases
folded_bases(item.name).each do |basename|
alias_methods = def_item(basename).aliases.merge(alias_methods)
end
fdoc.indent do
cls_members = InterfaceAnalyzer.class_interface_members_public(intf_class_name)
# generate documentation for any enums
cls_members.each do |member|
case member
when Extractor::EnumDef
unless member.is_type
member.items.each do |e|
const_name = rb_constant_name(e.name)
if xref_table.has_key?(const_name)
gen_constant_doc(fdoc, const_name, xref_table[const_name], get_constant_doc(e))
elsif xref_table.has_key?(e.name)
gen_constant_doc(fdoc, e.name, xref_table[e.name], get_constant_doc(e))
end
end
else
enum_name = rb_wx_name(member.name)
if xref_table.has_key?(enum_name)
gen_enum_doc(fdoc, enum_name, member, xref_table[enum_name] || {})
end
end
end
end if xref_table
# generate method and member var documentation
gen_class_doc_members(fdoc, item, cls_members, alias_methods)
cls_members = InterfaceAnalyzer.class_interface_members_protected(intf_class_name)
unless cls_members.empty?
fdoc.puts
fdoc.puts 'protected'
fdoc.puts
gen_class_doc_members(fdoc, item, cls_members, alias_methods)
end
end
fdoc.puts "end # #{clsnm}"
fdoc.puts
end
end
end
end
end
end