Skip to content

Commit f2e70fd

Browse files
committed
Improve generator robustness and file transformation
Enhance template copying to prevent infinite recursion and improve file detection. Consolidate text transformations into a unified system for better maintainability.
1 parent 00d7a1a commit f2e70fd

File tree

1 file changed

+115
-106
lines changed

1 file changed

+115
-106
lines changed

lib/gemplate/generator.rb

Lines changed: 115 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,21 @@ def create
2020
private
2121

2222
def copy_template_structure
23+
# Get list of all files/directories BEFORE creating the target directory
24+
# This prevents infinite recursion when the target gets created
25+
source_items = Dir.glob("#{@source_root}/*", File::FNM_DOTMATCH).map do |path|
26+
filename = File.basename(path)
27+
next if skip_file?(filename)
28+
{ source: path, filename: filename }
29+
end.compact
30+
31+
# Now create the target directory
2332
FileUtils.mkdir_p(@gem_name)
2433

25-
# Copy all files except those we want to exclude
26-
Dir.glob("#{@source_root}/*", File::FNM_DOTMATCH).each do |source_path|
27-
filename = File.basename(source_path)
28-
29-
# Skip files we don't want to copy
30-
next if skip_file?(filename)
31-
34+
# Copy the pre-determined list of files
35+
source_items.each do |item|
36+
source_path = item[:source]
37+
filename = item[:filename]
3238
dest_path = File.join(@gem_name, filename)
3339

3440
if File.directory?(source_path)
@@ -47,15 +53,27 @@ def skip_file?(filename)
4753
'..'
4854
]
4955

50-
# Skip any directory that looks like a generated gem (has a .gemspec file)
51-
if File.directory?(File.join(@source_root, filename))
52-
gemspec_pattern = File.join(@source_root, filename, "*.gemspec")
53-
if Dir.glob(gemspec_pattern).any?
56+
return true if excluded_files.include?(filename)
57+
58+
# Skip any directory that looks like a generated gem
59+
file_path = File.join(@source_root, filename)
60+
if File.directory?(file_path)
61+
# Skip if it has a gemspec file that's not the main gemplate.gemspec
62+
gemspec_files = Dir.glob(File.join(file_path, "*.gemspec"))
63+
if gemspec_files.any? && !gemspec_files.include?(File.join(file_path, "gemplate.gemspec"))
64+
return true
65+
end
66+
67+
# Skip if it looks like a previously generated gem directory
68+
if filename != 'lib' && filename != 'spec' &&
69+
(File.exist?(File.join(file_path, "#{filename}.gemspec")) ||
70+
File.exist?(File.join(file_path, "lib", filename)) ||
71+
File.exist?(File.join(file_path, "lib", "#{filename}.rb")))
5472
return true
5573
end
5674
end
5775

58-
excluded_files.include?(filename)
76+
false
5977
end
6078

6179
def copy_directory(source_dir, dest_dir)
@@ -116,24 +134,14 @@ def transform_file_contents
116134
# Clean up files that shouldn't be in generated gems
117135
cleanup_unwanted_files
118136

119-
# Transform gemspec
120-
transform_gemspec
121-
122-
# Transform main lib file
123-
transform_main_lib_file
137+
# Remove CLI-specific files
138+
remove_cli_files
124139

125-
# Transform version file
126-
transform_version_file
140+
# Apply global transformations to all text files
141+
transform_all_files
127142

128-
# Transform spec files
129-
transform_spec_helper
130-
transform_spec_file
131-
132-
# Transform README
133-
transform_readme
134-
135-
# Transform LICENSE
136-
transform_license
143+
# Create new README since the original is gemplate-specific
144+
create_generic_readme
137145
end
138146

