bunko 0.2.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/.standard.yml +3 -0
- data/CHANGELOG.md +41 -0
- data/CLAUDE.md +351 -0
- data/LICENSE.txt +21 -0
- data/README.md +641 -0
- data/ROADMAP.md +519 -0
- data/Rakefile +10 -0
- data/lib/bunko/configuration.rb +180 -0
- data/lib/bunko/controllers/acts_as.rb +22 -0
- data/lib/bunko/controllers/collection.rb +160 -0
- data/lib/bunko/controllers.rb +5 -0
- data/lib/bunko/models/acts_as.rb +24 -0
- data/lib/bunko/models/post_methods/publishable.rb +51 -0
- data/lib/bunko/models/post_methods/sluggable.rb +47 -0
- data/lib/bunko/models/post_methods/word_countable.rb +76 -0
- data/lib/bunko/models/post_methods.rb +75 -0
- data/lib/bunko/models/post_type_methods.rb +18 -0
- data/lib/bunko/models.rb +6 -0
- data/lib/bunko/railtie.rb +22 -0
- data/lib/bunko/routing/mapper_methods.rb +103 -0
- data/lib/bunko/routing.rb +4 -0
- data/lib/bunko/version.rb +5 -0
- data/lib/bunko.rb +11 -0
- data/lib/tasks/bunko/add.rake +259 -0
- data/lib/tasks/bunko/helpers.rb +25 -0
- data/lib/tasks/bunko/install.rake +125 -0
- data/lib/tasks/bunko/sample_data.rake +128 -0
- data/lib/tasks/bunko/setup.rake +186 -0
- data/lib/tasks/support/sample_data_generator.rb +399 -0
- data/lib/tasks/templates/INSTALL.md +62 -0
- data/lib/tasks/templates/config/initializers/bunko.rb.tt +45 -0
- data/lib/tasks/templates/controllers/controller.rb.tt +25 -0
- data/lib/tasks/templates/controllers/pages_controller.rb.tt +29 -0
- data/lib/tasks/templates/db/migrate/create_post_types.rb.tt +14 -0
- data/lib/tasks/templates/db/migrate/create_posts.rb.tt +31 -0
- data/lib/tasks/templates/models/post.rb.tt +8 -0
- data/lib/tasks/templates/models/post_type.rb.tt +8 -0
- data/lib/tasks/templates/views/collections/index.html.erb.tt +67 -0
- data/lib/tasks/templates/views/collections/show.html.erb.tt +39 -0
- data/lib/tasks/templates/views/layouts/bunko_footer.html.erb.tt +3 -0
- data/lib/tasks/templates/views/layouts/bunko_nav.html.erb.tt +9 -0
- data/lib/tasks/templates/views/layouts/bunko_styles.html.erb.tt +3 -0
- data/lib/tasks/templates/views/pages/show.html.erb.tt +16 -0
- data/sig/bunko.rbs +4 -0
- metadata +116 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "helpers"
|
|
5
|
+
|
|
6
|
+
namespace :bunko do
|
|
7
|
+
include Bunko::RakeHelpers
|
|
8
|
+
|
|
9
|
+
desc "Add a PostType or Collection (automatically detects which)"
|
|
10
|
+
task :add, [:name] => :environment do |t, args|
|
|
11
|
+
unless args[:name]
|
|
12
|
+
puts "⚠️ Please provide a name"
|
|
13
|
+
puts " Usage: rails bunko:add[blog]"
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
name = args[:name]
|
|
18
|
+
format = ENV.fetch("FORMAT", "html").downcase
|
|
19
|
+
|
|
20
|
+
# Validate format
|
|
21
|
+
valid_formats = %w[plain html]
|
|
22
|
+
unless valid_formats.include?(format)
|
|
23
|
+
puts "⚠️ Invalid format: #{format}"
|
|
24
|
+
puts " Valid formats: #{valid_formats.join(", ")}"
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if it's a PostType
|
|
29
|
+
pt_config = Bunko.configuration.post_types.find { |pt| pt[:name] == name }
|
|
30
|
+
|
|
31
|
+
# Check if it's a Collection
|
|
32
|
+
collection_config = Bunko.configuration.collections.find { |c| c[:name] == name }
|
|
33
|
+
|
|
34
|
+
unless pt_config || collection_config
|
|
35
|
+
# Not found in either
|
|
36
|
+
puts "⚠️ '#{name}' not found in configuration"
|
|
37
|
+
puts ""
|
|
38
|
+
|
|
39
|
+
available_post_types = Bunko.configuration.post_types.map { |pt| pt[:name] }
|
|
40
|
+
available_collections = Bunko.configuration.collections.map { |c| c[:name] }
|
|
41
|
+
|
|
42
|
+
if available_post_types.any?
|
|
43
|
+
puts " Available PostTypes: #{available_post_types.join(", ")}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if available_collections.any?
|
|
47
|
+
puts " Available Collections: #{available_collections.join(", ")}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts ""
|
|
51
|
+
puts " Add it to config/initializers/bunko.rb first:"
|
|
52
|
+
puts " config.post_type \"#{name}\""
|
|
53
|
+
puts " # or"
|
|
54
|
+
puts " config.collection \"#{name}\" do |c|"
|
|
55
|
+
puts " c.post_types = [...]"
|
|
56
|
+
puts " end"
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Step 1: If it's a PostType, create DB entry
|
|
61
|
+
if pt_config
|
|
62
|
+
create_post_type_in_database(name, pt_config[:title])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Step 2: Always generate artifacts (for both PostTypes and Collections)
|
|
66
|
+
is_collection = collection_config.present?
|
|
67
|
+
generate_artifacts(name, format: format, is_collection: is_collection)
|
|
68
|
+
|
|
69
|
+
# Step 3: Add to nav
|
|
70
|
+
if pt_config
|
|
71
|
+
add_to_nav(name, title: pt_config[:title])
|
|
72
|
+
else
|
|
73
|
+
add_to_nav(name, title: collection_config[:title])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Success message
|
|
77
|
+
puts ""
|
|
78
|
+
if pt_config
|
|
79
|
+
puts "PostType '#{name}' added successfully!"
|
|
80
|
+
else
|
|
81
|
+
puts "Collection '#{name}' added successfully!"
|
|
82
|
+
end
|
|
83
|
+
puts "Visit: http://localhost:3000/#{name.tr("_", "-")}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Helper methods
|
|
87
|
+
|
|
88
|
+
def create_post_type_in_database(name, title)
|
|
89
|
+
post_type = PostType.find_by(name: name)
|
|
90
|
+
|
|
91
|
+
if post_type
|
|
92
|
+
puts " ✓ PostType already exists: #{title} (#{name})"
|
|
93
|
+
else
|
|
94
|
+
PostType.create!(name: name, title: title)
|
|
95
|
+
puts " ✓ Created PostType: #{title} (#{name})"
|
|
96
|
+
end
|
|
97
|
+
puts ""
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def generate_artifacts(name, format:, is_collection:)
|
|
101
|
+
# Step 1: Generate controller
|
|
102
|
+
puts "Generating controller..."
|
|
103
|
+
generate_controller(name)
|
|
104
|
+
puts ""
|
|
105
|
+
|
|
106
|
+
# Step 2: Generate views
|
|
107
|
+
puts "Generating views..."
|
|
108
|
+
generate_views(name, format: format, is_collection: is_collection)
|
|
109
|
+
puts ""
|
|
110
|
+
|
|
111
|
+
# Step 3: Add route
|
|
112
|
+
puts "Adding route..."
|
|
113
|
+
add_route(name)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def generate_controller(collection_name)
|
|
117
|
+
controller_name = "#{collection_name.camelize}Controller"
|
|
118
|
+
controller_file = Rails.root.join("app/controllers/#{collection_name}_controller.rb")
|
|
119
|
+
|
|
120
|
+
if File.exist?(controller_file)
|
|
121
|
+
puts " - #{collection_name}_controller.rb already exists (skipped)"
|
|
122
|
+
return false
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
controller_content = render_template("controllers/controller.rb.tt", {
|
|
126
|
+
controller_name: controller_name,
|
|
127
|
+
collection_name: collection_name
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
File.write(controller_file, controller_content)
|
|
131
|
+
puts " ✓ Created #{collection_name}_controller.rb"
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def generate_views(collection_name, format:, is_collection:)
|
|
136
|
+
views_dir = Rails.root.join("app/views/#{collection_name}")
|
|
137
|
+
|
|
138
|
+
if Dir.exist?(views_dir) && Dir.glob("#{views_dir}/*").any?
|
|
139
|
+
puts " - #{collection_name} views already exist (skipped)"
|
|
140
|
+
return false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
FileUtils.mkdir_p(views_dir)
|
|
144
|
+
|
|
145
|
+
# Generate index.html.erb
|
|
146
|
+
index_content = generate_index_view(collection_name, is_collection: is_collection)
|
|
147
|
+
File.write(File.join(views_dir, "index.html.erb"), index_content)
|
|
148
|
+
|
|
149
|
+
# Collections only get index view, PostTypes get both index and show
|
|
150
|
+
if is_collection
|
|
151
|
+
puts " ✓ Created views for #{collection_name} (index only - collection)"
|
|
152
|
+
else
|
|
153
|
+
# Generate show.html.erb for PostTypes only
|
|
154
|
+
show_content = generate_show_view(collection_name, format: format)
|
|
155
|
+
File.write(File.join(views_dir, "show.html.erb"), show_content)
|
|
156
|
+
puts " ✓ Created views for #{collection_name} (index, show)"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def generate_index_view(collection_name, is_collection:)
|
|
163
|
+
is_plural = collection_name.pluralize == collection_name
|
|
164
|
+
|
|
165
|
+
render_template("views/collections/index.html.erb.tt", {
|
|
166
|
+
collection_name: collection_name,
|
|
167
|
+
collection_title: collection_name.titleize,
|
|
168
|
+
path_helper: "#{collection_name.singularize}_path",
|
|
169
|
+
index_path_helper: is_plural ? "#{collection_name}_path" : "#{collection_name}_index_path",
|
|
170
|
+
is_collection: is_collection
|
|
171
|
+
})
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def generate_show_view(collection_name, format:)
|
|
175
|
+
is_plural = collection_name.pluralize == collection_name
|
|
176
|
+
|
|
177
|
+
render_template("views/collections/show.html.erb.tt", {
|
|
178
|
+
collection_name: collection_name,
|
|
179
|
+
collection_title: collection_name.titleize,
|
|
180
|
+
index_path_helper: is_plural ? "#{collection_name}_path" : "#{collection_name}_index_path",
|
|
181
|
+
format: format
|
|
182
|
+
})
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def add_route(collection_name)
|
|
186
|
+
routes_file = Rails.root.join("config/routes.rb")
|
|
187
|
+
routes_content = File.read(routes_file)
|
|
188
|
+
|
|
189
|
+
route_line = " bunko_collection :#{collection_name}"
|
|
190
|
+
|
|
191
|
+
if routes_content.include?(route_line.strip)
|
|
192
|
+
puts " - Route for :#{collection_name} already exists (skipped)"
|
|
193
|
+
return false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Find the last 'end' in the file and insert before it
|
|
197
|
+
lines = routes_content.lines
|
|
198
|
+
last_end_index = lines.rindex { |line| line.match?(/^end\s*$/) }
|
|
199
|
+
|
|
200
|
+
if last_end_index
|
|
201
|
+
lines.insert(last_end_index, "#{route_line}\n")
|
|
202
|
+
updated_content = lines.join
|
|
203
|
+
else
|
|
204
|
+
# Fallback: append before the last line if no 'end' found
|
|
205
|
+
updated_content = routes_content.sub(/\z/, "#{route_line}\n")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
File.write(routes_file, updated_content)
|
|
209
|
+
puts " ✓ Added route for :#{collection_name}"
|
|
210
|
+
true
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def add_to_nav(name, title:)
|
|
214
|
+
shared_dir = Rails.root.join("app/views/shared")
|
|
215
|
+
nav_file = shared_dir.join("_bunko_nav.html.erb")
|
|
216
|
+
|
|
217
|
+
# If nav doesn't exist, generate from scratch (edge case)
|
|
218
|
+
unless File.exist?(nav_file)
|
|
219
|
+
FileUtils.mkdir_p(shared_dir)
|
|
220
|
+
nav_content = render_template("views/layouts/bunko_nav.html.erb.tt", {
|
|
221
|
+
post_types: Bunko.configuration.post_types,
|
|
222
|
+
collections: Bunko.configuration.collections
|
|
223
|
+
})
|
|
224
|
+
File.write(nav_file, nav_content)
|
|
225
|
+
puts " ✓ Created shared/_bunko_nav.html.erb"
|
|
226
|
+
return true
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Nav exists - append new link to existing file
|
|
230
|
+
nav_content = File.read(nav_file)
|
|
231
|
+
|
|
232
|
+
# Generate the new link
|
|
233
|
+
is_plural = name.pluralize == name
|
|
234
|
+
path_helper = is_plural ? "#{name}_path" : "#{name}_index_path"
|
|
235
|
+
new_link = " <%= link_to \"#{title}\", #{path_helper} %>\n"
|
|
236
|
+
|
|
237
|
+
# Check if link already exists (check for the title and path, not exact match)
|
|
238
|
+
if nav_content.match?(/link_to\s+"#{Regexp.escape(title)}",\s+#{path_helper}/)
|
|
239
|
+
puts " - #{title} already in nav (skipped)"
|
|
240
|
+
return false
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Try to insert before the marker comment (preferred)
|
|
244
|
+
marker = "<%# bunko_collection_links - additional collections will be added here unless you delete this line %>"
|
|
245
|
+
nav_content = if nav_content.include?(marker)
|
|
246
|
+
nav_content.sub(marker, "#{new_link} #{marker}")
|
|
247
|
+
else
|
|
248
|
+
# Fallback: Find the closing </div> before </nav> and insert the new link before it
|
|
249
|
+
nav_content.sub(/(\s*)<\/div>\s*<\/nav>/) do
|
|
250
|
+
indent = $1
|
|
251
|
+
"#{new_link}#{indent}</div>\n</nav>"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
File.write(nav_file, nav_content)
|
|
256
|
+
puts " ✓ Added #{title} to shared/_bunko_nav.html.erb"
|
|
257
|
+
true
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Bunko
|
|
6
|
+
module RakeHelpers
|
|
7
|
+
def render_template(template_name, locals = {})
|
|
8
|
+
template_path = File.expand_path("../templates/#{template_name}", __dir__)
|
|
9
|
+
|
|
10
|
+
unless File.exist?(template_path)
|
|
11
|
+
raise "Template file not found: #{template_path}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
template_content = File.read(template_path)
|
|
15
|
+
|
|
16
|
+
# Create a context object with all local variables as methods
|
|
17
|
+
context = Object.new
|
|
18
|
+
locals.each do |key, value|
|
|
19
|
+
context.define_singleton_method(key) { value }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ERB.new(template_content, trim_mode: "-").result(context.instance_eval { binding })
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "helpers"
|
|
5
|
+
|
|
6
|
+
namespace :bunko do
|
|
7
|
+
include Bunko::RakeHelpers
|
|
8
|
+
|
|
9
|
+
desc "Install Bunko by creating migrations, models, and initializer"
|
|
10
|
+
task install: :environment do
|
|
11
|
+
puts "Installing Bunko..."
|
|
12
|
+
puts ""
|
|
13
|
+
|
|
14
|
+
# Parse options from environment variables
|
|
15
|
+
skip_seo = ENV["SKIP_SEO"] == "true"
|
|
16
|
+
json_content = ENV["JSON_CONTENT"] == "true"
|
|
17
|
+
|
|
18
|
+
# Step 1: Create migrations
|
|
19
|
+
puts "Creating migrations..."
|
|
20
|
+
create_post_types_migration(skip_seo: skip_seo, json_content: json_content)
|
|
21
|
+
sleep 1 # Ensure different timestamps
|
|
22
|
+
create_posts_migration(skip_seo: skip_seo, json_content: json_content)
|
|
23
|
+
puts ""
|
|
24
|
+
|
|
25
|
+
# Step 2: Create models
|
|
26
|
+
puts "Creating models..."
|
|
27
|
+
create_models
|
|
28
|
+
puts ""
|
|
29
|
+
|
|
30
|
+
# Step 3: Create initializer
|
|
31
|
+
puts "Creating initializer..."
|
|
32
|
+
create_initializer
|
|
33
|
+
puts ""
|
|
34
|
+
|
|
35
|
+
# Step 4: Show instructions
|
|
36
|
+
show_install_instructions
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Helper methods
|
|
40
|
+
|
|
41
|
+
def create_post_types_migration(skip_seo:, json_content:)
|
|
42
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
43
|
+
migration_file = Rails.root.join("db/migrate/#{timestamp}_create_post_types.rb")
|
|
44
|
+
|
|
45
|
+
if Dir.glob(Rails.root.join("db/migrate/*_create_post_types.rb")).any?
|
|
46
|
+
puts " - create_post_types migration already exists (skipped)"
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
migration_content = render_template("db/migrate/create_post_types.rb.tt", {
|
|
51
|
+
include_seo_fields?: !skip_seo,
|
|
52
|
+
use_json_content?: json_content
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
File.write(migration_file, migration_content)
|
|
56
|
+
puts " ✓ Created db/migrate/#{timestamp}_create_post_types.rb"
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def create_posts_migration(skip_seo:, json_content:)
|
|
61
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
62
|
+
migration_file = Rails.root.join("db/migrate/#{timestamp}_create_posts.rb")
|
|
63
|
+
|
|
64
|
+
if Dir.glob(Rails.root.join("db/migrate/*_create_posts.rb")).any?
|
|
65
|
+
puts " - create_posts migration already exists (skipped)"
|
|
66
|
+
return false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
migration_content = render_template("db/migrate/create_posts.rb.tt", {
|
|
70
|
+
include_seo_fields?: !skip_seo,
|
|
71
|
+
use_json_content?: json_content
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
File.write(migration_file, migration_content)
|
|
75
|
+
puts " ✓ Created db/migrate/#{timestamp}_create_posts.rb"
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def create_models
|
|
80
|
+
# Create Post model
|
|
81
|
+
post_file = Rails.root.join("app/models/post.rb")
|
|
82
|
+
if File.exist?(post_file)
|
|
83
|
+
puts " - app/models/post.rb already exists (skipped)"
|
|
84
|
+
else
|
|
85
|
+
post_content = render_template("models/post.rb.tt", {})
|
|
86
|
+
File.write(post_file, post_content)
|
|
87
|
+
puts " ✓ Created app/models/post.rb"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Create PostType model
|
|
91
|
+
post_type_file = Rails.root.join("app/models/post_type.rb")
|
|
92
|
+
if File.exist?(post_type_file)
|
|
93
|
+
puts " - app/models/post_type.rb already exists (skipped)"
|
|
94
|
+
else
|
|
95
|
+
post_type_content = render_template("models/post_type.rb.tt", {})
|
|
96
|
+
File.write(post_type_file, post_type_content)
|
|
97
|
+
puts " ✓ Created app/models/post_type.rb"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def create_initializer
|
|
102
|
+
initializer_dir = Rails.root.join("config/initializers")
|
|
103
|
+
initializer_file = initializer_dir.join("bunko.rb")
|
|
104
|
+
|
|
105
|
+
if File.exist?(initializer_file)
|
|
106
|
+
puts " - config/initializers/bunko.rb already exists (skipped)"
|
|
107
|
+
return false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
FileUtils.mkdir_p(initializer_dir)
|
|
111
|
+
initializer_content = render_template("config/initializers/bunko.rb.tt", {})
|
|
112
|
+
File.write(initializer_file, initializer_content)
|
|
113
|
+
puts " ✓ Created config/initializers/bunko.rb"
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def show_install_instructions
|
|
118
|
+
instructions_path = File.expand_path("../templates/INSTALL.md", __dir__)
|
|
119
|
+
instructions = File.read(instructions_path)
|
|
120
|
+
|
|
121
|
+
puts "=" * 79
|
|
122
|
+
puts instructions
|
|
123
|
+
puts "=" * 79
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../support/sample_data_generator"
|
|
4
|
+
|
|
5
|
+
namespace :bunko do
|
|
6
|
+
desc "Generate sample posts for all configured post types"
|
|
7
|
+
task sample_data: :environment do
|
|
8
|
+
# Warn if running in production
|
|
9
|
+
if Rails.env.production?
|
|
10
|
+
puts ""
|
|
11
|
+
puts "⚠️ WARNING: You're about to generate sample data in PRODUCTION"
|
|
12
|
+
puts " Press Ctrl+C to cancel, or Enter to continue..."
|
|
13
|
+
$stdin.gets
|
|
14
|
+
puts ""
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Parse configuration from ENV
|
|
18
|
+
posts_per_type = ENV.fetch("COUNT", ENV.fetch("POSTS_PER_TYPE", "100")).to_i
|
|
19
|
+
min_words = ENV.fetch("MIN_WORDS", "500").to_i
|
|
20
|
+
max_words = ENV.fetch("MAX_WORDS", "2000").to_i
|
|
21
|
+
clear_existing = ENV.fetch("CLEAR", "false").downcase == "true"
|
|
22
|
+
format = ENV.fetch("FORMAT", "html").downcase.to_sym
|
|
23
|
+
|
|
24
|
+
# Validate format
|
|
25
|
+
unless Bunko::SampleDataGenerator::FORMATS.include?(format)
|
|
26
|
+
puts "⚠️ Invalid format: #{format}"
|
|
27
|
+
puts " Valid formats: #{Bunko::SampleDataGenerator::FORMATS.join(", ")}"
|
|
28
|
+
puts ""
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "Bunko Sample Data Generator"
|
|
33
|
+
puts "=" * 79
|
|
34
|
+
puts "Configuration:"
|
|
35
|
+
puts " Posts per type: #{posts_per_type}"
|
|
36
|
+
puts " Word range: #{min_words}-#{max_words} words"
|
|
37
|
+
puts " Content format: #{format}"
|
|
38
|
+
puts " Clear existing: #{clear_existing ? "Yes" : "No"}"
|
|
39
|
+
puts ""
|
|
40
|
+
|
|
41
|
+
# Clear existing posts if requested
|
|
42
|
+
if clear_existing
|
|
43
|
+
puts "Clearing existing posts..."
|
|
44
|
+
Post.destroy_all
|
|
45
|
+
puts "✓ Cleared #{Post.count} posts"
|
|
46
|
+
puts ""
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get all post types from database
|
|
50
|
+
post_types = PostType.all
|
|
51
|
+
|
|
52
|
+
if post_types.empty?
|
|
53
|
+
puts "⚠️ No post types found. Please run 'rails bunko:setup' first."
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
puts "Generating posts for #{post_types.size} post types..."
|
|
58
|
+
puts ""
|
|
59
|
+
|
|
60
|
+
# Generate posts for each type
|
|
61
|
+
post_types.each do |post_type|
|
|
62
|
+
puts "#{post_type.title} (#{post_type.name}):"
|
|
63
|
+
print " "
|
|
64
|
+
|
|
65
|
+
posts_per_type.times do |i|
|
|
66
|
+
# Create varied dates: 90% past, 10% future
|
|
67
|
+
published_at = if rand < 0.9
|
|
68
|
+
Bunko::SampleDataGenerator.past_date(years_ago: 2)
|
|
69
|
+
else
|
|
70
|
+
Bunko::SampleDataGenerator.future_date(months_ahead: 3)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Generate title and content based on post type
|
|
74
|
+
title = Bunko::SampleDataGenerator.title_for(post_type.name)
|
|
75
|
+
target_words = rand(min_words..max_words)
|
|
76
|
+
content = Bunko::SampleDataGenerator.content_for(post_type.name, target_words: target_words, format: format)
|
|
77
|
+
|
|
78
|
+
# Create unique slug
|
|
79
|
+
base_slug = title.parameterize
|
|
80
|
+
slug = base_slug
|
|
81
|
+
counter = 1
|
|
82
|
+
|
|
83
|
+
while Post.exists?(post_type: post_type, slug: slug)
|
|
84
|
+
slug = "#{base_slug}-#{counter}"
|
|
85
|
+
counter += 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Create meta description
|
|
89
|
+
meta_description = Bunko::SampleDataGenerator.sentence(word_count: rand(15..25)).chomp(".")
|
|
90
|
+
|
|
91
|
+
# Create post
|
|
92
|
+
Post.create!(
|
|
93
|
+
post_type: post_type,
|
|
94
|
+
title: title,
|
|
95
|
+
slug: slug,
|
|
96
|
+
content: content,
|
|
97
|
+
meta_description: meta_description,
|
|
98
|
+
title_tag: "#{title} | Sample Site",
|
|
99
|
+
status: "published",
|
|
100
|
+
published_at: published_at
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
print "." if (i + 1) % 5 == 0
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
puts " ✓"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
puts ""
|
|
110
|
+
puts "=" * 79
|
|
111
|
+
puts "Summary:"
|
|
112
|
+
post_types.each do |post_type|
|
|
113
|
+
count = Post.where(post_type: post_type).count
|
|
114
|
+
future_count = Post.where(post_type: post_type).where("published_at > ?", Time.current).count
|
|
115
|
+
avg_words = Post.where(post_type: post_type).average(:word_count).to_i
|
|
116
|
+
puts " #{post_type.title}: #{count} posts (#{future_count} scheduled, avg #{avg_words} words)"
|
|
117
|
+
end
|
|
118
|
+
puts "=" * 79
|
|
119
|
+
puts ""
|
|
120
|
+
puts "Usage examples:"
|
|
121
|
+
puts " rake bunko:sample_data # 100 posts per type (HTML with images)"
|
|
122
|
+
puts " rake bunko:sample_data COUNT=50 # 50 posts per type"
|
|
123
|
+
puts " rake bunko:sample_data FORMAT=markdown # Markdown formatted content"
|
|
124
|
+
puts " rake bunko:sample_data MIN_WORDS=500 MAX_WORDS=1500"
|
|
125
|
+
puts " rake bunko:sample_data CLEAR=true # Clear existing first"
|
|
126
|
+
puts ""
|
|
127
|
+
end
|
|
128
|
+
end
|