Blog works

This commit is contained in:
Sammy Shear 2024-03-21 17:58:25 -04:00
parent d0ca44c219
commit ac5d3487e8
No known key found for this signature in database
GPG Key ID: DDBE1CD1708599BD
17 changed files with 449 additions and 10 deletions

View File

@ -3,7 +3,6 @@ import tailwind from "@astrojs/tailwind";
import react from "@astrojs/react"; import react from "@astrojs/react";
import robotsTxt from "astro-robots-txt"; import robotsTxt from "astro-robots-txt";
import sitemap from "@astrojs/sitemap"; import sitemap from "@astrojs/sitemap";
import compressor from "astro-compressor"; import compressor from "astro-compressor";
// https://astro.build/config // https://astro.build/config
@ -15,7 +14,8 @@ export default defineConfig({
themes: { themes: {
light: "catppuccin-latte", light: "catppuccin-latte",
dark: "catppuccin-mocha" dark: "catppuccin-mocha"
} },
wrap: true
} }
} }
}); });

View File

@ -28,6 +28,7 @@
}, },
"devDependencies": { "devDependencies": {
"@catppuccin/tailwindcss": "^0.1.6", "@catppuccin/tailwindcss": "^0.1.6",
"@tailwindcss/typography": "^0.5.10",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-astro": "^0.13.0" "prettier-plugin-astro": "^0.13.0"
} }

View File

@ -52,6 +52,9 @@ devDependencies:
'@catppuccin/tailwindcss': '@catppuccin/tailwindcss':
specifier: ^0.1.6 specifier: ^0.1.6
version: 0.1.6(tailwindcss@3.4.1) version: 0.1.6(tailwindcss@3.4.1)
'@tailwindcss/typography':
specifier: ^0.5.10
version: 0.5.10(tailwindcss@3.4.1)
prettier: prettier:
specifier: ^3.2.5 specifier: ^3.2.5
version: 3.2.5 version: 3.2.5
@ -879,6 +882,18 @@ packages:
resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==} resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==}
dev: false dev: false
/@tailwindcss/typography@0.5.10(tailwindcss@3.4.1):
resolution: {integrity: sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.4.1
dev: true
/@types/babel__core@7.20.5: /@types/babel__core@7.20.5:
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
dependencies: dependencies:
@ -2287,6 +2302,18 @@ packages:
p-locate: 5.0.0 p-locate: 5.0.0
dev: false dev: false
/lodash.castarray@4.4.0:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
dev: true
/lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
dev: true
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/log-symbols@5.1.0: /log-symbols@5.1.0:
resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3069,6 +3096,14 @@ packages:
postcss: 8.4.35 postcss: 8.4.35
postcss-selector-parser: 6.0.16 postcss-selector-parser: 6.0.16
/postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true
/postcss-selector-parser@6.0.16: /postcss-selector-parser@6.0.16:
resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
engines: {node: '>=4'} engines: {node: '>=4'}

24
src/components/Post.astro Normal file
View File

@ -0,0 +1,24 @@
---
import { type CollectionEntry } from "astro:content";
import Prose from "./Prose.astro";
export interface Props {
post: CollectionEntry<"blog">;
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<article class="ml-5 mr-5 mt-5 w-[90%] sm:w-3/4 md:w-1/2 lg:w-1/3">
<header class="flex flex-col space-between-10 border-b-ctp-text border-b">
<h1 class="text-2xl">{post.data.title}</h1>
<h4 class="text-lg text-ctp-subtext0">{post.data.author}</h4>
<h4 class="text-lg text-ctp-subtext0">{post.data.publishedTime}</h4>
</header>
<main class="mt-5">
<Prose>
<Content />
</Prose>
</main>
</article>

29
src/components/Posts.tsx Normal file
View File

@ -0,0 +1,29 @@
import type { CollectionEntry } from "astro:content";
type Props = {
posts: CollectionEntry<"blog">[];
};
export default function Posts({ posts }: Props) {
return (
<div className="flex flex-col space-y-10 mt-5">
{posts.map((post: CollectionEntry<"blog">) => {
return (
<a
className="flex flex-col bg-ctp-surface1 p-4 rounded-md"
key={post.id}
href={`/posts/${post.slug}`}
>
<span className="text-2xl">{post.data.title}</span>
<span className="text-sm text-ctp-subtext0">
{post.data.author}
</span>
<span className="text-sm text-ctp-subtext0">
{post.data.publishedTime.toLocaleString()}
</span>
</a>
);
})}
</div>
);
}

View File

@ -0,0 +1,12 @@
---
---
<div
class="prose dark:prose-invert
prose-h1:font-bold prose-h1:text-xl
prose-a:text-blue-600 prose-p:text-justify prose-img:rounded-xl
prose-headings:underline"
>
<slot />
</div>

View File

@ -3,6 +3,5 @@ import type { Site } from "./types";
export const SITE: Site = { export const SITE: Site = {
title: "sshear.dev", title: "sshear.dev",
author: "Sammy Shear", author: "Sammy Shear",
lightAndDarkMode: true, desc: "Sammy's personal site, portfolio, and blog."
postPerPage: 5
}; };

View File

@ -0,0 +1,207 @@
---
title: I Followed a 40 year old Pascal Book (in Rust)
publishedTime: 2024-01-05T19:09:05.026Z
---
My father is a big fan of mystery novels, certainly more than I am. I enjoy a Sherlock Holmes story now and again, and I have had fun reading Agatha Christie novels, but he is and always will be a bigger fan of the genre than I. That being said, there is one mystery book I find much more interesting to me than he ever has, and that is "Elementary Pascal" by Henry Ledgard and Andrew Singer. My father will happily tell you the furthest he got in computer programming was making a 3D Cube in Basic and reading the programs for his Commodore 64 in a magazine, but almost never running them as he would have had to copy them each by hand. I, on the other hand, have much more of an interest in programming, as might be evident from the fact I am writing this blog post where I do so. When I was first learning to program as a kid, my father gave me this book that he'd had since he was a kid. He claimed to have hardly read it, but that I might be interested in it, and compared to him I certainly was. That said, I only ever read a few of the examples, as I had very little actual interest in learning Pascal. In more recent years, the book has sat on my shelf, undisturbed, waiting to once again be opened. Today was that day.
### What is this book?
This book is a way of teaching Pascal by examples written in the form of Sherlock Holmes mysteries. Each chapter is a story which teaches you core concepts of the both the Pascal language and programming in general.
### What will I be doing?
I will be, rewriting the Pascal code presented in the book as Rust mostly for fun, but also to see how programming has evolved between the points of these two languages. If there are any optimizations I notice can be made, I'll do my best to implement them, but that isn't really what I'll be focusing on with this.
## Part 1 (Chapter 2): Murder at the Metropolitan Club
If you have this book and are following along at home, you may notice this is technically Chapter 2, but given Chapter 1 is more of an introduction to the concept of a computer than anything else, I figured I'd skip to this chapter for this article.
This chapter is all about solving a murder based on clues provided to an algorithm. The basic algorithm used in the book is summed up by the pseudocode they provide:
```
1. Look at the next clue
2. If it establishes a fact then:
record that fact
else
dismiss the clue
3. Repeat this process until the murderer is found.
```
The clues provided are as follows:
1. Sir Raymond Jasper occupied Room 10.
2. The man occupying Room 14 had black hair.
3. Either Sir Raymond or Colonel Woodley wore a pince-nez.
4. Mr. Pope always carried a gold pocket-watch.
5. One of the suspects was seen driving a 4-wheel carriage.
6. The man with the pince-nez had brown hair.
7. Mr. Holman wore a ruby signet ring.
8. The man in Room 16 had tattered cuffs.
9. Mr. Holman occupied Room 12.
10. The man with the tattered cuffs had red hair.
11. The man in Room 12 had gray hair.
12. The man with a gold pocket watch occupied Room 14.
13. Colonel Woodley occupied a corner room.
14. The murderer had brown hair.
This is all the information we have to feed into the algorithm, but there is also a map of the rooms we can use to define what is a corner room. The map shows us that rooms 16 and 10 are corner rooms. For this chapter I will not write any code, as the chapter simply goes over the algorithm and how to create an algorithm in general. In the next chapter we get our first Pascal code, and that's what I will be rewriting.
## Part 2 (Chapter 3): Holmes Gives a Demonstration
The Pascal code is fairly interesting to me in the way it approaches the problem. Since I don't really know Pascal all too well, I can't tell you if this was simplified for the purposes of the book or if this is truly the best way to solve this problem. Regardless, I find the code we are given to be fascinating. So as to save me the trouble of transcribing all of it into this article, I will show snippets of the Pascal code with some explanations along the way, then I will show my Rust equivalents.
```pascal
const
UNKNOWN = 0;
RED = 1; BLACK = 2; GREY = 3; BROWN = 4;
PINCENEZ = 1; GOLDWATCH = 2; RUBYRING = 3; TATTEREDCUFFS = 4;
COLWOODLEY = 1; MRHOLMAN = 2; MRPOPE = 3; SIRRAYMOND = 4;
var
SUSPECT, MURDERER: INTEGER;
HAIR : array [COLWOODLEY .. SIRRAYMOND] of INTEGER;
ATTIRE : array [COLWOODLEY .. SIRRAYMOND] of INTEGER;
ROOM : array [COLWOODLEY .. SIRRAYMOND] of INTEGER;
```
The first bit of the program assigns the variables and constants that will be used globally in the program. You can probably start piecing together the implementation of the algorithm based on these declarations. There are 3 arrays that are the length of a range between `COLWOODLEY` and `SIRRAYMOND`, each of integers, while each piece of information is defined as a constant between 1 and 4, along with an `UNKNOWN` of 0. The thought process is, of course, that each person represents an index of the arrays, and as such the data of the array `ATTIRE` at that index represents whether they're wearing, say, a ruby ring or a gold watch. The same concept going for the other arrays, of course.
I probably would never have thought of this approach and would have instead defined structs and enums to do essentially the same thing, but I find this approach very sensical and almost delightfully simple.
The next bit of the program assigns all the known and unknown pieces of data in the array.
```pascal
MURDERER := UNKNOWN;
for SUSPECT := COLWOODLEY to SIRRAYMOND do begin
HAIR[SUSPECT] := UNKNOWN;
ATTIRE[SUSPECT] := UNKNOWN;
ROOM[SUSPECT] := UNKNOWN
end;
ROOM[SIRRAYMOND] := 10;
ATTIRE[MRPOPE] := GOLDWATCH;
ATTIRE[MRHOLMAN] := RUBYRING;
ROOM[MRHOLMAN] := 12;
```
I don't think there's much explaining that needs to be done for that snippet. Anyway, for this next bit I won't be typing out each if statement, I'll instead show the while loop definition and a few of the if statements for assigning data, mostly the ones in which I find the syntax particularly interesting.
```pascal
SUSPECT := COLWOODLEY;
while MURDERER = UNKNOWN do begin
if (ROOM[SUSPECT] = 14) then
HAIR[SUSPECT] := BLACK;
if (ATTIRE[SIRRAYMOND]<>UNKNOWN) and (ATTIRE[SIRRAYMOND]<>PINCENEZ) then
ATTIRE[COLWOODLEY] := PINCENEZ
...
if (ATTIRE[SUSPECT] = PINCENEZ) then
HAIR[SUSPECT] := BROWN;
...
if (HAIR[SUSPECT] := BROWN) then
MURDERER := SUSPECT;
if (SUSPECT = SIRRAYMOND) then
SUSPECT := COLWOODLEY; { -- prevents an index out of bounds }
else
SUSPECT := SUSPECT + 1;
end;
```
As you can see from the basic structure, as long as the murderer is still unknown, it will perform an iteration where it tries to assign new information to people based on the clues, then increments (or sets back to `COLWOODLEY`) the suspect for the next iteration. Also of note, to me at least, is the not equal to operator being `<>`. At this point you've probably also noticed that the assignment operator is `:=`, much like Go, which frees up `=` to be the equal to operator. The final thing to note is that earlier when the arrays were being declared, you may have noticed the range operator is very similar to Rust, which I didn't know before starting this post, and found quite interesting.
Anyway, after this while loop is finished, it checks who the murderer is and prints it to the standard output with `WRITE`.
Overall it's a very simple program, which is refreshing for me because I have a bad habit of overengineering things, so seeing it done this way is very helpful to not do the same in my Rust solution.
### Rust
Now it's time to implement it in Rust. I will be doing a 1 for 1 transcription of the Pascal code to Rust, but in future chapters that may change.
```rust
const UNKNOWN: u8 = 0;
const RED: u8 = 1;
const PINCENEZ: u8 = 1;
const COLWOODLEY: usize = 1;
const BLACK: u8 = 2;
const GOLDWATCH: u8 = 2;
const MRHOLMAN: usize = 2;
const GREY: u8 = 3;
const RUBYRING: u8 = 3;
const MRPOPE: usize = 3;
const BROWN: u8 = 4;
const TATTEREDCUFFS: u8 = 4;
const SIRRAYMOND: usize = 4;
fn main() {
let mut murderer = UNKNOWN;
let mut hair: Vec<u8> = vec![UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN]; // dummy value at 0 to keep parity with Pascal
let mut attire: Vec<u8> = vec![UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN];
let mut room: Vec<u8> = vec![UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN];
// establish known clues
room[SIRRAYMOND as usize] = 10;
attire[MRPOPE as usize] = GOLDWATCH;
attire[MRHOLMAN as usize] = RUBYRING;
room[MRHOLMAN as usize] = 12;
let mut suspect = COLWOODLEY;
while murderer == UNKNOWN {
if room[suspect] == 14 {
hair[suspect] = BLACK;
}
if attire[SIRRAYMOND] != UNKNOWN && attire[SIRRAYMOND] != PINCENEZ {
attire[COLWOODLEY] = PINCENEZ;
}
if attire[COLWOODLEY] != UNKNOWN && attire[COLWOODLEY] != PINCENEZ {
attire[SIRRAYMOND] = PINCENEZ;
}
if attire[suspect] == PINCENEZ {
hair[suspect] = BROWN;
}
if attire[suspect] == TATTEREDCUFFS {
hair[suspect] = RED;
}
if room[suspect] == 16 {
attire[suspect] = TATTEREDCUFFS;
}
if room[suspect] == 12 {
hair[suspect] = GREY;
}
if attire[suspect] == GOLDWATCH {
room[suspect] = 14;
}
if room[suspect] == 10 && suspect != COLWOODLEY {
room[COLWOODLEY] = 16;
}
if room[suspect] == 16 && suspect != COLWOODLEY {
room[COLWOODLEY] = 10;
}
if hair[suspect] == BROWN {
murderer = suspect as u8;
}
if suspect == SIRRAYMOND {
suspect = COLWOODLEY;
} else {
suspect = suspect + 1;
}
}
if suspect == COLWOODLEY {
println!("Colonel Woodley is the murderer.");
}
if suspect == MRHOLMAN {
println!("Mr. Holman is the murderer.");
}
if suspect == MRPOPE {
println!("Mr. Pope is the murderer.");
}
if suspect == SIRRAYMOND {
println!("Sir Raymond is the murderer.");
}
}
```
That's about it for this chapter, and so I will end the post here. I will in all likelihood be making this a series, so if you're interested, stay tuned for post number 2 where I'll try to tackle the fourth chapter, "The Adventure of the Bathing Machine." (These first two chapters are more of a warm up for the format of the book, the following chapters will each be their own mystery).

13
src/content/config.ts Normal file
View File

@ -0,0 +1,13 @@
import { SITE } from "../config";
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
schema: z.object({
author: z.string().default(SITE.author),
title: z.string(),
publishedTime: z.date()
})
});
export const collections = { blog };

View File

@ -1,5 +1,16 @@
--- ---
import { SITE } from "../config"; import { SITE } from "../config";
export interface Props {
title?: string;
author?: string;
description?: string;
}
const {
title = SITE.title,
author = SITE.author,
description = SITE.desc
} = Astro.props;
--- ---
<html lang="en"> <html lang="en">
@ -8,7 +19,10 @@ import { SITE } from "../config";
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{SITE.title}</title> <meta name="author" content={author} />
<meta name="description" content={description} />
<link rel="sitemap" href="/sitemap-index.xml" />
<title>{title}</title>
</head> </head>
<body <body
class="flex flex-col place-items-center bg-ctp-base ctp-latte text-ctp-text dark:ctp-mocha" class="flex flex-col place-items-center bg-ctp-base ctp-latte text-ctp-text dark:ctp-mocha"
@ -17,6 +31,18 @@ import { SITE } from "../config";
</body> </body>
</html> </html>
<style is:global>
html.dark .astro-code,
html.dark .astro-code span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
</style>
<script is:inline> <script is:inline>
const theme = (() => { const theme = (() => {
if ( if (

View File

@ -0,0 +1,18 @@
---
import Layout from "./Layout.astro";
import Header from "../components/Header.astro";
import { SITE } from "../config";
export interface Props {
frontmatter: {
title: string;
};
}
const { frontmatter } = Astro.props;
---
<Layout title={`${frontmatter.title} | ${SITE.title}`}>
<Header />
<slot />
</Layout>

View File

@ -0,0 +1,18 @@
---
import Layout from "./Layout.astro";
import Header from "../components/Header.astro";
import { SITE } from "../config";
import { type CollectionEntry } from "astro:content";
import Posts from "../components/Posts";
export interface Props {
posts: CollectionEntry<"blog">[];
}
const { posts } = Astro.props;
---
<Layout title={`Blog Posts | ${SITE.title}`}>
<Header />
<Posts posts={posts} />
</Layout>

View File

@ -5,4 +5,27 @@ import Layout from "../layouts/Layout.astro";
<Layout> <Layout>
<Header /> <Header />
<main
class="ml-5 mr-5 mt-5 w-[90%] sm:w-3/4 md:w-1/2 lg:w-1/3 flex flex-col space-between-5"
>
<p>
Hi, I'm Sammy Shear, welcome to my personal site and blog. I'm a
developer with experience in both Web and Native development, but I
have been mostly focused on the Web. I have experience in backend
and frontend. On the frontend I mostly do work in React and Vue (and
I am partial to HTMX), but over the years I've used many other
frameworks and technologies in general. On the backend at this point
I most often use Rust with Actix Web or Axum, but I also have
experience with Node and Express, as well as RPC using tRPC (a
TypeScript RPC implementation).
<br />
This site mostly serves as a blog for me to write about things I do or
am interested in, but I will probably add more uses for the site as I
think of them.
</p>
<a
class="text-ctp-base bg-ctp-sky hover:bg-ctp-blue focus:ring-4 focus:ring-ctp-blue font-medium rounded-lg text-sm px-5 py-2.5 focus:outline-none place-self-center"
href="posts/">Blog</a
>
</main>
</Layout> </Layout>

View File

@ -0,0 +1,26 @@
---
import PostLayout from "../../../layouts/PostLayout.astro";
import Post from "../../../components/Post.astro";
import { getCollection, type CollectionEntry } from "astro:content";
export interface Props {
post: CollectionEntry<"blog">;
}
export async function getStaticPaths() {
const posts = await getCollection("blog");
const postResult = posts.map((post) => ({
params: { slug: post.slug },
props: { post }
}));
return [...postResult];
}
const { post } = Astro.props;
---
<PostLayout frontmatter={{ title: post.data.title }}>
<Post post={post} />
</PostLayout>

View File

@ -0,0 +1,8 @@
---
import { getCollection } from "astro:content";
import PostsLayout from "../../layouts/PostsLayout.astro";
const posts = await getCollection("blog");
---
<PostsLayout posts={posts} />

View File

@ -1,6 +1,5 @@
export type Site = { export type Site = {
desc: string;
title: string; title: string;
author: string; author: string;
lightAndDarkMode: boolean;
postPerPage: number;
}; };

View File

@ -32,6 +32,7 @@ export default {
plugins: [ plugins: [
catppuccin({ catppuccin({
prefix: "ctp" prefix: "ctp"
}) }),
require("@tailwindcss/typography")
] ]
}; };