sutty/app/models/jekyll_i18n.rb
2019-03-26 12:32:20 -03:00

92 lines
2.3 KiB
Ruby

# frozen_string_literal: true
# Esta clase se encarga de guardan los archivos YAML de idiomas
#
# TODO se podría convertir en una clase genérica de editor de YAML
class JekyllI18n
attr_reader :site, :lang, :attributes
def initialize(site:, lang:, attributes:)
unless site.is_a? Site
raise ArgumentError, I18n.t('errors.argument_error', argument: :site, class: Site)
end
unless I18n.available_locales.include? lang.to_sym
raise ArgumentError, I18n.t('errors.unknown_locale', locale: lang)
end
@site = site
@lang = lang.to_sym
@attributes = attributes.to_hash # porque enviamos parametros
end
# Vuelva los datos a YAML y los guarda en el archivo correspondiente
#
# TODO es necesario evitar que se agreguen llaves nuevas? No veo nada
# inseguro...
#
# https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
#
# Pero parece que ya se resolvió hace rato. En general es mala
# práctica aceptar cualquier input de las usuarias, pero en este caso
# parece que no nos tenemos que preocupar?
#
# En cualquier caso habría que hacer un deep_merge, descartando las
# llaves que no están en el original y/o en el destino (pero queremos
# que el destino se parezca al original) y chequeando que no haya
# deserialización de objetos
#
# Jekyll usa SafeYAML para cargar los datos, con lo que estaríamos
# bien en cuanto a inyección de código.
def save
# Reemplaza los datos en el sitio
replace_lang_in_site
# Escribe los cambios en disco
write
end
# Obtiene la ruta a partir del sitio y el idioma
def path
File.join(@site.path, '_data', "#{@lang}.yml")
end
def exist?
File.exist? path
end
private
def replace_lang_in_site
@site.data[@lang.to_s] = @attributes
end
# Escribe los cambios en disco
#
# TODO unificar con Post.write
def write
r = File.open(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(content)
# Eliminar el resto
f.flush
f.truncate(f.pos)
end
return true if r.zero?
false
end
def content
@attributes.to_yaml
end
end