Add Practical Scheme blog post and fix og:url formatting
This commit is contained in:
parent
c63a517b7f
commit
89151a9356
|
|
@ -0,0 +1,273 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="bg-base-bg">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta property="og:title" content="Beyond Theory: Building Practical Tools with Guile Scheme">
|
||||
<meta property="og:description" content="">
|
||||
<meta property="og:url" content="https://glenneth.org/content/posts/2024-12-03-practical-scheme.html">
|
||||
<title>Beyond Theory: Building Practical Tools with Guile Scheme - Glenn Thompson</title>
|
||||
<link href="../../dist/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Merriweather:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
.prose-palenight {
|
||||
--tw-prose-body: #bfc7d5;
|
||||
--tw-prose-headings: #ffd580;
|
||||
--tw-prose-links: #82aaff;
|
||||
--tw-prose-code: #c792ea;
|
||||
--tw-prose-pre-bg: #1b1e2b;
|
||||
}
|
||||
.prose h2 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-family: Merriweather, serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.625;
|
||||
}
|
||||
.prose a {
|
||||
color: var(--tw-prose-links);
|
||||
text-decoration: none;
|
||||
}
|
||||
.prose a:hover {
|
||||
color: #89ddff;
|
||||
}
|
||||
.prose code {
|
||||
color: var(--tw-prose-code);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.prose pre {
|
||||
background-color: var(--tw-prose-pre-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose ul, .prose ol {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-base-bg text-palenight-50">
|
||||
<nav class="fixed w-full bg-base-darker/80 backdrop-blur-sm shadow-sm z-50 border-b border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<a href="/" class="flex items-center font-serif text-xl font-bold text-accent-purple">Glenn Thompson</a>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<a href="/#about" class="nav-link text-accent-blue hover:text-accent-cyan">About</a>
|
||||
<a href="/#blog" class="nav-link text-accent-blue hover:text-accent-cyan">Blog</a>
|
||||
<a href="/#projects" class="nav-link text-accent-blue hover:text-accent-cyan">Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="pt-24 pb-16 px-4">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="content text-palenight-100 space-y-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<a href="/" class="inline-flex items-center text-accent-blue hover:text-accent-cyan transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
<time datetime="2024-12-03 10:00" class="text-palenight-300">2024-12-03 10:00</time>
|
||||
</div>
|
||||
|
||||
<header class="mb-8">
|
||||
<h1 class="text-4xl font-serif font-bold text-accent-yellow">Beyond Theory: Building Practical Tools with Guile Scheme</h1>
|
||||
<div class="flex items-center gap-4 text-palenight-300 mt-4">
|
||||
<time datetime="2024-12-03 10:00">2024-12-03 10:00</time>
|
||||
<span>•</span>
|
||||
<span>5 min read</span>
|
||||
<span>•</span>
|
||||
<span>By Glenn Thompson</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<article class="prose prose-palenight max-w-none">
|
||||
<h1>Beyond Theory: Building Practical Tools with Guile Scheme</h1>
|
||||
<h2>Introduction</h2>
|
||||
<p>A few months ago, I shared my journey into learning Scheme through building <code>stash</code>, a symlink manager. Since then, I've discovered that the gap between learning Scheme and applying it to real-world problems is where the most valuable lessons emerge. This post explores what I've learned about building practical tools with Guile Scheme, sharing both successes and challenges along the way.</p>
|
||||
<h2>The Power of Modular Design</h2>
|
||||
<p>One of the most important lessons I learned was the value of modular design. Breaking down a program into focused, single-responsibility modules not only makes the code more maintainable but also helps in reasoning about the program's behavior. Here's how I structured <code>stash</code>:</p>
|
||||
<pre><code class="language-scheme">(use-modules (ice-9 getopt-long)
|
||||
(stash help) ;; Help module
|
||||
(stash colors) ;; ANSI colors
|
||||
(stash log) ;; Logging module
|
||||
(stash paths) ;; Path handling module
|
||||
(stash conflict) ;; Conflict resolution module
|
||||
(stash file-ops)) ;; File and symlink operations module
|
||||
</code></pre>
|
||||
<p>Each module has a specific responsibility:</p>
|
||||
<ul>
|
||||
<li><code>colors.scm</code>: Handles ANSI color formatting for terminal output</li>
|
||||
<li><code>conflict.scm</code>: Manages conflict resolution when files already exist</li>
|
||||
<li><code>file-ops.scm</code>: Handles file system operations</li>
|
||||
<li><code>help.scm</code>: Provides usage information</li>
|
||||
<li><code>log.scm</code>: Manages logging operations</li>
|
||||
<li><code>paths.scm</code>: Handles path manipulation and normalization</li>
|
||||
</ul>
|
||||
<h2>Robust Path Handling</h2>
|
||||
<p>One of the first challenges in building a file management tool is handling paths correctly. Here's how I approached it:</p>
|
||||
<pre><code class="language-scheme">(define (expand-home path)
|
||||
"Expand ~ to the user's home directory."
|
||||
(if (string-prefix? "~" path)
|
||||
(string-append (getenv "HOME") (substring path 1))
|
||||
path))
|
||||
|
||||
(define (concat-path base path)
|
||||
"Concatenate two paths, ensuring there are no double slashes."
|
||||
(if (string-suffix? "/" base)
|
||||
(string-append (string-drop-right base 1) "/" path)
|
||||
(string-append base "/" path)))
|
||||
|
||||
(define (ensure-config-path target-dir)
|
||||
"Ensure that the target directory has .config appended, avoiding double slashes."
|
||||
(let ((target-dir (expand-home target-dir)))
|
||||
(if (string-suffix? "/" target-dir)
|
||||
(set! target-dir (string-drop-right target-dir 1)))
|
||||
(if (not (string-suffix? "/.config" target-dir))
|
||||
(string-append target-dir "/.config")
|
||||
target-dir)))
|
||||
</code></pre>
|
||||
<p>This approach ensures that:</p>
|
||||
<ul>
|
||||
<li>Home directory references (<code>~</code>) are properly expanded</li>
|
||||
<li>Path concatenation doesn't create double slashes</li>
|
||||
<li>Configuration paths are consistently structured</li>
|
||||
</ul>
|
||||
<h2>Interactive Conflict Resolution</h2>
|
||||
<p>Real-world tools often need to handle conflicts. I implemented an interactive conflict resolution system:</p>
|
||||
<pre><code class="language-scheme">(define (prompt-user-for-action)
|
||||
"Prompt the user to decide how to handle a conflict: overwrite (o), skip (s), or cancel (c)."
|
||||
(display (color-message
|
||||
"A conflict was detected. Choose action - Overwrite (o), Skip (s), or Cancel (c): "
|
||||
yellow-text))
|
||||
(let ((response (read-line)))
|
||||
(cond
|
||||
((string-ci=? response "o") 'overwrite)
|
||||
((string-ci=? response "s") 'skip)
|
||||
((string-ci=? response "c") 'cancel)
|
||||
(else
|
||||
(display "Invalid input. Please try again.\n")
|
||||
(prompt-user-for-action)))))
|
||||
</code></pre>
|
||||
<p>This provides a user-friendly interface for resolving conflicts while maintaining data safety.</p>
|
||||
<h2>Logging for Debugging and Auditing</h2>
|
||||
<p>Proper logging is crucial for debugging and auditing. I implemented a simple but effective logging system:</p>
|
||||
<pre><code class="language-scheme">(define (current-timestamp)
|
||||
"Return the current date and time as a formatted string."
|
||||
(let* ((time (current-time))
|
||||
(seconds (time-second time)))
|
||||
(strftime "%Y-%m-%d-%H-%M-%S" (localtime seconds))))
|
||||
|
||||
(define (log-action message)
|
||||
"Log an action with a timestamp to the stash.log file."
|
||||
(let ((log-port (open-file "stash.log" "a")))
|
||||
(display (color-message
|
||||
(string-append "[" (current-timestamp) "] " message)
|
||||
green-text) log-port)
|
||||
(newline log-port)
|
||||
(close-port log-port)))
|
||||
</code></pre>
|
||||
<p>This logging system:</p>
|
||||
<ul>
|
||||
<li>Timestamps each action</li>
|
||||
<li>Uses color coding for better readability</li>
|
||||
<li>Maintains a persistent log file</li>
|
||||
<li>Properly handles file operations</li>
|
||||
</ul>
|
||||
<h2>File Operations with Safety</h2>
|
||||
<p>When dealing with file system operations, safety is paramount. Here's how I handle moving directories:</p>
|
||||
<pre><code class="language-scheme">(define (move-source-to-target source-dir target-dir)
|
||||
"Move the entire source directory to the target directory, ensuring .config in the target path."
|
||||
(let* ((target-dir (ensure-config-path target-dir))
|
||||
(source-dir (expand-home source-dir))
|
||||
(source-name (basename source-dir))
|
||||
(target-source-dir (concat-path target-dir source-name)))
|
||||
(if (not (file-exists? target-dir))
|
||||
(mkdir target-dir #o755))
|
||||
(if (file-exists? target-source-dir)
|
||||
(handle-conflict target-source-dir source-dir delete-directory log-action)
|
||||
(begin
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (format #f "Moved ~a to ~a\n" source-dir target-source-dir))
|
||||
(log-action (format #f "Moved ~a to ~a" source-dir target-source-dir))))
|
||||
target-source-dir))
|
||||
</code></pre>
|
||||
<p>This implementation:</p>
|
||||
<ul>
|
||||
<li>Ensures paths are properly formatted</li>
|
||||
<li>Creates necessary directories</li>
|
||||
<li>Handles conflicts gracefully</li>
|
||||
<li>Logs all operations</li>
|
||||
<li>Returns the new path for further operations</li>
|
||||
</ul>
|
||||
<h2>Lessons Learned</h2>
|
||||
<h3>What Worked Well</h3>
|
||||
<ol>
|
||||
<li><strong>Modular Design</strong>: Breaking the code into focused modules made it easier to maintain and test</li>
|
||||
<li><strong>Functional Approach</strong>: Using pure functions where possible made the code more predictable</li>
|
||||
<li><strong>Interactive Interface</strong>: Providing clear user prompts and colored output improved usability</li>
|
||||
<li><strong>Robust Logging</strong>: Detailed logging helped with debugging and understanding program flow</li>
|
||||
</ol>
|
||||
<h3>Challenges Faced</h3>
|
||||
<ol>
|
||||
<li><strong>Path Handling</strong>: Dealing with different path formats and edge cases required careful attention</li>
|
||||
<li><strong>Error States</strong>: Managing various error conditions while keeping the code clean</li>
|
||||
<li><strong>User Interface</strong>: Balancing between automation and user control</li>
|
||||
<li><strong>Documentation</strong>: Writing clear documentation that helps users understand the tool</li>
|
||||
</ol>
|
||||
<h2>Moving Forward</h2>
|
||||
<p>Building <code>stash</code> has taught me that while functional programming principles are valuable, pragmatism is equally important. The key is finding the right balance between elegant functional code and practical solutions.</p>
|
||||
<h2>Resources</h2>
|
||||
<ol>
|
||||
<li><a href="https://www.gnu.org/software/guile/manual/">Guile Manual</a></li>
|
||||
<li><a href="/content/posts/scheme-journey.html">My Previous Scheme Journey Post</a></li>
|
||||
<li><a href="https://systemcrafters.net/community">System Crafters Community</a></li>
|
||||
<li><a href="https://codeberg.org/glenneth/stash">Stash on Codeberg</a></li>
|
||||
</ol>
|
||||
<p>The code examples in this post are from my actual implementation of <code>stash</code>. Feel free to explore, use, and improve upon them!</p>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="bg-base-darker text-palenight-200 py-12 border-t border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<p class="text-palenight-300">© 2024 Glenn Thompson. All rights reserved.</p>
|
||||
<div class="webring-text mt-6">
|
||||
<p class="text-palenight-300">I am part of the <a href="https://systemcrafters.net" target="_blank" class="text-accent-blue hover:text-accent-cyan">System Crafters</a> webring:</p>
|
||||
</div>
|
||||
<div class="craftering mt-2 flex items-center justify-center gap-4 text-accent-blue">
|
||||
<a href="https://craftering.systemcrafters.net/@glenneth/previous" class="hover:text-accent-cyan">←</a>
|
||||
<a href="https://craftering.systemcrafters.net/" class="hover:text-accent-cyan">craftering</a>
|
||||
<a href="https://craftering.systemcrafters.net/@glenneth/next" class="hover:text-accent-cyan">→</a>
|
||||
</div>
|
||||
<p class="text-palenight-300 mt-2">
|
||||
<a href="mailto:glenn@glenneth.org" class="text-accent-blue hover:text-accent-cyan transition-colors">glenn@glenneth.org</a> |
|
||||
<a href="https://glenneth.org" class="text-accent-blue hover:text-accent-cyan transition-colors">glenneth.org</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
---
|
||||
title: Beyond Theory: Building Practical Tools with Guile Scheme
|
||||
author: Glenn Thompson
|
||||
date: 2024-12-03 10:00
|
||||
tags: tech, guile, scheme, development, functional-programming
|
||||
---
|
||||
|
||||
# Beyond Theory: Building Practical Tools with Guile Scheme
|
||||
|
||||
## Introduction
|
||||
|
||||
A few months ago, I shared my journey into learning Scheme through building `stash`, a symlink manager. Since then, I've discovered that the gap between learning Scheme and applying it to real-world problems is where the most valuable lessons emerge. This post explores what I've learned about building practical tools with Guile Scheme, sharing both successes and challenges along the way.
|
||||
|
||||
## The Power of Modular Design
|
||||
|
||||
One of the most important lessons I learned was the value of modular design. Breaking down a program into focused, single-responsibility modules not only makes the code more maintainable but also helps in reasoning about the program's behavior. Here's how I structured `stash`:
|
||||
|
||||
```scheme
|
||||
(use-modules (ice-9 getopt-long)
|
||||
(stash help) ;; Help module
|
||||
(stash colors) ;; ANSI colors
|
||||
(stash log) ;; Logging module
|
||||
(stash paths) ;; Path handling module
|
||||
(stash conflict) ;; Conflict resolution module
|
||||
(stash file-ops)) ;; File and symlink operations module
|
||||
```
|
||||
|
||||
Each module has a specific responsibility:
|
||||
- `colors.scm`: Handles ANSI color formatting for terminal output
|
||||
- `conflict.scm`: Manages conflict resolution when files already exist
|
||||
- `file-ops.scm`: Handles file system operations
|
||||
- `help.scm`: Provides usage information
|
||||
- `log.scm`: Manages logging operations
|
||||
- `paths.scm`: Handles path manipulation and normalization
|
||||
|
||||
## Robust Path Handling
|
||||
|
||||
One of the first challenges in building a file management tool is handling paths correctly. Here's how I approached it:
|
||||
|
||||
```scheme
|
||||
(define (expand-home path)
|
||||
"Expand ~ to the user's home directory."
|
||||
(if (string-prefix? "~" path)
|
||||
(string-append (getenv "HOME") (substring path 1))
|
||||
path))
|
||||
|
||||
(define (concat-path base path)
|
||||
"Concatenate two paths, ensuring there are no double slashes."
|
||||
(if (string-suffix? "/" base)
|
||||
(string-append (string-drop-right base 1) "/" path)
|
||||
(string-append base "/" path)))
|
||||
|
||||
(define (ensure-config-path target-dir)
|
||||
"Ensure that the target directory has .config appended, avoiding double slashes."
|
||||
(let ((target-dir (expand-home target-dir)))
|
||||
(if (string-suffix? "/" target-dir)
|
||||
(set! target-dir (string-drop-right target-dir 1)))
|
||||
(if (not (string-suffix? "/.config" target-dir))
|
||||
(string-append target-dir "/.config")
|
||||
target-dir)))
|
||||
```
|
||||
|
||||
This approach ensures that:
|
||||
- Home directory references (`~`) are properly expanded
|
||||
- Path concatenation doesn't create double slashes
|
||||
- Configuration paths are consistently structured
|
||||
|
||||
## Interactive Conflict Resolution
|
||||
|
||||
Real-world tools often need to handle conflicts. I implemented an interactive conflict resolution system:
|
||||
|
||||
```scheme
|
||||
(define (prompt-user-for-action)
|
||||
"Prompt the user to decide how to handle a conflict: overwrite (o), skip (s), or cancel (c)."
|
||||
(display (color-message
|
||||
"A conflict was detected. Choose action - Overwrite (o), Skip (s), or Cancel (c): "
|
||||
yellow-text))
|
||||
(let ((response (read-line)))
|
||||
(cond
|
||||
((string-ci=? response "o") 'overwrite)
|
||||
((string-ci=? response "s") 'skip)
|
||||
((string-ci=? response "c") 'cancel)
|
||||
(else
|
||||
(display "Invalid input. Please try again.\n")
|
||||
(prompt-user-for-action)))))
|
||||
```
|
||||
|
||||
This provides a user-friendly interface for resolving conflicts while maintaining data safety.
|
||||
|
||||
## Logging for Debugging and Auditing
|
||||
|
||||
Proper logging is crucial for debugging and auditing. I implemented a simple but effective logging system:
|
||||
|
||||
```scheme
|
||||
(define (current-timestamp)
|
||||
"Return the current date and time as a formatted string."
|
||||
(let* ((time (current-time))
|
||||
(seconds (time-second time)))
|
||||
(strftime "%Y-%m-%d-%H-%M-%S" (localtime seconds))))
|
||||
|
||||
(define (log-action message)
|
||||
"Log an action with a timestamp to the stash.log file."
|
||||
(let ((log-port (open-file "stash.log" "a")))
|
||||
(display (color-message
|
||||
(string-append "[" (current-timestamp) "] " message)
|
||||
green-text) log-port)
|
||||
(newline log-port)
|
||||
(close-port log-port)))
|
||||
```
|
||||
|
||||
This logging system:
|
||||
- Timestamps each action
|
||||
- Uses color coding for better readability
|
||||
- Maintains a persistent log file
|
||||
- Properly handles file operations
|
||||
|
||||
## File Operations with Safety
|
||||
|
||||
When dealing with file system operations, safety is paramount. Here's how I handle moving directories:
|
||||
|
||||
```scheme
|
||||
(define (move-source-to-target source-dir target-dir)
|
||||
"Move the entire source directory to the target directory, ensuring .config in the target path."
|
||||
(let* ((target-dir (ensure-config-path target-dir))
|
||||
(source-dir (expand-home source-dir))
|
||||
(source-name (basename source-dir))
|
||||
(target-source-dir (concat-path target-dir source-name)))
|
||||
(if (not (file-exists? target-dir))
|
||||
(mkdir target-dir #o755))
|
||||
(if (file-exists? target-source-dir)
|
||||
(handle-conflict target-source-dir source-dir delete-directory log-action)
|
||||
(begin
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (format #f "Moved ~a to ~a\n" source-dir target-source-dir))
|
||||
(log-action (format #f "Moved ~a to ~a" source-dir target-source-dir))))
|
||||
target-source-dir))
|
||||
```
|
||||
|
||||
This implementation:
|
||||
- Ensures paths are properly formatted
|
||||
- Creates necessary directories
|
||||
- Handles conflicts gracefully
|
||||
- Logs all operations
|
||||
- Returns the new path for further operations
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
### What Worked Well
|
||||
1. **Modular Design**: Breaking the code into focused modules made it easier to maintain and test
|
||||
2. **Functional Approach**: Using pure functions where possible made the code more predictable
|
||||
3. **Interactive Interface**: Providing clear user prompts and colored output improved usability
|
||||
4. **Robust Logging**: Detailed logging helped with debugging and understanding program flow
|
||||
|
||||
### Challenges Faced
|
||||
1. **Path Handling**: Dealing with different path formats and edge cases required careful attention
|
||||
2. **Error States**: Managing various error conditions while keeping the code clean
|
||||
3. **User Interface**: Balancing between automation and user control
|
||||
4. **Documentation**: Writing clear documentation that helps users understand the tool
|
||||
|
||||
## Moving Forward
|
||||
|
||||
Building `stash` has taught me that while functional programming principles are valuable, pragmatism is equally important. The key is finding the right balance between elegant functional code and practical solutions.
|
||||
|
||||
## Resources
|
||||
|
||||
1. [Guile Manual](https://www.gnu.org/software/guile/manual/)
|
||||
2. [My Previous Scheme Journey Post](/content/posts/scheme-journey.html)
|
||||
3. [System Crafters Community](https://systemcrafters.net/community)
|
||||
4. [Stash on Codeberg](https://codeberg.org/glenneth/stash)
|
||||
|
||||
The code examples in this post are from my actual implementation of `stash`. Feel free to explore, use, and improve upon them!
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="bg-base-bg">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta property="og:title" content="Beyond Theory: Building Practical Tools with Guile Scheme">
|
||||
<meta property="og:description" content="">
|
||||
<meta property="og:url" content="https://glenneth.org/content/posts/2024-12-03-practical-scheme.html">
|
||||
<title>Beyond Theory: Building Practical Tools with Guile Scheme - Glenn Thompson</title>
|
||||
<link href="../../dist/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Merriweather:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
.prose-palenight {
|
||||
--tw-prose-body: #bfc7d5;
|
||||
--tw-prose-headings: #ffd580;
|
||||
--tw-prose-links: #82aaff;
|
||||
--tw-prose-code: #c792ea;
|
||||
--tw-prose-pre-bg: #1b1e2b;
|
||||
}
|
||||
.prose h2 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-family: Merriweather, serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.625;
|
||||
}
|
||||
.prose a {
|
||||
color: var(--tw-prose-links);
|
||||
text-decoration: none;
|
||||
}
|
||||
.prose a:hover {
|
||||
color: #89ddff;
|
||||
}
|
||||
.prose code {
|
||||
color: var(--tw-prose-code);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.prose pre {
|
||||
background-color: var(--tw-prose-pre-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose ul, .prose ol {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-base-bg text-palenight-50">
|
||||
<nav class="fixed w-full bg-base-darker/80 backdrop-blur-sm shadow-sm z-50 border-b border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<a href="/" class="flex items-center font-serif text-xl font-bold text-accent-purple">Glenn Thompson</a>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<a href="/#about" class="nav-link text-accent-blue hover:text-accent-cyan">About</a>
|
||||
<a href="/#blog" class="nav-link text-accent-blue hover:text-accent-cyan">Blog</a>
|
||||
<a href="/#projects" class="nav-link text-accent-blue hover:text-accent-cyan">Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="pt-24 pb-16 px-4">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="content text-palenight-100 space-y-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<a href="/" class="inline-flex items-center text-accent-blue hover:text-accent-cyan transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
<time datetime="2024-12-03 10:00" class="text-palenight-300">2024-12-03 10:00</time>
|
||||
</div>
|
||||
|
||||
<header class="mb-8">
|
||||
<h1 class="text-4xl font-serif font-bold text-accent-yellow">Beyond Theory: Building Practical Tools with Guile Scheme</h1>
|
||||
<div class="flex items-center gap-4 text-palenight-300 mt-4">
|
||||
<time datetime="2024-12-03 10:00">2024-12-03 10:00</time>
|
||||
<span>•</span>
|
||||
<span>5 min read</span>
|
||||
<span>•</span>
|
||||
<span>By Glenn Thompson</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<article class="prose prose-palenight max-w-none">
|
||||
<h1>Beyond Theory: Building Practical Tools with Guile Scheme</h1>
|
||||
<h2>Introduction</h2>
|
||||
<p>A few months ago, I shared my journey into learning Scheme through building <code>stash</code>, a symlink manager. Since then, I've discovered that the gap between learning Scheme and applying it to real-world problems is where the most valuable lessons emerge. This post explores what I've learned about building practical tools with Guile Scheme, sharing both successes and challenges along the way.</p>
|
||||
<h2>The Power of Modular Design</h2>
|
||||
<p>One of the most important lessons I learned was the value of modular design. Breaking down a program into focused, single-responsibility modules not only makes the code more maintainable but also helps in reasoning about the program's behavior. Here's how I structured <code>stash</code>:</p>
|
||||
<pre><code class="language-scheme">(use-modules (ice-9 getopt-long)
|
||||
(stash help) ;; Help module
|
||||
(stash colors) ;; ANSI colors
|
||||
(stash log) ;; Logging module
|
||||
(stash paths) ;; Path handling module
|
||||
(stash conflict) ;; Conflict resolution module
|
||||
(stash file-ops)) ;; File and symlink operations module
|
||||
</code></pre>
|
||||
<p>Each module has a specific responsibility:</p>
|
||||
<ul>
|
||||
<li><code>colors.scm</code>: Handles ANSI color formatting for terminal output</li>
|
||||
<li><code>conflict.scm</code>: Manages conflict resolution when files already exist</li>
|
||||
<li><code>file-ops.scm</code>: Handles file system operations</li>
|
||||
<li><code>help.scm</code>: Provides usage information</li>
|
||||
<li><code>log.scm</code>: Manages logging operations</li>
|
||||
<li><code>paths.scm</code>: Handles path manipulation and normalization</li>
|
||||
</ul>
|
||||
<h2>Robust Path Handling</h2>
|
||||
<p>One of the first challenges in building a file management tool is handling paths correctly. Here's how I approached it:</p>
|
||||
<pre><code class="language-scheme">(define (expand-home path)
|
||||
"Expand ~ to the user's home directory."
|
||||
(if (string-prefix? "~" path)
|
||||
(string-append (getenv "HOME") (substring path 1))
|
||||
path))
|
||||
|
||||
(define (concat-path base path)
|
||||
"Concatenate two paths, ensuring there are no double slashes."
|
||||
(if (string-suffix? "/" base)
|
||||
(string-append (string-drop-right base 1) "/" path)
|
||||
(string-append base "/" path)))
|
||||
|
||||
(define (ensure-config-path target-dir)
|
||||
"Ensure that the target directory has .config appended, avoiding double slashes."
|
||||
(let ((target-dir (expand-home target-dir)))
|
||||
(if (string-suffix? "/" target-dir)
|
||||
(set! target-dir (string-drop-right target-dir 1)))
|
||||
(if (not (string-suffix? "/.config" target-dir))
|
||||
(string-append target-dir "/.config")
|
||||
target-dir)))
|
||||
</code></pre>
|
||||
<p>This approach ensures that:</p>
|
||||
<ul>
|
||||
<li>Home directory references (<code>~</code>) are properly expanded</li>
|
||||
<li>Path concatenation doesn't create double slashes</li>
|
||||
<li>Configuration paths are consistently structured</li>
|
||||
</ul>
|
||||
<h2>Interactive Conflict Resolution</h2>
|
||||
<p>Real-world tools often need to handle conflicts. I implemented an interactive conflict resolution system:</p>
|
||||
<pre><code class="language-scheme">(define (prompt-user-for-action)
|
||||
"Prompt the user to decide how to handle a conflict: overwrite (o), skip (s), or cancel (c)."
|
||||
(display (color-message
|
||||
"A conflict was detected. Choose action - Overwrite (o), Skip (s), or Cancel (c): "
|
||||
yellow-text))
|
||||
(let ((response (read-line)))
|
||||
(cond
|
||||
((string-ci=? response "o") 'overwrite)
|
||||
((string-ci=? response "s") 'skip)
|
||||
((string-ci=? response "c") 'cancel)
|
||||
(else
|
||||
(display "Invalid input. Please try again.\n")
|
||||
(prompt-user-for-action)))))
|
||||
</code></pre>
|
||||
<p>This provides a user-friendly interface for resolving conflicts while maintaining data safety.</p>
|
||||
<h2>Logging for Debugging and Auditing</h2>
|
||||
<p>Proper logging is crucial for debugging and auditing. I implemented a simple but effective logging system:</p>
|
||||
<pre><code class="language-scheme">(define (current-timestamp)
|
||||
"Return the current date and time as a formatted string."
|
||||
(let* ((time (current-time))
|
||||
(seconds (time-second time)))
|
||||
(strftime "%Y-%m-%d-%H-%M-%S" (localtime seconds))))
|
||||
|
||||
(define (log-action message)
|
||||
"Log an action with a timestamp to the stash.log file."
|
||||
(let ((log-port (open-file "stash.log" "a")))
|
||||
(display (color-message
|
||||
(string-append "[" (current-timestamp) "] " message)
|
||||
green-text) log-port)
|
||||
(newline log-port)
|
||||
(close-port log-port)))
|
||||
</code></pre>
|
||||
<p>This logging system:</p>
|
||||
<ul>
|
||||
<li>Timestamps each action</li>
|
||||
<li>Uses color coding for better readability</li>
|
||||
<li>Maintains a persistent log file</li>
|
||||
<li>Properly handles file operations</li>
|
||||
</ul>
|
||||
<h2>File Operations with Safety</h2>
|
||||
<p>When dealing with file system operations, safety is paramount. Here's how I handle moving directories:</p>
|
||||
<pre><code class="language-scheme">(define (move-source-to-target source-dir target-dir)
|
||||
"Move the entire source directory to the target directory, ensuring .config in the target path."
|
||||
(let* ((target-dir (ensure-config-path target-dir))
|
||||
(source-dir (expand-home source-dir))
|
||||
(source-name (basename source-dir))
|
||||
(target-source-dir (concat-path target-dir source-name)))
|
||||
(if (not (file-exists? target-dir))
|
||||
(mkdir target-dir #o755))
|
||||
(if (file-exists? target-source-dir)
|
||||
(handle-conflict target-source-dir source-dir delete-directory log-action)
|
||||
(begin
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (format #f "Moved ~a to ~a\n" source-dir target-source-dir))
|
||||
(log-action (format #f "Moved ~a to ~a" source-dir target-source-dir))))
|
||||
target-source-dir))
|
||||
</code></pre>
|
||||
<p>This implementation:</p>
|
||||
<ul>
|
||||
<li>Ensures paths are properly formatted</li>
|
||||
<li>Creates necessary directories</li>
|
||||
<li>Handles conflicts gracefully</li>
|
||||
<li>Logs all operations</li>
|
||||
<li>Returns the new path for further operations</li>
|
||||
</ul>
|
||||
<h2>Lessons Learned</h2>
|
||||
<h3>What Worked Well</h3>
|
||||
<ol>
|
||||
<li><strong>Modular Design</strong>: Breaking the code into focused modules made it easier to maintain and test</li>
|
||||
<li><strong>Functional Approach</strong>: Using pure functions where possible made the code more predictable</li>
|
||||
<li><strong>Interactive Interface</strong>: Providing clear user prompts and colored output improved usability</li>
|
||||
<li><strong>Robust Logging</strong>: Detailed logging helped with debugging and understanding program flow</li>
|
||||
</ol>
|
||||
<h3>Challenges Faced</h3>
|
||||
<ol>
|
||||
<li><strong>Path Handling</strong>: Dealing with different path formats and edge cases required careful attention</li>
|
||||
<li><strong>Error States</strong>: Managing various error conditions while keeping the code clean</li>
|
||||
<li><strong>User Interface</strong>: Balancing between automation and user control</li>
|
||||
<li><strong>Documentation</strong>: Writing clear documentation that helps users understand the tool</li>
|
||||
</ol>
|
||||
<h2>Moving Forward</h2>
|
||||
<p>Building <code>stash</code> has taught me that while functional programming principles are valuable, pragmatism is equally important. The key is finding the right balance between elegant functional code and practical solutions.</p>
|
||||
<h2>Resources</h2>
|
||||
<ol>
|
||||
<li><a href="https://www.gnu.org/software/guile/manual/">Guile Manual</a></li>
|
||||
<li><a href="/content/posts/scheme-journey.html">My Previous Scheme Journey Post</a></li>
|
||||
<li><a href="https://systemcrafters.net/community">System Crafters Community</a></li>
|
||||
<li><a href="https://codeberg.org/glenneth/stash">Stash on Codeberg</a></li>
|
||||
</ol>
|
||||
<p>The code examples in this post are from my actual implementation of <code>stash</code>. Feel free to explore, use, and improve upon them!</p>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="bg-base-darker text-palenight-200 py-12 border-t border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<p class="text-palenight-300">© 2024 Glenn Thompson. All rights reserved.</p>
|
||||
<div class="webring-text mt-6">
|
||||
<p class="text-palenight-300">I am part of the <a href="https://systemcrafters.net" target="_blank" class="text-accent-blue hover:text-accent-cyan">System Crafters</a> webring:</p>
|
||||
</div>
|
||||
<div class="craftering mt-2 flex items-center justify-center gap-4 text-accent-blue">
|
||||
<a href="https://craftering.systemcrafters.net/@glenneth/previous" class="hover:text-accent-cyan">←</a>
|
||||
<a href="https://craftering.systemcrafters.net/" class="hover:text-accent-cyan">craftering</a>
|
||||
<a href="https://craftering.systemcrafters.net/@glenneth/next" class="hover:text-accent-cyan">→</a>
|
||||
</div>
|
||||
<p class="text-palenight-300 mt-2">
|
||||
<a href="mailto:glenn@glenneth.org" class="text-accent-blue hover:text-accent-cyan transition-colors">glenn@glenneth.org</a> |
|
||||
<a href="https://glenneth.org" class="text-accent-blue hover:text-accent-cyan transition-colors">glenneth.org</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,347 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="bg-base-bg">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description"
|
||||
content="Glenn Thompson's personal blog about technology, engineering, and travel experiences in the Middle East">
|
||||
<meta property="og:title" content="Glenn Thompson - Technology, Engineering & Travel">
|
||||
<meta property="og:description"
|
||||
content="Exploring the intersection of electrical engineering, technology, and cultural experiences from two decades in the Middle East">
|
||||
<meta property="og:url" content="https://glenneth.org">
|
||||
<title>Glenn Thompson - Technology, Engineering & Travel</title>
|
||||
<link href="./dist/styles.css" rel="stylesheet">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Merriweather:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap"
|
||||
rel="stylesheet">
|
||||
<script defer src="./src/js/main.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-base-bg text-palenight-50">
|
||||
<nav class="fixed w-full bg-base-darker/80 backdrop-blur-sm shadow-sm z-50 border-b border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<a href="#" class="flex items-center font-serif text-xl font-bold text-accent-purple">Glenn
|
||||
Thompson</a>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<a href="#about" class="nav-link text-accent-blue hover:text-accent-cyan">About</a>
|
||||
<a href="#blog" class="nav-link text-accent-blue hover:text-accent-cyan">Blog</a>
|
||||
<a href="#projects" class="nav-link text-accent-blue hover:text-accent-cyan">Projects</a>
|
||||
<a href="#contact" class="nav-link text-accent-blue hover:text-accent-cyan">Contact</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<!-- Hero Section -->
|
||||
<section class="pt-24 pb-16 px-4 sm:pt-32 sm:pb-24 bg-base-bg">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl font-serif font-bold tracking-tight text-accent-yellow sm:text-6xl">
|
||||
Technology, Engineering & Travel
|
||||
</h1>
|
||||
<p class="mt-6 text-lg leading-8 text-palenight-100 max-w-2xl mx-auto">
|
||||
Exploring the intersection of electrical engineering, technology, and cultural experiences from
|
||||
two decades in the Middle East.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Featured Posts -->
|
||||
<section id="blog" class="py-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="text-3xl font-serif font-bold text-accent-yellow mb-8">Blog Posts</h2>
|
||||
<div class="grid gap-8 md:grid-cols-2">
|
||||
<!-- Practical Scheme Post -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Tech</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-12-03T10:00:00">December 3, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/2024-12-03-practical-scheme.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
Beyond Theory: Building Practical Tools with Guile Scheme
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">A deep dive into building real-world tools with Guile Scheme,
|
||||
featuring modular design, error handling, and practical solutions...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">scheme</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">guile</span>
|
||||
<span
|
||||
class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">development</span>
|
||||
</div>
|
||||
</article>
|
||||
<!-- Scheme Journey Post -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Tech</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-09-24T09:30:00">September 24, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/scheme-journey.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
A Journey into Scheme: Building a Simple Symlink Manager
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">Learning Guile Scheme and building a practical tool for
|
||||
managing symlinks...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">scheme</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">guile</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">gnu</span>
|
||||
</div>
|
||||
</article>
|
||||
<!-- GNU Guix Journey Post -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Tech</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-07-26T10:30:00">July 26, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/gnu-guix-journey.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
A Journey Through GNU Guix: From Installation to Returning to Arch Linux
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">An exploration into GNU Guix, its challenges, and the
|
||||
eventual return to Arch Linux...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">tech</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">gnu</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">linux</span>
|
||||
</div>
|
||||
</article>
|
||||
<!-- Hugo to Haunt Post -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Tech</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-05-15T10:30:00">May 15, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/hugo-to-haunt.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
Transitioning from Hugo to Haunt: Embracing Scheme and GNU Guix
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">A journey into the world of Scheme, GNU Guix, and static site
|
||||
generation...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">personal</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">tech</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">scheme</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Amman to Newcastle Journey -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Personal</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-05-01T17:40:58+03:00">May 1, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/amman-newcastle-journey.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
A Rollercoaster Week: From Amman to Newcastle, and back again
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">A journey filled with conference presentations, international
|
||||
travel, and unexpected challenges...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">work</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">travel</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Glove80 Review -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Tech</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-04-08">April 8, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/glove80-review.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
Aesthetic Meets Ergonomics: My Deep Dive into the Glove80 Keyboard
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">An in-depth review exploring the unique design, features, and
|
||||
impact on typing comfort of the Glove80 ergonomic keyboard...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">keyboards</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">tech</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Projects Section -->
|
||||
<section id="projects" class="py-16 bg-base-darker">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="text-3xl font-serif font-bold text-accent-yellow mb-8">Projects</h2>
|
||||
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- Personal Website Project -->
|
||||
<article
|
||||
class="bg-base-bg p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">Personal Website</h3>
|
||||
<p class="text-palenight-100 mb-4">A modern, responsive personal website built with HTML,
|
||||
TailwindCSS, and JavaScript. Features a dark theme and blog functionality.</p>
|
||||
<div class="flex gap-2 mb-4">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-darker text-xs">HTML</span>
|
||||
<span
|
||||
class="text-palenight-300 px-2 py-1 rounded-full bg-base-darker text-xs">TailwindCSS</span>
|
||||
<span
|
||||
class="text-palenight-300 px-2 py-1 rounded-full bg-base-darker text-xs">JavaScript</span>
|
||||
</div>
|
||||
<a href="https://github.com/glenneth1/personal-website"
|
||||
class="text-accent-cyan hover:text-accent-purple transition-colors">View Source →</a>
|
||||
</article>
|
||||
|
||||
<!-- Symlink Manager Project -->
|
||||
<article
|
||||
class="bg-base-bg p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">Scheme Symlink Manager</h3>
|
||||
<p class="text-palenight-100 mb-4">A command-line tool built with Guile Scheme for managing
|
||||
symbolic links in Unix-like systems. Simplifies dotfile management.</p>
|
||||
<div class="flex gap-2 mb-4">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-darker text-xs">Scheme</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-darker text-xs">Guile</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-darker text-xs">CLI</span>
|
||||
</div>
|
||||
<a href="#" class="text-accent-cyan hover:text-accent-purple transition-colors">Coming Soon
|
||||
→</a>
|
||||
</article>
|
||||
|
||||
<!-- Add Project Card -->
|
||||
<article
|
||||
class="bg-base-bg p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<p class="text-palenight-100 mb-2">More projects coming soon!</p>
|
||||
<p class="text-accent-cyan">Stay tuned...</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- About Section -->
|
||||
<section id="about" class="py-16 bg-base-bg">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 class="text-3xl font-serif font-bold text-accent-purple mb-6">About Me</h2>
|
||||
<p class="text-palenight-100 mb-4">
|
||||
With over 20 years in the electrical engineering field, I've had the privilege of working on
|
||||
groundbreaking projects across the Middle East. My journey has been marked by continuous
|
||||
learning, cultural exploration, and technological innovation.
|
||||
</p>
|
||||
<p class="text-palenight-100 mb-4">
|
||||
Beyond my professional work, I'm passionate about technology, particularly static site
|
||||
generation, Scheme programming, and tools like GNU Guix and Haunt. This blog is where I
|
||||
share my experiences, insights, and the lessons learned along the way.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-base-darker p-8 rounded-lg shadow-md border border-palenight-400/20">
|
||||
<h3 class="text-xl font-bold text-accent-yellow mb-4">Areas of Focus</h3>
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center text-palenight-100">
|
||||
<svg class="w-5 h-5 text-accent-blue mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Electrical Engineering
|
||||
</li>
|
||||
<li class="flex items-center text-palenight-100">
|
||||
<svg class="w-5 h-5 text-accent-blue mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Static Site Generation
|
||||
</li>
|
||||
<li class="flex items-center text-palenight-100">
|
||||
<svg class="w-5 h-5 text-accent-blue mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Scheme Programming
|
||||
</li>
|
||||
<li class="flex items-center text-palenight-100">
|
||||
<svg class="w-5 h-5 text-accent-blue mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Middle Eastern Culture
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Section -->
|
||||
<section id="contact" class="py-16 bg-base-darker">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<h2 class="text-3xl font-serif font-bold text-accent-purple mb-6">Get in Touch</h2>
|
||||
<p class="text-palenight-100 mb-8">
|
||||
Interested in connecting? Feel free to reach out for discussions about technology, engineering,
|
||||
or sharing travel stories.
|
||||
</p>
|
||||
<a href="mailto:glenn@glenneth.org"
|
||||
class="inline-flex items-center px-6 py-3 border border-accent-blue text-base font-medium rounded-md text-accent-blue hover:bg-accent-blue hover:text-base-bg transition-colors">
|
||||
Send a Message
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="bg-base-darker text-palenight-200 py-12 border-t border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<p class="text-palenight-300">© 2024 Glenn Thompson. All rights reserved.</p>
|
||||
<div class="webring-text mt-6">
|
||||
<p class="text-palenight-300">I am part of the <a href="https://systemcrafters.net" target="_blank"
|
||||
class="text-accent-blue hover:text-accent-cyan">System Crafters</a> webring:</p>
|
||||
</div>
|
||||
<div class="craftering mt-2 flex items-center justify-center gap-4 text-accent-blue">
|
||||
<a href="https://craftering.systemcrafters.net/@glenneth/previous"
|
||||
class="hover:text-accent-cyan">←</a>
|
||||
<a href="https://craftering.systemcrafters.net/" class="hover:text-accent-cyan">craftering</a>
|
||||
<a href="https://craftering.systemcrafters.net/@glenneth/next" class="hover:text-accent-cyan">→</a>
|
||||
</div>
|
||||
<p class="text-palenight-300 mt-2">
|
||||
<a href="mailto:glenn@glenneth.org"
|
||||
class="text-accent-blue hover:text-accent-cyan transition-colors">glenn@glenneth.org</a> |
|
||||
<a href="https://glenneth.org"
|
||||
class="text-accent-blue hover:text-accent-cyan transition-colors">glenneth.org</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -40,16 +40,30 @@ function convertMarkdownToHtml(mdFilePath) {
|
|||
const metadata = {};
|
||||
const content = markdown.replace(/^---\n([\s\S]*?)\n---\n/, (_, frontMatter) => {
|
||||
frontMatter.split('\n').forEach(line => {
|
||||
const [key, value] = line.split(': ');
|
||||
if (key && value) {
|
||||
metadata[key.trim()] = value.trim();
|
||||
const [key, ...valueParts] = line.split(':');
|
||||
if (key && valueParts.length > 0) {
|
||||
metadata[key.trim()] = valueParts.join(':').trim();
|
||||
}
|
||||
});
|
||||
return '';
|
||||
});
|
||||
|
||||
// Configure marked options for proper heading rendering
|
||||
const markedOptions = {
|
||||
headerIds: true,
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
pedantic: false,
|
||||
smartLists: true,
|
||||
smartypants: true
|
||||
};
|
||||
|
||||
// Convert markdown to HTML
|
||||
const articleContent = marked.parse(content, options);
|
||||
const articleContent = marked.parse(content, markedOptions);
|
||||
|
||||
// Calculate read time (rough estimate: 200 words per minute)
|
||||
const wordCount = content.trim().split(/\s+/).length;
|
||||
const readTime = Math.ceil(wordCount / 200);
|
||||
|
||||
// Create full HTML document
|
||||
const html = `<!DOCTYPE html>
|
||||
|
|
@ -57,36 +71,108 @@ function convertMarkdownToHtml(mdFilePath) {
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="${metadata.description || ''}">
|
||||
<meta property="og:title" content="${metadata.title || 'Blog Post'}">
|
||||
<meta property="og:description" content="${metadata.description || ''}">
|
||||
<meta property="og:url" content="https://glenneth.org/${mdFilePath.replace(/\.md$/, '.html')}">
|
||||
<title>${metadata.title || 'Blog Post'} - Glenn Thompson</title>
|
||||
<link href="/dist/styles.css" rel="stylesheet">
|
||||
<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=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;700&family=Merriweather:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link href="../../dist/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Merriweather:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
.prose-palenight {
|
||||
--tw-prose-body: #bfc7d5;
|
||||
--tw-prose-headings: #ffd580;
|
||||
--tw-prose-links: #82aaff;
|
||||
--tw-prose-code: #c792ea;
|
||||
--tw-prose-pre-bg: #1b1e2b;
|
||||
}
|
||||
.prose h2 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-family: Merriweather, serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.625;
|
||||
}
|
||||
.prose a {
|
||||
color: var(--tw-prose-links);
|
||||
text-decoration: none;
|
||||
}
|
||||
.prose a:hover {
|
||||
color: #89ddff;
|
||||
}
|
||||
.prose code {
|
||||
color: var(--tw-prose-code);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.prose pre {
|
||||
background-color: var(--tw-prose-pre-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose ul, .prose ol {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-base-bg text-palenight-200 font-sans">
|
||||
<header class="bg-base-darker border-b border-palenight-400/20">
|
||||
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="/" class="text-2xl font-serif font-bold text-accent-yellow hover:text-accent-cyan transition-colors">Glenn Thompson</a>
|
||||
<div class="flex space-x-6">
|
||||
<a href="/" class="text-palenight-200 hover:text-accent-cyan transition-colors">Home</a>
|
||||
<a href="/#blog" class="text-palenight-200 hover:text-accent-cyan transition-colors">Blog</a>
|
||||
<body class="bg-base-bg text-palenight-50">
|
||||
<nav class="fixed w-full bg-base-darker/80 backdrop-blur-sm shadow-sm z-50 border-b border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<a href="/" class="flex items-center font-serif text-xl font-bold text-accent-purple">Glenn Thompson</a>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<a href="/#about" class="nav-link text-accent-blue hover:text-accent-cyan">About</a>
|
||||
<a href="/#blog" class="nav-link text-accent-blue hover:text-accent-cyan">Blog</a>
|
||||
<a href="/#projects" class="nav-link text-accent-blue hover:text-accent-cyan">Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="py-12">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<article class="prose prose-invert prose-palenight max-w-none">
|
||||
<h1>${metadata.title || 'Blog Post'}</h1>
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-8">
|
||||
<span>${metadata.category || 'Blog'}</span>
|
||||
<span>•</span>
|
||||
<time datetime="${metadata.date || ''}">${metadata.date || ''}</time>
|
||||
<main class="pt-24 pb-16 px-4">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="content text-palenight-100 space-y-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<a href="/" class="inline-flex items-center text-accent-blue hover:text-accent-cyan transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
<time datetime="${metadata.date || ''}" class="text-palenight-300">${metadata.date || ''}</time>
|
||||
</div>
|
||||
${articleContent}
|
||||
</article>
|
||||
|
||||
<header class="mb-8">
|
||||
<h1 class="text-4xl font-serif font-bold text-accent-yellow">${metadata.title || 'Blog Post'}</h1>
|
||||
<div class="flex items-center gap-4 text-palenight-300 mt-4">
|
||||
<time datetime="${metadata.date || ''}">${metadata.date || ''}</time>
|
||||
<span>•</span>
|
||||
<span>${readTime} min read</span>
|
||||
<span>•</span>
|
||||
<span>By ${metadata.author || 'Glenn Thompson'}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<article class="prose prose-palenight max-w-none">
|
||||
${articleContent}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</main>${footer}`;
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
23
index.html
23
index.html
|
|
@ -57,6 +57,29 @@
|
|||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="text-3xl font-serif font-bold text-accent-yellow mb-8">Blog Posts</h2>
|
||||
<div class="grid gap-8 md:grid-cols-2">
|
||||
<!-- Practical Scheme Post -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-2">
|
||||
<span>Tech</span>
|
||||
<span>•</span>
|
||||
<time datetime="2024-12-03T10:00:00">December 3, 2024</time>
|
||||
</div>
|
||||
<h3 class="text-xl font-serif font-bold text-accent-yellow mb-3">
|
||||
<a href="/content/posts/2024-12-03-practical-scheme.html"
|
||||
class="hover:text-accent-cyan transition-colors">
|
||||
Beyond Theory: Building Practical Tools with Guile Scheme
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-palenight-100 mb-4">A deep dive into building real-world tools with Guile Scheme,
|
||||
featuring modular design, error handling, and practical solutions...</p>
|
||||
<div class="flex gap-2">
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">scheme</span>
|
||||
<span class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">guile</span>
|
||||
<span
|
||||
class="text-palenight-300 px-2 py-1 rounded-full bg-base-bg text-xs">development</span>
|
||||
</div>
|
||||
</article>
|
||||
<!-- Scheme Journey Post -->
|
||||
<article
|
||||
class="bg-base-darker p-6 rounded-lg shadow-lg border border-palenight-400/20 hover:border-accent-purple/40 transition-colors">
|
||||
|
|
|
|||
|
|
@ -40,16 +40,30 @@ function convertMarkdownToHtml(mdFilePath) {
|
|||
const metadata = {};
|
||||
const content = markdown.replace(/^---\n([\s\S]*?)\n---\n/, (_, frontMatter) => {
|
||||
frontMatter.split('\n').forEach(line => {
|
||||
const [key, value] = line.split(': ');
|
||||
if (key && value) {
|
||||
metadata[key.trim()] = value.trim();
|
||||
const [key, ...valueParts] = line.split(':');
|
||||
if (key && valueParts.length > 0) {
|
||||
metadata[key.trim()] = valueParts.join(':').trim();
|
||||
}
|
||||
});
|
||||
return '';
|
||||
});
|
||||
|
||||
// Configure marked options for proper heading rendering
|
||||
const markedOptions = {
|
||||
headerIds: true,
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
pedantic: false,
|
||||
smartLists: true,
|
||||
smartypants: true
|
||||
};
|
||||
|
||||
// Convert markdown to HTML
|
||||
const articleContent = marked.parse(content, options);
|
||||
const articleContent = marked.parse(content, markedOptions);
|
||||
|
||||
// Calculate read time (rough estimate: 200 words per minute)
|
||||
const wordCount = content.trim().split(/\s+/).length;
|
||||
const readTime = Math.ceil(wordCount / 200);
|
||||
|
||||
// Create full HTML document
|
||||
const html = `<!DOCTYPE html>
|
||||
|
|
@ -57,36 +71,108 @@ function convertMarkdownToHtml(mdFilePath) {
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="${metadata.description || ''}">
|
||||
<meta property="og:title" content="${metadata.title || 'Blog Post'}">
|
||||
<meta property="og:description" content="${metadata.description || ''}">
|
||||
<meta property="og:url" content="https://glenneth.org${mdFilePath.replace(/\.md$/, '')}">
|
||||
<title>${metadata.title || 'Blog Post'} - Glenn Thompson</title>
|
||||
<link href="/dist/styles.css" rel="stylesheet">
|
||||
<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=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;700&family=Merriweather:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link href="../../dist/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Merriweather:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
.prose-palenight {
|
||||
--tw-prose-body: #bfc7d5;
|
||||
--tw-prose-headings: #ffd580;
|
||||
--tw-prose-links: #82aaff;
|
||||
--tw-prose-code: #c792ea;
|
||||
--tw-prose-pre-bg: #1b1e2b;
|
||||
}
|
||||
.prose h2 {
|
||||
color: var(--tw-prose-headings);
|
||||
font-family: Merriweather, serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.625;
|
||||
}
|
||||
.prose a {
|
||||
color: var(--tw-prose-links);
|
||||
text-decoration: none;
|
||||
}
|
||||
.prose a:hover {
|
||||
color: #89ddff;
|
||||
}
|
||||
.prose code {
|
||||
color: var(--tw-prose-code);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.prose pre {
|
||||
background-color: var(--tw-prose-pre-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose ul, .prose ol {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-base-bg text-palenight-200 font-sans">
|
||||
<header class="bg-base-darker border-b border-palenight-400/20">
|
||||
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="/" class="text-2xl font-serif font-bold text-accent-yellow hover:text-accent-cyan transition-colors">Glenn Thompson</a>
|
||||
<div class="flex space-x-6">
|
||||
<a href="/" class="text-palenight-200 hover:text-accent-cyan transition-colors">Home</a>
|
||||
<a href="/#blog" class="text-palenight-200 hover:text-accent-cyan transition-colors">Blog</a>
|
||||
<body class="bg-base-bg text-palenight-50">
|
||||
<nav class="fixed w-full bg-base-darker/80 backdrop-blur-sm shadow-sm z-50 border-b border-palenight-400/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<a href="/" class="flex items-center font-serif text-xl font-bold text-accent-purple">Glenn Thompson</a>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<a href="/#about" class="nav-link text-accent-blue hover:text-accent-cyan">About</a>
|
||||
<a href="/#blog" class="nav-link text-accent-blue hover:text-accent-cyan">Blog</a>
|
||||
<a href="/#projects" class="nav-link text-accent-blue hover:text-accent-cyan">Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="py-12">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<article class="prose prose-invert prose-palenight max-w-none">
|
||||
<h1>${metadata.title || 'Blog Post'}</h1>
|
||||
<div class="flex items-center gap-2 text-accent-cyan text-sm mb-8">
|
||||
<span>${metadata.category || 'Blog'}</span>
|
||||
<span>•</span>
|
||||
<time datetime="${metadata.date || ''}">${metadata.date || ''}</time>
|
||||
<main class="pt-24 pb-16 px-4">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="content text-palenight-100 space-y-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<a href="/" class="inline-flex items-center text-accent-blue hover:text-accent-cyan transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
<time datetime="${metadata.date || ''}" class="text-palenight-300">${metadata.date || ''}</time>
|
||||
</div>
|
||||
${articleContent}
|
||||
</article>
|
||||
|
||||
<header class="mb-8">
|
||||
<h1 class="text-4xl font-serif font-bold text-accent-yellow">${metadata.title || 'Blog Post'}</h1>
|
||||
<div class="flex items-center gap-4 text-palenight-300 mt-4">
|
||||
<time datetime="${metadata.date || ''}">${metadata.date || ''}</time>
|
||||
<span>•</span>
|
||||
<span>${readTime} min read</span>
|
||||
<span>•</span>
|
||||
<span>By ${metadata.author || 'Glenn Thompson'}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<article class="prose prose-palenight max-w-none">
|
||||
${articleContent}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</main>${footer}`;
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue