Skip to content

Instantly share code, notes, and snippets.

@SamSaffron
Created May 29, 2025 05:21
Show Gist options
  • Save SamSaffron/95ad64dfc921174cb2cbadfcbbed66e8 to your computer and use it in GitHub Desktop.
Save SamSaffron/95ad64dfc921174cb2cbadfcbbed66e8 to your computer and use it in GitHub Desktop.
rename_script
#!/usr/bin/env ruby
# frozen_string_literal: true
require "fileutils"
require "date"
class PersonaToAgentRenamer
def initialize
@plugin_root = Dir.pwd
@manifest = []
end
def run
case ARGV[0]
when "--content-only"
validate_directory
replace_content_in_files
print_content_summary
when "--migrations-only"
validate_directory
create_migration
create_post_migration
print_migration_summary
else
puts colorize("=" * 55, :blue)
puts colorize("Renaming 'persona' to 'agent' throughout the codebase", :blue)
puts colorize("=" * 55, :blue)
puts colorize("Working in: #{@plugin_root}", :blue)
validate_directory
replace_content_in_files
rename_files_and_directories
create_migration
create_post_migration
print_summary
end
end
private
def colorize(text, color)
colors = {
red: "\033[0;31m",
green: "\033[0;32m",
yellow: "\033[0;33m",
blue: "\033[0;34m",
nc: "\033[0m",
}
"#{colors[color]}#{text}#{colors[:nc]}"
end
def validate_directory
unless File.exist?(File.join(@plugin_root, "plugin.rb"))
puts colorize("Error: Script must be run from the discourse-ai plugin root directory", :red)
exit 1
end
end
def git_tracked_files
`git ls-files`.split("\n").map { |f| File.join(@plugin_root, f) }
end
def all_directories_with_persona
# Find all directories that contain 'persona' in their name
output = `find . -type d -name "*persona*" 2>/dev/null | grep -v "\\.git"`.split("\n")
output.map { |dir| File.join(@plugin_root, dir.sub("./", "")) }
end
def replace_content_in_files
puts colorize("Replacing content in files...", :blue)
file_extensions = %w[rb js gjs yml erb scss hbs json]
git_tracked_files.each do |file|
# Skip files in db/ directory and tokenizers/
next if file.include?("/db/")
next if file.include?("/tokenizers/")
next if !file_extensions.include?(File.extname(file)[1..-1])
next if File.basename(file) == "rename_persona_to_agent.rb"
next unless File.exist?(file)
content = File.read(file)
next unless content.match?(/persona/i)
original_content = content.dup
# Replace different case variations
content.gsub!(/persona/, "agent")
content.gsub!(/Persona/, "Agent")
content.gsub!(/PERSONA/, "AGENT")
# Handle special cases
content.gsub!(/aiPersona/, "aiAgent")
content.gsub!(/AIPersona/, "AIAgent")
content.gsub!(/ai_persona/, "ai_agent")
content.gsub!(/Ai_persona/, "Ai_agent")
content.gsub!(/ai-persona/, "ai-agent")
if content != original_content
File.write(file, content)
relative_path = file.sub("#{@plugin_root}/", "")
puts colorize("Content updated: #{relative_path}", :green)
@manifest << "Content updated: #{relative_path}"
end
end
end
def rename_files_and_directories
puts colorize("Renaming files and directories using git mv...", :blue)
# Get all directories with 'persona' in their path (excluding db/ and tokenizers/)
# Sort by depth (deepest first) to avoid conflicts when renaming parent directories
dirs_to_rename =
all_directories_with_persona
.select { |path| !path.include?("/db/") && !path.include?("/tokenizers/") }
.sort_by { |path| -path.count("/") }
# Get all files with 'persona' in their names (excluding db/ and tokenizers/)
files_to_rename =
git_tracked_files.select do |path|
!path.include?("/db/") && !path.include?("/tokenizers/") &&
File.basename(path).match?(/persona/i)
end
# First, rename individual files that have 'persona' in their filename
puts colorize(" Renaming individual files with 'persona' in filename...", :blue)
files_to_rename.each do |old_path|
next unless File.exist?(old_path)
next if File.basename(old_path) == "rename_persona_to_agent.rb"
# Skip files that are inside directories we're going to rename
# (they'll be handled when we rename the directory)
next if dirs_to_rename.any? { |dir| old_path.start_with?(dir + "/") }
new_path = old_path.gsub(/persona/, "agent").gsub(/Persona/, "Agent")
if old_path != new_path
# Ensure parent directory exists
FileUtils.mkdir_p(File.dirname(new_path))
# Use git mv to preserve history
if system("git", "mv", old_path, new_path)
old_relative = old_path.sub("#{@plugin_root}/", "")
new_relative = new_path.sub("#{@plugin_root}/", "")
puts colorize(" File renamed: #{old_relative} -> #{new_relative}", :green)
@manifest << "File renamed: #{old_relative} -> #{new_relative}"
else
puts colorize(" Failed to rename: #{old_path}", :red)
end
end
end
# Then rename directories (deepest first to avoid path conflicts)
puts colorize(" Renaming directories with 'persona' in path...", :blue)
dirs_to_rename.each do |old_dir_path|
next unless File.exist?(old_dir_path) && File.directory?(old_dir_path)
new_dir_path = old_dir_path.gsub(/persona/, "agent").gsub(/Persona/, "Agent")
if old_dir_path != new_dir_path && !File.exist?(new_dir_path)
# Create parent directory if needed
FileUtils.mkdir_p(File.dirname(new_dir_path))
# Use git mv to preserve history for the entire directory tree
if system("git", "mv", old_dir_path, new_dir_path)
old_relative = old_dir_path.sub("#{@plugin_root}/", "")
new_relative = new_dir_path.sub("#{@plugin_root}/", "")
puts colorize(" Directory renamed: #{old_relative} -> #{new_relative}", :green)
@manifest << "Directory renamed: #{old_relative} -> #{new_relative}"
# Log all files that were moved as part of this directory rename
if File.directory?(new_dir_path)
Dir
.glob("#{new_dir_path}/**/*", File::FNM_DOTMATCH)
.each do |moved_file|
next if File.directory?(moved_file)
next if File.basename(moved_file).start_with?(".")
# Calculate what the old path would have been
relative_to_new_dir = moved_file.sub(new_dir_path + "/", "")
old_file_path = File.join(old_dir_path, relative_to_new_dir)
old_file_relative = old_file_path.sub("#{@plugin_root}/", "")
new_file_relative = moved_file.sub("#{@plugin_root}/", "")
puts colorize(
" File moved: #{old_file_relative} -> #{new_file_relative}",
:green,
)
@manifest << "File moved: #{old_file_relative} -> #{new_file_relative}"
end
end
else
puts colorize(" Failed to rename directory: #{old_dir_path}", :red)
end
end
end
end
def create_migration
puts colorize("Creating database migration to copy persona tables...", :blue)
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
migration_file =
File.join(@plugin_root, "db", "migrate", "#{timestamp}_copy_persona_tables_to_agent.rb")
migration_content = <<~RUBY
# frozen_string_literal: true
class CopyPersonaTablesToAgent < ActiveRecord::Migration[7.0]
def up
# Copy the main table structure and data
if table_exists?(:ai_personas) && !table_exists?(:ai_agents)
execute <<~SQL
CREATE TABLE ai_agents AS
SELECT * FROM ai_personas
SQL
# Copy indexes from ai_personas to ai_agents
execute <<~SQL
CREATE UNIQUE INDEX index_ai_agents_on_id
ON ai_agents USING btree (id)
SQL
# Copy any other indexes that exist on ai_personas
indexes = execute(<<~SQL).to_a
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'ai_personas'
AND indexname != 'ai_personas_pkey'
SQL
indexes.each do |index|
new_index_def = index['indexdef'].gsub('ai_personas', 'ai_agents')
new_index_name = index['indexname'].gsub('ai_personas', 'ai_agents')
new_index_def = new_index_def.gsub(index['indexname'], new_index_name)
execute(new_index_def)
end
end
# Update polymorphic associations to point to new table
execute <<~SQL
UPDATE rag_document_fragments
SET target_type = 'AiAgent'
WHERE target_type = 'AiPersona'
SQL
execute <<~SQL
UPDATE upload_references
SET target_type = 'AiAgent'
WHERE target_type = 'AiPersona'
SQL
end
def down
drop_table :ai_agents if table_exists?(:ai_agents)
# Revert polymorphic associations
execute <<~SQL
UPDATE rag_document_fragments
SET target_type = 'AiPersona'
WHERE target_type = 'AiAgent'
SQL
execute <<~SQL
UPDATE upload_references
SET target_type = 'AiPersona'
WHERE target_type = 'AiAgent'
SQL
end
end
RUBY
FileUtils.mkdir_p(File.dirname(migration_file))
File.write(migration_file, migration_content)
relative_migration = migration_file.sub("#{@plugin_root}/", "")
puts colorize("Created migration file: #{relative_migration}", :green)
@manifest << "Created migration file: #{relative_migration}"
end
def create_post_migration
puts colorize("Creating post-migration to drop old persona tables...", :blue)
timestamp = (Time.now + 1).strftime("%Y%m%d%H%M%S") # Ensure this runs after the main migration
post_migrate_dir = File.join(@plugin_root, "db", "post_migrate")
migration_file = File.join(post_migrate_dir, "#{timestamp}_drop_persona_tables.rb")
migration_content = <<~RUBY
# frozen_string_literal: true
class DropPersonaTables < ActiveRecord::Migration[7.0]
def up
# Drop the old table after copying to new one
drop_table :ai_personas if table_exists?(:ai_personas)
end
def down
raise IrreversibleMigration, "Cannot recreate dropped persona tables"
end
end
RUBY
FileUtils.mkdir_p(post_migrate_dir)
File.write(migration_file, migration_content)
relative_migration = migration_file.sub("#{@plugin_root}/", "")
puts colorize("Created post-migration file: #{relative_migration}", :green)
@manifest << "Created post-migration file: #{relative_migration}"
end
def print_content_summary
puts colorize("Content replacement completed!", :green)
puts colorize("Files updated: #{@manifest.count}", :yellow)
@manifest.each { |change| puts " #{change}" }
end
def print_migration_summary
puts colorize("Database migrations created!", :green)
puts colorize("Files created: #{@manifest.count}", :yellow)
@manifest.each { |change| puts " #{change}" }
end
def print_summary
puts colorize("=" * 55, :blue)
puts colorize("Completed renaming 'persona' to 'agent' in the codebase", :green)
puts colorize("=" * 55, :blue)
puts colorize("Changes made:", :yellow)
@manifest.each { |change| puts " #{change}" }
puts colorize("Next steps:", :yellow)
puts "1. Review changes with 'git diff'"
puts "2. Run tests and fix any remaining issues"
puts "3. Run the database migrations (migrate, then post_migrate)"
puts colorize("=" * 55, :blue)
end
end
# Run the renamer if this file is executed directly
PersonaToAgentRenamer.new.run if __FILE__ == $0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment