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.
@@ -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
- # Converte texto para camelCase (para nomes de argumentos)
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
- parts = str.strip.split(/[^a-zA-Z0-9]+/)
16
- parts.map.with_index { |w, i| i.zero? ? w.downcase : w.capitalize }.join
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 step definitions a partir de um arquivo .feature
20
- # - "<nome>" => {string}
21
- # - <nome> => {int}
22
- # - "texto" => {string}
23
- # - numeros inteiros ou floats soltos => {int}
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
- raise ArgumentError, "Caminho esperado como String, recebeu #{feature_path.class}" unless feature_path.is_a?(String)
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 = lines.find { |l| l =~ /^#\s*language:\s*(\w+)/i })
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
- pt_to_en = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
37
- en_to_pt = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
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 apenas linhas de passo
40
- step_lines = lines.map(&:strip).select do |l|
41
- ALL_KEYS.any? { |k| l.start_with?(k + ' ') }
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
- dir = File.join(File.dirname(feature_path), 'steps')
46
- FileUtils.mkdir_p(dir)
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
- content = +"# encoding: utf-8\n# Auto-generated step definitions for #{File.basename(feature_path)}\n\n"
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
- step_lines.each do |line|
52
- # Extrai keyword original e resto do passo
53
- orig_kw, rest = line.split(' ', 2)
54
- # Converte keyword conforme idioma de entrada
55
- kw = case lang
56
- when 'en' then pt_to_en[orig_kw] || orig_kw
57
- else en_to_pt[orig_kw] || orig_kw
58
- end
59
- raw = rest.dup
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
- scanner = ::StringScanner.new(rest)
62
- pattern = ''
63
- tokens = []
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
- pattern << '{string}'
96
+ padrao << '{string}'
70
97
  elsif scanner.check(/<([^>]+)>/)
71
98
  scanner.scan(/<([^>]+)>/)
72
99
  tokens << scanner[1]
73
- pattern << '{int}'
100
+ padrao << '{int}'
74
101
  elsif scanner.check(/"([^"<>]+)"/)
75
102
  scanner.scan(/"([^"<>]+)"/)
76
103
  tokens << scanner[1]
77
- pattern << '{string}'
104
+ padrao << '{string}'
78
105
  elsif scanner.check(/\d+(?:\.\d+)?/)
79
- num = scanner.scan(/\d+(?:\.\d+)?/)
80
- tokens << num
81
- pattern << '{int}'
106
+ numero = scanner.scan(/\d+(?:\.\d+)?/)
107
+ tokens << numero
108
+ padrao << '{int}'
82
109
  else
83
- pattern << scanner.getch
110
+ padrao << scanner.getch
84
111
  end
85
112
  end
86
113
 
87
- # Escapa aspas no padrão final
88
- safe_pattern = pattern.gsub('"', '\\"')
89
- signature = "#{kw}(\"#{safe_pattern}\")"
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
- args = tokens.each_index.map { |i| "args#{i+1}" }.join(', ')
93
- signature += " do |#{args}|"
120
+ argumentos = tokens.each_index.map { |i| "arg#{i+1}" }.join(', ')
121
+ assinatura << " do |#{argumentos}|"
94
122
  else
95
- signature += ' do'
123
+ assinatura << ' do'
96
124
  end
97
125
 
98
- content << signature << "\n"
99
- content << " pending 'Implementar passo: #{raw}'\n"
100
- content << "end\n\n"
126
+ conteudo << "#{assinatura}\n"
127
+ conteudo << " pending 'Implementar passo: #{texto_bruto}'\n"
128
+ conteudo << "end\n\n"
101
129
  end
102
130
 
103
- File.write(file, content)
104
- puts "✅ Steps gerados: #{file}"
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
@@ -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 = 'reports/backup'
23
+ pasta = 'reports/backup'
10
24
  FileUtils.mkdir_p(pasta)
11
- base = File.basename(caminho, ".feature")
12
- timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
13
- destino = "reports/backup/#{base}_#{timestamp}.feature"
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 diretório assets/fonts dentro da gem
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
- # Carrega famílias de fontes TrueType para Prawn
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
- files = {
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
- if files.values.all? { |path| File.file?(path) && File.size(path) > 12 }
25
- { 'DejaVuSansMono' => files }
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
  {}
@@ -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 GUARDADOS, incluindo português EXEMPLO/EXEMPLOS
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 (.txt) e retorna hash com atributos e grupos
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
- # todas as linhas, mantendo vazias
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: aceita "# lang: en" ou "# language: en"
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 Cabeçalho Como/Quero/Para
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
- historia = { como: como, quero: quero, para: para, idioma: idioma, grupos: [] }
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] ou [EXEMPLOS] ou [EXAMPLES]
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
- # inicia array se for o primeiro exemplo
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 de tipo (SUCCESS, FAILURE, REGRA etc.)
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 linhas ao último grupo
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 built-in
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 a partir de arquivos .feature no estilo pretty Cucumber em P/B
13
- # Params:
14
- # +caminho_feature+:: (String) caminho para um arquivo .feature específico. Se nil, gera para todos em features/*.feature
15
- # +only_new+:: (Boolean) se true, não sobrescreve PDFs já existentes
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, skipped = [], []
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
- nome = File.basename(feature, '.feature')
31
- destino = "reports/pdf/#{camel_case(nome)}.pdf"
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 o PDF formatado a partir de um único arquivo .feature, sem executar testes
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
- # Agrupa linhas de tabela e renderiza quando termina
93
+ # Agrega linhas de tabela até o bloco terminar
66
94
  if text =~ /^\s*\|.*\|/i
67
- table_buffer << text.gsub(/^\s*\||\|\s*$/, '').split('|').map(&:strip)
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 = true
72
- self.row_colors = ['EEEEEE', 'FFFFFF']
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 qualquer tabela remanescente
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 = true
117
- self.row_colors = ['EEEEEE', 'FFFFFF']
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
@@ -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 = grupo[:tipo]
16
- tag = grupo[: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
- if historia[:grupos].empty?
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
- if grupo[:passos].empty? && grupo[:exemplos].empty?
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
- if grupo[:tipo] == "EXAMPLES" && grupo[:exemplos].none? { |l| l.strip.start_with?('|') }
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