bddgenx 0.1.43 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +54 -129
- data/Rakefile +41 -40
- data/VERSION +1 -1
- data/lib/bddgenx/generator.rb +86 -41
- data/lib/bddgenx/runner.rb +52 -21
- data/lib/bddgenx/steps_generator.rb +87 -58
- data/lib/bddgenx/utils/backup.rb +19 -4
- data/lib/bddgenx/utils/fontLoader.rb +24 -6
- data/lib/bddgenx/utils/parser.rb +49 -10
- data/lib/bddgenx/utils/pdf_exporter.rb +51 -18
- data/lib/bddgenx/utils/tracer.rb +31 -3
- data/lib/bddgenx/utils/validator.rb +29 -3
- metadata +30 -3
- data/lib/bddgenx/utils/tipo_param.rb +0 -16
@@ -1,108 +1,137 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'strscan' # para usar ::StringScanner
|
3
|
-
require_relative 'utils/tipo_param'
|
4
|
-
|
5
1
|
# lib/bddgenx/steps_generator.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe StepsGenerator, responsável por gerar
|
5
|
+
# definições de passos do Cucumber a partir de arquivos .feature.
|
6
|
+
# Suporta palavras-chave Gherkin em Português e Inglês e parametriza
|
7
|
+
# strings e números conforme necessário.
|
8
|
+
|
9
|
+
require 'fileutils'
|
10
|
+
require 'strscan' # Para uso de StringScanner
|
6
11
|
|
7
12
|
module Bddgenx
|
13
|
+
# Gera arquivos de definições de passos Ruby para Cucumber
|
14
|
+
# com base em arquivos .feature.
|
8
15
|
class StepsGenerator
|
16
|
+
# Palavras-chave Gherkin em Português usadas em arquivos de feature
|
17
|
+
# @return [Array<String>]
|
9
18
|
GHERKIN_KEYS_PT = %w[Dado Quando Então E Mas].freeze
|
19
|
+
|
20
|
+
# Palavras-chave Gherkin em Inglês usadas em arquivos de feature
|
21
|
+
# @return [Array<String>]
|
10
22
|
GHERKIN_KEYS_EN = %w[Given When Then And But].freeze
|
11
|
-
ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
|
12
23
|
|
13
|
-
#
|
24
|
+
# Conjunto de todas as palavras-chave suportadas (PT + EN)
|
25
|
+
# @return [Array<String>]
|
26
|
+
ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
|
27
|
+
|
28
|
+
# Converte uma string para camelCase, útil para nomes de argumentos
|
29
|
+
#
|
30
|
+
# @param [String] str Texto de entrada a ser convertido
|
31
|
+
# @return [String] Versão em camelCase do texto
|
14
32
|
def self.camelize(str)
|
15
|
-
|
16
|
-
|
33
|
+
partes = str.strip.split(/[^a-zA-Z0-9]+/)
|
34
|
+
partes.map.with_index { |palavra, i| i.zero? ? palavra.downcase : palavra.capitalize }.join
|
17
35
|
end
|
18
36
|
|
19
|
-
# Gera
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# Respeita idioma de entrada (pt/en) para keywords geradas
|
37
|
+
# Gera definições de passos a partir de um arquivo .feature
|
38
|
+
#
|
39
|
+
# @param [String] feature_path Caminho para o arquivo .feature
|
40
|
+
# @raise [ArgumentError] Se feature_path não for String
|
41
|
+
# @return [Boolean] Retorna true se passos foram gerados, false se não houver passos
|
25
42
|
def self.gerar_passos(feature_path)
|
26
|
-
|
43
|
+
# Valida tipo de entrada
|
44
|
+
unless feature_path.is_a?(String)
|
45
|
+
raise ArgumentError, "Caminho esperado como String, recebeu #{feature_path.class}"
|
46
|
+
end
|
47
|
+
|
48
|
+
linhas = File.readlines(feature_path)
|
27
49
|
|
28
|
-
lines = File.readlines(feature_path)
|
29
50
|
# Detecta idioma no cabeçalho: "# language: pt" ou "# language: en"
|
30
|
-
lang = if (m =
|
51
|
+
lang = if (m = linhas.find { |l| l =~ /^#\s*language:\s*(\w+)/i })
|
31
52
|
m[/^#\s*language:\s*(\w+)/i, 1].downcase
|
32
53
|
else
|
33
54
|
'pt'
|
34
55
|
end
|
35
56
|
|
36
|
-
|
37
|
-
|
57
|
+
# Mapas de tradução entre PT e EN
|
58
|
+
pt_para_en = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
|
59
|
+
en_para_pt = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
|
38
60
|
|
39
|
-
# Seleciona
|
40
|
-
|
41
|
-
ALL_KEYS.any? { |
|
61
|
+
# Seleciona linhas que começam com palavras-chave Gherkin
|
62
|
+
linhas_passos = linhas.map(&:strip).select do |linha|
|
63
|
+
ALL_KEYS.any? { |chave| linha.start_with?(chave + ' ') }
|
42
64
|
end
|
43
|
-
return false if step_lines.empty?
|
44
65
|
|
45
|
-
|
46
|
-
|
47
|
-
file = File.join(dir, "#{File.basename(feature_path, '.feature')}_steps.rb")
|
66
|
+
# Se não encontrar passos, retorna false
|
67
|
+
return false if linhas_passos.empty?
|
48
68
|
|
49
|
-
|
69
|
+
# Cria diretório e arquivo de saída
|
70
|
+
dir_saida = File.join(File.dirname(feature_path), 'steps')
|
71
|
+
FileUtils.mkdir_p(dir_saida)
|
72
|
+
arquivo_saida = File.join(dir_saida, "#{File.basename(feature_path, '.feature')}_steps.rb")
|
50
73
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
74
|
+
# Cabeçalho do arquivo gerado
|
75
|
+
conteudo = +"# encoding: utf-8\n"
|
76
|
+
conteudo << "# Definições de passos geradas automaticamente para #{File.basename(feature_path)}\n\n"
|
77
|
+
|
78
|
+
linhas_passos.each do |linha|
|
79
|
+
palavra_original, restante = linha.split(' ', 2)
|
80
|
+
|
81
|
+
# Define palavra-chave no idioma de saída
|
82
|
+
chave = case lang
|
83
|
+
when 'en' then pt_para_en[palavra_original] || palavra_original
|
84
|
+
else en_para_pt[palavra_original] || palavra_original
|
85
|
+
end
|
60
86
|
|
61
|
-
|
62
|
-
|
63
|
-
|
87
|
+
texto_bruto = restante.dup
|
88
|
+
scanner = ::StringScanner.new(restante)
|
89
|
+
padrao = ''
|
90
|
+
tokens = []
|
64
91
|
|
65
92
|
until scanner.eos?
|
66
93
|
if scanner.check(/"<([^>]+)>"/)
|
67
94
|
scanner.scan(/"<([^>]+)>"/)
|
68
95
|
tokens << scanner[1]
|
69
|
-
|
96
|
+
padrao << '{string}'
|
70
97
|
elsif scanner.check(/<([^>]+)>/)
|
71
98
|
scanner.scan(/<([^>]+)>/)
|
72
99
|
tokens << scanner[1]
|
73
|
-
|
100
|
+
padrao << '{int}'
|
74
101
|
elsif scanner.check(/"([^"<>]+)"/)
|
75
102
|
scanner.scan(/"([^"<>]+)"/)
|
76
103
|
tokens << scanner[1]
|
77
|
-
|
104
|
+
padrao << '{string}'
|
78
105
|
elsif scanner.check(/\d+(?:\.\d+)?/)
|
79
|
-
|
80
|
-
tokens <<
|
81
|
-
|
106
|
+
numero = scanner.scan(/\d+(?:\.\d+)?/)
|
107
|
+
tokens << numero
|
108
|
+
padrao << '{int}'
|
82
109
|
else
|
83
|
-
|
110
|
+
padrao << scanner.getch
|
84
111
|
end
|
85
112
|
end
|
86
113
|
|
87
|
-
# Escapa aspas no padrão
|
88
|
-
|
89
|
-
|
114
|
+
# Escapa aspas no padrão
|
115
|
+
padrao_seguro = padrao.gsub('"', '\\"')
|
116
|
+
assinatura = "#{chave}(\"#{padrao_seguro}\")"
|
90
117
|
|
118
|
+
# Adiciona parâmetros se existirem tokens
|
91
119
|
if tokens.any?
|
92
|
-
|
93
|
-
|
120
|
+
argumentos = tokens.each_index.map { |i| "arg#{i+1}" }.join(', ')
|
121
|
+
assinatura << " do |#{argumentos}|"
|
94
122
|
else
|
95
|
-
|
123
|
+
assinatura << ' do'
|
96
124
|
end
|
97
125
|
|
98
|
-
|
99
|
-
|
100
|
-
|
126
|
+
conteudo << "#{assinatura}\n"
|
127
|
+
conteudo << " pending 'Implementar passo: #{texto_bruto}'\n"
|
128
|
+
conteudo << "end\n\n"
|
101
129
|
end
|
102
130
|
|
103
|
-
|
104
|
-
|
131
|
+
# Escreve arquivo de saída
|
132
|
+
File.write(arquivo_saida, conteudo)
|
133
|
+
puts "✅ Steps gerados: #{arquivo_saida}"
|
105
134
|
true
|
106
135
|
end
|
107
136
|
end
|
108
|
-
end
|
137
|
+
end
|
data/lib/bddgenx/utils/backup.rb
CHANGED
@@ -1,16 +1,31 @@
|
|
1
|
+
# lib/bddgenx/backup.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe Backup, responsável por criar cópias de segurança
|
5
|
+
# de arquivos .feature antes de serem sobrescritos.
|
6
|
+
# As cópias são salvas em 'reports/backup' com timestamp no nome.
|
7
|
+
|
1
8
|
require 'fileutils'
|
2
9
|
require 'time'
|
3
10
|
|
4
11
|
module Bddgenx
|
12
|
+
# Gerencia a criação de backups de arquivos .feature
|
5
13
|
class Backup
|
14
|
+
# Salva uma versão antiga de um arquivo .feature em reports/backup,
|
15
|
+
# adicionando um timestamp ao nome do arquivo.
|
16
|
+
#
|
17
|
+
# @param caminho [String] Caminho completo para o arquivo .feature original
|
18
|
+
# @return [void]
|
19
|
+
# @note Se o arquivo não existir, não faz nada
|
6
20
|
def self.salvar_versao_antiga(caminho)
|
7
21
|
return unless File.exist?(caminho)
|
8
22
|
|
9
|
-
pasta
|
23
|
+
pasta = 'reports/backup'
|
10
24
|
FileUtils.mkdir_p(pasta)
|
11
|
-
|
12
|
-
|
13
|
-
|
25
|
+
|
26
|
+
base = File.basename(caminho, '.feature')
|
27
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
28
|
+
destino = "reports/backup/#{base}_#{timestamp}.feature"
|
14
29
|
|
15
30
|
FileUtils.cp(caminho, destino)
|
16
31
|
puts "📦 Backup criado: #{destino}"
|
@@ -1,28 +1,46 @@
|
|
1
1
|
# lib/bddgenx/font_loader.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe FontLoader, responsável por localizar e carregar
|
5
|
+
# famílias de fontes TrueType para uso com Prawn em geração de PDFs.
|
6
|
+
# Busca os arquivos de fonte no diretório assets/fonts dentro da gem.
|
7
|
+
|
2
8
|
require 'prawn'
|
3
|
-
require 'rubygems' # para Gem.loaded_specs
|
9
|
+
require 'rubygems' # para Gem.loaded_specs se necessário
|
4
10
|
|
5
11
|
module Bddgenx
|
12
|
+
# Gerencia o carregamento de fontes TTF para os documentos PDF.
|
6
13
|
class FontLoader
|
7
|
-
# Retorna o
|
14
|
+
# Retorna o caminho absoluto para a pasta assets/fonts dentro da gem
|
15
|
+
#
|
16
|
+
# @return [String] Caminho completo para o diretório de fontes
|
8
17
|
def self.fonts_path
|
9
18
|
File.expand_path('../../assets/fonts', __dir__)
|
10
19
|
end
|
11
20
|
|
12
|
-
#
|
21
|
+
# Cria o hash de famílias de fontes para registro no Prawn
|
22
|
+
#
|
23
|
+
# Verifica se os arquivos de fonte DejaVuSansMono incluem normal, bold,
|
24
|
+
# italic e bold_italic e têm tamanho mínimo aceitável.
|
25
|
+
# Se estiverem ausentes ou corrompidas, retorna hash vazio para usar fallback.
|
26
|
+
#
|
27
|
+
# @return [Hash{String => Hash<Symbol, String>}]
|
28
|
+
# - Chave: nome da família ('DejaVuSansMono')
|
29
|
+
# - Valor: mapa de estilos (:normal, :bold, :italic, :bold_italic) para os caminhos dos arquivos
|
13
30
|
def self.families
|
14
31
|
base = fonts_path
|
15
32
|
return {} unless Dir.exist?(base)
|
16
33
|
|
17
|
-
|
34
|
+
arquivos = {
|
18
35
|
normal: File.join(base, 'DejaVuSansMono.ttf'),
|
19
36
|
bold: File.join(base, 'DejaVuSansMono-Bold.ttf'),
|
20
37
|
italic: File.join(base, 'DejaVuSansMono-Oblique.ttf'),
|
21
38
|
bold_italic: File.join(base, 'DejaVuSansMono-BoldOblique.ttf')
|
22
39
|
}
|
23
40
|
|
24
|
-
|
25
|
-
|
41
|
+
# Verifica existência e tamanho mínimo de cada arquivo
|
42
|
+
if arquivos.values.all? { |path| File.file?(path) && File.size(path) > 12 }
|
43
|
+
{ 'DejaVuSansMono' => arquivos }
|
26
44
|
else
|
27
45
|
warn "⚠️ Fontes DejaVuSansMono ausentes ou corrompidas em #{base}. Usando fallback Courier."
|
28
46
|
{}
|
data/lib/bddgenx/utils/parser.rb
CHANGED
@@ -1,25 +1,56 @@
|
|
1
|
+
# lib/bddgenx/parser.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe Parser, responsável por ler e interpretar
|
5
|
+
# arquivos de história (.txt), extraindo cabeçalho e blocos de passos e exemplos.
|
6
|
+
# Utiliza constantes para identificação de tipos de blocos e suporta idiomas
|
7
|
+
# Português e Inglês na marcação de idioma e blocos de exemplos.
|
8
|
+
|
1
9
|
module Bddgenx
|
2
|
-
# Tipos de blocos
|
10
|
+
# Tipos de blocos reconhecidos na história (.txt), incluindo variações em Português
|
11
|
+
# e Inglês para blocos de exemplo.
|
12
|
+
# @return [Array<String>]
|
3
13
|
TIPOS_BLOCOS = %w[
|
4
14
|
CONTEXT SUCCESS FAILURE ERROR EXCEPTION
|
5
15
|
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
6
16
|
EXEMPLO EXEMPLOS RULE
|
7
17
|
].freeze
|
8
18
|
|
19
|
+
# Parser de arquivos de história, converte .txt em estrutura de hash
|
20
|
+
# com elementos :como, :quero, :para, :idioma e lista de :grupos.
|
9
21
|
class Parser
|
10
|
-
# Lê arquivo de história
|
22
|
+
# Lê e analisa um arquivo de história, retornando um Hash com a estrutura:
|
23
|
+
# {
|
24
|
+
# como: String ou nil,
|
25
|
+
# quero: String ou nil,
|
26
|
+
# para: String ou nil,
|
27
|
+
# idioma: 'pt' ou 'en',
|
28
|
+
# grupos: [
|
29
|
+
# {
|
30
|
+
# tipo: String (tipo de bloco),
|
31
|
+
# tag: String ou nil (tag opcional após o bloco),
|
32
|
+
# passos: Array<String> (linhas de passo),
|
33
|
+
# exemplos: Array<String> (linhas de exemplo)
|
34
|
+
# },
|
35
|
+
# ...
|
36
|
+
# ]
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# @param caminho_arquivo [String] Caminho para o arquivo .txt de história
|
40
|
+
# @raise [Errno::ENOENT] Se o arquivo não for encontrado
|
41
|
+
# @return [Hash] Estrutura da história pronta para geração de feature
|
11
42
|
def self.ler_historia(caminho_arquivo)
|
12
|
-
#
|
43
|
+
# Carrega linhas do arquivo, preservando linhas vazias e encoding UTF-8
|
13
44
|
linhas = File.readlines(caminho_arquivo, encoding: 'utf-8').map(&:rstrip)
|
14
45
|
|
15
|
-
# Detecta idioma:
|
46
|
+
# Detecta idioma no topo do arquivo: suporta '# lang: <codigo>' ou '# language: <codigo>'
|
16
47
|
idioma = 'pt'
|
17
48
|
if linhas.first =~ /^#\s*lang(?:uage)?\s*:\s*(\w+)/i
|
18
49
|
idioma = Regexp.last_match(1).downcase
|
19
50
|
linhas.shift
|
20
51
|
end
|
21
52
|
|
22
|
-
# Extrai
|
53
|
+
# Extrai cabeçalho: linhas que começam com Como/Quero/Para (variações PT/EN)
|
23
54
|
como, quero, para = nil, nil, nil
|
24
55
|
linhas.reject! do |l|
|
25
56
|
if l =~ /^\s*(?:como|eu como|as a)/i && como.nil?
|
@@ -36,19 +67,27 @@ module Bddgenx
|
|
36
67
|
end
|
37
68
|
end
|
38
69
|
|
39
|
-
|
70
|
+
# Inicializa estrutura da história
|
71
|
+
historia = {
|
72
|
+
como: como,
|
73
|
+
quero: quero,
|
74
|
+
para: para,
|
75
|
+
idioma: idioma,
|
76
|
+
grupos: []
|
77
|
+
}
|
40
78
|
exemplos_mode = false
|
41
79
|
|
80
|
+
# Processa cada linha restante para blocos e exemplos
|
42
81
|
linhas.each do |linha|
|
43
|
-
# Início de bloco de exemplos: [EXEMPLO]
|
82
|
+
# Início de bloco de exemplos: [EXEMPLO], [EXEMPLOS] ou [EXAMPLES] com tag opcional
|
44
83
|
if linha =~ /^\[(?:EXEMPLO|EXEMPLOS|EXAMPLES)\](?:@(\w+))?$/i
|
45
84
|
exemplos_mode = true
|
46
|
-
#
|
85
|
+
# Cria array de exemplos no último grupo, se ainda não existir
|
47
86
|
historia[:grupos].last[:exemplos] = []
|
48
87
|
next
|
49
88
|
end
|
50
89
|
|
51
|
-
# Início de bloco
|
90
|
+
# Início de bloco com tipo definido em TIPOS_BLOCOS e tag opcional
|
52
91
|
if linha =~ /^\[(#{TIPOS_BLOCOS.join('|')})\](?:@(\w+))?$/i
|
53
92
|
exemplos_mode = false
|
54
93
|
tipo = Regexp.last_match(1).upcase
|
@@ -57,7 +96,7 @@ module Bddgenx
|
|
57
96
|
next
|
58
97
|
end
|
59
98
|
|
60
|
-
# Atribui
|
99
|
+
# Atribui linha ao último bloco, como passo ou exemplo conforme modo
|
61
100
|
next if historia[:grupos].empty?
|
62
101
|
bloco = historia[:grupos].last
|
63
102
|
if exemplos_mode
|
@@ -1,18 +1,34 @@
|
|
1
1
|
# lib/bddgenx/pdf_exporter.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe PDFExporter, responsável por gerar documentos
|
5
|
+
# PDF a partir de arquivos .feature, formatados no estilo pretty Cucumber em
|
6
|
+
# preto e branco.
|
7
|
+
# Utiliza a gem Prawn para renderização de texto e tabelas.
|
8
|
+
|
2
9
|
require 'prawn'
|
3
10
|
require 'prawn/table'
|
4
11
|
require 'fileutils'
|
5
12
|
require_relative 'fontLoader'
|
6
13
|
|
7
|
-
# Suprime aviso de internacionalização para fontes AFM
|
14
|
+
# Suprime aviso de internacionalização para fontes AFM internas
|
8
15
|
Prawn::Fonts::AFM.hide_m17n_warning = true
|
9
16
|
|
10
17
|
module Bddgenx
|
18
|
+
# Gera documentos PDF baseados em arquivos .feature.
|
11
19
|
class PDFExporter
|
12
|
-
# Gera PDFs
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
20
|
+
# Gera PDFs de features, criando um para cada arquivo .feature ou apenas para
|
21
|
+
# o especificado em caminho_feature.
|
22
|
+
#
|
23
|
+
# @param caminho_feature [String, nil]
|
24
|
+
# Caminho para um arquivo .feature específico. Se nil ou vazio, gera para todos
|
25
|
+
# os arquivos em features/*.feature.
|
26
|
+
# @param only_new [Boolean]
|
27
|
+
# Se true, não sobrescreve arquivos PDF já existentes.
|
28
|
+
# @return [Hash<Symbol, Array<String>>]
|
29
|
+
# Retorna um hash com duas chaves:
|
30
|
+
# - :generated => array de caminhos dos PDFs gerados
|
31
|
+
# - :skipped => array de caminhos dos PDFs que foram pulados
|
16
32
|
def self.exportar_todos(caminho_feature: nil, only_new: false)
|
17
33
|
FileUtils.mkdir_p('reports/pdf')
|
18
34
|
features_list = if caminho_feature && !caminho_feature.empty?
|
@@ -21,14 +37,17 @@ module Bddgenx
|
|
21
37
|
Dir.glob('features/*.feature')
|
22
38
|
end
|
23
39
|
|
24
|
-
generated
|
40
|
+
generated = []
|
41
|
+
skipped = []
|
42
|
+
|
25
43
|
features_list.each do |feature|
|
26
44
|
unless File.file?(feature)
|
27
45
|
warn "⚠️ Feature não encontrada: #{feature}"
|
28
46
|
next
|
29
47
|
end
|
30
|
-
|
31
|
-
|
48
|
+
|
49
|
+
nome = File.basename(feature, '.feature')
|
50
|
+
destino = "reports/pdf/#{camel_case(nome)}.pdf"
|
32
51
|
|
33
52
|
if only_new && File.exist?(destino)
|
34
53
|
skipped << destino
|
@@ -42,14 +61,22 @@ module Bddgenx
|
|
42
61
|
{ generated: generated, skipped: skipped }
|
43
62
|
end
|
44
63
|
|
45
|
-
# Converte string para camelCase, removendo caracteres especiais
|
64
|
+
# Converte uma string para formato camelCase, removendo caracteres especiais.
|
65
|
+
#
|
66
|
+
# @param str [String] A string de entrada a ser transformada.
|
67
|
+
# @return [String] String no formato camelCase.
|
46
68
|
def self.camel_case(str)
|
47
69
|
clean = str.gsub(/[^0-9A-Za-z ]/, '')
|
48
70
|
parts = clean.split(/ |_/)
|
49
71
|
([parts.first&.downcase] + (parts[1..] || []).map(&:capitalize)).join
|
50
72
|
end
|
51
73
|
|
52
|
-
# Gera
|
74
|
+
# Gera um documento PDF a partir de um arquivo .feature, aplicando estilos
|
75
|
+
# de cabeçalhos, cenários, passos e tabelas conforme padrões Cucumber.
|
76
|
+
#
|
77
|
+
# @param origem [String] Caminho para o arquivo .feature de origem.
|
78
|
+
# @param destino [String] Caminho onde o PDF será salvo.
|
79
|
+
# @return [void]
|
53
80
|
def self.exportar_arquivo(origem, destino)
|
54
81
|
FileUtils.mkdir_p(File.dirname(destino))
|
55
82
|
conteudo = File.read(origem, encoding: 'utf-8')
|
@@ -59,17 +86,21 @@ module Bddgenx
|
|
59
86
|
pdf.font_size 9
|
60
87
|
|
61
88
|
table_buffer = []
|
89
|
+
|
62
90
|
conteudo.each_line do |linha|
|
63
91
|
text = linha.chomp
|
64
92
|
|
65
|
-
#
|
93
|
+
# Agrega linhas de tabela até o bloco terminar
|
66
94
|
if text =~ /^\s*\|.*\|/i
|
67
|
-
|
95
|
+
# Remove bordas laterais e separa colunas
|
96
|
+
row = text.gsub(/^\s*\||\|\s*$/, '').split('|').map(&:strip)
|
97
|
+
table_buffer << row
|
68
98
|
next
|
69
99
|
elsif table_buffer.any?
|
100
|
+
# Renderiza tabela acumulada
|
70
101
|
pdf.table(table_buffer, header: true, width: pdf.bounds.width) do
|
71
|
-
self.header
|
72
|
-
self.row_colors
|
102
|
+
self.header = true
|
103
|
+
self.row_colors = ['EEEEEE', 'FFFFFF']
|
73
104
|
self.cell_style = { size: 8, font: 'Courier' }
|
74
105
|
end
|
75
106
|
pdf.move_down 4
|
@@ -95,6 +126,7 @@ module Bddgenx
|
|
95
126
|
pdf.text text, size: 8, style: :italic
|
96
127
|
pdf.move_down 4
|
97
128
|
when /^(?:\s*)(Given|When|Then|And|But|Dado|Quando|Então|E|Mas)\b/i
|
129
|
+
# Passo Gherkin: destaca palavra-chave e texto
|
98
130
|
keyword, rest = text.strip.split(' ', 2)
|
99
131
|
pdf.indent(20) do
|
100
132
|
pdf.formatted_text [
|
@@ -110,20 +142,21 @@ module Bddgenx
|
|
110
142
|
end
|
111
143
|
end
|
112
144
|
|
113
|
-
# Renderiza
|
145
|
+
# Renderiza tabela remanescente, se houver
|
114
146
|
if table_buffer.any?
|
115
147
|
pdf.table(table_buffer, header: true, width: pdf.bounds.width) do
|
116
|
-
self.header
|
117
|
-
self.row_colors
|
148
|
+
self.header = true
|
149
|
+
self.row_colors = ['EEEEEE', 'FFFFFF']
|
118
150
|
self.cell_style = { size: 8, font: 'Courier' }
|
119
151
|
end
|
120
152
|
pdf.move_down 4
|
121
153
|
end
|
122
154
|
|
155
|
+
# Numeração de páginas
|
123
156
|
pdf.number_pages 'Página <page> de <total>', align: :right, size: 8
|
124
157
|
end
|
125
158
|
rescue => e
|
126
159
|
warn "❌ Erro ao gerar PDF de #{origem}: #{e.message}"
|
127
160
|
end
|
128
161
|
end
|
129
|
-
end
|
162
|
+
end
|
data/lib/bddgenx/utils/tracer.rb
CHANGED
@@ -1,20 +1,40 @@
|
|
1
|
+
# lib/bddgenx/tracer.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe Tracer, responsável por gerar e manter
|
5
|
+
# informações de rastreabilidade de cenários e passos em um arquivo CSV.
|
6
|
+
# Útil para auditoria e análise de cobertura de cenários gerados.
|
7
|
+
|
1
8
|
require 'csv'
|
2
9
|
require 'fileutils'
|
3
10
|
|
4
11
|
module Bddgenx
|
12
|
+
# Classe para adicionar registros de rastreabilidade a um relatório CSV.
|
5
13
|
class Tracer
|
14
|
+
# Adiciona entradas de rastreabilidade para cada passo de cada grupo
|
15
|
+
# da história em um arquivo CSV localizado em 'reports/output/rastreabilidade.csv'.
|
16
|
+
#
|
17
|
+
# @param historia [Hash]
|
18
|
+
# Objeto de história contendo :quero (título da funcionalidade) e :grupos,
|
19
|
+
# onde cada grupo possui :tipo, :tag, e :passos (Array<String>)
|
20
|
+
# @param nome_arquivo_feature [String]
|
21
|
+
# Nome do arquivo .feature de onde os passos foram gerados
|
22
|
+
# @return [void]
|
6
23
|
def self.adicionar_entrada(historia, nome_arquivo_feature)
|
24
|
+
# Garante existência do diretório de saída
|
7
25
|
FileUtils.mkdir_p('reports/output')
|
8
26
|
arquivo_csv = 'reports/output/rastreabilidade.csv'
|
9
27
|
|
28
|
+
# Cabeçalho padrão do CSV: identifica colunas
|
10
29
|
cabecalho = ['Funcionalidade', 'Tipo', 'Tag', 'Cenário', 'Passo', 'Origem']
|
11
30
|
|
12
31
|
linhas = []
|
13
32
|
|
33
|
+
# Itera sobre grupos de passos para compor linhas de rastreabilidade
|
14
34
|
historia[:grupos].each_with_index do |grupo, idx|
|
15
|
-
tipo
|
16
|
-
tag
|
17
|
-
passos = grupo[:passos]
|
35
|
+
tipo = grupo[:tipo]
|
36
|
+
tag = grupo[:tag]
|
37
|
+
passos = grupo[:passos] || []
|
18
38
|
|
19
39
|
nome_funcionalidade = historia[:quero].gsub(/^Quero\s*/, '').strip
|
20
40
|
nome_cenario = "Cenário #{idx + 1}"
|
@@ -31,10 +51,18 @@ module Bddgenx
|
|
31
51
|
end
|
32
52
|
end
|
33
53
|
|
54
|
+
# Escreve ou anexa as linhas geradas ao CSV
|
34
55
|
escrever_csv(arquivo_csv, cabecalho, linhas)
|
35
56
|
end
|
36
57
|
|
58
|
+
# Escreve ou anexa registros em um arquivo CSV, criando cabeçalho se necessário.
|
59
|
+
#
|
60
|
+
# @param caminho [String] Caminho completo para o arquivo CSV de rastreabilidade
|
61
|
+
# @param cabecalho [Array<String>] Array de títulos das colunas a serem escritos
|
62
|
+
# @param linhas [Array<Array<String>>] Dados a serem gravados no CSV (cada sub-array é uma linha)
|
63
|
+
# @return [void]
|
37
64
|
def self.escrever_csv(caminho, cabecalho, linhas)
|
65
|
+
# Verifica se é um novo arquivo para incluir o cabeçalho
|
38
66
|
novo_arquivo = !File.exist?(caminho)
|
39
67
|
|
40
68
|
CSV.open(caminho, 'a+', col_sep: ';', force_quotes: true) do |csv|
|
@@ -1,32 +1,58 @@
|
|
1
|
+
# lib/bddgenx/validator.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# Este arquivo define a classe Validator, responsável por validar a estrutura
|
5
|
+
# de uma história antes de gerar cenários ou arquivos .feature.
|
6
|
+
# Verifica presença de cabeçalho obrigatório e integridade dos grupos de passos.
|
7
|
+
|
1
8
|
module Bddgenx
|
9
|
+
# Valida objetos de história garantindo que possuam campos e blocos corretos.
|
2
10
|
class Validator
|
11
|
+
# Valida o hash de história fornecido.
|
12
|
+
#
|
13
|
+
# Verifica:
|
14
|
+
# - Presença das chaves :como, :quero, :para no cabeçalho
|
15
|
+
# - Presença de pelo menos um grupo em :grupos
|
16
|
+
# - Cada grupo deve ter ao menos passos ou exemplos
|
17
|
+
# - Grupos do tipo "EXAMPLES" devem conter uma tabela de exemplos válida
|
18
|
+
#
|
19
|
+
# @param historia [Hash] Objeto de história com chaves :como, :quero, :para e :grupos
|
20
|
+
# @return [Boolean] Retorna true se a história for válida; caso contrário, false
|
3
21
|
def self.validar(historia)
|
4
22
|
erros = []
|
5
23
|
|
24
|
+
# Verificação do cabeçalho obrigatório
|
6
25
|
unless historia[:como] && historia[:quero] && historia[:para]
|
7
26
|
erros << "❌ Cabeçalho incompleto (Como, Quero, Para obrigatórios)"
|
8
27
|
end
|
9
28
|
|
10
|
-
|
29
|
+
# Verificação de grupos de passos
|
30
|
+
if historia[:grupos].nil? || historia[:grupos].empty?
|
11
31
|
erros << "❌ Nenhum grupo de blocos detectado"
|
12
32
|
else
|
13
33
|
historia[:grupos].each_with_index do |grupo, idx|
|
14
|
-
|
34
|
+
# Cada grupo deve conter passos ou exemplos
|
35
|
+
if (grupo[:passos].nil? || grupo[:passos].empty?) &&
|
36
|
+
(grupo[:exemplos].nil? || grupo[:exemplos].empty?)
|
15
37
|
erros << "❌ Grupo #{idx + 1} do tipo [#{grupo[:tipo]}] está vazio"
|
16
38
|
end
|
17
39
|
|
18
|
-
|
40
|
+
# Validação específica para blocos de exemplos
|
41
|
+
if grupo[:tipo].casecmp('EXAMPLES').zero? &&
|
42
|
+
grupo[:exemplos].none? { |l| l.strip.start_with?('|') }
|
19
43
|
erros << "❌ Grupo de EXAMPLES no bloco #{idx + 1} não contém tabela válida"
|
20
44
|
end
|
21
45
|
end
|
22
46
|
end
|
23
47
|
|
48
|
+
# Exibe erros e retorna false se houver falhas
|
24
49
|
if erros.any?
|
25
50
|
puts "⚠️ Erros encontrados no arquivo:"
|
26
51
|
erros.each { |e| puts " - #{e}" }
|
27
52
|
return false
|
28
53
|
end
|
29
54
|
|
55
|
+
# História válida
|
30
56
|
true
|
31
57
|
end
|
32
58
|
end
|