bddgenx 2.4.5 → 2.4.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35e8e21b724442b0cb35cda26d862a4aa3a1eccbfd1bdb050dba306c1606600e
4
- data.tar.gz: 540d3c814c5621fb7184261c4f070b4f30fa7c0ea351028b6205739556864bc9
3
+ metadata.gz: 94b275818990d76f36659c7be2a611232c1dfa90d812ac0e70dbfa6ae9c6e3e9
4
+ data.tar.gz: 5219e00c5048c07aee0ad93617a8a140e5725610ea532fe06fea798c097fe5b7
5
5
  SHA512:
6
- metadata.gz: 6942cf6d50f8f4f4a1ddd69f05a294f066907798f3b3dcfaac0cbc4bb04844267488bad866c65eef47c1bc9596efca105b366102d6eff3305025afbb70b8565e
7
- data.tar.gz: ed96a04fa5b37f17f768f3f015eaed93e5a944ae9d041425743610b97b3dc1bbf58caef555c692d6cf732b1229d373a3f3b0fae6ebe02c719514eaa6f253b839
6
+ metadata.gz: 59b7b3e17525393b9cf63e6ef4b9a71ad3e2a49c69c35967180883d920ab0160400ba3ce42fe8dd5a1571c9246264dccc388faa5bcbae7808c6385dc79133ad5
7
+ data.tar.gz: 8dc211ad3e323a958fdb2e0b273fb5054d5f432d7be98677fbe1bf41eea2923284d763e0fe73181cc1740dfe2c5c9cfc88fa76d4fe299569f5af9e4decf0d5cc
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.4.5
1
+ 2.4.7
@@ -8,30 +8,6 @@
8
8
 
9
9
  module Bddgenx
10
10
  class Generator
11
- # Palavras-chave do Gherkin em Português
12
- GHERKIN_KEYS_PT = %w[Dado Quando Então E Mas].freeze
13
-
14
- # Palavras-chave do Gherkin em Inglês
15
- GHERKIN_KEYS_EN = %w[Given When Then And But].freeze
16
-
17
- # Mapeamento PT → EN
18
- GHERKIN_MAP_PT_EN = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
19
-
20
- # Mapeamento EN → PT
21
- GHERKIN_MAP_EN_PT = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
22
-
23
- # Todas as palavras-chave reconhecidas pelos parsers
24
- ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
25
-
26
- ##
27
- # Extrai todas as linhas de exemplo de um array de strings.
28
- #
29
- # @param raw [Array<String>] Array com linhas do grupo de passos
30
- # @return [Array<String>] Somente as linhas que contêm exemplos (começam com '|')
31
- def self.dividir_examples(raw)
32
- raw.select { |l| l.strip.start_with?('|') }
33
- end
34
-
35
11
  ##
36
12
  # Gera o conteúdo de um arquivo `.feature` baseado na história fornecida.
37
13
  # Pode operar em três modos:
@@ -42,51 +18,54 @@ module Bddgenx
42
18
  # @param input [String, Hash] Caminho para um `.txt` ou estrutura de história já processada
43
19
  # @param override_path [String, nil] Caminho alternativo de saída
44
20
  # @return [Array<String, String>] Caminho e conteúdo do `.feature`
21
+
45
22
  def self.gerar_feature(input, override_path = nil)
46
23
  modo = ENV['BDD_MODE']&.to_sym || :static
47
24
 
25
+ # Verifique o idioma antes de continuar
48
26
  if input.is_a?(String) && input.end_with?('.txt') && [:gemini, :chatgpt].include?(modo)
49
27
  # Geração com IA
50
28
  raw_txt = File.read(input)
51
29
  historia = {
52
- idioma: 'pt',
30
+ idioma: 'pt', # Idioma inicial, caso não seja detectado no arquivo
53
31
  quero: File.basename(input, '.txt').tr('_', ' ').capitalize,
54
32
  como: '',
55
33
  para: '',
56
34
  grupos: []
57
35
  }
