339 lines
12 KiB
Ruby
Executable File
339 lines
12 KiB
Ruby
Executable File
#!/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
|