#!/usr/bin/env ruby require 'optparse' OPTIONS = { :instiki_root => nil, :storage => nil, :database => 'mysql' } ARGV.options do |opts| script_name = File.basename($0) opts.banner = "Usage: ruby #{script_name} [options]" opts.separator "" opts.on("-t", "--storage /full/path/to/storage", String, "Full path to your storage, ", "such as /home/joe/instiki/storage/2500", "It should be the directory that ", "contains .snapshot files.") do |storage| OPTIONS[:storage] = storage end opts.separator "" opts.on("-i", "--instiki /full/path/to/instiki", String, "Full path to your Instiki 0.10 installation, ", "such as /home/joe/instiki-0.10.2") do |instiki| OPTIONS[:instiki] = instiki end opts.separator "" opts.on("-o", "--outfile /full/path/to/output_file", String, "Full path (including filename!) to where ", "you want the SQL output placed, such as ", "/home/joe/instiki.sql") do |outfile| OPTIONS[:outfile] = outfile end opts.on("-d", "--database {mysql|sqlite|postgres}", String, "Target database (they have slightly different syntax)", "default: mysql") do |database| OPTIONS[:database] = database end opts.separator "" opts.on_tail("-h", "--help", "Show this help message.") { puts opts; exit } opts.parse! end if OPTIONS[:instiki].nil? or OPTIONS[:storage].nil? or OPTIONS[:outfile].nil? $stderr.puts "Please specify full paths to Instiki 0.10 installation and storage," $stderr.puts "as well as the path to the output file" $stderr.puts puts ARGV.options exit -1 end if FileTest.exists? OPTIONS[:outfile] $stderr.puts "Output file #{OPTIONS[:outfile]} already exists!" $stderr.puts "Please specify a new file" $stderr.puts puts ARGV.options exit -1 end raise "Directory #{OPTIONS[:instiki]} not found" unless File.directory?(OPTIONS[:instiki]) raise "Directory #{OPTIONS[:storage]} not found" unless File.directory?(OPTIONS[:storage]) expected_page_rb_path = File.join(OPTIONS[:instiki], 'app/models/page.rb') raise "Instiki installation not found in #{OPTIONS[:instiki]}" unless File.file?(expected_page_rb_path) expected_snapshot_pattern = File.join(OPTIONS[:storage], '*.snapshot') raise "No snapshots found in #{expected_snapshot_pattern}" if Dir[expected_snapshot_pattern].empty? INSTIKI_ROOT = File.expand_path(OPTIONS[:instiki]) ADDITIONAL_LOAD_PATHS = %w( app/models lib vendor/madeleine-0.7.1/lib vendor/RedCloth-3.0.3/lib vendor/RedCloth-3.0.4/lib vendor/rubyzip-0.5.8/lib ).map { |dir| "#{File.expand_path(File.join(INSTIKI_ROOT, dir))}" }.delete_if { |dir| not File.exist?(dir) } # Prepend to $LOAD_PATH ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } require 'webrick' require 'wiki_service' # substitute an extremely expensive method with something cheap. class Revision alias :__display_content :display_content def display_content return self end end class Time def ansi strftime('%Y-%m-%d %H:%M:%S') end end def sql_insert(table, hash) columns = hash.keys values = columns.map { |column| hash[column] } values = values.map do |value| if value.nil? 'NULL' else if (value == false or value == true) and OPTIONS[:database] == 'mysql' value = value ? '1' : '0' end case OPTIONS[:database] when 'mysql', 'postgres' value = value.to_s.gsub("'", "\\\\'") when 'sqlite' value = value.to_s.gsub("'", "''") else raise "Unsupported database option #{OPTIONS[:database]}" end "'#{value.gsub("\r\n", "\n")}'" end end output = "INSERT INTO #{table} (" output << columns.join(", ") output << ") VALUES (" output << values.join(", ") output << ");" output end def delete_all(outfile) %w(wiki_references revisions pages system webs).each { |table| outfile.puts "DELETE FROM #{table};" } end def next_id(key) $ids ||= {} if $ids[key].nil? $ids[key] = 1 else $ids[key] = $ids[key] + 1 end $ids[key] end def current_id(key) $ids[key] or raise "No curent ID for #{key.inspect}" end WikiService.storage_path = OPTIONS[:storage] wiki = WikiService.instance File.open(OPTIONS[:outfile], 'w') { |outfile| outfile.puts "BEGIN;" delete_all(outfile) outfile.puts "COMMIT;" wiki.webs.each_pair do |web_name, web| outfile.puts "BEGIN;" outfile.puts sql_insert(:webs, { :id => next_id(:web), :name => web.name, :address => web.address, :password => web.password, :additional_style => web.additional_style, :allow_uploads => web.allow_uploads, :published => web.published, :count_pages => web.count_pages, :markup => web.markup, :color => web.color, :max_upload_size => web.max_upload_size, :safe_mode => web.safe_mode, :brackets_only => web.brackets_only, :created_at => web.pages.values.map { |p| p.revisions.first.created_at }.min.ansi, :updated_at => web.pages.values.map { |p| p.revisions.last.created_at }.max.ansi }) outfile.puts "COMMIT;" puts "Web #{web_name} has #{web.pages.keys.size} pages" web.pages.each_pair do |page_name, page| outfile.puts "BEGIN;" outfile.puts sql_insert(:pages, { :id => next_id(:page), :web_id => current_id(:web), :locked_by => page.locked_by, :name => page.name, :created_at => page.revisions.first.created_at.ansi, :updated_at => page.revisions.last.created_at.ansi }) puts " Page #{page_name} has #{page.revisions.size} revisions" page.revisions.each_with_index do |rev, i| outfile.puts sql_insert(:revisions, { :id => next_id(:revision), :page_id => current_id(:page), :content => rev.content, :author => rev.author.to_s, :ip => (rev.author.is_a?(Author) ? rev.author.ip : 'N/A'), :created_at => rev.created_at.ansi, :updated_at => rev.created_at.ansi, :revised_at => rev.created_at.ansi }) puts " Revision #{i} created at #{rev.created_at.ansi}" end outfile.puts "COMMIT;" end end }