Ya no estoy usando Geminator, en su lugar estoy empleando dos scripts en Lua: compone y publica.
El primero sirve para componer el artículo añadiendo cabecera y pie de página al borrador, moviéndolo al directorio «programados».
El segundo comprueba si la fecha prevista de publicación coincide con la fecha de hoy y, de ser así, mueve el archivo al directorio «articulos» (sin acento por no dificultar la programación) y añade un enlace a los índices de mi web (yretek.com/index.gmi yretek.com/todo.gmi e yretek.com/articulos/index.gmi).
Lo hago en dos scripts por simplificar todo. La filosofía de un programa pequeño que haga una cosa, pero lo haga bien, me parece una magnífica idea. Además últimamente estaba editando y publicando en momentos diferentes así que tenía sentido. Y si quiero hacerlo en un solo momento basta con ```lua compone.lua && lua publica.lua```. Notaréis que no los he hecho auto-ejecutables, porque tampoco le veo la necesidad, la verdad.
Ambos tienen una licencia GNU-GPL, por tener buenos modales más que nada, porque están pensados para mi propia cápsula y necesidades. No obstante, si los consideras de utilidad no tengas reparo en cortar, pegar y modificarlos a gusto y necesidad.
He puesto los comentarios en inglés, porque francamente estar pasando del inglés al español es un lío. Sobre todo con los nombres de las variables. Pero vamos, está en un inglés muy sencillito.
Paso ahora a incluir el código de cada uno ---aunque la licencia incluye ambos aquí lo pongo solo una vez y al final del artículo--- junto con una breve explicación.
-- Compone --[[ Adds header, footer to a draft file, building a "blog" gemtext entry The resulting file is then written on the ../programados folder ]] -- Functions function read_text_file(f_name) -- Takes the contents from f_name Returns it as a string t_str local f_ile = io.open(f_name, 'r') local t_str = f_ile:read('*all') f_ile:close() return t_str end function write_text_file(f_name, texto) -- Writes texto on file f_name local f_ile = io.open(f_name, 'w') io.output(f_ile) io.write(texto) io.close(f_ile) end function ask_user(prompt_str) -- prompts user prompt_str, gets answer io.stdout:write(prompt_str .. " > ") local answer_str = io.stdin:read() return answer_str end function is_file_here(file_name) local was_found = false i_file = io.open(file_name, i) if i_file~=nil then io.close(i_file) was_found = true end return was_found end function ensures_dot_gmi(in_str) -- Appends ".gmi" to in_str if in_str has no .gmi extension local out_str = in_str local ext = string.sub(in_str,-4,-1) if ext ~= ".gmi" then out_str = out_str..".gmi" end return out_str end function split_paragraph(in_str) -- Splits a paragraph into first and other_lines local i = string.find(in_str,'\n') if i ~= nil then local first_line = string.sub(in_str,1,i-1) local other_lines = string.sub(in_str,i+1,-1) return first_line, other_lines else return in_str, "" end end function strip(in_str) -- Strips spaces from in_str (right and left) -- " Hola mundo " -> "Hola mundo" local r = string.find(in_str,"%s+\$") local out_str = in_str if r ~= nil then out_str = string.sub(in_str, 1, r-1) end local _, l = string.find(out_str,"^%s+") if l ~= nil then out_str = string.sub(out_str, l+1,-1) end return out_str end function get_word_count(f_name) -- returns the word count from a file named f_name os.execute("wc -w " .. f_name .. " > .words.txt") local raw_wc_str = read_text_file(".words.txt") local wc_str = string.sub(raw_wc_str, string.find(raw_wc_str, "%d+%s")) return "* Palabras: "..wc_str.."\n" end --[[ ·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·- Main ----------------------------------------------- ·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·- ]] scheduled_dir = "programados/" today_str = os.date('%Y-%m-%d') -- Gets draft file name from user prompt_str = "¿Cuál es el archivo de borrador? %%% Para cancelar" repeat draft_file_str = ask_user(prompt_str) if draft_file_str == "%%%" then os.exit() end prompt_str = "Fichero no encontrado.\nUse otro nombre.\n%%% detiene el programa." until is_file_here(draft_file_str) -- Gets to_publication date from user repeat prompt_str = "Fecha de publicación, enter para hoy" raw_date_str = ask_user(prompt_str) until string.len(raw_date_str) == 10 or raw_date_str == "" -- minimum validation if raw_date_str == "" then date_str = today_str else date_str = raw_date_str end raw_text_str = read_text_file(draft_file_str) title_str, draft_text_str = split_paragraph(raw_text_str) title_str = strip(title_str) if title_str == "" then title_str = "Sin título" end draft_text_str = strip(draft_text_str) word_count_str = get_word_count(draft_file_str) header_str = "# Yretek 🍃 " .. title_str .. "\n\n" footer_title_str = "\n\n### Pie de página" .. "\n" date_field_str = "* Fecha: " .. date_str .. "\n" author_str = [[ ~ Miguel de Luis Espinosa => mailto:yretek@proton.me yretek@proton.me ]] footer_str = footer_title_str .. date_field_str .. word_count_str .. author_str to_write_str = header_str .. draft_text_str .. footer_str out_file_name = date_str .. "_" .. draft_file_str out_file_name = ensures_dot_gmi(out_file_name) write_text_file(out_file_name, to_write_str) -- a last look at the entry os.execute("vim "..out_file_name) -- moves entry to its directory os.execute("mv "..out_file_name.." "..scheduled_dir ) -- Ta da!
Vale, esta es la secuencia del programa:
1. Pide al usuario el nombre del fichero de borrador.
2. Valida el input (determina si el fichero existe y si es así continua, si no repite la pregunta.)
3. Pide al usuario la fecha deseada de publicación (no tengo todavía una buena validación)
4. Asigna como título del artículo la primera línea del borrador.
5. Crea la cabecera (El símbolo .. en Lua concatena, o sea, una el texto, para un ordenador un texto se parece a una cadena en la que cada eslabón es un carácter, letra o número etc)
6. Crea el pie de página
7. Lo concatena todo, cabecera, contenido y pie en una sola cadena de texto.
8. Guarda el resultado anterior en un archivo.
9. Llama al mejor editor de texto del universo, o sea vim, para que yo pueda revisar el resultado.
10. Cuando termina lo mueve al directorio de "programados"
--[[ Examines the contents of the "programados" folder, determines which are to be published the day this script is run. Those who are get moved to the "articulos" directory Links are then inserted into index files This is meant for my capsule only. Nevertheless the code is offered as reference. These are gemtext files which filenames follow the format of yyyy-mm-dd_file_title.gmi ]] function read_text_file(f_name) -- Takes the contents from f_name -- Returns it as a string t_str f_ile = io.open(f_name, 'r') t_str = f_ile:read('*all') f_ile:close() return t_str end function del_rpt_consecutive_lines(in_parg, p_line, out_parg) -- deletes consecutive repeated lines in a paragraph p_line = p_line or "" -- previous line in_parg = in_parg or "" -- in paragraph out_parg = out_parg or "" -- out paragraph fl,ol = split_paragraph(in_parg) if p_line ~= fl then out_parg = out_parg..fl..'\n' end p_line = fl in_parg = ol if in_parg ~= "" then return del_rpt_consecutive_lines(in_parg, p_line, out_parg) else return out_parg end end function insert_in_str(target_str, mark_str, insert_str) -- inserta insert_str in "target_str" after mark_str return string.gsub(target_str, mark_str, mark_str..insert_str, 1) end function split_paragraph(in_str) -- Splits a paragraph into first and other_lines local i = string.find(in_str,'\n') if i ~= nil then local first_line = string.sub(in_str,1,i-1) local other_lines = string.sub(in_str,i+1,-1) return first_line, other_lines else return in_str, "" end end function get_title(f_name) local in_str = read_text_file(f_name) f_line, _ = split_paragraph(in_str) return string.sub(f_line,14,-1) end function make_link(article_name, title_str) -- Makes a suscribible link line in the shape of -- gemini://whatever/folder/file.gmi 2022-01-01 Title folder_url = "gemini://yretek.com/articulos/" full_url = folder_url .. article_name return "=> " .. full_url .. " " .. title_str end function update_index(index_f_name, insert_point, link_str) local old_index = read_text_file(index_f_name) local new_index = insert_in_str(old_index, insert_point, link_str ) new_index = del_rpt_consecutive_lines(new_index) write_text_file(index_f_name, new_index) end function update_indexes(link_str) update_index("../index.gmi", "## Artículos\n", link_str) update_index("../articulos/index.gmi","·\n", link_str) update_index("../todo.gmi","·\n", link_str) end function write_text_file(f_name, texto) -- Writes texto on file f_name local f_ile = io.open(f_name, 'w') io.output(f_ile) io.write(texto) io.close(f_ile) end -- .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- -- backing up old indexes os.execute("cp ../index.gmi backup/index_prncpl.bak") os.execute("cp ../articulos/index.gmi backup/index_artcls.bak") os.execute("cp ../todo.gmi backup/todo.bak") local scheduled_dir = "programados/" local today_str = os.date('%Y-%m-%d') local link_str = "" local articulos = "../articulos/" local scheduled_dir_contents = io.popen("ls "..scheduled_dir):read("*a") print(scheduled_dir_contents) -- Table to store the lines local scheduled_dir_lines = {} -- Iterate over the lines in the string and store them in the table for line in scheduled_dir_contents:gmatch("[^\r\n]+") do table.insert(scheduled_dir_lines, line) end -- Examine entries for i, article_name in ipairs(scheduled_dir_lines) do to_pub_date = string.sub(article_name,1,10) print(to_pub_date, i, article_name) if to_pub_date == today_str then title_str = " "..to_pub_date..get_title(scheduled_dir..article_name) link_str = make_link(article_name, title_str).."\n" print("Let's go", article_name) -- mv artcl to articulos folder os.execute("mv "..scheduled_dir..article_name.." ".."../articulos/" ) update_indexes(link_str) else print("No go", article_name) end end
Este es mucho más sencillo. Básicamente lee los archivos que aparecen en el directorio programados. Uno por uno va extrayendo la fecha de publicación. Esto es sencillo porque todos los archivos tiene la estructura yyyy-mm-dd_nombre_archivo.gmi Donde yyyy es el año con cuatro dígtios, mm el mes y dd el día del mes. Vamos, la estructura usual en Gemini. Luego comprueba si coincide con la fecha de hoy y, si es así, da el mensaje "Let's go" y pasa a la segunda parte. Si no coincide imprime en pantalla "No go"y va al siguiente archivo hasta terminar el contenido de la carpeta.
La segunda parte comienza por determinar el título mediante la función get_title. Esta lo único que hace es aprovechar que los títulos de mis artículos empiezan siempre en la primera línea del archivo y en la columna catorce (nota que internamente los smileis cuentan como más de un caracter). Después, make_link crea el enlace. Finalmente mueve el archivo al directorio articulos (sin acento… ) e incluye los enlaces en los índices.
Y eso es todo. Creo que sabiendo un poquito de Lua se entiende todo muy bien. Y si no, pues tengo correo, o preguntad a alguien que sepa más, ¿vale?
Estos programas pueden sufrir cambios, pero su última versión estará en
Nota: en la práctica los ejecuto en local, desde mi propio ordenador, y revisado que está todo bien subo el resultado al servidor.
Y con esto termino, mostrando a continuación la licencia, por aquello de los buenos modales. Recuerda programar en abierto, sé amable.
·-·-· Licence ·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. For A copy of the GNU General Public License along see <https://www.gnu.org/licenses/>.
~ Miguel de Luis Espinosa