Created
March 16, 2020 00:07
-
-
Save masked-rpgmaker/8003f792c371a01117171cd8db17050e to your computer and use it in GitHub Desktop.
Gerador automático de documentação para scripts do RPG Maker VX Ace.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#============================================================================== | |
# RGSS Doc | v0.2.1 | por Masked | |
# | |
# para RPG Maker VX Ace | |
#------------------------------------------------------------------------------ | |
# Gera automaticamente documentação para todos os scripts do jogo e salva em | |
# uma pasta pré-definida (./docs por padrão) no formato Markdown. | |
#============================================================================== | |
#============================================================================== | |
# ** RGSSDoc | |
#------------------------------------------------------------------------------ | |
# Este módulo concentra as funções de interface com a geração de documentação | |
# e serve como namespace para os demais módulos do script. | |
#============================================================================== | |
module RGSSDoc | |
#-------------------------------------------------------------------------- | |
# * Pasta onde serão salvos os documentos | |
#-------------------------------------------------------------------------- | |
OUTPUT_FOLDER = './docs' | |
#-------------------------------------------------------------------------- | |
# * Gera documentação para um script | |
# name : Nome do script | |
# code : Código do script | |
#-------------------------------------------------------------------------- | |
def self.generate(name, code) | |
doc = ScriptDoc.new(name, code) | |
name.gsub!(/[\x00\/\\:\*\?\"<>\|]/, '') | |
doc.save_doc File.join(OUTPUT_FOLDER, "#{name}.md") | |
doc.save_reference_graph File.join(OUTPUT_FOLDER, "#{name}.dot") | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::Lexer | |
#------------------------------------------------------------------------------ | |
# Esta classe processa um código Ruby e divide em tokens. | |
#============================================================================== | |
class RGSSDoc::Lexer | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :index | |
#-------------------------------------------------------------------------- | |
# * Constantes | |
#-------------------------------------------------------------------------- | |
OPERATORS = %w[& && || | + < > << >> ! = ^ - / * ** %] | |
OPERATORS_EQUALS = OPERATORS.map { |op| op + '='} | |
MISC = %w[\[ \] \[\] { } ( ) <=> =~ :: ~ ? : , . ;] | |
SYMBOLS = MISC + OPERATORS + OPERATORS_EQUALS | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# code : Código | |
#-------------------------------------------------------------------------- | |
def initialize(code) | |
@code = code | |
@word = false | |
reset | |
end | |
#-------------------------------------------------------------------------- | |
# * Reseta a iteração | |
# index : Posição inicial da iteração | |
#-------------------------------------------------------------------------- | |
def reset(index = 0) | |
@index = index | |
end | |
#-------------------------------------------------------------------------- | |
# * Itera sobre os tokens no código | |
# start : Posição inicial no código | |
#-------------------------------------------------------------------------- | |
def tokens(start = 0) | |
token = '' | |
TokenEnumerator.new(self) do |enum| | |
reset(start) | |
while @index < @code.size | |
next_token(enum) | |
end | |
enum.yield Token.new(@index, :eos, :eof) | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Limpa e envia a token para o enumerador | |
# start : Posição de início da token atual | |
# enum : Enumerador de tokens | |
# token : Token | |
#-------------------------------------------------------------------------- | |
def flush(start, enum, token) | |
return false if token.empty? | |
@word = true | |
number = token =~ /^\d/ | |
enum.yield(Token.new(start, number ? :number : :word, token)) if enum | |
true | |
end | |
#-------------------------------------------------------------------------- | |
# * Avança um token no código | |
# enum : Enumerador de tokens | |
#-------------------------------------------------------------------------- | |
def next_token(enum) | |
token = '' | |
start = nil | |
word = @word | |
@word = false | |
while @index < @code.size | |
char = @code[@index] | |
if char == '#' | |
return if flush(start, enum, token) | |
return comment(enum) | |
elsif char =~ /["']/ | |
return if flush(start, enum, token) | |
return string(enum) | |
elsif char =~ /[\n;]/ | |
return if flush(start, enum, token) | |
@index += 1 | |
return enum.yield Token.new(@index - 1, :eos, char) | |
elsif char =~ /\s/ | |
return if flush(start, enum, token) | |
elsif char == '/' and not word | |
@word = true | |
return string(enum) | |
elsif char == '%' and not word | |
@word = true | |
return percent_string(enum) | |
elsif (char =~ /[?!]/ and ((not word and token.empty?) or (@index > 0 and @code[@index - 1] =~ /\s/))) or char =~ /[\[\]\{\}\(\)=\-\+\/%\*\.;,\|&<>\:]/ | |
return if flush(start, enum, token) | |
return symbol(enum) | |
elsif char =~ /[?!]/ | |
token << char | |
@index += 1 | |
return flush(start, enum, token) | |
else | |
start ||= @index | |
token << char | |
end | |
@index += 1 | |
end | |
flush(start, enum, token) | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa uma token de comentário | |
# enum : Enumerador de tokens | |
#-------------------------------------------------------------------------- | |
def comment(enum) | |
while @code[@index] == '#' | |
start = @index + 1 | |
@index += 1 until @index == @code.size or @code[@index] == "\n" | |
if enum | |
enum.yield Token.new(start, :comment, @code[start...@index].strip) | |
end | |
@index += 1 | |
@index += 1 while @code[@index] =~ /\s/ | |
end | |
return if @index >= @code.size | |
@index -= 1 until @code[@index] == "\n" | |
end | |
#-------------------------------------------------------------------------- | |
# * Avança a leitura do código para o fim de uma string | |
# end_mark : Símbolo de fim da string | |
# simple : Se a string é simples (sem interpolação) | |
#-------------------------------------------------------------------------- | |
def find_string_end(end_mark, simple = false) | |
escape = false | |
@index += 1 | |
until @index >= @code.size or (@code[@index] == end_mark and not escape) | |
if not simple and @code[@index, 2] == '#{' and not escape | |
skip_string_interpolation | |
next | |
else | |
escape = (not escape and @code[@index] == "\\") | |
end | |
@index += 1 | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa uma token de string | |
# enum : Enumerador de tokens | |
#-------------------------------------------------------------------------- | |
def string(enum) | |
mark = @code[@index] | |
start = @index + 1 | |
find_string_end(mark, mark == "'") | |
@index += 1 | |
return unless enum | |
enum.yield Token.new(start - 1, :string, @code[start...@index - 1]) | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa uma token de expressão %... | |
# enum : Enumerador de tokens | |
#-------------------------------------------------------------------------- | |
def percent_string(enum) | |
@index += 1 | |
@index += 1 if @code[@index] =~ /[iqrswx]/ | |
mark = @code[@index] | |
if not mark =~ /[\(\[\{\<]/ | |
end_mark = mark | |
elsif mark == '(' | |
end_mark = ')' | |
else | |
end_mark = (mark.ord + 2).chr | |
end | |
start = @index + 1 | |
find_string_end(end_mark) | |
@index += 1 | |
return unless enum | |
enum.yield Token.new(start - 1, :string, @code[start...@index - 1]) | |
end | |
#-------------------------------------------------------------------------- | |
# * Pula interpolação de string | |
#-------------------------------------------------------------------------- | |
def skip_string_interpolation | |
@index += 2 | |
depth = 1 | |
enum = Enumerator.new do |enum| | |
next_token(enum) until depth.zero? | |
end | |
for token in enum | |
depth -= 1 if token.value == '}' | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa uma token de símbolo | |
# enum : Enumerador de tokens | |
#-------------------------------------------------------------------------- | |
def symbol(enum) | |
token = @code[@index] | |
start = @index | |
@index += 1 | |
while @index < @code.size and SYMBOLS.include?(token + @code[@index]) | |
token << @code[@index] | |
@index += 1 | |
end | |
@word = %w<) ] }>.include?(token) | |
return unless enum | |
enum.yield Token.new(start, :symbol, token) | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::BasicToken | |
#------------------------------------------------------------------------------ | |
# Esta classe representa tokens de modo geral. | |
#============================================================================== | |
class RGSSDoc::BasicToken | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :index | |
attr_reader :type | |
attr_reader :value | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# index : Posição da token no código | |
# type : Tipo de token | |
# value : Texto da token | |
#-------------------------------------------------------------------------- | |
def initialize(index, type, value) | |
@index = index | |
@type = type | |
@value = value | |
end | |
#-------------------------------------------------------------------------- | |
# * Converte a token em string | |
#-------------------------------------------------------------------------- | |
alias inspect to_s | |
#-------------------------------------------------------------------------- | |
# * Converte a token em string | |
#-------------------------------------------------------------------------- | |
def to_s | |
"<#{self.class}:#{@type}@#{@index} #{@value.inspect}>" | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::Lexer::Token | |
#------------------------------------------------------------------------------ | |
# Esta classe representa tokens de código Ruby. | |
#============================================================================== | |
class RGSSDoc::Lexer::Token < RGSSDoc::BasicToken | |
#-------------------------------------------------------------------------- | |
# * Constantes | |
#-------------------------------------------------------------------------- | |
BLOCK_KEYWORDS = %w(begin case for class def do if module unless until while) | |
OTHER_KEYWORDS = %w(alias and break next defined? else elsif ensure rescue | |
false in nil not or redo retry super self then return | |
true undef yield end when) | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma palavra | |
#-------------------------------------------------------------------------- | |
def word? | |
type == :word | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é um símbolo | |
#-------------------------------------------------------------------------- | |
def symbol? | |
type == :symbol | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é um fim de sequência | |
#-------------------------------------------------------------------------- | |
def eos? | |
type == :eos | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é um comentário | |
#-------------------------------------------------------------------------- | |
def comment? | |
type == :comment | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é um nome de variável | |
#-------------------------------------------------------------------------- | |
def name? | |
word? and not keyword? | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma constante | |
#-------------------------------------------------------------------------- | |
def const? | |
name? and value[0] =~ /[A-Z]/ | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma variável de instância | |
#-------------------------------------------------------------------------- | |
def instance_variable? | |
name? and value[0] == /@/ | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma variável global | |
#-------------------------------------------------------------------------- | |
def global_variable? | |
name? and value[0] == /$/ | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma variável | |
#-------------------------------------------------------------------------- | |
def variable? | |
name? and not const? | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma palavra chave | |
#-------------------------------------------------------------------------- | |
def keyword? | |
return true if block? | |
return OTHER_KEYWORDS.include?(value) | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a token é uma keyword de bloco | |
#-------------------------------------------------------------------------- | |
def block?(last = :eos) | |
return false unless word? | |
return false unless BLOCK_KEYWORDS.include?(value) | |
return false unless last == :eos or value == 'do' | |
true | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::Lexer::TokenEnumerator | |
#------------------------------------------------------------------------------ | |
# Esta classe implementa um enumerador de tokens. Inclui funções úteis para | |
# leitura das tokens de forma estruturada. | |
#============================================================================== | |
class RGSSDoc::Lexer::TokenEnumerator < Enumerator | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :lexer | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# lexer : Lexer associado ao enumerador | |
# block : Bloco de iteração | |
#-------------------------------------------------------------------------- | |
def initialize(lexer, &block) | |
super(&block) | |
@lexer = lexer | |
end | |
#-------------------------------------------------------------------------- | |
# * Avança a iteração até a próxima token do tipo EOS | |
#-------------------------------------------------------------------------- | |
def skip_to_eos | |
token = nil | |
loop do | |
token = self.next | |
break if token.eos? | |
end | |
lexer.reset(token.index) | |
end | |
#-------------------------------------------------------------------------- | |
# * Pula uma token caso ela exista, não faz nada se não | |
#-------------------------------------------------------------------------- | |
def skip(type, value = nil) | |
token = self.next | |
return token unless token.type == type | |
return self.next if token.value == value or value.nil? | |
token | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::Parser | |
#------------------------------------------------------------------------------ | |
# Esta classe processa blocos de código Ruby para composição da documentação. | |
#============================================================================== | |
class RGSSDoc::Parser | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :lexer | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# code : Código | |
#-------------------------------------------------------------------------- | |
def initialize(code) | |
@code = code | |
@lexer = RGSSDoc::Lexer.new(code) | |
end | |
#-------------------------------------------------------------------------- | |
# * Reseta a iteração | |
#-------------------------------------------------------------------------- | |
def reset | |
@lexer.reset | |
end | |
#-------------------------------------------------------------------------- | |
# * Itera sobre as sentenças no código | |
# start : Posição inicial no código | |
#-------------------------------------------------------------------------- | |
def statements(start = 0) | |
Enumerator.new do |enum| | |
comment = [] | |
last = :eos | |
for token in @lexer.tokens(start) | |
if token.block?(last) | |
@lexer.reset(token.index) | |
skip_code_block | |
text = @code[start...@lexer.index] | |
enum.yield Statement.new(start, :block, text, comment.join("\r\n")) | |
comment.clear | |
start = @lexer.index | |
elsif token.comment? and (last == :eos or last == :comment) | |
comment << token.value | |
start = @lexer.index | |
elsif token.eos? | |
text = @code[start...@lexer.index] || '' | |
unless text.strip.empty? | |
enum.yield Statement.new(start, :simple, text, comment.join("\r\n")) | |
comment.clear | |
end | |
start = @lexer.index | |
end | |
last = token.type | |
end | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Lê um bloco de código terminado em end. | |
#-------------------------------------------------------------------------- | |
def skip_code_block | |
depth = 0 | |
last = :eos | |
has_while = false | |
for token in @lexer.tokens(@lexer.index) | |
has_while ||= ['while', 'until'].include?(token.value) | |
if token.word? | |
if token.block?(last) and not (token.value == 'do' and has_while) | |
depth += 1 | |
elsif token.value == 'end' | |
depth -= 1 | |
end | |
elsif token.eos? | |
has_while = false | |
end | |
last = token.type | |
break if depth.zero? | |
end | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::Parser::Statement | |
#------------------------------------------------------------------------------ | |
# Esta classe representa sentenças de código Ruby. Uma sentença é o menor | |
# bloco de código com significado, que pode ser executado independente do | |
# código ao seu redor sem apresentar erros de sintaxe. | |
#============================================================================== | |
class RGSSDoc::Parser::Statement < RGSSDoc::BasicToken | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :comment | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# index : Posição da token no código | |
# type : Tipo de token | |
# value : Texto da token | |
# comment : Comentário | |
#-------------------------------------------------------------------------- | |
def initialize(index, type, value, comment) | |
super(index, type, value) | |
@comment = comment.force_encoding('utf-8') | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se a sentença é um bloco de código | |
#-------------------------------------------------------------------------- | |
def block? | |
type == :block | |
end | |
#-------------------------------------------------------------------------- | |
# * Primeira token da sentença | |
#-------------------------------------------------------------------------- | |
def tokens | |
@lexer ||= RGSSDoc::Lexer.new(value) | |
@lexer.tokens | |
end | |
#-------------------------------------------------------------------------- | |
# * Lê o cabeçalho da sentença. | |
#-------------------------------------------------------------------------- | |
def head | |
return value unless type == :block | |
return @head if @head | |
tokens.skip_to_eos | |
@head = value[0...@lexer.index].strip | |
end | |
#-------------------------------------------------------------------------- | |
# * Lê o código interno da sentença. | |
#-------------------------------------------------------------------------- | |
def inner_code | |
return value unless type == :block | |
return @inner if @inner | |
head | |
start = @lexer.index | |
depth = 0 | |
has_while = false | |
last = :eos | |
for token in @lexer.tokens(start) | |
if token.word? | |
has_while ||= token.value == 'while' | |
if token.block?(last) and not (token.value == 'do' and has_while) | |
depth += 1 | |
elsif token.value == 'end' | |
depth -= 1 | |
if depth == -1 | |
@lexer.reset(token.index) | |
break | |
end | |
end | |
elsif token.eos? | |
has_while = false | |
end | |
last = token.type | |
end | |
@inner = value[start...@lexer.index] | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::ScriptDoc | |
#------------------------------------------------------------------------------ | |
# Esta classe representa um script sendo documentado. | |
#============================================================================== | |
class RGSSDoc::ScriptDoc | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :name | |
attr_reader :code | |
attr_reader :modules | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# name : Nome do script | |
# code : Código do script | |
#-------------------------------------------------------------------------- | |
def initialize(name, code) | |
@name = name | |
@code = code | |
parse_modules | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa o código e extrai os módulos do script | |
#-------------------------------------------------------------------------- | |
def parse_modules | |
@modules = [] | |
for m in RGSSDoc::ModuleDoc.parse(code) | |
@modules << m | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Salva a documentação | |
# file : Nome do arquivo de destino | |
#-------------------------------------------------------------------------- | |
def save_doc(file) | |
directory = File.dirname(file) | |
Dir.mkdir(directory) unless FileTest.directory?(directory) | |
File.open(file, 'w') do |file| | |
file.puts "# #{name}" | |
for m in modules | |
file.puts "## `#{m.name}` <small style='color: grey;'>(#{m.type})</small>" | |
file.puts | |
file.puts "Herda de `#{m.super_class}`\r\n" if m.super_class | |
static_methods = m.methods.select(&:static?) | |
unless static_methods.empty? | |
file.puts '### Métodos estáticos' | |
file.puts | |
file.puts '<table>' | |
file.puts '<thead>' | |
file.puts '<tr><th>Nome</th><th>Descrição</th><th>Argumentos</th></tr>' | |
file.puts '</thead>' | |
file.puts '<tbody>' | |
for method in static_methods | |
file.print '<tr>' | |
file.print "<td><code>#{method.name}</code></td>" | |
file.print "<td>#{method.description}</td>" | |
file.print '<td><ul>' | |
for a in method.arguments | |
file.print '<li>' | |
file.print "<code>#{a.name}</code>: #{a.description}" | |
file.print '</li>' | |
end | |
file.print '</ul></td>' | |
file.puts '</tr>' | |
end | |
file.puts '</tbody>' | |
file.puts '</table>' | |
file.puts | |
end | |
instance_methods = m.methods.reject(&:static?) | |
unless instance_methods.empty? | |
file.puts '### Métodos de instância' | |
file.puts | |
file.puts '<table>' | |
file.puts '<thead>' | |
file.puts '<tr><th>Nome</th><th>Descrição</th><th>Argumentos</th></tr>' | |
file.puts '</thead>' | |
file.puts '<tbody>' | |
for method in instance_methods | |
file.print '<tr>' | |
file.print "<td><code>#{method.name}</code></td>" | |
file.print "<td>#{method.description}</td>" | |
file.print '<td><ul>' | |
for a in method.arguments | |
file.print '<li>' | |
file.print "<code>#{a.name}</code>: #{a.description}" | |
file.print '</li>' | |
end | |
file.print '</ul></td>' | |
file.puts '</tr>' | |
end | |
file.puts '</tbody>' | |
file.puts '</table>' | |
file.puts | |
end | |
file.puts | |
file.puts '---' | |
end | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Salva o grafo de referências das classes do script | |
# file : Nome do arquivo de destino | |
#-------------------------------------------------------------------------- | |
def save_reference_graph(file) | |
File.open(file, 'w') do |file| | |
file.puts "digraph \"#{name}\" {" | |
for m in @modules | |
for ref in m.references | |
file.puts "\t\"#{m.name}\" -> \"#{ref}\"" | |
end | |
next if (m.super_class || '').empty? | |
file.print "\"#{m.name}\" -> \"#{m.super_class}\"" | |
file.puts " [style=dotted label=super]" | |
end | |
file.puts "}" | |
end | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::RubyEntityDoc | |
#------------------------------------------------------------------------------ | |
# Esta classe representa uma entidade sendo documentada. Uma entidade é | |
# um módulo, classe ou método. | |
#============================================================================== | |
class RGSSDoc::RubyEntityDoc | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :statement | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# statement : Sentença de declaração do módulo | |
#-------------------------------------------------------------------------- | |
def initialize(statement) | |
@statement = statement | |
end | |
#-------------------------------------------------------------------------- | |
# * Código da entidade | |
#-------------------------------------------------------------------------- | |
def code | |
statement.value | |
end | |
#-------------------------------------------------------------------------- | |
# * Descrição da entidade | |
#-------------------------------------------------------------------------- | |
def description | |
raise NotImplementedError | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::ModuleDoc | |
#------------------------------------------------------------------------------ | |
# Esta classe representa um módulo sendo documentado. | |
#============================================================================== | |
class RGSSDoc::ModuleDoc < RGSSDoc::RubyEntityDoc | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :submodules | |
attr_reader :methods | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# statement : Sentença de declaração do módulo | |
#-------------------------------------------------------------------------- | |
def initialize(statement) | |
super | |
parse_submodules | |
parse_methods | |
end | |
#-------------------------------------------------------------------------- | |
# * Nome do módulo | |
#-------------------------------------------------------------------------- | |
def name | |
return @name if @name | |
tokens = statement.tokens | |
tokens.next | |
token = tokens.skip(:symbol, '<<') | |
parts = [] | |
loop do | |
break unless token.word? | |
parts << token.value | |
token = tokens.skip(:symbol, '::') | |
end | |
@name = parts.join('::') | |
end | |
#-------------------------------------------------------------------------- | |
# * Tipo do módulo (class/module) | |
#-------------------------------------------------------------------------- | |
def type | |
statement.tokens.first.value | |
end | |
#-------------------------------------------------------------------------- | |
# * Verifica se o módulo é uma classe | |
#-------------------------------------------------------------------------- | |
def class? | |
type == 'class' | |
end | |
#-------------------------------------------------------------------------- | |
# * Superclasse | |
#-------------------------------------------------------------------------- | |
def super_class | |
return @super_class if @super_class | |
tokens = statement.tokens | |
loop do | |
token = tokens.next | |
return nil if token.eos? | |
break if token.symbol? and token.value == '<' | |
end | |
parts = [] | |
loop do | |
token = tokens.next | |
next if token.symbol? and token.value == '::' | |
break unless token.word? | |
parts << token.value | |
end | |
@super_class = parts.join('::') | |
end | |
#-------------------------------------------------------------------------- | |
# * Lista de referências do módulo | |
#-------------------------------------------------------------------------- | |
def references | |
begin | |
@methods.flat_map do |method| | |
method.references(eval(self.name)) | |
end.uniq | |
rescue NameError | |
[] | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Descrição do módulo | |
#-------------------------------------------------------------------------- | |
def description | |
statement.comment =~ /\s*===+\s*\n\*\* \S+\s*\n---+(.+)\s*\n===+/m | |
return $1.strip.gsub(/\r?\n/, '') if $1 | |
nil | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa o código do módulo e extrai os submódulos declarados nele | |
#-------------------------------------------------------------------------- | |
def parse_submodules | |
@submodules = [] | |
for m in RGSSDoc::ModuleDoc.parse(statement.inner_code) | |
@submodules << m | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa o código do módulo e extrai os métodos declarados nele | |
#-------------------------------------------------------------------------- | |
def parse_methods | |
@methods = [] | |
for m in RGSSDoc::MethodDoc.parse(statement.inner_code) | |
@methods << m | |
end | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa código e extrai os módulos contidos nele | |
# code : Código do script | |
#-------------------------------------------------------------------------- | |
def self.parse(code) | |
Enumerator.new do |enum| | |
doc = RGSSDoc::Parser.new(code) | |
for statement in doc.statements | |
token = statement.tokens.first | |
next unless token.word? | |
next unless ['module', 'class'].include?(token.value) | |
enum.yield(RGSSDoc::ModuleDoc.new(statement)) | |
end | |
end | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::MethodDoc | |
#------------------------------------------------------------------------------ | |
# Esta classe representa um método sendo documentado. | |
#============================================================================== | |
class RGSSDoc::MethodDoc < RGSSDoc::RubyEntityDoc | |
#-------------------------------------------------------------------------- | |
# * Verifica se o método é estático | |
#-------------------------------------------------------------------------- | |
def static? | |
tokens = statement.tokens | |
tokens.next | |
token = tokens.next | |
token.word? and token.value == 'self' | |
end | |
#-------------------------------------------------------------------------- | |
# * Nome do método | |
#-------------------------------------------------------------------------- | |
def name | |
tokens = statement.tokens | |
tokens.next | |
token = tokens.next | |
if token.word? and token.value == 'self' | |
tokens.next | |
token = tokens.next | |
end | |
token.value | |
end | |
#-------------------------------------------------------------------------- | |
# * Lista de referências do método | |
#-------------------------------------------------------------------------- | |
def references(scope = Kernel) | |
Enumerator.new do |enum| | |
parser = RGSSDoc::Parser.new(statement.inner_code) | |
for statement in parser.statements | |
tokens = statement.tokens | |
begin | |
loop do | |
token = tokens.next | |
next unless token.const? | |
name = token.value | |
token = tokens.next | |
while token.symbol? and token.value == '::' | |
token = tokens.next | |
next 2 unless token.const? | |
name << "::" << token.value | |
token = tokens.next | |
end | |
begin | |
x = scope.module_eval(name) | |
is_module = x.is_a?(Module) | |
next unless is_module | |
enum.yield(x.name) | |
rescue NameError => e | |
end | |
end | |
rescue StopIteration | |
end | |
end | |
end.to_a.uniq | |
end | |
#-------------------------------------------------------------------------- | |
# * Descrição do método | |
#-------------------------------------------------------------------------- | |
def description | |
statement.comment =~ /\s*---+\s*\n\* ([^\n]+)\s*.*\n---+/m | |
return $1.strip.gsub(/\r?\n/, '') if $1 | |
nil | |
end | |
#-------------------------------------------------------------------------- | |
# * Argumentos documentados do método | |
#-------------------------------------------------------------------------- | |
def arguments | |
statement.comment =~ /\s*---+\s*\n\* [^\n]+\s*(.*)\n---+/m | |
return {} unless $1 | |
doc = $1 | |
arg = nil | |
args = [] | |
for line in doc.split(/(?:\r?\n)+/) | |
if line =~ /(\S+)\s*:\s*(.+)/ | |
args << arg if arg | |
arg = ArgumentDoc.new($1, $2.strip) | |
elsif arg | |
arg.description << ' ' + line.strip | |
end | |
end | |
args << arg if arg | |
return args | |
end | |
#-------------------------------------------------------------------------- | |
# * Processa código e extrai os métodos contidos nele | |
# code : Código do script | |
#-------------------------------------------------------------------------- | |
def self.parse(code) | |
Enumerator.new do |enum| | |
doc = RGSSDoc::Parser.new(code) | |
for statement in doc.statements | |
token = statement.tokens.first | |
next unless token.word? | |
next unless token.value == 'def' | |
enum.yield(RGSSDoc::MethodDoc.new(statement)) | |
end | |
end | |
end | |
end | |
#============================================================================== | |
# ** RGSSDoc::MethodDoc::ArgumentDoc | |
#------------------------------------------------------------------------------ | |
# Esta classe representa um argumento de um método sendo documentado. | |
#============================================================================== | |
class RGSSDoc::MethodDoc::ArgumentDoc | |
#-------------------------------------------------------------------------- | |
# * Atributos | |
#-------------------------------------------------------------------------- | |
attr_reader :name | |
attr_accessor :description | |
#-------------------------------------------------------------------------- | |
# * Inicialização do objeto | |
# name : Nome do argumento | |
# description : Descrição do argumento | |
#-------------------------------------------------------------------------- | |
def initialize(name, description) | |
@name = name | |
@description = description | |
end | |
#-------------------------------------------------------------------------- | |
# * Converte o objeto em string | |
#-------------------------------------------------------------------------- | |
def to_s | |
"#{name} : #{description}" | |
end | |
end | |
#============================================================================== | |
# ** Main | |
#------------------------------------------------------------------------------ | |
# Lê cada script do jogo e gera documentação para ele de acordo. | |
#============================================================================== | |
for id, name, compressed, code in $RGSS_SCRIPTS | |
next if name.empty? or code.strip.empty? | |
RGSSDoc.generate(name, code) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment