bddgenx 1.0.0 → 2.0.5

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: 691d4d259476c68894a8859cc74ab4d926fdfe3b7304f5a020f05843442f33d2
4
- data.tar.gz: 0aeff404aee94ac4d8419dec2772379a0816fdb5ce69a1e8cf967d47499e2f48
3
+ metadata.gz: 0ee7808e2b0f0cf6e65b1dbc5ea11689a1522dbc06ec2db6ed35494329920913
4
+ data.tar.gz: f88f37264c7e26caa90f4b25aef60e194cd5b08d7a97819b4aa623a7e99d6b04
5
5
  SHA512:
6
- metadata.gz: c08188d0e944723b8e109063d2c2e682e335be6c05ff731f51962daba3a3dccac072201107fb23dda9f0d0b3901f75b97a10e1bcb289cf3305ad6deab8ccc198
7
- data.tar.gz: fd8bf60a0b13bb05e63486daa3b4ccead3ddfdc4653046e5f48b252eede5cb8af35498d0d259a23870c203a5912a63a1c70cfc01fdd9b1e5141e58bc56d4c986
6
+ metadata.gz: 1bdc7124701b3fd7eba6e60993e75e31fb19613a1ae53cb7549afeae1c0d4a7f167a3e5c19bf6805ee118ffd6a6c7a6b5d29b0d25e34e894dbfb86c0e45f2879
7
+ data.tar.gz: eec2871ad622cb5c638163854dbb7dabcb5da57e6bb784732c561c50cc1a4fabc81c3647b82c63509a6bd125e72801db2a69a88415d550af44ccf9f944ed7ac7
data/README.md CHANGED
@@ -5,6 +5,56 @@
5
5
 
6
6
  Ferramenta Ruby para gerar automaticamente arquivos Gherkin (`.feature`) e definições de passos (`steps.rb`) a partir de histórias em texto. Atende aos padrões ISTQB, suporta parametrização com blocos de exemplos e fornece relatórios de QA (rastreabilidade, backups e PDF).
7
7
 
8
+ ## Estrutura do projeto
9
+ ```
10
+ bdd-generation/
11
+ ├── .github/ # Workflows do GitHub Actions
12
+ │ └── workflows/
13
+ │ └── main.yml
14
+ ├── bin/
15
+ │ └── console # Execução local (se necessário)
16
+ ├── features/ # Gherkin gerados automaticamente
17
+ │ └── steps/ # Steps correspondentes
18
+ ├── input/ # Histórias de entrada
19
+ │ ├── historia.txt
20
+ │ ├── historia_en.txt
21
+ │ └── ...
22
+ ├── lib/
23
+ │ └── bddgenx/
24
+ │ ├── ia/ # Módulo de IA
25
+ │ │ ├── chatgpt_cliente.rb
26
+ │ │ └── gemini_cliente.rb
27
+ │ ├── generators/ # Responsável por geração
28
+ │ │ ├── generator.rb
29
+ │ │ ├── steps_generator.rb
30
+ │ │ └── runner.rb
31
+ │ ├── reports/ # Exportadores, backups e rastreabilidade
32
+ │ │ ├── pdf_exporter.rb
33
+ │ │ ├── backup.rb
34
+ │ │ └── tracer.rb
35
+ │ ├── support/ # Utilitários auxiliares
36
+ │ │ ├── gherkin_cleaner.rb
37
+ │ │ ├── remover_steps_duplicados.rb
38
+ │ │ ├── validator.rb
39
+ │ │ └── font_loader.rb
40
+ │ ├── parser.rb # Parse do .txt
41
+ │ ├── version.rb # Versão da gem
42
+ │ └── bddgenx.rb # Arquivo principal (require central)
43
+ ├── reports/ # Saídas: PDF, backup, rastreabilidade
44
+ │ ├── pdf/
45
+ │ ├── backup/
46
+ │ └── rastreabilidade/
47
+ ├── .env # Variáveis de ambiente
48
+ ├── .gitignore
49
+ ├── bddgenx.gemspec
50
+ ├── bump_version.sh
51
+ ├── Gemfile
52
+ ├── Gemfile.lock
53
+ ├── LICENSE
54
+ ├── Rakefile
55
+ ├── README.md
56
+ └── VERSION
57
+ ```
8
58
  ## Instalação
9
59
 
10
60
  Adicione ao seu `Gemfile`:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 2.0.5
data/bin/bddgenx CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative "../lib/bddgenx/runner"
2
+ require_relative "../lib/bddgenx/generators/runner"
3
3
 
4
4
  Bddgenx::Runner.execute
@@ -6,30 +6,45 @@
6
6
  # Suporta Gherkin em Português e Inglês, inclusão de tags, cenários simples
7
7
  # e esquemas de cenário com exemplos.
8
8
 
9
- require 'fileutils'
10
- require 'set'
11
- require_relative 'utils/gherkin_cleaner'
12
- require_relative 'ia/gemini_cliente'
13
- require_relative 'utils/remover_steps_duplicados'
14
- # require_relative 'clients/chatgpt_cliente'
15
-
16
9
  module Bddgenx
17
10
  class Generator
11
+ # Palavras-chave do Gherkin em Português
18
12
  GHERKIN_KEYS_PT = %w[Dado Quando Então E Mas].freeze
13
+
14
+ # Palavras-chave do Gherkin em Inglês
19
15
  GHERKIN_KEYS_EN = %w[Given When Then And But].freeze
16
+
17
+ # Mapeamento de PT → EN
20
18
  GHERKIN_MAP_PT_EN = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
19
+
20
+ # Mapeamento de EN → PT
21
21
  GHERKIN_MAP_EN_PT = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
22
+
23
+ # Todas as palavras-chave reconhecidas
22
24
  ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
23
25
 
26
+ ##
27
+ # Extrai todas as linhas de exemplo de um array de strings.
28
+ #
29
+ # @param raw [Array<String>] um array contendo linhas de texto
30
+ # @return [Array<String>] apenas as linhas que começam com '|', ou seja, exemplos
24
31
  def self.dividir_examples(raw)
25
32
  raw.select { |l| l.strip.start_with?('|') }
26
33
  end
27
34
 
35
+ ##
36
+ # Gera o conteúdo de um arquivo `.feature` a partir de uma história.
37
+ # Pode operar em três modos: estático (hash ou arquivo estruturado),
38
+ # IA com Gemini, ou IA com ChatGPT.
39
+ #
40
+ # @param input [String, Hash] caminho para um arquivo .txt ou um hash estruturado
41
+ # @param override_path [String, nil] caminho alternativo para salvar o arquivo gerado
42
+ # @return [Array(String, String)] caminho do arquivo gerado e conteúdo do .feature
28
43
  def self.gerar_feature(input, override_path = nil)
29
44
  modo = ENV['BDD_MODE']&.to_sym || :static
30
45
 
31
- # IA: Lê o arquivo txt e passa para IA → aplica limpeza
32
46
  if input.is_a?(String) && input.end_with?('.txt') && [:gemini, :chatgpt].include?(modo)
47
+ # Modo com IA: gera cenários automaticamente com base no texto da história
33
48
  raw_txt = File.read(input)
34
49
  historia = {
35
50
  idioma: 'pt',
@@ -50,15 +65,15 @@ module Bddgenx
50
65
  tag: 'ia',
51
66
  passos: GherkinCleaner.limpar(texto_gerado).lines.map(&:strip).reject(&:empty?)
52
67
  }
53
-
54
68
  else
55
- # Caminho tradicional (hash de entrada ou parser de arquivo)
69
+ # Modo estático: utiliza estrutura vinda do Parser ou de um hash diretamente
56
70
  historia = input.is_a?(String) ? Parser.ler_historia(input) : input
57
71
  end
58
72
 
59
73
  idioma = historia[:idioma] || 'pt'
60
74
  cont = 1
61
75
 
76
+ # Normaliza o nome base do arquivo
62
77
  nome_base = historia[:quero]
63
78
  .gsub(/[^a-z0-9]/i, '_')
64
79
  .downcase
@@ -69,6 +84,7 @@ module Bddgenx
69
84
 
70
85
  caminho = override_path || "features/#{nome_base}.feature"
71
86
 
87
+ # Define palavras-chave com base no idioma
72
88
  palavras = {
73
89
  feature: idioma == 'en' ? 'Feature' : 'Funcionalidade',
74
90
  contexto: idioma == 'en' ? 'Background' : 'Contexto',
@@ -79,15 +95,13 @@ module Bddgenx
79
95
  }
80
96
 
81
97
  conteudo = <<~GHK
82
- # language: #{idioma}
83
- #{palavras[:feature]}: #{historia[:quero].sub(/^Quero\s*/i,'')}
84
- # #{historia[:como]}
85
- # #{historia[:quero]}
86
- # #{historia[:para]}
87
-
98
+ # language: #{idioma}
99
+ #{palavras[:feature]}: #{historia[:quero].sub(/^Quero\s*/i,'')}
100
+ # #{historia[:como]}
101
+ # #{historia[:quero]}
102
+ # #{historia[:para]}
88
103
  GHK
89
104
 
90
- # Set para rastrear steps únicos
91
105
  passos_unicos = Set.new
92
106
  pt_map = GHERKIN_MAP_PT_EN
93
107
  en_map = GHERKIN_MAP_EN_PT
@@ -100,7 +114,6 @@ module Bddgenx
100
114
  next if passos.empty?
101
115
 
102
116
  tag_line = ["@#{grupo[:tipo].downcase}", ("@#{grupo[:tag]}" if grupo[:tag])].compact.join(' ')
103
-
104
117
  conteudo << " #{tag_line}\n"
105
118
 
106
119
  if exemplos.any?
@@ -137,11 +150,22 @@ module Bddgenx
137
150
  [caminho, conteudo]
138
151
  end
139
152
 
153
+ ##
154
+ # Gera o caminho padrão de saída para um arquivo `.feature` com base no nome do `.txt`.
155
+ #
156
+ # @param arquivo_txt [String] caminho do arquivo .txt de entrada
157
+ # @return [String] caminho completo do arquivo .feature correspondente
140
158
  def self.path_para_feature(arquivo_txt)
141
159
  nome = File.basename(arquivo_txt, '.txt')
142
160
  File.join('features', "#{nome}.feature")
143
161
  end
144
162
 
163
+ ##
164
+ # Salva o conteúdo gerado no disco, criando diretórios se necessário.
165
+ #
166
+ # @param caminho [String] caminho completo para salvar o arquivo
167
+ # @param conteudo [String] conteúdo do arquivo .feature
168
+ # @return [void]
145
169
  def self.salvar_feature(caminho, conteudo)
146
170
  FileUtils.mkdir_p(File.dirname(caminho))
147
171
  File.write(caminho, conteudo)
@@ -4,16 +4,7 @@
4
4
  # Este arquivo define a classe Runner (CLI) da gem bddgenx,
5
5
  # responsável por orquestrar o fluxo de leitura de histórias,
6
6
  # validação, geração de features, steps, backups e exportação de PDFs.
7
- require 'dotenv/load'
8
- require 'fileutils'
9
- require_relative 'utils/parser'
10
- require_relative 'generator'
11
- require_relative 'utils/pdf_exporter'
12
- require_relative 'steps_generator'
13
- require_relative 'utils/validator'
14
- require_relative 'utils/backup'
15
- require_relative 'ia/gemini_cliente'
16
- require_relative 'utils/gherkin_cleaner'
7
+ require_relative '../../bddgenx'
17
8
 
18
9
  module Bddgenx
19
10
  # Ponto de entrada da gem: coordena todo o processo de geração BDD.
@@ -110,26 +101,15 @@ module Bddgenx
110
101
  end
111
102
 
112
103
  # Geração de feature
113
- if modo == 'gemini'
114
- puts "🤖 Gerando cenários com IA (Gemini)..."
115
- idioma = IA::GeminiCliente.detecta_idioma_arquivo(arquivo) # Seu método que detecta o idioma no .txt (ex: 'pt' ou 'en')
116
- spinner = Thread.new do
117
- loop do
118
- print "\r⏳ Aguardando resposta da IA "
119
- 3.times do |i|
120
- print "." * (i + 1)
121
- sleep(0.4)
122
- print "\r⏳ Aguardando resposta da IA#{'.' * (i + 1)} "
123
- end
104
+ if modo == 'gemini' || modo == 'chatgpt'
105
+ puts "🤖 Gerando cenários com IA (#{modo.capitalize})..."
106
+ idioma = IA::GeminiCliente.detecta_idioma_arquivo(arquivo)
107
+ feature_text =
108
+ if modo == 'gemini'
109
+ IA::GeminiCliente.gerar_cenarios(historia, idioma)
110
+ else
111
+ IA::ChatGptCliente.gerar_cenarios(historia, idioma)
124
112
  end
125
- end
126
- begin
127
- feature_text = IA::GeminiCliente.gerar_cenarios(historia, idioma)
128
- ensure
129
- Thread.kill(spinner)
130
- print "\r✅ Resposta da IA recebida! \n"
131
- end
132
- # feature_text = IA::GeminiCliente.gerar_cenarios(historia, idioma)
133
113
  if feature_text
134
114
  feature_path = Generator.path_para_feature(arquivo)
135
115
  feature_content = Bddgenx::GherkinCleaner.limpar(feature_text)
@@ -6,26 +6,45 @@
6
6
  # Suporta palavras-chave Gherkin em Português e Inglês e parametriza
7
7
  # strings e números conforme necessário.
8
8
 
9
- require 'fileutils'
10
- require 'strscan' # Para uso de StringScanner
11
- require 'set'
12
-
13
9
  module Bddgenx
14
10
  class StepsGenerator
11
+ # Palavras-chave Gherkin em Português
15
12
  GHERKIN_KEYS_PT = %w[Dado Quando Então E Mas].freeze
13
+
14
+ # Palavras-chave Gherkin em Inglês
16
15
  GHERKIN_KEYS_EN = %w[Given When Then And But].freeze
16
+
17
+ # Conjunto de todas as palavras-chave suportadas
17
18
  ALL_KEYS = GHERKIN_KEYS_PT + GHERKIN_KEYS_EN
18
19
 
20
+ ##
21
+ # Transforma uma string em estilo camelCase.
22
+ #
23
+ # @param str [String] A string a ser transformada.
24
+ # @return [String] A string convertida para camelCase.
25
+ #
19
26
  def self.camelize(str)
20
27
  partes = str.strip.split(/[^a-zA-Z0-9]+/)
21
28
  partes.map.with_index { |palavra, i| i.zero? ? palavra.downcase : palavra.capitalize }.join
22
29
  end
23
30
 
31
+ ##
32
+ # Gera arquivos de passos do Cucumber a partir de um arquivo .feature.
33
+ #
34
+ # O método lê o arquivo, detecta o idioma, extrai os passos,
35
+ # parametriza as variáveis (números e strings), e escreve os métodos
36
+ # em um novo arquivo no diretório `steps/`.
37
+ #
38
+ # @param feature_path [String] Caminho para o arquivo .feature.
39
+ # @return [Boolean] Retorna true se os passos forem gerados com sucesso, false se não houver passos.
40
+ # @raise [ArgumentError] Se o caminho fornecido não for uma String.
41
+ #
24
42
  def self.gerar_passos(feature_path)
25
43
  raise ArgumentError, "Caminho esperado como String, recebeu #{feature_path.class}" unless feature_path.is_a?(String)
26
44
 
27
45
  linhas = File.readlines(feature_path)
28
46
 
47
+ # Detecta o idioma com base na diretiva "# language:"
29
48
  lang = if (m = linhas.find { |l| l =~ /^#\s*language:\s*(\w+)/i })
30
49
  m[/^#\s*language:\s*(\w+)/i, 1].downcase
31
50
  else
@@ -35,6 +54,7 @@ module Bddgenx
35
54
  pt_para_en = GHERKIN_KEYS_PT.zip(GHERKIN_KEYS_EN).to_h
36
55
  en_para_pt = GHERKIN_KEYS_EN.zip(GHERKIN_KEYS_PT).to_h
37
56
 
57
+ # Seleciona apenas linhas que começam com palavras-chave Gherkin
38
58
  linhas_passos = linhas.map(&:strip).select do |linha|
39
59
  ALL_KEYS.any? { |chave| linha.start_with?(chave + ' ') }
40
60
  end
@@ -53,6 +73,7 @@ module Bddgenx
53
73
  linhas_passos.each do |linha|
54
74
  palavra_original, restante = linha.split(' ', 2)
55
75
 
76
+ # Tradução de palavras-chave se necessário
56
77
  chave = case lang
57
78
  when 'en' then pt_para_en[palavra_original] || palavra_original
58
79
  else en_para_pt[palavra_original] || palavra_original
@@ -63,6 +84,7 @@ module Bddgenx
63
84
  padrao = ''
64
85
  tokens = []
65
86
 
87
+ # Analisa e parametriza o conteúdo dos passos
66
88
  until scanner.eos?
67
89
  if scanner.check(/"<([^>]+)>"/)
68
90
  scanner.scan(/"<([^>]+)>"/)
@@ -87,7 +109,7 @@ module Bddgenx
87
109
 
88
110
  padrao_seguro = padrao.gsub('"', '\\"')
89
111
 
90
- # Impede duplicatas
112
+ # Impede criação de métodos duplicados
91
113
  next if passos_unicos.include?(padrao_seguro)
92
114
 
93
115
  passos_unicos << padrao_seguro
@@ -0,0 +1,147 @@
1
+ # lib/bddgenx/ia/chatgpt_cliente.rb
2
+
3
+ module Bddgenx
4
+ module IA
5
+ ##
6
+ # Cliente para interação com a API do ChatGPT da OpenAI para gerar
7
+ # cenários BDD no formato Gherkin, com suporte a fallback para Gemini.
8
+ #
9
+ class ChatGptCliente
10
+ CHATGPT_API_URL = 'https://api.openai.com/v1/chat/completions'.freeze
11
+ MODEL = 'gpt-4o'
12
+
13
+ ##
14
+ # Gera cenários BDD a partir de uma história fornecida,
15
+ # solicitando à API do ChatGPT a criação dos cenários em formato Gherkin.
16
+ # Se a API key não estiver configurada ou houver erro na requisição,
17
+ # utiliza fallback com o GeminiCliente.
18
+ #
19
+ # @param historia [String] Texto com a história para basear os cenários.
20
+ # @param idioma [String] Código do idioma ('pt' ou 'en'), padrão 'pt'.
21
+ # @return [String] Cenários gerados em formato Gherkin com palavras-chave no idioma indicado.
22
+ #
23
+ def self.gerar_cenarios(historia, idioma = 'pt')
24
+ api_key = ENV['OPENAI_API_KEY']
25
+
26
+ unless api_key
27
+ warn "❌ API Key do ChatGPT não encontrada no .env (OPENAI_API_KEY)"
28
+ return fallback_com_gemini(historia, idioma)
29
+ end
30
+
31
+ # Palavras-chave Gherkin para português e inglês
32
+ keywords_pt = {
33
+ feature: "Funcionalidade",
34
+ scenario: "Cenário",
35
+ scenario_outline: "Esquema do Cenário",
36
+ examples: "Exemplos",
37
+ given: "Dado",
38
+ when: "Quando",
39
+ then: "Então",
40
+ and: "E"
41
+ }
42
+
43
+ keywords_en = {
44
+ feature: "Feature",
45
+ scenario: "Scenario",
46
+ scenario_outline: "Scenario Outline",
47
+ examples: "Examples",
48
+ given: "Given",
49
+ when: "When",
50
+ then: "Then",
51
+ and: "And"
52
+ }
53
+
54
+ keywords = idioma == 'en' ? keywords_en : keywords_pt
55
+
56
+ # Prompt base enviado ao ChatGPT, instruindo a saída no formato correto
57
+ prompt_base = <<~PROMPT
58
+ Gere cenários BDD no formato Gherkin, usando as palavras-chave de estrutura no idioma \"#{idioma}\":
59
+ Feature: #{keywords[:feature]}
60
+ Scenario: #{keywords[:scenario]}
61
+ Scenario Outline: #{keywords[:scenario_outline]}
62
+ Examples: #{keywords[:examples]}
63
+ Given: #{keywords[:given]}
64
+ When: #{keywords[:when]}
65
+ Then: #{keywords[:then]}
66
+ And: #{keywords[:and]}
67
+
68
+ Atenção: Os textos e descrições dos cenários e passos devem ser escritos em português, mesmo que as palavras-chave estejam em inglês.
69
+
70
+ História:
71
+ #{historia}
72
+ PROMPT
73
+
74
+ uri = URI(CHATGPT_API_URL)
75
+ request_body = {
76
+ model: MODEL,
77
+ messages: [
78
+ {
79
+ role: "user",
80
+ content: prompt_base
81
+ }
82
+ ]
83
+ }
84
+
85
+ headers = {
86
+ "Content-Type" => "application/json",
87
+ "Authorization" => "Bearer #{api_key}"
88
+ }
89
+
90
+ response = Net::HTTP.post(uri, request_body.to_json, headers)
91
+
92
+ if response.is_a?(Net::HTTPSuccess)
93
+ json = JSON.parse(response.body)
94
+ texto_ia = json.dig("choices", 0, "message", "content")
95
+
96
+ if texto_ia
97
+ texto_limpo = Bddgenx::GherkinCleaner.limpar(texto_ia)
98
+ Utils::StepCleaner.remover_steps_duplicados(texto_ia, idioma)
99
+
100
+ # Ajusta a linha de idioma no arquivo gerado
101
+ texto_limpo.sub!(/^# language: .*/, "# language: #{idioma}")
102
+ texto_limpo.prepend("# language: #{idioma}\n") unless texto_limpo.start_with?("# language:")
103
+ return texto_limpo
104
+ else
105
+ warn "❌ Resposta da IA sem conteúdo de texto"
106
+ warn JSON.pretty_generate(json)
107
+ return fallback_com_gemini(historia, idioma)
108
+ end
109
+ else
110
+ warn "❌ Erro ao chamar ChatGPT: #{response.code} - #{response.body}"
111
+ return fallback_com_gemini(historia, idioma)
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Método de fallback que chama o GeminiCliente para gerar cenários,
117
+ # usado quando a API do ChatGPT não está disponível ou ocorre erro.
118
+ #
119
+ # @param historia [String] Texto da história para basear os cenários.
120
+ # @param idioma [String] Código do idioma ('pt' ou 'en').
121
+ # @return [String] Cenários gerados pelo GeminiCliente.
122
+ #
123
+ def self.fallback_com_gemini(historia, idioma)
124
+ warn "🔁 Tentando gerar com Gemini como fallback..."
125
+ GeminiCliente.gerar_cenarios(historia, idioma)
126
+ end
127
+
128
+ ##
129
+ # Detecta o idioma de um arquivo de feature pela linha "# language:".
130
+ #
131
+ # @param caminho_arquivo [String] Caminho para o arquivo de feature.
132
+ # @return [String] Código do idioma detectado ('pt' por padrão).
133
+ #
134
+ def self.detecta_idioma_arquivo(caminho_arquivo)
135
+ return 'pt' unless File.exist?(caminho_arquivo)
136
+
137
+ File.foreach(caminho_arquivo) do |linha|
138
+ if linha =~ /^#\s*language:\s*(\w{2})/i
139
+ return $1.downcase
140
+ end
141
+ end
142
+
143
+ 'pt'
144
+ end
145
+ end
146
+ end
147
+ end
@@ -1,15 +1,22 @@
1
1
  # lib/bddgenx/ia/gemini_cliente.rb
2
- require 'net/http'
3
- require 'json'
4
- require 'uri'
5
- require_relative '../utils/gherkin_cleaner'
6
- require_relative '../utils/remover_steps_duplicados'
7
2
 
8
3
  module Bddgenx
9
4
  module IA
5
+ ##
6
+ # Cliente para interação com a API Gemini do Google para geração
7
+ # de conteúdo, aqui usado para criar cenários BDD no formato Gherkin.
8
+ #
10
9
  class GeminiCliente
11
10
  GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'.freeze
12
11
 
12
+ ##
13
+ # Gera cenários BDD baseados em uma história, solicitando à API Gemini
14
+ # o retorno no formato Gherkin com palavras-chave no idioma desejado.
15
+ #
16
+ # @param historia [String] Texto base da história para gerar os cenários.
17
+ # @param idioma [String] Código do idioma, 'pt' por padrão.
18
+ # @return [String, nil] Cenários no formato Gherkin, ou nil em caso de erro.
19
+ #
13
20
  def self.gerar_cenarios(historia, idioma = 'pt')
14
21
  api_key = ENV['GEMINI_API_KEY']
15
22
 
@@ -37,7 +44,7 @@ module Bddgenx
37
44
 
38
45
  keywords = idioma == 'en' ? keywords_en : keywords_pt
39
46
 
40
- # Prompt base para solicitar saída Gherkin estruturada da IA
47
+ # Prompt base que instrui a IA a gerar cenários Gherkin no idioma indicado
41
48
  prompt_base = <<~PROMPT
42
49
  Gere cenários BDD no formato Gherkin, usando as palavras-chave de estrutura no idioma "#{idioma}":
43
50
  Feature: #{keywords[:feature]}
@@ -54,23 +61,25 @@ module Bddgenx
54
61
  História:
55
62
  #{historia}
56
63
  PROMPT
64
+
57
65
  unless api_key
58
66
  warn "❌ API Key do Gemini não encontrada no .env (GEMINI_API_KEY)"
59
67
  return nil
60
68
  end
61
69
 
62
70
  uri = URI("#{GEMINI_API_URL}?key=#{api_key}")
63
- prompt = prompt_base % { historia: historia }
64
71
 
72
+ # Estrutura do corpo da requisição para a API Gemini
65
73
  request_body = {
66
74
  contents: [
67
75
  {
68
76
  role: "user",
69
- parts: [{ text: prompt }]
77
+ parts: [{ text: prompt_base }]
70
78
  }
71
79
  ]
72
80
  }
73
81
 
82
+ # Executa requisição POST para a API Gemini
74
83
  response = Net::HTTP.post(uri, request_body.to_json, { "Content-Type" => "application/json" })
75
84
 
76
85
  if response.is_a?(Net::HTTPSuccess)
@@ -84,11 +93,11 @@ module Bddgenx
84
93
 
85
94
  texto_ia = json["candidates"].first.dig("content", "parts", 0, "text")
86
95
  if texto_ia
87
- # Sanitiza o texto para garantir formato Gherkin correto
96
+ # Limpeza e sanitização do texto para manter padrão Gherkin
88
97
  texto_limpo = Bddgenx::GherkinCleaner.limpar(texto_ia)
89
98
  Utils::StepCleaner.remover_steps_duplicados(texto_ia, idioma)
90
99
 
91
- # Insere a diretiva language dinamicamente com base no idioma detectado
100
+ # Ajuste da diretiva de idioma na saída gerada
92
101
  texto_limpo.sub!(/^# language: .*/, "# language: #{idioma}")
93
102
  texto_limpo.prepend("# language: #{idioma}\n") unless texto_limpo.start_with?("# language:")
94
103
 
@@ -104,6 +113,12 @@ module Bddgenx
104
113
  end
105
114
  end
106
115
 
116
+ ##
117
+ # Detecta o idioma do arquivo de feature pela linha "# language:".
118
+ #
119
+ # @param caminho_arquivo [String] Caminho do arquivo para detecção do idioma.
120
+ # @return [String] Código do idioma detectado (ex: 'pt'), padrão 'pt'.
121
+ #
107
122
  def self.detecta_idioma_arquivo(caminho_arquivo)
108
123
  return 'pt' unless File.exist?(caminho_arquivo)
109
124
 
@@ -113,7 +128,7 @@ module Bddgenx
113
128
  end
114
129
  end
115
130
 
116
- 'pt' # padrão
131
+ 'pt' # idioma padrão caso não encontre
117
132
  end
118
133
  end
119
134
  end
@@ -4,10 +4,6 @@
4
4
  # Este arquivo define a classe Backup, responsável por criar cópias de segurança
5
5
  # de arquivos .feature antes de serem sobrescritos.
6
6
  # As cópias são salvas em 'reports/backup' com timestamp no nome.
7
-
8
- require 'fileutils'
9
- require 'time'
10
-
11
7
  module Bddgenx
12
8
  # Gerencia a criação de backups de arquivos .feature
13
9
  class Backup
@@ -6,11 +6,6 @@
6
6
  # preto e branco.
7
7
  # Utiliza a gem Prawn para renderização de texto e tabelas.
8
8
 
9
- require 'prawn'
10
- require 'prawn/table'
11
- require 'fileutils'
12
- require_relative 'font_loader'
13
-
14
9
  # Suprime aviso de internacionalização para fontes AFM internas
15
10
  Prawn::Fonts::AFM.hide_m17n_warning = true
16
11
 
@@ -4,10 +4,6 @@
4
4
  # Este arquivo define a classe Tracer, responsável por gerar e manter
5
5
  # informações de rastreabilidade de cenários e passos em um arquivo CSV.
6
6
  # Útil para auditoria e análise de cobertura de cenários gerados.
7
-
8
- require 'csv'
9
- require 'fileutils'
10
-
11
7
  module Bddgenx
12
8
  # Classe para adicionar registros de rastreabilidade a um relatório CSV.
13
9
  class Tracer
@@ -4,10 +4,6 @@
4
4
  # Este arquivo define a classe FontLoader, responsável por localizar e carregar
5
5
  # famílias de fontes TrueType para uso com Prawn em geração de PDFs.
6
6
  # Busca os arquivos de fonte no diretório assets/fonts dentro da gem.
7
-
8
- require 'prawn'
9
- require 'rubygems' # para Gem.loaded_specs se necessário
10
-
11
7
  module Bddgenx
12
8
  # Gerencia o carregamento de fontes TTF para os documentos PDF.
13
9
  class FontLoader
@@ -0,0 +1,102 @@
1
+ module Bddgenx
2
+ class GherkinCleaner
3
+ # Método principal para limpar o texto Gherkin recebido.
4
+ # Executa uma sequência de operações para deixar o texto formatado e correto.
5
+ #
6
+ # Passos:
7
+ # - Remove blocos de código markdown (```).
8
+ # - Garante que exista somente uma linha # language: correta.
9
+ # - Corrige a indentação dos blocos Gherkin para o padrão esperado.
10
+ # - Remove espaços em branco no início/fim do texto.
11
+ #
12
+ # Retorna o texto limpo e formatado.
13
+ def self.limpar(texto)
14
+ texto = remover_blocos_markdown(texto)
15
+ texto = corrigir_language(texto)
16
+ texto = corrigir_indentacao(texto)
17
+ texto.strip
18
+ end
19
+
20
+ # Remove blocos markdown que usam as crases tripas (```).
21
+ # Muitas vezes a IA retorna os textos dentro desses blocos, que precisam ser limpos.
22
+ #
23
+ # Exemplo:
24
+ # ```gherkin
25
+ # Feature: Exemplo
26
+ # ```
27
+ #
28
+ # Essa função remove as linhas contendo as crases, deixando só o conteúdo.
29
+ def self.remover_blocos_markdown(texto)
30
+ texto.gsub(/```[a-z]*\n?/i, '').gsub(/```/, '')
31
+ end
32
+
33
+ # Garante que o arquivo contenha exatamente uma linha "# language: xx" no topo.
34
+ #
35
+ # Passos:
36
+ # - Procura a primeira ocorrência da linha de language no texto.
37
+ # - Remove todas as outras linhas duplicadas de language.
38
+ # - Se encontrar, move essa linha para o início do texto.
39
+ # - Se não encontrar, detecta o idioma do texto e adiciona a linha no topo.
40
+ #
41
+ # Isso evita erros de parsing em ferramentas BDD que exigem essa diretiva.
42
+ def self.corrigir_language(texto)
43
+ linhas = texto.lines
44
+ primeira_language = linhas.find { |linha| linha.strip.start_with?('# language:') }
45
+
46
+ # Remove todas as linhas duplicadas de language
47
+ linhas.reject! { |linha| linha.strip.start_with?('# language:') }
48
+
49
+ if primeira_language
50
+ # Insere a linha de language original no topo
51
+ linhas.unshift(primeira_language.strip + "\n")
52
+ else
53
+ # Se não existir, detecta o idioma e insere padrão
54
+ idioma = detectar_idioma(linhas.join)
55
+ linhas.unshift("# language: #{idioma}\n")
56
+ end
57
+
58
+ linhas.join
59
+ end
60
+
61
+ # Detecta o idioma do conteúdo baseado nas palavras-chave Gherkin presentes.
62
+ #
63
+ # Retorna:
64
+ # - 'pt' se encontrar palavras-chave em português (Dado, Quando, Então, E).
65
+ # - 'en' se encontrar palavras-chave em inglês (Given, When, Then, And).
66
+ # - 'pt' como padrão se não detectar.
67
+ #
68
+ # Isso ajuda a definir a diretiva # language: corretamente.
69
+ def self.detectar_idioma(texto)
70
+ return 'pt' if texto =~ /Dado|Quando|Então|E /i
71
+ return 'en' if texto =~ /Given|When|Then|And /i
72
+ 'pt' # padrão
73
+ end
74
+
75
+ # Corrige a indentação das linhas para seguir o padrão Gherkin:
76
+ #
77
+ # Feature e Funcionalidade no nível 0 (sem indentação).
78
+ # Scenario e Scenario Outline com 2 espaços.
79
+ # Passos (Given, When, Then, And e equivalentes PT) com 4 espaços.
80
+ # Tabelas (linhas que começam com '|') com 6 espaços.
81
+ #
82
+ # Outras linhas recebem indentação padrão de 2 espaços.
83
+ #
84
+ # Essa padronização melhora legibilidade e compatibilidade com parsers Gherkin.
85
+ def self.corrigir_indentacao(texto)
86
+ linhas = texto.lines.map do |linha|
87
+ if linha.strip.start_with?('Feature', 'Funcionalidade')
88
+ linha.strip + "\n"
89
+ elsif linha.strip.start_with?('Scenario', 'Cenário', 'Scenario Outline', 'Esquema do Cenário')
90
+ " #{linha.strip}\n"
91
+ elsif linha.strip.start_with?('Given', 'When', 'Then', 'And', 'Dado', 'Quando', 'Então', 'E')
92
+ " #{linha.strip}\n"
93
+ elsif linha.strip.start_with?('|')
94
+ " #{linha.strip}\n"
95
+ else
96
+ " #{linha.strip}\n"
97
+ end
98
+ end
99
+ linhas.join
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,81 @@
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
@@ -4,7 +4,6 @@
4
4
  # Este arquivo define a classe Validator, responsável por validar a estrutura
5
5
  # de uma história antes de gerar cenários ou arquivos .feature.
6
6
  # Verifica presença de cabeçalho obrigatório e integridade dos grupos de passos.
7
-
8
7
  module Bddgenx
9
8
  # Valida objetos de história garantindo que possuam campos e blocos corretos.
10
9
  class Validator
data/lib/bddgenx.rb CHANGED
@@ -1,7 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative 'bddgenx/runner'
2
+ # Shebang para permitir execução direta do script no Linux/macOS, usando o interpretador Ruby do PATH
3
3
 
4
- # Só executa quando este arquivo for o entrypoint, não no require da gem
4
+ require_relative 'env'
5
+ # Carrega o arquivo 'env.rb' relativo a este script. Geralmente contém configurações e carregamento da gem Bddgenx.
6
+
7
+ ## Evita warns na saida do console
8
+ Gem::Specification.reset
9
+ # Reseta as especificações das gems carregadas para evitar warnings no console, comum em alguns ambientes Ruby.
10
+
11
+ # Só executa o código abaixo quando este arquivo for o ponto de entrada (entrypoint) da aplicação,
12
+ # evitando que o código seja executado quando este arquivo for apenas requerido por outro arquivo.
5
13
  if __FILE__ == $PROGRAM_NAME
6
14
  Bddgenx::Runner.execute
15
+ # Invoca o método principal da gem Bddgenx para iniciar a execução da ferramenta.
7
16
  end
data/lib/env.rb ADDED
@@ -0,0 +1,46 @@
1
+ # lib/bddgenx/env.rb
2
+
3
+ # Carregamento de bibliotecas padrão e gems externas usadas no projeto
4
+
5
+ require 'json' # Para manipulação de dados JSON
6
+ require 'net/http' # Para fazer requisições HTTP
7
+ require 'uri' # Para manipulação de URLs
8
+ require 'fileutils' # Para manipulação de arquivos e diretórios
9
+ require 'prawn' # Biblioteca para geração de PDFs
10
+ require 'prawn/table' # Suporte a tabelas no Prawn PDF
11
+ require 'prawn-svg' # Para incorporar SVG em PDFs com Prawn
12
+ require 'open3' # Para executar comandos externos com captura de saída
13
+ require 'faraday' # Cliente HTTP para Gemini API
14
+ require 'dotenv' # Para carregar variáveis de ambiente de arquivos .env
15
+ require 'unicode' # Para manipulação avançada de strings Unicode (ex: remoção de acentos)
16
+ require 'bigdecimal' # Para operações matemáticas precisas com decimais
17
+
18
+ # Configura o caminho base do projeto e carrega as gems definidas no Gemfile (se existir)
19
+ require 'bundler/setup' if File.exist?(File.expand_path('../../Gemfile', __FILE__))
20
+
21
+ # Carregamento dos módulos utilitários (helpers)
22
+ require_relative 'bddgenx/support/gherkin_cleaner' # Limpeza e normalização de textos Gherkin
23
+ require_relative 'bddgenx/support/remover_steps_duplicados' # Remoção de steps duplicados em features
24
+ require_relative 'bddgenx/support/validator' # Validação de dados e entrada
25
+ require_relative 'bddgenx/support/font_loader' # Carregamento de fontes para geração PDF
26
+
27
+ # Carregamento dos clientes para Integração com Inteligência Artificial
28
+ require_relative 'bddgenx/ia/gemini_cliente' # Cliente para API Gemini (Google)
29
+ require_relative 'bddgenx/ia/chatgtp_cliente' # Cliente para API ChatGPT (OpenAI)
30
+
31
+ # Carregamento dos geradores de BDD (features, steps e runner)
32
+ require_relative 'bddgenx/generators/generator' # Gerador principal de arquivos .feature
33
+ require_relative 'bddgenx/generators/steps_generator' # Gerador de arquivos steps.rb
34
+ require_relative 'bddgenx/generators/runner' # Classe responsável pela execução do processo de geração
35
+
36
+ # Parser do arquivo de entrada e versão da gem
37
+ require_relative 'parser' # Parser para interpretar arquivos de entrada
38
+ require_relative 'version' # Informação da versão da gem
39
+
40
+ # Relatórios e exportação
41
+ require_relative 'bddgenx/reports/pdf_exporter' # Exporta relatórios em PDF usando Prawn
42
+ require_relative 'bddgenx/reports/backup' # Mecanismo de backup dos arquivos gerados
43
+ require_relative 'bddgenx/reports/tracer' # Rastreabilidade dos processos
44
+
45
+ # Define variável de ambiente global para indicar que o ambiente BDDGENX está em modo desenvolvimento
46
+ ENV['BDDGENX_ENV'] = 'development'
@@ -4,9 +4,7 @@
4
4
  # Este arquivo define a classe Parser, responsável por ler e interpretar
5
5
  # arquivos de história (.txt), extraindo cabeçalho e blocos de passos e exemplos.
6
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
- require_relative '../ia/gemini_cliente'
9
-
7
+ # Português e Inglês na marcação de idioma e blocos de exemplos
10
8
  module Bddgenx
11
9
  # Tipos de blocos reconhecidos na história (.txt), incluindo variações em Português
12
10
  # e Inglês para blocos de exemplo.
data/lib/version.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Bddgenx
2
+ # Caminho absoluto para o arquivo VERSION, que fica 3 níveis acima deste arquivo:
3
+ # lib/bddgenx/version.rb → lib/bddgenx → lib → raiz do projeto
4
+ VERSION_FILE = File.expand_path("../../VERSION", __FILE__)
5
+
6
+ # Lê o conteúdo do arquivo VERSION para definir a constante VERSION
7
+ # Se o arquivo não existir, exibe um aviso e define o valor padrão "0.0.0"
8
+ VERSION = if File.exist?(VERSION_FILE)
9
+ File.read(VERSION_FILE).strip
10
+ else
11
+ warn "WARNING: VERSION file not found, defaulting to 0.0.0"
12
+ "0.0.0"
13
+ end
14
+ end
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: 1.0.0
4
+ version: 2.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento
@@ -52,6 +52,62 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: ruby-openai
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '8.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '8.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.13.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.13.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: dotenv
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '3.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: unicode
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0.4'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0.4'
55
111
  description: Transforma arquivos .txt com histórias em arquivos .feature, com steps,
56
112
  rastreabilidade e integração com CI/CD.
57
113
  email:
@@ -70,20 +126,21 @@ files:
70
126
  - lib/bddgenx/assets/fonts/DejaVuSansMono-BoldOblique.ttf
71
127
  - lib/bddgenx/assets/fonts/DejaVuSansMono-Oblique.ttf
72
128
  - lib/bddgenx/assets/fonts/DejaVuSansMono.ttf
73
- - lib/bddgenx/generator.rb
129
+ - lib/bddgenx/generators/generator.rb
130
+ - lib/bddgenx/generators/runner.rb
131
+ - lib/bddgenx/generators/steps_generator.rb
132
+ - lib/bddgenx/ia/chatgtp_cliente.rb
74
133
  - lib/bddgenx/ia/gemini_cliente.rb
75
- - lib/bddgenx/ia/gemini_generator.rb
76
- - lib/bddgenx/runner.rb
77
- - lib/bddgenx/steps_generator.rb
78
- - lib/bddgenx/utils/backup.rb
79
- - lib/bddgenx/utils/font_loader.rb
80
- - lib/bddgenx/utils/gherkin_cleaner.rb
81
- - lib/bddgenx/utils/parser.rb
82
- - lib/bddgenx/utils/pdf_exporter.rb
83
- - lib/bddgenx/utils/remover_steps_duplicados.rb
84
- - lib/bddgenx/utils/tracer.rb
85
- - lib/bddgenx/utils/validator.rb
86
- - lib/bddgenx/version.rb
134
+ - lib/bddgenx/reports/backup.rb
135
+ - lib/bddgenx/reports/pdf_exporter.rb
136
+ - lib/bddgenx/reports/tracer.rb
137
+ - lib/bddgenx/support/font_loader.rb
138
+ - lib/bddgenx/support/gherkin_cleaner.rb
139
+ - lib/bddgenx/support/remover_steps_duplicados.rb
140
+ - lib/bddgenx/support/validator.rb
141
+ - lib/env.rb
142
+ - lib/parser.rb
143
+ - lib/version.rb
87
144
  homepage: https://github.com/David-Nascimento/bdd-generation
88
145
  licenses:
89
146
  - MIT
@@ -1,127 +0,0 @@
1
- # lib/bddgenx/cli.rb
2
- # encoding: utf-8
3
- #
4
- # Este arquivo define a classe Runner (CLI) da gem bddgenx,
5
- # responsável por orquestrar o fluxo de leitura de histórias,
6
- # validação, geração de features, steps, backups e exportação de PDFs.
7
- require 'dotenv/load'
8
- require 'fileutils'
9
- require_relative 'utils/parser'
10
- require_relative 'generator'
11
- require_relative 'utils/pdf_exporter'
12
- require_relative 'steps_generator'
13
- require_relative 'utils/validator'
14
- require_relative 'utils/backup'
15
- require_relative 'ia/gemini_cliente'
16
- require_relative 'utils/gherkin_cleaner'
17
- require_relative 'gemini_generator'
18
-
19
- module Bddgenx
20
- # Ponto de entrada da gem: coordena todo o processo de geração BDD.
21
- class Runner
22
- def self.choose_files(input_dir)
23
- ARGV.any? ? selecionar_arquivos_txt(input_dir) : choose_input(input_dir)
24
- end
25
-
26
- def self.selecionar_arquivos_txt(input_dir)
27
- ARGV.map do |arg|
28
- nome = arg.end_with?('.txt') ? arg : "#{arg}.txt"
29
- path = File.join(input_dir, nome)
30
- unless File.exist?(path)
31
- warn "⚠️ Arquivo não encontrado: #{path}"
32
- next
33
- end
34
- path
35
- end.compact
36
- end
37
-
38
- def self.choose_input(input_dir)
39
- files = Dir.glob(File.join(input_dir, '*.txt'))
40
- if files.empty?
41
- warn "❌ Não há arquivos .txt no diretório #{input_dir}"; exit 1
42
- end
43
-
44
- puts "Selecione o arquivo de história para processar:"
45
- files.each_with_index { |f, i| puts "#{i+1}. #{File.basename(f)}" }
46
- print "Digite o número correspondente (ou ENTER para todos): "
47
- choice = STDIN.gets.chomp
48
-
49
- return files if choice.empty?
50
- idx = choice.to_i - 1
51
- unless idx.between?(0, files.size - 1)
52
- warn "❌ Escolha inválida."; exit 1
53
- end
54
- [files[idx]]
55
- end
56
-
57
- def self.execute
58
- modo = ENV['BDDGENX_MODE'] || 'static'
59
-
60
- input_dir = 'input'
61
- Dir.mkdir(input_dir) unless Dir.exist?(input_dir)
62
-
63
- arquivos = choose_files(input_dir)
64
- if arquivos.empty?
65
- warn "❌ Nenhum arquivo de história para processar."; exit 1
66
- end
67
-
68
- total = features = steps = ignored = 0
69
- skipped_steps = []
70
- generated_pdfs = []
71
- skipped_pdfs = []
72
-
73
- arquivos.each do |arquivo|
74
- total += 1
75
- puts "\n🔍 Processando: #{arquivo}"
76
-
77
- historia =
78
- if modo == 'gemini'
79
- puts "🤖 Gerando cenários com IA (Gemini)..."
80
- begin
81
- idioma = GeminiCliente.detecta_idioma_arquivo(arquivo)
82
- historia = File.read(arquivo)
83
- GeminiCliente.gerar_cenarios(historia, idioma)
84
- rescue => e
85
- ignored += 1
86
- puts "❌ Falha ao gerar com Gemini: #{e.message}"
87
- next
88
- end
89
- else
90
- historia = Parser.ler_historia(arquivo)
91
- unless Validator.validar(historia)
92
- ignored += 1
93
- puts "❌ História inválida: #{arquivo}"
94
- next
95
- end
96
- historia
97
- end
98
-
99
- historia_limpa = GherkinCleaner.limpar(historia_ia_gerada)
100
- feature_path, feature_content = Generator.gerar_feature(historia_limpa)
101
-
102
- Backup.salvar_versao_antiga(feature_path)
103
- features += 1 if Generator.salvar_feature(feature_path, feature_content)
104
-
105
- if StepsGenerator.gerar_passos(feature_path)
106
- steps += 1
107
- else
108
- skipped_steps << feature_path
109
- end
110
-
111
- FileUtils.mkdir_p('reports')
112
- result = PDFExporter.exportar_todos(only_new: true)
113
- generated_pdfs.concat(result[:generated])
114
- skipped_pdfs.concat(result[:skipped])
115
- end
116
-
117
- puts "\n✅ Processamento concluído"
118
- puts "- Total de histórias: #{total}"
119
- puts "- Features geradas: #{features}"
120
- puts "- Steps gerados: #{steps}"
121
- puts "- Steps ignorados: #{skipped_steps.size}"
122
- puts "- PDFs gerados: #{generated_pdfs.size}"
123
- puts "- PDFs já existentes: #{skipped_pdfs.size}"
124
- puts "- Histórias ignoradas: #{ignored}"
125
- end
126
- end
127
- end
@@ -1,57 +0,0 @@
1
- require 'set'
2
- require 'unicode'
3
-
4
- module Bddgenx
5
- class GherkinCleaner
6
- def self.limpar(texto)
7
- texto = remover_blocos_markdown(texto)
8
- texto = corrigir_language(texto)
9
- texto = corrigir_indentacao(texto)
10
- texto.strip
11
- end
12
-
13
- def self.remover_blocos_markdown(texto)
14
- texto.gsub(/```[a-z]*\n?/i, '').gsub(/```/, '')
15
- end
16
-
17
- def self.corrigir_language(texto)
18
- linhas = texto.lines
19
- primeira_language = linhas.find { |linha| linha.strip.start_with?('# language:') }
20
-
21
- # Remove duplicações de # language
22
- linhas.reject! { |linha| linha.strip.start_with?('# language:') }
23
-
24
- if primeira_language
25
- linhas.unshift(primeira_language.strip + "\n")
26
- else
27
- idioma = detectar_idioma(linhas.join)
28
- linhas.unshift("# language: #{idioma}\n")
29
- end
30
-
31
- linhas.join
32
- end
33
-
34
- def self.detectar_idioma(texto)
35
- return 'pt' if texto =~ /Dado|Quando|Então|E /i
36
- return 'en' if texto =~ /Given|When|Then|And /i
37
- 'pt' # padrão
38
- end
39
-
40
- def self.corrigir_indentacao(texto)
41
- linhas = texto.lines.map do |linha|
42
- if linha.strip.start_with?('Feature', 'Funcionalidade')
43
- linha.strip + "\n"
44
- elsif linha.strip.start_with?('Scenario', 'Cenário', 'Scenario Outline', 'Esquema do Cenário')
45
- " #{linha.strip}\n"
46
- elsif linha.strip.start_with?('Given', 'When', 'Then', 'And', 'Dado', 'Quando', 'Então', 'E')
47
- " #{linha.strip}\n"
48
- elsif linha.strip.start_with?('|')
49
- " #{linha.strip}\n"
50
- else
51
- " #{linha.strip}\n"
52
- end
53
- end
54
- linhas.join
55
- end
56
- end
57
- end
@@ -1,44 +0,0 @@
1
- require 'set'
2
- require 'unicode'
3
-
4
- module Bddgenx
5
- module Utils
6
- class StepCleaner
7
- def self.remover_steps_duplicados(texto, idioma)
8
- keywords = idioma == 'en' ? %w[Given When Then And] : %w[Dado Quando Então E]
9
- seen = Set.new
10
- resultado = []
11
-
12
- texto.each_line do |linha|
13
- if keywords.any? { |kw| linha.strip.start_with?(kw) }
14
- canonical = canonicalize_step(linha, keywords)
15
- unless seen.include?(canonical)
16
- seen.add(canonical)
17
- resultado << linha
18
- end
19
- else
20
- resultado << linha
21
- end
22
- end
23
-
24
- resultado.join
25
- end
26
-
27
- def self.canonicalize_step(linha, keywords)
28
- # Remove keyword
29
- texto = linha.dup.strip
30
- keywords.each do |kw|
31
- texto.sub!(/^#{kw}\s+/i, '')
32
- end
33
-
34
- # Generaliza: substitui textos entre aspas, colchetes e números por <param>
35
- texto.gsub!(/"[^"]*"|<[^>]*>|\b\d+\b/, '<param>')
36
-
37
- # Remove acentos e pontuação, normaliza espaços
38
- texto = Unicode.normalize_KD(texto).gsub(/\p{Mn}/, '')
39
- texto.gsub!(/[^a-zA-Z0-9\s<>]/, '')
40
- texto.downcase.strip.squeeze(" ")
41
- end
42
- end
43
- end
44
- end
@@ -1,11 +0,0 @@
1
- module Bddgenx
2
- # Sobe 3 níveis: lib/bddgenx/version.rb → bddgenx → lib → [raiz do projeto]
3
- VERSION_FILE = File.expand_path("../../../VERSION", __FILE__)
4
-
5
- VERSION = if File.exist?(VERSION_FILE)
6
- File.read(VERSION_FILE).strip
7
- else
8
- warn "WARNING: VERSION file not found, defaulting to 0.0.0"
9
- "0.0.0"
10
- end
11
- end