tractive 1.0.11 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ebb8048967ee1732b68d54ed6c10798e0959d533e7ed9744a843f1a129f764d
4
- data.tar.gz: 4799587764dcaa848e76971b50df220e72ffa914a5f1bc7c3cad20ef2e5de83d
3
+ metadata.gz: 07fb75035716d30de8f58206ad93cd0b932d74807646d5577d519d848eb2bfa0
4
+ data.tar.gz: c4e50252da3aaf67b919041cdc682e8559abb28163dd847671d30c91808d15e0
5
5
  SHA512:
6
- metadata.gz: 0c052a1f936f6e4d4749e4fd9f8ec9fcab3042f4220e2c4f10e6a7ed127f2c1ee8a856f49a58ae8b960a38f12ffdc35d9097a55361142b39bfa605a269ea2d1b
7
- data.tar.gz: 6d0025c7682c74526aa3a0fb8a4d93d4f46fad4cab5e560898e3b2c504e8981cffe10203e59afb7c8d818d3598efe3729b854c2939d7b8ca64105846de55f131
6
+ metadata.gz: efc63d0e9e8b08d3780cccd3877554c7a1df4408cd8db3c5bcf4fd11634eab73ac7f29fa300fb9191e77a892ba5d795c80e0d1a5a97283e6c541370372ef5725
7
+ data.tar.gz: b1dc726435943c2b0cf70f369c21ffd049d07e13147a5d88f162dc725fb1ad7d62a8a034b2adb05903cc9e393dd421497af457fbaf2ad8cf45684b783b63b5ba
data/.rubocop.yml CHANGED
@@ -18,6 +18,7 @@ Metrics/ClassLength:
18
18
 
19
19
  Metrics/BlockLength:
20
20
  Max: 500
21
+ IgnoredMethods: ['describe']
21
22
 
22
23
  Style/Documentation:
23
24
  Enabled: false
@@ -37,6 +38,9 @@ Metrics/MethodLength:
37
38
  Metrics/ModuleLength:
38
39
  Max: 150
39
40
 
41
+ Metrics/ParameterLists:
42
+ Max: 6
43
+
40
44
  Security/Open:
41
45
  Enabled: false
42
46
 
data/README.adoc CHANGED
@@ -802,6 +802,14 @@ If attachment files are reachable via a URL we reference this here.
802
802
  | Path to config file.
803
803
  | String
804
804
 
805
+ | `-e`, `--wiki-extensions`
806
+ | Space separated list of extensions or filenames (if the file don't have an extension). This is required to convert file SVN source links to Github links. This is used to determine if a path belongs to a file or a directory.
807
+ | Array
808
+
809
+ | `-f`, `--source-folders`
810
+ | Space separated list of non standard folders in SVN that are used to find if a path is complete or partial.
811
+ | Array
812
+
805
813
  |===
806
814
 
807
815
 
data/exe/tractive CHANGED
@@ -58,6 +58,12 @@ class TractiveCommand < CommandBase
58
58
  desc: "Full path of the Trac sqlite3 database export file"
59
59
  method_option "repo-path", type: :string, aliases: ["-r"], banner: "/GIT/ROOT/DIR",
60
60
  desc: "Full path to the root of the git-repository that is our destination"
61
+ method_option "home-page-name", type: :string, aliases: ["-h"], default: "WikiStart",
62
+ desc: "Name of the SVN wiki to map to the home page in Github wiki"
63
+ method_option "wiki-extensions", type: :array, aliases: ["-e"], default: [".py", "changelog", "expire-ids"],
64
+ desc: "Array of strings to determinte whether a given path is a file path or a directory in wiki"
65
+ method_option "source-folders", type: :array, aliases: ["-f"], default: ["personal", "attic", "sprint", "branch/hawk"],
66
+ desc: "Array of strings to figure out if a path is complete or partial"
61
67
  def migrate_wikis
62
68
  Tractive::Main.new(options).migrate_wikis
63
69
  end
@@ -53,9 +53,10 @@ module Tractive
53
53
  $logger.info "Saving attachments of ticket #{attachment.id}... "
54
54
  FileUtils.mkdir_p "#{output_dir}/#{attachment.id}"
55
55
 
56
- File.open("#{output_dir}/#{attachment.id}/#{attachment.filename}", "wb") do |file|
57
- file.write URI.open(uri_parser.escape("#{trac_url}/#{attachment.id}/#{attachment.filename}")).read
58
- end
56
+ File.binwrite(
57
+ "#{output_dir}/#{attachment.id}/#{attachment.filename}",
58
+ URI.open(uri_parser.escape("#{trac_url}/#{attachment.id}/#{attachment.filename}")).read
59
+ )
59
60
  end
60
61
  end
61
62
  end
@@ -33,7 +33,8 @@ module Migrator
33
33
  @attachment_options,
34
34
  @changeset_base_url,
35
35
  @wiki_attachments_url,
36
- @revmap_file_path
36
+ @revmap_file_path,
37
+ git_repo: @repo, home_page_name: args[:opts]["home-page-name"]
37
38
  )
38
39
  end
39
40
 
@@ -1,27 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+
3
5
  module Migrator
4
6
  module Converter
5
7
  # twf => Trac wiki format
6
8
  class TwfToMarkdown
7
- def initialize(base_url, attachment_options, changeset_base_url, wiki_attachments_url, revmap_file_path)
9
+ def initialize(base_url, attachment_options, changeset_base_url, wiki_attachments_url, revmap_file_path, options = {})
8
10
  @base_url = base_url
9
11
  @attach_url = attachment_options[:url]
10
12
  @attach_hashed = attachment_options[:hashed]
11
13
  @changeset_base_url = changeset_base_url
12
14
  @wiki_attachments_url = wiki_attachments_url
13
15
  @revmap = load_revmap_file(revmap_file_path)
16
+
17
+ @git_repo = options[:git_repo]
18
+ @home_page_name = options[:home_page_name]
19
+ @wiki_extensions = options[:wiki_extensions] # || [".py", "changelog", "expire-ids"]
20
+ @source_folders = options[:source_folders] # || %w[personal attic sprint branch/hawk]
14
21
  end
15
22
 
16
23
  def convert(str)
24
+ # Fix 'Windows EOL' to 'Linux EOL'
25
+ str.gsub!("\r\n", "\n")
26
+
27
+ convert_tables(str)
17
28
  convert_newlines(str)
29
+ convert_comments(str)
30
+ convert_html_snippets(str)
18
31
  convert_code_snippets(str)
19
32
  convert_headings(str)
20
- convert_links(str)
33
+ convert_links(str, @git_repo)
21
34
  convert_font_styles(str)
22
35
  convert_changeset(str, @changeset_base_url)
23
36
  convert_image(str, @base_url, @attach_url, @wiki_attachments_url)
24
37
  convert_ticket(str, @base_url)
38
+ revert_intermediate_references(str)
25
39
 
26
40
  str
27
41
  end
@@ -46,18 +60,12 @@ module Migrator
46
60
  revmap
47
61
  end
48
62
 
49
- # CommitTicketReference
50
- def convert_ticket_reference(str)
51
- str.gsub!(/\{\{\{\n(#!CommitTicketReference .+?)\}\}\}/m, '\1')
52
- str.gsub!(/#!CommitTicketReference .+\n/, "")
53
- end
54
-
55
63
  # Ticket
56
64
  def convert_ticket(str, base_url)
57
65
  # replace a full ticket id with the github short refrence
58
66
  if base_url
59
67
  baseurlpattern = base_url.gsub("/", "\\/")
60
- str.gsub!(%r{#{baseurlpattern}/(\d+)}) { "ticket:#{Regexp.last_match[1]}" }
68
+ str.gsub!(%r{#{baseurlpattern}/(\d+)}, '#\1')
61
69
  end
62
70
 
63
71
  # Ticket
@@ -80,16 +88,35 @@ module Migrator
80
88
  str.gsub!("\r\n", "\n")
81
89
  end
82
90
 
91
+ # Comments
92
+ def convert_comments(str)
93
+ str.gsub!(/\{\{\{(?>((?!(?:}}}|{{{)).+?|\g<0>))*\}\}\}/m) do |str_match|
94
+ str_match.gsub(/\{\{\{\s*#!comment(\s*)(.*)\}\}\}/m, '<!--\1\2\1-->')
95
+ end
96
+ end
97
+
98
+ # HTML Snippets
99
+ def convert_html_snippets(str)
100
+ str.gsub!(/\{\{\{#!html(.*?)\}\}\}/m, '\1')
101
+ end
102
+
103
+ # CommitTicketReference
104
+ def convert_ticket_reference(str)
105
+ str.gsub!(/\{\{\{\n(#!CommitTicketReference .+?)\}\}\}/m, '\1')
106
+ str.gsub!(/#!CommitTicketReference .+\n/, "")
107
+ end
108
+
83
109
  # Code
84
110
  def convert_code_snippets(str)
85
111
  str.gsub!(/\{\{\{([^\n]+?)\}\}\}/, '`\1`')
112
+ str.gsub!(/\{\{\{#!(.*?)\n(.+?)\}\}\}/m, "```\\1\n\\2\n```")
86
113
  str.gsub!(/\{\{\{(.+?)\}\}\}/m, '```\1```')
87
114
  str.gsub!(/(?<=```)#!/m, "")
88
115
  end
89
116
 
90
117
  # Changeset
91
118
  def convert_changeset(str, changeset_base_url)
92
- str.gsub!(%r{#{Regexp.quote(changeset_base_url)}/(\d+)/?}, '[changeset:\1]') if changeset_base_url
119
+ str.gsub!(%r{#{Regexp.quote(changeset_base_url)}/(\d+)/?}, '[changeset:\1]') if changeset_base_url && !changeset_base_url.empty?
93
120
  str.gsub!(/\[changeset:"r(\d+)".*\]/, '[changeset:\1]')
94
121
  str.gsub!(/\[changeset:r(\d+)\]/, '[changeset:\1]')
95
122
  str.gsub!(/\br(\d+)\b/) { Tractive::Utilities.map_changeset(Regexp.last_match[1], @revmap, changeset_base_url) }
@@ -103,15 +130,170 @@ module Migrator
103
130
  def convert_font_styles(str)
104
131
  str.gsub!(/'''(.+?)'''/, '**\1**')
105
132
  str.gsub!(/''(.+?)''/, '*\1*')
106
- str.gsub!(%r{[^:]//(.+?[^:])//}, '_\1_')
133
+ str.gsub!(%r{([^:])//(.+?[^:])//}, '\1_\2_')
134
+ end
135
+
136
+ # Tables
137
+ def convert_tables(str)
138
+ str.gsub!(/^( *\|\|[^\n]+\|\| *[^\n|]+$)+$/, '\1 ||')
139
+
140
+ str.gsub!(/(?:^( *\|\|[^\n]+\|\| *)\n?)+/) do |match_result|
141
+ rows = match_result.gsub("||", "|").split("\n")
142
+ rows.insert(1, "| #{"--- | " * (rows[0].split("|").size - 1)}".strip)
143
+
144
+ "#{rows.join("\n")}\n"
145
+ end
107
146
  end
108
147
 
109
148
  # Links
110
- def convert_links(str)
111
- str.gsub!(/\[(http[^\s\[\]]+)\s([^\[\]]+)\]/, '[\2](\1)')
149
+ def convert_links(str, git_repo)
150
+ convert_camel_case_links(str, git_repo)
151
+ convert_double_bracket_wiki_links(str, git_repo)
152
+ convert_single_bracket_wiki_links(str, git_repo)
153
+
154
+ str.gsub!(/(^!)\[(http[^\s\[\]]+)\s([^\[\]]+)\]/, '[\2](\1)')
112
155
  str.gsub!(/!(([A-Z][a-z0-9]+){2,})/, '\1')
113
156
  end
114
157
 
158
+ def convert_single_bracket_wiki_links(str, git_repo)
159
+ str.gsub!(/(!?)\[((?:wiki|source):)?([^\s\]]*) ?(.*?)\]/) do |match_result|
160
+ source = Regexp.last_match[2]
161
+ path = Regexp.last_match[3]
162
+ name = Regexp.last_match[4]
163
+
164
+ formatted_link(
165
+ match_result,
166
+ git_repo,
167
+ { source: source, path: path, name: name }
168
+ )
169
+ end
170
+ end
171
+
172
+ def convert_double_bracket_wiki_links(str, git_repo)
173
+ str.gsub!(/(!?)\[\[((?:wiki|source):)?([^|\n]*)\|?(.*?)\]\]/) do |match_result|
174
+ source = Regexp.last_match[2]
175
+ path = Regexp.last_match[3]
176
+ name = Regexp.last_match[4]
177
+
178
+ formatted_link(
179
+ match_result,
180
+ git_repo,
181
+ { source: source, path: path, name: name }
182
+ )
183
+ end
184
+ end
185
+
186
+ def formatted_link(unformatted_text, git_repo, url_options = {})
187
+ return unformatted_text.gsub("!", "{~") if unformatted_text.start_with?("!")
188
+
189
+ if url_options[:source] == "wiki:"
190
+ link, internal_link = url_options[:path].split("#")
191
+ link = "Home" if link == @home_page_name
192
+ internal_link = Tractive::Utilities.dasharize(internal_link) if internal_link
193
+ url_options[:name] = link if url_options[:name].empty?
194
+ "{{#{url_options[:name]}}}(https://github.com/#{git_repo}/wiki/#{link}##{internal_link})"
195
+ elsif url_options[:source] == "source:"
196
+ url_options[:name] = url_options[:path] if url_options[:name].empty?
197
+ "{{#{url_options[:name]}}}(https://github.com/#{git_repo}/#{source_git_path(url_options[:path])})"
198
+ elsif url_options[:path].start_with?("http")
199
+ url_options[:name] = url_options[:path] if url_options[:name].empty?
200
+ "{{#{url_options[:name]}}}(#{url_options[:path]})"
201
+ else
202
+ unformatted_text
203
+ end
204
+ end
205
+
206
+ def source_git_path(trac_path)
207
+ trac_path = trac_path.gsub("trunk/", "main/")
208
+ trac_path = trac_path.delete_prefix("/").delete_suffix("/")
209
+
210
+ return "" if trac_path.empty?
211
+
212
+ uri = URI.parse(trac_path)
213
+
214
+ trac_path = uri.path
215
+ line_number = uri.fragment
216
+ trac_path, revision = trac_path.split("@")
217
+
218
+ if trac_path.split("/").count <= 1
219
+ wiki_path(trac_path)
220
+ else
221
+ unless trac_path.start_with?("tags")
222
+ params = CGI.parse(uri.query || "")
223
+ revision ||= params["rev"].first
224
+
225
+ # TODO: Currently @ does not work with file paths except for main branch
226
+ sha = @revmap[revision]&.strip
227
+
228
+ trac_path = if sha && file?(trac_path)
229
+ trac_path.gsub("main/", "#{sha}/")
230
+ else
231
+ sha || trac_path
232
+ end
233
+ end
234
+
235
+ wiki_path(trac_path.delete_prefix("tags/"), line_number)
236
+ end
237
+ end
238
+
239
+ def index_paths
240
+ @index_paths ||= {
241
+ "tags" => "tags",
242
+ "tags/" => "tags",
243
+ "branch" => "branches/all"
244
+ }
245
+ end
246
+
247
+ def file?(trac_path)
248
+ return false unless trac_path
249
+
250
+ @wiki_extensions.any? { |extension| trac_path.end_with?(extension) }
251
+ end
252
+
253
+ def wiki_path(path, line_number = "")
254
+ # TODO: This will not work for folders given in the source_folder parameter and
255
+ # will not work for subfolders paths like `personal/rjs` unless given in the parameters.
256
+ return "branches/all?query=#{path}" if @source_folders.any? { |folder| folder == path }
257
+ return index_paths[path] if index_paths[path]
258
+
259
+ prefix = if file?(path)
260
+ "blob"
261
+ else
262
+ "tree"
263
+ end
264
+
265
+ "#{prefix}/#{path}#{"#" unless line_number.to_s.empty?}#{line_number}"
266
+ end
267
+
268
+ # CamelCase page names follow these rules:
269
+ # 1. The name must consist of alphabetic characters only;
270
+ # no digits, spaces, punctuation or underscores are allowed.
271
+ # 2. A name must have at least two capital letters.
272
+ # 3. The first character must be capitalized.
273
+ # 4. Every capital letter must be followed by one or more lower-case letters.
274
+ # 5. The use of slash ( / ) is permitted in page names, where it typically represents a hierarchy.
275
+ def convert_camel_case_links(str, git_repo)
276
+ name_regex = %r{(^| )(!?)(/?[A-Z][a-z]+(/?[A-Z][a-z]+)+/?)}
277
+ wiki_pages_names = Tractive::Wiki.select(:name).distinct.map(:name)
278
+ str.gsub!(name_regex) do
279
+ start = Regexp.last_match[2]
280
+ name = Regexp.last_match[3]
281
+
282
+ wiki_link = if start != "!" && wiki_pages_names.include?(name)
283
+ make_wiki_link(name, git_repo)
284
+ else
285
+ name
286
+ end
287
+
288
+ "#{Regexp.last_match[1]}#{wiki_link}"
289
+ end
290
+ end
291
+
292
+ def make_wiki_link(wiki_name, git_repo)
293
+ wiki_name = "Home" if wiki_name == @home_page_name
294
+ "[#{wiki_name}](https://github.com/#{git_repo}/wiki/#{wiki_name})"
295
+ end
296
+
115
297
  def convert_image(str, base_url, attach_url, wiki_attachments_url)
116
298
  # https://trac.edgewall.org/wiki/WikiFormatting#Images
117
299
  # [[Image(picture.gif)]] Current page (Ticket, Wiki, Comment)
@@ -119,28 +301,37 @@ module Migrator
119
301
  # [[Image(ticket:1:picture.gif)]] (file attached to a ticket)
120
302
 
121
303
  image_regex = /\[\[Image\((?:(?<module>(?:source|wiki)):)?(?<path>[^)]+)\)\]\]/
122
- d = image_regex.match(str)
123
- return if d.nil?
124
-
125
- path = d[:path]
126
- mod = d[:module]
127
-
128
- image_path = if mod == "source"
129
- "![#{path.split("/").last}](#{base_url}#{path})"
130
- elsif mod == "wiki"
131
- id, file = path.split(":")
132
- upload_path = "#{wiki_attachments_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
133
- "![#{file}](#{upload_path})"
134
- elsif path.start_with?("http")
135
- # [[Image(http://example.org/s.jpg)]]
136
- "![#{d[:path]}](#{d[:path]})"
137
- else
138
- _, id, file = path.split(":")
139
- file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
140
- "![#{d[:path]}](#{file_path})"
141
- end
142
-
143
- str.gsub!(image_regex, image_path)
304
+
305
+ str.gsub!(image_regex) do
306
+ path = Regexp.last_match[:path]
307
+ mod = Regexp.last_match[:module]
308
+
309
+ converted_image = if mod == "source"
310
+ "!{{#{path.split("/").last}}}(#{base_url}#{path})"
311
+ elsif mod == "wiki"
312
+ id, file = path.split(":")
313
+ upload_path = "#{wiki_attachments_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
314
+ "!{{#{file}}}(#{upload_path})"
315
+ elsif path.start_with?("http")
316
+ # [[Image(http://example.org/s.jpg)]]
317
+ "!{{#{path}}}(#{path})"
318
+ else
319
+ _, id, file = path.split(":")
320
+ file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
321
+ "!{{#{path}}}(#{file_path})"
322
+ end
323
+
324
+ # There are also ticket references in the format of ticket:1 so
325
+ # changing this now and will revert it at the end again
326
+ converted_image.gsub(/ticket:(\d+)/, 'ImageTicket~\1')
327
+ end
328
+ end
329
+
330
+ def revert_intermediate_references(str)
331
+ str.gsub!(/ImageTicket~(\d)/, 'ticket:\1')
332
+ str.gsub!("{{", "[")
333
+ str.gsub!("}}", "]")
334
+ str.gsub!(/(\{~)*/, "")
144
335
  end
145
336
  end
146
337
  end
@@ -13,12 +13,17 @@ module Migrator
13
13
  @authors_map = @config["users"].to_h
14
14
 
15
15
  @tracticketbaseurl = @config["trac"]["ticketbaseurl"]
16
+ @git_repo = @config["github"]["repo"]
16
17
  @changeset_base_url = @config["trac"]["changeset_base_url"] || ""
17
- @wiki_attachments_url = @options["attachment-base-url"] || @config.dig("wiki", "attachments", "url") || ""
18
- @repo_path = @options["repo-path"] || ""
19
18
  @revmap_path = @config["revmap_path"]
20
19
  @attachments_hashed = @config.dig("wiki", "attachments", "hashed")
21
20
 
21
+ @wiki_attachments_url = @options["attachment-base-url"] || @config.dig("wiki", "attachments", "url") || ""
22
+ @repo_path = @options["repo-path"] || ""
23
+ @home_page_name = @options["home-page-name"]
24
+ @wiki_extensions = @options["wiki-extensions"]
25
+ @source_folders = @options["source-folders"]
26
+
22
27
  @attachment_options = {
23
28
  hashed: @attachments_hashed
24
29
  }
@@ -26,7 +31,17 @@ module Migrator
26
31
  verify_options
27
32
  verify_locations
28
33
 
29
- @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(@tracticketbaseurl, @attachment_options, @changeset_base_url, @wiki_attachments_url, @revmap_path)
34
+ @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(
35
+ @tracticketbaseurl,
36
+ @attachment_options,
37
+ @changeset_base_url,
38
+ @wiki_attachments_url,
39
+ @revmap_path,
40
+ git_repo: @git_repo,
41
+ home_page_name: @home_page_name,
42
+ wiki_extensions: @wiki_extensions,
43
+ source_folders: @source_folders
44
+ )
30
45
  end
31
46
 
32
47
  def migrate_wikis
@@ -43,7 +58,8 @@ module Migrator
43
58
  wiki[:comment].gsub('"', '\"')
44
59
  end
45
60
 
46
- file_name = "#{cleanse_filename(wiki[:name])}.md"
61
+ file_name = filename_for_wiki(wiki)
62
+
47
63
  $logger.info("Working with file [#{file_name}]")
48
64
  $logger.debug("Object: #{wiki}")
49
65
 
@@ -75,6 +91,12 @@ module Migrator
75
91
 
76
92
  private
77
93
 
94
+ def filename_for_wiki(wiki)
95
+ return "Home.md" if @home_page_name == wiki[:name]
96
+
97
+ "#{cleanse_filename(wiki[:name])}.md"
98
+ end
99
+
78
100
  def verify_options
79
101
  $logger.info("Verifying options...")
80
102
 
@@ -36,6 +36,10 @@ module Tractive
36
36
  db
37
37
  end
38
38
 
39
+ def dasharize(str)
40
+ str.gsub(/([a-z\d])([A-Z])/, '\1-\2').downcase
41
+ end
42
+
39
43
  def attachment_path(id, filename, options = {})
40
44
  return "#{id}/#{filename}" unless options[:hashed]
41
45
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tractive
4
- VERSION = "1.0.11"
4
+ VERSION = "1.0.12"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tractive
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-17 00:00:00.000000000 Z
11
+ date: 2022-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mysql2