bddgenx 2.4.7 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94b275818990d76f36659c7be2a611232c1dfa90d812ac0e70dbfa6ae9c6e3e9
4
- data.tar.gz: 5219e00c5048c07aee0ad93617a8a140e5725610ea532fe06fea798c097fe5b7
3
+ metadata.gz: adf58e7c99b89027c56542f9b2dbff2cc71c336ca164eb6a2bc65308546beef3
4
+ data.tar.gz: 2093d4ac9a62eea83e87eb7faff89a43196f0a4585f885e515c967e52e0688b8
5
5
  SHA512:
6
- metadata.gz: 59b7b3e17525393b9cf63e6ef4b9a71ad3e2a49c69c35967180883d920ab0160400ba3ce42fe8dd5a1571c9246264dccc388faa5bcbae7808c6385dc79133ad5
7
- data.tar.gz: 8dc211ad3e323a958fdb2e0b273fb5054d5f432d7be98677fbe1bf41eea2923284d763e0fe73181cc1740dfe2c5c9cfc88fa76d4fe299569f5af9e4decf0d5cc
6
+ metadata.gz: 10e4ffafcb2230a6a88cd3556ab995dfe936c36925c86027fee92d0e8f74f7e7b75c4bf6ea0415ec5a083dd5c63f9043750b350dcfa04176b3740c1123e67fa8
7
+ data.tar.gz: 3791efeb6c7a684e666bd73cfa03d21d2f8cc0825e6791655aa639f51901395c0595170661ff06b331e801ee2a158ec5193182ce4e6030712133c0c10c0b1176
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.4.7
1
+ 2.4.9
@@ -1,34 +1,92 @@
1
- # lib/bddgenx/configuration.rb
1
+ # encoding: utf-8
2
+ #
3
+ # Este arquivo define a classe de configuração global da gem BDDGenX.
4
+ #
5
+ # A classe `Bddgenx::Configuration` permite configurar o modo de operação da ferramenta
6
+ # (ex: estático ou com IA) e define as variáveis de ambiente que armazenam as chaves de API
7
+ # para serviços externos como OpenAI (ChatGPT), Google Gemini e Microsoft Copilot.
8
+
2
9
  module Bddgenx
10
+ # Classe de configuração principal da gem BDDGenX.
11
+ # Permite definir o modo de geração BDD e os nomes das variáveis de ambiente
12
+ # que armazenam as chaves de API para integração com serviços de IA.
3
13
  class Configuration
4
- # :static, :chatgpt ou :gemini
14
+ # Modo de execução da gem.
15
+ # Pode ser:
16
+ # - :static → geração local
17
+ # - :chatgpt → uso da IA do ChatGPT (OpenAI)
18
+ # - :gemini → uso da IA Gemini (Google)
19
+ # - :copilot → uso do Microsoft Copilot
20
+ #
21
+ # @return [Symbol]
5
22
  attr_accessor :mode
6
23
 
7
- # Nomes das ENV vars para as chaves
8
- attr_accessor :openai_api_key_env, :gemini_api_key_env
24
+ # Nome da variável de ambiente que contém a chave da API da OpenAI
25
+ # @return [String]
26
+ attr_accessor :openai_api_key_env
27
+
28
+ # Nome da variável de ambiente que contém a chave da API do Google Gemini
29
+ # @return [String]
30
+ attr_accessor :gemini_api_key_env
31
+
32
+ # Nome da variável de ambiente que contém a chave da API do Microsoft Copilot
33
+ # @return [String]
34
+ attr_accessor :microsoft_copilot_api_env
9
35
 
36
+ ##
37
+ # Inicializa a configuração com valores padrão:
38
+ # - modo: :static
39
+ # - ENV keys: 'OPENAI_API_KEY', 'GEMINI_API_KEY', 'MICROSOFT_COPILOT_API_KEY'
10
40
  def initialize
11
41
  @mode = :static
12
42
  @openai_api_key_env = 'OPENAI_API_KEY'
13
43
  @gemini_api_key_env = 'GEMINI_API_KEY'
44
+ @microsoft_copilot_api_env = 'MICROSOFT_COPILOT_API_KEY'
14
45
  end
