Adicionado documentação dos componentes de Vitruvio.
This commit is contained in:
+332
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use File::Find;
|
||||
use File::Basename;
|
||||
use Encode qw(decode_utf8 is_utf8);
|
||||
|
||||
binmode(STDOUT, ':encoding(UTF-8)');
|
||||
|
||||
my $root = '/home/victor/davinti';
|
||||
my $components_dir = "$root/Vitruvio/Documentação/Componentes";
|
||||
my @sources = ("$root/Vitruvio/Paineis", "$root/Vitruvio/Processos");
|
||||
|
||||
my @component_files;
|
||||
find(
|
||||
sub {
|
||||
return unless -f $_;
|
||||
return unless $_ =~ /\.ts$/i;
|
||||
return if $_ eq 'index.ts';
|
||||
push @component_files, $File::Find::name;
|
||||
},
|
||||
$components_dir
|
||||
);
|
||||
|
||||
my @xml_files;
|
||||
for my $src (@sources) {
|
||||
next unless -d $src;
|
||||
find(
|
||||
sub {
|
||||
return unless -f $_;
|
||||
return unless $_ =~ /\.xml$/i;
|
||||
push @xml_files, $File::Find::name;
|
||||
},
|
||||
$src
|
||||
);
|
||||
}
|
||||
|
||||
for my $doc_file (@component_files) {
|
||||
my $component = basename($doc_file, '.ts');
|
||||
|
||||
my %attrs;
|
||||
my %attr_values;
|
||||
my %events;
|
||||
my @examples;
|
||||
my $occ_total = 0;
|
||||
my $occ_paineis = 0;
|
||||
my $occ_processos = 0;
|
||||
|
||||
my $tag_regex = qr{<\s*\Q$component\E\b([^<>]*?)(?:/\s*>|>(?:.*?)</\s*\Q$component\E\s*>)}si;
|
||||
|
||||
for my $xml (@xml_files) {
|
||||
my $content = read_text($xml);
|
||||
next if $content eq '';
|
||||
|
||||
while ($content =~ /$tag_regex/g) {
|
||||
my $inside = defined $1 ? $1 : '';
|
||||
$occ_total++;
|
||||
|
||||
if ($xml =~ m{/Vitruvio/Paineis/}) {
|
||||
$occ_paineis++;
|
||||
} elsif ($xml =~ m{/Vitruvio/Processos/}) {
|
||||
$occ_processos++;
|
||||
}
|
||||
|
||||
if (scalar(@examples) < 12) {
|
||||
my $line = line_from_offset($content, pos($content));
|
||||
push @examples, rel_path($xml, $root) . ':' . $line;
|
||||
}
|
||||
|
||||
while ($inside =~ /([A-Za-z_][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g) {
|
||||
my $attr = $1;
|
||||
my $value = defined $2 ? $2 : $3;
|
||||
$attrs{$attr} = 1;
|
||||
$attr_values{$attr}{$value} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
while ($content =~ /<\s*\Q$component\E\b[\s\S]*?<event\b[^>]*\bname\s*=\s*(?:"([^"]+)"|'([^']+)')[^>]*>/gsi) {
|
||||
my $event = defined $1 ? $1 : $2;
|
||||
next unless defined $event;
|
||||
$events{$event} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
my @props_sorted = sort { lc($a) cmp lc($b) } keys %attrs;
|
||||
my @events_sorted = sort { lc($a) cmp lc($b) } keys %events;
|
||||
|
||||
my $family = infer_family($component);
|
||||
my $purpose = infer_purpose($component, $family);
|
||||
|
||||
my $ts = build_ts_doc(
|
||||
component => $component,
|
||||
family => $family,
|
||||
purpose => $purpose,
|
||||
total => $occ_total,
|
||||
paineis => $occ_paineis,
|
||||
processos => $occ_processos,
|
||||
attrs => \@props_sorted,
|
||||
attr_values => \%attr_values,
|
||||
events => \@events_sorted,
|
||||
examples => \@examples,
|
||||
);
|
||||
|
||||
write_text($doc_file, $ts);
|
||||
}
|
||||
|
||||
print "OK: " . scalar(@component_files) . " componentes documentados\n";
|
||||
|
||||
sub read_text {
|
||||
my ($file) = @_;
|
||||
open my $fh, '<:encoding(UTF-8)', $file or return '';
|
||||
local $/ = undef;
|
||||
my $txt = <$fh>;
|
||||
close $fh;
|
||||
return defined $txt ? $txt : '';
|
||||
}
|
||||
|
||||
sub write_text {
|
||||
my ($file, $content) = @_;
|
||||
open my $fh, '>:encoding(UTF-8)', $file or die "Erro ao escrever $file: $!";
|
||||
print {$fh} $content;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
sub line_from_offset {
|
||||
my ($txt, $offset) = @_;
|
||||
$offset = 0 unless defined $offset;
|
||||
my $prefix = substr($txt, 0, $offset);
|
||||
my $count = ($prefix =~ tr/\n//);
|
||||
return $count + 1;
|
||||
}
|
||||
|
||||
sub rel_path {
|
||||
my ($path, $base) = @_;
|
||||
$path = decode_utf8($path) unless is_utf8($path);
|
||||
$base = decode_utf8($base) unless is_utf8($base);
|
||||
$path =~ s/^\Q$base\E\/?//;
|
||||
return $path;
|
||||
}
|
||||
|
||||
sub quote_str {
|
||||
my ($s) = @_;
|
||||
$s = '' unless defined $s;
|
||||
$s =~ s/\\/\\\\/g;
|
||||
$s =~ s/"/\\"/g;
|
||||
$s =~ s/\r/\\r/g;
|
||||
$s =~ s/\n/\\n/g;
|
||||
$s =~ s/\t/\\t/g;
|
||||
return '"' . $s . '"';
|
||||
}
|
||||
|
||||
sub infer_family {
|
||||
my ($component) = @_;
|
||||
return 'BancoDeDados' if $component =~ /^(DB|Data)/;
|
||||
return 'Layout' if $component =~ /(Layout|Panel|Section|Window|Tab|Accordion|Grid|Cell|Container|Split|Card)$/;
|
||||
return 'EntradaDeDados' if $component =~ /(Field|Input|Combo|Select|Picker|Check|Radio|Upload|Editor|TextArea|Date|Time|Number)/;
|
||||
return 'Visualizacao' if $component =~ /(Label|Image|Chart|Table|List|Tree|Map|HTML|Widget|View|Viewer|Progress|Badge|Icon|Tooltip)/;
|
||||
return 'Acao' if $component =~ /(Button|Action|Menu|Dialog|Modal|Popup|Confirm|Toolbar)/;
|
||||
return 'Infraestrutura';
|
||||
}
|
||||
|
||||
sub infer_purpose {
|
||||
my ($component, $family) = @_;
|
||||
return 'Componente orientado a dados com integracao de datasource SQL e bind de parametros.' if $family eq 'BancoDeDados';
|
||||
return 'Componente de organizacao visual da tela para estruturar conteudo e navegacao.' if $family eq 'Layout';
|
||||
return 'Componente de captura e edicao de dados em formularios e filtros.' if $family eq 'EntradaDeDados';
|
||||
return 'Componente para exibicao de informacoes, resultados e feedback visual.' if $family eq 'Visualizacao';
|
||||
return 'Componente de interacao para disparar acoes e fluxos de processo.' if $family eq 'Acao';
|
||||
return 'Componente de suporte para configuracao, integracao e comportamento da tela.';
|
||||
}
|
||||
|
||||
sub build_ts_doc {
|
||||
my (%p) = @_;
|
||||
my @props = @{ $p{attrs} || [] };
|
||||
my %prop_values = %{ $p{attr_values} || {} };
|
||||
my @events = @{ $p{events} || [] };
|
||||
my @examples = @{ $p{examples} || [] };
|
||||
|
||||
my $props_block = join(",\n", map { ' ' . quote_str($_) } @props);
|
||||
my $events_block = join(",\n", map { ' ' . quote_str($_) } @events);
|
||||
my $examples_block = join(",\n", map { ' ' . quote_str($_) } @examples);
|
||||
|
||||
my @pv_lines;
|
||||
my @pv_type_lines;
|
||||
for my $prop (@props) {
|
||||
my @vals_raw = sort { lc($a) cmp lc($b) } keys %{ $prop_values{$prop} || {} };
|
||||
my @vals = grep { is_value_relevant($prop, $_) } @vals_raw;
|
||||
@vals = @vals_raw if !@vals && @vals_raw;
|
||||
|
||||
my $value_type = infer_value_type($prop, \@vals);
|
||||
|
||||
my $truncated = 0;
|
||||
if (scalar(@vals) > 15) {
|
||||
@vals = @vals[0..14];
|
||||
$truncated = 1;
|
||||
}
|
||||
push @vals, '__TRUNCADO__' if $truncated;
|
||||
my $vals_txt = '[' . join(', ', map { quote_str($_) } @vals) . ']';
|
||||
push @pv_lines, ' ' . quote_str($prop) . ': ' . $vals_txt;
|
||||
push @pv_type_lines, ' ' . quote_str($prop) . ': ' . quote_str($value_type);
|
||||
}
|
||||
|
||||
my $property_values_block = join(",\n", @pv_lines);
|
||||
my $property_value_types_block = join(",\n", @pv_type_lines);
|
||||
|
||||
my $txt = '';
|
||||
$txt .= "export type VitruvioComponentDoc = {\n";
|
||||
$txt .= " component: string;\n";
|
||||
$txt .= " summary: string;\n";
|
||||
$txt .= " status: \"draft\" | \"review\" | \"ready\";\n";
|
||||
$txt .= " context: {\n";
|
||||
$txt .= " module: string;\n";
|
||||
$txt .= " family: string;\n";
|
||||
$txt .= " mappedFrom: string[];\n";
|
||||
$txt .= " occurrences: {\n";
|
||||
$txt .= " total: number;\n";
|
||||
$txt .= " paineis: number;\n";
|
||||
$txt .= " processos: number;\n";
|
||||
$txt .= " };\n";
|
||||
$txt .= " };\n";
|
||||
$txt .= " sections: {\n";
|
||||
$txt .= " purpose: string;\n";
|
||||
$txt .= " basicUsage: string;\n";
|
||||
$txt .= " properties: string[];\n";
|
||||
$txt .= " propertyValues: { [property: string]: string[] };\n";
|
||||
$txt .= " propertyValueTypes: { [property: string]: \"boolean\" | \"enum\" | \"numeric\" | \"text\" | \"mixed\" };\n";
|
||||
$txt .= " events: string[];\n";
|
||||
$txt .= " examples: string[];\n";
|
||||
$txt .= " notes: string[];\n";
|
||||
$txt .= " };\n";
|
||||
$txt .= "};\n\n";
|
||||
|
||||
$txt .= "const " . $p{component} . "Doc: VitruvioComponentDoc = {\n";
|
||||
$txt .= " component: " . quote_str($p{component}) . ",\n";
|
||||
$txt .= " summary: " . quote_str("Documentacao do componente " . $p{component} . " mapeada a partir de exemplos reais de XML.") . ",\n";
|
||||
$txt .= " status: \"draft\",\n";
|
||||
$txt .= " context: {\n";
|
||||
$txt .= " module: \"Vitruvio\",\n";
|
||||
$txt .= " family: " . quote_str($p{family}) . ",\n";
|
||||
$txt .= " mappedFrom: [\"Vitruvio/Paineis\", \"Vitruvio/Processos\"],\n";
|
||||
$txt .= " occurrences: { total: " . ($p{total} || 0) . ", paineis: " . ($p{paineis} || 0) . ", processos: " . ($p{processos} || 0) . " }\n";
|
||||
$txt .= " },\n";
|
||||
$txt .= " sections: {\n";
|
||||
$txt .= " purpose: " . quote_str($p{purpose}) . ",\n";
|
||||
$txt .= " basicUsage: " . quote_str("Usar " . $p{component} . " no contexto adequado da tela; as propriedades abaixo foram observadas em exemplos reais do projeto.") . ",\n";
|
||||
$txt .= " properties: [\n$props_block\n ],\n";
|
||||
$txt .= " propertyValues: {\n$property_values_block\n },\n";
|
||||
$txt .= " propertyValueTypes: {\n$property_value_types_block\n },\n";
|
||||
$txt .= " events: [\n$events_block\n ],\n";
|
||||
$txt .= " examples: [\n$examples_block\n ],\n";
|
||||
$txt .= " notes: [\n";
|
||||
$txt .= " \"Mapeamento automatico baseado em uso observado nos XMLs de paineis e processos.\",\n";
|
||||
$txt .= " \"Valores ruidosos/textos muito longos sao filtrados para facilitar consulta.\",\n";
|
||||
$txt .= " \"propertyValueTypes classifica o perfil observado da propriedade (boolean, enum, numeric, text ou mixed).\",\n";
|
||||
$txt .= " \"Validar com documentacao oficial do Vitruvio quando houver divergencia funcional.\"\n";
|
||||
$txt .= " ]\n";
|
||||
$txt .= " }\n";
|
||||
$txt .= "};\n\n";
|
||||
$txt .= "export default " . $p{component} . "Doc;\n";
|
||||
|
||||
return $txt;
|
||||
}
|
||||
|
||||
sub is_value_relevant {
|
||||
my ($prop, $value) = @_;
|
||||
return 0 unless defined $value;
|
||||
|
||||
my $raw = $value;
|
||||
my $trim = $value;
|
||||
$trim =~ s/^\s+//;
|
||||
$trim =~ s/\s+$//;
|
||||
return 1 if $trim eq '';
|
||||
|
||||
# Remove trechos tecnicos comuns de ruido em mapeamento automatico.
|
||||
return 0 if $raw =~ /<[^>]+>/;
|
||||
return 0 if $raw =~ /\{\{|\}\}|\$\{|#\{/;
|
||||
return 0 if $raw =~ /\bSELECT\b|\bUPDATE\b|\bINSERT\b|\bDELETE\b/i;
|
||||
|
||||
my $is_text_prop = $prop =~ /caption|description|label|title|subCaption|placeholder|message|tooltip|hint/i;
|
||||
my $len = length($trim);
|
||||
my $word_count = scalar(grep { $_ ne '' } split(/\s+/, $trim));
|
||||
|
||||
# Para campos textuais, ignora frases grandes que viram ruido na documentação.
|
||||
if ($is_text_prop) {
|
||||
return 0 if $len > 70;
|
||||
return 0 if $word_count > 10;
|
||||
}
|
||||
|
||||
# Filtro leve global para blocos muito longos.
|
||||
return 0 if $len > 120;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub infer_value_type {
|
||||
my ($prop, $vals_ref) = @_;
|
||||
my @vals = @{ $vals_ref || [] };
|
||||
return 'mixed' if !@vals;
|
||||
|
||||
my @clean = map {
|
||||
my $x = $_;
|
||||
$x =~ s/^\s+//;
|
||||
$x =~ s/\s+$//;
|
||||
$x;
|
||||
} @vals;
|
||||
|
||||
my @non_empty = grep { $_ ne '' } @clean;
|
||||
return 'text' if !@non_empty;
|
||||
|
||||
my $all_boolean = 1;
|
||||
my $all_numeric = 1;
|
||||
my $has_space = 0;
|
||||
|
||||
for my $v (@non_empty) {
|
||||
my $lc = lc($v);
|
||||
$all_boolean = 0 unless $lc eq 'true' || $lc eq 'false';
|
||||
$all_numeric = 0 unless $v =~ /^-?\d+(?:\.\d+)?(?:%|px)?$/;
|
||||
$has_space = 1 if $v =~ /\s/;
|
||||
}
|
||||
|
||||
return 'boolean' if $all_boolean;
|
||||
return 'numeric' if $all_numeric;
|
||||
|
||||
my $is_text_prop = $prop =~ /caption|description|label|title|subCaption|placeholder|message|tooltip|hint|text/i;
|
||||
return 'text' if $is_text_prop;
|
||||
|
||||
my $unique = scalar(@non_empty);
|
||||
return 'enum' if $unique <= 20 && !$has_space;
|
||||
return 'mixed' if $unique <= 30;
|
||||
return 'text';
|
||||
}
|
||||
Reference in New Issue
Block a user