bddgenx 1.0.0 → 2.1.0
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/README.md +146 -28
- data/Rakefile +33 -39
- data/VERSION +1 -1
- data/bin/bddgenx +1 -1
- data/lib/bddgenx/configuration.rb +35 -0
- data/lib/bddgenx/{generator.rb → generators/generator.rb} +42 -18
- data/lib/bddgenx/{runner.rb → generators/runner.rb} +9 -29
- data/lib/bddgenx/{steps_generator.rb → generators/steps_generator.rb} +27 -5
- data/lib/bddgenx/ia/chatgtp_cliente.rb +147 -0
- data/lib/bddgenx/ia/gemini_cliente.rb +27 -12
- data/lib/bddgenx/{utils → reports}/backup.rb +0 -4
- data/lib/bddgenx/{utils → reports}/pdf_exporter.rb +0 -5
- data/lib/bddgenx/{utils → reports}/tracer.rb +0 -4
- data/lib/bddgenx/{utils → support}/font_loader.rb +0 -4
- data/lib/bddgenx/support/gherkin_cleaner.rb +102 -0
- data/lib/bddgenx/support/remover_steps_duplicados.rb +81 -0
- data/lib/bddgenx/{utils → support}/validator.rb +0 -1
- data/lib/bddgenx/version.rb +4 -1
- data/lib/bddgenx.rb +11 -2
- data/lib/env.rb +51 -0
- data/lib/{bddgenx/utils/parser.rb → parser.rb} +1 -3
- metadata +71 -13
- data/lib/bddgenx/ia/gemini_generator.rb +0 -127
- data/lib/bddgenx/utils/gherkin_cleaner.rb +0 -57
- data/lib/bddgenx/utils/remover_steps_duplicados.rb +0 -44
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
|
4
|
+
version: 2.1.0
|
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,22 @@ 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/
|
129
|
+
- lib/bddgenx/configuration.rb
|
130
|
+
- lib/bddgenx/generators/generator.rb
|
131
|
+
- lib/bddgenx/generators/runner.rb
|
132
|
+
- lib/bddgenx/generators/steps_generator.rb
|
133
|
+
- lib/bddgenx/ia/chatgtp_cliente.rb
|
74
134
|
- lib/bddgenx/ia/gemini_cliente.rb
|
75
|
-
- lib/bddgenx/
|
76
|
-
- lib/bddgenx/
|
77
|
-
- lib/bddgenx/
|
78
|
-
- lib/bddgenx/
|
79
|
-
- lib/bddgenx/
|
80
|
-
- lib/bddgenx/
|
81
|
-
- lib/bddgenx/
|
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
|
135
|
+
- lib/bddgenx/reports/backup.rb
|
136
|
+
- lib/bddgenx/reports/pdf_exporter.rb
|
137
|
+
- lib/bddgenx/reports/tracer.rb
|
138
|
+
- lib/bddgenx/support/font_loader.rb
|
139
|
+
- lib/bddgenx/support/gherkin_cleaner.rb
|
140
|
+
- lib/bddgenx/support/remover_steps_duplicados.rb
|
141
|
+
- lib/bddgenx/support/validator.rb
|
86
142
|
- lib/bddgenx/version.rb
|
143
|
+
- lib/env.rb
|
144
|
+
- lib/parser.rb
|
87
145
|
homepage: https://github.com/David-Nascimento/bdd-generation
|
88
146
|
licenses:
|
89
147
|
- 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
|