15
46
 
16
- # Retorna a chave real, carregada do ENV
47
+ ##
48
+ # Retorna a chave da API do OpenAI, lida diretamente da ENV.
49
+ #
50
+ # @return [String, nil] Chave de API ou nil se não definida
17
51
  def openai_api_key
18
52
  ENV[@openai_api_key_env]
19
53
  end
20
54
 
55
+ ##
56
+ # Retorna a chave da API do Gemini, lida diretamente da ENV.
57
+ #
58
+ # @return [String, nil] Chave de API ou nil se não definida
21
59
  def gemini_api_key
22
60
  ENV[@gemini_api_key_env]
23
61
  end
62
+
63
+ ##
64
+ # Retorna a chave da API do Microsoft Copilot, lida diretamente da ENV.
65
+ #
66
+ # @return [String, nil] Chave de API ou nil se não definida
67
+ def microsoft_copilot_api_key
68
+ ENV[@microsoft_copilot_api_env]
69
+ end
24
70
  end
25
71
 
26
- # Singleton de configuração
72
+ ##
73
+ # Retorna uma instância singleton da configuração da gem.
74
+ #
75
+ # @return [Configuration]
27
76
  def self.configuration
28
77
  @configuration ||= Configuration.new
29
78
  end
30
79
 
31
- # DSL para configurar
80
+ ##
81
+ # Permite configurar a gem usando um bloco DSL.
82
+ #
83
+ # Exemplo:
84
+ # Bddgenx.configure do |config|
85
+ # config.mode = :gemini
86
+ # config.gemini_api_key_env = 'MY_GEMINI_KEY'
87
+ # end
88
+ #
89
+ # @yieldparam config [Configuration] instância de configuração atual
32
90
  def self.configure
33
91
  yield(configuration)
34
92
  end
@@ -1,36 +1,49 @@
1
1
  # lib/bddgenx/cli.rb
2
2
  # encoding: utf-8
3
3
  #
4
- # Este arquivo define a classe Runner (CLI) da gem bddgenx,
5
- # responsável por orquestrar todo o fluxo de geração BDD:
6
- # leitura e validação de histórias, geração de features, steps,
7
- # exportação de PDFs e controle de modo (static / IA).
4
+ # Este arquivo define a classe Runner (CLI) da gem BDDGenX.
5
+ #
6
+ # A Runner é responsável por orquestrar todo o fluxo de geração BDD:
7
+ # - Leitura e validação de histórias (arquivos `.txt`)
8
+ # - Geração de arquivos `.feature` e steps
9
+ # - Integração com IA (ChatGPT, Gemini, Copilot)
10
+ # - Exportação para PDF
11
+ # - Rastreabilidade via CSV
12
+ #
13
+ # Esta classe é o ponto de entrada da gem em execução via terminal (CLI).
14
+ # O comportamento é configurado com variáveis de ambiente como:
15
+ # - BDDGENX_MODE (static, chatgpt, gemini, copilot)
16
+ # - BDDGENX_LANG (pt, en)
8
17
 
9
18
  require_relative '../../bddgenx'
10
19
 
11
20
  module Bddgenx
12
- # Classe principal de execução da gem.
13
- # Atua como ponto de entrada (CLI) para processar arquivos de entrada
14
- # e gerar todos os artefatos BDD relacionados.
21
+ # Classe principal de execução da gem BDDGenX.
22
+ # Controla o fluxo de leitura de histórias, geração de artefatos BDD
23
+ # e exportação de relatórios. Suporta execução via terminal.
15
24
  class Runner
16
25
 
17
26
  ##
18
- # Retorna a lista de arquivos de entrada.
19
- # Se houver argumentos em ARGV, utiliza-os como nomes de arquivos `.txt`.
20
- # Caso contrário, chama prompt interativo.
27
+ # Retorna a lista de arquivos `.txt` a processar.
28
+ #
29
+ # - Se ARGV contiver argumentos, usa esses nomes como arquivos `.txt`
30
+ # - Caso contrário, entra em modo interativo para seleção manual
21
31
  #
22
- # @param input_dir [String] Caminho do diretório de entrada
23
- # @return [Array<String>] Lista de arquivos `.txt` a processar
32
+ # @param input_dir [String] Caminho do diretório com arquivos `.txt`
33
+ # @return [Array<String>] Lista de caminhos de arquivos `.txt`
24
34
  def self.choose_files(input_dir)
25
35
  ARGV.any? ? selecionar_arquivos_txt(input_dir) : choose_input(input_dir)
26
36
  end
27
37
 
28
38
  ##
29
- # Processa argumentos ARGV e converte em caminhos válidos de arquivos `.txt`.
30
- # Adiciona extensão `.txt` se ausente e remove arquivos inexistentes.
39
+ # Processa os argumentos da linha de comando (ARGV) e gera caminhos
40
+ # completos para arquivos `.txt` no diretório informado.
31
41
  #
32
- # @param input_dir [String] Diretório onde estão os arquivos
33
- # @return [Array<String>] Caminhos válidos para arquivos de entrada
42
+ # - Se a extensão `.txt` estiver ausente, ela é adicionada.
43
+ # - Arquivos inexistentes são ignorados com aviso.
44
+ #
45
+ # @param input_dir [String] Diretório base onde estão os arquivos `.txt`
46
+ # @return [Array<String>] Lista de arquivos válidos encontrados
34
47
  def self.selecionar_arquivos_txt(input_dir)
35
48
  ARGV.map do |arg|
36
49
  nome = arg.end_with?('.txt') ? arg : "#{arg}.txt"
@@ -44,15 +57,18 @@ module Bddgenx
44
57
  end
45
58
 
46
59
  ##
47
- # Interface interativa para o usuário selecionar arquivos `.txt` a processar.
48
- # Exibe uma lista dos arquivos disponíveis e solicita um número ao usuário.
60
+ # Modo interativo para o usuário escolher o arquivo `.txt` a ser processado.
61
+ #
62
+ # Exibe uma lista numerada com os arquivos disponíveis no diretório.
63
+ # O usuário pode selecionar um específico ou pressionar ENTER para todos.
49
64
  #
50
- # @param input_dir [String] Diretório de entrada
51
- # @return [Array<String>] Lista com o arquivo escolhido ou todos
65
+ # @param input_dir [String] Caminho do diretório com arquivos `.txt`
66
+ # @return [Array<String>] Arquivo selecionado ou todos disponíveis
52
67
  def self.choose_input(input_dir)
53
68
  files = Dir.glob(File.join(input_dir, '*.txt'))
54
69
  if files.empty?
55
- warn "❌ Não há arquivos .txt no diretório #{input_dir}"; exit 1
70
+ warn "❌ Não há arquivos .txt no diretório #{input_dir}"
71
+ exit 1
56
72
  end
57
73
 
58
74
  puts "Selecione o arquivo de história para processar:"
@@ -63,20 +79,24 @@ module Bddgenx
63
79
  return files if choice.empty?
64
80
  idx = choice.to_i - 1
65
81
  unless idx.between?(0, files.size - 1)
66
- warn "❌ Escolha inválida."; exit 1
82
+ warn "❌ Escolha inválida."
83
+ exit 1
67
84
  end
68
85
  [files[idx]]
69
86
  end
70
87
 
71
88
  ##
72
- # Executa o fluxo completo de geração BDD:
73
- # - Define o modo (static / IA)
74
- # - Coleta arquivos de entrada
75
- # - Valida as histórias
76
- # - Gera arquivos `.feature` e `steps`
77
- # - Exporta PDFs e faz backup de versões antigas
89
+ # Executa o fluxo principal de geração dos artefatos BDD.
90
+ #
91
+ # Etapas executadas:
92
+ # - Detecta o modo de execução via ENV['BDDGENX_MODE'] (static/chatgpt/gemini/copilot)
93
+ # - Carrega e valida histórias de usuário
94
+ # - Gera arquivos `.feature` e seus respectivos steps
95
+ # - Executa geração via IA (quando aplicável)
96
+ # - Exporta arquivos em PDF
97
+ # - Gera rastreabilidade via CSV
78
98
  #
79
- # O modo de execução é lido da variável de ambiente `BDDGENX_MODE`.
99
+ # Exibe no final um resumo das operações executadas.
80
100
  #
