Pruebas anterior con metaprogramación
Primero creé varios scripts para automatizar la creación de una web a partir de las notas. Debe seguir publicado en esta dirección.
#!/bin/bash
. ./.private
# 0. Definición variables
# ------------------------------------------------------------------------------
ORG_ROAM_ORIGINAL_FOLDER=~/Documents/org-roam-files # Ubicación de mis notas
ORG_ROAM_HTML_FILES_FOLDER=orgroam-html # Donde están los html generados por build-orgroam-with-emacs.sh. Esta variable se usa en build-site.el
# Donde guardo de forma plana todos los archivos para poder usarlo en python para extraer abstract.
# TODO: a eliminar porque debemos poder extraer del html generado, lo que nos evita mucho trabajo de expresión regular en python.
ORG_ROAM_RAW_FILES_FOLDER=orgroam-raw
IMAGES_FOLDER=posts/img
PROJECT_FOLDER=~/projects/blogs/qoback-orgroam
# 1. Borramos todo lo antiguo. Más bonito si controlase fechas y copiase solo lo necesario, pero innecesario.
# ------------------------------------------------------------------------------
rm posts/*.html
rm -r $ORG_ROAM_HTML_FILES_FOLDER/**/*.html
rm $ORG_ROAM_RAW_FILES_FOLDER/*.org
# 2. Copio mis notas salvo las que son privadas
# ------------------------------------------------------------------------------
find $ORG_ROAM_ORIGINAL_FOLDER -type d \( -path $ORG_ROAM_ORIGINAL_FOLDER/home -o -path $ORG_ROAM_ORIGINAL_FOLDER/fitosat \) -prune -o -name '*.org' -print | xargs -I% cp % $ORG_ROAM_RAW_FILES_FOLDER
# 3. Construyo html a partir de org. Se guarda la misma estructura de carpetas
# ------------------------------------------------------------------------------
emacs -Q --script build-site.el
$PROJECT_FOLDER/create_graph.py $ORG_ROAM_ORIGINAL_FOLDER
# rm -rf ~/projects/blogs/qoback/$ORG_ROAM_HTML_FILES_FOLDER/home
# rm -rf ~/projects/blogs/qoback/$ORG_ROAM_HTML_FILES_FOLDER/fitosat
find $ORG_ROAM_ORIGINAL_FOLDER -type f -name "*.png" -o -name "*.jpg" | xargs -I% cp % $IMAGES_FOLDER
# 4. Creo html de cada poast
# ------------------------------------------------------------------------------
echo ""
echo "==> Creando Posts para publicación"
for file in $(find $ORG_ROAM_HTML_FILES_FOLDER -type d \( -path $ORG_ROAM_HTML_FILES_FOLDER/home -o -path $ORG_ROAM_HTML_FILES_FOLDER/fitosat \) -prune -o -name "*.html" -print); do
echo " $file"
./insert_raw_post_in_layout.sh layout/main.html $file
done
# TODO: mezclar y que solamente haya un listado con abstract y grafo
# 5. Creo index.html con previas
# ------------------------------------------------------------------------------
echo ""
echo "==> Creando índice"
./create_index_page.py posts
# 6. Creo posts/index.html con grafo de dependencias
# ------------------------------------------------------------------------------
./create_knwoledge_page.py
# 7. [Opcional] Desplegamos
# ------------------------------------------------------------------------------
if [[ $1 == 'up' ]]; then
ip=${host[ip]}
port=${host[port]}
user=${host[user]}
rsync -avzP -e "ssh -p $port" --exclude-from="exclude.txt" . $user@$ip:$dst
fi
Script para crear el grafo que mostrar más tarde con echarts.
#!/usr/bin/python
import os
import sys
import re
import json
path_to_org_files = sys.argv[1]
connections = {}
conns = []
categories = {}
files = os.listdir(path_to_org_files)
max_length = max([len(f) for f in files])
for dirpath, dirnames, filenames in os.walk(path_to_org_files):
for file in filenames:
if file.endswith('.org'):
with open(os.path.join(dirpath, file), 'r', encoding="utf-8") as f:
content = f.read()
match_id = re.search(r':ID:\s*([-\w]+)', content)
match_name = re.search(r'#\+TITLE:\s*(.+)', content)
if match_id:
current_id = match_id.group(1)
current_name = match_name.group(1).strip() if match_name else current_id
conns.append({
"id": current_id,
'name': current_name,
'links': list(set(re.findall(r'\[\[id:([-\w]+)\]', content))),
'file': file,
'category': None
})
conns = sorted(conns, key=lambda e: len(e['links']), reverse=True)
for idx, conn in enumerate(conns):
connections[conn['id']] = conn
category = idx if categories.get(conn['id'], None) is None else categories[conn['id']]
if not len(conn['links']):
categories[conn['id']] = category
print(f"{conn['name']:<{max_length}} [{category:>2}]")
for link in conn['links']:
print(f"{conn['name']:<{max_length}} [{category:>2}] -> {link}")
categories[link] = category
connections[conn['id']]['category'] = category
json_data = json.dumps(connections, indent=4)
with open('posts/connections.json', 'w') as f:
f.write(json_data)
print("\n==> JSON creado")
Post para montara cada post, insert_raw_post_in_layout.sh.
[[ $# != 2 ]] && echo "HTML layout and raw post HTML must be provided." && exit -1
function replace_text {
file_path=$1
sed -i 's/Table of Contents/Índice/g' $file_path
sed -i 's/Footnotes:/Notas/g' $file_path
}
# TODO: This must be seteable from command line with extra key arguments.
INSERT_STYLE=0 # --ijs 1
INSERT_SCRIPT=0 # --icss 1
LAYOUT_FILE=$1
RAW_HTML_PATH=$2
LAYOUT_FILE=layout/main.html
RAW_HTML_FILE=${RAW_HTML_PATH##*/}
NL_BODY=$(grep -n %INSERT% $LAYOUT_FILE | cut -d':' -f 1)
NL_STYLE=$(grep -n %STYLE-SCRIPT% $LAYOUT_FILE | cut -d':' -f 1)
DST_FOLDER=posts
DST_PATH=$DST_FOLDER/$RAW_HTML_FILE
mkdir -p $DST_FOLDER
BODY=$(xmllint --html --xpath "//body//div[@id='content']" $RAW_HTML_PATH)
if [[ $INSERT_STYLE == 1 ]]; then
STYLE=$(xmllint --html --xpath "string(//style)" $RAW_HTML_PATH)
fi
if [[ $INSERT_SCRIPT == 1 ]]; then
SCRIPT=$(xmllint --html --xpath "string(//script)" $RAW_HTML_PATH)
fi
{
head -n $NL_STYLE $LAYOUT_FILE
[[ $INSERT_STYLE == 1 ]] && echo "<style type=\"text/css\">" && echo "$STYLE" && echo "</style>"
[[ $INSERT_SCRIPT == 1 ]] && echo "<script type=\"text/javascript\">" && echo "$SCRIPT" && echo "</script>"
tail -n +$NL_STYLE $LAYOUT_FILE | head -n $((NL_BODY-NL_STYLE))
echo "$BODY"
tail -n +$NL_BODY $LAYOUT_FILE
} > $DST_PATH
replace_text $DST_PATH
Con python creamos el index.html, con el archivo create_index_page.py
#!/usr/bin/python
import os
import sys
import re
import json
import sys
html_files_path = sys.argv[1]
files = os.listdir(html_files_path)
max_length = max([len(f) for f in files])
pre = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Qoback Roam</title>
<link rel="stylesheet" href="/css/posts.css" />
<link rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/css/org-styles.css" />
<link rel="stylesheet" href="/css/simple.css" />
<link rel="stylesheet" href="/css/post.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" /> -->
<link
href="https://fonts.googleapis.com/css2?family=Teko&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Spline+Sans+Mono:ital,wght@0,300;0,400;0,600;1,400&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Akshar:wght@300;400;600&display=swap"
rel="stylesheet"
/>
<!-- %STYLE-SCRIPT% -->
</head>
<body>
<nav>
<a href="/"><div>Home</div></a>
<a href="/posts/"><div>Roam</div></a>
</nav>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script
id="MathJax-script"
async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
></script>
<input type='text'/>
<div id='content' class='posts'>
"""
post = """
</div>
</body>
</html>
"""
with open("index.html", "w") as writer:
writer.write(pre)
for dirpath, dirnames, filenames in os.walk(html_files_path):
for file in filenames:
# print(dirpath, dirnames, file)
base_name = file[:-5]
if not file.endswith(".html") or file == "index.html":
continue
try:
with open(f"orgroam-raw/{base_name}.org", "r") as org_reader:
content = org_reader.read()
match_created = re.search(r"#\+CREATED:\s*\[(.+)\]", content)
match_updated = re.search(r"#\+LAST_MODIFIED:\s*\[(.+)\]", content)
with open(f"{dirpath}/{file}", "r") as org_reader:
c = org_reader.read()
abstract = re.search(r"(<p>.*?</p>)", c, re.DOTALL).group(1)
title = re.sub("[-_]", " ", base_name)
card = f"""
<div class='wrapper'>
<div class='preview'>
<a class=\"title\" href='/posts/{base_name}.html'>
{title}
</a>
</br>
<div style="display: flex; justify-content: space-between;">
<sup>{match_created.group(1)}</sup>
<sup>{match_updated.group(1)}</sup>
</div>
{abstract}
</div>
</div>"""
writer.write(card)
except:
print(f"\t ** Passing though file {file} **")
writer.write(post)
Finalmente creamos el index.html donde se muestra el grafo creado con python usando echarts.
#!/usr/bin/python
import os
import re
# <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
print("\n\n==>> Creating post/index.html")
pre = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Qoback Blog</title>
<link rel="stylesheet" href="/css/posts.css" />
<link rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/css/org-styles.css" />
<link rel="stylesheet" href="/css/simple.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Teko&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Spline+Sans+Mono:ital,wght@0,300;0,400;0,600;1,400&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Akshar:wght@300;400;600&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.2.2/echarts.min.js"></script>
</head>
<body>
<nav>
<a href="/"><div>Home</div></a>
<a href="/posts/"><div>Roam</div></a>
</nav>
<div id="main" style="width: 100%;height:40vh;"></div>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script
id="MathJax-script"
async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
></script>
<script>
fetch('connections.json')
.then(response => response.json())
.then(data => {
let nodes = [];
let links = [];
let uniqueColors = new Set(); // Guardar los colores únicos para definir la paleta de colores
for (let id in data) {
nodes.push({
id: id,
name: data[id].name,
category: data[id].category,
itemStyle: {
color: `hsl(${data[id].category * 50 % 360}, 70%, 60%)` // Generar color basado en el valor
}
});
uniqueColors.add(data[id].category);
data[id].links.forEach(link => {
links.push({
source: id,
target: link
});
});
}
const option = {
series: [{
animation: false,
categories: [],
focusNodeAdjacency: true,
itemStyle: {
borderColor: '#fff',
borderWidth: 1,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.3)'
},
draggable: true,
type: 'graph',
layout: 'force',
data: nodes,
links: links,
roam: true,
label: {
position: 'right',
formatter: '{b}'
},
lineStyle: {
color: 'source',
curveness: 0.3
},
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 3
}
}
}]
};
let chart = echarts.init(document.getElementById('main'));
chart.setOption(option);
chart.on('click', function(params) {
if (params.dataType === 'node') {
const nodeId = params.data.id;
//const filePath = `posts/${data[nodeId].file.slice(0, -3)}html`;
const filePath = `${data[nodeId].file.slice(0, -3)}html`;
window.open(filePath, '_blank');
}
});
});
</script>
"""
def create_body(path_to_html_files):
pattern = "[-_]"
body = """
<input type='text'/>
<div id='content' class='posts'>"""
for _, _, filenames in os.walk(path_to_html_files):
for file in filenames:
if file != "index.html" and file.endswith(".html"):
print(f"\tCreating post item for file {file}")
body += f"""
<div class='wrapper'>
<div class='preview'>
<a class=\"title\" href='/posts/{file}'>
{re.sub(pattern, ' ', file[:-5])}
</a>
<br>
</div>
</div>
"""
return body
post = """
</body>
</html>
"""
with open("posts/index.html", "w") as f:
f.write(pre)
f.write(create_body("posts"))
f.write(post)