sutty-migration 0.4.0rc0 → 0.4.0rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33b04f68ec23be8ad61143db87c0496fc1a01a84fdb3351b9f76a2782f79342f
4
- data.tar.gz: 411e0050ee48d300c7bcda874f1943b33d867f4c090f72650c0122a69027faa2
3
+ metadata.gz: 7e6f379b5ad8bdb17dbe4353ac4436f244cbcbee4db468070df6da38befde458
4
+ data.tar.gz: 26e38ae35fdef4c9d702b959ed24ea51c1cf41d457a89c4914a7757a5a942289
5
5
  SHA512:
6
- metadata.gz: 708dff82c233c3819acbe9ccf3901d8345463282ba9df953adb8ac0f15f9646560e514c87499921d13ac6bfa2693c71b0d634e03e1f04e87d0e148174bd91c50
7
- data.tar.gz: 0faa1dc6c107f0999d8a52b7c28b08ad1540e44cff8fd488bfcfae55d2d22548fe0afe61fb54520f4b1d8552674079ba65d617a361139cbb1a5c761581e6df7f
6
+ metadata.gz: 73cf557ebcf3747e6878e56fb045586573d8b6b2883717fc092a4a85572c3e93361234877deaf4f5aa44af4ae47ac3b6b2c007a707b3cbb57db03afa0af5234b
7
+ data.tar.gz: 8182bf944bee9bdf1afb61517a700aaa87e99b45a2a19f6c405d1840ca1adc5eb866fca462b94cf96af97d7cad227f88e4a259f6c50a7737d6926af437a9fa4a
data/README.md CHANGED
@@ -119,48 +119,17 @@ a backup, put them there so the plugin finds them.
119
119
  ### WordPress XML
120
120
 
121
121
  If you have the XML dump from a WordPress site, you can migrate content
122
- by writing a migration plugin.
122
+ by using the XML migration plugin and hooking to its steps.
123
123
 
124
124
  ```ruby
125
+ # _plugins/wordpress.rb
125
126
  # frozen_string_literal: true
126
127
 
127
- require 'sutty_migration/jekyll/document_creator'
128
- require 'sutty_migration/wordpress_xml'
129
- require 'jekyll-write-and-commit-changes'
130
- require 'securerandom'
131
-
132
- # Run after reading the site
133
- Jekyll::Hooks.register :site, :post_read do |site|
134
- # Put the XML dump at _files/wordpress.xml
135
- xml = SuttyMigration::WordpressXml.new site: site, file: '_files/wordpress.xml'
136
-
137
- # Download all files
138
- xml.attachments.values.map(&:download)
139
-
140
- # Migrate posts. You can move metadata around and recover
141
- # relationships or any info your theme requires.
142
- xml.posts.values.each do |post|
143
- # Update documents already migrated.
144
- doc = Jekyll::Document.find_or_create(site: site, collection: locale, title: post.title, slug: post.slug, date: post.date)
145
- # Don't change the UUIDv4
146
- d.data['uuid'] ||= SecureRandom.uuid
147
- d.data['draft'] = post.draft?
148
- d.data['layout'] = 'post'
149
- d.data['last_modified_at'] = post.last_modified_at
150
-
151
- d.data['categories'] = post.categories.map { |c| c[:title] }
152
- d.data['tags'] = post.tags.map { |t| t[:title] }
153
-
154
- d.data['author'] = post.author[:email]
155
- d.data['description'] = post.description
156
- d.content = post.content
157
-
158
- doc.save
159
- rescue => e
160
- Jekyll.logger.warn "Couldn't migrate #{post.title}"
161
- end
162
-
163
- exit # Stop here
128
+ require 'jekyll-wordpress-xml-migration'
129
+ require 'pry'
130
+
131
+ Jekyll::Hooks.register :wordpress_post, :post_init do |item|
132
+ binding.pry
164
133
  end
165
134
  ```
166
135
 
@@ -10,10 +10,81 @@
10
10
  # can use `wordpress_url`.
11
11
  #
12
12
  # JEKYLL_ENV=migration bundle exec jekyll build
13
- Jekyll::Hooks.register :site, :post_read, priority: :high do |_site|
13
+ Jekyll::Hooks.register :site, :post_read, priority: :high do |site|
14
14
  next unless Jekyll.env == 'migration'
15
15
 
16
- wordpress_xml = '_files/wordpress.xml'
16
+ file = '_files/wordpress.xml'
17
17
 
18
- Jekyll.logger.abort_with 'Migration:', "#{wordpress_xml} is missing!" unless File.exists wordpress_xml
18
+ Jekyll.logger.abort_with 'Migration:', "#{file} is missing!" unless File.exist? file
19
+
20
+ require 'sutty_migration/wordpress_xml'
21
+ require 'sutty_migration/jekyll/document_creator'
22
+ require 'jekyll-write-and-commit-changes'
23
+
24
+ wordpress = SuttyMigration::WordpressXml.new(site:, file:)
25
+ migrated_posts = []
26
+
27
+ Jekyll::Hooks.trigger :migration, :post_init, wordpress
28
+
29
+ wordpress.attachments.values.uniq.each do |attachment|
30
+ Jekyll::Hooks.trigger :wordpress_attachment, :post_init, attachment.item
31
+
32
+ Jekyll::Hooks.trigger :wordpress_attachment, :pre_render, attachment
33
+ # Download and convert to Sutty's public/
34
+ attachment.public_path
35
+ Jekyll::Hooks.trigger :wordpress_attachment, :post_convert, attachment
36
+ Jekyll::Hooks.trigger :wordpress_attachment, :post_render, attachment
37
+ Jekyll::Hooks.trigger :wordpress_attachment, :post_write, attachment
38
+ end
39
+
40
+ Jekyll::Hooks.trigger :migration, :pre_render, wordpress
41
+
42
+ wordpress.posts.each_value do |post|
43
+ Jekyll::Hooks.trigger :wordpress_post, :post_init, post.item
44
+ Jekyll::Hooks.trigger :wordpress_post, :pre_render, post
45
+ Jekyll::Document.find_or_create(site:, slug: post.slug, title: post.title, date: post.date,
46
+ collection: 'posts').tap do |doc|
47
+ # If you want to customize the layout, change it with a :post_init
48
+ # hook.
49
+ doc.data['layout'] = 'post'
50
+ doc.data['permalink'] = post.permalink
51
+ doc.data['created_at'] = post.date
52
+ doc.data['last_modified_at'] = post.last_modified_at&.localtime
53
+ doc.data['uuid'] ||= SecureRandom.uuid
54
+ doc.data['categories'] = post.categories.map { _1[:title] }
55
+ doc.data['tags'] = post.tags.map { _1[:title] }
56
+ doc.data['draft'] = post.draft?
57
+
58
+ unless (attachment_id = post.meta['_thumbnail_id'].to_i).zero?
59
+ attachment = wordpress.attachments[attachment_id]
60
+
61
+ if attachment
62
+ doc.data['image'] = {
63
+ 'path' => attachment.public_path,
64
+ 'description' => ''
65
+ }
66
+ end
67
+ end
68
+
69
+ Jekyll::Hooks.trigger :wordpress_post, :post_convert, doc
70
+
71
+ doc.content = post.beautiful_content
72
+
73
+ Jekyll::Hooks.trigger :wordpress_post, :post_render, doc
74
+
75
+ migrated_posts << doc
76
+ end
77
+ end
78
+
79
+ Jekyll::Hooks.trigger :migration, :post_convert, wordpress
80
+ Jekyll::Hooks.trigger :migration, :post_render, migrated_posts
81
+
82
+ # Save them all at the end
83
+ migrated_posts.each do |doc|
84
+ doc.save
85
+
86
+ Jekyll::Hooks.trigger :wordpress_post, :post_write, doc
87
+ end
88
+
89
+ Jekyll::Hooks.trigger :migration, :post_write, migrated_posts
19
90
  end
@@ -83,4 +83,4 @@ module SuttyMigration
83
83
  end
84
84
  end
85
85
 
86
- ::Jekyll::Document.include SuttyMigration::Jekyll::DocumentCreator
86
+ Jekyll::Document.include SuttyMigration::Jekyll::DocumentCreator
@@ -10,6 +10,16 @@ module SuttyMigration
10
10
  class WordpressXml
11
11
  # Represents an attachment or uploaded file.
12
12
  class Attachment < Post
13
+ @@connection_failed = false
14
+
15
+ def self.connection_failed
16
+ @@connection_failed
17
+ end
18
+
19
+ def self.connection_failed=(value)
20
+ @@connection_failed = value
21
+ end
22
+
13
23
  # File URL
14
24
  #
15
25
  # @return [String]
@@ -17,11 +27,19 @@ module SuttyMigration
17
27
  @attachment_url ||= attribute_value('attachment_url') || attribute_value('guid')
18
28
  end
19
29
 
30
+ def parsed_url
31
+ @parsed_url ||= Addressable::URI.parse(attachment_url)
32
+ end
33
+
34
+ def normalized_url
35
+ @normalized_url ||= parsed_url.normalize
36
+ end
37
+
20
38
  # File destination
21
39
  #
22
40
  # @return [String]
23
41
  def dest
24
- @dest ||= CGI.unescape(Addressable::URI.parse(attachment_url).path.sub(%r{\A/}, '').unicode_normalize)
42
+ @dest ||= CGI.unescape(parsed_url.path.sub(%r{\A/}, '').unicode_normalize)
25
43
  end
26
44
 
27
45
  # Metadata, with file information as a Hash
@@ -33,6 +51,8 @@ module SuttyMigration
33
51
  end
34
52
  end
35
53
 
54
+ attr_reader :head
55
+
36
56
  # Download the file if it doesn't exist. Optionally show a
37
57
  # progress bar.
38
58
  #
@@ -40,33 +60,41 @@ module SuttyMigration
40
60
  # @return [Boolean]
41
61
  def download(progress: true)
42
62
  return true if File.exist? dest
63
+ return false if self.class.connection_failed
43
64
 
44
- ::Jekyll.logger.info 'Downloading:', dest
45
-
46
- FileUtils.mkdir_p File.dirname(dest)
47
-
48
- File.open(dest, 'w') do |f|
49
- head = Faraday.head(attachment_url)
50
-
51
- if head.status > 399
52
- ::Jekyll.logger.warn 'Error:', head.status.to_s
65
+ @head =
66
+ begin
67
+ Faraday.head(normalized_url)
68
+ rescue Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ServerError => e
69
+ ::Jekyll.logger.warn 'Error:', e.message
70
+ self.class.connection_failed = true
71
+ return false
72
+ rescue Faraday::Error => e
73
+ ::Jekyll.logger.warn 'Error:', e.message
53
74
  return false
54
75
  end
55
76
 
56
- content_length = head.headers['content-length'].to_i
77
+ if head.status > 399
78
+ ::Jekyll.logger.warn 'Error:', "#{head.status} #{normalized_url}"
79
+ return false
80
+ end
57
81
 
58
- if content_length.zero?
59
- ::Jekyll.logger.warn 'Error:', 'Content length is zero'
60
- return false
61
- end
82
+ content_length = head.headers['content-length'].to_i
62
83
 
63
- if progress
64
- progress_bar = ProgressBar.create(title: File.basename(dest), total: content_length,
65
- output: $stderr)
66
- end
84
+ if content_length.zero?
85
+ ::Jekyll.logger.warn 'Error:', "Content length is zero for #{normalized_url}"
86
+ return false
87
+ end
67
88
 
68
- begin
69
- Faraday.get(attachment_url) do |req|
89
+ ::Jekyll.logger.info 'Downloading:', dest
90
+
91
+ progress_bar =
92
+ (ProgressBar.create(title: File.basename(dest), total: content_length, output: $stderr) if progress)
93
+
94
+ begin
95
+ FileUtils.mkdir_p File.dirname(dest)
96
+ File.open(dest, 'w') do |f|
97
+ Faraday.get(normalized_url) do |req|
70
98
  req.options.on_data = proc do |chunk, downloaded_bytes|
71
99
  if downloaded_bytes > content_length
72
100
  raise Faraday::Error, 'File is bigger than informed, aborting download'
@@ -74,16 +102,18 @@ module SuttyMigration
74
102
 
75
103
  f.write chunk
76
104
 
77
- progress_bar.progress = downloaded_bytes if progress
105
+ progress_bar&.progress = downloaded_bytes
78
106
  end
79
107
  end
80
- rescue Faraday::Error => e
81
- ::Jekyll.logger.warn 'Error:', e.message
82
108
  end
109
+ rescue Faraday::Error => e
110
+ ::Jekyll.logger.warn 'Error:', e.message
111
+ FileUtils.rm_f(File.dirname(dest))
83
112
  end
84
113
 
85
- progress_bar.stop if progress
86
- File.exist? dest
114
+ progress_bar&.stop
115
+
116
+ File.exist?(dest)
87
117
  end
88
118
 
89
119
  # Local path following Sutty's conventions. Instead of producing
@@ -94,35 +124,34 @@ module SuttyMigration
94
124
  #
95
125
  # @return [String]
96
126
  def public_path
127
+ return unless digest
128
+
97
129
  @public_path ||= File.join('public', digest[..27], dest.split('/').last).tap do
98
- next if File.exist? _1
99
-
100
- # If the directory exists and it's not empty, but the file is
101
- # not there, we either have the same file with different names
102
- # or old thumbnails.
103
- dir = File.dirname(_1)
104
- if File.directory?(dir) && !Dir.empty?(dir)
105
- Jekyll::Logger.warn 'Migration:',
106
- "#{dir} is not empty, you probably have the same file with different names"
130
+ # If there's a file with the same digest, we replace the
131
+ # public_path so we deduplicate them.
132
+ if (other = wordpress.attachments[digest]) == self
133
+ next if File.exist? _1
134
+
135
+ FileUtils.mkdir_p(File.dirname(_1))
136
+ FileUtils.ln(dest, _1)
137
+ else
138
+ _1.replace other.public_path
107
139
  end
108
-
109
- FileUtils.mkdir_p(File.dirname(_1))
110
- FileUtils.ln(dest, _1)
111
140
  end
112
141
  end
113
142
 
114
- # Download and produce a digest. Produce an error if the file
115
- # can't be downloaded.
143
+ # Download and produce a digest if the file can be downloaded.
144
+ # Index by digest too.
116
145
  #
117
146
  # @return [String]
118
147
  def digest
119
148
  @digest ||=
120
- begin
149
+ if download
121
150
  require 'digest'
122
151
 
123
- raise Faraday::Error unless download
124
-
125
- Digest::SHA2.file(dest).hexdigest
152
+ Digest::SHA2.file(dest).hexdigest.tap do |digest|
153
+ wordpress.attachments[digest] ||= self
154
+ end
126
155
  end
127
156
  end
128
157
  end
@@ -25,7 +25,7 @@ module SuttyMigration
25
25
  data-turbo href id name referrerpolicy rel reversed rowspan src start target title type]
26
26
  # rubocop:enable Style/MutableConstant
27
27
  SLASH = '/'
28
- THUMBNAIL_PREFIX = /(-scaled)?(-\d+x\d+)?(\.....?)\z/
28
+ THUMBNAIL_PREFIX = /-\d+x\d+(\.....?)\z/
29
29
  # TODO: Move to wordpress-formatting
30
30
  WORDPRESS_ELEMENT_START = /\[(\w+ [^\]]+)\]/
31
31
  WORDPRESS_ELEMENT_END = %r{\[(/\w+)\]}
@@ -50,13 +50,13 @@ module SuttyMigration
50
50
 
51
51
  # Permalink. Absolute URL to the post.
52
52
  #
53
- # @return [String]
53
+ # @return [String, nil]
54
54
  def permalink
55
55
  @permalink ||=
56
56
  begin
57
- p = attribute_value('link')&.delete(wordpress.url)
57
+ p = attribute_value('link')&.sub(wordpress.url, '')&.split('?', 2)&.first
58
58
 
59
- unless p.nil?
59
+ unless p.nil? || p.blank?
60
60
  p << SLASH if !p.end_with?(SLASH) && File.extname(p).empty?
61
61
 
62
62
  p.squeeze(SLASH)
@@ -104,10 +104,10 @@ module SuttyMigration
104
104
  #
105
105
  # @return [Time, nil]
106
106
  def last_modified_at
107
- @last_modified_at ||=
108
- if (date = attribute_value('post_modified_gmt'))
109
- ::Jekyll::Utils.parse_date(date)
110
- end
107
+ @last_modified_at ||= %w[post_modified_gmt post_modified].map do |date_attr|
108
+ ::Jekyll::Utils.parse_date attribute_value(date_attr)
109
+ rescue StandardError
110
+ end.compact.first
111
111
  end
112
112
 
113
113
  # Content as an HTML string, converting Wordpress [tags] to <tags>
@@ -135,10 +135,17 @@ module SuttyMigration
135
135
  @parsed_content ||= Nokogiri::HTML.fragment(sanitized_content).tap do |html|
136
136
  html.css('[src]').each do |element|
137
137
  # Remove the thumbnail suffix, we only want the original files
138
- element['src'] = element['src'].sub(THUMBNAIL_PREFIX, '\\3')
138
+ element['src'] = element['src'].sub(THUMBNAIL_PREFIX, '\\1')
139
139
 
140
140
  if element['src'].start_with? wordpress.url
141
- element['src'] = wordpress.attachments[CGI.unescape element['src']].public_path
141
+ attachment = wordpress.attachments[CGI.unescape element['src']]
142
+
143
+ unless attachment
144
+ ::Jekyll.logger.warn 'Error:', "Not found #{element['src']}"
145
+ next
146
+ end
147
+
148
+ element['src'] = attachment.public_path
142
149
 
143
150
  # Move the element into a <figure> like our editor does
144
151
  unless element.parent && element.parent.name == '#document-fragment'
@@ -15,7 +15,13 @@ module SuttyMigration
15
15
  def initialize(site:, file:)
16
16
  @site = site
17
17
  @file = file
18
- @xml = Nokogiri::XML File.read(file)
18
+ @xml = Nokogiri::XML File.read(file).delete("\b").delete("\u0002")
19
+
20
+ if xml.errors.empty?
21
+ ::Jekyll.logger.info 'Migration:', "#{file} parsed correctly"
22
+ else
23
+ ::Jekyll.logger.warn 'Migration:', "#{xml.errors.count} parsing errors found on #{file}!"
24
+ end
19
25
 
20
26
  # Make things easier by removing namespaces.
21
27
  xml.remove_namespaces!
@@ -108,8 +114,8 @@ module SuttyMigration
108
114
  #
109
115
  # @return [Hash]
110
116
  def posts
111
- @posts ||= items_find_by('post_type', 'post').map do |post|
112
- { attribute_value(post, 'post_id').to_i => Post.new(wordpress: self, item: post) }
117
+ @posts ||= items_find_by('post_type', 'post').map do |item|
118
+ { attribute_value(item, 'post_id').to_i => Post.new(wordpress: self, item:) }
113
119
  end.reduce(&:merge)
114
120
  end
115
121
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sutty-migration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0rc0
4
+ version: 0.4.0rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - f
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-16 00:00:00.000000000 Z
11
+ date: 2026-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll