foobar_templates 2.0.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.beader/.gitignore +6 -0
  3. data/.beader/issues/1-support-flexible-template-topologies.yaml +21 -0
  4. data/.beader/issues/2-add-monorepo-support-for-template-discovery-and-generation.yaml +33 -0
  5. data/.beader/issues/3-implement-monorepo-aware-recursive-template-inventory.yaml +29 -0
  6. data/.beader/issues/4-add-leaf-template-selection-with-ambiguity-and-not-found-handling.yaml +30 -0
  7. data/.beader/issues/5-integrate-leaf-template-resolution-into-generation-flow.yaml +30 -0
  8. data/.beader/issues/6-add-monorepo-discovery-and-generation-test-coverage.yaml +31 -0
  9. data/.beader/issues/7-document-monorepo-template-configuration-and-selection.yaml +30 -0
  10. data/.beader/meta.yaml +1 -0
  11. data/.gitignore +16 -0
  12. data/.rspec +2 -0
  13. data/Gemfile +3 -0
  14. data/LICENSE.txt +22 -0
  15. data/README.md +154 -0
  16. data/Rakefile +55 -0
  17. data/bin/foobar_templates +72 -0
  18. data/changelog +107 -0
  19. data/config/config +8 -0
  20. data/foobar_templates.gemspec +33 -0
  21. data/lib/foobar_templates/cli/cheat_sheet.rb +12 -0
  22. data/lib/foobar_templates/cli/cli.rb +3 -0
  23. data/lib/foobar_templates/cli/dir_to_template.rb +120 -0
  24. data/lib/foobar_templates/cli/setup_personal_templates_repo.rb +135 -0
  25. data/lib/foobar_templates/cli/template_generator.rb +462 -0
  26. data/lib/foobar_templates/configurator.rb +99 -0
  27. data/lib/foobar_templates/core/core.rb +9 -0
  28. data/lib/foobar_templates/core/dir_to_template.rb +114 -0
  29. data/lib/foobar_templates/strings.rb +23 -0
  30. data/lib/foobar_templates/template_manager.rb +119 -0
  31. data/lib/foobar_templates/templates/template-test/.vscode/launch.json +0 -0
  32. data/lib/foobar_templates/templates/template-test/foo-bar/keep +0 -0
  33. data/lib/foobar_templates/templates/template-test/foo-bar.rb +16 -0
  34. data/lib/foobar_templates/templates/template-test/foo_bar/keep +0 -0
  35. data/lib/foobar_templates/templates/template-test/foobar.yml +2 -0
  36. data/lib/foobar_templates/templates/template-test/simple_dir/keep +0 -0
  37. data/lib/foobar_templates/templates/template-test/test_confirmed +0 -0
  38. data/lib/foobar_templates/templates/test_template/foo-bar/keep +0 -0
  39. data/lib/foobar_templates/templates/test_template/foo-bar.rb +21 -0
  40. data/lib/foobar_templates/templates/test_template/foo_bar/keep +0 -0
  41. data/lib/foobar_templates/templates/test_template/foobar.yml +2 -0
  42. data/lib/foobar_templates/templates/test_template/simple_dir/keep +0 -0
  43. data/lib/foobar_templates/version.rb +3 -0
  44. data/lib/foobar_templates.rb +151 -0
  45. data/spec/data/variable_manifest_test.rb +21 -0
  46. data/spec/foobar_templates/cli/dir_to_template_spec.rb +153 -0
  47. data/spec/foobar_templates/core/dir_to_template_spec.rb +104 -0
  48. data/spec/foobar_templates_spec.rb +573 -0
  49. data/spec/spec_helper.rb +106 -0
  50. data/spec/template_manager_spec.rb +68 -0
  51. metadata +157 -0
data/config/config ADDED
@@ -0,0 +1,8 @@
1
+ # Comments made to this file will not be preserved
2
+ ---
3
+ default_template: cli_gem
4
+ public_templates: https://github.com/TheNotary/template-builtins
5
+ registry_domain: null
6
+ repo_domain: null
7
+ k8s_domain: null
8
+ always_perform_git_init: false
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'foobar_templates/version'
5
+
6
+ version = FoobarTemplates::VERSION
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = "foobar_templates"
10
+ s.version = version
11
+ s.authors = ["TheNotary"]
12
+ s.email = ["no@mail.plz"]
13
+ s.summary = %q{This gem makes more gems!}
14
+ s.description = %q{ This is a gem for making more gems! I know! It's like asking a genie for more wishes but it actually works!}
15
+ s.homepage = "https://github.com/thenotary/foobar_templates"
16
+ s.license = "MIT"
17
+ s.required_ruby_version = '>= 3.0'
18
+
19
+ s.metadata["bug_tracker_uri"] = "https://github.com/TheNotary/foobar_templates/issues"
20
+ s.metadata["changelog_uri"] = "https://github.com/TheNotary/foobar_templates/releases/tag/v#{version}"
21
+ s.metadata["documentation_uri"] = "https://github.com/TheNotary/foobar_templates"
22
+ s.metadata["source_code_uri"] = "https://github.com/TheNotary/foobar_templates/tree/v#{version}"
23
+
24
+ s.files = `git ls-files -z`.split("\x0")
25
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
26
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
27
+ s.require_paths = ["lib"]
28
+
29
+ s.add_development_dependency "rake", "~> 13.2"
30
+ s.add_development_dependency "rdoc", "~> 7.0"
31
+ s.add_development_dependency "rspec", "~> 3.13"
32
+ s.add_development_dependency "pry", "~> 0.16"
33
+ end
@@ -0,0 +1,12 @@
1
+ module FoobarTemplates::CLI
2
+ module CheatSheet
3
+ class << self
4
+
5
+ def go
6
+ gem_root = File.expand_path("../../../..", __FILE__)
7
+ File.read("#{gem_root}/spec/data/variable_manifest_test.rb").lines[3..-1].join
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ require 'foobar_templates/cli/dir_to_template'
2
+ require 'foobar_templates/cli/cheat_sheet'
3
+ require 'foobar_templates/cli/setup_personal_templates_repo'
@@ -0,0 +1,120 @@
1
+ require 'find'
2
+ require 'fileutils'
3
+ require 'open3'
4
+ require 'shellwords'
5
+
6
+
7
+ module FoobarTemplates::CLI
8
+ module DirToTemplate
9
+ NO_PERSONAL_REPO_MSG = "Unable to convert to template, no personal templates repo exists. Run `foobar_templates --setup-personal-templates` to get that setup.".freeze
10
+
11
+ class << self
12
+
13
+ def go(input: $stdin, output: $stdout)
14
+ personal_dir = FoobarTemplates::CLI::SetupPersonalTemplatesRepo.personal_templates_dir
15
+ if personal_dir.nil? || !File.directory?(personal_dir)
16
+ output.puts NO_PERSONAL_REPO_MSG
17
+ raise FoobarTemplates::CLIError
18
+ end
19
+
20
+ validate_inside_git_repo!(output: output)
21
+
22
+ project_name = File.basename(Dir.pwd)
23
+ default_category = read_existing_category(Dir.pwd) || "misc"
24
+
25
+ template_folder_name = prompt(input, output, "Template folder name", project_name)
26
+ validate_folder_name!(template_folder_name, output: output)
27
+
28
+ category = prompt(input, output, "Category", default_category)
29
+
30
+ dest = File.join(personal_dir, template_folder_name)
31
+
32
+ if File.exist?(dest)
33
+ output.puts "Error: a template already exists at #{dest}. Remove it or rename your project before re-running."
34
+ raise FoobarTemplates::CLIError
35
+ end
36
+
37
+ copy_tracked_files_to(dest)
38
+ write_foobar_yml(dest, category)
39
+
40
+ files_changed = Dir.chdir(dest) do
41
+ FoobarTemplates::Core::DirToTemplate.🧙🪄! Find.find('.'), template_name: project_name
42
+ end
43
+
44
+ output.puts "Template created at: #{dest}"
45
+ output.puts "Tip: review the new template directory and remove any files that aren't helpful as a starting point (build artifacts, secrets, lockfiles, large fixtures, etc.)."
46
+ files_changed
47
+ end
48
+
49
+ private
50
+
51
+ def validate_inside_git_repo!(output: $stdout)
52
+ _stdout, _stderr, status = Open3.capture3("git rev-parse --is-inside-work-tree")
53
+ return if status.success?
54
+ output.puts "Error: --copy-to-templates must be run from within a git repository (it uses `git ls-files` to choose what to copy)."
55
+ raise FoobarTemplates::CLIError
56
+ end
57
+
58
+ def copy_tracked_files_to(dest)
59
+ # -c: cached/tracked, -o: untracked, --exclude-standard: respect .gitignore
60
+ # This naturally excludes .git/ and gitignored files while keeping .gitignore.
61
+ listing, _stderr, status = Open3.capture3("git ls-files -co --exclude-standard -z")
62
+ if !status.success?
63
+ $stderr.puts "Error: failed to enumerate files via `git ls-files`."
64
+ raise FoobarTemplates::CLIError
65
+ end
66
+
67
+ FileUtils.mkdir_p(dest)
68
+ listing.split("\0").each do |rel_path|
69
+ next if rel_path.empty?
70
+ src = rel_path
71
+ next unless File.file?(src) # skip submodule pointers, deleted files, etc.
72
+ target = File.join(dest, rel_path)
73
+ FileUtils.mkdir_p(File.dirname(target))
74
+ FileUtils.cp(src, target)
75
+ end
76
+ end
77
+
78
+ def ensure_foobar_yml(dest)
79
+ path = File.join(dest, "foobar.yml")
80
+ File.write(path, "category: misc\n") unless File.exist?(path)
81
+ end
82
+
83
+ def write_foobar_yml(dest, category)
84
+ path = File.join(dest, "foobar.yml")
85
+ if File.exist?(path)
86
+ contents = File.read(path)
87
+ if contents =~ /^category:.*$/
88
+ contents = contents.sub(/^category:.*$/, "category: #{category}")
89
+ else
90
+ contents = "category: #{category}\n" + contents
91
+ end
92
+ File.write(path, contents)
93
+ else
94
+ File.write(path, "category: #{category}\n")
95
+ end
96
+ end
97
+
98
+ def read_existing_category(dir)
99
+ path = File.join(dir, "foobar.yml")
100
+ return nil unless File.exist?(path)
101
+ m = File.read(path).match(/^category:\s*(.+?)\s*$/)
102
+ m && m[1].empty? ? nil : (m && m[1])
103
+ end
104
+
105
+ def prompt(input, output, message, default)
106
+ output.print "#{message} [#{default}]: "
107
+ answer = (input.gets || "").strip
108
+ answer.empty? ? default : answer
109
+ end
110
+
111
+ def validate_folder_name!(name, output: $stdout)
112
+ if name.nil? || name.empty? || name == "." || name == ".." || name.include?("/") || name.include?("\\")
113
+ output.puts "Error: invalid template folder name: #{name.inspect}"
114
+ raise FoobarTemplates::CLIError
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,135 @@
1
+ module FoobarTemplates::CLI
2
+ module SetupPersonalTemplatesRepo
3
+ class << self
4
+
5
+ def go(input: $stdin, output: $stdout)
6
+ FoobarTemplates::validate_git_is_installed!
7
+ configurator = FoobarTemplates::Configurator.new
8
+
9
+ repo_domain = configurator.domain(:repo_domain)
10
+ if repo_domain.nil? || repo_domain.to_s.strip.empty?
11
+ output.puts "Error: `repo_domain` is not set in #{ENV['HOME']}/.foobar/config."
12
+ output.puts "Please set it (e.g. `repo_domain: github.com`) and re-run."
13
+ return
14
+ end
15
+
16
+ github_name = personal_templates_github_name(input: input, output: output)
17
+ return if github_name.nil? || github_name.empty?
18
+
19
+ local_dir = "#{ENV['HOME']}/.foobar/templates/templates-#{github_name}"
20
+ https_url = "https://#{repo_domain}/#{github_name}/templates-#{github_name}"
21
+ ssh_url = "git@#{repo_domain}:#{github_name}/templates-#{github_name}.git"
22
+
23
+ if File.exist?(local_dir)
24
+ output.puts "The template directory already exists, #{local_dir}"
25
+ return
26
+ end
27
+
28
+ if remote_repo_exists?(https_url)
29
+ output.print "You already have a templates directory available at #{https_url}, clone it down? [Y/n] "
30
+ answer = (input.gets || "").strip.downcase
31
+ if answer == "" || answer == "y" || answer == "yes"
32
+ clone_personal_templates(local_dir, ssh_url, https_url, output: output)
33
+ else
34
+ output.puts "Aborted. No local directory was created."
35
+ end
36
+ else
37
+ init_personal_templates_dir(local_dir, github_name, ssh_url, output: output)
38
+ end
39
+ end
40
+
41
+ # Non-interactive resolver: returns the personal templates dir path based on
42
+ # `git config --global user.name`, or nil if that is not set. Does not prompt.
43
+ def personal_templates_dir
44
+ name = `git config --global user.name`.to_s.strip.downcase
45
+ return nil if name.empty?
46
+ "#{ENV['HOME']}/.foobar/templates/templates-#{name}"
47
+ end
48
+
49
+ def personal_templates_github_name(input: $stdin, output: $stdout)
50
+ name = `git config --global user.name`.to_s.strip
51
+ return name unless name.empty?
52
+
53
+ output.print "Enter your GitHub user name: "
54
+ answer = (input.gets || "").strip
55
+ if answer.empty?
56
+ output.puts "Error: GitHub user name is required."
57
+ return nil
58
+ end
59
+ answer
60
+ end
61
+
62
+ def remote_repo_exists?(url)
63
+ cmd = %(curl -o /dev/null -s -w "%{http_code}" -L #{url})
64
+ status = `#{cmd}`.to_s.strip
65
+ return false if status == "404"
66
+ # treat curl failure (empty status / non-numeric) as "exists" to avoid clobbering
67
+ true
68
+ end
69
+
70
+ def clone_personal_templates(local_dir, ssh_url, https_url, output: $stdout)
71
+ redirect = $test_env ? " 2> /dev/null" : ""
72
+ ssh_cmd = "git clone #{ssh_url} #{local_dir}#{redirect}"
73
+ `#{ssh_cmd}`
74
+ if $?.success?
75
+ output.puts "Cloned #{ssh_url} into #{local_dir}"
76
+ return
77
+ end
78
+
79
+ https_cmd = "git clone #{https_url} #{local_dir}#{redirect}"
80
+ `#{https_cmd}`
81
+ if $?.success?
82
+ output.puts "Cloned #{https_url} into #{local_dir}"
83
+ else
84
+ output.puts "Error: failed to clone from #{ssh_url} or #{https_url}."
85
+ end
86
+ end
87
+
88
+ def init_personal_templates_dir(local_dir, github_name, ssh_url, output: $stdout)
89
+ FileUtils.mkdir_p(local_dir)
90
+
91
+ File.write("#{local_dir}/foobar.yml", "monorepo: true\n")
92
+ File.write("#{local_dir}/README.md", personal_templates_readme(github_name))
93
+
94
+ redirect = $test_env ? " > /dev/null 2>&1" : ""
95
+ `cd #{local_dir} && git init#{redirect}`
96
+
97
+ output.puts "Created personal templates mono-repo at #{local_dir}"
98
+ output.puts "Next step: push it to your remote with:"
99
+ output.puts " cd #{local_dir} && git remote add origin #{ssh_url}"
100
+ end
101
+
102
+ def personal_templates_readme(github_name)
103
+ <<~MARKDOWN
104
+ # templates-#{github_name}
105
+
106
+ This is a personal mono-repo of [foobar_templates](https://github.com/thenotary/foobar_templates)
107
+ templates. It was scaffolded by `foobar_templates --setup-personal-templates`.
108
+
109
+ ## Structure
110
+
111
+ Each immediate child directory is a template. A child becomes a template when
112
+ it contains its own `foobar.yml`.
113
+
114
+ ```
115
+ templates-#{github_name}/
116
+ foobar.yml # monorepo: true
117
+ README.md
118
+ my-widget-template/
119
+ foobar.yml # category: frontend
120
+ foo-bar.rb
121
+ masm-project/
122
+ foobar.yml # category: low_level
123
+ main.asm
124
+ ```
125
+
126
+ Run `foobar_templates --list` to see all templates discovered here, and
127
+ `foobar_templates --template <name> my-project` to scaffold a new project from one.
128
+
129
+ See https://github.com/thenotary/foobar_templates for full documentation.
130
+ MARKDOWN
131
+ end
132
+
133
+ end
134
+ end
135
+ end