# # migrate.rb # ========== # # Migrate an I2 Wiki to Instiki. There must be only one 'book' in I2. A default # 'Web' is created in Instiki for this, with a system password of "changeme", # configured for Textile with CamelCase Wiki words disabled - only the # '[[foo]]' syntax is allowed. # # Run this script using "script/runner" inside the target Instiki installation. # # Create a base class which establishes the alternative database connection. # Update the connection details as required to read the source Wiki data. class I2Database < ActiveRecord::Base establish_connection( "adapter"=>"postgresql", "host"=>"/home/rool/postgres/", "database"=>"i2", "username"=>"rool", "password"=>"set-password-here" ) end # We're going to use namespaced models to avoid name clashes below. This # provokes problems with ActiveRecord; see: # # https://rails.lighthouseapp.com/projects/8994/tickets/2283-unnecessary-exception-raised-in-asdependenciesload_missing_constant # # The ugly but effective monkey patch given in the above ticket is applied # below. More structural changes were eventually considered, but this is # sufficient for our needs here. module ActiveSupport module Dependencies extend self #def load_missing_constant(from_mod, const_name) def forgiving_load_missing_constant( from_mod, const_name ) begin old_load_missing_constant(from_mod, const_name) rescue ArgumentError => arg_err if arg_err.message == "#{from_mod} is not missing constant #{const_name}!" return from_mod.const_get(const_name) else raise end end end alias :old_load_missing_constant :load_missing_constant alias :load_missing_constant :forgiving_load_missing_constant end end # Create a module to avoid namespace clashes (with 'Page' in particular) - # use "I2::Page" for an I2 page model, c.f. just "Page" for an Instiki model. module I2 class Page < I2Database set_table_name 'pages' has_many :versions end class Version < I2Database set_table_name 'versions' belongs_to :author belongs_to :page end class Author < I2Database set_table_name 'authors' has_many :versions has_many :pages, :through => :versions end end # Create the basic System and Web objects. system = System.new system.password = "changeme" system.save! web = Web.new web.name = "Documentation" web.address = "documentation" web.additional_style = "" web.allow_uploads = 1 web.published = 0 web.count_pages = 0 web.markup = "textile" web.color = "008B26" web.max_upload_size = 100 web.safe_mode = 0 web.brackets_only = 1 web.save! # Given an I2::Version instance, return body text suitable for Instiki. # def process_body( i2version ) # Complications: # # I2 allowed a nil revision body for some of its Versions. Instiki fails # if you have that, so make sure at least an empty string is present. content = ( i2version.body ) || '' # New RedCloth versions don't like it if you try to use an ID/anchor with # capital letters, so use a rather permissive gsub to fix these - it # risks catching other "#foo" cases than just anchor/IDs in links or # definitions, but (at least in ROOL's case) that seems to be very rare # (visual examination of about 1700 matches didn't spot any). We also # need to change "+"s to "%20" - see the next complication for why. content.gsub!(/\#[A-Za-z\-\+_]+/) { | str | str.downcase.gsub('+', '%20') } # # Textilised references of the form "a+b+c" were interpreted as "a b c" # # under I2 (i.e. "+" mapped to " ") but this is not the case under # # Instiki. Replacing the "+"s with "%20" solves the problem albeit in an # # ugly fashion - but it's less trouble than patching Instiki and having # # difficulty with upgrades later. # # 2011-03-13 (ADH): Commented out as changes to the Page model within # Instiki handle this case via a different method. # # content.gsub!(/((\"\:)|(\]))[A-Za-z0-9_\-\%]+\+([A-Za-z0-9_\-\%]+(\+)?)+/) { | str | str.gsub('+', '%20') } # A common vertical spacing hack in the ROOL wiki to separate tables was # to use
just above the table definition, but in new Textile any # block element must have a blank line above it else the block text is # rendered 'raw'. CSS improvements for the new Wiki make sure that such # blocks don't vertically abut so the line breaks are useless - remove, # but keep the line feed to make sure we don't let Textile block markup # touch without an intervening blank line, provoking the problem we're # trying to solve. return content.gsub(/\n\/, "\n") end # Set the ID of the Web all pages are to be placed into here; set the ID of the # I2 Book to copy over here as well. target_instiki_web = Web.first target_instiki_web_id = target_instiki_web.id source_i2_book_id = 1 # Get this e.g. from observation of SQL. #Web.transaction do begin # Pages # ===== # # Instiki Page maps to I2 Page # # I2 field Instiki field # ====================================== # title name # created_at created_at # updated_at updated_at # book_id 1 (well, Web.first.id) # locked_by and locked_at => nil or empty or whatever # # Revisons # ======== # # Instiki Version maps to I2 Revision # # I2 field Instiki field # ====================================== # page_id mapped equivalent "page_id" # author_id get author and write name to "author" # created_at created_at # body content # book_id N/A (redundant; Revision has Web through Page) # updated_at => copy created_at # revised_at => copy created_at # ip => Make up (e.g. 127.0.0.1) page_count = 0 page_total = I2::Page.count version_count = 0 version_total = I2::Version.count wiki = Wiki.new app = ActionController::Integration::Session.new app.host = "www.riscosopen.org" app.https! PageRenderer.setup_url_generator( UrlGenerator.new( app ) ) I2::Page.all( :order => 'id ASC' ).each do | i2page | next unless i2page.book_id == source_i2_book_id # Create and save the equivalent Instiki page and revisions. I2 has a very # shaky use of i2versions = i2page.versions.find( :all, :order => 'created_at ASC' ) i2version = i2versions.try( :shift ) if ( i2version.nil? ) puts "WARNING: No versions found for page '#{ i2page.title }' (#{ i2page.id }) - skipping" next end # Instiki assumes a name of "HomePage" for the default starting page in # any given 'Web'. name = i2page.title name = "HomePage" if ( name == "Home Page" ) # I2 on the ROOL site has had one or two nasty hiccups wherein a page with # a duplicate title was created. We can't handle that. Instiki just adds # a new revision to whatever existing page has a matching title, even if # you use its "write_page" call (see below). The best solution based on # observation of the data at hand is to simply skip the duplicate page; the # original page of a given title always takes precedence and any later # same-name copies are ignored. next unless ( Page.find_by_name( name ).nil? ) # Create the page and associated first revision. begin page_count += 1 version_count += 1 puts "Page #{ page_count } of #{ page_total } ('#{ name }')..." wiki.write_page( target_instiki_web.address, name, process_body( i2version ), i2page.created_at, Author.new( i2version.author.name, '127.0.0.1' ), PageRenderer.new ) rescue => e puts "WARNING: Could not save I2 page ID #{ i2page.id }:" puts e.message next # Must not try to process other revisions since we have no page end # Now handle any other revisions. i2versions.each do | i2version | begin version_count += 1 puts "version #{ version_count } of #{ version_total } done..." wiki.revise_page( target_instiki_web.address, name, name, process_body( i2version ), i2version.created_at, Author.new( i2version.author.name, '127.0.0.1' ), PageRenderer.new ) rescue => e puts "WARNING: Could not save I2 version ID #{ i2version.id }:" puts e.message end end end puts "Finished" end # Done!