sutty-migration 0.1.2 → 0.2.0

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: ffe46cc7d270c7f30d4a505704cb244e507df588bb380ad2c101c3a01675844b
4
- data.tar.gz: d63a9dd7fde09627c61f02f7b714e151f5122d5133e26d8c4d3be7917d35501e
3
+ metadata.gz: 046cf945de1c0736e329224151a4b331c87e44cab6e6c2bc1e69f22b7639fe68
4
+ data.tar.gz: 58effbee202ab51c7ff1ed4e8e98498cd5a65f618c6ae6fc4f924fe6aed2e0e1
5
5
  SHA512:
6
- metadata.gz: 9c9278d28ab6d4b862c5cc615941102c5ef8716c30b674093dd31f5a6dce1c337ff6729fab69e6018263a113b3b82f8faadc9b8aad2a9bc38cfd472d082d711c
7
- data.tar.gz: a016f1a5ff26e8c8c5c1c95d0393f6ebe497919b0301282d412440779ec44fbd0c06e7b7ba95a8d30169d3c936dd4eaf5d3f8e59e8fdd6c563504b6027125a02
6
+ metadata.gz: a21cb549bddd9bc55218c0633932300811e547d2d4cfde2525fbcdcaf8a2ff3bb89f0542813b46fdb9b90c9d739166ce37e303fb4de76b918b52bee9402fcb6d
7
+ data.tar.gz: 544f18359b4e9996c07f6828643bb8d4856d55457b4a6134b2ef2cedf01d4471d294effc6024598ef66f9b1d2258345f44ac370e1678166a2b9d19b5c4a55e74
data/README.md CHANGED
@@ -66,6 +66,54 @@ To start migration just build your site:
66
66
  bundle exec jekyll build
67
67
  ```
68
68
 
69
+ **Tip:** Files can also be JSON, TSV and YAML, since they're all
70
+ supported by Jekyll.
71
+
72
+ ### Wordpress
73
+
74
+ Instead of requiring you to install and configure MariaDB/MySQL, you can
75
+ convert the database into SQLite3 like this:
76
+
77
+ ```bash
78
+ git clone https://0xacab.org/sutty/mysql2sqlite.git
79
+ cd mysql2sqlite
80
+ ./mysql2sqlite /path/to/database/dump.sql |
81
+ sed -re "s/, 0x([0-9a-f]+),/, X'\1',/i" |
82
+ sqlite3 wordpress.sqlite3
83
+ ```
84
+
85
+ It will probably show some errors.
86
+
87
+ Note the `sed` command is required to convert hexadecimal values into
88
+ SQLite syntax, since `mysql2sqlite` doesn't support this yet.
89
+
90
+ Wordpress websites can include lots of posts and metadata, depending on
91
+ the amount of plugins installed. We don't have an official way of
92
+ dumping everything into Jekyll, because you will probably want to move
93
+ things around. You can write a plugin like this:
94
+
95
+ ```ruby
96
+ # _plugins/wordpress.rb
97
+ # frozen_string_literal: true
98
+
99
+ require 'sutty_migration/wordpress'
100
+ require 'sutty_migration/jekyll/document_creator'
101
+ require 'jekyll-write-and-commit-changes'
102
+
103
+ Jekyll::Hooks.register :site, :post_read, priority: :low do |site|
104
+ wp = SuttyMigration::Wordpress.new(site: site, database: 'wordpress.sqlite3', prefix: 'wp_', url: 'https://wordpre.ss')
105
+
106
+ # Download all files
107
+ wp.download_all
108
+
109
+ wp.posts(layout: 'post').each do |post|
110
+ doc = Jekyll::Document.create(site: site, title: post[:post_title], date: post[:post_date], collection: 'posts')
111
+ doc.content = post[:content]
112
+ doc.save
113
+ end
114
+ end
115
+ ```
116
+
69
117
  ## Contributing
70
118
 
71
119
  Bug reports and pull requests are welcome on 0xacab.org at
@@ -1,71 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
4
- require 'fast_blank'
5
- require 'jekyll-write-and-commit-changes'
6
-
7
- Jekyll::Hooks.register :site, :post_read, priority: :low do |site|
8
- documents = site.documents
9
-
10
- site.data['layouts']&.each do |name, layout|
11
- site.data.dig('migration', name)&.each do |row|
12
- row['date'] = Jekyll::Utils.parse_date(row['date']) unless row['date'].blank?
13
-
14
- if row['id']
15
- document = documents.find do |doc|
16
- doc.data['id'] == row['id']
17
- end
18
- end
19
-
20
- document ||=
21
- begin
22
- base = "#{row['date'] || Date.today.to_s}-#{Jekyll::Utils.slugify(row['title'], mode: 'latin')}.markdown"
23
- path = File.join(site.source, '_posts', base)
24
-
25
- raise ArgumentError, "Row #{row['id']} duplicates file #{base}" if File.exist? path
26
-
27
- doc = Jekyll::Document.new(path, site: site, collection: site.collections['posts'])
28
- site.collections['posts'] << doc
29
-
30
- doc
31
- end
32
-
33
- row.each do |attribute, value|
34
- row[attribute] =
35
- case layout.dig(attribute, 'type')
36
- when 'string' then value
37
- when 'text' then value
38
- when 'tel' then value
39
- when 'color' then value # TODO: validar
40
- when 'date' then Jekyll::Utils.parse_date(value)
41
- when 'email' then value # TODO: validar
42
- when 'url' then value # TODO: validar
43
- when 'content' then value
44
- when 'markdown_content' then value
45
- when 'markdown' then value
46
- when 'number' then value.to_i
47
- when 'order' then value.to_i
48
- when 'boolean' then !value.strip.empty?
49
- when 'array' then value.split(',').map(&:strip)
50
- # TODO: procesar los valores en base a los valores predefinidos
51
- when 'predefined_array' then value.split(',').map(&:strip)
52
- when 'image' then { 'path' => value, 'description' => '' }
53
- when 'file' then { 'path' => value, 'description' => '' }
54
- when 'geo' then %w[lat lng].zip(value.split(',', 2).map(&:to_f)).to_h
55
- when 'belongs_to' then value
56
- when 'has_many' then value.split(',').map(&:strip)
57
- when 'has_and_belongs_to_many' then value.split(',').map(&:strip)
58
- when 'related_posts' then value.split(',').map(&:strip)
59
- when 'locales' then value.split(',').map(&:strip)
60
- else value
61
- end
62
- end
63
-
64
- document.data['uuid'] ||= SecureRandom.uuid
65
- document.content = row.delete('content')
66
-
67
- document.data.merge! row
68
- document.save
69
- end
70
- end
71
- end
3
+ require_relative 'sutty_migration/data'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Expandir String para poder verificar si está vacía
4
+ require 'fast_blank'
5
+
6
+ # Verificar que los valores nulos estén vacíos
7
+ class NilClass
8
+ def blank?
9
+ true
10
+ end
11
+
12
+ def present?
13
+ false
14
+ end
15
+ end
16
+
17
+ # Verificar que una fecha está vacía
18
+ class Time
19
+ def blank?
20
+ false
21
+ end
22
+
23
+ def present?
24
+ true
25
+ end
26
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'core_extensions'
5
+ require_relative 'jekyll/document_creator'
6
+
7
+ # Registers a plugin for converting CSV files into posts following
8
+ # Sutty's layout definition.
9
+ #
10
+ # If jekyll-write-and-commit-changes is enabled, documents will be saved
11
+ # on disk and commited is the build command is run with
12
+ # JEKYLL_ENV=production
13
+ Jekyll::Hooks.register :site, :post_read, priority: :low do |site|
14
+ documents = site.documents
15
+
16
+ site.data['layouts']&.each do |name, layout|
17
+ site.data.dig('migration', name)&.each do |row|
18
+ row['date'] = Jekyll::Utils.parse_date(row['date']) unless row['date'].blank?
19
+ row['date'] ||= Time.now
20
+
21
+ unless row['id'].blank?
22
+ document = documents.find do |doc|
23
+ doc.data['id'] == row['id']
24
+ end
25
+ end
26
+
27
+ document ||= Jekyll::Document.create(site: site, collection: 'posts', **row.slice(*%w[date slug title]).transform_keys(&:to_sym))
28
+
29
+ row.each do |attribute, value|
30
+ next unless value.blank?
31
+
32
+ row[attribute] =
33
+ case layout.dig(attribute, 'type')
34
+ when 'string' then value
35
+ when 'text' then value
36
+ when 'tel' then value
37
+ # TODO: validate
38
+ when 'color' then value
39
+ when 'date' then Jekyll::Utils.parse_date(value)
40
+ # TODO: validate
41
+ when 'email' then value
42
+ # TODO: validate
43
+ when 'url' then value
44
+ when 'content' then value
45
+ when 'markdown_content' then value
46
+ when 'markdown' then value
47
+ when 'number' then value.to_i
48
+ when 'order' then value.to_i
49
+ when 'boolean' then !value.strip.empty?
50
+ when 'array' then value.split(',').map(&:strip)
51
+ # TODO: process values from the default array
52
+ when 'predefined_array' then value.split(',').map(&:strip)
53
+ when 'image' then { 'path' => value, 'description' => '' }
54
+ when 'file' then { 'path' => value, 'description' => '' }
55
+ when 'geo' then %w[lat lng].zip(value.split(',', 2).map(&:to_f)).to_h
56
+ when 'belongs_to' then value
57
+ when 'has_many' then value.split(',').map(&:strip)
58
+ when 'has_and_belongs_to_many' then value.split(',').map(&:strip)
59
+ when 'related_posts' then value.split(',').map(&:strip)
60
+ when 'locales' then value.split(',').map(&:strip)
61
+ else value
62
+ end
63
+ end
64
+
65
+ document.data['uuid'] ||= SecureRandom.uuid
66
+ document.content = row.delete('content')
67
+
68
+ document.data.merge! row
69
+ document.save if document.respond_to? :save
70
+ end
71
+ end
72
+
73
+ next unless site.respond_to?(:repository)
74
+ next unless ENV['JEKYLL_ENV'] == 'production'
75
+
76
+ site.repository.commit 'CSV Migration'
77
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll/utils'
4
+ require_relative '../core_extensions'
5
+
6
+ module SuttyMigration
7
+ module Jekyll
8
+ module DocumentCreator
9
+ class DocumentExists < ArgumentError; end
10
+ def self.included(base)
11
+ base.class_eval do
12
+
13
+ # Creates a new document in a collection or fails if it already
14
+ # exists.
15
+ #
16
+ # @param :site [Jekyll::Site] Jekyll site
17
+ # @param :date [Time] Post date
18
+ # @param :title [String] Post title
19
+ # @param :slug [String] Post slug, slugified title if empty
20
+ # @param :collection [Jekyll::Collection,String] Collection label or collection
21
+ # @return [Jekyll::Document] A new document
22
+ def self.create(site:, date:, title:, slug: nil, collection:)
23
+ collection = site.collections[collection] if collection.is_a? String
24
+ slug = ::Jekyll::Utils.slugify(title, mode: 'latin') if slug.blank?
25
+ basename = "#{date.strftime('%F')}-#{slug}.markdown"
26
+ path = File.join(collection.directory, basename)
27
+
28
+ raise DocumentExists, "#{path} already exists" if File.exist? path
29
+
30
+ ::Jekyll::Document.new(path, site: site, collection: collection).tap do |document|
31
+ collection.docs << document
32
+ document.data['title'] = title
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ ::Jekyll::Document.include SuttyMigration::Jekyll::DocumentCreator
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'securerandom'
5
+ require 'sequel'
6
+ require 'sqlite3'
7
+ require 'json'
8
+ require 'faraday'
9
+ require 'progressbar'
10
+ require 'jekyll/utils'
11
+
12
+ module SuttyMigration
13
+ # Brings posts and attachments from a SQLite3 database. You can
14
+ # convert a MySQL/MariaDB dump by using `mysql2sqlite`.
15
+ #
16
+ # It doesn't convert them into Jekyll posts but allows you to write a
17
+ # migration plugin where you can convert data by yourself. We may add
18
+ # this feature in the future.
19
+ class Wordpress
20
+ attr_reader :site, :prefix, :limit, :url, :wp, :database, :multisite
21
+
22
+ # @param :site [Jekyll::Site] Jekyll site
23
+ # @param :url [String] Wordpress site URL (must be up for downloads)
24
+ # @param :database [String] Database path, by default `_data/wordpress.sqlite3`
25
+ # @param :prefix [String] WP table prefix
26
+ # @param :limit [Integer] Page length
27
+ # @param :multisite [Boolean] Site is multisite
28
+ def initialize(site:, url:, database: nil, prefix: 'wp_', limit: 10, multisite: nil)
29
+ @site = site
30
+ @prefix = prefix.freeze
31
+ @limit = limit.freeze
32
+ @url = url.freeze
33
+ @database = database || File.join(site.source, '_data', 'wordpress.sqlite3')
34
+ @multisite = multisite
35
+ end
36
+
37
+ # Generate database connections for a multisite WP
38
+ #
39
+ # @return [Hash] { "ID" => SuttyMigration::Wordpress }
40
+ def blogs
41
+ @blogs ||= wp["select blog_id as id, domain, path from #{prefix}blogs"].to_a.map do |blog|
42
+ url = "https://#{blog[:domain]}#{blog[:path]}"
43
+ pfx = "#{prefix}#{blog[:id]}_" if blog[:id] > 1
44
+ pfx ||= prefix
45
+
46
+ [ blog[:id], self.class.new(site: site, url: url, prefix: pfx, database: database, limit: limit, multisite: self) ]
47
+ end.to_h
48
+ end
49
+
50
+ # Open the database.
51
+ #
52
+ # @return [Sequel::SQLite::Database]
53
+ def wp
54
+ @wp ||= Sequel.sqlite(database).tap do |db|
55
+ db.extension :pagination
56
+ end
57
+ end
58
+
59
+ # Download all attachments. Adds the local path to them.
60
+ #
61
+ # @param :progress [Boolean] Toggle progress bar
62
+ # @return [Nil]
63
+ def download_all(progress: true)
64
+ posts(layout: 'attachment').each do |attachment|
65
+ attachment[:front_matter]['file_path'] = download(url: attachment[:guid], progress: progress)
66
+ end
67
+ end
68
+
69
+ # Downloads a file if needed, optionally showing a progress bar.
70
+ #
71
+ # @param :url [String] File URL
72
+ # @param :progress [Boolean] Toggle progress bar
73
+ # @return [String] File local path
74
+ def download(url:, progress: true)
75
+ uri = URI(url)
76
+ dest = uri.path.sub(%r{\A/}, '')
77
+ full = File.join(site.source, dest)
78
+
79
+ return dest if File.exist? full
80
+
81
+ ::Jekyll.logger.info "Downloading #{dest}"
82
+
83
+ FileUtils.mkdir_p File.dirname(full)
84
+
85
+ File.open(full, 'w') do |f|
86
+ if progress
87
+ head = Faraday.head(url)
88
+ content_length = head.headers['content-length'].to_i
89
+ progress = ProgressBar.create(title: File.basename(dest), total: content_length, output: $stderr)
90
+ end
91
+
92
+ Faraday.get(url) do |req|
93
+ req.options.on_data = Proc.new do |chunk, downloaded_bytes|
94
+ f.write chunk
95
+
96
+ if progress
97
+ progress.progress = (downloaded_bytes > content_length) ? content_length : downloaded_bytes
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ dest
104
+ end
105
+
106
+ # List post types
107
+ #
108
+ # @return [Array]
109
+ def layouts
110
+ @layouts ||= wp["select distinct post_type from #{prefix}posts"].to_a.map(&:values).flatten
111
+ end
112
+
113
+ # Finds all posts optionally filtering by post type. This is not
114
+ # the official Sequel syntax, but it retrieves metadata as objects
115
+ # with a single query (and a sub-query).
116
+ #
117
+ # @param :layout [String] Layout name, one of #layouts
118
+ # @param :with_meta [Boolean] Toggle metadata pulling and conversion
119
+ # @return [Enumerator]
120
+ def posts(**options)
121
+ unless options[:layout].blank? || layouts.include?(options[:layout])
122
+ raise ArgumentError, "#{options[:layout]} must be one of #{layouts.join(', ')}"
123
+ end
124
+
125
+ wp[post_query(**options)].each_page(limit).to_a.map(&:to_a).flatten.tap do |p|
126
+ p.map do |post|
127
+ # Sequel parses dates on localtime
128
+ post[:date] = ::Jekyll::Utils.parse_date(post[:date]) unless post[:date].blank?
129
+ post[:last_modified_at] = ::Jekyll::Utils.parse_date(post[:last_modified_at]) unless post[:last_modified_at].blank?
130
+
131
+ post[:front_matter] = JSON.parse(post[:front_matter]).transform_keys(&:to_sym) unless post[:front_matter].blank?
132
+ post[:terms] = JSON.parse(post[:terms]).transform_keys(&:to_sym) unless post[:terms].blank?
133
+ end
134
+ end
135
+ end
136
+
137
+ # Brings all users.
138
+ #
139
+ # @param :with_meta [Boolean] include metadata
140
+ # @return [Array]
141
+ def users(**options)
142
+ options[:with_meta] = true unless options.key? :with_meta
143
+
144
+ wp[user_query(**options)].each_page(limit).to_a.map(&:to_a).flatten.tap do |u|
145
+ next unless options[:with_meta]
146
+
147
+ u.map do |user|
148
+ user[:meta] = JSON.parse(user[:meta]).transform_keys(&:to_sym) unless user[:meta].blank?
149
+ end
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ # Finds all users. If it's a multisite WP, we need to check the
156
+ # main table.
157
+ #
158
+ # @param :with_meta [Boolean] include metadata
159
+ # @return [String]
160
+ def user_query(with_meta: true)
161
+ pfx = multisite&.prefix || prefix
162
+
163
+ <<~EOQ
164
+ select
165
+ u.*
166
+ #{", json_group_object(m.meta_key, m.meta_value) as meta" if with_meta}
167
+ from #{pfx}users as u
168
+ #{"left join #{pfx}usermeta as m on m.user_id = u.id" if with_meta}
169
+ group by u.id
170
+ EOQ
171
+ end
172
+
173
+ # Query for posts, optionally bringing metadata as JSON objects.
174
+ #
175
+ # @param :layout [String] Layout name
176
+ # @param :with_meta [Boolean] Query metadata
177
+ # @return [String]
178
+ def post_query(layout: nil, with_meta: true)
179
+ <<~EOQ
180
+ select
181
+ p.ID as id,
182
+ strftime('%Y-%m-%d %H:%M:%S UTC', p.post_date_gmt) as date,
183
+ strftime('%Y-%m-%d %H:%M:%S UTC', p.post_modified_gmt) as last_modified_at,
184
+ p.post_author as author,
185
+ p.post_type as layout,
186
+ p.post_name as slug,
187
+ p.post_title as title,
188
+ p.post_content as content,
189
+ p.post_excerpt as excerpt,
190
+ p.post_status as status,
191
+ p.comment_status as comment_status,
192
+ p.ping_status as ping_status,
193
+ p.post_password as password,
194
+ p.to_ping as to_ping,
195
+ p.pinged as pinged,
196
+ p.post_content_filtered as content_filtered,
197
+ p.post_parent as parent,
198
+ p.guid as guid,
199
+ p.menu_order as menu_order,
200
+ p.post_mime_type as mime_type,
201
+ p.comment_count as comment_count
202
+ #{", json_group_object(f.meta_key, f.meta_value) as front_matter" if with_meta}
203
+ #{", t.terms as terms" if with_meta}
204
+ from #{prefix}posts as p
205
+ left join #{prefix}postmeta as f on p.ID = f.post_id
206
+ #{"left join (#{terms_query(layout: layout)}) as t on t.id = p.ID" if with_meta}
207
+ #{"where p.post_type = '#{layout}'" if layout}
208
+ group by p.ID
209
+ EOQ
210
+ end
211
+
212
+ # Term taxonomy query
213
+ #
214
+ # @param :layout [String] Layout name
215
+ # @return [String]
216
+ def terms_query(layout: nil)
217
+ <<~EOQ
218
+ select
219
+ p.ID as id,
220
+ json_group_object(tt.taxonomy, t.name) as terms
221
+ from #{prefix}posts as p
222
+ left join #{prefix}term_relationships as r on r.object_id = p.ID
223
+ left join #{prefix}term_taxonomy as tt on tt.term_taxonomy_id = r.term_taxonomy_id
224
+ left join #{prefix}terms as t on t.term_id = tt.term_id
225
+ #{"where p.post_type = '#{layout}'" if layout}
226
+ group by p.ID
227
+ EOQ
228
+ end
229
+ end
230
+ end
data/lib/wordpress.rb CHANGED
@@ -1,20 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Debug
4
- require 'pry'
5
3
  # Generar UUIDs
6
4
  require 'securerandom'
7
5
  # Traer resultados de la base de datos
8
6
  require 'sequel'
9
7
  require 'sqlite3'
10
8
  require 'json'
11
- # Limpieza de contenido
12
- require 'loofah'
13
- require 'rails/html/scrubbers'
14
- require 'rails/html/sanitizer'
15
- require 'reverse_markdown'
16
9
  # Descargar archivos
17
10
  require 'faraday'
11
+ require 'progressbar'
18
12
 
19
13
  class Wordpress
20
14
  attr_reader :site, :prefix, :limit, :url
@@ -33,7 +27,7 @@ class Wordpress
33
27
  end
34
28
 
35
29
  def download(file)
36
- dest = 'wp-content/uploads/' + file
30
+ dest = "wp-content/uploads/#{file}"
37
31
  full = File.join(site.source, dest)
38
32
 
39
33
  return dest if File.exist? full
@@ -43,8 +37,13 @@ class Wordpress
43
37
  FileUtils.mkdir_p File.dirname(full)
44
38
 
45
39
  File.open(full, 'w') do |f|
46
- Faraday.get(url + '/' + dest) do |req|
47
- req.options.on_data = Proc.new do |chunk, _|
40
+ url = "#{url}/#{dest}"
41
+ head = Faraday.head(url)
42
+ content_length = head.headers['content-length']
43
+ progress_bar = ProgressBar.new
44
+
45
+ Faraday.get(url) do |req|
46
+ req.options.on_data = Proc.new do |chunk, downloaded_bytes|
48
47
  f.write chunk
49
48
  end
50
49
  end
@@ -53,56 +52,75 @@ class Wordpress
53
52
  dest
54
53
  end
55
54
 
55
+ # Obtiene todos los tipos de artículos disponibles
56
+ #
57
+ # @return [Array]
58
+ def layouts
59
+ @layouts ||= @wp["select distinct post_type from #{prefix}posts"].to_a.map(&:values).flatten
60
+ end
61
+
56
62
  # Obtiene todos los posts opcionalmente filtrando por tipo de post.
57
63
  # No es la forma oficial de Sequel pero no tenemos tiempo de
58
64
  # aprenderla específicamente y además tenemos las opciones en formato
59
65
  # JSON que no estarían soportadas.
60
- def posts(layout: nil)
61
- query = post_query.dup
62
- query += " where post_type = '#{layout}'" if layout
63
- query += ' group by posts.ID'
66
+ #
67
+ # @param :layout [String] Layout name, one of #layouts
68
+ # @param :with_meta [Boolean]
69
+ # @return [Enumerator]
70
+ def posts(**options)
71
+ if options[:layout] && !layouts.include?(options[:layout])
72
+ raise ArgumentError, "#{layout} must be one of #{layouts.join(', ')}"
73
+ end
64
74
 
65
- @wp[query].each_page(limit)
66
- end
75
+ @posts ||= {}
76
+ @posts[options[:layout] || 'all'] ||= @wp[post_query(**options)].each_page(limit).to_a.map(&:to_a).flatten.tap do |p|
77
+ next unless options[:with_meta]
67
78
 
68
- def meta(id:)
69
- @wp[meta_query(id: id)].to_a
79
+ p.map do |post|
80
+ post[:front_matter] = JSON.parse(post[:front_matter]) unless post[:front_matter].nil?
81
+ post[:terms] = JSON.parse(post[:terms]) unless post[:terms].nil?
82
+ end
83
+ end
70
84
  end
71
85
 
72
86
  private
73
87
 
74
- # Obtener todos los posts, json_objectagg requiere mariadb 10.5
75
- def post_query
88
+ # Consulta para los posts, incluyendo metadatos en JSON. Los
89
+ # metadatos vienen en dos partes porque tienen dos
90
+ #
91
+ # @return [String]
92
+ def post_query(layout: nil, with_meta: true)
76
93
  @post_query ||= <<~EOQ
77
- select ID as id,
78
- post_title as title,
79
- post_name as slug,
80
- post_type as layout,
81
- strftime('%Y-%m-%d', post_date) as date,
82
- post_status as status,
83
- post_content as content,
84
- json_group_object(meta_key, meta_value) as data
85
- from #{prefix}posts as posts
86
- left join #{prefix}postmeta as frontmatter
87
- on posts.ID = frontmatter.post_id
94
+ select
95
+ p.ID as id,
96
+ p.post_title as title,
97
+ p.post_name as slug,
98
+ p.post_type as layout,
99
+ p.strftime('%Y-%m-%d', post_date) as date,
100
+ p.post_status as status,
101
+ p.post_content as content
102
+ #{", json_group_object(f.meta_key, f.meta_value) as front_matter" if with_meta}
103
+ #{", t.meta as meta" if with_meta}
104
+ from #{prefix}posts as p
105
+ left join #{prefix}postmeta as f on p.ID = f.post_id
106
+ #{"left join (#{meta_query(layout: layout)}) as as t on t.id = p.ID" if with_meta}
107
+ #{"where p.post_type = :layout" if layout}
108
+ group by p.ID
88
109
  EOQ
89
110
  end
90
111
 
91
- def meta_query(id:)
92
- <<~EOQ
93
- SELECT
94
- terms.name AS `name`,
95
- ttax.taxonomy AS `type`,
96
- ttax.parent AS `parent`,
97
- ttax.term_id AS `id`
98
- FROM
99
- #{prefix}terms AS `terms`,
100
- #{prefix}term_relationships AS `trels`,
101
- #{prefix}term_taxonomy AS `ttax`
102
- WHERE
103
- trels.object_id = '#{id}' AND
104
- trels.term_taxonomy_id = ttax.term_taxonomy_id AND
105
- terms.term_id = ttax.term_id
112
+ #
113
+ def meta_query(layout: nil)
114
+ @meta_query ||= <<~EOQ
115
+ select
116
+ p.ID as id,
117
+ json_group_object(tt.taxonomy, t.name) as meta
118
+ from #{prefix}posts as p
119
+ left join #{prefix}term_relationships as r on r.object_id = p.ID
120
+ left join #{prefix}term_taxonomy as tt on tt.term_taxonomy_id = r.term_taxonomy_id
121
+ left join #{prefix}terms as t on t.term_id = tt.term_id
122
+ #{"where p.post_type = :layout" if layout}
123
+ group by p.ID
106
124
  EOQ
107
125
  end
108
126
  end
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.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - f
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-28 00:00:00.000000000 Z
11
+ date: 2021-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -52,6 +52,76 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: progressbar
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.11'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sequel
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5.45'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5.45'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
55
125
  description: Takes datafiles and converts them into posts
56
126
  email:
57
127
  - f@sutty.nl
@@ -64,6 +134,10 @@ files:
64
134
  - LICENSE.txt
65
135
  - README.md
66
136
  - lib/sutty-migration.rb
137
+ - lib/sutty_migration/core_extensions.rb
138
+ - lib/sutty_migration/data.rb
139
+ - lib/sutty_migration/jekyll/document_creator.rb
140
+ - lib/sutty_migration/wordpress.rb
67
141
  - lib/wordpress.rb
68
142
  homepage: https://0xacab.org/sutty/jekyll/sutty-migration
69
143
  licenses: