Skip to content

Instantly share code, notes, and snippets.

@DudeThatsErin
Last active February 14, 2025 15:54
Show Gist options
  • Save DudeThatsErin/95bd33ca27a3c169d4a90622bafbcc8a to your computer and use it in GitHub Desktop.
Save DudeThatsErin/95bd33ca27a3c169d4a90622bafbcc8a to your computer and use it in GitHub Desktop.
Everything you need to create your own blog with Hugo!
{{ $pages := where .Site.RegularPages "Type" "in" (slice "posts") }}
{{ $query := .Get "query" | default "" }}
<div class="dataview-list">
<ul>
{{ range $pages }}
{{ if or (not $query) (in .Content $query) }}
<li>
<div class="list-item">
<h4><a href="{{ .RelPermalink }}">{{ .Title }}</a></h4>
<div class="metadata">
<span class="date">{{ .Date.Format "2006-01-02" }}</span>
{{ with .Params.tags }}
<span class="tags">
{{ range . }}
<a href="/blog/tags/{{ . | urlize }}">#{{ . }}</a>
{{ end }}
</span>
{{ end }}
</div>
{{ with .Description }}
<p class="description">{{ . }}</p>
{{ end }}
</div>
</li>
{{ end }}
{{ end }}
</ul>
</div>
<style>
.dataview-list {
margin: 2rem 0;
}
.dataview-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.dataview-list li {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.dataview-list li:last-child {
border-bottom: none;
}
.dataview-list h4 {
margin: 0 0 0.5rem 0;
}
.dataview-list .metadata {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.dataview-list .tags {
margin-left: 1rem;
}
.dataview-list .tags a {
margin-right: 0.5rem;
color: #0066cc;
text-decoration: none;
}
.dataview-list .tags a:hover {
text-decoration: underline;
}
.dataview-list .description {
margin: 0.5rem 0 0 0;
color: #444;
}
</style>
{{ $headers := .Get "headers" | split "," }}
{{ $data := .Inner }}
<!-- Debug output -->
{{ if hugo.IsServer }}
<div class="debug-info" style="display: none;">
<p>Headers: {{ $headers }}</p>
<p>Raw Data: {{ $data }}</p>
</div>
{{ end }}
<div class="dataview-table">
<table>
<thead>
<tr>
{{ range $headers }}
<th>{{ . | strings.TrimSpace | humanize }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ $rows := split (strings.TrimSpace $data) "\n" }}
{{ range $rows }}
{{ $row := . }}
{{ if $row }}
<tr>
{{ $cells := split $row "|" }}
{{ range $cells }}
<td>{{ . | strings.TrimSpace | markdownify }}</td>
{{ end }}
</tr>
{{ end }}
{{ end }}
</tbody>
</table>
</div>
<style>
.dataview-table {
margin: 2rem 0;
overflow-x: auto;
}
.dataview-table table {
width: 100%;
border-collapse: collapse;
margin: 0;
font-size: 0.95rem;
background: var(--background);
}
.dataview-table th {
background-color: var(--accent);
color: var(--background);
padding: 0.75rem 1rem;
text-align: left;
font-weight: bold;
border-bottom: 2px solid var(--border-color);
}
.dataview-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-color);
vertical-align: top;
}
.dataview-table tr:hover {
background-color: var(--hover);
}
.dataview-table tr:last-child td {
border-bottom: none;
}
.dataview-table a {
color: var(--accent);
text-decoration: none;
}
.dataview-table a:hover {
text-decoration: underline;
}
.debug-info {
background: #f0f0f0;
padding: 1rem;
margin: 1rem 0;
border: 1px solid #ddd;
white-space: pre-wrap;
}
.toggle-debug {
background: #eee;
border: 1px solid #ddd;
padding: 0.5rem;
margin: 1rem 0;
cursor: pointer;
}
@media (max-width: 768px) {
.dataview-table {
margin: 1rem -1rem;
width: calc(100% + 2rem);
}
.dataview-table th,
.dataview-table td {
padding: 0.5rem;
}
}
</style>
{{ if hugo.IsServer }}
<button class="toggle-debug" onclick="toggleDebug(this)">Show Debug Info</button>
<script>
function toggleDebug(btn) {
const debug = btn.previousElementSibling.previousElementSibling;
if (debug.style.display === 'none') {
debug.style.display = 'block';
btn.textContent = 'Hide Debug Info';
} else {
debug.style.display = 'none';
btn.textContent = 'Show Debug Info';
}
}
</script>
{{ end }}
<!-- Favicon -->
<link rel="icon" type="image/png" href="/blog/favicon.png">
<link rel="shortcut icon" type="image/png" href="/blog/favicon.png">
import os
import re
import yaml
from datetime import datetime
import traceback
# Paths
posts_dir = r"LOCATION OF YOUR HUGO INSTALL\content\posts"
attachments_base = r"FOLDER INSIDE YOUR OBSIDIAN VAULT"
static_images_dir = r"LOCATION OF YOUR HUGO INSTALL\static\images"
static_files_dir = r"LOCATION OF YOUR HUGO INSTALL\static\files"
# Ensure the target directories exist
os.makedirs(static_images_dir, exist_ok=True)
os.makedirs(static_files_dir, exist_ok=True)
def parse_dataview_query(query_block):
"""Parse a Dataview query block and convert it to Hugo-compatible format."""
print("\nProcessing Dataview query block:")
print(query_block)
# Extract the query type and parameters
lines = [line.strip() for line in query_block.strip().split('\n')]
query_type = lines[0].lower()
print(f"Query type: {query_type}")
# Initialize result
result = []
if 'table' in query_type:
print("Processing TABLE query")
# Handle TABLE queries
fields = []
field_aliases = {}
data = []
in_data = False
# First line might contain field definitions
field_line = lines[0].lower().replace('table', '').strip()
if field_line:
# Parse fields and their aliases
field_parts = [f.strip() for f in field_line.split(',')]
for part in field_parts:
if ' as ' in part.lower():
field, alias = [p.strip().strip('"') for p in part.lower().split(' as ')]
fields.append(field)
field_aliases[field] = alias
else:
fields.append(part)
field_aliases[part] = part
print(f"Fields: {fields}")
print(f"Aliases: {field_aliases}")
# Process the remaining lines
for line in lines[1:]:
if not line: # Skip empty lines
continue
print(f"Processing line: {line}")
if 'from' in line.lower():
in_data = True
source_folder = line.split('"')[1] if '"' in line else line.split(' ')[1]
print(f"Found FROM clause, source: {source_folder}")
# Get all markdown files in the posts directory
for filename in os.listdir(posts_dir):
if filename.endswith('.md'):
file_path = os.path.join(posts_dir, filename)
row_data = []
for field in fields:
if 'file.name' in field:
value = os.path.splitext(filename)[0]
elif 'title' in field.lower():
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
front_matter = re.search(r'^---\s*\n(.*?)\n\s*---', content, re.DOTALL)
if front_matter:
metadata = yaml.safe_load(front_matter.group(1))
value = metadata.get('title', os.path.splitext(filename)[0])
else:
value = os.path.splitext(filename)[0]
except Exception as e:
print(f"Error getting title: {e}")
value = os.path.splitext(filename)[0]
elif 'url' in field.lower() or 'file.path' in field.lower():
value = f"/blog/{get_file_url(os.path.splitext(filename)[0])}"
elif 'date' in field:
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
front_matter = re.search(r'^---\s*\n(.*?)\n\s*---', content, re.DOTALL)
if front_matter:
metadata = yaml.safe_load(front_matter.group(1))
value = metadata.get('date', '')
else:
value = ''
except Exception as e:
print(f"Error getting date: {e}")
value = ''
elif 'tags' in field:
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
front_matter = re.search(r'^---\s*\n(.*?)\n\s*---', content, re.DOTALL)
if front_matter:
metadata = yaml.safe_load(front_matter.group(1))
tags = metadata.get('tags', [])
value = ', '.join(tags)
else:
value = ''
except Exception as e:
print(f"Error getting tags: {e}")
value = ''
else:
value = ''
row_data.append(value)
print(f"Field {field}: {value}")
if row_data:
data.append(row_data)
print(f"Added row: {row_data}")
continue
if 'where' in line.lower() or 'sort' in line.lower():
print(f"Skipping {line}")
continue
if fields:
# Generate HTML table
html = ['<div class="dataview-table">']
html.append('<style>')
html.append('''
.dataview-table {
margin: 2rem 0;
overflow-x: auto;
}
.dataview-table table {
width: 100%;
border-collapse: collapse;
margin: 0;
font-size: 0.95rem;
background: var(--background);
}
.dataview-table th {
background-color: var(--accent);
color: var(--background);
padding: 0.75rem 1rem;
text-align: left;
font-weight: bold;
border-bottom: 2px solid var(--border-color);
}
.dataview-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-color);
vertical-align: top;
}
.dataview-table tr:hover {
background-color: var(--hover);
}
.dataview-table tr:last-child td {
border-bottom: none;
}
.dataview-table a {
color: var(--accent);
text-decoration: none;
}
.dataview-table a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.dataview-table {
margin: 1rem -1rem;
width: calc(100% + 2rem);
}
.dataview-table th,
.dataview-table td {
padding: 0.5rem;
}
}
''')
html.append('</style>')
html.append('<table>')
# Add headers
header_names = [field_aliases.get(field, field).title() for field in fields]
html.append('<thead><tr>')
for header in header_names:
html.append(f'<th>{header}</th>')
html.append('</tr></thead>')
# Add data rows
html.append('<tbody>')
for row in data:
html.append('<tr>')
for i, cell in enumerate(row):
if 'url' in fields[i].lower() or 'file.path' in fields[i].lower():
html.append(f'<td><a href="{cell}">{cell}</a></td>')
else:
html.append(f'<td>{cell}</td>')
html.append('</tr>')
html.append('</tbody>')
html.append('</table>')
html.append('</div>')
result = '\n'.join(html)
print("\nGenerated HTML table:")
print(result)
elif 'list' in query_type:
print("Processing LIST query")
# Handle LIST queries
query = ""
for line in lines[1:]:
if line.lower().startswith('where'):
query = line[6:].strip() # Extract the where clause
print(f"Found WHERE clause: {query}")
break
# Generate HTML list
html = ['<div class="dataview-list">']
html.append('<style>')
html.append('''
.dataview-list {
margin: 2rem 0;
}
.dataview-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.dataview-list li {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.dataview-list li:last-child {
border-bottom: none;
}
.dataview-list h4 {
margin: 0 0 0.5rem 0;
}
.dataview-list .metadata {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.dataview-list .tags {
margin-left: 1rem;
}
.dataview-list .tags a {
margin-right: 0.5rem;
color: var(--accent);
text-decoration: none;
}
.dataview-list .tags a:hover {
text-decoration: underline;
}
.dataview-list .description {
margin: 0.5rem 0 0 0;
color: #444;
}
''')
html.append('</style>')
html.append('<ul>')
# Get all markdown files in the posts directory
for filename in os.listdir(posts_dir):
if filename.endswith('.md'):
file_path = os.path.join(posts_dir, filename)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
front_matter = re.search(r'^---\s*\n(.*?)\n\s*---', content, re.DOTALL)
if front_matter:
metadata = yaml.safe_load(front_matter.group(1))
if not query or query.lower() in content.lower():
title = metadata.get('title', os.path.splitext(filename)[0])
date = metadata.get('date', '')
tags = metadata.get('tags', [])
description = metadata.get('description', '')
url = get_file_url(os.path.splitext(filename)[0])
html.append('<li>')
html.append('<div class="list-item">')
html.append(f'<h4><a href="/blog/{url}">{title}</a></h4>')
html.append('<div class="metadata">')
if date:
html.append(f'<span class="date">{date}</span>')
if tags:
html.append('<span class="tags">')
for tag in tags:
html.append(f'<a href="/blog/tags/{tag.lower()}">{tag}</a>')
html.append('</span>')
html.append('</div>')
if description:
html.append(f'<p class="description">{description}</p>')
html.append('</div>')
html.append('</li>')
except Exception as e:
print(f"Error processing file {filename}: {e}")
html.append('</ul>')
html.append('</div>')
result = '\n'.join(html)
print("\nGenerated HTML list:")
print(result)
return result
def process_dataview(content):
"""Find and process Dataview code blocks."""
print("\nLooking for Dataview blocks in content...")
# Updated pattern to be more flexible with whitespace and backticks
dataview_pattern = r'(?:^|\n)[ \t]*```+[ \t]*dataview[ \t]*\n(.*?)\n[ \t]*```+[ \t]*(?:\n|$)'
matches = re.finditer(dataview_pattern, content, flags=re.DOTALL)
match_count = 0
def replace_dataview(match):
nonlocal match_count
match_count += 1
print(f"\nProcessing Dataview block #{match_count}")
query_block = match.group(1).strip()
result = parse_dataview_query(query_block)
print(f"Replacing Dataview block with: {result}")
return f"\n{result}\n"
processed_content = re.sub(dataview_pattern, replace_dataview, content, flags=re.DOTALL)
if match_count == 0:
print("No Dataview blocks found in content")
print("Content preview:")
print(content[:500]) # Print first 500 chars to help debug
else:
print(f"Processed {match_count} Dataview blocks")
return processed_content
def create_filename(title):
"""Convert a title to a filename-friendly format."""
# Replace spaces with hyphens and preserve special characters
filename = title.lower().replace(' ', '-')
# Ensure special characters are preserved (single encoding)
filename = filename.replace('#', '2') # Just use 2 for the number
return filename
def format_url(title):
"""Format a title into a URL-friendly string."""
# Convert to lowercase and replace spaces with hyphens
url = title.lower().replace(' ', '-')
# Handle special characters
url = url.replace('#', '%232')
return url
def clean_filename(filename):
"""Extract the actual filename from Obsidian path and remove alias."""
# Remove any folder structure
filename = filename.split('/')[-1]
# Remove any alias
filename = filename.split('|')[0]
return filename
def get_file_url(filename):
"""Get the URL-friendly version of a filename."""
# Remove .md extension if present
base_name = filename.replace('.md', '')
# For Test Blog 2, return test-blog-2
return base_name.lower().replace(' ', '-')
def get_post_attachments_dir(post_name):
"""Get the attachments directory for a specific post."""
return os.path.join(attachments_base, post_name)
# Get list of all markdown files and their metadata
file_metadata = {}
for filename in os.listdir(posts_dir):
if filename.endswith(".md"):
base_name = filename.replace('.md', '')
filepath = os.path.join(posts_dir, filename)
with open(filepath, "r", encoding="utf-8") as file:
content = file.read()
# Extract front matter
front_matter = re.search(r'^---\s*\n(.*?)\n\s*---', content, re.DOTALL)
if front_matter:
try:
metadata = yaml.safe_load(front_matter.group(1))
title = metadata.get('title', base_name)
file_metadata[base_name] = {
'title': title,
'url': get_file_url(base_name)
}
except Exception as e:
print(f"Warning: Could not parse front matter for {filename}: {str(e)}")
file_metadata[base_name] = {
'title': base_name,
'url': get_file_url(base_name)
}
else:
file_metadata[base_name] = {
'title': base_name,
'url': get_file_url(base_name)
}
print("\nFound posts:", file_metadata)
def parse_cardlink(cardlink_block):
"""Parse a Cardlink block and convert it to Hugo-compatible HTML."""
print("\nProcessing Cardlink block:")
print(cardlink_block)
# Extract cardlink properties
properties = {}
lines = [line.strip() for line in cardlink_block.strip().split('\n')]
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
properties[key.strip()] = value.strip().strip('"')
# Generate HTML for the cardlink
html = ['<div class="cardlink">']
html.append('<style>')
html.append('''
.cardlink {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: var(--background);
transition: transform 0.2s;
display: block;
}
.cardlink:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.cardlink a {
text-decoration: none;
color: inherit;
}
.cardlink-content {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.cardlink-text {
flex: 1;
}
.cardlink-title {
font-size: 1.1rem;
font-weight: bold;
color: var(--accent);
margin: 0 0 0.5rem 0;
}
.cardlink-description {
font-size: 0.9rem;
color: var(--color);
margin: 0;
opacity: 0.8;
}
.cardlink-host {
font-size: 0.8rem;
color: var(--color);
opacity: 0.6;
margin-top: 0.5rem;
}
.cardlink-image {
width: 160px;
height: 120px;
object-fit: cover;
border-radius: 4px;
margin: 0;
}
@media (max-width: 600px) {
.cardlink-content {
flex-direction: column;
}
.cardlink-image {
width: 100%;
height: 160px;
margin-top: 1rem;
}
}
''')
html.append('</style>')
# Create the card content
html.append(f'<a href="{properties.get("url", "#")}" target="_blank" rel="noopener noreferrer">')
html.append('<div class="cardlink-content">')
# Text content
html.append('<div class="cardlink-text">')
if 'title' in properties:
html.append(f'<h3 class="cardlink-title">{properties["title"]}</h3>')
if 'description' in properties:
html.append(f'<p class="cardlink-description">{properties["description"]}</p>')
if 'host' in properties:
html.append(f'<div class="cardlink-host">{properties["host"]}</div>')
html.append('</div>')
# Image if available
if 'image' in properties:
html.append(f'<img class="cardlink-image" src="{properties["image"]}" alt="{properties.get("title", "Link preview")}">')
html.append('</div>')
html.append('</a>')
html.append('</div>')
return '\n'.join(html)
def parse_kanban(kanban_content):
"""Parse a Kanban board and convert it to HTML."""
try:
# Replace tabs with spaces to avoid YAML parsing issues
kanban_content = kanban_content.replace('\t', ' ')
# Extract YAML frontmatter
frontmatter_match = re.search(r'^---\s*(.*?)\s*---', kanban_content, re.DOTALL)
if not frontmatter_match:
raise ValueError("No YAML frontmatter found")
frontmatter = yaml.safe_load(frontmatter_match.group(1))
if not frontmatter.get('kanban-plugin') == 'board':
raise ValueError("Not a Kanban board")
# Get content after frontmatter
content = kanban_content[frontmatter_match.end():]
# Extract header and content separately
header_match = re.search(r'(?m)^(## [^\n]+)', content)
if header_match:
header = header_match.group(1).strip('# ').strip()
content = content[header_match.end():].strip()
else:
header = None
content = content.strip()
html = []
# Add the header if found
if header:
html.append(f'<h2>{header}</h2>')
html.append('<div class="kanban-board">')
html.append('''<style>
.kanban-board {
display: flex;
gap: 1rem;
overflow-x: auto;
padding: 1rem 0;
min-height: 400px;
margin: 1rem 0;
background: var(--background);
}
.kanban-lane {
min-width: 300px;
flex: 1;
background: var(--background);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
display: flex;
flex-direction: column;
}
.kanban-lane-header {
font-weight: bold;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--accent);
color: var(--color);
}
.kanban-cards {
flex: 1;
min-height: 100px;
}
.kanban-card {
background: var(--background);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 0.75rem;
margin-bottom: 0.75rem;
transition: transform 0.2s;
}
.kanban-card:hover {
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.kanban-card-text {
color: var(--color);
font-size: 0.9rem;
white-space: pre-wrap;
}
.kanban-card-checkbox {
margin-right: 0.5rem;
opacity: 0.6;
}
.kanban-card-title {
font-weight: bold;
margin-bottom: 0.5rem;
}
.kanban-card-checklist {
margin-left: 1.5rem;
margin-top: 0.5rem;
}
.kanban-card-checklist-item {
display: flex;
align-items: flex-start;
margin-bottom: 0.25rem;
}
@media (max-width: 768px) {
.kanban-board {
flex-direction: column;
}
.kanban-lane {
min-width: 100%;
}
}
</style>''')
# Process each section as a lane
for i in range(0, len(content), 2):
header = content[i:i+2].strip() if i+1 < len(content) else None
content = content[i+2:].strip() if i+1 < len(content) else ""
if not header:
continue
# Split content into items
items = []
current_card = None
for line in content.strip().split('\n'):
line = line.strip()
if not line:
continue
if line.startswith('- [ ]'): # New card
if current_card:
items.append(current_card)
current_card = {
'text': line[5:].strip(),
'checklist': []
}
elif line.startswith(' - [ ]') and current_card: # Checklist item
current_card['checklist'].append(line[9:].strip())
elif line.startswith(' ') and current_card: # Card text
if 'description' not in current_card:
current_card['description'] = []
current_card['description'].append(line.strip())
if current_card:
items.append(current_card)
# Generate HTML for lane
html.append('<div class="kanban-lane">')
html.append(f'<div class="kanban-lane-header">{header}</div>')
html.append('<div class="kanban-cards">')
# Generate HTML for cards
for item in items:
html.append('<div class="kanban-card">')
# Card title/text
text = item['text']
if text.startswith('# '): # Handle title formatting
title = text[2:].strip()
html.append(f'<div class="kanban-card-title">{title}</div>')
else:
html.append(f'<div class="kanban-card-text"><span class="kanban-card-checkbox">☐</span>{text}</div>')
# Card description
if 'description' in item:
html.append(f'<div class="kanban-card-text">{" ".join(item["description"])}</div>')
# Card checklist
if item['checklist']:
html.append('<div class="kanban-card-checklist">')
for checklist_item in item['checklist']:
html.append('<div class="kanban-card-checklist-item">')
html.append(f'<span class="kanban-card-checkbox">☐</span>{checklist_item}')
html.append('</div>')
html.append('</div>')
html.append('</div>') # Close kanban-card
html.append('</div>') # Close kanban-cards
html.append('</div>') # Close kanban-lane
html.append('</div>') # Close kanban-board
return '\n'.join(html)
except Exception as e:
print(f"Error parsing Kanban: {e}")
print("Full traceback:")
traceback.print_exc()
print("\nKanban content preview:")
print(kanban_content[:500]) # Print first 500 chars to help debug
return f'<div class="error">Error parsing Kanban board: {str(e)}</div>'
def is_kanban_file(content):
"""Check if a file is a Kanban board by looking for the kanban-plugin YAML frontmatter."""
try:
frontmatter_match = re.search(r'^---\s*(.*?)\s*---', content, re.DOTALL)
if frontmatter_match:
frontmatter = yaml.safe_load(frontmatter_match.group(1))
return frontmatter.get('kanban-plugin') == 'board'
except:
pass
return False
def process_embedded_files(content, base_name):
"""Process embedded Kanban files."""
print("\nChecking content for embedded files...")
print(f"Content preview: {content[:200]}...") # Debug: Show content preview
# Pattern for embedded files: ![[filename.extension|embed]] or ![[filename.extension]]
embed_pattern = r'!\[\[(.*?(?:\.md))(?:\|([^]]*)?)?\]\]'
matches = list(re.finditer(embed_pattern, content, re.IGNORECASE))
print(f"Found {len(matches)} potential embedded files") # Debug: Show number of matches
for match in matches:
file_path = match.group(1)
is_embed = match.group(2) == 'embed' if match.group(2) else True # Default to embed if not specified
file_name = clean_filename(file_path)
file_ext = os.path.splitext(file_name)[1].lower()
print(f"\nProcessing embedded file:") # Debug info
print(f" File path: {file_path}")
print(f" Is embed: {is_embed}")
print(f" File name: {file_name}")
print(f" Extension: {file_ext}")
# Get the post-specific attachments directory
post_attachments_dir = get_post_attachments_dir(base_name)
# Try multiple possible locations for the file
possible_sources = [
os.path.join(post_attachments_dir, file_name),
os.path.join(attachments_base, file_name),
os.path.join(posts_dir, file_name),
os.path.join(posts_dir, "attachments", file_name),
os.path.join(attachments_base, base_name, file_name)
]
file_found = False
for file_source in possible_sources:
print(f"Checking for file at: {file_source}")
if os.path.exists(file_source):
print(f"Found file at: {file_source}")
try:
with open(file_source, 'r', encoding='utf-8') as f:
file_content = f.read()
print(f"File content preview: {file_content[:200]}...") # Debug: Show file content
if is_embed:
if file_ext == '.md':
# Check if it's a Kanban file
is_kanban = is_kanban_file(file_content)
print(f"Is Kanban file: {is_kanban}") # Debug: Show Kanban detection result
if is_kanban:
print(f"Processing Kanban file: {file_name}")
html = parse_kanban(file_content)
print(f"Generated HTML preview: {html[:200]}...") # Debug: Show generated HTML
else:
html = f'<div class="error">Not a Kanban board: {file_name}</div>'
else:
html = f'<div class="error">Unsupported file type for embedding: {file_ext}</div>'
# Replace the match with the generated HTML
old_content = content
content = content.replace(match.group(0), html)
if content == old_content:
print("Warning: Content replacement did not change the content") # Debug: Check if replacement worked
print("Original match:", match.group(0))
print("Generated HTML preview:", html[:200])
else:
print("Successfully replaced content with HTML")
else:
# Generate link to separate note
title = os.path.splitext(file_name)[0]
url = get_file_url(title)
# Copy file to posts directory if it's not already there
target_path = os.path.join(posts_dir, f"{title}.md")
if not os.path.exists(target_path):
with open(target_path, 'w', encoding='utf-8') as f:
f.write(f'''---
title: "{title}"
date: {datetime.now().strftime('%Y-%m-%d')}
type: "{file_ext.replace('.', '')}"
---
{file_content}
''')
# Replace with markdown link
markdown_link = f'[{title}](/blog/{url})'
content = content.replace(match.group(0), markdown_link)
file_found = True
break
except Exception as e:
print(f"Error processing file {file_name}: {e}")
traceback.print_exc()
if not file_found:
print(f"Warning: File not found in any location: {file_name}")
print(f"Tried paths:")
for path in possible_sources:
print(f" - {path}")
print(f"\nContents of directories:")
print(f"Post attachments dir ({post_attachments_dir}):")
try:
print(os.listdir(post_attachments_dir))
except Exception as e:
print(f"Error listing post attachments directory: {e}")
print(f"\nBase attachments dir ({attachments_base}):")
try:
print(os.listdir(attachments_base))
except Exception as e:
print(f"Error listing base attachments directory: {e}")
return content
# Update the main file processing loop
for filename in os.listdir(posts_dir):
if filename.endswith(".md"):
filepath = os.path.join(posts_dir, filename)
print(f"\nProcessing file: {filename}")
base_name = os.path.splitext(filename)[0]
with open(filepath, "r", encoding="utf-8") as file:
content = file.read()
# Process Dataview blocks
content = process_dataview(content)
# Process Cardlink blocks
cardlink_pattern = r'```cardlink\n(.*?)\n```'
content = re.sub(cardlink_pattern, lambda m: parse_cardlink(m.group(1)), content, flags=re.DOTALL)
# Process embedded Kanban files
content = process_embedded_files(content, base_name)
# Handle internal links
internal_links = re.findall(r'\[\[([^]\.]*)\]\]', content)
for link_name in internal_links:
# Try to match the link with a file
link_base = link_name.replace('.md', '')
if link_base in file_metadata:
meta = file_metadata[link_base]
markdown_link = f"[{meta['title']}](/blog/{meta['url']})"
print(f"Converting internal link: {link_name} -> {markdown_link}")
content = re.sub(r'\[\[' + re.escape(link_name) + r'\]\]', markdown_link, content)
# Write the updated content back
with open(filepath, "w", encoding="utf-8") as file:
file.write(content)
print("\nAll files processed successfully!")
def process_markdown_content(content):
"""Process markdown content and convert special blocks."""
# Process Dataview blocks
dataview_pattern = r'```dataview\n(.*?)\n```'
content = re.sub(dataview_pattern, lambda m: parse_dataview_query(m.group(1)), content, flags=re.DOTALL)
# Process Cardlink blocks
cardlink_pattern = r'```cardlink\n(.*?)\n```'
content = re.sub(cardlink_pattern, lambda m: parse_cardlink(m.group(1)), content, flags=re.DOTALL)
return content
<div class="pdf-container">
<iframe src="{{ .Get "src" }}" style="width: 100%; height: 800px;" frameborder="0">
<p>It appears your browser doesn't support iframes.</p>
</iframe>
<p class="pdf-fallback" style="display: none;">
If the PDF is not displaying correctly, you can
<a href="{{ .Get "src" }}" target="_blank">open it in a new tab</a>.
</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var iframe = document.querySelector('.pdf-container iframe');
var fallback = document.querySelector('.pdf-fallback');
iframe.onload = function() {
if (iframe.contentDocument === null) {
fallback.style.display = 'block';
}
};
});
</script>
{{ $pages := where .Site.RegularPages "Type" "in" (slice "posts") }}
<div class="post-list">
{{ range $pages.ByDate.Reverse }}
<div class="post-item">
<h3><a href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
<div class="post-meta">
<span class="date">{{ .Date.Format "2006-01-02" }}</span>
{{ with .Params.tags }}
<span class="tags">
{{ range . }}
<a href="/blog/tags/{{ . | urlize }}">#{{ . }}</a>
{{ end }}
</span>
{{ end }}
</div>
{{ with .Description }}
<div class="description">{{ . }}</div>
{{ end }}
</div>
{{ end }}
</div>
<style>
.post-list {
margin: 2rem 0;
}
.post-item {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #ddd;
}
.post-item:last-child {
border-bottom: none;
}
.post-meta {
font-size: 0.9rem;
color: #666;
margin: 0.5rem 0;
}
.post-meta .tags {
margin-left: 1rem;
}
.post-meta .tags a {
margin-right: 0.5rem;
color: #0066cc;
text-decoration: none;
}
.post-meta .tags a:hover {
text-decoration: underline;
}
.description {
margin-top: 0.5rem;
color: #444;
}
</style>
# Put this file on your desktop so you can run the updateblog.ps1 file from your desktop.
# Set the blog directory path
$blogPath = "YOURHUGOINSTALLLOCATION"
# Change to the blog directory
Set-Location -Path $blogPath
# Run the update script
& "$blogPath\updateblog.ps1"
# Keep the window open if there are any errors
if ($LASTEXITCODE -ne 0) {
Write-Host "`nPress any key to continue..."
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
# PowerShell Script for Windows
# Set error handling
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
# Set variables for Obsidian to Hugo copy
$sourcePath = "E:\Obs\MyVault\Blogs" # Update this path to match your Obsidian vault location
$destinationPath = Join-Path $PSScriptRoot "content\posts"
$attachments_base = "E:\Obs\MyVault\90-Attachments\Blogs" # Path to attachments
$staticImagesPath = Join-Path $PSScriptRoot "static\images"
$staticFilesPath = Join-Path $PSScriptRoot "static\files"
$myrepo = "[email protected]:GITHUBUSERNAME/YOURREPO.git"
# Change to the script's directory
Set-Location $PSScriptRoot
# Function to check if a command exists
function Test-Command($cmdname) {
return [bool](Get-Command -Name $cmdname -ErrorAction SilentlyContinue)
}
# Check for required commands
$requiredCommands = @('git', 'hugo')
foreach ($cmd in $requiredCommands) {
if (-not (Test-Command $cmd)) {
Write-Error "$cmd is not installed or not in PATH."
exit 1
}
}
# Check for Python command (python or python3)
if (Test-Command 'python') {
$pythonCommand = 'python'
} elseif (Test-Command 'python3') {
$pythonCommand = 'python3'
} else {
Write-Error "Python is not installed or not in PATH."
exit 1
}
# Step 1: Check if Git is initialized, and initialize if necessary
if (-not (Test-Path ".git")) {
Write-Host "Initializing Git repository..."
git init
git remote add origin $myrepo
} else {
Write-Host "Git repository already initialized."
$remotes = git remote
if (-not ($remotes -contains 'origin')) {
Write-Host "Adding remote origin..."
git remote add origin $myrepo
}
}
# Step 2: Ensure static directories exist
Write-Host "Creating static directories if they don't exist..."
New-Item -ItemType Directory -Force -Path $staticImagesPath | Out-Null
New-Item -ItemType Directory -Force -Path $staticFilesPath | Out-Null
# Step 3: Sync posts from Obsidian to Hugo content folder
Write-Host "Syncing posts from Obsidian..."
if (-not (Test-Path $sourcePath)) {
Write-Error "Source path does not exist: $sourcePath"
exit 1
}
if (-not (Test-Path $destinationPath)) {
Write-Error "Destination path does not exist: $destinationPath"
exit 1
}
# Use Robocopy to mirror the directories
$robocopyOptions = @('/MIR', '/Z', '/W:5', '/R:3', '/NFL', '/NDL')
$robocopyResult = robocopy $sourcePath $destinationPath @robocopyOptions
if ($LASTEXITCODE -ge 8) {
Write-Error "Robocopy failed with exit code $LASTEXITCODE"
exit 1
}
# Step 4: Process Markdown files with Python script
Write-Host "Processing image links in Markdown files..."
if (-not (Test-Path "files.py")) {
Write-Error "Python script files.py not found."
exit 1
}
# Execute the Python script
try {
$env:ATTACHMENTS_BASE = $attachments_base
& $pythonCommand files.py
Remove-Item env:ATTACHMENTS_BASE
} catch {
Write-Error "Failed to process files: $_"
exit 1
}
# Step 5: Build the Hugo site
Write-Host "Building the Hugo site..."
try {
hugo
} catch {
Write-Error "Hugo build failed: $_"
exit 1
}
# Step 6: Add changes to Git
Write-Host "Staging changes for Git..."
$hasChanges = (git status --porcelain) -ne $null
if (-not $hasChanges) {
Write-Host "No changes to stage."
} else {
git add .
}
# Step 7: Commit changes with a dynamic message
$commitMessage = "New Blog Post on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$hasStagedChanges = (git diff --cached --name-only) -ne $null
if (-not $hasStagedChanges) {
Write-Host "No changes to commit."
} else {
Write-Host "Committing changes..."
git commit -m "$commitMessage"
}
# Step 8: Push all changes to the main branch
Write-Host "Deploying to GitHub Master..."
try {
git push origin master
} catch {
Write-Error "Failed to push to Master branch: $_"
exit 1
}
# Step 9: Push the public folder to the hostinger branch
Write-Host "Deploying to GitHub Hostinger..."
# Check if the temporary branch exists and delete it
$branchExists = git branch --list "hostinger-deploy"
if ($branchExists) {
git branch -D hostinger-deploy
}
# Perform subtree split
try {
git subtree split --prefix public -b hostinger-deploy
} catch {
Write-Error "Subtree split failed: $_"
exit 1
}
# Push to hostinger branch with force
try {
git push origin hostinger-deploy:hostinger --force
} catch {
Write-Error "Failed to push to hostinger branch: $_"
git branch -D hostinger-deploy
exit 1
}
Write-Host "Deployment completed successfully!" -ForegroundColor Green
#!/bin/bash
# Set error handling
set -e
# Set variables for Obsidian to Hugo copy
SOURCE_PATH="$HOME/Obsidian/MyVault/Blogs" # Update this path to match your Obsidian vault location
DESTINATION_PATH="$PWD/content/posts"
ATTACHMENTS_BASE="$HOME/Obsidian/MyVault/90-Attachments/Blogs" # Path to attachments
STATIC_IMAGES_DIR="$PWD/static/images"
STATIC_FILES_DIR="$PWD/static/files"
MYREPO="[email protected]:GITHUBUSERNAME/YOURREPO.git"
# Change to the script's directory
cd "$(dirname "$0")"
# Check for required commands
for cmd in git hugo python3; do
if ! command -v $cmd &> /dev/null; then
echo "Error: $cmd is not installed or not in PATH."
exit 1
fi
done
# Step 1: Check if Git is initialized, and initialize if necessary
if [ ! -d ".git" ]; then
echo "Initializing Git repository..."
git init
git remote add origin $MYREPO
else
echo "Git repository already initialized."
if ! git remote | grep -q "^origin$"; then
echo "Adding remote origin..."
git remote add origin $MYREPO
fi
fi
# Step 2: Ensure static directories exist
echo "Creating static directories if they don't exist..."
mkdir -p "$STATIC_IMAGES_DIR"
mkdir -p "$STATIC_FILES_DIR"
# Step 3: Sync posts from Obsidian to Hugo content folder
echo "Syncing posts from Obsidian..."
if [ ! -d "$SOURCE_PATH" ]; then
echo "Error: Source path does not exist: $SOURCE_PATH"
exit 1
fi
if [ ! -d "$DESTINATION_PATH" ]; then
echo "Error: Destination path does not exist: $DESTINATION_PATH"
exit 1
fi
# Use rsync for directory synchronization
rsync -av --delete "$SOURCE_PATH/" "$DESTINATION_PATH/"
# Step 4: Process Markdown files with Python script
echo "Processing image links in Markdown files..."
if [ ! -f "files.py" ]; then
echo "Error: Python script files.py not found."
exit 1
fi
# Execute the Python script
export ATTACHMENTS_BASE
python3 files.py
# Step 5: Build the Hugo site
echo "Building the Hugo site..."
hugo
# Step 6: Add changes to Git
echo "Staging changes for Git..."
if [ -n "$(git status --porcelain)" ]; then
git add .
else
echo "No changes to stage."
fi
# Step 7: Commit changes with a dynamic message
COMMIT_MESSAGE="New Blog Post on $(date '+%Y-%m-%d %H:%M:%S')"
if [ -n "$(git diff --cached --name-only)" ]; then
echo "Committing changes..."
git commit -m "$COMMIT_MESSAGE"
else
echo "No changes to commit."
fi
# Step 8: Push all changes to the main branch
echo "Deploying to GitHub Master..."
git push origin master || {
echo "Failed to push to Master branch."
exit 1
}
# Step 9: Push the public folder to the hostinger branch
echo "Deploying to GitHub Hostinger..."
# Check if the temporary branch exists and delete it
if git show-ref --verify --quiet refs/heads/hostinger-deploy; then
git branch -D hostinger-deploy
fi
# Perform subtree split
git subtree split --prefix public -b hostinger-deploy || {
echo "Subtree split failed."
exit 1
}
# Push to hostinger branch with force
if ! git push origin hostinger-deploy:hostinger --force; then
echo "Failed to push to hostinger branch."
git branch -D hostinger-deploy
exit 1
fi
echo "Deployment completed successfully!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment