bddgenx 2.4.6 → 2.4.9
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/VERSION +1 -1
- data/lib/bddgenx/configuration.rb +65 -7
- data/lib/bddgenx/generators/generator.rb +18 -47
- data/lib/bddgenx/generators/runner.rb +63 -38
- data/lib/bddgenx/generators/steps_generator.rb +6 -18
- data/lib/bddgenx/ia/chatgtp_cliente.rb +3 -21
- data/lib/bddgenx/ia/gemini_cliente.rb +3 -21
- data/lib/bddgenx/ia/microsoft_copilot_cliente.rb +133 -0
- data/lib/bddgenx/locales/en.yml +1 -0
- data/lib/bddgenx/locales/pt.yml +1 -0
- data/lib/bddgenx/reports/tracer.rb +101 -30
- data/lib/bddgenx/support/properties_loader.rb +108 -0
- data/lib/bddgenx/{support/gherkin_cleaner.rb → utils/gherkin_cleaner_helper.rb} +1 -1
- data/lib/bddgenx/utils/language_helper.rb +45 -0
- data/lib/bddgenx/utils/remover_steps_duplicados_helper.rb +79 -0
- data/lib/env.rb +65 -50
- metadata +7 -5
- data/Rakefile +0 -36
- data/lib/bddgenx/support/remover_steps_duplicados.rb +0 -81
@@ -0,0 +1,133 @@
|
|
1
|
+
module Bddgenx
|
2
|
+
module IA
|
3
|
+
##
|
4
|
+
# Cliente para interação com a API Microsoft Copilot para geração
|
5
|
+
# de conteúdo, aqui usado para criar cenários BDD no formato Gherkin.
|
6
|
+
#
|
7
|
+
class MicrosoftCopilotCliente
|
8
|
+
MICROSOFT_COPILOT_API_URL = ENV['MICROSOFT_COPILOT_API_URL']
|
9
|
+
|
10
|
+
##
|
11
|
+
# Gera cenários BDD baseados em uma história, solicitando à API Microsoft Copilot
|
12
|
+
# o retorno no formato Gherkin com palavras-chave no idioma desejado.
|
13
|
+
#
|
14
|
+
# @param historia [String] Texto base da história para gerar os cenários.
|
15
|
+
# @param idioma [String] Código do idioma, 'pt' por padrão.
|
16
|
+
# @return [String, nil] Cenários no formato Gherkin, ou nil em caso de erro.
|
17
|
+
#
|
18
|
+
def self.gerar_cenarios(historia, idioma = 'pt')
|
19
|
+
api_key = Bddgenx.configuration.microsoft_copilot_api_key # para Copilot
|
20
|
+
|
21
|
+
# Define as palavras-chave para os cenários BDD
|
22
|
+
keywords_pt = {
|
23
|
+
feature: "Funcionalidade",
|
24
|
+
scenario: "Cenário",
|
25
|
+
scenario_outline: "Esquema do Cenário",
|
26
|
+
examples: "Exemplos",
|
27
|
+
given: "Dado",
|
28
|
+
when: "Quando",
|
29
|
+
then: "Então",
|
30
|
+
and: "E"
|
31
|
+
}
|
32
|
+
|
33
|
+
keywords_en = {
|
34
|
+
feature: "Feature",
|
35
|
+
scenario: "Scenario",
|
36
|
+
scenario_outline: "Scenario Outline",
|
37
|
+
examples: "Examples",
|
38
|
+
given: "Given",
|
39
|
+
when: "When",
|
40
|
+
then: "Then",
|
41
|
+
and: "And"
|
42
|
+
}
|
43
|
+
|
44
|
+
# Escolhe o conjunto de palavras-chave conforme o idioma
|
45
|
+
keywords = idioma == 'en' ? keywords_en : keywords_pt
|
46
|
+
|
47
|
+
# Prompt base que instrui a IA a gerar cenários Gherkin no idioma indicado
|
48
|
+
prompt_base = <<~PROMPT
|
49
|
+
Gere cenários BDD no formato Gherkin, utilizando as palavras-chave estruturais no idioma "#{idioma}":
|
50
|
+
Feature: #{keywords[:feature]}
|
51
|
+
Scenario: #{keywords[:scenario]}
|
52
|
+
Scenario Outline: #{keywords[:scenario_outline]}
|
53
|
+
Examples: #{keywords[:examples]}
|
54
|
+
Given: #{keywords[:given]}
|
55
|
+
When: #{keywords[:when]}
|
56
|
+
Then: #{keywords[:then]}
|
57
|
+
And: #{keywords[:and]}
|
58
|
+
|
59
|
+
Instruções:
|
60
|
+
- Todos os textos dos passos devem ser escritos em **português**.
|
61
|
+
- Use as palavras-chave Gherkin no idioma especificado ("#{idioma}").
|
62
|
+
- Gere **vários cenários**, incluindo positivos e negativos.
|
63
|
+
- Use `Scenario Outline` e `Examples` sempre que houver valores variáveis.
|
64
|
+
- Mantenha os parâmetros como `<email>`, `<senha>` e outros entre colchetes angulares, exatamente como aparecem.
|
65
|
+
- Se a história fornecer contexto (ex: `[CONTEXT]` ou "Dado que..."), utilize-o como base para os cenários.
|
66
|
+
- Se não houver contexto explícito, **crie um coerente** baseado na história.
|
67
|
+
- A primeira linha do resultado deve conter obrigatoriamente `# language: #{idioma}`.
|
68
|
+
- Evite passos vagos ou genéricos. Use ações claras e específicas.
|
69
|
+
- Gere apenas o conteúdo da feature, sem explicações adicionais.
|
70
|
+
|
71
|
+
História fornecida:
|
72
|
+
#{historia}
|
73
|
+
PROMPT
|
74
|
+
|
75
|
+
# Verifica se a chave de API foi configurada corretamente
|
76
|
+
unless api_key
|
77
|
+
warn "❌ API Key do Microsoft Copilot não encontrada no .env (MICROSOFT_COPILOT_API_KEY)"
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define o endpoint da API Microsoft Copilot
|
82
|
+
uri = URI("#{MICROSOFT_COPILOT_API_URL}?key=#{api_key}")
|
83
|
+
|
84
|
+
# Estrutura do corpo da requisição para a API Microsoft Copilot
|
85
|
+
request_body = {
|
86
|
+
contents: [
|
87
|
+
{
|
88
|
+
model: "o4-mini",
|
89
|
+
role: "user",
|
90
|
+
parts: [{ text: prompt_base }]
|
91
|
+
}
|
92
|
+
]
|
93
|
+
}
|
94
|
+
|
95
|
+
# Executa requisição POST para a API Microsoft Copilot
|
96
|
+
response = Net::HTTP.post(uri, request_body.to_json, { "Content-Type" => "application/json" })
|
97
|
+
|
98
|
+
# Verifica se a resposta foi bem-sucedida
|
99
|
+
if response.is_a?(Net::HTTPSuccess)
|
100
|
+
json = JSON.parse(response.body)
|
101
|
+
|
102
|
+
unless json["choices"]&.is_a?(Array) && json["choices"].any?
|
103
|
+
warn "❌ Resposta da IA sem candidatos válidos:"
|
104
|
+
warn JSON.pretty_generate(json)
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
# Recupera o conteúdo gerado pela IA
|
109
|
+
texto_ia = json["choices"].first.dig("message", "content")
|
110
|
+
|
111
|
+
if texto_ia
|
112
|
+
# Limpeza e sanitização do texto para manter padrão Gherkin
|
113
|
+
texto_limpo = Utils.limpar(texto_ia)
|
114
|
+
Utils.remover_steps_duplicados(texto_ia, idioma)
|
115
|
+
|
116
|
+
# Ajuste da diretiva de idioma na saída gerada
|
117
|
+
texto_limpo.sub!(/^# language: .*/, "# language: #{idioma}")
|
118
|
+
texto_limpo.prepend("# language: #{idioma}\n") unless texto_limpo.start_with?("# language:")
|
119
|
+
|
120
|
+
return texto_limpo
|
121
|
+
else
|
122
|
+
warn I18n.t('errors.ia_no_content')
|
123
|
+
warn JSON.pretty_generate(json)
|
124
|
+
return nil
|
125
|
+
end
|
126
|
+
else
|
127
|
+
warn I18n.t('errors.microsoft_copilot_error', code: response.code, body: response.body)
|
128
|
+
return nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/bddgenx/locales/en.yml
CHANGED
@@ -28,6 +28,7 @@ en:
|
|
28
28
|
ia_no_content: "❌ No content returned from AI"
|
29
29
|
gemini_error: "❌ Error calling Gemini: %{code} - %{body}"
|
30
30
|
chatgpt_key_missing: "❌ ChatGPT API key not found in .env (OPENAI_API_KEY)"
|
31
|
+
microsoft_copilot_error: "Erro"
|
31
32
|
openai_quota: "❌ OpenAI API quota exceeded."
|
32
33
|
openai_check_usage: "🔗 Check your usage: https://platform.openai.com/account/usage"
|
33
34
|
feature_not_found: "⚠️ Feature not found: %{feature}"
|
data/lib/bddgenx/locales/pt.yml
CHANGED
@@ -29,6 +29,7 @@ pt:
|
|
29
29
|
gemini_error: "❌ Erro ao chamar Gemini: %{code} - %{body}"
|
30
30
|
chatgpt_key_missing: "❌ API Key do ChatGPT não encontrada no .env (OPENAI_API_KEY)"
|
31
31
|
openai_quota: "❌ Limite de uso da API OpenAI excedido."
|
32
|
+
microsoft_copilot_error: "Erro"
|
32
33
|
openai_check_usage: "🔗 Verifique sua conta: https://platform.openai.com/account/usage"
|
33
34
|
feature_not_found: "⚠️ Feature não encontrada: %{feature}"
|
34
35
|
pdf_generation_failed: "❌ Erro ao gerar PDF de %{file}: %{error}"
|
@@ -1,70 +1,141 @@
|
|
1
|
-
# lib/bddgenx/tracer.rb
|
2
1
|
# encoding: utf-8
|
3
2
|
#
|
4
|
-
# Este arquivo define a classe Tracer
|
5
|
-
#
|
6
|
-
#
|
3
|
+
# Este arquivo define a classe `Tracer`, responsável por gerar arquivos de rastreabilidade
|
4
|
+
# (CSV) a partir das features geradas automaticamente pela gem BDDGenX.
|
5
|
+
#
|
6
|
+
# Para cada feature processada, o `Tracer` extrai os cenários da própria feature `.feature`
|
7
|
+
# e associa cada passo definido na história original com o bloco Gherkin correspondente.
|
8
|
+
# O objetivo é fornecer visibilidade e rastreabilidade completa entre requisitos e testes.
|
9
|
+
|
10
|
+
require 'csv'
|
11
|
+
require 'fileutils'
|
12
|
+
|
7
13
|
module Bddgenx
|
8
|
-
# Classe
|
14
|
+
# Classe responsável por rastrear os artefatos gerados pela gem
|
15
|
+
# e exportá-los em arquivos CSV, um por funcionalidade.
|
16
|
+
#
|
17
|
+
# Para cada grupo de passos (do `.txt`), associa os dados com o
|
18
|
+
# cenário equivalente gerado no arquivo `.feature`.
|
9
19
|
class Tracer
|
10
|
-
|
11
|
-
#
|
20
|
+
##
|
21
|
+
# Adiciona entradas de rastreabilidade a um CSV baseado na feature gerada.
|
22
|
+
#
|
23
|
+
# - Cada funcionalidade recebe um arquivo CSV próprio, salvo em:
|
24
|
+
# `reports/output/funcionalidade_<nome>.csv`
|
25
|
+
#
|
26
|
+
# - A coluna "BDD" contém o cenário completo extraído diretamente do `.feature`,
|
27
|
+
# preservando a sintaxe original do Gherkin (cenário, steps, tags).
|
12
28
|
#
|
13
29
|
# @param historia [Hash]
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
30
|
+
# Hash representando a história extraída do `.txt`, contendo:
|
31
|
+
# - :quero → nome da funcionalidade
|
32
|
+
# - :grupos → lista de blocos com :tipo, :tag e :passos
|
33
|
+
#
|
34
|
+
# @param feature_path [String]
|
35
|
+
# Caminho do arquivo `.feature` já gerado no sistema
|
36
|
+
#
|
18
37
|
# @return [void]
|
19
|
-
def self.adicionar_entrada(historia,
|
20
|
-
# Garante existência do diretório de saída
|
38
|
+
def self.adicionar_entrada(historia, feature_path)
|
21
39
|
FileUtils.mkdir_p('reports/output')
|
22
|
-
arquivo_csv = 'reports/output/rastreabilidade.csv'
|
23
40
|
|
24
|
-
|
25
|
-
|
41
|
+
nome_funcionalidade = historia[:quero].gsub(/^Quero\s*/, '').strip
|
42
|
+
nome_funcionalidade_sanitizado = nome_funcionalidade.downcase.gsub(/[^a-z0-9]+/, '_')
|
43
|
+
arquivo_csv = "reports/output/funcionalidade_#{nome_funcionalidade_sanitizado}.csv"
|
26
44
|
|
45
|
+
cabecalho = ['Funcionalidade', 'Tipo', 'Tag', 'Cenário', 'Passo', 'Origem', 'BDD']
|
27
46
|
linhas = []
|
28
47
|
|
29
|
-
#
|
48
|
+
# Leitura real da feature gerada
|
49
|
+
blocos_gherkin = extrair_cenarios_gherkin(feature_path)
|
50
|
+
|
30
51
|
historia[:grupos].each_with_index do |grupo, idx|
|
31
52
|
tipo = grupo[:tipo]
|
32
|
-
tag = grupo[:tag]
|
53
|
+
tag = grupo[:tag] || '-'
|
33
54
|
passos = grupo[:passos] || []
|
34
|
-
|
35
|
-
nome_funcionalidade = historia[:quero].gsub(/^Quero\s*/, '').strip
|
36
55
|
nome_cenario = "Cenário #{idx + 1}"
|
37
56
|
|
57
|
+
# Bloco Gherkin real do cenário gerado
|
58
|
+
gherkin_bloco = blocos_gherkin[idx] || ''
|
59
|
+
|
38
60
|
passos.each do |passo|
|
39
61
|
linhas << [
|
40
62
|
nome_funcionalidade,
|
41
63
|
tipo,
|
42
|
-
tag
|
64
|
+
tag,
|
43
65
|
nome_cenario,
|
44
66
|
passo,
|
45
|
-
File.basename(
|
67
|
+
File.basename(feature_path),
|
68
|
+
gherkin_bloco
|
46
69
|
]
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
50
|
-
# Escreve ou anexa as linhas geradas ao CSV
|
51
73
|
escrever_csv(arquivo_csv, cabecalho, linhas)
|
52
74
|
end
|
53
75
|
|
54
|
-
|
76
|
+
##
|
77
|
+
# Escreve ou anexa dados em um arquivo CSV.
|
78
|
+
# - Cria o cabeçalho caso seja a primeira escrita.
|
79
|
+
# - Evita duplicações com base na combinação "Passo + Origem".
|
80
|
+
#
|
81
|
+
# @param caminho [String] Caminho completo do arquivo CSV a ser salvo
|
82
|
+
# @param cabecalho [Array<String>] Títulos das colunas do CSV
|
83
|
+
# @param novas_linhas [Array<Array>] Linhas de conteúdo a serem gravadas
|
55
84
|
#
|
56
|
-
# @param caminho [String] Caminho completo para o arquivo CSV de rastreabilidade
|
57
|
-
# @param cabecalho [Array<String>] Array de títulos das colunas a serem escritos
|
58
|
-
# @param linhas [Array<Array<String>>] Dados a serem gravados no CSV (cada sub-array é uma linha)
|
59
85
|
# @return [void]
|
60
|
-
def self.escrever_csv(caminho, cabecalho,
|
61
|
-
# Verifica se é um novo arquivo para incluir o cabeçalho
|
86
|
+
def self.escrever_csv(caminho, cabecalho, novas_linhas)
|
62
87
|
novo_arquivo = !File.exist?(caminho)
|
63
88
|
|
89
|
+
existentes = []
|
90
|
+
if File.exist?(caminho)
|
91
|
+
existentes = CSV.read(caminho, col_sep: ';', headers: true).map do |row|
|
92
|
+
[row['Passo'], row['Origem']]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
64
96
|
CSV.open(caminho, 'a+', col_sep: ';', force_quotes: true) do |csv|
|
65
97
|
csv << cabecalho if novo_arquivo
|
66
|
-
|
98
|
+
|
99
|
+
novas_linhas.each do |linha|
|
100
|
+
passo, origem = linha[4], linha[5]
|
101
|
+
next if existentes.include?([passo, origem])
|
102
|
+
csv << linha
|
103
|
+
end
|
67
104
|
end
|
68
105
|
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Extrai todos os cenários completos do arquivo `.feature` gerado,
|
109
|
+
# preservando a estrutura Gherkin original (cenários, tags, steps).
|
110
|
+
#
|
111
|
+
# Um novo bloco é iniciado quando uma das palavras-chave de título
|
112
|
+
# de cenário é encontrada.
|
113
|
+
#
|
114
|
+
# @param feature_path [String] Caminho completo do arquivo `.feature`
|
115
|
+
# @return [Array<String>] Lista de blocos Gherkin, um por cenário
|
116
|
+
def self.extrair_cenarios_gherkin(feature_path)
|
117
|
+
return [] unless File.exist?(feature_path)
|
118
|
+
|
119
|
+
content = File.read(feature_path)
|
120
|
+
linhas = content.lines
|
121
|
+
|
122
|
+
blocos = []
|
123
|
+
bloco_atual = []
|
124
|
+
capturando = false
|
125
|
+
|
126
|
+
linhas.each_with_index do |linha, i|
|
127
|
+
if linha.strip =~ /^(Scenario|Scenario Outline|Cenário|Esquema do Cenário):/i
|
128
|
+
# Novo cenário → salva anterior
|
129
|
+
blocos << bloco_atual.join if bloco_atual.any?
|
130
|
+
bloco_atual = [linha]
|
131
|
+
capturando = true
|
132
|
+
elsif capturando
|
133
|
+
bloco_atual << linha
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
blocos << bloco_atual.join if bloco_atual.any?
|
138
|
+
blocos
|
139
|
+
end
|
69
140
|
end
|
70
141
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# lib/bddgenx/properties_loader.rb
|
2
|
+
#
|
3
|
+
# Módulo `Bddgenx::PropertiesLoader` é responsável por carregar e processar os arquivos de configuração
|
4
|
+
# `.properties` que contêm variáveis de ambiente, além de também carregar as variáveis do arquivo `.env`.
|
5
|
+
# Este módulo lida com a substituição de placeholders nas propriedades, garantindo que as variáveis de ambiente
|
6
|
+
# sejam corretamente carregadas e definidas para uso no sistema.
|
7
|
+
#
|
8
|
+
# O fluxo de trabalho é o seguinte:
|
9
|
+
# 1. Carregar variáveis de ambiente a partir do arquivo `.env`.
|
10
|
+
# 2. Localizar e ler arquivos `.properties` presentes no diretório raiz do projeto.
|
11
|
+
# 3. Substituir placeholders no conteúdo dos arquivos `.properties` com variáveis de ambiente.
|
12
|
+
# 4. Mesclar as propriedades carregadas e definir as variáveis de ambiente no Ruby.
|
13
|
+
#
|
14
|
+
# Este módulo permite a configuração flexível de variáveis de ambiente, com suporte tanto para arquivos `.env`
|
15
|
+
# quanto para arquivos `.properties`.
|
16
|
+
|
17
|
+
module Bddgenx
|
18
|
+
class PropertiesLoader
|
19
|
+
# Carregar as variáveis do arquivo .env
|
20
|
+
#
|
21
|
+
# Este método utiliza a gem `dotenv` para carregar variáveis de ambiente a partir de um arquivo `.env`.
|
22
|
+
# Ele carrega as variáveis do arquivo `.env` para o ambiente de execução, onde elas ficam disponíveis via
|
23
|
+
# `ENV['VAR_NAME']` em qualquer parte do código.
|
24
|
+
def self.load_env_variables
|
25
|
+
Dotenv.load # Carrega as variáveis do .env automaticamente
|
26
|
+
end
|
27
|
+
|
28
|
+
# Função para substituir variáveis no conteúdo do arquivo .properties
|
29
|
+
#
|
30
|
+
# Este método recebe o conteúdo de um arquivo `.properties` e substitui os placeholders no formato `{{VAR_NAME}}`
|
31
|
+
# pelas variáveis de ambiente correspondentes, se definidas. Caso a variável de ambiente não esteja definida,
|
32
|
+
# o placeholder original é mantido no conteúdo.
|
33
|
+
#
|
34
|
+
# @param content [String] O conteúdo do arquivo `.properties` a ser processado.
|
35
|
+
# @return [String] O conteúdo com os placeholders substituídos pelas variáveis de ambiente.
|
36
|
+
def self.replace_placeholders(content)
|
37
|
+
content.gsub!(/\{\{(\w+)\}\}/) do |match|
|
38
|
+
ENV[$1] || match # Substitui pela variável de ambiente ou mantém o placeholder
|
39
|
+
end
|
40
|
+
content
|
41
|
+
end
|
42
|
+
|
43
|
+
# Função para garantir que o arquivo seja lido com a codificação correta
|
44
|
+
#
|
45
|
+
# Este método lê um arquivo especificado com codificação UTF-8. Caso o arquivo contenha caracteres inválidos,
|
46
|
+
# eles são substituídos por um caractere de substituição, garantindo que o conteúdo seja lido corretamente.
|
47
|
+
#
|
48
|
+
# @param file [String] O caminho do arquivo a ser lido.
|
49
|
+
# @return [String] O conteúdo do arquivo lido, com caracteres inválidos substituídos, se necessário.
|
50
|
+
def self.read_file_with_correct_encoding(file)
|
51
|
+
# Lê o arquivo com codificação UTF-8 e ignora caracteres inválidos
|
52
|
+
content = File.read(file, encoding: 'UTF-8')
|
53
|
+
content.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
54
|
+
end
|
55
|
+
|
56
|
+
# Carregar e substituir propriedades de arquivos .properties
|
57
|
+
#
|
58
|
+
# Este método localiza todos os arquivos `.properties` no diretório raiz do projeto,
|
59
|
+
# lê seu conteúdo, substitui os placeholders pelas variáveis de ambiente, carrega as propriedades
|
60
|
+
# e mescla essas propriedades em um único hash.
|
61
|
+
#
|
62
|
+
# Após carregar as propriedades, ele também define as variáveis de ambiente no Ruby (via `ENV`)
|
63
|
+
# usando as propriedades carregadas, mas não sobrescreve as variáveis de ambiente já definidas.
|
64
|
+
#
|
65
|
+
# @return [Hash] O hash contendo as propriedades carregadas e mescladas dos arquivos `.properties`.
|
66
|
+
def self.load_properties
|
67
|
+
# Carregar variáveis do .env primeiro
|
68
|
+
load_env_variables
|
69
|
+
|
70
|
+
# Localizar arquivos .properties na raiz do projeto
|
71
|
+
properties_files = Dir.glob(File.expand_path('../*.properties', __dir__))
|
72
|
+
|
73
|
+
properties = {}
|
74
|
+
|
75
|
+
properties_files.each do |file|
|
76
|
+
# Forçar a leitura do arquivo com codificação UTF-8 e lidar com caracteres inválidos
|
77
|
+
content = read_file_with_correct_encoding(file)
|
78
|
+
|
79
|
+
# Substituir os placeholders antes de carregar as propriedades
|
80
|
+
content = replace_placeholders(content)
|
81
|
+
|
82
|
+
# Carregar as propriedades do arquivo
|
83
|
+
file_properties = JavaProperties::Properties.load(StringIO.new(content))
|
84
|
+
|
85
|
+
# Mesclar as propriedades carregadas no hash
|
86
|
+
properties.merge!(file_properties.to_h)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Agora, define as variáveis de ambiente a partir das propriedades carregadas
|
90
|
+
set_environment_variables(properties)
|
91
|
+
|
92
|
+
properties
|
93
|
+
end
|
94
|
+
|
95
|
+
# Função para definir variáveis de ambiente a partir das propriedades carregadas
|
96
|
+
#
|
97
|
+
# Este método percorre as propriedades carregadas e as define como variáveis de ambiente (`ENV`) no Ruby.
|
98
|
+
# Se a variável de ambiente já estiver definida (por exemplo, pelo `.env`), ela não será sobrescrita.
|
99
|
+
#
|
100
|
+
# @param properties [Hash] O hash contendo as propriedades carregadas dos arquivos `.properties`.
|
101
|
+
def self.set_environment_variables(properties)
|
102
|
+
properties.each do |key, value|
|
103
|
+
# Se a variável de ambiente já estiver definida, não sobrescreve
|
104
|
+
ENV[key.upcase] ||= value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -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
|