19 |
19 |
require 'iconv'
|
20 |
20 |
require 'pp'
|
21 |
21 |
|
|
22 |
require 'redmine/scm/adapters/abstract_adapter'
|
|
23 |
require 'redmine/scm/adapters/subversion_adapter'
|
|
24 |
require 'rexml/document'
|
|
25 |
require 'uri'
|
|
26 |
|
|
27 |
require 'tempfile'
|
|
28 |
|
22 |
29 |
namespace :redmine do
|
23 |
30 |
desc 'Trac migration script'
|
24 |
31 |
task :migrate_from_trac => :environment do
|
... | ... | |
192 |
199 |
def time; Time.at(read_attribute(:time)) end
|
193 |
200 |
end
|
194 |
201 |
|
195 |
|
TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
|
|
202 |
TRAC_WIKI_PAGES = %w(InterMapTxt TracInstallPlatforms TracMultipleProjects InterTrac InterWiki RecentChanges SandBox \
|
|
203 |
TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
|
196 |
204 |
TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
|
197 |
205 |
TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
|
198 |
206 |
TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
|
... | ... | |
271 |
279 |
|
272 |
280 |
# Basic wiki syntax conversion
|
273 |
281 |
def self.convert_wiki_text(text)
|
274 |
|
# Titles
|
275 |
|
text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
|
276 |
|
# External Links
|
277 |
|
text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
|
278 |
|
# Ticket links:
|
279 |
|
# [ticket:234 Text],[ticket:234 This is a test]
|
280 |
|
text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1')
|
281 |
|
# ticket:1234
|
282 |
|
# #1 is working cause Redmine uses the same syntax.
|
283 |
|
text = text.gsub(/ticket\:([^\ ]+)/, '#\1')
|
284 |
|
# Milestone links:
|
285 |
|
# [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)]
|
286 |
|
# The text "Milestone 0.1.0 (Mercury)" is not converted,
|
287 |
|
# cause Redmine's wiki does not support this.
|
288 |
|
text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"')
|
289 |
|
# [milestone:"0.1.0 Mercury"]
|
290 |
|
text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
|
291 |
|
text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
|
292 |
|
# milestone:0.1.0
|
293 |
|
text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
|
294 |
|
text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
|
295 |
|
# Internal Links
|
296 |
|
text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
|
297 |
|
text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
298 |
|
text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
299 |
|
text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
300 |
|
text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
301 |
|
text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
|
302 |
|
|
303 |
|
# Links to pages UsingJustWikiCaps
|
304 |
|
text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
|
305 |
|
# Normalize things that were supposed to not be links
|
306 |
|
# like !NotALink
|
307 |
|
text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
|
308 |
|
# Revisions links
|
309 |
|
text = text.gsub(/\[(\d+)\]/, 'r\1')
|
310 |
|
# Ticket number re-writing
|
311 |
|
text = text.gsub(/#(\d+)/) do |s|
|
312 |
|
if $1.length < 10
|
313 |
|
# TICKET_MAP[$1.to_i] ||= $1
|
314 |
|
"\##{TICKET_MAP[$1.to_i] || $1}"
|
315 |
|
else
|
316 |
|
s
|
317 |
|
end
|
318 |
|
end
|
319 |
|
# We would like to convert the Code highlighting too
|
320 |
|
# This will go into the next line.
|
321 |
|
shebang_line = false
|
322 |
|
# Reguar expression for start of code
|
323 |
|
pre_re = /\{\{\{/
|
324 |
|
# Code hightlighing...
|
325 |
|
shebang_re = /^\#\!([a-z]+)/
|
326 |
|
# Regular expression for end of code
|
327 |
|
pre_end_re = /\}\}\}/
|
328 |
|
|
329 |
|
# Go through the whole text..extract it line by line
|
330 |
|
text = text.gsub(/^(.*)$/) do |line|
|
331 |
|
m_pre = pre_re.match(line)
|
332 |
|
if m_pre
|
333 |
|
line = '<pre>'
|
334 |
|
else
|
335 |
|
m_sl = shebang_re.match(line)
|
336 |
|
if m_sl
|
337 |
|
shebang_line = true
|
338 |
|
line = '<code class="' + m_sl[1] + '">'
|
339 |
|
end
|
340 |
|
m_pre_end = pre_end_re.match(line)
|
341 |
|
if m_pre_end
|
342 |
|
line = '</pre>'
|
343 |
|
if shebang_line
|
344 |
|
line = '</code>' + line
|
345 |
|
end
|
346 |
|
end
|
347 |
|
end
|
348 |
|
line
|
349 |
|
end
|
350 |
|
|
351 |
|
# Highlighting
|
352 |
|
text = text.gsub(/'''''([^\s])/, '_*\1')
|
353 |
|
text = text.gsub(/([^\s])'''''/, '\1*_')
|
354 |
|
text = text.gsub(/'''/, '*')
|
355 |
|
text = text.gsub(/''/, '_')
|
356 |
|
text = text.gsub(/__/, '+')
|
357 |
|
text = text.gsub(/~~/, '-')
|
358 |
|
text = text.gsub(/`/, '@')
|
359 |
|
text = text.gsub(/,,/, '~')
|
360 |
|
# Lists
|
361 |
|
text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
|
362 |
|
|
363 |
|
text
|
|
282 |
convert_wiki_text_mapping(text, TICKET_MAP)
|
364 |
283 |
end
|
365 |
284 |
|
366 |
285 |
def self.migrate
|
... | ... | |
400 |
319 |
# Milestones
|
401 |
320 |
print "Migrating milestones"
|
402 |
321 |
version_map = {}
|
|
322 |
milestone_wiki = Array.new
|
403 |
323 |
TracMilestone.find(:all).each do |milestone|
|
404 |
324 |
print '.'
|
405 |
325 |
STDOUT.flush
|
... | ... | |
419 |
339 |
|
420 |
340 |
next unless v.save
|
421 |
341 |
version_map[milestone.name] = v
|
|
342 |
milestone_wiki.push(milestone.name);
|
422 |
343 |
migrated_milestones += 1
|
423 |
344 |
end
|
424 |
345 |
puts
|
425 |
|
|
|
346 |
|
426 |
347 |
# Custom fields
|
427 |
348 |
# TODO: read trac.ini instead
|
428 |
349 |
print "Migrating custom fields"
|
429 |
350 |
custom_field_map = {}
|
|
351 |
|
430 |
352 |
TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
|
431 |
353 |
print '.'
|
432 |
354 |
STDOUT.flush
|
... | ... | |
456 |
378 |
r.save!
|
457 |
379 |
custom_field_map['resolution'] = r
|
458 |
380 |
|
|
381 |
# Trac ticket id as a Redmine custom field
|
|
382 |
tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" })
|
|
383 |
tid = IssueCustomField.new(:name => 'TracID',
|
|
384 |
:field_format => 'string',
|
|
385 |
:is_filter => true) if tid.nil?
|
|
386 |
tid.trackers = Tracker.find(:all)
|
|
387 |
tid.projects << @target_project
|
|
388 |
tid.save!
|
|
389 |
custom_field_map['tracid'] = tid
|
|
390 |
|
459 |
391 |
# Tickets
|
460 |
392 |
print "Migrating tickets"
|
461 |
393 |
TracTicket.find_each(:batch_size => 200) do |ticket|
|
... | ... | |
463 |
395 |
STDOUT.flush
|
464 |
396 |
i = Issue.new :project => @target_project,
|
465 |
397 |
:subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
|
466 |
|
:description => convert_wiki_text(encode(ticket.description)),
|
|
398 |
:description => encode(ticket.description),
|
467 |
399 |
:priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
|
468 |
400 |
:created_on => ticket.time
|
469 |
401 |
i.author = find_or_create_user(ticket.reporter)
|
... | ... | |
488 |
420 |
resolution_change = changeset.select {|change| change.field == 'resolution'}.first
|
489 |
421 |
comment_change = changeset.select {|change| change.field == 'comment'}.first
|
490 |
422 |
|
491 |
|
n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
|
|
423 |
n = Journal.new :notes => (comment_change ? encode(comment_change.newvalue) : ''),
|
492 |
424 |
:created_on => time
|
493 |
425 |
n.user = find_or_create_user(changeset.first.author)
|
494 |
426 |
n.journalized = i
|
... | ... | |
534 |
466 |
if custom_field_map['resolution'] && !ticket.resolution.blank?
|
535 |
467 |
custom_values[custom_field_map['resolution'].id] = ticket.resolution
|
536 |
468 |
end
|
|
469 |
if custom_field_map['tracid']
|
|
470 |
custom_values[custom_field_map['tracid'].id] = ticket.id
|
|
471 |
end
|
537 |
472 |
i.custom_field_values = custom_values
|
538 |
473 |
i.save_custom_field_values
|
539 |
474 |
end
|
... | ... | |
576 |
511 |
end
|
577 |
512 |
end
|
578 |
513 |
|
|
514 |
end
|
|
515 |
puts
|
|
516 |
|
|
517 |
# Now load each wiki page and transform its content into textile format
|
|
518 |
print "Fixing ticket identifiers"
|
|
519 |
puts
|
|
520 |
|
|
521 |
print "...in Wiki pages"
|
579 |
522 |
wiki.reload
|
580 |
523 |
wiki.pages.each do |page|
|
|
524 |
print '.'
|
581 |
525 |
page.content.text = convert_wiki_text(page.content.text)
|
582 |
526 |
Time.fake(page.content.updated_on) { page.content.save }
|
583 |
527 |
end
|
584 |
|
end
|
585 |
|
puts
|
|
528 |
puts
|
586 |
529 |
|
|
530 |
print "...in Issue descriptions"
|
|
531 |
TICKET_MAP.each do |newId|
|
|
532 |
|
|
533 |
next if newId.nil?
|
|
534 |
|
|
535 |
print '.'
|
|
536 |
issue = findIssue(newId)
|
|
537 |
next if issue.nil?
|
|
538 |
|
|
539 |
issue.description = convert_wiki_text(issue.description)
|
|
540 |
issue.save
|
|
541 |
end
|
|
542 |
puts
|
|
543 |
|
|
544 |
print "...in Issue journal descriptions"
|
|
545 |
TICKET_MAP.each do |newId|
|
|
546 |
next if newId.nil?
|
|
547 |
|
|
548 |
print '.'
|
|
549 |
issue = findIssue(newId)
|
|
550 |
next if issue.nil?
|
|
551 |
|
|
552 |
issue.journals.find(:all).each do |journal|
|
|
553 |
print '.'
|
|
554 |
journal.notes = convert_wiki_text(journal.notes)
|
|
555 |
journal.save
|
|
556 |
end
|
|
557 |
|
|
558 |
end
|
|
559 |
puts
|
|
560 |
|
|
561 |
print "...in Milestone descriptions"
|
|
562 |
|
|
563 |
|
|
564 |
# Now load each page and transform its content into textile format
|
|
565 |
milestone_wiki.each do |name|
|
|
566 |
p = wiki.find_page(name)
|
|
567 |
next if p.nil?
|
|
568 |
|
|
569 |
print '.'
|
|
570 |
p.content.text = convert_wiki_text(p.content.text)
|
|
571 |
p.content.save
|
|
572 |
end
|
|
573 |
puts
|
|
574 |
|
587 |
575 |
puts
|
588 |
576 |
puts "Components: #{migrated_components}/#{TracComponent.count}"
|
589 |
577 |
puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
|
... | ... | |
593 |
581 |
puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
|
594 |
582 |
puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
|
595 |
583 |
end
|
|
584 |
|
|
585 |
def self.findIssue(id)
|
|
586 |
|
|
587 |
return Issue.find(id)
|
596 |
588 |
|
|
589 |
rescue ActiveRecord::RecordNotFound
|
|
590 |
puts
|
|
591 |
print "[#{id}] not found"
|
|
592 |
|
|
593 |
nil
|
|
594 |
end
|
|
595 |
|
597 |
596 |
def self.limit_for(klass, attribute)
|
598 |
597 |
klass.columns_hash[attribute.to_s].limit
|
599 |
598 |
end
|
... | ... | |
746 |
745 |
DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
|
747 |
746 |
|
748 |
747 |
prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
|
749 |
|
prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
|
|
748 |
prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite3') {|adapter| TracMigrate.set_trac_adapter adapter}
|
750 |
749 |
unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
|
751 |
750 |
prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
|
752 |
751 |
prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
|
... | ... | |
764 |
763 |
|
765 |
764 |
TracMigrate.migrate
|
766 |
765 |
end
|
|
766 |
|
|
767 |
|
|
768 |
desc 'Subversion migration script'
|
|
769 |
task :migrate_svn_commits => :environment do
|
|
770 |
|
|
771 |
module SvnMigrate
|
|
772 |
TICKET_MAP = []
|
|
773 |
|
|
774 |
class Commit
|
|
775 |
attr_accessor :revision, :message
|
|
776 |
|
|
777 |
def initialize(attributes={})
|
|
778 |
self.message = attributes[:message] || ""
|
|
779 |
self.revision = attributes[:revision]
|
|
780 |
end
|
|
781 |
end
|
|
782 |
|
|
783 |
class SvnExtendedAdapter < Redmine::Scm::Adapters::SubversionAdapter
|
|
784 |
|
|
785 |
|
|
786 |
|
|
787 |
def set_message(path=nil, revision=nil, msg=nil)
|
|
788 |
path ||= ''
|
|
789 |
|
|
790 |
Tempfile.open('msg') do |tempfile|
|
|
791 |
|
|
792 |
# This is a weird thing. We need to cleanup cr/lf so we have uniform line separators
|
|
793 |
tempfile.print msg.gsub(/\r\n/,'\n')
|
|
794 |
tempfile.flush
|
|
795 |
|
|
796 |
filePath = tempfile.path.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
|
|
797 |
|
|
798 |
cmd = "#{SVN_BIN} propset svn:log --quiet --revprop -r #{revision} -F \"#{filePath}\" "
|
|
799 |
cmd << credentials_string
|
|
800 |
cmd << ' ' + target(URI.escape(path))
|
|
801 |
|
|
802 |
shellout(cmd) do |io|
|
|
803 |
begin
|
|
804 |
loop do
|
|
805 |
line = io.readline
|
|
806 |
puts line
|
|
807 |
end
|
|
808 |
rescue EOFError
|
|
809 |
end
|
|
810 |
end
|
|
811 |
|
|
812 |
raise if $? && $?.exitstatus != 0
|
|
813 |
|
|
814 |
end
|
|
815 |
|
|
816 |
end
|
|
817 |
|
|
818 |
def messages(path=nil)
|
|
819 |
path ||= ''
|
|
820 |
|
|
821 |
commits = Array.new
|
|
822 |
|
|
823 |
cmd = "#{SVN_BIN} log --xml -r 1:HEAD"
|
|
824 |
cmd << credentials_string
|
|
825 |
cmd << ' ' + target(URI.escape(path))
|
|
826 |
|
|
827 |
shellout(cmd) do |io|
|
|
828 |
begin
|
|
829 |
doc = REXML::Document.new(io)
|
|
830 |
doc.elements.each("log/logentry") do |logentry|
|
|
831 |
|
|
832 |
commits << Commit.new(
|
|
833 |
{
|
|
834 |
:revision => logentry.attributes['revision'].to_i,
|
|
835 |
:message => logentry.elements['msg'].text
|
|
836 |
})
|
|
837 |
end
|
|
838 |
rescue => e
|
|
839 |
puts"Error !!!"
|
|
840 |
puts e
|
|
841 |
end
|
|
842 |
end
|
|
843 |
return nil if $? && $?.exitstatus != 0
|
|
844 |
commits
|
|
845 |
end
|
|
846 |
|
|
847 |
end
|
|
848 |
|
|
849 |
def self.migrate
|
|
850 |
|
|
851 |
project = Project.find(@@redmine_project)
|
|
852 |
if !project
|
|
853 |
puts "Could not find project identifier '#{@@redmine_project}'"
|
|
854 |
raise
|
|
855 |
end
|
|
856 |
|
|
857 |
tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" })
|
|
858 |
if !tid
|
|
859 |
puts "Could not find issue custom field 'TracID'"
|
|
860 |
raise
|
|
861 |
end
|
|
862 |
|
|
863 |
Issue.find( :all, :conditions => { :project_id => project }).each do |issue|
|
|
864 |
val = nil
|
|
865 |
issue.custom_values.each do |value|
|
|
866 |
if value.custom_field.id == tid.id
|
|
867 |
val = value
|
|
868 |
break
|
|
869 |
end
|
|
870 |
end
|
|
871 |
|
|
872 |
TICKET_MAP[val.value.to_i] = issue.id if !val.nil?
|
|
873 |
end
|
|
874 |
|
|
875 |
svn = self.scm
|
|
876 |
msgs = svn.messages(@svn_url)
|
|
877 |
msgs.each do |commit|
|
|
878 |
|
|
879 |
newText = convert_wiki_text(commit.message)
|
|
880 |
|
|
881 |
if newText != commit.message
|
|
882 |
puts "Updating message #{commit.revision}"
|
|
883 |
scm.set_message(@svn_url, commit.revision, newText)
|
|
884 |
end
|
|
885 |
end
|
|
886 |
|
|
887 |
|
|
888 |
end
|
|
889 |
|
|
890 |
# Basic wiki syntax conversion
|
|
891 |
def self.convert_wiki_text(text)
|
|
892 |
convert_wiki_text_mapping(text, TICKET_MAP )
|
|
893 |
end
|
|
894 |
|
|
895 |
def self.set_svn_url(url)
|
|
896 |
@@svn_url = url
|
|
897 |
end
|
|
898 |
|
|
899 |
def self.set_svn_username(username)
|
|
900 |
@@svn_username = username
|
|
901 |
end
|
|
902 |
|
|
903 |
def self.set_svn_password(password)
|
|
904 |
@@svn_password = password
|
|
905 |
end
|
|
906 |
|
|
907 |
def self.set_redmine_project_identifier(identifier)
|
|
908 |
@@redmine_project = identifier
|
|
909 |
end
|
|
910 |
|
|
911 |
def self.scm
|
|
912 |
@scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password, 0, "", nil
|
|
913 |
@scm
|
|
914 |
end
|
|
915 |
end
|
|
916 |
|
|
917 |
def prompt(text, options = {}, &block)
|
|
918 |
default = options[:default] || ''
|
|
919 |
while true
|
|
920 |
print "#{text} [#{default}]: "
|
|
921 |
value = STDIN.gets.chomp!
|
|
922 |
value = default if value.blank?
|
|
923 |
break if yield value
|
|
924 |
end
|
|
925 |
end
|
|
926 |
|
|
927 |
puts
|
|
928 |
if Redmine::DefaultData::Loader.no_data?
|
|
929 |
puts "Redmine configuration need to be loaded before importing data."
|
|
930 |
puts "Please, run this first:"
|
|
931 |
puts
|
|
932 |
puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
|
|
933 |
exit
|
|
934 |
end
|
|
935 |
|
|
936 |
puts "WARNING: all commit messages with references to trac pages will be modified"
|
|
937 |
print "Are you sure you want to continue ? [y/N] "
|
|
938 |
break unless STDIN.gets.match(/^y$/i)
|
|
939 |
puts
|
|
940 |
|
|
941 |
prompt('Subversion repository url') {|repository| SvnMigrate.set_svn_url repository.strip}
|
|
942 |
prompt('Subversion repository username') {|username| SvnMigrate.set_svn_username username}
|
|
943 |
prompt('Subversion repository password') {|password| SvnMigrate.set_svn_password password}
|
|
944 |
prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier}
|
|
945 |
puts
|
|
946 |
|
|
947 |
SvnMigrate.migrate
|
|
948 |
|
|
949 |
end
|
|
950 |
|
|
951 |
|
|
952 |
# Basic wiki syntax conversion
|
|
953 |
def convert_wiki_text_mapping(text, ticket_map = [])
|
|
954 |
# Images
|
|
955 |
text = text.gsub(/\[\[Image\(([a-zA-Z]+\:(\d+\:)?)?([^\)^,]+)[^\)]*\)\]\]/, '!\3!')
|
|
956 |
# Titles
|
|
957 |
text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
|
|
958 |
# External Links
|
|
959 |
text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
|
|
960 |
# Ticket links:
|
|
961 |
# [ticket:234 Text],[ticket:234 This is a test]
|
|
962 |
text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1')
|
|
963 |
# ticket:1234
|
|
964 |
# #1 is working cause Redmine uses the same syntax.
|
|
965 |
text = text.gsub(/ticket\:([^\ ]+)/, '#\1')
|
|
966 |
|
|
967 |
# Source links:
|
|
968 |
# [source:/trunk/readme.txt Readme File]
|
|
969 |
# The text "Readme File" is not converted,
|
|
970 |
# cause Redmine's wiki does not support this.
|
|
971 |
text = text.gsub(/\[source\:\"([^\"]+)\"\ (.+?)\]/, 'source:"\1"')
|
|
972 |
# [source:/trunk/readme.txt]
|
|
973 |
text = text.gsub(/\[source\:\"([^\"]+)\"\]/, 'source:"\1"')
|
|
974 |
text = text.gsub(/source\:\"([^\"]+)\"/, 'source:"\1"')
|
|
975 |
|
|
976 |
# Milestone links:
|
|
977 |
# [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)]
|
|
978 |
# The text "Milestone 0.1.0 (Mercury)" is not converted,
|
|
979 |
# cause Redmine's wiki does not support this.
|
|
980 |
text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"')
|
|
981 |
# [milestone:"0.1.0 Mercury"]
|
|
982 |
text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
|
|
983 |
text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
|
|
984 |
# milestone:0.1.0
|
|
985 |
text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
|
|
986 |
text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
|
|
987 |
# Internal Links
|
|
988 |
text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
|
|
989 |
text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
990 |
text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
991 |
text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
992 |
text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
993 |
text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
|
|
994 |
|
|
995 |
# Links to pages UsingJustWikiCaps
|
|
996 |
text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
|
|
997 |
# Normalize things that were supposed to not be links
|
|
998 |
# like !NotALink
|
|
999 |
text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
|
|
1000 |
|
|
1001 |
# Revisions links
|
|
1002 |
text = text.gsub(/\[(\d+)\]/, 'r\1')
|
|
1003 |
# Ticket number re-writing
|
|
1004 |
text = text.gsub(/#(\d+)/) do |s|
|
|
1005 |
if $1.length < 10
|
|
1006 |
ticket_map[$1.to_i] ||= $1
|
|
1007 |
"\##{ticket_map[$1.to_i] || $1}"
|
|
1008 |
else
|
|
1009 |
s
|
|
1010 |
end
|
|
1011 |
end
|
|
1012 |
# We would like to convert the Code highlighting too
|
|
1013 |
# This will go into the next line.
|
|
1014 |
shebang_line = false
|
|
1015 |
# Reguar expression for start of code
|
|
1016 |
pre_re = /\{\{\{/
|
|
1017 |
# Code hightlighing...
|
|
1018 |
shebang_re = /^\#\!([a-z]+)/
|
|
1019 |
# Regular expression for end of code
|
|
1020 |
pre_end_re = /\}\}\}/
|
|
1021 |
|
|
1022 |
# Go through the whole text..extract it line by line
|
|
1023 |
text = text.gsub(/^(.*)$/) do |line|
|
|
1024 |
m_pre = pre_re.match(line)
|
|
1025 |
if m_pre
|
|
1026 |
line = '<pre>'
|
|
1027 |
else
|
|
1028 |
m_sl = shebang_re.match(line)
|
|
1029 |
if m_sl
|
|
1030 |
shebang_line = true
|
|
1031 |
line = '<code class="' + m_sl[1] + '">'
|
|
1032 |
end
|
|
1033 |
m_pre_end = pre_end_re.match(line)
|
|
1034 |
if m_pre_end
|
|
1035 |
line = '</pre>'
|
|
1036 |
if shebang_line
|
|
1037 |
line = '</code>' + line
|
|
1038 |
end
|
|
1039 |
end
|
|
1040 |
end
|
|
1041 |
line
|
|
1042 |
end
|
|
1043 |
|
|
1044 |
# Highlighting
|
|
1045 |
text = text.gsub(/'''''([^\s])/, '_*\1')
|
|
1046 |
text = text.gsub(/([^\s])'''''/, '\1*_')
|
|
1047 |
text = text.gsub(/'''/, '*')
|
|
1048 |
text = text.gsub(/''/, '_')
|
|
1049 |
text = text.gsub(/__/, '+')
|
|
1050 |
text = text.gsub(/~~/, '-')
|
|
1051 |
text = text.gsub(/`/, '@')
|
|
1052 |
text = text.gsub(/,,/, '~')
|
|
1053 |
# Lists
|
|
1054 |
text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
|
|
1055 |
text = text.gsub(/^([ ]+)[0-9]+\. /) {|s| '#' * $1.length + " "}
|
|
1056 |
|
|
1057 |
text
|
|
1058 |
end
|
767 |
1059 |
end
|
768 |
1060 |
|