58
36
 
37
+ # Detecta idioma do arquivo
38
+ idioma = Utils::detecta_idioma_de_texto(raw_txt)
39
+ historia[:idioma] = idioma
40
+
59
41
  texto_gerado = if modo == :gemini
60
42
  GeminiCliente.gerar_cenarios(raw_txt)
61
43
  else
62
- ChatGPTCliente.gerar_cenarios(raw_txt)
44
+ ChatGptCliente.gerar_cenarios(raw_txt)
63
45
  end
64
46
 
65
47
  historia[:grupos] << {
66
48
  tipo: 'gerado',
67
49
  tag: 'ia',
68
- passos: GherkinCleaner.limpar(texto_gerado).lines.map(&:strip).reject(&:empty?)
50
+ passos: Utils.limpar(texto_gerado).lines.map(&:strip).reject(&:empty?)
69
51
  }
70
52
  else
71
53
  # Geração estática
72
54
  historia = input.is_a?(String) ? Parser.ler_historia(input) : input
73
55
  end
74
56
 
75
- idioma = historia[:idioma] || 'pt'
57
+ # Verifique o idioma que está sendo usado
58
+ idioma = historia[:idioma] || Utils::obter_idioma_do_arquivo(input)
59
+
60
+ # Verifique se o idioma está correto
61
+ puts "Idioma detectado: #{idioma}"
62
+
76
63
  cont = 1
77
64
 
78
65
  # Cria nome-base do arquivo .feature
79
- nome_base = historia[:quero]
80
- .gsub(/[^a-z0-9]/i, '_')
81
- .downcase
82
- .split('_')
83
- .reject(&:empty?)
84
- .first(5)
85
- .join('_')
86
-
66
+ nome_base = historia[:quero].gsub(/[^a-z0-9]/i, '_').downcase.split('_').reject(&:empty?).first(5).join('_')
87
67
  caminho = override_path || "features/#{nome_base}.feature"
88
68
 
89
- # Palavras-chave localizadas
90
69
  palavras = {
91
70
  feature: idioma == 'en' ? 'Feature' : 'Funcionalidade',
92
71
  contexto: idioma == 'en' ? 'Background' : 'Contexto',
@@ -96,7 +75,6 @@ module Bddgenx
96
75
  regra: idioma == 'en' ? 'Rule' : 'Regra'
97
76
  }
98
77
 
99
- # Cabeçalho do arquivo .feature
100
78
  conteudo = <<~GHK
101
79
  # language: #{idioma}
102
80
  #{palavras[:feature]}: #{historia[:quero].sub(/^Quero\s*/i,'')}
@@ -107,9 +85,9 @@ module Bddgenx
107
85
 
108
86
  # Controle para não repetir passos
109
87
  passos_unicos = Set.new
110
- pt_map = GHERKIN_MAP_PT_EN
111
- en_map = GHERKIN_MAP_EN_PT
112
- detect = ALL_KEYS
88
+ pt_map = Utils::GHERKIN_MAP_PT_EN
89
+ en_map = Utils::GHERKIN_MAP_EN_PT
90
+ detect = Utils::ALL_KEYS
113
91
 
114
92
  historia[:grupos].each do |grupo|
115
93
  passos = grupo[:passos] || []
@@ -173,7 +151,9 @@ module Bddgenx
173
151
  def self.salvar_feature(caminho, conteudo)
174
152
  FileUtils.mkdir_p(File.dirname(caminho))
175
153
  File.write(caminho, conteudo)
176
- puts "✅ Arquivo .feature gerado: #{caminho}"
154
+ puts I18n.t('messages.feature_created', caminho: caminho)
177
155
  end
178
156
  end
179
157
  end
158
+
159
+
@@ -101,16 +101,18 @@ module Bddgenx
101
101
  puts "\n🔍 #{I18n.t('messages.processing')}: #{arquivo}"
102
102
 
103
103
  historia = Parser.ler_historia(arquivo)
104
+ idioma = Utils.obter_idioma_do_arquivo(arquivo) || historia[:idioma]
105
+ historia[:idioma] = idioma
104
106
  unless Validator.validar(historia)
105
107
  ignored += 1
106
108
  puts "❌ #{I18n.t('messages.invalid_story')}: #{arquivo}"
107
109
  next
108
110
  end
109
111
 
110
- # Geração via IA (ChatGPT, Gemini, Deepseek)
111
- if %w[gemini chatgpt deepseek].include?(modo)
112
+ # Geração via IA (ChatGPT, Gemini)
113
+ if %w[gemini chatgpt].include?(modo)
112
114
  puts I18n.t('messages.start_ia', modo: modo.capitalize)
113
- idioma = IA::GeminiCliente.detecta_idioma_arquivo(arquivo)
115
+ idioma = Utils.obter_idioma_do_arquivo(arquivo) || historia[:idioma]
114
116
 
115
117
  feature_text = Support::Loader.run(I18n.t('messages.ia_waiting'), :default) do
116
118
  case modo
@@ -118,14 +120,12 @@ module Bddgenx
118
120
  IA::GeminiCliente.gerar_cenarios(historia, idioma)
119
121
  when 'chatgpt'
120
122
  IA::ChatGptCliente.gerar_cenarios(historia, idioma)
121
- when 'deepseek'
122
- IA::DeepseekCliente.gerar_cenarios(historia, idioma)
123
123
  end
124
124
  end
125
125
 
126
126
  if feature_text
127
127
  feature_path = Generator.path_para_feature(arquivo)
128
- feature_content = Bddgenx::GherkinCleaner.limpar(feature_text)
128
+ feature_content = Utils.limpar(feature_text)
129
129
  else
130
130
  ignored += 1
131
131
  puts I18n.t('messages.feature_fail', arquivo: arquivo)
@@ -8,15 +8,6 @@
8
8
 
9
9
  module Bddgenx
10
10
  class StepsGenerator
11
- # Palavras-chave Gherkin em Português
12
- GHERKIN_KEYS_PT = %w[Dado Quando Então E Mas].freeze
13
-
14
- # Palavras-chave Gherkin em Inglês
15
- GHERKIN_KEYS_EN = %w[Given When Then And But].freeze
16
-
17
- # Conjunto de todas as palavras-chave reconhecidas
18
- ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
19
-
20
11
  ##
21
12
  # Transforma uma string em camelCase (sem alterar acentuação).
22
13
  #
@@ -38,23 +29,20 @@ module Bddgenx
38
29
  raise ArgumentError, I18n.t('errors.invalid_path', path: feature_path.class) unless feature_path.is_a?(String)
39
30
 
40
31
  linhas = File.readlines(feature_path)
32
+ lang = Utils::detecta_idioma_de_texto(linhas.join)
33
+
34
+ I18n.locale = lang.to_sym rescue :pt
41
35
 
42
- # Detecta o idioma a partir da linha `# language:`
43
- lang = if (m = linhas.find { |l| l =~ /^#\s*language:\s*(\w+)/i })
44
- m[/^#\s*language:\s*(\w+)/i, 1].downcase
45
- else
46
- 'pt'
47
- end
48
36
 
49
37
  # Define o locale do I18n conforme idioma detectado
50
38
  I18n.locale = lang.to_sym rescue :pt
51
39
 
52
- pt_para_en = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
53
- en_para_pt = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
40
+ pt_para_en = Utils::GHERKIN_KEYS_PT.zip(Utils::GHERKIN_KEYS_EN).to_h
41
+ en_para_pt = Utils::GHERKIN_KEYS_EN.zip(Utils::GHERKIN_KEYS_PT).to_h
54
42
 
55
43
  # Seleciona apenas as linhas que representam passos
56
44
  linhas_passos = linhas.map(&:strip).select do |linha|
57
- ALL_KEYS.any? { |chave| linha.start_with?(chave + ' ') }
45
+ Utils::ALL_KEYS.any? { |chave| linha.start_with?(chave + ' ') }
58
46
  end
59
47
 
60
48
  return false if linhas_passos.empty?
@@ -28,7 +28,7 @@ module Bddgenx
28
28
  return fallback_com_gemini(historia, idioma)
29
29
  end
30
30
 
31
- # Palavras-chave Gherkin para português e inglês
31
+
32
32
  keywords_pt = {
33
33
  feature: "Funcionalidade",
34
34
  scenario: "Cenário",
@@ -104,8 +104,8 @@ module Bddgenx
104
104
  texto_ia = json.dig("choices", 0, "message", "content")
105
105
 
106
106
  if texto_ia
107
- texto_limpo = Bddgenx::GherkinCleaner.limpar(texto_ia)
108
- Utils::StepCleaner.remover_steps_duplicados(texto_ia, idioma)
107
+ texto_limpo = Utils.limpar(texto_ia)
108
+ Utils::remover_steps_duplicados(texto_ia, idioma)
109
109
 
110
110
  # Ajusta a linha de idioma no arquivo gerado
111
111
  texto_limpo.sub!(/^# language: .*/, "# language: #{idioma}")
@@ -137,24 +137,6 @@ module Bddgenx
137
137
  warn I18n.t('messages.fallback_gemini')
138
138
  GeminiCliente.gerar_cenarios(historia, idioma)
139
139
  end
140
-
141
- ##
142
- # Detecta o idioma de um arquivo de feature pela linha "# language:".
143
- #
144
- # @param caminho_arquivo [String] Caminho para o arquivo de feature.
145
- # @return [String] Código do idioma detectado ('pt' por padrão).
146
- #
147
- def self.detecta_idioma_arquivo(caminho_arquivo)
148
- return 'pt' unless File.exist?(caminho_arquivo)
149
-
150
- File.foreach(caminho_arquivo) do |linha|
151
- if linha =~ /^#\s*language:\s*(\w{2})/i
152
- return $1.downcase
153
- end
154
- end
155
-
156
- 'pt'
157
- end
158
140
  end
159
141
  end
160
142
  end
@@ -106,15 +106,15 @@ module Bddgenx
106
106
  texto_ia = json["candidates"].first.dig("content", "parts", 0, "text")
107
107
  if texto_ia
108
108
  # Limpeza e sanitização do texto para manter padrão Gherkin
109
- texto_limpo = Bddgenx::GherkinCleaner.limpar(texto_ia)
110
- Utils::StepCleaner.remover_steps_duplicados(texto_ia, idioma)
109
+ texto_limpo = Utils.limpar(texto_ia)
110
+ Utils.remover_steps_duplicados(texto_ia, idioma)
111
111
 
112
112
  # Ajuste da diretiva de idioma na saída gerada
113
113
  texto_limpo.sub!(/^# language: .*/, "# language: #{idioma}")
114
114
  texto_limpo.prepend("# language: #{idioma}\n") unless texto_limpo.start_with?("# language:")
115
115
 
116
116
  # Garante diretiva de idioma
117
- feature_text = Bddgenx::GherkinCleaner.limpar(texto_ia)
117
+ feature_text = Utils.limpar(texto_ia)
118
118
  feature_text.sub!(/^# language: .*/, "") # remove qualquer # language: existente
119
119
  feature_text.prepend("# language: #{idioma}\n") # insere a correta
120
120
 
@@ -129,24 +129,6 @@ module Bddgenx
129
129
  return nil
130
130
  end
131
131
  end
132
-
133
- ##
134
- # Detecta o idioma do arquivo de feature pela linha "# language:".
135
- #
136
- # @param caminho_arquivo [String] Caminho do arquivo para detecção do idioma.
137
- # @return [String] Código do idioma detectado (ex: 'pt'), padrão 'pt'.
138
- #
139
- def self.detecta_idioma_arquivo(caminho_arquivo)
140
- return 'pt' unless File.exist?(caminho_arquivo)
141
-
142
- File.foreach(caminho_arquivo) do |linha|
143
- if linha =~ /^#\s*language:\s*(\w{2})/i
144
- return $1.downcase
145
- end
146
- end
147
-
148
- 'pt' # idioma padrão caso não encontre
149
- end
150
132
  end
151
133
  end
152
134
  end
@@ -1,5 +1,5 @@
1
1
  module Bddgenx
2
- class GherkinCleaner
2
+ module Utils
3
3
  # Método principal para limpar o texto Gherkin recebido.
4
4
  # Executa uma sequência de operações para deixar o texto formatado e correto.
5
5
  #
@@ -0,0 +1,45 @@
1
+ module Bddgenx
2
+ module Utils
3
+ # Palavras-chave do Gherkin em Português
4
+ GHERKIN_KEYS_PT = %w[Dado Quando Então E Mas].freeze
5
+
6
+ # Palavras-chave do Gherkin em Inglês
7
+ GHERKIN_KEYS_EN = %w[Given When Then And But].freeze
8
+
9
+ # Mapeamento PT → EN
10
+ GHERKIN_MAP_PT_EN = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
11
+
12
+ # Mapeamento EN → PT
13
+ GHERKIN_MAP_EN_PT = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
14
+
15
+ # Todas as palavras-chave reconhecidas
16
+ ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
17
+
18
+ ##
19
+ # Extrai o idioma do arquivo .txt, a partir da linha "# language:".
20
+ # @param txt_file [String] Caminho do arquivo .txt
21
+ # @return [String] O idioma extraído ou 'pt' como padrão
22
+ def self.obter_idioma_do_arquivo(caminho_arquivo)
23
+ return 'pt' unless File.exist?(caminho_arquivo)
24
+
25
+ File.foreach(caminho_arquivo) do |linha|
26
+ if linha =~ /^#\s*language:\s*(\w{2})/i
27
+ return $1.downcase
28
+ end
29
+ end
30
+
31
+ 'pt' # idioma padrão caso não encontre
32
+ end
33
+
34
+ ##
35
+ # Detecta o idioma a partir de um texto (como conteúdo de arquivo ou string).
36
+ # @param texto [String] O texto onde o idioma será detectado
37
+ # @return [String] O idioma detectado ('pt' por padrão)
38
+ def self.detecta_idioma_de_texto(texto)
39
+ if texto =~ /^#\s*language:\s*(\w{2})/i
40
+ return $1.downcase
41
+ end
42
+ 'pt' # Idioma padrão se o idioma não for detectado
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,79 @@
1
+ module Bddgenx
2
+ module Utils
3
+ # Remove passos duplicados em um texto de cenários BDD,
4
+ # levando em conta o idioma para identificar as keywords (Given, When, Then, And / Dado, Quando, Então, E)
5
+ #
6
+ # Parâmetros:
7
+ # - texto: string contendo o texto do cenário BDD
8
+ # - idioma: 'en' para inglês ou qualquer outro para português
9
+ #
10
+ # Retorna o texto com passos duplicados removidos, preservando a ordem original
11
+ def self.remover_steps_duplicados(texto, idioma)
12
+ # Define as keywords principais para o idioma
13
+ keywords = idioma == 'en' ? %w[Given When Then And] : %w[Dado Quando Então E]
14
+
15
+ # Conjunto para rastrear passos já vistos (versão canônica)
16
+ seen = Set.new
17
+ resultado = []
18
+
19
+ # Percorre linha a linha
20
+ texto.each_line do |linha|
21
+ # Verifica se a linha começa com uma das keywords
22
+ if keywords.any? { |kw| linha.strip.start_with?(kw) }
23
+ # Canonicaliza o passo para comparação sem variações irrelevantes
24
+ canonical = Utils::canonicalize_step(linha, keywords)
25
+
26
+ # Só adiciona se ainda não viu o passo canônico
27
+ unless seen.include?(canonical)
28
+ seen.add(canonical)
29
+ resultado << linha
30
+ end
31
+ else
32
+ # Linhas que não são passos são adicionadas normalmente
33
+ resultado << linha
34
+ end
35
+ end
36
+
37
+ # Retorna o texto reconstruído sem duplicatas
38
+ resultado.join
39
+ end
40
+
41
+ # Gera uma versão canônica (normalizada) do passo para facilitar
42
+ # a identificação de duplicatas mesmo com variações menores de texto.
43
+ #
44
+ # Exemplo: Dado "usuario" fez login e Dado <usuario> fez login
45
+ # gerarão o mesmo canonical para evitar repetição.
46
+ #
47
+ # Passos:
48
+ # - Remove a keyword (Given, When, etc) do começo
49
+ # - Substitui textos entre aspas, placeholders <> e números por <param>
50
+ # - Remove acentuação e pontuação para normalizar
51
+ # - Converte para minúsculas e remove espaços extras
52
+ #
53
+ # Parâmetros:
54
+ # - linha: string com o passo completo
55
+ # - keywords: array com as keywords para remoção
56
+ #
57
+ # Retorna uma string normalizada representando o passo
58
+ def self.canonicalize_step(linha, keywords)
59
+ texto = linha.dup.strip
60
+
61
+ # Remove a keyword do início, se existir
62
+ keywords.each do |kw|
63
+ texto.sub!(/^#{kw}\s+/i, '')
64
+ end
65
+
66
+ # Substitui textos entre aspas, placeholders e números por <param>
67
+ texto.gsub!(/"[^"]*"|<[^>]*>|\b\d+\b/, '<param>')
68
+
69
+ # Remove acentos usando Unicode Normalization Form KD (decompõe caracteres)
70
+ texto = Unicode.normalize_KD(texto).gsub(/\p{Mn}/, '')
71
+
72
+ # Remove pontuação, deixando apenas letras, números, espaços e <>
73
+ texto.gsub!(/[^a-zA-Z0-9\s<>]/, '')
74
+
75
+ # Converte para minúsculas, remove espaços extras e retorna
76
+ texto.downcase.strip.squeeze(" ")
77
+ end
78
+ end
79
+ end
data/lib/env.rb CHANGED
@@ -59,11 +59,13 @@ require 'bundler/setup' if File.exist?(File.expand_path('../../Gemfile', __FILE_
59
59
  # 🧩 Módulos utilitários da gem
60
60
  # --------------------------------------
61
61
 
62
- require_relative 'bddgenx/support/gherkin_cleaner' # Sanitização de Gherkin gerado
63
- require_relative 'bddgenx/support/remover_steps_duplicados' # Remove passos duplicados
64
62
  require_relative 'bddgenx/support/validator' # Valida estrutura de entrada
65
63
  require_relative 'bddgenx/support/font_loader' # Carrega fontes do PDF
66
64
 
65
+ require_relative 'bddgenx/utils/gherkin_cleaner_helper' # Sanitização de Gherkin gerado
66
+ require_relative 'bddgenx/utils/remover_steps_duplicados_helper' # Remove passos duplicados
67
+ require_relative 'bddgenx/utils/language_helper'
68
+
67
69
  # --------------------------------------
68
70
  # 🤖 Clientes de IA (ChatGPT, Gemini)
69
71
  # --------------------------------------
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bddgenx
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.5
4
+ version: 2.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento
@@ -139,10 +139,11 @@ files:
139
139
  - lib/bddgenx/reports/tracer.rb
140
140
  - lib/bddgenx/setup.rb
141
141
  - lib/bddgenx/support/font_loader.rb
142
- - lib/bddgenx/support/gherkin_cleaner.rb
143
142
  - lib/bddgenx/support/loader.rb
144
- - lib/bddgenx/support/remover_steps_duplicados.rb
145
143
  - lib/bddgenx/support/validator.rb
144
+ - lib/bddgenx/utils/gherkin_cleaner_helper.rb
145
+ - lib/bddgenx/utils/language_helper.rb
146
+ - lib/bddgenx/utils/remover_steps_duplicados_helper.rb
146
147
  - lib/bddgenx/version.rb
147
148
  - lib/env.rb
148
149
  - lib/parser.rb
@@ -1,81 +0,0 @@
1
- module Bddgenx
2
- module Utils
3
- class StepCleaner
4
- # Remove passos duplicados em um texto de cenários BDD,
5
- # levando em conta o idioma para identificar as keywords (Given, When, Then, And / Dado, Quando, Então, E)
6
- #
7
- # Parâmetros:
8
- # - texto: string contendo o texto do cenário BDD
9
- # - idioma: 'en' para inglês ou qualquer outro para português
10
- #
11
- # Retorna o texto com passos duplicados removidos, preservando a ordem original
12
- def self.remover_steps_duplicados(texto, idioma)
13
- # Define as keywords principais para o idioma
14
- keywords = idioma == 'en' ? %w[Given When Then And] : %w[Dado Quando Então E]
15
-
16
- # Conjunto para rastrear passos já vistos (versão canônica)
17
- seen = Set.new
18
- resultado = []
19
-
20
- # Percorre linha a linha
21
- texto.each_line do |linha|
22
- # Verifica se a linha começa com uma das keywords
23
- if keywords.any? { |kw| linha.strip.start_with?(kw) }
24
- # Canonicaliza o passo para comparação sem variações irrelevantes
25
- canonical = canonicalize_step(linha, keywords)
26
-
27
- # Só adiciona se ainda não viu o passo canônico
28
- unless seen.include?(canonical)
29
- seen.add(canonical)
30
- resultado << linha
31
- end
32
- else
33
- # Linhas que não são passos são adicionadas normalmente
34
- resultado << linha
35
- end
36
- end
37
-
38
- # Retorna o texto reconstruído sem duplicatas
39
- resultado.join
40
- end
41
-
42
- # Gera uma versão canônica (normalizada) do passo para facilitar
43
- # a identificação de duplicatas mesmo com variações menores de texto.
44
- #
45
- # Exemplo: Dado "usuario" fez login e Dado <usuario> fez login
46
- # gerarão o mesmo canonical para evitar repetição.
47
- #
48
- # Passos:
49
- # - Remove a keyword (Given, When, etc) do começo
50
- # - Substitui textos entre aspas, placeholders <> e números por <param>
51
- # - Remove acentuação e pontuação para normalizar
52
- # - Converte para minúsculas e remove espaços extras
53
- #
54
- # Parâmetros:
55
- # - linha: string com o passo completo
56
- # - keywords: array com as keywords para remoção
57
- #
58
- # Retorna uma string normalizada representando o passo
59
- def self.canonicalize_step(linha, keywords)
60
- texto = linha.dup.strip
61
-
62
- # Remove a keyword do início, se existir
63
- keywords.each do |kw|
64
- texto.sub!(/^#{kw}\s+/i, '')
65
- end
66
-
67
- # Substitui textos entre aspas, placeholders e números por <param>
68
- texto.gsub!(/"[^"]*"|<[^>]*>|\b\d+\b/, '<param>')
69
-
70
- # Remove acentos usando Unicode Normalization Form KD (decompõe caracteres)
71
- texto = Unicode.normalize_KD(texto).gsub(/\p{Mn}/, '')
72
-
73
- # Remove pontuação, deixando apenas letras, números, espaços e <>
74
- texto.gsub!(/[^a-zA-Z0-9\s<>]/, '')
75
-
76
- # Converte para minúsculas, remove espaços extras e retorna
77
- texto.downcase.strip.squeeze(" ")
78
- end
79
- end
80
- end
81
- end