170 lines
3.7 KiB
Ruby
170 lines
3.7 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
require 'jekyll/utils'
|
||
|
|
||
|
# Esta clase representa un post en un sitio jekyll e incluye métodos
|
||
|
# para modificarlos y crear nuevos
|
||
|
class Post
|
||
|
attr_accessor :content, :front_matter
|
||
|
attr_reader :post, :site, :errors
|
||
|
|
||
|
REJECT_FROM_DATA = %w[excerpt slug draft date ext].freeze
|
||
|
|
||
|
# Trabajar con posts. Si estamos creando uno nuevo, el **site** y
|
||
|
# el **front_matter** son necesarios, sino, **site** y **post**.
|
||
|
# XXX chequear que se den las condiciones
|
||
|
def initialize(site:, post:, front_matter: {})
|
||
|
@site = site
|
||
|
@post = post
|
||
|
@errors = []
|
||
|
@front_matter = front_matter
|
||
|
|
||
|
new_post! unless @post
|
||
|
load_data! unless new?
|
||
|
end
|
||
|
|
||
|
# Si el archivo destino no existe, el post es nuevo
|
||
|
def new?
|
||
|
!File.exist? @post.path
|
||
|
end
|
||
|
|
||
|
# Guarda los cambios
|
||
|
def save
|
||
|
merge_data_with_front_matter!
|
||
|
clean_content!
|
||
|
|
||
|
return unless write
|
||
|
return unless detect_file_rename!
|
||
|
|
||
|
# Vuelve a leer el post para tomar los cambios
|
||
|
@post.read
|
||
|
true
|
||
|
end
|
||
|
alias :save! :save
|
||
|
|
||
|
def title
|
||
|
get_metadata 'title'
|
||
|
end
|
||
|
|
||
|
def date
|
||
|
get_metadata 'date'
|
||
|
end
|
||
|
|
||
|
def tags
|
||
|
get_metadata 'tags'
|
||
|
end
|
||
|
|
||
|
def categories
|
||
|
get_metadata 'categories'
|
||
|
end
|
||
|
alias :category :categories
|
||
|
|
||
|
def path
|
||
|
@post.try :path || File.join(@site.path, '_posts', basename_from_front_matter)
|
||
|
end
|
||
|
|
||
|
def id
|
||
|
get_metadata 'slug'
|
||
|
end
|
||
|
alias :slug :id
|
||
|
alias :to_s :id
|
||
|
|
||
|
def basename_changed?
|
||
|
@post.basename != basename_from_front_matter
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def new_post
|
||
|
opts = { site: @site, collection: @site.posts }
|
||
|
@post = ::Jekyll::Document.new(path, opts)
|
||
|
@site.posts.docs << @post
|
||
|
@site.posts.docs.sort!
|
||
|
|
||
|
@post
|
||
|
end
|
||
|
|
||
|
# Obtiene metadatos
|
||
|
def get_metadata(name)
|
||
|
new? ? @front_matter.dig(name) : @post.data[name]
|
||
|
end
|
||
|
|
||
|
# Cambiar el nombre del archivo si cambió el título o la fecha.
|
||
|
# Como Jekyll no tiene métodos para modificar un Document, lo
|
||
|
# engañamos eliminando la instancia de @post y recargando otra.
|
||
|
def detect_file_rename!
|
||
|
return true unless basename_changed?
|
||
|
|
||
|
if File.exist? path
|
||
|
@errors << 'El archivo destino ya existe'
|
||
|
return
|
||
|
end
|
||
|
|
||
|
FileUtils.mv @post.path, path
|
||
|
replace_post! path
|
||
|
end
|
||
|
|
||
|
def replace_post!(path)
|
||
|
@site.posts.docs.delete @post
|
||
|
|
||
|
new_post
|
||
|
end
|
||
|
|
||
|
# Obtiene el nombre del archivo a partir de los datos que le
|
||
|
# pasemos
|
||
|
def basename_from_front_matter
|
||
|
date = @front_matter[:date].strftime('%F')
|
||
|
title = ::Jekyll::Utils.slugify(@front_matter[:title])
|
||
|
ext = @post.try :ext || 'markdown'
|
||
|
|
||
|
"#{date}-#{title}.#{ext}"
|
||
|
end
|
||
|
|
||
|
# Toma los datos del front matter local y los mueve a los datos
|
||
|
# que van a ir al post. Si hay símbolos se convierten a cadenas,
|
||
|
# porque Jekyll trabaja con cadenas.
|
||
|
def merge_data_with_front_matter!
|
||
|
@data.merge! Hash[@front_matter.map { |k, v| [k.to_s, v] }]
|
||
|
end
|
||
|
|
||
|
# Carga una copia de los datos del post original excluyendo datos
|
||
|
# que no nos interesan
|
||
|
def load_data!
|
||
|
@data ||= @post.data.reject do |key, _|
|
||
|
REJECT_FROM_DATA.include? key
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Aplica limpiezas básicas del contenido
|
||
|
def clean_content!
|
||
|
@content = @content.delete("\r")
|
||
|
end
|
||
|
|
||
|
# Guarda los cambios en el archivo destino
|
||
|
def write
|
||
|
r = File.open(@post.path, File::RDWR | File::CREAT, 0o640) do |f|
|
||
|
# Bloquear el archivo para que no sea accedido por otro
|
||
|
# proceso u otra editora
|
||
|
f.flock(File::LOCK_EX)
|
||
|
|
||
|
# Empezar por el principio
|
||
|
f.rewind
|
||
|
|
||
|
# Escribir
|
||
|
f.write(full_content)
|
||
|
|
||
|
# Eliminar el resto
|
||
|
f.flush
|
||
|
f.truncate(f.pos)
|
||
|
end
|
||
|
|
||
|
return true if r.zero?
|
||
|
|
||
|
@errors << 'No se pudo escribir el archivo'
|
||
|
false
|
||
|
end
|
||
|
|
||
|
def full_content
|
||
|
"#{@data.to_yaml}---\n\n#{@content}"
|
||
|
end
|
||
|
end
|