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.
- checksums.yaml +7 -0
- data/.beader/.gitignore +6 -0
- data/.beader/issues/1-support-flexible-template-topologies.yaml +21 -0
- data/.beader/issues/2-add-monorepo-support-for-template-discovery-and-generation.yaml +33 -0
- data/.beader/issues/3-implement-monorepo-aware-recursive-template-inventory.yaml +29 -0
- data/.beader/issues/4-add-leaf-template-selection-with-ambiguity-and-not-found-handling.yaml +30 -0
- data/.beader/issues/5-integrate-leaf-template-resolution-into-generation-flow.yaml +30 -0
- data/.beader/issues/6-add-monorepo-discovery-and-generation-test-coverage.yaml +31 -0
- data/.beader/issues/7-document-monorepo-template-configuration-and-selection.yaml +30 -0
- data/.beader/meta.yaml +1 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +55 -0
- data/bin/foobar_templates +72 -0
- data/changelog +107 -0
- data/config/config +8 -0
- data/foobar_templates.gemspec +33 -0
- data/lib/foobar_templates/cli/cheat_sheet.rb +12 -0
- data/lib/foobar_templates/cli/cli.rb +3 -0
- data/lib/foobar_templates/cli/dir_to_template.rb +120 -0
- data/lib/foobar_templates/cli/setup_personal_templates_repo.rb +135 -0
- data/lib/foobar_templates/cli/template_generator.rb +462 -0
- data/lib/foobar_templates/configurator.rb +99 -0
- data/lib/foobar_templates/core/core.rb +9 -0
- data/lib/foobar_templates/core/dir_to_template.rb +114 -0
- data/lib/foobar_templates/strings.rb +23 -0
- data/lib/foobar_templates/template_manager.rb +119 -0
- data/lib/foobar_templates/templates/template-test/.vscode/launch.json +0 -0
- data/lib/foobar_templates/templates/template-test/foo-bar/keep +0 -0
- data/lib/foobar_templates/templates/template-test/foo-bar.rb +16 -0
- data/lib/foobar_templates/templates/template-test/foo_bar/keep +0 -0
- data/lib/foobar_templates/templates/template-test/foobar.yml +2 -0
- data/lib/foobar_templates/templates/template-test/simple_dir/keep +0 -0
- data/lib/foobar_templates/templates/template-test/test_confirmed +0 -0
- data/lib/foobar_templates/templates/test_template/foo-bar/keep +0 -0
- data/lib/foobar_templates/templates/test_template/foo-bar.rb +21 -0
- data/lib/foobar_templates/templates/test_template/foo_bar/keep +0 -0
- data/lib/foobar_templates/templates/test_template/foobar.yml +2 -0
- data/lib/foobar_templates/templates/test_template/simple_dir/keep +0 -0
- data/lib/foobar_templates/version.rb +3 -0
- data/lib/foobar_templates.rb +151 -0
- data/spec/data/variable_manifest_test.rb +21 -0
- data/spec/foobar_templates/cli/dir_to_template_spec.rb +153 -0
- data/spec/foobar_templates/core/dir_to_template_spec.rb +104 -0
- data/spec/foobar_templates_spec.rb +573 -0
- data/spec/spec_helper.rb +106 -0
- data/spec/template_manager_spec.rb +68 -0
- metadata +157 -0
data/config/config
ADDED
|
@@ -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,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
|