139147
def cleanup_unwanted_files
@@ -158,86 +166,97 @@ def cleanup_unwanted_files
158166
end
159167
end
160168
end
161-
162-
def transform_gemspec
163-
gemspec_file = File.join(@gem_name, "#{@gem_name}.gemspec")
164-
return unless File.exist?(gemspec_file)
165-
166-
content = File.read(gemspec_file)
167-
content.gsub!(/require_relative 'lib\/gemplate\/version'/, "require_relative 'lib/#{@snake_name}/version'")
168-
content.gsub!(/spec\.name\s*=\s*'gemplate'/, "spec.name = '#{@gem_name}'")
169-
content.gsub!(/Gemplate::VERSION/, "#{@module_name}::VERSION")
170-
content.gsub!(/spec\.summary\s*=.*/, "spec.summary = 'Write a short summary for your gem'")
171-
content.gsub!(/spec\.description\s*=.*/, "spec.description = 'Write a longer description for your gem'")
172-
content.gsub!(/spec\.homepage\s*=.*/, "spec.homepage = \"https://github.com/yourusername/#{@gem_name}\"")
173-
content.gsub!(/spec\.authors\s*=.*/, "spec.authors = ['Your Name']")
174-
content.gsub!(/spec\.email\s*=.*/, "spec.email = ['your.email@example.com']")
175-
content.gsub!(/spec\.executables\s*=.*/, "spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }")
176-
177-
File.write(gemspec_file, content)
178-
end
179-
180-
def transform_main_lib_file
181-
lib_file = File.join(@gem_name, 'lib', "#{@snake_name}.rb")
182-
return unless File.exist?(lib_file)
183-
184-
content = File.read(lib_file)
185-
content.gsub!(/require_relative 'gemplate\//, "require_relative '#{@snake_name}/")
186-
content.gsub!(/module Gemplate/, "module #{@module_name}")
187-
188-
# Remove CLI-specific requires since this won't be a CLI gem
189-
content.gsub!(/require_relative '#{@snake_name}\/cli'\n/, "")
190-
content.gsub!(/require_relative '#{@snake_name}\/generator'\n/, "")
191-
192-
# Remove CLI-specific files that shouldn't be in generated gems
193-
cli_file = File.join(@gem_name, 'lib', @snake_name, 'cli.rb')
194-
generator_file = File.join(@gem_name, 'lib', @snake_name, 'generator.rb')
195-
196-
File.delete(cli_file) if File.exist?(cli_file)
197-
File.delete(generator_file) if File.exist?(generator_file)
169+
170+
def remove_cli_files
171+
cli_files = [
172+
File.join(@gem_name, 'lib', @snake_name, 'cli.rb'),
173+
File.join(@gem_name, 'lib', @snake_name, 'generator.rb')
174+
]
198175

199-
File.write(lib_file, content)
176+
cli_files.each { |file| File.delete(file) if File.exist?(file) }
200177
end
201178

202-
def transform_version_file
203-
version_file = File.join(@gem_name, 'lib', @snake_name, 'version.rb')
204-
return unless File.exist?(version_file)
205-
206-
content = File.read(version_file)
207-
content.gsub!(/module Gemplate/, "module #{@module_name}")
179+
def transform_all_files
180+
# Find all text files to transform
181+
text_files = Dir.glob("#{@gem_name}/**/*", File::FNM_DOTMATCH).select do |file|
182+
File.file?(file) && text_file?(file)
183+
end
208184

209-
File.write(version_file, content)
185+
text_files.each do |file_path|
186+
content = File.read(file_path)
187+
188+
# Apply all transformations
189+
content = apply_transformations(content)
190+
191+
File.write(file_path, content)
192+
end
210193
end
211-
212-
def transform_spec_helper
213-
spec_helper = File.join(@gem_name, 'spec', 'spec_helper.rb')
214-
return unless File.exist?(spec_helper)
215-
216-
content = File.read(spec_helper)
217-
content.gsub!(/require 'gemplate'/, "require '#{@snake_name}'")
218-
219-
File.write(spec_helper, content)
194+
195+
def text_file?(file_path)
196+
# Skip some specific binary file extensions
197+
return false if file_path.end_with?('.png', '.jpg', '.jpeg', '.gif', '.ico', '.zip', '.tar', '.gz')
198+
199+
# Simple check for text files - read first few bytes
200+
begin
201+
File.open(file_path, 'rb') do |file|
202+
chunk = file.read(512)
203+
return false if chunk.nil?
204+
# If it contains null bytes, it's likely binary
205+
return false if chunk.include?("\x00")
206+
end
207+
true
208+
rescue
209+
false
210+
end
220211
end
221-
222-
def transform_spec_file
223-
spec_file = File.join(@gem_name, 'spec', @snake_name, "#{@snake_name}_spec.rb")
224-
return unless File.exist?(spec_file)
212+
213+
def apply_transformations(content)
214+
# Global find and replace patterns
215+
transformations = {
216+
# Module and class names
217+
'Gemplate' => @module_name,
218+
219+
# File paths and requires
220+
"require_relative 'lib/gemplate/" => "require_relative 'lib/#{@snake_name}/",
221+
"require_relative 'gemplate/" => "require_relative '#{@snake_name}/",
222+
"require 'gemplate'" => "require '#{@snake_name}'",
223+
224+
# Gem specification
225+
"spec.name = 'gemplate'" => "spec.name = '#{@gem_name}'",
226+
"'gemplate'" => "'#{@gem_name}'",
227+
'"gemplate"' => "\"#{@gem_name}\"",
228+
229+
# URLs and references
230+
'TwilightCoders/gemplate' => "yourusername/#{@gem_name}",
231+
232+
# Generic placeholders for new gems
233+
/spec\.summary\s*=.*/ => "spec.summary = 'Write a short summary for your gem'",
234+
/spec\.description\s*=.*/ => "spec.description = 'Write a longer description for your gem'",
235+
/spec\.authors\s*=.*/ => "spec.authors = ['Your Name']",
236+
/spec\.email\s*=.*/ => "spec.email = ['your.email@example.com']",
237+
/spec\.executables\s*=.*/ => "spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }",
238+
/Copyright \(c\) \d+ .+/ => "Copyright (c) #{Time.now.year} Your Name"
239+
}
240+
241+
# Remove CLI-specific requires
242+
content.gsub!(/require_relative '#{@snake_name}\/cli'\n/, "")
243+
content.gsub!(/require_relative '#{@snake_name}\/generator'\n/, "")
225244

226-
content = File.read(spec_file)
227-
content.gsub!(/describe Gemplate/, "describe #{@module_name}")
228-
content.gsub!(/Gemplate::VERSION/, "#{@module_name}::VERSION")
229-
content.gsub!(/Gemplate\.root/, "#{@module_name}.root")
245+
# Apply all transformations
246+
transformations.each do |pattern, replacement|
247+
if pattern.is_a?(Regexp)
248+
content.gsub!(pattern, replacement)
249+
else
250+
content.gsub!(pattern, replacement)
251+
end
252+
end
230253

231-
File.write(spec_file, content)
254+
content
232255
end
233256

234-
def transform_readme
257+
def create_generic_readme
235258
readme_file = File.join(@gem_name, 'README.md')
236-
return unless File.exist?(readme_file)
237-
238-
content = File.read(readme_file)
239259

240-
# Replace the entire README with a generic template
241260
new_content = <<~README
242261
# #{@module_name}
243262
@@ -281,16 +300,6 @@ def transform_readme
281300
File.write(readme_file, new_content)
282301
end
283302

284-
def transform_license
285-
license_file = File.join(@gem_name, 'LICENSE.txt')
286-
return unless File.exist?(license_file)
287-
288-
content = File.read(license_file)
289-
content.gsub!(/Copyright \(c\) \d+ .+/, "Copyright (c) #{Time.now.year} Your Name")
290-
291-
File.write(license_file, content)
292-
end
293-
294303
def show_created_files
295304
Dir.glob("#{@gem_name}/**/*", File::FNM_DOTMATCH).sort.each do |file|
296305
next if File.directory?(file)

0 commit comments

Comments
 (0)