mingle-link-fixer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +33 -0
- data/bin/mingle-link-fixer +38 -0
- data/lib/mingle/api.rb +44 -0
- data/lib/mingle/attachment.rb +27 -0
- data/lib/mingle/attachment_link.rb +43 -0
- data/lib/mingle/attachment_link_finder.rb +27 -0
- data/lib/mingle/card.rb +82 -0
- data/lib/mingle/historical_attachments.rb +25 -0
- data/lib/mingle/http_client.rb +58 -0
- data/lib/mingle/logging.rb +26 -0
- data/lib/mingle/stats.rb +42 -0
- data/lib/mingle_link_fixer.rb +70 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3fbe569bf3bcac0083625a2c51097cd37d96f3fc
|
4
|
+
data.tar.gz: 9176dc5401d134f6000c1d99d3bb86339564ec04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d127e2b6d7de385d1ba32eede46b059361d240faae99d9eeee88f02b352ad1fd04f5099d11400283b026b746145a35b5d6fe318e222f8a40c12922dc123529b1
|
7
|
+
data.tar.gz: ccf029ae581e2a83c4f19d99ca5f3fc898d60d93250eddceeb101cfa78c71efddb69cce0201d5f275753ff3d935a4b2491a94a1d732c9cae5dced75367e30195
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2014 ThoughtWorks Inc.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Mingle Attachment Link Fixer
|
2
|
+
============================
|
3
|
+
|
4
|
+
If you're like many [Mingle](http://getmingle.io) users and you wanted to add link to an attachment, then you probably just right-clicked the attachment at the bottom of the card and pasted that into the link box.
|
5
|
+
|
6
|
+
Which is all fine and dandy, until you move Mingle servers, move from on-premise to Mingle SaaS, or recover your project from a data backup. In this case, the attachment IDs will be different. All the links will be broken!!
|
7
|
+
|
8
|
+
This script is designed to fix this problem. If you have your export file with valid attachments inside, then this tool can go through your cards and magically restore your links. It will use the archaic (but correct and guaranteed not to break) syntax:
|
9
|
+
|
10
|
+
[[Your link text|#123/my-file.ext]]
|
11
|
+
|
12
|
+
What's that, you say? That's wiki markup? Well, yes, it is. We chose to stick with the format our users were used to rather than invent a way to put that into HTML links.
|
13
|
+
|
14
|
+
Usage
|
15
|
+
-----
|
16
|
+
|
17
|
+
You'll need Ruby 2.0+
|
18
|
+
|
19
|
+
> gem install mingle-link-fixer
|
20
|
+
|
21
|
+
> mingle-link-fixer http://mingle.your.org/projects/iphone_app /path/containing/extracted/good/export
|
22
|
+
|
23
|
+
You can set the environment variables VERBOSE and DRY_RUN to true if you want more information or want to see what will happen when it runs, respectively.
|
24
|
+
|
25
|
+
|
26
|
+
Contributors / Bugs / &c.
|
27
|
+
-------------------------
|
28
|
+
|
29
|
+
Please open a GitHub issue and we'll get in touch.
|
30
|
+
|
31
|
+
Snap CI build status:
|
32
|
+
|
33
|
+
[![Build Status](https://snap-ci.com/ThoughtWorksStudios/mingle-link-fixer/branch/master/build_image)](https://snap-ci.com/ThoughtWorksStudios/mingle-link-fixer/branch/master)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'highline/import'
|
3
|
+
require 'uri'
|
4
|
+
require_relative '../lib/mingle_link_fixer'
|
5
|
+
|
6
|
+
def my_simple_name
|
7
|
+
$0.split('/')[-1]
|
8
|
+
end
|
9
|
+
|
10
|
+
def die_with_usage
|
11
|
+
puts(<<-USAGE.strip)
|
12
|
+
usage: #{my_simple_name} PROJECT_URL OLD_PROJECT_EXPORT
|
13
|
+
example: #{my_simple_name} http://mingle.your.org/projects/plan_a /path/to/extracted/proj/export
|
14
|
+
USAGE
|
15
|
+
exit -1
|
16
|
+
end
|
17
|
+
|
18
|
+
die_with_usage if ARGV[0].nil? || ARGV[0].empty?
|
19
|
+
project_uri = URI.parse(ARGV[0])
|
20
|
+
|
21
|
+
old_project_export_folder = if ARGV[1].nil? || ARGV[1].empty?
|
22
|
+
die_with_usage
|
23
|
+
elsif Dir[File.join(ARGV[1], "*.yml")].count > 0
|
24
|
+
ARGV[1]
|
25
|
+
else
|
26
|
+
puts "Could find attachments yml files in #{ARGV[1]}"
|
27
|
+
exit -1
|
28
|
+
end
|
29
|
+
|
30
|
+
username = ENV['MINGLE_USERNAME'] || ask("enter username for #{project_uri.hostname}")
|
31
|
+
password = ENV['MINGLE_PASSWORD'] || ask("enter password for #{username}: ") { |prompt| prompt.echo = false }
|
32
|
+
|
33
|
+
fixer = Mingle::LinkFixer.new(project_url: project_uri.to_s,
|
34
|
+
historical_attachments_folder: old_project_export_folder,
|
35
|
+
username: username,
|
36
|
+
password: password)
|
37
|
+
|
38
|
+
fixer.fix(dry_run: ENV['DRY_RUN'] == 'true', starting_card: ENV['STARTING_CARD'], limit: ENV['LIMIT'])
|
data/lib/mingle/api.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
require_relative 'logging'
|
5
|
+
|
6
|
+
module Mingle
|
7
|
+
|
8
|
+
class API
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
def initialize(http_client)
|
12
|
+
@http_client = http_client
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_card(number)
|
16
|
+
@http_client.get("/cards/#{number}.xml").body
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_attachments_on(card_number)
|
20
|
+
@http_client.get("/cards/#{card_number}/attachments.xml").body
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_card(card)
|
24
|
+
@http_client.put("/cards/#{card.number}.xml", body: card.to_xml, 'Content-Type' => 'text/xml')
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute_mql(mql)
|
28
|
+
response = @http_client.get('/cards/execute_mql.xml', mql: mql)
|
29
|
+
logger.debug(response.body)
|
30
|
+
Nokogiri::XML.parse(response.body).search('result').inject([]) do |results, result|
|
31
|
+
result_hash = result.children.inject({}) do |hash, child|
|
32
|
+
unless Nokogiri::XML::Text === child
|
33
|
+
hash[child.name] = child.text
|
34
|
+
end
|
35
|
+
hash
|
36
|
+
end
|
37
|
+
results << result_hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Mingle
|
4
|
+
class Attachment
|
5
|
+
|
6
|
+
attr_reader :filename
|
7
|
+
|
8
|
+
def initialize(filename)
|
9
|
+
@filename = filename
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.api=(api)
|
13
|
+
@@api = api
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find_all_by_card_number(number)
|
17
|
+
[].tap do |attachments|
|
18
|
+
xml = @@api.get_attachments_on(number)
|
19
|
+
document = Nokogiri::XML.parse(xml)
|
20
|
+
document.xpath('./attachments/attachment').each do |element|
|
21
|
+
filename = element.xpath('./file_name').text
|
22
|
+
attachments << Attachment.new(filename)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'logging'
|
2
|
+
require_relative 'card'
|
3
|
+
require_relative 'historical_attachments'
|
4
|
+
require_relative 'attachment'
|
5
|
+
|
6
|
+
module Mingle
|
7
|
+
class AttachmentLink
|
8
|
+
include Logging
|
9
|
+
|
10
|
+
def initialize(element)
|
11
|
+
@element = element
|
12
|
+
end
|
13
|
+
|
14
|
+
def rewrite(card, historical_attachments)
|
15
|
+
href = @element['href']
|
16
|
+
old_attachment_id = if href =~ /projects.*\/attachments\/(\d+)/
|
17
|
+
$1
|
18
|
+
elsif href =~ /attachments\/[0-9a-f]+\/(\d+)/
|
19
|
+
$1
|
20
|
+
end
|
21
|
+
old_attachment = historical_attachments.find_by_id(old_attachment_id)
|
22
|
+
raise "Could not find historical attachment based on #{old_attachment_id}" unless old_attachment
|
23
|
+
new_attachment = card.attachments.find { |attachment| attachment.filename == old_attachment.filename }
|
24
|
+
if new_attachment
|
25
|
+
"[[#{@element.text}|##{card.number}/#{new_attachment.filename}]]"
|
26
|
+
else
|
27
|
+
raise "Could not find matching attachmen for #{old_attachment.filename} in #{card.attachments.map(&:filename)}"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def rewrite!(card, historical_attachments)
|
33
|
+
new_content = rewrite(card, historical_attachments)
|
34
|
+
logger.debug "replacing #{@element.to_html} with #{new_content}"
|
35
|
+
@element.replace(Nokogiri::XML::Text.new(new_content, @element.document))
|
36
|
+
nil
|
37
|
+
rescue => e
|
38
|
+
raise "Could not replace #{@element} because: #{e.message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require_relative 'attachment_link'
|
3
|
+
|
4
|
+
module Mingle
|
5
|
+
class AttachmentLinkFinder
|
6
|
+
|
7
|
+
attr_reader :attachment_links, :card_description_document
|
8
|
+
|
9
|
+
def initialize(description)
|
10
|
+
@attachment_links = find_links(description)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find_links(html)
|
16
|
+
@card_description_document = Nokogiri::HTML.parse(html)
|
17
|
+
@card_description_document.search('a').inject([]) do |memo, anchor|
|
18
|
+
href = anchor['href']
|
19
|
+
if href =~ /attachments/
|
20
|
+
memo << AttachmentLink.new(anchor)
|
21
|
+
end
|
22
|
+
memo
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/mingle/card.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require_relative 'api'
|
3
|
+
|
4
|
+
module Mingle
|
5
|
+
class Card
|
6
|
+
|
7
|
+
def self.all(options={})
|
8
|
+
options = {starting_from: '1'}.merge(options)
|
9
|
+
Logging.debug "Mingle API => #{@@api.inspect}"
|
10
|
+
starting_card_number = options[:starting_with].to_i
|
11
|
+
max_card_number = unless options[:limit]
|
12
|
+
mql = "SELECT MAX(number)"
|
13
|
+
results = @@api.execute_mql(mql)
|
14
|
+
Logging.debug "#{mql}: #{results.inspect}"
|
15
|
+
results[0]['max_number'].to_i
|
16
|
+
else
|
17
|
+
starting_card_number + options[:limit].to_i
|
18
|
+
end
|
19
|
+
Enumerator.new do |yielder|
|
20
|
+
(starting_card_number..max_card_number).to_a.each do |number|
|
21
|
+
begin
|
22
|
+
yielder.yield Card.find_by_number(number)
|
23
|
+
rescue => e
|
24
|
+
Logging.error "Unable to fetch card ##{number} due to #{e.message}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_by_number(number)
|
31
|
+
Logging.debug("fetching card ##{number}")
|
32
|
+
card_xml = @@api.get_card(number)
|
33
|
+
Card.new(card_xml)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.api=(api)
|
37
|
+
@@api = api
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :description, :number, :name
|
41
|
+
|
42
|
+
def initialize(xml)
|
43
|
+
document = Nokogiri::XML.parse(xml)
|
44
|
+
@description = document.xpath('./card/description').text
|
45
|
+
@number = document.xpath('./card/number').text.to_i
|
46
|
+
@name = document.xpath('./card/name').text
|
47
|
+
end
|
48
|
+
|
49
|
+
def description=(desc)
|
50
|
+
@description = remove_enclosing_html_doc_and_body(desc)
|
51
|
+
end
|
52
|
+
|
53
|
+
def attachments
|
54
|
+
@attachments ||= Attachment.find_all_by_card_number(number)
|
55
|
+
end
|
56
|
+
|
57
|
+
def save!
|
58
|
+
@@api.save_card(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_xml
|
62
|
+
Nokogiri::XML::Builder.new { |xml|
|
63
|
+
xml.card {
|
64
|
+
xml.description(@description)
|
65
|
+
}
|
66
|
+
}.to_xml
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
DOCTYPE_REGEX = /^<!DOCTYPE html PUBLIC "-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN" "http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">/
|
72
|
+
|
73
|
+
def remove_enclosing_html_doc_and_body(html)
|
74
|
+
html.gsub(DOCTYPE_REGEX, '').strip
|
75
|
+
.gsub(/<html>/, '').strip
|
76
|
+
.gsub(/<body>/, '').strip
|
77
|
+
.gsub(/<\/html>$/, '').strip
|
78
|
+
.gsub(/<\/body>$/, '').strip
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative 'attachment'
|
3
|
+
|
4
|
+
module Mingle
|
5
|
+
class HistoricalAttachments
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
@attachments = []
|
10
|
+
Dir[File.join(@path, 'attachments_*.yml')].each do |file|
|
11
|
+
@attachments += YAML.load_file(file)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
@attachments.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_by_id(id)
|
20
|
+
attachment = @attachments.find { |attachment| attachment['id'].to_s == id.to_s }
|
21
|
+
Attachment.new(attachment['file']) if attachment
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Mingle
|
5
|
+
class HttpClient
|
6
|
+
include Logging
|
7
|
+
|
8
|
+
attr_accessor :base_url
|
9
|
+
|
10
|
+
def initialize(username, password)
|
11
|
+
@username = username
|
12
|
+
@password = password
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(path, params={})
|
16
|
+
url = File.join(base_url, path + to_url(params))
|
17
|
+
logger.debug "HTTP GET #{url}"
|
18
|
+
process(Net::HTTP::Get, url)
|
19
|
+
end
|
20
|
+
|
21
|
+
def put(path, params={})
|
22
|
+
url = url = File.join(base_url, path)
|
23
|
+
body = params.delete :body
|
24
|
+
process(Net::HTTP::Put, url, params, body)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def to_url(params)
|
30
|
+
params.inject([]) do |memo, pair|
|
31
|
+
memo.tap do |memo|
|
32
|
+
key, value = *pair
|
33
|
+
memo << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
34
|
+
end
|
35
|
+
end.join("&").tap { |params| return "?#{params}" unless params.empty? }
|
36
|
+
end
|
37
|
+
|
38
|
+
def process(request_class, url, headers={}, body=nil)
|
39
|
+
uri = URI.parse(url)
|
40
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
41
|
+
http.use_ssl = true if uri.scheme == 'https'
|
42
|
+
|
43
|
+
request = request_class.new(uri.request_uri)
|
44
|
+
request.basic_auth @username, @password
|
45
|
+
request.body = body if body.tap { logger.debug("HTTP body: #{body}") if body }
|
46
|
+
headers.each do |key, value|
|
47
|
+
request[key] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
response = http.request(request)
|
51
|
+
|
52
|
+
response.tap do
|
53
|
+
raise "Unexpected response of #{response.code} to #{url}" unless response.code.to_i >= 200 && response.code.to_i < 400
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Mingle
|
4
|
+
module Logging
|
5
|
+
|
6
|
+
VERBOSE = (ENV['VERBOSE'] == 'true')
|
7
|
+
LOGGER = Logger.new(STDOUT).tap { |l| l.level = VERBOSE ? Logger::DEBUG : Logger::INFO }
|
8
|
+
|
9
|
+
def self.info(msg)
|
10
|
+
LOGGER.info(msg)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.debug(msg)
|
14
|
+
LOGGER.debug(msg)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.error(msg)
|
18
|
+
LOGGER.error(msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
def logger
|
22
|
+
LOGGER
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/mingle/stats.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Mingle
|
2
|
+
class Stats
|
3
|
+
|
4
|
+
attr_accessor :total_cards_checked, :cards_without_attachments, :cards_without_links,
|
5
|
+
:cards_fixed, :problematic_cards
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@total_cards_checked = 0
|
9
|
+
@cards_without_attachments = 0
|
10
|
+
@cards_without_links = 0
|
11
|
+
@problematic_cards = {}
|
12
|
+
@cards_fixed = 0
|
13
|
+
@start = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def duration_in_seconds
|
17
|
+
Time.now - @start
|
18
|
+
end
|
19
|
+
|
20
|
+
SPACER = "=" * 80
|
21
|
+
|
22
|
+
def to_pretty_string
|
23
|
+
%{
|
24
|
+
#{SPACER}
|
25
|
+
SUMMARY
|
26
|
+
|
27
|
+
Completed in #{duration_in_seconds} sec
|
28
|
+
|
29
|
+
Total Cards Checked: #{total_cards_checked}
|
30
|
+
Cards Without Attachments: #{cards_without_attachments}
|
31
|
+
Cards Without Fixable Links: #{cards_without_links}
|
32
|
+
|
33
|
+
Problematic Cards: #{problematic_cards.size}
|
34
|
+
#{'(specific errors can be seen if you set VERBOSE environment variable)' if problematic_cards.any?}
|
35
|
+
|
36
|
+
Fixed Cards: #{cards_fixed}
|
37
|
+
#{SPACER}
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require_relative 'mingle/logging'
|
4
|
+
require_relative 'mingle/stats'
|
5
|
+
require_relative 'mingle/http_client'
|
6
|
+
require_relative 'mingle/api'
|
7
|
+
require_relative 'mingle/card'
|
8
|
+
require_relative 'mingle/historical_attachments'
|
9
|
+
require_relative 'mingle/attachment_link_finder'
|
10
|
+
|
11
|
+
module Mingle
|
12
|
+
class LinkFixer
|
13
|
+
include Logging
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@http_client = HttpClient.new(options[:username], options[:password])
|
17
|
+
@http_client.base_url = options[:project_url].gsub('/projects/', '/api/v2/projects/')
|
18
|
+
@api = API.new(@http_client)
|
19
|
+
@historical_attachments = HistoricalAttachments.new(options[:historical_attachments_folder])
|
20
|
+
Card.api = Attachment.api = @api
|
21
|
+
end
|
22
|
+
|
23
|
+
def fix(options={})
|
24
|
+
stats = Stats.new
|
25
|
+
options = {dry_run: false, starting_card: '1'}.merge(options)
|
26
|
+
logger.info "running #{self.class} (dry_run: #{options[:dry_run]}, verbose_logging: #{VERBOSE})"
|
27
|
+
Card.all(starting_with: options[:starting_card], limit: options[:limit]).each do |card|
|
28
|
+
stats.total_cards_checked += 1
|
29
|
+
begin
|
30
|
+
if card.attachments.empty?
|
31
|
+
logger.debug "skipping Card ##{card.number} because it has no attachments."
|
32
|
+
stats.cards_without_attachments += 1
|
33
|
+
next
|
34
|
+
end
|
35
|
+
finder = AttachmentLinkFinder.new(card.description)
|
36
|
+
|
37
|
+
if finder.attachment_links.empty?
|
38
|
+
logger.info "no attachment links present"
|
39
|
+
stats.cards_without_links += 1
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
logger.info "fixing #{finder.attachment_links.size} links"
|
44
|
+
|
45
|
+
finder.attachment_links.each do |attachment_link|
|
46
|
+
attachment_link.rewrite!(card, @historical_attachments)
|
47
|
+
end
|
48
|
+
|
49
|
+
html_document = finder.card_description_document
|
50
|
+
card.description = html_document.to_html
|
51
|
+
|
52
|
+
if options[:dry_run]
|
53
|
+
logger.info("(skipped saving card because dry_run is enabled)")
|
54
|
+
else
|
55
|
+
card.save!
|
56
|
+
stats.cards_fixed += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
rescue => e
|
60
|
+
logger.error "Unable to fix Card ##{card.number} because of error: #{e.message}"
|
61
|
+
stats.cards_with_problematic_links[card.number] = e.message
|
62
|
+
logger.debug e.backtrace.join("\n")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
logger.info stats.to_pretty_string
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mingle-link-fixer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bill DePhillips
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: fixes inline attachment links when a project is relocated
|
14
|
+
email:
|
15
|
+
- bill.dephillips@gmail.com
|
16
|
+
executables:
|
17
|
+
- mingle-link-fixer
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/mingle_link_fixer.rb
|
22
|
+
- lib/mingle/logging.rb
|
23
|
+
- lib/mingle/http_client.rb
|
24
|
+
- lib/mingle/api.rb
|
25
|
+
- lib/mingle/attachment.rb
|
26
|
+
- lib/mingle/historical_attachments.rb
|
27
|
+
- lib/mingle/attachment_link.rb
|
28
|
+
- lib/mingle/attachment_link_finder.rb
|
29
|
+
- lib/mingle/card.rb
|
30
|
+
- lib/mingle/stats.rb
|
31
|
+
- LICENSE
|
32
|
+
- README.md
|
33
|
+
- bin/mingle-link-fixer
|
34
|
+
homepage: https://github.com/thoughtworksstudios/mingle-link-fixer
|
35
|
+
licenses:
|
36
|
+
- Apache
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.0.14
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: fixes attachment links if you relocate a project
|
58
|
+
test_files: []
|