81
101
  # @return [void]
82
102
  def self.execute
@@ -90,7 +110,7 @@ module Bddgenx
90
110
  exit 1
91
111
  end
92
112
 
93
- # Contadores de geração
113
+ # Contadores
94
114
  total = features = steps = ignored = 0
95
115
  skipped_steps = []
96
116
  generated_pdfs = []
@@ -103,23 +123,22 @@ module Bddgenx
103
123
  historia = Parser.ler_historia(arquivo)
104
124
  idioma = Utils.obter_idioma_do_arquivo(arquivo) || historia[:idioma]
105
125
  historia[:idioma] = idioma
126
+
106
127
  unless Validator.validar(historia)
107
128
  ignored += 1
108
129
  puts "❌ #{I18n.t('messages.invalid_story')}: #{arquivo}"
109
130
  next
110
131
  end
111
132
 
112
- # Geração via IA (ChatGPT, Gemini)
113
- if %w[gemini chatgpt].include?(modo)
133
+ # IA: geração de cenários com Gemini, ChatGPT ou Copilot
134
+ if %w[gemini chatgpt copilot].include?(modo)
114
135
  puts I18n.t('messages.start_ia', modo: modo.capitalize)
115
- idioma = Utils.obter_idioma_do_arquivo(arquivo) || historia[:idioma]
116
136
 
117
137
  feature_text = Support::Loader.run(I18n.t('messages.ia_waiting'), :default) do
118
138
  case modo
119
- when 'gemini'
120
- IA::GeminiCliente.gerar_cenarios(historia, idioma)
121
- when 'chatgpt'
122
- IA::ChatGptCliente.gerar_cenarios(historia, idioma)
139
+ when 'gemini' then IA::GeminiCliente.gerar_cenarios(historia, idioma)
140
+ when 'chatgpt' then IA::ChatGptCliente.gerar_cenarios(historia, idioma)
141
+ when 'copilot' then IA::MicrosoftCopilotCliente.gerar_cenarios(historia, idioma)
123
142
  end
124
143
  end
125
144
 
@@ -139,7 +158,9 @@ module Bddgenx
139
158
  end
140
159
  end
141
160
 
161
+ # Salva versão antiga se existir
142
162
  Backup.salvar_versao_antiga(feature_path)
163
+
143
164
  features += 1 if Generator.salvar_feature(feature_path, feature_content)
144
165
 
145
166
  if StepsGenerator.gerar_passos(feature_path)
@@ -150,6 +171,8 @@ module Bddgenx
150
171
 
151
172
  FileUtils.mkdir_p('reports')
152
173
  result = PDFExporter.exportar_todos(only_new: true)
174
+ Tracer.adicionar_entrada(historia, feature_path)
175
+
153
176
  generated_pdfs.concat(result[:generated])
154
177
  skipped_pdfs.concat(result[:skipped])
155
178
  end
@@ -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
@@ -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}"
@@ -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, 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.
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 para adicionar registros de rastreabilidade a um relatório CSV.
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
- # Adiciona entradas de rastreabilidade para cada passo de cada grupo
11
- # da história em um arquivo CSV localizado em 'reports/output/rastreabilidade.csv'.
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
- # Objeto de história contendo :quero (título da funcionalidade) e :grupos,
15
- # onde cada grupo possui :tipo, :tag, e :passos (Array<String>)
16
- # @param nome_arquivo_feature [String]
17
- # Nome do arquivo .feature de onde os passos foram gerados
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, nome_arquivo_feature)
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
- # Cabeçalho padrão do CSV: identifica colunas
25
- cabecalho = ['Funcionalidade', 'Tipo', 'Tag', 'Cenário', 'Passo', 'Origem']
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
- # Itera sobre grupos de passos para compor linhas de rastreabilidade
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(nome_arquivo_feature)
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
- # Escreve ou anexa registros em um arquivo CSV, criando cabeçalho se necessário.
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, linhas)
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
- linhas.each { |linha| csv << linha }
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
data/lib/env.rb CHANGED
@@ -1,10 +1,14 @@
1
- # lib/bddgenx/env.rb
2
1
  # encoding: utf-8
3
2
  #
4
- # Responsável por carregar todas as dependências da gem bddgenx.
5
- # Inclui bibliotecas padrão, gems externas e arquivos internos
6
- # essenciais para o funcionamento da geração BDD, com suporte a I18n,
7
- # IA (ChatGPT, Gemini), geração de PDF, validações e estrutura de projeto.
3
+ # Este arquivo é responsável por carregar todas as dependências da gem `bddgenx`.
4
+ #
5
+ # Ele inclui:
6
+ # - Gems padrão do Ruby (ex: JSON, net/http, fileutils)
7
+ # - Gems externas (ex: Prawn, Faraday, Dotenv)
8
+ # - Módulos internos do projeto `bddgenx`
9
+ #
10
+ # Também define o idioma ativo da gem (via I18n), configura variáveis de ambiente
11
+ # e carrega clientes de IA, geradores, validadores, exportadores e estruturas de projeto.
8
12
 
9
13
  # --------------------------------------
10
14
  # 📦 Gems padrão da linguagem Ruby
@@ -14,98 +18,107 @@ require 'json' # Manipulação de dados JSON
14
18
  require 'net/http' # Requisições HTTP nativas
15
19
  require 'uri' # Manipulação de URLs
16
20
  require 'fileutils' # Operações com arquivos e diretórios
17
- require 'open3' # Execução de comandos externos com captura de saída
18
- require 'bigdecimal' # Cálculos matemáticos de alta precisão
19
- require 'i18n' # Internacionalização (traduções dinâmicas)
21
+ require 'open3' # Execução de comandos externos
22
+ require 'bigdecimal' # Cálculos com alta precisão
23
+ require 'i18n' # Internacionalização
24
+ require 'csv' # Manipulação de arquivos CSV
25
+ require 'yard' # Documentação automática
20
26
 
21
27
  # --------------------------------------
22
28
  # 📚 Gems externas
23
29
  # --------------------------------------
24
30
 
25
- require 'prawn' # Geração de documentos PDF
26
- require 'prawn/table' # Suporte a tabelas no Prawn
31
+ require 'prawn' # Geração de PDFs
32
+ require 'prawn/table' # Tabelas em PDF
27
33
  require 'prawn-svg' # Suporte a SVG no PDF
28
- require 'faraday' # Cliente HTTP para integração com APIs (ex: Gemini)
29
- require 'dotenv' # Carrega variáveis de ambiente do arquivo `.env`
30
- require 'unicode' # Manipulação e normalização de caracteres Unicode
34
+ require 'faraday' # Cliente HTTP
35
+ require 'dotenv' # Carrega variáveis de .env
36
+ require 'unicode' # Manipulação de caracteres unicode
37
+ require 'java_properties'# Leitura de arquivos .properties
38
+ require 'stringio' # IO virtual em memória
39
+ require 'tempfile' # Arquivos temporários
31
40
 
32
41
  # --------------------------------------
33
42
  # 🌍 Configuração de idioma (I18n)
34
43
  # --------------------------------------
35
44
 
36
- Dotenv.load # Carrega variáveis como BDDGENX_LANG e APIs
45
+ Dotenv.load # Carrega as variáveis do `.env`
37
46
 
38
- # Define o caminho de arquivos de tradução YAML
47
+ # Carrega os arquivos de tradução do diretório `locales/`
39
48
  locales_path = File.expand_path('bddgenx/locales/*.yml', __dir__)
40
49
  I18n.load_path += Dir[locales_path]
41
50
 
42
- # Define o idioma ativo somente se estiver presente e válido
43
- idioma_env = ENV['BDDGENX_LANG']
44
- if idioma_env && !idioma_env.strip.empty?
45
- I18n.locale = idioma_env.strip.to_sym
46
- else
47
- I18n.locale = :pt
48
- end
49
-
51
+ # Define o idioma ativo com base em ENV['BDDGENX_LANG'], padrão: :pt
52
+ I18n.locale = ENV['BDDGENX_LANG']&.strip&.to_sym || :pt
50
53
 
51
54
  # --------------------------------------
52
- # 🔧 Bundler (para projetos com Gemfile)
55
+ # 🔧 Bundler (para carregar dependências do Gemfile)
53
56
  # --------------------------------------
54
57
 
55
- # Carrega as dependências listadas no Gemfile (se houver)
56
58
  require 'bundler/setup' if File.exist?(File.expand_path('../../Gemfile', __FILE__))
57
59
 
58
60
  # --------------------------------------
59
- # 🧩 Módulos utilitários da gem
61
+ # 🧩 Módulos utilitários internos
60
62
  # --------------------------------------
61
63
 
62
- require_relative 'bddgenx/support/validator' # Valida estrutura de entrada
63
- require_relative 'bddgenx/support/font_loader' # Carrega fontes do PDF
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
64
+ require_relative 'bddgenx/support/validator'
65
+ require_relative 'bddgenx/support/font_loader'
66
+ require_relative 'bddgenx/utils/gherkin_cleaner_helper'
67
+ require_relative 'bddgenx/utils/remover_steps_duplicados_helper'
67
68
  require_relative 'bddgenx/utils/language_helper'
68
69
 
69
70
  # --------------------------------------
70
- # 🤖 Clientes de IA (ChatGPT, Gemini)
71
+ # 🤖 Clientes de IA (OpenAI, Gemini, Copilot)
71
72
  # --------------------------------------
72
73
 
73
- require_relative 'bddgenx/ia/gemini_cliente' # Integração com Google Gemini
74
- require_relative 'bddgenx/ia/chatgtp_cliente' # Integração com OpenAI (ChatGPT)
74
+ require_relative 'bddgenx/ia/gemini_cliente'
75
+ require_relative 'bddgenx/ia/chatgtp_cliente'
76
+ require_relative 'bddgenx/ia/microsoft_copilot_cliente'
75
77
 
76
78
  # --------------------------------------
77
- # 🛠 Geradores (features, steps e execução)
79
+ # 🛠 Geradores e Orquestrador
78
80
  # --------------------------------------
79
81
 
80
- require_relative 'bddgenx/generators/generator' # Geração do conteúdo `.feature`
81
- require_relative 'bddgenx/generators/steps_generator' # Geração de arquivos `*_steps.rb`
82
- require_relative 'bddgenx/generators/runner' # Orquestrador da execução CLI
82
+ require_relative 'bddgenx/generators/generator'
83
+ require_relative 'bddgenx/generators/steps_generator'
84
+ require_relative 'bddgenx/generators/runner'
83
85
 
84
86
  # --------------------------------------
85
- # 📄 Parser e metadados
87
+ # 📄 Parser e Metadados
86
88
  # --------------------------------------
87
89
 
88
- require_relative 'parser' # Interpreta arquivos `.txt` de entrada
89
- require_relative 'bddgenx/version' # Lê versão do arquivo `VERSION`
90
+ require_relative 'parser'
91
+ require_relative 'bddgenx/version'
90
92
 
91
93
  # --------------------------------------
92
- # 📤 Relatórios e exportação
94
+ # 📤 Relatórios e Exportação
93
95
  # --------------------------------------
94
96
 
95
- require_relative 'bddgenx/reports/pdf_exporter' # Exporta features para PDF
96
- require_relative 'bddgenx/reports/backup' # Gera backups de arquivos
97
- require_relative 'bddgenx/reports/tracer' # Rastreabilidade de geração
97
+ require_relative 'bddgenx/reports/pdf_exporter'
98
+ require_relative 'bddgenx/reports/backup'
99
+ require_relative 'bddgenx/reports/tracer'
98
100
 
99
101
  # --------------------------------------
100
- # ⚙️ Configuração da gem e loaders auxiliares
102
+ # ⚙️ Configuração e Setup
101
103
  # --------------------------------------
102
104
 
103
- require_relative 'bddgenx/configuration' # Variáveis de configuração (modo, APIs, etc.)
104
- require_relative 'bddgenx/setup' # Inicializa estrutura do projeto (input/, features/, etc.)
105
- require_relative 'bddgenx/support/loader' # Exibe loaders/spinners no terminal
105
+ require_relative 'bddgenx/configuration'
106
+ require_relative 'bddgenx/setup'
107
+ require_relative 'bddgenx/support/loader'
108
+ require_relative 'bddgenx/support/properties_loader'
106
109
 
107
110
  # --------------------------------------
108
- # 🔁 Define modo de execução (ambiente de dev por padrão)
111
+ # 🔁 Carregamento de propriedades como variáveis de ambiente
109
112
  # --------------------------------------
110
113
 
111
- ENV['BDDGENX_ENV'] = 'development'
114
+ properties = Bddgenx::PropertiesLoader.load_properties
115
+
116
+ # Mapeamento de variáveis .properties → ENV
117
+ ENV['CHATGPT_API_URL'] ||= properties['openai.api.url']
118
+ ENV['OPENAI_API_KEY'] ||= properties['openai.api.key']
119
+ ENV['GEMINI_API_URL'] ||= properties['gemini.api.url']
120
+ ENV['GEMINI_API_KEY'] ||= properties['gemini.api.key']
121
+ ENV['MICROSOFT_COPILOT_API_URL'] ||= properties['copilot.api.url']
122
+ ENV['MICROSOFT_COPILOT_API_KEY'] ||= properties['copilot.api.key']
123
+ ENV['BDDGENX_MODE'] ||= properties['mode']
124
+ ENV['BDDGENX_LANG'] ||= properties['lang']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bddgenx
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.7
4
+ version: 2.4.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-18 00:00:00.000000000 Z
11
+ date: 2025-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn
@@ -118,7 +118,6 @@ extensions: []
118
118
  extra_rdoc_files: []
119
119
  files:
120
120
  - README.md
121
- - Rakefile
122
121
  - VERSION
123
122
  - bin/bddgenx
124
123
  - lib/bddgenx.rb
@@ -132,6 +131,7 @@ files:
132
131
  - lib/bddgenx/generators/steps_generator.rb
133
132
  - lib/bddgenx/ia/chatgtp_cliente.rb
134
133
  - lib/bddgenx/ia/gemini_cliente.rb
134
+ - lib/bddgenx/ia/microsoft_copilot_cliente.rb
135
135
  - lib/bddgenx/locales/en.yml
136
136
  - lib/bddgenx/locales/pt.yml
137
137
  - lib/bddgenx/reports/backup.rb
@@ -140,6 +140,7 @@ files:
140
140
  - lib/bddgenx/setup.rb
141
141
  - lib/bddgenx/support/font_loader.rb
142
142
  - lib/bddgenx/support/loader.rb
143
+ - lib/bddgenx/support/properties_loader.rb
143
144
  - lib/bddgenx/support/validator.rb
144
145
  - lib/bddgenx/utils/gherkin_cleaner_helper.rb
145
146
  - lib/bddgenx/utils/language_helper.rb
data/Rakefile DELETED
@@ -1,36 +0,0 @@
1
- require 'bddgenx'
2
- require 'rake'
3
-
4
- namespace :bddgenx do
5
- desc 'Executa geração interativa: escolha entre static, chatgpt, gemini ou deepseek'
6
- task :generate do
7
- puts "=== Qual modo deseja usar para gerar os cenários? ==="
8
- puts "1. static (sem IA)"
9
- puts "2. chatgpt (via OpenAI)"
10
- puts "3. gemini (via Google)"
11
- print "Digite o número (1-3): "
12
-
13
- escolha = STDIN.gets.chomp.to_i
14
-
15
- modo = case escolha
16
- when 1 then :static
17
- when 2 then :chatgpt
18
- when 3 then :gemini
19
- else
20
- puts "❌ Opção inválida. Saindo."; exit 1
21
- end
22
-
23
- Bddgenx.configure do |config|
24
- config.mode = modo
25
- config.openai_api_key_env = 'OPENAI_API_KEY'
26
- config.gemini_api_key_env = 'GEMINI_API_KEY'
27
- end
28
-
29
- # ⚠️ Limpa o ARGV antes de executar para evitar que [static] seja interpretado como nome de arquivo
30
- ARGV.clear
31
-
32
- ENV['BDDGENX_MODE'] = modo.to_s
33
- puts "\n⚙️ Modo selecionado: #{modo}\n\n"
34
- Bddgenx::Runner.execute
35
- end
36
- end