foobar_templates 2.0.1.rc1 → 2.0.1.rc2

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: 247c1ea143d5bffcb645aa6ee4ce0793a8962869b100e268c424697d77b8b4c8
4
- data.tar.gz: c6fe7ccc5771db07b24c61d8d640f3516f2b73308d1937334df69069139658cd
3
+ metadata.gz: ef1e2cd0e959fef6c0ac4f5e4475675f8ea8c9b4c60d818ff3606ca94d2638a7
4
+ data.tar.gz: e5d6e2ef6a066ad687a0509eb80d199f7d381307bf4ea2470f8f45f9c6eca274
5
5
  SHA512:
6
- metadata.gz: 78e4608ba4ec94b0a0271f7753ae5b65d79cbbe6dec0a36d42cce040bdbd78353ba872158abce356cb82c0e98e1198eedf28becdc4956d36be0b3750821151db
7
- data.tar.gz: fb918a1c50461ed3bc66e0c3c54fc1f0ba50ae0f0bcd04d381810a7009563fbfaec5142e3c111329c2c1f69b44df31c30b17f447ea6de45c0afa26527cb05d76
6
+ metadata.gz: 59087e7c7d265a35bc574ef126d11ff253b3c4970ce156e1d3edc0bbfb67d73f39f0894498f6594f9eb2e63e1fc7307803fe9e243bfe000fae7bc985d14b3484
7
+ data.tar.gz: cd0ee89ddd0b52b6ab18c222deb7af07a933cc253428861ffd6e276943b9f70225e95ca8a95e4a502a89989358596d372020d75628fe532bfab9c048fe70f336
data/changelog CHANGED
@@ -1,5 +1,6 @@
1
1
  ** Unreleased **
2
2
  - Rename: project renamed from `bundlegem` to `foobar_templates`. Module is now `FoobarTemplates`, executable is `foobar_templates`, per-template config file is `foobar.yml`, and user config/templates live under `~/.foobar/`. No backward-compatibility shims.
3
+ - Bugfix: fixes `Arg list too long` crash when generating from templates containing many gitignored files (e.g. node_modules); ignored directories are now pruned during a breadth-first traversal and path checks are streamed to `git check-ignore` via NUL-delimited stdin instead of argv.
3
4
 
4
5
  ** Planned 0.2.x **
5
6
  - Feature: templates can declare a `name_validation:` block in bundlegem.yml with two optional keys: `reserved_names` (exact-match denylist) and `regex_validator` (a Ruby regex the project name must match). Validation runs after the built-in regex check and before any files are written. All checks happen in pure Ruby — no shell, no cross-platform concerns.
data/config/config CHANGED
@@ -3,6 +3,6 @@
3
3
  default_template: cli_gem
4
4
  public_templates: https://github.com/TheNotary/template-builtins
5
5
  registry_domain: null
6
- repo_domain: null
7
- k8s_domain: null
6
+ repo_domain: null # github.com
7
+ k8s_domain: null # k8s.example.com
8
8
  always_perform_git_init: false
@@ -1,7 +1,6 @@
1
1
  require 'pathname'
2
2
  require 'yaml'
3
3
  require 'open3'
4
- require 'shellwords'
5
4
  require 'set'
6
5
 
7
6
  $TRACE = false
@@ -271,39 +270,67 @@ module FoobarTemplates::CLI
271
270
 
272
271
  # Returns a hash of source directory names and their destination mappings
273
272
  def dynamically_generate_template_directories
274
- template_dirs = Dir.glob("#{@template_src}/**/*", File::FNM_DOTMATCH).filter_map do |f|
275
- base_path = f[@template_src.length+1..-1]
276
- next if base_path.start_with?(".git" + File::SEPARATOR) || base_path == ".git"
277
- next if f == "#{@template_src}/." || f == "#{@template_src}/.."
278
- next unless File.directory?(f)
279
- # next if ignored_by_git?(@template_src, base_path)
280
-
281
- [base_path, substitute_template_values(base_path)]
282
- end.to_h
283
- filter_ignored_files!(@template_src, template_dirs)
273
+ template_relative_paths.each_with_object({}) do |rel, dirs|
274
+ next unless File.directory?(File.join(@template_src, rel))
284
275
 
285
- template_dirs
276
+ dirs[rel] = substitute_template_values(rel)
277
+ end
286
278
  end
287
279
 
288
280
  # Figures out the translation between all template files and their
289
281
  # destination names
290
282
  def dynamically_generate_templates_files
291
- template_files = Dir.glob("#{@template_src}/**/*", File::FNM_DOTMATCH).filter_map do |f|
292
- base_path = f[@template_src.length+1..-1]
293
- next if base_path.nil?
294
- next if base_path.start_with?(".git" + File::SEPARATOR) || base_path == ".git"
295
- next if base_path == "foobar.yml"
296
- next unless File.file?(f)
283
+ template_files = template_relative_paths.each_with_object({}) do |rel, files|
284
+ next if rel == "foobar.yml"
285
+ next unless File.file?(File.join(@template_src, rel))
297
286
 
298
- [base_path, substitute_template_values(base_path)]
299
- end.to_h
287
+ files[rel] = substitute_template_values(rel)
288
+ end
300
289
 
301
290
  raise_no_files_in_template_error! if template_files.empty?
302
- filter_ignored_files!(@template_src, template_files)
303
291
 
304
292
  return template_files
305
293
  end
306
294
 
295
+ # Enumerates every relative path under the template source, skipping the
296
+ # .git directory and any gitignored paths. Ignored directories are pruned
297
+ # during traversal so their (potentially huge) contents are never walked.
298
+ def template_relative_paths
299
+ @template_relative_paths ||= collect_non_ignored_paths(@template_src)
300
+ end
301
+
302
+ # Breadth-first walk that prunes ignored directories. One batched
303
+ # `git check-ignore` call is made per directory depth level, so we never
304
+ # descend into (or enumerate) an ignored subtree such as node_modules.
305
+ def collect_non_ignored_paths(root)
306
+ results = []
307
+ frontier = [nil] # relative dirs to scan at the current level; nil == root
308
+
309
+ until frontier.empty?
310
+ level_children = []
311
+ frontier.each do |rel_dir|
312
+ abs_dir = rel_dir ? File.join(root, rel_dir) : root
313
+ Dir.children(abs_dir).each do |name|
314
+ next if name == ".git"
315
+
316
+ level_children << (rel_dir ? File.join(rel_dir, name) : name)
317
+ end
318
+ end
319
+ break if level_children.empty?
320
+
321
+ ignored = ignored_paths(root, level_children)
322
+ next_frontier = []
323
+ level_children.each do |rel|
324
+ next if ignored.include?(rel)
325
+
326
+ results << rel
327
+ next_frontier << rel if File.directory?(File.join(root, rel))
328
+ end
329
+ frontier = next_frontier
330
+ end
331
+
332
+ results
333
+ end
307
334
 
308
335
  # Applies literal foo-bar variant substitutions to path strings
309
336
  def substitute_template_values(path_str)
@@ -351,12 +378,19 @@ module FoobarTemplates::CLI
351
378
  chunk.nil? || chunk.include?("\x00")
352
379
  end
353
380
 
354
- def filter_ignored_files!(repo_root, path_hash)
355
- cmd = "git -C #{repo_root} check-ignore #{Shellwords.join(path_hash.keys)}"
356
- stdout, _, status = Open3.capture3(cmd)
357
- filter_these_paths = stdout.split
358
-
359
- path_hash.delete_if { |key, _| filter_these_paths.include?(key) }
381
+ # Returns the subset of the given relative paths that git considers ignored.
382
+ # Paths are streamed via NUL-delimited stdin rather than argv to avoid the
383
+ # OS ARG_MAX limit ("Arg list too long") and to handle paths containing
384
+ # spaces or newlines. Returns an empty set when root is not a git repo.
385
+ def ignored_paths(root, rel_paths)
386
+ return Set.new if rel_paths.empty?
387
+
388
+ stdin_data = rel_paths.join("\x00")
389
+ stdout, _, _status = Open3.capture3(
390
+ "git", "-C", root.to_s, "check-ignore", "-z", "--stdin",
391
+ stdin_data: stdin_data
392
+ )
393
+ stdout.split("\x00").to_set
360
394
  end
361
395
 
362
396
  def create_template_directories(template_directories, target)
@@ -1,3 +1,3 @@
1
1
  module FoobarTemplates
2
- VERSION = "2.0.1.rc1"
2
+ VERSION = "2.0.1.rc2"
3
3
  end
@@ -128,6 +128,26 @@ describe FoobarTemplates do
128
128
  expect(src_dst_map['simple_dir']).to eq 'simple_dir'
129
129
  end
130
130
 
131
+ it "collect_non_ignored_paths walks files but prunes gitignored directories" do
132
+ template_dir = create_user_defined_template("testing", "template-user-supplied")
133
+ options = { bin: false, ext: false, coc: false, template: "template-user-supplied" }
134
+ my_gem = FoobarTemplates::CLI::TemplateGenerator.new(options, "good-dog")
135
+
136
+ File.write("#{template_dir}/.gitignore", "node_modules/\n")
137
+ File.write("#{template_dir}/README.md", "Hello")
138
+ FileUtils.mkdir_p("#{template_dir}/src")
139
+ File.write("#{template_dir}/src/main.rb", "puts 1")
140
+ FileUtils.mkdir_p("#{template_dir}/node_modules/big_pkg")
141
+ File.write("#{template_dir}/node_modules/big_pkg/index.js", "ignored")
142
+ `git init #{template_dir}`
143
+
144
+ paths = my_gem.send('collect_non_ignored_paths', template_dir)
145
+
146
+ expect(paths).to include("README.md", "src", "src/main.rb")
147
+ expect(paths).not_to include("node_modules")
148
+ expect(paths.grep(/node_modules/)).to be_empty
149
+ end
150
+
131
151
  it "returns the expected interpolated string when substitute_template_values is called" do
132
152
  options = { bin: false, ext: false, coc: false, template: "test_template" }
133
153
  gem_name = "good-dog"
@@ -173,6 +193,26 @@ describe FoobarTemplates do
173
193
  expect(File).not_to exist "#{@dst_dir}/#{gem_name}/node_modules"
174
194
  end
175
195
 
196
+ it "handles templates with a large number of gitignored files without overflowing the arg list" do
197
+ template_dir = create_user_defined_template("testing", "template-user-supplied")
198
+ options = { bin: false, ext: false, coc: false, template: "template-user-supplied" }
199
+ gem_name = "good-dog"
200
+
201
+ File.write("#{template_dir}/.gitignore", "node_modules/")
202
+ File.write("#{template_dir}/README.md", "Hello")
203
+ FileUtils.mkdir("#{template_dir}/node_modules")
204
+ # Enough files to blow past ARG_MAX if paths were passed as argv.
205
+ 5000.times { |i| File.write("#{template_dir}/node_modules/file_#{i}.rb", "ignored #{i}") }
206
+ `git init #{template_dir}`
207
+
208
+ capture_stdout do
209
+ FoobarTemplates.generate_template(options, gem_name)
210
+ end
211
+
212
+ expect(File).to exist "#{@dst_dir}/#{gem_name}/README.md"
213
+ expect(File).not_to exist "#{@dst_dir}/#{gem_name}/node_modules"
214
+ end
215
+
176
216
  it "executes the bootstrap_command if supplied" do
177
217
  template_dir = create_user_defined_template("testing", "template-user-supplied")
178
218
  options = { bin: false, ext: false, coc: false, template: "template-user-supplied" }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobar_templates
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1.rc1
4
+ version: 2.0.1.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - TheNotary
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake
@@ -130,9 +130,9 @@ licenses:
130
130
  - MIT
131
131
  metadata:
132
132
  bug_tracker_uri: https://github.com/TheNotary/foobar_templates/issues
133
- changelog_uri: https://github.com/TheNotary/foobar_templates/releases/tag/v2.0.1.rc1
133
+ changelog_uri: https://github.com/TheNotary/foobar_templates/releases/tag/v2.0.1.rc2
134
134
  documentation_uri: https://github.com/TheNotary/foobar_templates
135
- source_code_uri: https://github.com/TheNotary/foobar_templates/tree/v2.0.1.rc1
135
+ source_code_uri: https://github.com/TheNotary/foobar_templates/tree/v2.0.1.rc2
136
136
  rdoc_options: []
137
137
  require_paths:
138
138
  - lib
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
147
  - !ruby/object:Gem::Version
148
148
  version: '0'
149
149
  requirements: []
150
- rubygems_version: 3.6.2
150
+ rubygems_version: 4.0.10
151
151
  specification_version: 4
152
152
  summary: This gem makes more gems!
153
153
  test_files: