From dbc3e0dfd893435f31cca39873f7ba9bf13b93a6 Mon Sep 17 00:00:00 2001
From: nsfisis
Date: Wed, 9 Apr 2025 20:27:54 +0900
Subject: feat(blog/nuldoc): change format of nuldoc builder from .ndoc to .dj
---
vhosts/blog/deno.jsonc | 1 +
vhosts/blog/deno.lock | 5 +
vhosts/blog/nuldoc-src/commands/build.ts | 6 +-
vhosts/blog/nuldoc-src/commands/new.ts | 14 +-
vhosts/blog/nuldoc-src/djot/djot2ndoc.ts | 826 ++++++++++++++++++++++++++++++
vhosts/blog/nuldoc-src/djot/document.ts | 60 +++
vhosts/blog/nuldoc-src/djot/parse.ts | 33 ++
vhosts/blog/nuldoc-src/djot/to_html.ts | 320 ++++++++++++
vhosts/blog/nuldoc-src/generators/post.ts | 4 +-
vhosts/blog/nuldoc-src/ndoc/document.ts | 66 ---
vhosts/blog/nuldoc-src/ndoc/parse.ts | 33 --
vhosts/blog/nuldoc-src/ndoc/to_html.ts | 292 -----------
vhosts/blog/nuldoc-src/pages/PostPage.tsx | 2 +-
vhosts/blog/nuldoc-src/renderers/html.ts | 2 +
vhosts/blog/nuldoc-src/xml.ts | 265 ----------
vhosts/blog/nuldoc-src/xml_test.ts | 17 -
16 files changed, 1258 insertions(+), 688 deletions(-)
create mode 100644 vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
create mode 100644 vhosts/blog/nuldoc-src/djot/document.ts
create mode 100644 vhosts/blog/nuldoc-src/djot/parse.ts
create mode 100644 vhosts/blog/nuldoc-src/djot/to_html.ts
delete mode 100644 vhosts/blog/nuldoc-src/ndoc/document.ts
delete mode 100644 vhosts/blog/nuldoc-src/ndoc/parse.ts
delete mode 100644 vhosts/blog/nuldoc-src/ndoc/to_html.ts
delete mode 100644 vhosts/blog/nuldoc-src/xml.ts
delete mode 100644 vhosts/blog/nuldoc-src/xml_test.ts
(limited to 'vhosts/blog')
diff --git a/vhosts/blog/deno.jsonc b/vhosts/blog/deno.jsonc
index b0550a5b..55c2fc2e 100644
--- a/vhosts/blog/deno.jsonc
+++ b/vhosts/blog/deno.jsonc
@@ -1,5 +1,6 @@
{
"imports": {
+ "@djot/djot": "npm:@djot/djot@^0.3.2",
"@std/assert": "jsr:@std/assert@^1.0.12",
"@std/cli": "jsr:@std/cli@^1.0.15",
"@std/fs": "jsr:@std/fs@^1.0.15",
diff --git a/vhosts/blog/deno.lock b/vhosts/blog/deno.lock
index 300594ba..f9eda6fb 100644
--- a/vhosts/blog/deno.lock
+++ b/vhosts/blog/deno.lock
@@ -16,6 +16,7 @@
"jsr:@std/path@^1.0.8": "1.0.8",
"jsr:@std/streams@^1.0.9": "1.0.9",
"jsr:@std/toml@^1.0.3": "1.0.3",
+ "npm:@djot/djot@~0.3.2": "0.3.2",
"npm:shiki@^3.2.1": "3.2.1"
},
"jsr": {
@@ -85,6 +86,9 @@
}
},
"npm": {
+ "@djot/djot@0.3.2": {
+ "integrity": "sha512-joMKR24B8rxueyFiJbpZAqEiypjvOyzTxzkhyr0q5mM/sUBaOD3unna/9IxtOotFugViyYlkIRaiXg3xM//zxg=="
+ },
"@shikijs/core@3.2.1": {
"integrity": "sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==",
"dependencies": [
@@ -549,6 +553,7 @@
"jsr:@std/http@^1.0.13",
"jsr:@std/path@^1.0.8",
"jsr:@std/toml@^1.0.3",
+ "npm:@djot/djot@~0.3.2",
"npm:shiki@^3.2.1"
]
}
diff --git a/vhosts/blog/nuldoc-src/commands/build.ts b/vhosts/blog/nuldoc-src/commands/build.ts
index 4702fd0a..52aca1f7 100644
--- a/vhosts/blog/nuldoc-src/commands/build.ts
+++ b/vhosts/blog/nuldoc-src/commands/build.ts
@@ -2,7 +2,7 @@ import { dirname, join, joinGlobs } from "@std/path";
import { ensureDir, expandGlob } from "@std/fs";
import { generateFeedPageFromEntries } from "../generators/atom.ts";
import { Config, getTagLabel } from "../config.ts";
-import { parseNulDocFile } from "../ndoc/parse.ts";
+import { parseDjotFile } from "../djot/parse.ts";
import { Page } from "../page.ts";
import { render } from "../render.ts";
import { dateToString } from "../revision.ts";
@@ -49,7 +49,7 @@ async function buildPostPages(config: Config): Promise {
async function collectPostFiles(sourceDir: string): Promise {
const filePaths = [];
- const globPattern = joinGlobs([sourceDir, "**", "*.ndoc"]);
+ const globPattern = joinGlobs([sourceDir, "**", "*.dj"]);
for await (const entry of expandGlob(globPattern)) {
filePaths.push(entry.path);
}
@@ -63,7 +63,7 @@ async function parsePosts(
const posts = [];
for (const postFile of postFiles) {
posts.push(
- await generatePostPage(await parseNulDocFile(postFile, config), config),
+ await generatePostPage(await parseDjotFile(postFile, config), config),
);
}
return posts;
diff --git a/vhosts/blog/nuldoc-src/commands/new.ts b/vhosts/blog/nuldoc-src/commands/new.ts
index e694ce20..651c59e6 100644
--- a/vhosts/blog/nuldoc-src/commands/new.ts
+++ b/vhosts/blog/nuldoc-src/commands/new.ts
@@ -50,7 +50,7 @@ OPTIONS:
}
function getFilename(type: "post" | "slide"): string {
- return type === "post" ? "TODO.ndoc" : "TODO.toml";
+ return type === "post" ? "TODO.dj" : "TODO.toml";
}
function getDirPath(type: "post" | "slide"): string {
@@ -73,14 +73,10 @@ tags = [
date = "${date}"
remark = "公開"
---
-
-
-
+{#TODO}
+# TODO
+
+TODO
`;
} else {
return `[slide]
diff --git a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
new file mode 100644
index 00000000..07071441
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
@@ -0,0 +1,826 @@
+import {
+ Block as DjotBlock,
+ BlockQuote as DjotBlockQuote,
+ BulletList as DjotBulletList,
+ CodeBlock as DjotCodeBlock,
+ Definition as DjotDefinition,
+ DefinitionList as DjotDefinitionList,
+ DefinitionListItem as DjotDefinitionListItem,
+ Delete as DjotDelete,
+ DisplayMath as DjotDisplayMath,
+ Div as DjotDiv,
+ Doc as DjotDoc,
+ DoubleQuoted as DjotDoubleQuoted,
+ Email as DjotEmail,
+ Emph as DjotEmph,
+ FootnoteReference as DjotFootnoteReference,
+ HardBreak as DjotHardBreak,
+ Heading as DjotHeading,
+ Image as DjotImage,
+ Inline as DjotInline,
+ InlineMath as DjotInlineMath,
+ Insert as DjotInsert,
+ Link as DjotLink,
+ ListItem as DjotListItem,
+ Mark as DjotMark,
+ NonBreakingSpace as DjotNonBreakingSpace,
+ OrderedList as DjotOrderedList,
+ Para as DjotPara,
+ RawBlock as DjotRawBlock,
+ RawInline as DjotRawInline,
+ Section as DjotSection,
+ SingleQuoted as DjotSingleQuoted,
+ SmartPunctuation as DjotSmartPunctuation,
+ SoftBreak as DjotSoftBreak,
+ Span as DjotSpan,
+ Str as DjotStr,
+ Strong as DjotStrong,
+ Subscript as DjotSubscript,
+ Superscript as DjotSuperscript,
+ Symb as DjotSymb,
+ Table as DjotTable,
+ TaskList as DjotTaskList,
+ TaskListItem as DjotTaskListItem,
+ Term as DjotTerm,
+ ThematicBreak as DjotThematicBreak,
+ Url as DjotUrl,
+ Verbatim as DjotVerbatim,
+} from "@djot/djot";
+import { Element, Node } from "../dom.ts";
+
+function processBlock(node: DjotBlock): Element {
+ switch (node.tag) {
+ case "section":
+ return processSection(node);
+ case "para":
+ return processPara(node);
+ case "heading":
+ return processHeading(node);
+ case "thematic_break":
+ return processThematicBreak(node);
+ case "block_quote":
+ return processBlockQuote(node);
+ case "code_block":
+ return processCodeBlock(node);
+ case "bullet_list":
+ return processBulletList(node);
+ case "ordered_list":
+ return processOrderedList(node);
+ case "task_list":
+ return processTaskList(node);
+ case "definition_list":
+ return processDefinitionList(node);
+ case "table":
+ return processTable(node);
+ case "div":
+ return processDiv(node);
+ case "raw_block":
+ return processRawBlock(node);
+ }
+}
+
+function processSection(node: DjotSection): Element {
+ return {
+ kind: "element",
+ name: "section",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processBlock),
+ };
+}
+
+function processPara(node: DjotPara): Element {
+ return {
+ kind: "element",
+ name: "p",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processHeading(node: DjotHeading): Element {
+ const attributes = convertAttributes(node.attributes);
+ return {
+ kind: "element",
+ name: "h",
+ attributes,
+ children: node.children.map(processInline),
+ };
+}
+
+function processThematicBreak(node: DjotThematicBreak): Element {
+ return {
+ kind: "element",
+ name: "hr",
+ attributes: convertAttributes(node.attributes),
+ children: [],
+ };
+}
+
+function processBlockQuote(node: DjotBlockQuote): Element {
+ return {
+ kind: "element",
+ name: "blockquote",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processBlock),
+ };
+}
+
+function processCodeBlock(node: DjotCodeBlock): Element {
+ const attributes = convertAttributes(node.attributes);
+ if (node.lang) {
+ attributes.set("language", node.lang);
+ }
+ return {
+ kind: "element",
+ name: "codeblock",
+ attributes,
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+function processBulletList(node: DjotBulletList): Element {
+ const attributes = convertAttributes(node.attributes);
+ attributes.set("--tight", node.tight ? "true" : "false");
+ return {
+ kind: "element",
+ name: "ul",
+ attributes,
+ children: node.children.map(processListItem),
+ };
+}
+
+function processOrderedList(node: DjotOrderedList): Element {
+ const attributes = convertAttributes(node.attributes);
+ attributes.set("--tight", node.tight ? "true" : "false");
+ if (node.start !== undefined && node.start !== 1) {
+ attributes.set("start", node.start.toString());
+ }
+ return {
+ kind: "element",
+ name: "ol",
+ attributes,
+ children: node.children.map(processListItem),
+ };
+}
+
+function processTaskList(node: DjotTaskList): Element {
+ const attributes = convertAttributes(node.attributes);
+ attributes.set("type", "task");
+ attributes.set("--tight", node.tight ? "true" : "false");
+ return {
+ kind: "element",
+ name: "ul",
+ attributes,
+ children: node.children.map(processTaskListItem),
+ };
+}
+
+function processListItem(node: DjotListItem): Element {
+ return {
+ kind: "element",
+ name: "li",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processBlock),
+ };
+}
+
+function processTaskListItem(node: DjotTaskListItem): Element {
+ const attributes = convertAttributes(node.attributes);
+ attributes.set("checked", node.checkbox === "checked" ? "true" : "false");
+ return {
+ kind: "element",
+ name: "li",
+ attributes,
+ children: node.children.map(processBlock),
+ };
+}
+
+function processDefinitionList(node: DjotDefinitionList): Element {
+ return {
+ kind: "element",
+ name: "dl",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.flatMap(processDefinitionListItem),
+ };
+}
+
+function processDefinitionListItem(node: DjotDefinitionListItem): Element[] {
+ return [
+ processTerm(node.children[0]),
+ processDefinition(node.children[1]),
+ ];
+}
+
+function processTerm(node: DjotTerm): Element {
+ return {
+ kind: "element",
+ name: "dt",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processDefinition(node: DjotDefinition): Element {
+ return {
+ kind: "element",
+ name: "dd",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processBlock),
+ };
+}
+
+function processTable(node: DjotTable): Element {
+ // Tables in Djot have a caption as first child and then rows
+ // For now, we'll create a basic table structure and ignore caption
+ const tableElement: Element = {
+ kind: "element",
+ name: "table",
+ attributes: convertAttributes(node.attributes),
+ children: [],
+ };
+
+ // Process caption if it exists (first child)
+ if (node.children.length > 0 && node.children[0].tag === "caption") {
+ const caption: Element = {
+ kind: "element",
+ name: "caption",
+ attributes: new Map(),
+ children: node.children[0].children.map(processInline),
+ };
+ tableElement.children.push(caption);
+ }
+
+ // Group rows into thead, tbody based on head property
+ const headerRows: Element[] = [];
+ const bodyRows: Element[] = [];
+
+ // Start from index 1 to skip caption
+ for (let i = 1; i < node.children.length; i++) {
+ const row = node.children[i];
+ if (row.tag === "row") {
+ const rowElement: Element = {
+ kind: "element",
+ name: "tr",
+ attributes: convertAttributes(row.attributes),
+ children: row.children.map((cell) => {
+ const cellElement: Element = {
+ kind: "element",
+ name: cell.head ? "th" : "td",
+ attributes: convertAttributes(cell.attributes),
+ children: cell.children.map(processInline),
+ };
+
+ // Set alignment attribute if needed
+ if (cell.align !== "default") {
+ cellElement.attributes.set("align", cell.align);
+ }
+
+ return cellElement;
+ }),
+ };
+
+ if (row.head) {
+ headerRows.push(rowElement);
+ } else {
+ bodyRows.push(rowElement);
+ }
+ }
+ }
+
+ // Add thead and tbody if needed
+ if (headerRows.length > 0) {
+ tableElement.children.push({
+ kind: "element",
+ name: "thead",
+ attributes: new Map(),
+ children: headerRows,
+ });
+ }
+
+ if (bodyRows.length > 0) {
+ tableElement.children.push({
+ kind: "element",
+ name: "tbody",
+ attributes: new Map(),
+ children: bodyRows,
+ });
+ }
+
+ return tableElement;
+}
+
+function processInline(node: DjotInline): Node {
+ switch (node.tag) {
+ case "str":
+ return processStr(node);
+ case "soft_break":
+ return processSoftBreak(node);
+ case "hard_break":
+ return processHardBreak(node);
+ case "verbatim":
+ return processVerbatim(node);
+ case "emph":
+ return processEmph(node);
+ case "strong":
+ return processStrong(node);
+ case "link":
+ return processLink(node);
+ case "image":
+ return processImage(node);
+ case "mark":
+ return processMark(node);
+ case "superscript":
+ return processSuperscript(node);
+ case "subscript":
+ return processSubscript(node);
+ case "insert":
+ return processInsert(node);
+ case "delete":
+ return processDelete(node);
+ case "email":
+ return processEmail(node);
+ case "footnote_reference":
+ return processFootnoteReference(node);
+ case "url":
+ return processUrl(node);
+ case "span":
+ return processSpan(node);
+ case "inline_math":
+ return processInlineMath(node);
+ case "display_math":
+ return processDisplayMath(node);
+ case "non_breaking_space":
+ return processNonBreakingSpace(node);
+ case "symb":
+ return processSymb(node);
+ case "raw_inline":
+ return processRawInline(node);
+ case "double_quoted":
+ return processDoubleQuoted(node);
+ case "single_quoted":
+ return processSingleQuoted(node);
+ case "smart_punctuation":
+ return processSmartPunctuation(node);
+ }
+}
+
+function processStr(node: DjotStr): Node {
+ return {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ };
+}
+
+function processSoftBreak(_node: DjotSoftBreak): Node {
+ return {
+ kind: "text",
+ content: "\n",
+ raw: false,
+ };
+}
+
+function processHardBreak(_node: DjotHardBreak): Node {
+ return {
+ kind: "element",
+ name: "br",
+ attributes: new Map(),
+ children: [],
+ };
+}
+
+function processVerbatim(node: DjotVerbatim): Element {
+ return {
+ kind: "element",
+ name: "code",
+ attributes: convertAttributes(node.attributes),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+function processEmph(node: DjotEmph): Element {
+ return {
+ kind: "element",
+ name: "em",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processStrong(node: DjotStrong): Element {
+ return {
+ kind: "element",
+ name: "strong",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processLink(node: DjotLink): Element {
+ const attributes = convertAttributes(node.attributes);
+ if (node.destination !== undefined) {
+ attributes.set("href", node.destination);
+ }
+ return {
+ kind: "element",
+ name: "a",
+ attributes,
+ children: node.children.map(processInline),
+ };
+}
+
+function processImage(node: DjotImage): Element {
+ const attributes = convertAttributes(node.attributes);
+ if (node.destination !== undefined) {
+ attributes.set("src", node.destination);
+ }
+
+ // Alt text is derived from children in Djot
+ const alt = node.children
+ .map((child) => {
+ if (child.tag === "str") {
+ return child.text;
+ }
+ return "";
+ })
+ .join("");
+
+ if (alt) {
+ attributes.set("alt", alt);
+ }
+
+ return {
+ kind: "element",
+ name: "img",
+ attributes,
+ children: [],
+ };
+}
+
+function processMark(node: DjotMark): Element {
+ return {
+ kind: "element",
+ name: "mark",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processSuperscript(node: DjotSuperscript): Element {
+ return {
+ kind: "element",
+ name: "sup",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processSubscript(node: DjotSubscript): Element {
+ return {
+ kind: "element",
+ name: "sub",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processInsert(node: DjotInsert): Element {
+ return {
+ kind: "element",
+ name: "ins",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processDelete(node: DjotDelete): Element {
+ return {
+ kind: "element",
+ name: "del",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processEmail(node: DjotEmail): Element {
+ return {
+ kind: "element",
+ name: "email",
+ attributes: convertAttributes(node.attributes),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+function processFootnoteReference(node: DjotFootnoteReference): Element {
+ return {
+ kind: "element",
+ name: "footnoteref",
+ attributes: new Map([["reference", node.text]]),
+ children: [],
+ };
+}
+
+function processUrl(node: DjotUrl): Element {
+ return {
+ kind: "element",
+ name: "a",
+ attributes: new Map([
+ ["href", node.text],
+ ...Object.entries(node.attributes || {}),
+ ]),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+function processSpan(node: DjotSpan): Element {
+ return {
+ kind: "element",
+ name: "span",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processInline),
+ };
+}
+
+function processInlineMath(node: DjotInlineMath): Element {
+ // For inline math, we'll wrap it in a span with a class
+ return {
+ kind: "element",
+ name: "span",
+ attributes: new Map([
+ ["class", "math inline"],
+ ...Object.entries(node.attributes || {}),
+ ]),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+function processDisplayMath(node: DjotDisplayMath): Element {
+ // For display math, we'll wrap it in a div with a class
+ return {
+ kind: "element",
+ name: "div",
+ attributes: new Map([
+ ["class", "math display"],
+ ...Object.entries(node.attributes || {}),
+ ]),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+function processNonBreakingSpace(_node: DjotNonBreakingSpace): Node {
+ return {
+ kind: "text",
+ content: "\u00A0", // Unicode non-breaking space
+ raw: false,
+ };
+}
+
+function processSymb(node: DjotSymb): Node {
+ // Map symbol aliases to their Unicode characters
+ const symbolMap: Record = {
+ "->": "→",
+ "<-": "←",
+ "<->": "↔",
+ "=>": "⇒",
+ "<=": "⇐",
+ "<=>": "⇔",
+ "--": "–", // en dash
+ "---": "—", // em dash
+ "...": "…", // ellipsis
+ // Add more symbol mappings as needed
+ };
+
+ const symbolText = symbolMap[node.alias] || node.alias;
+
+ return {
+ kind: "text",
+ content: symbolText,
+ raw: false,
+ };
+}
+
+function processRawInline(node: DjotRawInline): Node {
+ // If the format is HTML, return as raw HTML
+ if (node.format === "html" || node.format === "HTML") {
+ return {
+ kind: "text",
+ content: node.text,
+ raw: true,
+ };
+ }
+
+ // For other formats, just return as text
+ return {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ };
+}
+
+function processDoubleQuoted(node: DjotDoubleQuoted): Node {
+ const children = node.children.map(processInline);
+ const attributes = convertAttributes(node.attributes);
+
+ if (
+ children.length === 1 && children[0].kind === "text" &&
+ attributes.size === 0
+ ) {
+ const content = children[0].content;
+ return {
+ kind: "text",
+ content: `\u201C${content}\u201D`,
+ raw: false,
+ };
+ } else {
+ return {
+ kind: "element",
+ name: "span",
+ attributes: convertAttributes(node.attributes),
+ children,
+ };
+ }
+}
+
+function processSingleQuoted(node: DjotSingleQuoted): Node {
+ const children = node.children.map(processInline);
+ const attributes = convertAttributes(node.attributes);
+
+ if (
+ children.length === 1 && children[0].kind === "text" &&
+ attributes.size === 0
+ ) {
+ const content = children[0].content;
+ return {
+ kind: "text",
+ content: `\u2018${content}\u2019`,
+ raw: false,
+ };
+ } else {
+ return {
+ kind: "element",
+ name: "span",
+ attributes: convertAttributes(node.attributes),
+ children,
+ };
+ }
+}
+
+function processSmartPunctuation(node: DjotSmartPunctuation): Node {
+ // Map smart punctuation types to Unicode characters
+ const punctuationMap: Record = {
+ "left_single_quote": "\u2018", // '
+ "right_single_quote": "\u2019", // '
+ "left_double_quote": "\u201C", // "
+ "right_double_quote": "\u201D", // "
+ "ellipses": "\u2026", // …
+ "em_dash": "\u2014", // —
+ "en_dash": "\u2013", // –
+ };
+
+ return {
+ kind: "text",
+ content: punctuationMap[node.type] || node.text,
+ raw: false,
+ };
+}
+
+function processDiv(node: DjotDiv): Element {
+ if (node.attributes?.class === "note") {
+ delete node.attributes.class;
+ return {
+ kind: "element",
+ name: "note",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processBlock),
+ };
+ }
+
+ return {
+ kind: "element",
+ name: "div",
+ attributes: convertAttributes(node.attributes),
+ children: node.children.map(processBlock),
+ };
+}
+
+function processRawBlock(node: DjotRawBlock): Element {
+ // If the format is HTML, wrap the HTML content in a div
+ if (node.format === "html" || node.format === "HTML") {
+ return {
+ kind: "element",
+ name: "div",
+ attributes: new Map([["class", "raw-html"]]),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: true,
+ },
+ ],
+ };
+ }
+
+ // For other formats, wrap in a pre tag
+ return {
+ kind: "element",
+ name: "pre",
+ attributes: new Map([["data-format", node.format]]),
+ children: [
+ {
+ kind: "text",
+ content: node.text,
+ raw: false,
+ },
+ ],
+ };
+}
+
+// Helper function to convert Djot attributes to Nuldoc attributes
+function convertAttributes(
+ attrs?: Record,
+): Map {
+ const result = new Map();
+ if (attrs) {
+ for (const [key, value] of Object.entries(attrs)) {
+ result.set(key, value);
+ }
+ }
+ return result;
+}
+
+export function djot2ndoc(doc: DjotDoc): Element {
+ const children: Node[] = [];
+ for (const child of doc.children) {
+ children.push(processBlock(child));
+ }
+
+ // Process footnotes if any exist
+ if (doc.footnotes && Object.keys(doc.footnotes).length > 0) {
+ const footnoteSection: Element = {
+ kind: "element",
+ name: "section",
+ attributes: new Map([["class", "footnotes"]]),
+ children: [],
+ };
+
+ for (const [id, footnote] of Object.entries(doc.footnotes)) {
+ const footnoteElement: Element = {
+ kind: "element",
+ name: "footnote",
+ attributes: new Map([["id", id]]),
+ children: footnote.children.map(processBlock),
+ };
+ footnoteSection.children.push(footnoteElement);
+ }
+
+ children.push(footnoteSection);
+ }
+
+ return {
+ kind: "element",
+ name: "__root__",
+ attributes: new Map(),
+ children: [{
+ kind: "element",
+ name: "article",
+ attributes: new Map(),
+ children,
+ }],
+ };
+}
diff --git a/vhosts/blog/nuldoc-src/djot/document.ts b/vhosts/blog/nuldoc-src/djot/document.ts
new file mode 100644
index 00000000..be9c08d5
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/djot/document.ts
@@ -0,0 +1,60 @@
+import { Doc as DjotDoc } from "@djot/djot";
+import { join } from "@std/path";
+import { z } from "zod/mod.ts";
+import { Config } from "../config.ts";
+import { Element } from "../dom.ts";
+import { Revision, stringToDate } from "../revision.ts";
+import { djot2ndoc } from "./djot2ndoc.ts";
+
+export const PostMetadataSchema = z.object({
+ article: z.object({
+ uuid: z.string(),
+ title: z.string(),
+ description: z.string(),
+ tags: z.array(z.string()),
+ revisions: z.array(z.object({
+ date: z.string(),
+ remark: z.string(),
+ isInternal: z.boolean().optional(),
+ })),
+ }),
+});
+
+export type PostMetadata = z.infer;
+
+export type Document = {
+ root: Element;
+ sourceFilePath: string;
+ uuid: string;
+ link: string;
+ title: string;
+ description: string; // TODO: should it be markup text?
+ tags: string[];
+ revisions: Revision[];
+};
+
+export function createNewDocumentFromDjotDocument(
+ root: DjotDoc,
+ meta: PostMetadata,
+ sourceFilePath: string,
+ config: Config,
+): Document {
+ const cwd = Deno.cwd();
+ const contentDir = join(cwd, config.locations.contentDir);
+ const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/");
+ return {
+ root: djot2ndoc(root),
+ sourceFilePath,
+ uuid: meta.article.uuid,
+ link: link,
+ title: meta.article.title,
+ description: meta.article.description,
+ tags: meta.article.tags,
+ revisions: meta.article.revisions.map((r, i) => ({
+ number: i,
+ date: stringToDate(r.date),
+ remark: r.remark,
+ isInternal: !!r.isInternal,
+ })),
+ };
+}
diff --git a/vhosts/blog/nuldoc-src/djot/parse.ts b/vhosts/blog/nuldoc-src/djot/parse.ts
new file mode 100644
index 00000000..884ab154
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/djot/parse.ts
@@ -0,0 +1,33 @@
+import { parse as parseDjot } from "@djot/djot";
+import { parse as parseToml } from "@std/toml";
+import { Config } from "../config.ts";
+import {
+ createNewDocumentFromDjotDocument,
+ Document,
+ PostMetadata,
+ PostMetadataSchema,
+} from "./document.ts";
+import toHtml from "./to_html.ts";
+
+export async function parseDjotFile(
+ filePath: string,
+ config: Config,
+): Promise {
+ try {
+ const fileContent = await Deno.readTextFile(filePath);
+ const parts = fileContent.split(/^---$/m);
+ const meta = parseMetadata(parts[1]);
+ const root = parseDjot(parts[2]);
+ const doc = createNewDocumentFromDjotDocument(root, meta, filePath, config);
+ return await toHtml(doc);
+ } catch (e) {
+ if (e instanceof Error) {
+ e.message = `${e.message} in ${filePath}`;
+ }
+ throw e;
+ }
+}
+
+function parseMetadata(s: string): PostMetadata {
+ return PostMetadataSchema.parse(parseToml(s));
+}
diff --git a/vhosts/blog/nuldoc-src/djot/to_html.ts b/vhosts/blog/nuldoc-src/djot/to_html.ts
new file mode 100644
index 00000000..5ee76023
--- /dev/null
+++ b/vhosts/blog/nuldoc-src/djot/to_html.ts
@@ -0,0 +1,320 @@
+import { BundledLanguage, bundledLanguages, codeToHtml } from "shiki";
+import { Document } from "./document.ts";
+import { NuldocError } from "../errors.ts";
+import {
+ addClass,
+ Element,
+ forEachChild,
+ forEachChildRecursively,
+ forEachChildRecursivelyAsync,
+ Node,
+ RawHTML,
+ Text,
+} from "../dom.ts";
+
+export default async function toHtml(doc: Document): Promise {
+ removeUnnecessaryTextNode(doc);
+ transformLinkLikeToAnchorElement(doc);
+ transformSectionIdAttribute(doc);
+ setSectionTitleAnchor(doc);
+ transformSectionTitleElement(doc);
+ transformNoteElement(doc);
+ addAttributesToExternalLinkElement(doc);
+ setDefaultLangAttribute(doc);
+ traverseFootnotes(doc);
+ removeUnnecessaryParagraphNode(doc);
+ await transformAndHighlightCodeBlockElement(doc);
+ return doc;
+}
+
+function removeUnnecessaryTextNode(doc: Document) {
+ forEachChildRecursively(doc.root, (n) => {
+ if (n.kind !== "element") {
+ return;
+ }
+
+ let changed = true;
+ while (changed) {
+ changed = false;
+ if (n.children.length === 0) {
+ break;
+ }
+ const firstChild = n.children[0];
+ if (firstChild.kind === "text" && firstChild.content.trim() === "") {
+ n.children.shift();
+ changed = true;
+ }
+ if (n.children.length === 0) {
+ break;
+ }
+ const lastChild = n.children[n.children.length - 1];
+ if (lastChild.kind === "text" && lastChild.content.trim() === "") {
+ n.children.pop();
+ changed = true;
+ }
+ }
+ });
+}
+
+function transformLinkLikeToAnchorElement(doc: Document) {
+ forEachChildRecursively(doc.root, (n) => {
+ if (
+ n.kind !== "element" || n.name === "a" || n.name === "code" ||
+ n.name === "codeblock"
+ ) {
+ return;
+ }
+
+ const newChildren: Node[] = [];
+ for (const child of n.children) {
+ if (child.kind !== "text") {
+ newChildren.push(child);
+ continue;
+ }
+ let restContent = child.content;
+ while (restContent !== "") {
+ const match = /^(.*?)(https?:\/\/[^ \n]+)(.*)$/s.exec(restContent);
+ if (!match) {
+ newChildren.push({ kind: "text", content: restContent, raw: false });
+ restContent = "";
+ break;
+ }
+ const [_, prefix, url, suffix] = match;
+ newChildren.push({ kind: "text", content: prefix, raw: false });
+ newChildren.push({
+ kind: "element",
+ name: "a",
+ attributes: new Map([["href", url]]),
+ children: [{ kind: "text", content: url, raw: false }],
+ });
+ restContent = suffix;
+ }
+ }
+ n.children = newChildren;
+ });
+}
+
+function transformSectionIdAttribute(doc: Document) {
+ const sectionStack: string[] = [];
+ const usedIds = new Set();
+
+ const processNode = (n: Node) => {
+ if (n.kind !== "element") {
+ return;
+ }
+
+ if (n.name === "section") {
+ const idAttr = n.attributes.get("id");
+ if (!idAttr) {
+ return;
+ }
+
+ let newId: string;
+ if (sectionStack.length === 0) {
+ newId = `section--${idAttr}`;
+ } else {
+ newId = `section--${sectionStack.join("--")}--${idAttr}`;
+ }
+
+ if (usedIds.has(newId)) {
+ throw new NuldocError(
+ `[nuldoc.tohtml] Duplicate section ID: ${newId}`,
+ );
+ }
+
+ usedIds.add(newId);
+ n.attributes.set("id", newId);
+ sectionStack.push(idAttr);
+
+ forEachChild(n, processNode);
+
+ sectionStack.pop();
+ } else {
+ forEachChild(n, processNode);
+ }
+ };
+
+ forEachChild(doc.root, processNode);
+}
+
+function setSectionTitleAnchor(doc: Document) {
+ const sectionStack: Element[] = [];
+ const g = (c: Node) => {
+ if (c.kind !== "element") {
+ return;
+ }
+
+ if (c.name === "section") {
+ sectionStack.push(c);
+ }
+ forEachChild(c, g);
+ if (c.name === "section") {
+ sectionStack.pop();
+ }
+ if (c.name === "h") {
+ const currentSection = sectionStack[sectionStack.length - 1];
+ if (!currentSection) {
+ throw new NuldocError(
+ "[nuldoc.tohtml] element must be inside ",
+ );
+ }
+ const sectionId = currentSection.attributes.get("id");
+ const aElement: Element = {
+ kind: "element",
+ name: "a",
+ attributes: new Map(),
+ children: c.children,
+ };
+ aElement.attributes.set("href", `#${sectionId}`);
+ c.children = [aElement];
+ }
+ };
+ forEachChild(doc.root, g);
+}
+
+function transformSectionTitleElement(doc: Document) {
+ let sectionLevel = 1;
+ const g = (c: Node) => {
+ if (c.kind !== "element") {
+ return;
+ }
+
+ if (c.name === "section") {
+ sectionLevel += 1;
+ c.attributes.set("--section-level", sectionLevel.toString());
+ }
+ forEachChild(c, g);
+ if (c.name === "section") {
+ sectionLevel -= 1;
+ }
+ if (c.name === "h") {
+ c.name = `h${sectionLevel}`;
+ }
+ };
+ forEachChild(doc.root, g);
+}
+
+function transformNoteElement(doc: Document) {
+ forEachChildRecursively(doc.root, (n) => {
+ if (n.kind !== "element" || n.name !== "note") {
+ return;
+ }
+
+ const labelElement: Element = {
+ kind: "element",
+ name: "div",
+ attributes: new Map([["class", "admonition-label"]]),
+ children: [{
+ kind: "text",
+ content: "NOTE",
+ raw: false,
+ }],
+ };
+ const contentElement: Element = {
+ kind: "element",
+ name: "div",
+ attributes: new Map([["class", "admonition-content"]]),
+ children: n.children,
+ };
+ n.name = "div";
+ addClass(n, "admonition");
+ n.children = [
+ labelElement,
+ contentElement,
+ ];
+ });
+}
+
+function addAttributesToExternalLinkElement(doc: Document) {
+ forEachChildRecursively(doc.root, (n) => {
+ if (n.kind !== "element" || n.name !== "a") {
+ return;
+ }
+
+ const href = n.attributes.get("href") ?? "";
+ if (!href.startsWith("http")) {
+ return;
+ }
+ n.attributes
+ .set("target", "_blank")
+ .set("rel", "noreferrer");
+ });
+}
+
+function setDefaultLangAttribute(_doc: Document) {
+ // TODO
+ // if (!e.attributes.has("lang")) {
+ // e.attributes.set("lang", "ja-JP");
+ // }
+}
+
+function traverseFootnotes(doc: Document) {
+ forEachChildRecursively(doc.root, (n) => {
+ if (n.kind !== "element" || n.name !== "footnote") {
+ return;
+ }
+
+ // TODO
+ // x
+ //
+ //
+ //
+ //
+ n.name = "span";
+ n.children = [];
+ });
+}
+
+function removeUnnecessaryParagraphNode(doc: Document) {
+ forEachChildRecursively(doc.root, (n) => {
+ if (n.kind !== "element" || (n.name !== "ul" && n.name !== "ol")) {
+ return;
+ }
+
+ const isTight = n.attributes.get("--tight") === "true";
+ if (!isTight) {
+ return;
+ }
+
+ for (const child of n.children) {
+ if (child.kind !== "element" || child.name !== "li") {
+ continue;
+ }
+ if (child.children.length !== 1) {
+ continue;
+ }
+ const grandChild = child.children[0];
+ if (grandChild.kind !== "element" || grandChild.name !== "p") {
+ continue;
+ }
+ child.children = grandChild.children;
+ }
+ });
+}
+
+async function transformAndHighlightCodeBlockElement(doc: Document) {
+ await forEachChildRecursivelyAsync(doc.root, async (n) => {
+ if (n.kind !== "element" || n.name !== "codeblock") {
+ return;
+ }
+
+ const language = n.attributes.get("language") || "text";
+ const sourceCodeNode = n.children[0] as Text | RawHTML;
+ const sourceCode = sourceCodeNode.content.trimEnd();
+
+ const highlighted = await codeToHtml(sourceCode, {
+ lang: language in bundledLanguages ? language as BundledLanguage : "text",
+ theme: "github-light",
+ colorReplacements: {
+ "#fff": "#f5f5f5",
+ },
+ });
+
+ sourceCodeNode.content = highlighted;
+ sourceCodeNode.raw = true;
+ n.name = "div";
+ n.attributes.set("class", "codeblock");
+ });
+}
diff --git a/vhosts/blog/nuldoc-src/generators/post.ts b/vhosts/blog/nuldoc-src/generators/post.ts
index ae96c573..0e2a9553 100644
--- a/vhosts/blog/nuldoc-src/generators/post.ts
+++ b/vhosts/blog/nuldoc-src/generators/post.ts
@@ -2,7 +2,7 @@ import { join } from "@std/path";
import { renderToDOM } from "../jsx/render.ts";
import PostPage from "../pages/PostPage.tsx";
import { Config } from "../config.ts";
-import { Document } from "../ndoc/document.ts";
+import { Document } from "../djot/document.ts";
import { Page } from "../page.ts";
import { Date, Revision } from "../revision.ts";
@@ -44,7 +44,7 @@ export async function generatePostPage(
const cwd = Deno.cwd();
const contentDir = join(cwd, config.locations.contentDir);
const destFilePath = join(
- doc.sourceFilePath.replace(contentDir, "").replace(".ndoc", ""),
+ doc.sourceFilePath.replace(contentDir, "").replace(".dj", ""),
"index.html",
);
return {
diff --git a/vhosts/blog/nuldoc-src/ndoc/document.ts b/vhosts/blog/nuldoc-src/ndoc/document.ts
deleted file mode 100644
index dfb6d03b..00000000
--- a/vhosts/blog/nuldoc-src/ndoc/document.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { join } from "@std/path";
-import { Config } from "../config.ts";
-import { NuldocError } from "../errors.ts";
-import { Revision, stringToDate } from "../revision.ts";
-import { Element, findFirstChildElement } from "../dom.ts";
-import { z } from "zod/mod.ts";
-
-export const PostMetadataSchema = z.object({
- article: z.object({
- uuid: z.string(),
- title: z.string(),
- description: z.string(),
- tags: z.array(z.string()),
- revisions: z.array(z.object({
- date: z.string(),
- remark: z.string(),
- isInternal: z.boolean().optional(),
- })),
- }),
-});
-
-export type PostMetadata = z.infer;
-
-export type Document = {
- root: Element;
- sourceFilePath: string;
- uuid: string;
- link: string;
- title: string;
- description: string; // TODO: should it be markup text?
- tags: string[];
- revisions: Revision[];
-};
-
-export function createNewDocumentFromRootElement(
- root: Element,
- meta: PostMetadata,
- sourceFilePath: string,
- config: Config,
-): Document {
- const article = findFirstChildElement(root, "article");
- if (!article) {
- throw new NuldocError(
- `[nuldoc.new] element not found`,
- );
- }
-
- const cwd = Deno.cwd();
- const contentDir = join(cwd, config.locations.contentDir);
- const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/");
- return {
- root: root,
- sourceFilePath: sourceFilePath,
- uuid: meta.article.uuid,
- link: link,
- title: meta.article.title,
- description: meta.article.description,
- tags: meta.article.tags,
- revisions: meta.article.revisions.map((r, i) => ({
- number: i,
- date: stringToDate(r.date),
- remark: r.remark,
- isInternal: !!r.isInternal,
- })),
- };
-}
diff --git a/vhosts/blog/nuldoc-src/ndoc/parse.ts b/vhosts/blog/nuldoc-src/ndoc/parse.ts
deleted file mode 100644
index 4bb96f4d..00000000
--- a/vhosts/blog/nuldoc-src/ndoc/parse.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { parse as parseToml } from "@std/toml";
-import { Config } from "../config.ts";
-import { parseXmlString } from "../xml.ts";
-import {
- createNewDocumentFromRootElement,
- Document,
- PostMetadata,
- PostMetadataSchema,
-} from "./document.ts";
-import toHtml from "./to_html.ts";
-
-export async function parseNulDocFile(
- filePath: string,
- config: Config,
-): Promise {
- try {
- const fileContent = await Deno.readTextFile(filePath);
- const parts = fileContent.split(/^---$/m);
- const meta = parseMetadata(parts[1]);
- const root = parseXmlString("" + parts[2]);
- const doc = createNewDocumentFromRootElement(root, meta, filePath, config);
- return await toHtml(doc);
- } catch (e) {
- if (e instanceof Error) {
- e.message = `${e.message} in ${filePath}`;
- }
- throw e;
- }
-}
-
-function parseMetadata(s: string): PostMetadata {
- return PostMetadataSchema.parse(parseToml(s));
-}
diff --git a/vhosts/blog/nuldoc-src/ndoc/to_html.ts b/vhosts/blog/nuldoc-src/ndoc/to_html.ts
deleted file mode 100644
index a82f0333..00000000
--- a/vhosts/blog/nuldoc-src/ndoc/to_html.ts
+++ /dev/null
@@ -1,292 +0,0 @@
-import { BundledLanguage, bundledLanguages, codeToHtml } from "shiki";
-import { Document } from "./document.ts";
-import { NuldocError } from "../errors.ts";
-import {
- addClass,
- Element,
- forEachChild,
- forEachChildRecursively,
- forEachChildRecursivelyAsync,
- Node,
- RawHTML,
- Text,
-} from "../dom.ts";
-
-export default async function toHtml(doc: Document): Promise {
- removeUnnecessaryTextNode(doc);
- transformLinkLikeToAnchorElement(doc);
- transformSectionIdAttribute(doc);
- setSectionTitleAnchor(doc);
- transformSectionTitleElement(doc);
- transformNoteElement(doc);
- addAttributesToExternalLinkElement(doc);
- setDefaultLangAttribute(doc);
- traverseFootnotes(doc);
- await transformAndHighlightCodeBlockElement(doc);
- return doc;
-}
-
-function removeUnnecessaryTextNode(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element") {
- return;
- }
-
- let changed = true;
- while (changed) {
- changed = false;
- if (n.children.length === 0) {
- break;
- }
- const firstChild = n.children[0];
- if (firstChild.kind === "text" && firstChild.content.trim() === "") {
- n.children.shift();
- changed = true;
- }
- if (n.children.length === 0) {
- break;
- }
- const lastChild = n.children[n.children.length - 1];
- if (lastChild.kind === "text" && lastChild.content.trim() === "") {
- n.children.pop();
- changed = true;
- }
- }
- });
-}
-
-function transformLinkLikeToAnchorElement(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (
- n.kind !== "element" || n.name === "a" || n.name === "code" ||
- n.name === "codeblock"
- ) {
- return;
- }
-
- const newChildren: Node[] = [];
- for (const child of n.children) {
- if (child.kind !== "text") {
- newChildren.push(child);
- continue;
- }
- let restContent = child.content;
- while (restContent !== "") {
- const match = /^(.*?)(https?:\/\/[^ \n]+)(.*)$/s.exec(restContent);
- if (!match) {
- newChildren.push({ kind: "text", content: restContent, raw: false });
- restContent = "";
- break;
- }
- const [_, prefix, url, suffix] = match;
- newChildren.push({ kind: "text", content: prefix, raw: false });
- newChildren.push({
- kind: "element",
- name: "a",
- attributes: new Map([["href", url]]),
- children: [{ kind: "text", content: url, raw: false }],
- });
- restContent = suffix;
- }
- }
- n.children = newChildren;
- });
-}
-
-function transformSectionIdAttribute(doc: Document) {
- const sectionStack: string[] = [];
- const usedIds = new Set();
-
- const processNode = (n: Node) => {
- if (n.kind !== "element") {
- return;
- }
-
- if (n.name === "section") {
- const idAttr = n.attributes.get("id");
- if (!idAttr) {
- return;
- }
-
- let newId: string;
- if (sectionStack.length === 0) {
- newId = `section--${idAttr}`;
- } else {
- newId = `section--${sectionStack.join("--")}--${idAttr}`;
- }
-
- if (usedIds.has(newId)) {
- throw new NuldocError(
- `[nuldoc.tohtml] Duplicate section ID: ${newId}`,
- );
- }
-
- usedIds.add(newId);
- n.attributes.set("id", newId);
- sectionStack.push(idAttr);
-
- forEachChild(n, processNode);
-
- sectionStack.pop();
- } else {
- forEachChild(n, processNode);
- }
- };
-
- forEachChild(doc.root, processNode);
-}
-
-function setSectionTitleAnchor(doc: Document) {
- const sectionStack: Element[] = [];
- const g = (c: Node) => {
- if (c.kind !== "element") {
- return;
- }
-
- if (c.name === "section") {
- sectionStack.push(c);
- }
- forEachChild(c, g);
- if (c.name === "section") {
- sectionStack.pop();
- }
- if (c.name === "h") {
- const currentSection = sectionStack[sectionStack.length - 1];
- if (!currentSection) {
- throw new NuldocError(
- "[nuldoc.tohtml] element must be inside ",
- );
- }
- const sectionId = currentSection.attributes.get("id");
- const aElement: Element = {
- kind: "element",
- name: "a",
- attributes: new Map(),
- children: c.children,
- };
- aElement.attributes.set("href", `#${sectionId}`);
- c.children = [aElement];
- }
- };
- forEachChild(doc.root, g);
-}
-
-function transformSectionTitleElement(doc: Document) {
- let sectionLevel = 1;
- const g = (c: Node) => {
- if (c.kind !== "element") {
- return;
- }
-
- if (c.name === "section") {
- sectionLevel += 1;
- c.attributes.set("--section-level", sectionLevel.toString());
- }
- forEachChild(c, g);
- if (c.name === "section") {
- sectionLevel -= 1;
- }
- if (c.name === "h") {
- c.name = `h${sectionLevel}`;
- }
- };
- forEachChild(doc.root, g);
-}
-
-function transformNoteElement(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element" || n.name !== "note") {
- return;
- }
-
- const labelElement: Element = {
- kind: "element",
- name: "div",
- attributes: new Map([["class", "admonition-label"]]),
- children: [{
- kind: "text",
- content: "NOTE",
- raw: false,
- }],
- };
- const contentElement: Element = {
- kind: "element",
- name: "div",
- attributes: new Map([["class", "admonition-content"]]),
- children: n.children,
- };
- n.name = "div";
- addClass(n, "admonition");
- n.children = [
- labelElement,
- contentElement,
- ];
- });
-}
-
-function addAttributesToExternalLinkElement(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element" || n.name !== "a") {
- return;
- }
-
- const href = n.attributes.get("href") ?? "";
- if (!href.startsWith("http")) {
- return;
- }
- n.attributes
- .set("target", "_blank")
- .set("rel", "noreferrer");
- });
-}
-
-function setDefaultLangAttribute(_doc: Document) {
- // TODO
- // if (!e.attributes.has("lang")) {
- // e.attributes.set("lang", "ja-JP");
- // }
-}
-
-function traverseFootnotes(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element" || n.name !== "footnote") {
- return;
- }
-
- // TODO
- // x
- //
- //
- //
- //
- n.name = "span";
- n.children = [];
- });
-}
-
-async function transformAndHighlightCodeBlockElement(doc: Document) {
- await forEachChildRecursivelyAsync(doc.root, async (n) => {
- if (n.kind !== "element" || n.name !== "codeblock") {
- return;
- }
-
- const language = n.attributes.get("language") || "text";
- const sourceCodeNode = n.children[0] as Text | RawHTML;
- const sourceCode = sourceCodeNode.content;
-
- const highlighted = await codeToHtml(sourceCode, {
- lang: language in bundledLanguages ? language as BundledLanguage : "text",
- theme: "github-light",
- colorReplacements: {
- "#fff": "#f5f5f5",
- },
- });
-
- sourceCodeNode.content = highlighted;
- sourceCodeNode.raw = true;
- n.name = "div";
- n.attributes.set("class", "codeblock");
- });
-}
diff --git a/vhosts/blog/nuldoc-src/pages/PostPage.tsx b/vhosts/blog/nuldoc-src/pages/PostPage.tsx
index e6aa83aa..37755ae3 100644
--- a/vhosts/blog/nuldoc-src/pages/PostPage.tsx
+++ b/vhosts/blog/nuldoc-src/pages/PostPage.tsx
@@ -3,7 +3,7 @@ import GlobalHeader from "../components/GlobalHeader.tsx";
import PageLayout from "../components/PageLayout.tsx";
import { Config, getTagLabel } from "../config.ts";
import { Element } from "../dom.ts";
-import { Document } from "../ndoc/document.ts";
+import { Document } from "../djot/document.ts";
import { dateToString } from "../revision.ts";
import { getPostPublishedDate } from "../generators/post.ts";
diff --git a/vhosts/blog/nuldoc-src/renderers/html.ts b/vhosts/blog/nuldoc-src/renderers/html.ts
index 19e6af08..ec77eb4d 100644
--- a/vhosts/blog/nuldoc-src/renderers/html.ts
+++ b/vhosts/blog/nuldoc-src/renderers/html.ts
@@ -89,6 +89,8 @@ function getDtd(name: string): Dtd {
return { type: "inline" };
case "strong":
return { type: "inline" };
+ case "sub":
+ return { type: "inline" };
case "sup":
return { type: "inline" };
case "table":
diff --git a/vhosts/blog/nuldoc-src/xml.ts b/vhosts/blog/nuldoc-src/xml.ts
deleted file mode 100644
index 9f53ef8c..00000000
--- a/vhosts/blog/nuldoc-src/xml.ts
+++ /dev/null
@@ -1,265 +0,0 @@
-import { Element, Node, Text } from "./dom.ts";
-import { XmlParseError } from "./errors.ts";
-
-export function parseXmlString(source: string): Element {
- return parse({ source: source, index: 0 });
-}
-
-type Parser = {
- source: string;
- index: number;
-};
-
-function parse(p: Parser): Element {
- parseXmlDeclaration(p);
- skipWhitespaces(p);
- const e = parseXmlElement(p);
- const root: Element = {
- kind: "element",
- name: "__root__",
- attributes: new Map(),
- children: [e],
- };
- return root;
-}
-
-function parseXmlDeclaration(p: Parser) {
- expect(p, "");
- next(p, 2);
-}
-
-function parseXmlElement(p: Parser): Element {
- const { name, attributes, closed } = parseStartTag(p);
- if (closed) {
- return {
- kind: "element",
- name: name,
- attributes: attributes,
- children: [],
- };
- }
- const children = parseChildNodes(p);
- parseEndTag(p, name);
-
- const thisElement: Element = {
- kind: "element",
- name: name,
- attributes: attributes,
- children: children,
- };
- return thisElement;
-}
-
-function parseChildNodes(p: Parser): Node[] {
- const nodes = [];
- while (true) {
- const c = peek(p);
- const c2 = peekN(p, 2);
- const c3 = peekN(p, 3);
- if (c === "<") {
- if (c2 === "/") {
- break;
- } else if (c2 === "!") {
- if (c3 === "[") {
- // ");
- next(p, "]]>".length);
- return {
- kind: "text",
- content: formatCdata(content),
- raw: false,
- };
-}
-
-function skipComment(p: Parser) {
- expect(p, "");
- next(p, "-->".length);
-}
-
-function formatCdata(s: string): string {
- //
- // => "foo\n bar\nbaz"
- s = s.replace(/^\n(.*)\n *$/s, "$1");
- const ls = s.split("\n");
- const n = Math.min(
- ...ls.filter((l) => l !== "").map((l) =>
- l.match(/^( *)/)?.[0]?.length ?? 0
- ),
- );
- let z = "";
- for (const p of s.split("\n")) {
- z += p.slice(n) + "\n";
- }
- return z.slice(0, -1);
-}
-
-function parseStartTag(
- p: Parser,
-): { name: string; attributes: Map; closed: boolean } {
- expect(p, "<");
- const name = parseIdentifier(p);
- skipWhitespaces(p);
- if (peek(p) === "/") {
- expect(p, "/>");
- return { name: name, attributes: new Map(), closed: true };
- }
- if (peek(p) === ">") {
- next(p);
- return { name: name, attributes: new Map(), closed: false };
- }
- const attributes = new Map();
- while (peek(p) !== ">" && peek(p) !== "/") {
- const { name, value } = parseAttribute(p);
- attributes.set(name, value);
- }
- let closed = false;
- if (peek(p) === "/") {
- next(p);
- closed = true;
- }
- expect(p, ">");
- return { name: name, attributes: attributes, closed: closed };
-}
-
-function parseEndTag(p: Parser, name: string) {
- expect(p, `${name}>`);
-}
-
-function parseAttribute(p: Parser): { name: string; value: string } {
- skipWhitespaces(p);
- let name = parseIdentifier(p);
- if (peek(p) === ":") {
- next(p);
- const name2 = parseIdentifier(p);
- name += ":" + name2;
- }
- expect(p, "=");
- const value = parseQuotedString(p);
- skipWhitespaces(p);
- return { name: name, value: replaceEntityReferences(value) };
-}
-
-function parseQuotedString(p: Parser): string {
- expect(p, '"');
- const content = skipTo(p, '"');
- next(p);
- return content;
-}
-
-function parseIdentifier(p: Parser): string {
- let id = "";
- while (p.index < p.source.length) {
- const c = peek(p);
- if (!c || !/[A-Za-z]/.test(c)) {
- break;
- }
- id += c;
- next(p);
- }
- return id;
-}
-
-function expect(p: Parser, expected: string) {
- let actual = "";
- for (let i = 0; i < expected.length; i++) {
- actual += peek(p);
- next(p);
- }
- if (actual !== expected) {
- throw new XmlParseError(
- `[parse.expect] expected ${expected}, but actually got ${
- escapeForHuman(actual)
- } (pos: ${p.index})`,
- );
- }
-}
-
-function skipTo(p: Parser, delimiter: string): string {
- const indexStart = p.index;
- let i = 0;
- while (i < delimiter.length) {
- if (peek(p) === delimiter[i]) {
- i++;
- } else {
- i = 0;
- }
- next(p);
- }
- back(p, delimiter.length);
- return p.source.substring(indexStart, p.index);
-}
-
-function skipWhitespaces(p: Parser) {
- while (p.index < p.source.length) {
- const c = peek(p);
- if (!c || !/[ \n\t]/.test(c)) {
- break;
- }
- next(p);
- }
-}
-
-function peek(p: Parser): string | null {
- return peekN(p, 1);
-}
-
-function peekN(p: Parser, n: number): string | null {
- return (p.index + n - 1 < p.source.length) ? p.source[p.index + n - 1] : null;
-}
-
-function next(p: Parser, n = 1) {
- p.index += n;
-}
-
-function back(p: Parser, n = 1) {
- p.index -= n;
-}
-
-function replaceEntityReferences(s: string): string {
- return s
- .replaceAll(/&/g, "&")
- .replaceAll(/</g, "<")
- .replaceAll(/>/g, ">")
- .replaceAll(/'/g, "'")
- .replaceAll(/"/g, '"');
-}
-
-function escapeForHuman(s: string): string {
- // support more characters?
- return s
- .replaceAll("\n", "\\n")
- .replaceAll("\t", "\\t")
- .replaceAll("\r", "\\r");
-}
diff --git a/vhosts/blog/nuldoc-src/xml_test.ts b/vhosts/blog/nuldoc-src/xml_test.ts
deleted file mode 100644
index c423800e..00000000
--- a/vhosts/blog/nuldoc-src/xml_test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { assertEquals } from "@std/assert";
-import { parseXmlString } from "./xml.ts";
-
-Deno.test("Parse XML", () => {
- assertEquals(
- "__root__",
- parseXmlString(
- `
-
-
-
-
-
-`,
- ).name,
- );
-});
--
cgit v1.2.3-70-g09d2
From 1e2f2e68286ce018c57945ab3cdcbaaf484377ee Mon Sep 17 00:00:00 2001
From: nsfisis
Date: Wed, 9 Apr 2025 20:28:28 +0900
Subject: feat(blog/content): convert from .ndoc to .dj
---
vhosts/blog/.editorconfig | 2 +-
.../blog/content/posts/2021-03-05/my-first-post.dj | 21 +
.../content/posts/2021-03-05/my-first-post.ndoc | 25 -
.../content/posts/2021-03-30/phperkaigi-2021.dj | 177 ++++++
.../content/posts/2021-03-30/phperkaigi-2021.ndoc | 209 ------
.../cpp-you-can-use-keywords-in-attributes.dj | 113 ++++
.../cpp-you-can-use-keywords-in-attributes.ndoc | 140 ----
.../posts/2021-10-02/python-unbound-local-error.dj | 70 ++
.../2021-10-02/python-unbound-local-error.ndoc | 84 ---
.../ruby-detect-running-implementation.dj | 73 +++
.../ruby-detect-running-implementation.ndoc | 119 ----
.../2021-10-02/ruby-then-keyword-and-case-in.dj | 232 +++++++
.../2021-10-02/ruby-then-keyword-and-case-in.ndoc | 267 --------
.../rust-where-are-primitive-types-from.dj | 205 ++++++
.../rust-where-are-primitive-types-from.ndoc | 234 -------
...nce-between-autocmd-bufwrite-and-bufwritepre.dj | 126 ++++
...e-between-autocmd-bufwrite-and-bufwritepre.ndoc | 162 -----
.../2021-10-02/vim-swap-order-of-selected-lines.dj | 161 +++++
.../vim-swap-order-of-selected-lines.ndoc | 212 -------
.../posts/2022-04-09/phperkaigi-2022-tokens.dj | 489 ++++++++++++++
.../posts/2022-04-09/phperkaigi-2022-tokens.ndoc | 560 ----------------
...banner-write-tool-showing-banner-in-terminal.dj | 97 +++
...nner-write-tool-showing-banner-in-terminal.ndoc | 119 ----
.../content/posts/2022-05-01/phperkaigi-2022.dj | 132 ++++
.../content/posts/2022-05-01/phperkaigi-2022.ndoc | 166 -----
.../2022-08-27/php-conference-okinawa-code-golf.dj | 99 +++
.../php-conference-okinawa-code-golf.ndoc | 119 ----
.../support-for-communty-is-employee-benefits.dj | 52 ++
.../support-for-communty-is-employee-benefits.ndoc | 69 --
.../write-fizzbuzz-in-php-2-letters-per-line.dj | 617 ++++++++++++++++++
.../write-fizzbuzz-in-php-2-letters-per-line.ndoc | 705 ---------------------
.../phperkaigi-2023-unused-token-quiz-1.dj | 154 +++++
.../phperkaigi-2023-unused-token-quiz-1.ndoc | 185 ------
.../posts/2022-10-28/setup-server-for-this-site.dj | 264 ++++++++
.../2022-10-28/setup-server-for-this-site.ndoc | 310 ---------
.../phperkaigi-2023-unused-token-quiz-2.dj | 139 ++++
.../phperkaigi-2023-unused-token-quiz-2.ndoc | 171 -----
.../phperkaigi-2023-unused-token-quiz-3.dj | 289 +++++++++
.../phperkaigi-2023-unused-token-quiz-3.ndoc | 326 ----------
.../2023-03-10/rewrite-this-blog-generator.dj | 73 +++
.../2023-03-10/rewrite-this-blog-generator.ndoc | 95 ---
.../implementation-of-minimal-png-image-encoder.dj | 544 ++++++++++++++++
...mplementation-of-minimal-png-image-encoder.ndoc | 605 ------------------
.../posts/2023-04-04/phperkaigi-2023-report.dj | 141 +++++
.../posts/2023-04-04/phperkaigi-2023-report.ndoc | 184 ------
.../posts/2023-06-25/phpconfuk-2023-report.dj | 64 ++
.../posts/2023-06-25/phpconfuk-2023-report.ndoc | 106 ----
.../2023-10-02/compile-php-runtime-to-wasm.dj | 286 +++++++++
.../2023-10-02/compile-php-runtime-to-wasm.ndoc | 335 ----------
.../i-entered-the-open-university-of-japan.dj | 22 +
.../i-entered-the-open-university-of-japan.ndoc | 28 -
vhosts/blog/content/posts/2023-12-03/isucon-13.dj | 75 +++
.../blog/content/posts/2023-12-03/isucon-13.ndoc | 92 ---
.../content/posts/2023-12-31/2023-reflections.dj | 76 +++
.../content/posts/2023-12-31/2023-reflections.ndoc | 92 ---
...sert-namespace-declaration-to-empty-php-file.dj | 205 ++++++
...rt-namespace-declaration-to-empty-php-file.ndoc | 221 -------
.../install-wireguard-on-personal-server.dj | 144 +++++
.../install-wireguard-on-personal-server.ndoc | 180 ------
.../posts/2024-02-10/yapcjapan-2024-report.dj | 44 ++
.../posts/2024-02-10/yapcjapan-2024-report.ndoc | 55 --
.../posts/2024-02-22/phpkansai-2024-report.dj | 44 ++
.../posts/2024-02-22/phpkansai-2024-report.ndoc | 55 --
.../posts/2024-03-17/phperkaigi-2024-report.dj | 85 +++
.../posts/2024-03-17/phperkaigi-2024-report.ndoc | 110 ----
.../content/posts/2024-03-20/my-bucket-list.dj | 46 ++
.../content/posts/2024-03-20/my-bucket-list.ndoc | 57 --
.../posts/2024-04-14/phpcon-odawara-2024-report.dj | 76 +++
.../2024-04-14/phpcon-odawara-2024-report.ndoc | 102 ---
.../2024-04-21/pipefail-option-in-gitlab-ci-cd.dj | 155 +++++
.../pipefail-option-in-gitlab-ci-cd.ndoc | 180 ------
...file-completion-for-composer-custom-commands.dj | 86 +++
...le-completion-for-composer-custom-commands.ndoc | 103 ---
.../posts/2024-05-11/phpconkagawa-2024-report.dj | 64 ++
.../posts/2024-05-11/phpconkagawa-2024-report.ndoc | 107 ----
.../posts/2024-06-19/scalamatsuri-2024-report.dj | 48 ++
.../posts/2024-06-19/scalamatsuri-2024-report.ndoc | 63 --
.../reparojson-fix-only-json-formatter.dj | 132 ++++
.../reparojson-fix-only-json-formatter.ndoc | 153 -----
...ss-outer-scope-pipeline-within-with-or-range.dj | 118 ++++
...-outer-scope-pipeline-within-with-or-range.ndoc | 141 -----
.../content/posts/2024-09-28/mncore-challenge-1.dj | 37 ++
.../posts/2024-09-28/mncore-challenge-1.ndoc | 45 --
.../content/posts/2024-12-04/cohackpp-report.dj | 197 ++++++
.../content/posts/2024-12-04/cohackpp-report.ndoc | 217 -------
.../content/posts/2024-12-33/2024-reflections.dj | 84 +++
.../content/posts/2024-12-33/2024-reflections.ndoc | 101 ---
.../posts/2025-01-08/phperkaigi-2023-tokens-q1.dj | 350 ++++++++++
.../2025-01-08/phperkaigi-2023-tokens-q1.ndoc | 406 ------------
.../yaml-breaking-changes-between-v1-1-and-v1-2.dj | 76 +++
...aml-breaking-changes-between-v1-1-and-v1-2.ndoc | 87 ---
.../posts/2025-02-24/phpcon-nagoya-2025-report.dj | 49 ++
.../2025-02-24/phpcon-nagoya-2025-report.ndoc | 64 --
.../zip-function-like-command-paste-command.dj | 93 +++
.../zip-function-like-command-paste-command.ndoc | 111 ----
.../http-1-1-send-multiple-same-headers.dj | 102 +++
.../http-1-1-send-multiple-same-headers.ndoc | 120 ----
vhosts/blog/nuldoc-src/djot/djot2ndoc.ts | 54 +-
98 files changed, 7058 insertions(+), 8421 deletions(-)
create mode 100644 vhosts/blog/content/posts/2021-03-05/my-first-post.dj
delete mode 100644 vhosts/blog/content/posts/2021-03-05/my-first-post.ndoc
create mode 100644 vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.dj
delete mode 100644 vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc
create mode 100644 vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj
delete mode 100644 vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.ndoc
create mode 100644 vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.dj
delete mode 100644 vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.ndoc
create mode 100644 vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj
delete mode 100644 vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.ndoc
create mode 100644 vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.dj
delete mode 100644 vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.ndoc
create mode 100644 vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj
delete mode 100644 vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.ndoc
create mode 100644 vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj
delete mode 100644 vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.ndoc
create mode 100644 vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj
delete mode 100644 vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.ndoc
create mode 100644 vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj
delete mode 100644 vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.ndoc
create mode 100644 vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.dj
delete mode 100644 vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.ndoc
create mode 100644 vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj
delete mode 100644 vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.ndoc
create mode 100644 vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
delete mode 100644 vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.ndoc
create mode 100644 vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.dj
delete mode 100644 vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.ndoc
create mode 100644 vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj
delete mode 100644 vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.ndoc
create mode 100644 vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.dj
delete mode 100644 vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.ndoc
create mode 100644 vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.dj
delete mode 100644 vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.ndoc
create mode 100644 vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj
delete mode 100644 vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc
create mode 100644 vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj
delete mode 100644 vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.ndoc
create mode 100644 vhosts/blog/content/posts/2023-12-03/isucon-13.dj
delete mode 100644 vhosts/blog/content/posts/2023-12-03/isucon-13.ndoc
create mode 100644 vhosts/blog/content/posts/2023-12-31/2023-reflections.dj
delete mode 100644 vhosts/blog/content/posts/2023-12-31/2023-reflections.ndoc
create mode 100644 vhosts/blog/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj
delete mode 100644 vhosts/blog/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.ndoc
create mode 100644 vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.dj
delete mode 100644 vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.ndoc
create mode 100644 vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-03-20/my-bucket-list.dj
delete mode 100644 vhosts/blog/content/posts/2024-03-20/my-bucket-list.ndoc
create mode 100644 vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj
delete mode 100644 vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc
create mode 100644 vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj
delete mode 100644 vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.ndoc
create mode 100644 vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj
delete mode 100644 vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.ndoc
create mode 100644 vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj
delete mode 100644 vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.ndoc
create mode 100644 vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.dj
delete mode 100644 vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.ndoc
create mode 100644 vhosts/blog/content/posts/2024-12-04/cohackpp-report.dj
delete mode 100644 vhosts/blog/content/posts/2024-12-04/cohackpp-report.ndoc
create mode 100644 vhosts/blog/content/posts/2024-12-33/2024-reflections.dj
delete mode 100644 vhosts/blog/content/posts/2024-12-33/2024-reflections.ndoc
create mode 100644 vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj
delete mode 100644 vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.ndoc
create mode 100644 vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj
delete mode 100644 vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.ndoc
create mode 100644 vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj
delete mode 100644 vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.ndoc
create mode 100644 vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.dj
delete mode 100644 vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.ndoc
create mode 100644 vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj
delete mode 100644 vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.ndoc
(limited to 'vhosts/blog')
diff --git a/vhosts/blog/.editorconfig b/vhosts/blog/.editorconfig
index f6e2ae3e..a036f907 100644
--- a/vhosts/blog/.editorconfig
+++ b/vhosts/blog/.editorconfig
@@ -5,6 +5,6 @@ charset = utf-8
end_of_line = lf
insert_final_newline = true
-[*.ndoc]
+[*.dj]
indent_style = space
indent_size = 2
diff --git a/vhosts/blog/content/posts/2021-03-05/my-first-post.dj b/vhosts/blog/content/posts/2021-03-05/my-first-post.dj
new file mode 100644
index 00000000..a84dbd93
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-03-05/my-first-post.dj
@@ -0,0 +1,21 @@
+---
+[article]
+uuid = "6e9c71fd-bc8d-43ce-99c5-13d9f5b87ed2"
+title = "My First Post"
+description = "これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。"
+tags = []
+
+[[article.revisions]]
+date = "2021-03-05"
+remark = "公開"
+---
+{#test}
+# Test
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
+velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+mollit anim id est laborum.
diff --git a/vhosts/blog/content/posts/2021-03-05/my-first-post.ndoc b/vhosts/blog/content/posts/2021-03-05/my-first-post.ndoc
deleted file mode 100644
index ee23da14..00000000
--- a/vhosts/blog/content/posts/2021-03-05/my-first-post.ndoc
+++ /dev/null
@@ -1,25 +0,0 @@
----
-[article]
-uuid = "6e9c71fd-bc8d-43ce-99c5-13d9f5b87ed2"
-title = "My First Post"
-description = "これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。"
-tags = []
-
-[[article.revisions]]
-date = "2021-03-05"
-remark = "公開"
----
-
-
- Test
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
- tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
- veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
- commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
- velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
- occaecat cupidatat non proident, sunt in culpa qui officia deserunt
- mollit anim id est laborum.
-
-
-
diff --git a/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.dj b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.dj
new file mode 100644
index 00000000..fac9f1ed
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.dj
@@ -0,0 +1,177 @@
+---
+[article]
+uuid = "3fbe0b8c-216e-48f6-b905-c0d361b94542"
+title = "PHPerKaigi 2021"
+description = "2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2021-03-30"
+remark = "公開"
+
+[[article.revisions]]
+date = "2025-04-09"
+remark = "それぞれの発表に関するメモ部分を削除し、感想のみに"
+---
+{#report}
+# PHPerKaigi 2021 参加レポ
+
+2021-03-26 から 2021-03-28
+にかけて開催された、[PHPerKaigi 2021](https://phperkaigi.jp/2021/)
+に一般参加者として参加した。
+弊社[デジタルサーカス株式会社](https://www.dgcircus.com/)
+(今年1月から勤務)
+はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。
+
+このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。
+
+発表はトラック A、B に分かれていたのだが、今回はすべて A
+トラックを視聴している (切り替えるのが面倒だっただけ)。
+
+{#day-0}
+## Day 0 前夜祭 (2021/03/27)
+
+{#1730-a}
+### 17:30 [A] LAMPをこじらせてサーバーレスに乗り遅れたPHPerがLambdaに入門してみる
+
+AWS Lambda のような Function as a Service
+はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に
+web サービスを作る具体的なイメージがまだ見えない (注: すべて for me
+として書いている)。
+
+PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。
+
+勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP +
+Laravel などでは動かなさそう)
+だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。
+
+{#1810-a}
+### 18:10 [A] 大規模サイトにおけるSEO観点でのURL設計
+
+SEO (Search Engine Optimization)
+は大して知らないので新鮮な話が多かった。その分語れることも少ない……。
+
+{#1850-a}
+### 18:50 [A] PHPerでもわかる!実践Webアクセシビリティ
+
+つい最近 WAI-ARIA
+についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが)
+いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。
+
+{#1930-a}
+### 19:30 [A] PHP でファイルシステムを作ろう
+
+PHP で FUSE
+
+個人的に楽しみだった発表。
+
+期待通りの興味深い発表だった。FUSE
+自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。
+この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ
+(ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。
+
+{#day-1}
+## Day 1 (2021/03/27)
+
+{#1050-a}
+### 10:50 [A] 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜
+
+User Acceptance Test (UAT)
+くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。
+高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。
+
+{#1150-a}
+### 11:50 [A] 静的型解析を用いた大規模レガシーコードのリファクタリング計画
+
+型のある世界で生きてきた身として大いに楽しみにしていた発表。
+
+昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。
+今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で
+Ruby の typeprof には注目している。
+
+{#1310-a}
+### 13:10 [A] 目的に沿ったDocumentation as Codeをいかにして実現していくか
+
+この発表も以前から非常に楽しみにしていた。
+
+ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な
+(乖離しない)
+情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。
+
+{#1410-a}
+### 14:10 [A] PHPで学ぶ、セッションの基本と応用
+
+全体的に基本的な話だったので特に触れない。Cookie
+やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。
+
+{#1450-a}
+### 14:50 [A] PHP8になった今の時代に、PHPの「エラー」「例外」そして「Error」をおさらいしておこう
+
+PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で
+PHP を書き始めてから 4ヶ月ほどになる)。
+
+個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell
+などのエラーを「値として」扱う言語だと思っている。try-catch
+は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は
+C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる
+(C のそれはまともな型付けではない。念のため)。
+
+PHP
+のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。
+
+{#1530-a}
+### 15:30 [A] Laravel のメール認証の内部実装を掘り下げる
+
+Laravel
+の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。
+
+{#1610-a}
+### 16:10 [A] ブラウザから始めるgRPC 〜 gRPC-WebにPHPを添えて
+
+(発表の中でもまさに同じことをおっしゃっていたが) PHP
+以外の方が向いているだろう、というのが第一の感想である。gRPC
+はそれ自体というよりも Protobuf
+というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。
+
+{#day-2}
+## Day 2 (2021/03/28)
+
+冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。
+
+残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。
+
+{#comments}
+## 全体の感想
+
+Day 2
+にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも
+(特に初参加者として) 嬉しいポイントだった。
+
+今回、雑談/登壇者への質問等向けに Discord
+サーバもあったのだが、こちらは参加こそしたものの ROM
+のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord
+表示に
+1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった
+(さらにいうと Zoom
+でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。
+
+1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord
+しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。
+まあ初カンファレンスだし、とお茶を濁しておこう。
+
+さて、カンファレンスで一つ気になったことがある。それは、Discord
+という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord
+の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord
+があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。
+
+----------------
+
+最後になりましたが、毎年の PHPerKaigi
+開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました!
+(ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い)
+
+ではまた来年。
diff --git a/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.ndoc b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.ndoc
deleted file mode 100644
index b9a1f67e..00000000
--- a/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.ndoc
+++ /dev/null
@@ -1,209 +0,0 @@
----
-[article]
-uuid = "3fbe0b8c-216e-48f6-b905-c0d361b94542"
-title = "PHPerKaigi 2021"
-description = "2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。"
-tags = [
- "conference",
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2021-03-30"
-remark = "公開"
-
-[[article.revisions]]
-date = "2025-04-09"
-remark = "それぞれの発表に関するメモ部分を削除し、感想のみに"
----
-
-
- PHPerKaigi 2021 参加レポ
-
- 2021-03-26 から 2021-03-28
- にかけて開催された、 PHPerKaigi 2021
- に一般参加者として参加した。
- 弊社 デジタルサーカス株式会社
- (今年1月から勤務)
- はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。
-
-
- このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。
-
-
- 発表はトラック A、B に分かれていたのだが、今回はすべて A
- トラックを視聴している (切り替えるのが面倒だっただけ)。
-
-
- Day 0 前夜祭 (2021/03/27)
-
- 17:30 [A] LAMPをこじらせてサーバーレスに乗り遅れたPHPerがLambdaに入門してみる
-
- AWS Lambda のような Function as a Service
- はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に
- web サービスを作る具体的なイメージがまだ見えない (注: すべて for me
- として書いている)。
-
-
- PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。
-
-
- 勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP +
- Laravel などでは動かなさそう)
- だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。
-
-
-
- 18:10 [A] 大規模サイトにおけるSEO観点でのURL設計
-
- SEO (Search Engine Optimization)
- は大して知らないので新鮮な話が多かった。その分語れることも少ない……。
-
-
-
- 18:50 [A] PHPerでもわかる!実践Webアクセシビリティ
-
- つい最近 WAI-ARIA
- についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが)
- いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。
-
-
-
- 19:30 [A] PHP でファイルシステムを作ろう
-
- PHP で FUSE
-
-
- 個人的に楽しみだった発表。
-
-
- 期待通りの興味深い発表だった。FUSE
- 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。
- この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ
- (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。
-
-
-
-
- Day 1 (2021/03/27)
-
- 10:50 [A] 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜
-
- User Acceptance Test (UAT)
- くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。
- 高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。
-
-
-
- 11:50 [A] 静的型解析を用いた大規模レガシーコードのリファクタリング計画
-
- 型のある世界で生きてきた身として大いに楽しみにしていた発表。
-
-
- 昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。
- 今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で
- Ruby の typeprof には注目している。
-
-
-
- 13:10 [A] 目的に沿ったDocumentation as Codeをいかにして実現していくか
-
- この発表も以前から非常に楽しみにしていた。
-
-
- ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な
- (乖離しない)
- 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。
-
-
-
- 14:10 [A] PHPで学ぶ、セッションの基本と応用
-
- 全体的に基本的な話だったので特に触れない。Cookie
- やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。
-
-
-
- 14:50 [A] PHP8になった今の時代に、PHPの「エラー」「例外」そして「Error」をおさらいしておこう
-
- PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で
- PHP を書き始めてから 4ヶ月ほどになる)。
-
-
- 個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell
- などのエラーを「値として」扱う言語だと思っている。try-catch
- は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は
- C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる
- (C のそれはまともな型付けではない。念のため)。
-
-
- PHP
- のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。
-
-
-
- 15:30 [A] Laravel のメール認証の内部実装を掘り下げる
-
- Laravel
- の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。
-
-
-
- 16:10 [A] ブラウザから始めるgRPC 〜 gRPC-WebにPHPを添えて
-
- (発表の中でもまさに同じことをおっしゃっていたが) PHP
- 以外の方が向いているだろう、というのが第一の感想である。gRPC
- はそれ自体というよりも Protobuf
- というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。
-
-
-
-
- Day 2 (2021/03/28)
-
- 冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。
-
-
- 残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。
-
-
-
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj
new file mode 100644
index 00000000..94690092
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj
@@ -0,0 +1,113 @@
+---
+[article]
+uuid = "69863d75-ef21-42db-b743-5958f7c86827"
+title = "【C++】 属性構文の属性名にはキーワードが使える"
+description = "C++ の属性構文の属性名には、キーワードが使える。ネタ記事。"
+tags = [
+ "cpp",
+ "cpp17",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/94090937bcf860cfa93b
+:::
+
+タイトル落ち。まずはこのコードを見て欲しい。
+
+```cpp
+#include
+
+[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
+[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
+[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
+[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
+[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
+[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
+[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
+[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
+[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
+[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
+[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
+[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
+[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
+// [[using]]
+int main() {
+ std::cout << "Hello, World!" << std::endl;
+}
+```
+
+コンパイラのバージョン
+
+```
+$ clang++ –version Apple clang version 11.0.0
+(clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model:
+posix InstalledDir:
+/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
+```
+
+コンパイルコマンド (C17指定)
+
+```
+$ clang –std=c++17 hoge.cpp
+```
+
+この記事から得られるものはこれ以上ないので以下は蛇足になる。
+
+別件で [cppreference.com の identifier のページ](https://en.cppreference.com/w/cpp/language/identifiers)を読んでいた時、次の文が目に止まった。
+
+> * the identifiers that are keywords cannot be used for other purposes;
+>
+> * The only place they can be used as non-keywords is in an attribute-token. (e.g. [[private]] is a valid attribute) (since C++11)
+
+キーワードでも属性として指定する場合は非キーワードとして使えるらしい。
+実際にやってみる。
+
+同サイトの [keywords のページ](https://en.cppreference.com/w/cpp/keyword)
+から一覧を拝借し、上のコードが出来上がった (C++17
+においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown
+attribute \`〇〇' ignored)
+がコンパイラから出力されるが、コンパイルできる。
+
+上のコードでは `[[using]]` をコメントアウトしているが、これは `using`
+キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。
+
+```cpp
+// using の例
+[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文
+```
+
+C++17 の仕様も見てみる (正確には標準化前のドラフト)。
+
+引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4
+
+> If a keyword or an alternative token that satisfies the syntactic
+> requirements of an identifier is contained in an attribute-token, it is
+> considered an identifier.
+
+「`identifier` の構文上の要件を満たすキーワードまたは代替トークンが
+`attribute-token` に含まれている場合、`identifier`
+とみなされる」とある。どうやら間違いないようだ。
+
+ところで、代替トークン (alternative token) とは `and` (`&`) や `bitor`
+(`|`) などのことだが、`identifier`
+の構文上の要件を満たさないような代替トークンなどあるのか?
+疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい
+(参考:
+[同ドラフト](https://timsong-cpp.github.io/cppwp/n4659/lex.digraph) )
+
+* `<%` → `{`
+* `%>` → `}`
+* `<:` → `[`
+* `:>` → `]`
+* `%:` → `#`
+* `%:%:` → `##`
+
+「`identifier`
+の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。
+
+調べた感想: 字句解析器か構文解析器が辛そう
diff --git a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.ndoc b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.ndoc
deleted file mode 100644
index 244786dd..00000000
--- a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.ndoc
+++ /dev/null
@@ -1,140 +0,0 @@
----
-[article]
-uuid = "69863d75-ef21-42db-b743-5958f7c86827"
-title = "【C++】 属性構文の属性名にはキーワードが使える"
-description = "C++ の属性構文の属性名には、キーワードが使える。ネタ記事。"
-tags = [
- "cpp",
- "cpp17",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/94090937bcf860cfa93b
-
-
- タイトル落ち。まずはこのコードを見て欲しい。
-
-
-
-
- [[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
- [[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
- [[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
- [[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
- [[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
- [[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
- [[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
- [[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
- [[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
- [[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
- [[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
- [[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
- [[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
- // [[using]]
- int main() {
- std::cout << "Hello, World!" << std::endl;
- }
- ]]>
-
-
-
- コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0
- (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model:
- posix InstalledDir:
- /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
-
-
- コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp
-
-
-
- この記事から得られるものはこれ以上ないので以下は蛇足になる。
-
-
- 別件で cppreference.com の
- identifier
- のページ を読んでいた時、次の文が目に止まった。
-
-
-
- -
- the identifiers that are keywords cannot be used for other purposes;
-
- -
- The only place they can be used as non-keywords is in an
- attribute-token. (e.g. [[private]] is a valid attribute) (since C++11)
-
-
-
-
-
-
- キーワードでも属性として指定する場合は非キーワードとして使えるらしい。
- 実際にやってみる。
-
-
- 同サイトの keywords のページ
- から一覧を拝借し、上のコードが出来上がった (C++17
- においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown
- attribute `〇〇' ignored)
- がコンパイラから出力されるが、コンパイルできる。
-
-
- 上のコードでは [[using]] をコメントアウトしているが、これは using
- キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。
-
-
-
-
-
- C++17 の仕様も見てみる (正確には標準化前のドラフト)。
-
-
- 引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4
-
-
-
- If a keyword or an alternative token that satisfies the syntactic
- requirements of an identifier is contained in an attribute-token, it is
- considered an identifier.
-
-
-
- 「identifier の構文上の要件を満たすキーワードまたは代替トークンが
- attribute-token に含まれている場合、identifier
- とみなされる」とある。どうやら間違いないようだ。
-
-
- ところで、代替トークン (alternative token) とは and (&) や bitor
- (|) などのことだが、identifier
- の構文上の要件を満たさないような代替トークンなどあるのか?
- 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい
- (参考:
- 同ドラフト)
-
-
- <% → {
- %> → }
- <: → [
- :> → ]
- %: → #
- %:%: → ##
-
-
- 「identifier
- の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。
-
-
- 調べた感想: 字句解析器か構文解析器が辛そう
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.dj b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.dj
new file mode 100644
index 00000000..88d03151
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.dj
@@ -0,0 +1,70 @@
+---
+[article]
+uuid = "e1aff84c-d6d4-4dea-bc45-9c41e6445006"
+title = "【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment"
+description = "Python における UnboundLocalError の理由と対処法。"
+tags = [
+ "python",
+ "python3",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/5d733703afcb35bbf399
+:::
+
+本記事は Python 3.7.6 の動作結果を元にして書かれている。
+
+Python でクロージャを作ろうと、次のようなコードを書いた。
+
+```python
+def f():
+ x = 0
+ def g():
+ x += 1
+ g()
+
+f()
+```
+
+関数 `g` から 関数 `f` のスコープ内で定義された変数 `x` を参照し、それに
+1 を足そうとしている。 これを実行すると `x += 1`
+の箇所でエラーが発生する。
+
+> UnboundLocalError: local variable \`x' referenced before assignment
+
+local変数 `x` が代入前に参照された、とある。これは、`f` の `x`
+を参照するのではなく、新しく別の変数を `g` 内に作ってしまっているため。
+前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。`var`
+を変数宣言のための構文として擬似的に利用している。
+
+```python
+# 注: var は正しい Python の文法ではない。上記参照のこと
+def f():
+ var x # f の local変数 'x' を宣言
+ x = 0 # x に 0 を代入
+ def g(): # f の内部関数 g を定義
+ var x # g の local変数 'x' を宣言
+ # たまたま f にも同じ名前の変数があるが、それとは別の変数
+ x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文)
+ # 加算する前の値を参照しようとするが、まだ代入されていないためエラー
+ g()
+```
+
+当初の意図を表現するには、次のように書けばよい。
+
+```python
+def f():
+ x = 0
+ def g():
+ nonlocal x ## (*)
+ x += 1
+ g()
+```
+
+`(*)` のように、`nonlocal` を追加する。これにより一つ外側のスコープ (`g`
+の一つ外側 = `f`) で定義されている `x` を探しに行くようになる。
diff --git a/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.ndoc b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.ndoc
deleted file mode 100644
index b771e8c8..00000000
--- a/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.ndoc
+++ /dev/null
@@ -1,84 +0,0 @@
----
-[article]
-uuid = "e1aff84c-d6d4-4dea-bc45-9c41e6445006"
-title = "【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment"
-description = "Python における UnboundLocalError の理由と対処法。"
-tags = [
- "python",
- "python3",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/5d733703afcb35bbf399
-
-
- 本記事は Python 3.7.6 の動作結果を元にして書かれている。
-
-
- Python でクロージャを作ろうと、次のようなコードを書いた。
-
-
-
-
-
- 関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに
- 1 を足そうとしている。 これを実行すると x += 1
- の箇所でエラーが発生する。
-
-
-
- UnboundLocalError: local variable `x' referenced before assignment
-
-
-
- local変数 x が代入前に参照された、とある。これは、f の x
- を参照するのではなく、新しく別の変数を g 内に作ってしまっているため。
- 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。var
- を変数宣言のための構文として擬似的に利用している。
-
-
-
-
-
- 当初の意図を表現するには、次のように書けばよい。
-
-
-
-
-
- (*) のように、nonlocal を追加する。これにより一つ外側のスコープ (g
- の一つ外側 = f) で定義されている x を探しに行くようになる。
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.dj b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.dj
new file mode 100644
index 00000000..4c73580c
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.dj
@@ -0,0 +1,73 @@
+---
+[article]
+uuid = "e1456a50-4fc6-42ef-89f3-8be78e01da13"
+title = "【Ruby】 自身を実行している処理系の種類を判定する"
+description = "Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。"
+tags = [
+ "ruby",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791
+:::
+
+Ruby
+という言語には複数の実装があるが、それらをスクリプト上からどのようにして
+programmatically に見分ければよいだろうか。
+
+`Object` クラスに定義されている `RUBY_ENGINE`
+という定数がこの用途に使える。
+
+参考:
+[Object::RUBY_ENGINE](https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html)
+
+上記ページの例から引用する:
+
+```shell-session
+$ ruby-1.9.1 -ve 'p RUBY_ENGINE'
+ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux]
+"ruby"
+$ jruby -ve 'p RUBY_ENGINE'
+jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]
+"jruby"
+```
+
+それぞれの処理系がどのような値を返すかだが、stack overflow
+に良い質問と回答があった。
+
+[What values for RUBY_ENGINE correspond to which Ruby implementations?](https://stackoverflow.com/a/9894232) より引用:
+
+> ```
+> | RUBY_ENGINE | Implementation |
+> |:-----------:|:------------------|
+> | | MRI < 1.9 |
+> | 'ruby' | MRI >= 1.9 or REE |
+> | 'jruby' | JRuby |
+> | 'macruby' | MacRuby |
+> | 'rbx' | Rubinius |
+> | 'maglev' | MagLev |
+> | 'ironruby' | IronRuby |
+> | 'cardinal' | Cardinal |
+> ```
+
+なお、この質問・回答は
+2014年になされたものであり、値は変わっている可能性がある。MRI (aka
+CRuby) については執筆時現在 (2020/12/8) も `'ruby'`
+が返ってくることを確認済み。
+
+この表にない主要な処理系として、 [mruby](https://mruby.org) は `'mruby'`
+を返す。
+
+[mruby 該当部分のソース](https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35) より引用:
+
+```c
+/*
+ * Ruby engine.
+ */
+#define MRUBY_RUBY_ENGINE "mruby"
+```
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.ndoc b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.ndoc
deleted file mode 100644
index af02fde4..00000000
--- a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.ndoc
+++ /dev/null
@@ -1,119 +0,0 @@
----
-[article]
-uuid = "e1456a50-4fc6-42ef-89f3-8be78e01da13"
-title = "【Ruby】 自身を実行している処理系の種類を判定する"
-description = "Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。"
-tags = [
- "ruby",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791
-
-
- Ruby
- という言語には複数の実装があるが、それらをスクリプト上からどのようにして
- programmatically に見分ければよいだろうか。
-
-
- Object クラスに定義されている RUBY_ENGINE
- という定数がこの用途に使える。
-
-
- 参考:
- Object::RUBY_ENGINE
-
-
- 上記ページの例から引用する:
-
-
-
-
-
- それぞれの処理系がどのような値を返すかだが、stack overflow
- に良い質問と回答があった。
-
-
- What values for RUBY_ENGINE
- correspond to which Ruby implementations? より引用:
-
-
-
-
-
- | RUBY_ENGINE |
- Implementation |
-
-
-
-
- | <undefined> |
- MRI < 1.9 |
-
-
- | `ruby' |
- MRI >= 1.9 or REE |
-
-
- | `jruby' |
- JRuby |
-
-
- | `macruby' |
- MacRuby |
-
-
- | `rbx' |
- Rubinius |
-
-
- | `maglev' |
- MagLev |
-
-
- | `ironruby' |
- IronRuby |
-
-
- | `cardinal' |
- Cardinal |
-
-
-
-
-
- なお、この質問・回答は
- 2014年になされたものであり、値は変わっている可能性がある。MRI (aka
- CRuby) については執筆時現在 (2020/12/8) も 'ruby'
- が返ってくることを確認済み。
-
-
- この表にない主要な処理系として、mruby は 'mruby'
- を返す。
-
-
- mruby
- 該当部分のソース より引用:
-
-
-
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj
new file mode 100644
index 00000000..7d8616fc
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj
@@ -0,0 +1,232 @@
+---
+[article]
+uuid = "87455008-fe5b-49bf-af5a-b875264f8326"
+title = "【Ruby】 then キーワードと case in"
+description = "Ruby 3.0 で追加される case in 構文と、then キーワードについて。"
+tags = [
+ "ruby",
+ "ruby3",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/787a8cf888a304497223
+:::
+
+{#tl-dr}
+# TL; DR
+
+`case` - `in` によるパターンマッチング構文でも、`case` - `when`
+と同じように `then` が使える (場合によっては使う必要がある)。
+
+{#what-is-then-keyword}
+# `then` とは
+
+使われることは稀だが、Ruby では `then`
+がキーワードになっている。次のように使う:
+
+```ruby
+if cond then
+ puts "Y"
+else
+ puts "N"
+end
+```
+
+このキーワードが現れうる場所はいくつかあり、`if`、`unless`、`rescue`、`case`
+構文がそれに当たる。 上記のように、何か条件を書いた後 `then`
+を置き、式がそこで終了していることを示すマーカーとして機能する。
+
+```ruby
+# Example:
+
+if x then
+ a
+end
+
+unless x then
+ a
+end
+
+begin
+ a
+rescue then
+ b
+end
+
+case x
+when p then
+ a
+end
+```
+
+{#why-then-is-usually-unnecessary}
+# なぜ普段は書かなくてもよいのか
+
+普通 Ruby のコードで `then`
+を書くことはない。なぜか。次のコードを実行してみるとわかる。
+
+```ruby
+if true puts 'Hello, World!' end
+```
+
+次のような構文エラーが出力される。
+
+```
+20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n'
+if true puts 'Hello, World!' end
+ ^~~~
+20:1: syntax error, unexpected `end', expecting end-of-input
+...f true puts 'Hello, World!' end
+```
+
+二つ目のメッセージは無視して一つ目を読むと、`then` か `;`
+か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。
+
+ポイントは改行が `then` (や `;`) の代わりとなることである。`true`
+の後に改行を入れてみる。
+
+```ruby
+if true
+puts 'Hello, World!' end
+```
+
+無事 Hello, World! と出力されるようになった。
+
+{#why-then-or-linebreak-is-needed}
+# なぜ `then` や `;` や改行が必要か
+
+なぜ `then` や `;` や改行 (以下 「`then` 等」)
+が必要なのだろうか。次の例を見てほしい:
+
+```ruby
+if a b end
+```
+
+`then` も `;`
+も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。
+この例は二通りに解釈できる。
+
+```ruby
+# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
+if a then
+b
+end
+```
+
+```ruby
+# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
+# その結果が truthy なら何もしない
+if a(b) then
+end
+```
+
+`then` 等はこの曖昧性を排除するためにあり、条件式は `if` から `then`
+等までの間にある、ということを明確にする。 C系の `if` 後に来る `(`/`)`
+や、Python の `:`、Rust/Go/Swift などの `{` も同じ役割を持つ。
+
+Ruby の場合、プログラマーが書きやすいよう改行でもって `then`
+が代用できるので、ほとんどの場合 `then` は必要ない。
+
+{#then-in-case-in}
+# `case` - `in` における `then`
+
+ようやく本題にたどり着いた。来る Ruby 3.0 では `case` と `in`
+キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして
+`then` 等が必要になる。 (現在の) Ruby には formal
+な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc
+の説明は省略)。
+
+https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986
+
+```yacc
+p_case_body : keyword_in
+{
+ SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
+ p->command_start = FALSE;
+ $1 = p->ctxt;
+ p->ctxt.in_kwarg = 1;
+ $$ = push_pvtbl(p);
+}
+{
+ $$ = push_pktbl(p);
+}
+p_top_expr then
+{
+ pop_pktbl(p, $3);
+ pop_pvtbl(p, $2);
+ p->ctxt.in_kwarg = $1.in_kwarg;
+}
+compstmt
+p_cases
+{
+ /*%%%*/
+ $$ = NEW_IN($4, $7, $8, &@$);
+ /*% %*/
+ /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
+}
+;
+```
+
+簡略版:
+
+```yacc
+p_case_body : keyword_in p_top_expr then compstmt p_cases
+;
+```
+
+ここで、`keyword_in` は文字通り `in`、`p_top_expr`
+はいわゆるパターン、`then` は `then`
+キーワードのことではなく、この記事で `then` 等と呼んでいるもの、つまり
+`then` キーワード、`;`、改行のいずれかである。
+
+これにより、`case` - `when` による従来の構文と同じように、`then`
+等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:
+
+```ruby
+case x
+in 1 then a
+in 2 then b
+in 3 then c
+end
+
+case x
+in 1
+ a
+in 2
+ b
+in 3
+ c
+end
+
+case x
+in 1; a
+in 2; b
+in 3; c
+end
+```
+
+ところで、`p_top_expr` には `if` による guard clause
+が書けるので、その場合は `if` - `then` と似たような見た目になる。
+
+```ruby
+case x
+in 0 then a
+in n if n < 0 then b
+in n then c
+end
+```
+
+{#outro}
+# まとめ
+
+* `if` や `case` の条件の後ろには `then`、`;`、改行のいずれかが必要
+
+ * 通常は改行しておけばよい
+
+* 3.0 で入る予定の `case` - `in` でも `then` 等が必要になる
+* Ruby の構文を正確に知るには (現状) `parse.y` を直接読めばよい
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc
deleted file mode 100644
index ef2534c4..00000000
--- a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc
+++ /dev/null
@@ -1,267 +0,0 @@
----
-[article]
-uuid = "87455008-fe5b-49bf-af5a-b875264f8326"
-title = "【Ruby】 then キーワードと case in"
-description = "Ruby 3.0 で追加される case in 構文と、then キーワードについて。"
-tags = [
- "ruby",
- "ruby3",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/787a8cf888a304497223
-
-
- TL; DR
-
- case - in によるパターンマッチング構文でも、case - when
- と同じように then が使える (場合によっては使う必要がある)。
-
-
-
- then とは
-
- 使われることは稀だが、Ruby では then
- がキーワードになっている。次のように使う:
-
-
-
-
-
- このキーワードが現れうる場所はいくつかあり、if、unless、rescue、case
- 構文がそれに当たる。 上記のように、何か条件を書いた後 then
- を置き、式がそこで終了していることを示すマーカーとして機能する。
-
-
-
-
-
-
- なぜ普段は書かなくてもよいのか
-
- 普通 Ruby のコードで then
- を書くことはない。なぜか。次のコードを実行してみるとわかる。
-
-
-
-
-
- 次のような構文エラーが出力される。
-
-
-
-
-
- 二つ目のメッセージは無視して一つ目を読むと、then か ;
- か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。
-
-
- ポイントは改行が then (や ;) の代わりとなることである。true
- の後に改行を入れてみる。
-
-
-
-
-
- 無事 Hello, World! と出力されるようになった。
-
-
-
- なぜ then や ; や改行が必要か
-
- なぜ then や ; や改行 (以下 「then 等」)
- が必要なのだろうか。次の例を見てほしい:
-
-
-
-
-
- then も ;
- も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。
- この例は二通りに解釈できる。
-
-
-
-
-
-
-
-
- then 等はこの曖昧性を排除するためにあり、条件式は if から then
- 等までの間にある、ということを明確にする。 C系の if 後に来る (/)
- や、Python の :、Rust/Go/Swift などの { も同じ役割を持つ。
-
-
- Ruby の場合、プログラマーが書きやすいよう改行でもって then
- が代用できるので、ほとんどの場合 then は必要ない。
-
-
-
- case - in における then
-
- ようやく本題にたどり着いた。来る Ruby 3.0 では case と in
- キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして
- then 等が必要になる。 (現在の) Ruby には formal
- な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc
- の説明は省略)。
-
-
- https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986
-
-
- command_start = FALSE;
- $1 = p->ctxt;
- p->ctxt.in_kwarg = 1;
- $$ = push_pvtbl(p);
- }
- {
- $$ = push_pktbl(p);
- }
- p_top_expr then
- {
- pop_pktbl(p, $3);
- pop_pvtbl(p, $2);
- p->ctxt.in_kwarg = $1.in_kwarg;
- }
- compstmt
- p_cases
- {
- /*%%%*/
- $$ = NEW_IN($4, $7, $8, &@$);
- /*% %*/
- /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
- }
- ;
- ]]>
-
-
- 簡略版:
-
-
-
-
-
- ここで、keyword_in は文字通り in、p_top_expr
- はいわゆるパターン、then は then
- キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり
- then キーワード、;、改行のいずれかである。
-
-
- これにより、case - when による従来の構文と同じように、then
- 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:
-
-
-
-
-
- ところで、p_top_expr には if による guard clause
- が書けるので、その場合は if - then と似たような見た目になる。
-
-
-
-
-
-
- まとめ
-
- -
-
if や case の条件の後ろには then、;、改行のいずれかが必要
-
-
- - 3.0 で入る予定の
case - in でも then 等が必要になる
- - Ruby の構文を正確に知るには (現状)
parse.y を直接読めばよい
-
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj
new file mode 100644
index 00000000..212cd3d6
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj
@@ -0,0 +1,205 @@
+---
+[article]
+uuid = "91c61980-c262-4e8d-89b0-4304e7f6d644"
+title = "Rust のプリミティブ型はどこからやって来るか"
+description = "Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。"
+tags = [
+ "rust",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/9a429432258bbcd6c565
+:::
+
+{#intro}
+# 前置き
+
+Rust
+において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。
+
+```rust
+#![allow(non_camel_case_types)]
+#![allow(dead_code)]
+
+struct bool;
+struct char;
+struct i8;
+struct i16;
+struct i32;
+struct i64;
+struct i128;
+struct isize;
+struct u8;
+struct u16;
+struct u32;
+struct u64;
+struct u128;
+struct usize;
+struct f32;
+struct f64;
+struct str;
+```
+
+では、普段単に `bool` と書いたとき、この `bool`
+は一体どこから来ているのか。rustc のソースを追ってみた。
+
+ 前提知識: 一般的なコンパイラの構造、用語。`rustc` そのものの知識は不要
+ (というよりも筆者自身がよく知らない)
+
+{#code-reading}
+# 調査
+
+調査に使用したソース (調査時点での最新 master)
+
+https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98
+
+どのようにして調べるか。rustc
+の構造には詳しくないため、すぐに当たりをつけるのは難しい。
+
+大雑把な構造としては、`compiler` フォルダ以下に `rustc_*`
+という名前のクレートが数十個入っている。これがどうやら `rustc`
+コマンドの実装部のようだ。
+
+`rustc` はセルフホストされている (= `rustc` 自身が Rust で書かれている)
+ので、`bool` や `char`
+などで適当に検索をかけてもノイズが多すぎて話にならない。
+しかし、お誂え向きなことに `i128`/`u128`
+というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って
+`git grep` してみる。
+
+```
+$ git grep "\bi128\b" | wc # i128
+165 1069 15790
+
+$ git grep "\bu128\b" | wc # u128
+293 2127 26667
+
+$ git grep "\bbool\b" | wc # cf. bool の結果
+3563 23577 294659
+```
+
+165
+程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
+
+```
+$ git grep "\bi128\b"
+...
+rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));
+...
+```
+
+`rustc_resolve`
+というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。
+
+```rust
+/// Interns the names of the primitive types.
+///
+/// All other types are defined somewhere and possibly imported, but the primitive ones need
+/// special handling, since they have no place of origin.
+struct PrimitiveTypeTable {
+ primitive_types: FxHashMap,
+}
+
+impl PrimitiveTypeTable {
+ fn new() -> PrimitiveTypeTable {
+ let mut table = FxHashMap::default();
+
+ table.insert(sym::bool, Bool);
+ table.insert(sym::char, Char);
+ table.insert(sym::f32, Float(FloatTy::F32));
+ table.insert(sym::f64, Float(FloatTy::F64));
+ table.insert(sym::isize, Int(IntTy::Isize));
+ table.insert(sym::i8, Int(IntTy::I8));
+ table.insert(sym::i16, Int(IntTy::I16));
+ table.insert(sym::i32, Int(IntTy::I32));
+ table.insert(sym::i64, Int(IntTy::I64));
+ table.insert(sym::i128, Int(IntTy::I128));
+ table.insert(sym::str, Str);
+ table.insert(sym::usize, Uint(UintTy::Usize));
+ table.insert(sym::u8, Uint(UintTy::U8));
+ table.insert(sym::u16, Uint(UintTy::U16));
+ table.insert(sym::u32, Uint(UintTy::U32));
+ table.insert(sym::u64, Uint(UintTy::U64));
+ table.insert(sym::u128, Uint(UintTy::U128));
+ Self { primitive_types: table }
+ }
+}
+```
+
+これは初めに列挙したプリミティブ型の一覧と一致している。doc comment
+にも、
+
+```
+All other types are defined somewhere and possibly imported, but the
+primitive ones need special handling, since they have no place of
+origin.
+```
+
+とある。次はこの struct
+の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
+
+```rust
+/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
+/// (略)
+fn resolve_ident_in_lexical_scope(
+ &mut self,
+ mut ident: Ident,
+ ns: Namespace,
+ // (略)
+) -> Option> {
+ // (略)
+
+ if ns == TypeNS {
+ if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) {
+ let binding =
+ (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root())
+ .to_name_binding(self.arenas);
+ return Some(LexicalScopeBinding::Item(binding));
+ }
+ }
+
+ None
+}
+```
+
+関数名や doc comment が示している通り、この関数は識別子 (identifier,
+ident) を現在のレキシカルスコープ内で解決 (resolve) する。
+`if ns == TypeNS` のブロック内では、`primitive_type_table` (上記の
+`PrimitiveTypeTable::new()` で作られた変数) に含まれている識別子
+(`bool`、`i32` など)
+かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。
+
+なお、`ns` は「名前空間」を示す変数である。Rust
+における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この
+`if`
+は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。
+
+重要なのは、これが `resolve_ident_in_lexical_scope()`
+の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。
+
+動作がわかったところで、例として次のコードを考える。
+
+```rust
+#![allow(non_camel_case_types)]
+
+struct bool;
+
+fn main() {
+ let _: bool = bool;
+}
+```
+
+ここで `main()` の `bool` は `struct bool`
+として解決される。なぜなら、プリミティブ型の判定をする前に `bool`
+という名前の別の型が見つかるからだ。
+
+{#outro}
+# まとめ
+
+Rust
+のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。
diff --git a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.ndoc b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.ndoc
deleted file mode 100644
index 1ce2607f..00000000
--- a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.ndoc
+++ /dev/null
@@ -1,234 +0,0 @@
----
-[article]
-uuid = "91c61980-c262-4e8d-89b0-4304e7f6d644"
-title = "Rust のプリミティブ型はどこからやって来るか"
-description = "Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。"
-tags = [
- "rust",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/9a429432258bbcd6c565
-
-
- 前置き
-
- Rust
- において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。
-
-
-
-
-
- では、普段単に bool と書いたとき、この bool
- は一体どこから来ているのか。rustc のソースを追ってみた。
-
-
-
- 前提知識: 一般的なコンパイラの構造、用語。rustc そのものの知識は不要
- (というよりも筆者自身がよく知らない)
-
-
-
-
- 調査
-
- 調査に使用したソース (調査時点での最新 master)
-
-
- https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98
-
-
- どのようにして調べるか。rustc
- の構造には詳しくないため、すぐに当たりをつけるのは難しい。
-
-
- 大雑把な構造としては、compiler フォルダ以下に rustc_*
- という名前のクレートが数十個入っている。これがどうやら rustc
- コマンドの実装部のようだ。
-
-
- rustc はセルフホストされている (= rustc 自身が Rust で書かれている)
- ので、bool や char
- などで適当に検索をかけてもノイズが多すぎて話にならない。
- しかし、お誂え向きなことに i128/u128
- というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って
- git grep してみる。
-
-
-
-
-
- 165
- 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
-
-
-
-
-
- rustc_resolve
- というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。
-
-
- ,
- }
-
- impl PrimitiveTypeTable {
- fn new() -> PrimitiveTypeTable {
- let mut table = FxHashMap::default();
-
- table.insert(sym::bool, Bool);
- table.insert(sym::char, Char);
- table.insert(sym::f32, Float(FloatTy::F32));
- table.insert(sym::f64, Float(FloatTy::F64));
- table.insert(sym::isize, Int(IntTy::Isize));
- table.insert(sym::i8, Int(IntTy::I8));
- table.insert(sym::i16, Int(IntTy::I16));
- table.insert(sym::i32, Int(IntTy::I32));
- table.insert(sym::i64, Int(IntTy::I64));
- table.insert(sym::i128, Int(IntTy::I128));
- table.insert(sym::str, Str);
- table.insert(sym::usize, Uint(UintTy::Usize));
- table.insert(sym::u8, Uint(UintTy::U8));
- table.insert(sym::u16, Uint(UintTy::U16));
- table.insert(sym::u32, Uint(UintTy::U32));
- table.insert(sym::u64, Uint(UintTy::U64));
- table.insert(sym::u128, Uint(UintTy::U128));
- Self { primitive_types: table }
- }
- }
- ]]>
-
-
- これは初めに列挙したプリミティブ型の一覧と一致している。doc comment
- にも、
-
-
-
- All other types are defined somewhere and possibly imported, but the
- primitive ones need special handling, since they have no place of
- origin.
-
-
-
- とある。次はこの struct
- の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
-
-
- Option> {
- // (略)
-
- if ns == TypeNS {
- if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) {
- let binding =
- (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root())
- .to_name_binding(self.arenas);
- return Some(LexicalScopeBinding::Item(binding));
- }
- }
-
- None
- }
- ]]>
-
-
- 関数名や doc comment が示している通り、この関数は識別子 (identifier,
- ident) を現在のレキシカルスコープ内で解決 (resolve) する。
- if ns == TypeNS のブロック内では、primitive_type_table (上記の
- PrimitiveTypeTable::new() で作られた変数) に含まれている識別子
- (bool、i32 など)
- かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。
-
-
- なお、ns は「名前空間」を示す変数である。Rust
- における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この
- if
- は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。
-
-
- 重要なのは、これが resolve_ident_in_lexical_scope()
- の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。
-
-
- 動作がわかったところで、例として次のコードを考える。
-
-
-
-
-
- ここで main() の bool は struct bool
- として解決される。なぜなら、プリミティブ型の判定をする前に bool
- という名前の別の型が見つかるからだ。
-
-
-
- まとめ
-
- Rust
- のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。
-
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj
new file mode 100644
index 00000000..37af679a
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj
@@ -0,0 +1,126 @@
+---
+[article]
+uuid = "44171f75-c312-4c92-9927-3d260e162175"
+title = "【Vim】 autocmd events の BufWrite/BufWritePre の違い"
+description = "Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。"
+tags = [
+ "vim",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/79ab4db8564032de0b25
+:::
+
+{#tl-dr}
+# TL; DR
+
+違いはない。ただのエイリアス。
+
+{#code-reading}
+# 調査記録
+
+Vim の autocmd events には似通った名前のものがいくつかある。大抵は
+`:help`
+に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。
+
+* `BufRead`/`BufReadPost`
+* `BufWrite`/`BufWritePre`
+* `BufAdd`/`BufCreate`
+
+このうち、`BufAdd`/`BufCreate` に関しては、`:help BufCreate` に
+
+> The BufCreate event is for historic reasons.
+
+とあり、おそらくは `BufAdd`
+のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため
+vim と neovim のソースコードを調査した。
+
+ソースコードへのリンク
+
+* [vim (調査時点での master branch)](https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2)
+* [neovim (上に同じ)](https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3)
+
+{#vim}
+## vim のソースコード
+
+以下は、autocmd events
+の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。
+
+https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86
+
+```c
+{"BufAdd", EVENT_BUFADD},
+{"BufCreate", EVENT_BUFADD},
+```
+
+https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97
+
+```c
+{"BufRead", EVENT_BUFREADPOST},
+{"BufReadCmd", EVENT_BUFREADCMD},
+{"BufReadPost", EVENT_BUFREADPOST},
+```
+
+https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
+
+```c
+{"BufWrite", EVENT_BUFWRITEPRE},
+{"BufWritePost", EVENT_BUFWRITEPOST},
+{"BufWritePre", EVENT_BUFWRITEPRE},
+```
+
+{#neovim}
+## neovim のソースコード
+
+neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua
+で書かれている。以下にある通り、はっきり `aliases` と書かれている。
+
+https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124
+
+```lua
+aliases = {
+ BufCreate = 'BufAdd',
+ BufRead = 'BufReadPost',
+ BufWrite = 'BufWritePre',
+ FileEncoding = 'EncodingChanged',
+},
+```
+
+ところで、上では取り上げなかった `FileEncoding` だが、これは
+`:help FileEncoding` にしっかりと書いてある。
+
+```
+ *FileEncoding*
+FileEncoding Obsolete. It still works and is equivalent
+ to |EncodingChanged|.
+```
+
+{#outro}
+# まとめ
+
+記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。
+
+* `BufAdd`/`BufCreate`
+
+ * → `BufCreate` は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (`BufAdd`) を使う
+
+* `BufRead`/`BufReadPost`
+
+ * → `BufReadPre` との対称性のため、あるいは `BufWritePost` との対称性のため `BufReadPost` を使う
+
+* `BufWrite`/`BufWritePre`
+
+ * → `BufWritePost` との対称性のため、あるいは `BufReadPre` との対称性のため `BufWritePre` を使う
+
+* `FileEncoding`/`EncodingChanged`
+
+ * → `FileEncoding` は "Obsolete" と明言されているので、`EncodingChanged` を使う
+
+ところでこの調査で知ったのだが、`BufRead` と `BufWrite`
+は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら
+`Pre`/`Post` 付きのものを使った方が分かりやすいだろう。
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc
deleted file mode 100644
index 705cf6c4..00000000
--- a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc
+++ /dev/null
@@ -1,162 +0,0 @@
----
-[article]
-uuid = "44171f75-c312-4c92-9927-3d260e162175"
-title = "【Vim】 autocmd events の BufWrite/BufWritePre の違い"
-description = "Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。"
-tags = [
- "vim",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/79ab4db8564032de0b25
-
-
- TL; DR
-
- 違いはない。ただのエイリアス。
-
-
-
- 調査記録
-
- Vim の autocmd events には似通った名前のものがいくつかある。大抵は
- :help
- に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。
-
-
- BufRead/BufReadPost
- BufWrite/BufWritePre
- BufAdd/BufCreate
-
-
- このうち、BufAdd/BufCreate に関しては、:help BufCreate に
-
-
-
- The BufCreate event is for historic reasons.
-
-
-
- とあり、おそらくは BufAdd
- のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため
- vim と neovim のソースコードを調査した。
-
-
-
- ソースコードへのリンク
- vim (調査時点での master branch)
- neovim (上に同じ)
-
-
-
- vim のソースコード
-
- 以下は、autocmd events
- の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。
-
-
- https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86
-
-
-
-
-
- https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97
-
-
-
-
-
- https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
-
-
-
-
-
-
- neovim のソースコード
-
- neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua
- で書かれている。以下にある通り、はっきり aliases と書かれている。
-
-
- https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124
-
-
-
-
-
- ところで、上では取り上げなかった FileEncoding だが、これは
- :help FileEncoding にしっかりと書いてある。
-
-
-
-
-
-
-
- まとめ
-
- 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。
-
-
- -
-
BufAdd/BufCreate
-
- - →
BufCreate は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (BufAdd) を使う
-
-
- -
-
BufRead/BufReadPost
-
- - →
BufReadPre との対称性のため、あるいは BufWritePost との対称性のため BufReadPost を使う
-
-
- -
-
BufWrite/BufWritePre
-
- - →
BufWritePost との対称性のため、あるいは BufReadPre との対称性のため BufWritePre を使う
-
-
- -
-
FileEncoding/EncodingChanged
-
- - →
FileEncoding は `Obsolete'' と明言されているので、`EncodingChanged を使う
-
-
-
-
- ところでこの調査で知ったのだが、BufRead と BufWrite
- は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら
- Pre/Post 付きのものを使った方が分かりやすいだろう。
-
-
-
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj
new file mode 100644
index 00000000..5de1075d
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj
@@ -0,0 +1,161 @@
+---
+[article]
+uuid = "665de47e-0ed6-405e-ad30-81c3c4592d45"
+title = "Vimで選択した行の順番を入れ替える"
+description = "Vim で選択した行の順番を入れ替える方法。"
+tags = [
+ "vim",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+::: note
+この記事は Qiita から移植してきたものです。
+元 URL: https://qiita.com/nsfisis/items/4fefb361d9a693803520
+:::
+
+{#tl-dr}
+# TL; DR
+
+```vim
+" License: Public Domain
+
+command! -bar -range=%
+ \ Reverse
+ \ keeppatterns ,g/^/m-1
+```
+
+{#version}
+# バージョン情報
+
+`:version` の一部
+
+> VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11\:30\:30) macOS
+> version Included patches: 1-148 Huge version without GUI.
+
+{#existing-solution}
+# よく紹介されている手法
+
+{#external-commands}
+## `tac` / `tail`
+
+`tac` や `tail -r` などの外部コマンドを `!`
+を使って呼び出し、置き換える。
+
+> :h v_!
+
+`tac` コマンドや `tail` の `-r`
+オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい
+
+{#global-command}
+## `:g/^/m0`
+
+こちらは外部コマンドに頼らず、Vim の機能のみを使う。`g` は `:global`
+コマンドの、`m` は `:move` コマンドの略
+
+`:global` コマンドは `:[range]global/{pattern}/[command]`
+のように使い、`[range]` で指定された範囲の行のうち、`{pattern}`
+で指定された検索パターンにマッチする行に対して、順番に `[command]`
+で指定された Ex コマンドを呼び出す。
+
+> :h :global
+
+`:move` コマンドは `[range]:move {address}` のように使い、`[range]`
+で指定された範囲の行を `{address}` で指定された位置に移動させる。
+
+> :h :move
+
+`:g/^/m0` のように組み合わせると、「すべての行を1行ずつ
+0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。
+
+なお、`:g/^/m0` は全ての行を入れ替えるが、`:N,Mg/^/mN-1` とすることで
+N行目から
+M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。
+
+```vim
+command! -bar -range=%
+ \ Reverse
+ \ ,g/^/m-1
+```
+
+これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。
+
+{#problem-of-global-command}
+# `:g/^/m0` の問題点
+
+`:global`
+コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。`^`
+は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。`'hlsearch'`
+オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと
+`n` コマンドなどの際に不便である。
+
+> :h @/
+
+{#solution}
+# 解決策
+
+::: note
+[2020/9/28追記] より簡潔な方法を見つけたので次節に追記した
+:::
+
+前述した `:Reverse` コマンドの定義を少し変えて、次のようにする:
+
+```vim
+function! s:reverse_lines(from, to) abort
+ execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1)
+endfunction
+
+command! -bar -range=%
+ \ Reverse
+ \ call reverse_lines(, )
+```
+
+実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。
+
+この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが
+`^` で上書きされることがなくなる。
+
+Vim のヘルプから該当箇所を引用する (強調は筆者による)。
+
+> :h autocmd-searchpat
+>
+> *Autocommands do not change the current search patterns.* Vim saves the
+> current search patterns before executing autocommands then restores them
+> after the autocommands finish. This means that autocommands do not
+> affect the strings highlighted with the 'hlsearch' option.
+
+これは autocommand
+の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは
+`:nohlsearch` のヘルプにある。同じく該当箇所を引用する
+(強調は筆者による)。
+
+> :h :nohlsearch
+>
+> (略) This command doesn’t work in an autocommand, because the
+> highlighting state is saved and restored when executing autocommands
+> |autocmd-searchpat|. *Same thing for when invoking a user function.*
+
+この仕様により、`:g/^/m0`
+の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。
+
+{#solution-revised}
+# 解決策 (改訂版)
+
+::: note
+[2020/9/28追記] より簡潔な方法を見つけたため追記する
+:::
+
+```vim
+command! -bar -range=%
+ \ Reverse
+ \ keeppatterns ,g/^/m-1
+```
+
+まさにこのための Exコマンド、`:keeppatterns`
+が存在する。`:keeppatterns {command}`
+のように使い、読んで字の如く、後ろに続く
+Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
+
+> :h :keeppatterns
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.ndoc b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.ndoc
deleted file mode 100644
index 149d038d..00000000
--- a/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.ndoc
+++ /dev/null
@@ -1,212 +0,0 @@
----
-[article]
-uuid = "665de47e-0ed6-405e-ad30-81c3c4592d45"
-title = "Vimで選択した行の順番を入れ替える"
-description = "Vim で選択した行の順番を入れ替える方法。"
-tags = [
- "vim",
-]
-
-[[article.revisions]]
-date = "2021-10-02"
-remark = "Qiita から移植"
----
-
-
- この記事は Qiita から移植してきたものです。
- 元 URL: https://qiita.com/nsfisis/items/4fefb361d9a693803520
-
-
- TL; DR
-
- ,g/^/m-1
- ]]>
-
-
-
- バージョン情報
-
- :version の一部
-
-
-
- VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS
- version Included patches: 1-148 Huge version without GUI.
-
-
-
-
- よく紹介されている手法
-
- tac / tail
-
- tac や tail -r などの外部コマンドを !
- を使って呼び出し、置き換える。
-
-
-
- :h v_!
-
-
-
- tac コマンドや tail の -r
- オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい
-
-
-
- :g/^/m0
-
- こちらは外部コマンドに頼らず、Vim の機能のみを使う。g は :global
- コマンドの、m は :move コマンドの略
-
-
- :global コマンドは :[range]global/{pattern}/[command]
- のように使い、[range] で指定された範囲の行のうち、{pattern}
- で指定された検索パターンにマッチする行に対して、順番に [command]
- で指定された Ex コマンドを呼び出す。
-
-
-
- :h :global
-
-
-
- :move コマンドは [range]:move {address} のように使い、[range]
- で指定された範囲の行を {address} で指定された位置に移動させる。
-
-
-
- :h :move
-
-
-
- :g/^/m0 のように組み合わせると、「すべての行を1行ずつ
- 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。
-
-
- なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで
- N行目から
- M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。
-
-
- ,g/^/m-1
- ]]>
-
-
- これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。
-
-
-
-
- :g/^/m0 の問題点
-
- :global
- コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。^
- は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。'hlsearch'
- オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと
- n コマンドなどの際に不便である。
-
-
-
- :h @/
-
-
-
-
- 解決策
-
-
- [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した
-
-
-
- 前述した :Reverse コマンドの定義を少し変えて、次のようにする:
-
-
- reverse_lines(, )
- ]]>
-
-
- 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。
-
-
- この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが
- ^ で上書きされることがなくなる。
-
-
- Vim のヘルプから該当箇所を引用する (強調は筆者による)。
-
-
-
- :h autocmd-searchpat
-
-
- Autocommands do not change the current search patterns. Vim saves the
- current search patterns before executing autocommands then restores them
- after the autocommands finish. This means that autocommands do not
- affect the strings highlighted with the `hlsearch' option.
-
-
-
- これは autocommand
- の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは
- :nohlsearch のヘルプにある。同じく該当箇所を引用する
- (強調は筆者による)。
-
-
-
- :h :nohlsearch
-
-
- (略) This command doesn’t work in an autocommand, because the
- highlighting state is saved and restored when executing autocommands
- |autocmd-searchpat|. Same thing for when invoking a user function.
-
-
-
- この仕様により、:g/^/m0
- の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。
-
-
-
- 解決策 (改訂版)
-
-
- [2020/9/28追記] より簡潔な方法を見つけたため追記する
-
-
-
- ,g/^/m-1
- ]]>
-
-
- まさにこのための Exコマンド、:keeppatterns
- が存在する。:keeppatterns {command}
- のように使い、読んで字の如く、後ろに続く
- Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
-
-
-
- :h :keeppatterns
-
-
-
-
diff --git a/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.dj b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.dj
new file mode 100644
index 00000000..e0fe0279
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.dj
@@ -0,0 +1,489 @@
+---
+[article]
+uuid = "f4985d54-0907-4449-8101-0fcd382f9e02"
+title = "PHPerKaigi 2022 トークン問題の解説"
+description = "PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。"
+tags = [
+ "conference",
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2022-04-09"
+remark = "公開"
+
+[[article.revisions]]
+date = "2022-04-16"
+remark = "2問目、3問目の解説を追加、1問目に加筆"
+---
+{#intro}
+# はじめに
+
+本日開始された [PHPerKaigi 2022](https://phperkaigi.jp/2022/) の PHPer
+チャレンジにおいて、弊社
+[デジタルサーカス株式会社](https://www.dgcircus.com/) の問題を
+3問作成した。この記事では、これらの問題の解説をおこなう。
+
+リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens
+
+{#q1-brainfuck}
+# 第1問 brainf_ck.php
+
+ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
+
+```php
+ (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
+$id = \spl_object_id(...);
+$put = fn($c) => \printf('%c', $c);
+$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
+
+$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
+$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
+$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
+$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
+$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
+$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+ +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+ $b => $loop(++$pc, ++$n),
+ $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
+ default => $loop(++$pc, $n),
+ })($pc, -![])],
+ default => [$mp, ++$pc],
+};
+$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+ +!![] => [$mp, ++$pc],
+ default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+ $e => $loop(--$pc, ++$n),
+ $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
+ default => $loop(--$pc, $n),
+ })($pc, -![])],
+};
+$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
+ isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
+)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
+
+$🐘([
+ $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+ $🤡,
+ $👉, $👍, $👍, $👍,
+ $👉, $👍, $👍, $👍, $👍, $👍,
+ $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+ $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+ $👈, $👈, $👈, $👈, $👎,
+ $🎪,
+ $👉, $👍, $👍, $👍, $👍, $👍, $📝,
+ $👎, $👎, $📝,
+ $👉, $👎, $👎, $👎, $📝,
+ $👉, $👎, $👎, $👎, $📝,
+ $👎, $👎, $📝,
+ $👎, $📝,
+ $👈, $📝,
+ $👉, $👉, $👎, $👎, $📝,
+ $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
+ $👈, $👎, $👎, $👎, $👎, $📝,
+ $👈, $📝,
+ $👉, $👍, $👍, $📝,
+ $👉, $👎, $📝,
+ $👈, $📝,
+]);
+```
+
+この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
+
+{#commentary}
+## 解説
+
+{#emoji}
+### 絵文字
+
+まず目につくのは大量の絵文字だろう。 PHP
+は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
+
+{#brainfuck}
+### プログラム全体
+
+Brainf\*ck のインタプリタとプログラムになっている。 Brainf\*ck
+とは、難解プログラミング言語のひとつであり、ここで説明するよりも
+Wikipedia の該当ページを読んだ方がよい。
+
+https://ja.wikipedia.org/wiki/Brainfuck
+
+なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。
+
+```
++ + + + + + + + + +
+[
+ > + + +
+ > + + + + +
+ > + + + + + + + + + + + +
+ > + + + + + + + + + +
+ < < < < -
+]
+> + + + + + .
+- - .
+> - - - .
+> - - - .
+- - .
+- .
+< .
+> > - - .
++ + + + + + + .
+< - - - - .
+< .
+> + + .
+> - .
+< .
+```
+
+実行結果はこちら: https://ideone.com/22VWmb
+
+それぞれの絵文字で表された関数が、各命令に対応している。
+
+* `$👉`: `>`
+* `$👈`: `<`
+* `$👍`: `+`
+* `$👎`: `-`
+* `$📝`: `.`
+* `$🤡`: `[`
+* `$🎪`: `]`
+
+`,` (入力) に対応する関数はない
+(このプログラムでは使わないので用意していない)。
+
+なお、`$🐘` はいわゆる main 関数であり、プログラムの実行部分である。
+
+{#emoji-selection}
+### 絵文字の選択
+
+おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪`
+は弊社デジタルサーカスにちなんでいる。 また、`$🐘` は PHP
+のマスコットの象に由来する。
+
+{#strict-types}
+### strict_types
+
+`declare` 文の `strict_types` に指定できるのは、`0` か `1`
+の数値リテラルだが、 `0x0` や `0b1` のような値も受け付ける。 今回は、PHP
+8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。
+
+{#url}
+### URL
+
+ソースコードのライセンスを示したこの部分だが、
+
+```php
+https://creativecommons.org/publicdomain/zero/1.0/
+```
+
+完全に合法な PHP のコードである。 `https:` 部分はラベル、`//`
+以降は行コメントになっている。
+
+{#numbers}
+### リテラルなしで数値を生成する
+
+ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。
+PHP では、型変換を利用することで任意の整数を作り出すことができる。
+
+```php
+assert(0 === +!![]);
+assert(1 === +![]);
+assert(2 === ![]+![]);
+assert(3 === ![]+![]+![]);
+assert(10 === +(![].+!![]));
+```
+
+`[]` に `!` を適用すると `true` が返ってくる。それに `+`
+を適用すると、`bool` から `int` ヘの型変換が走り、`1` が生成される。`10`
+はさらにトリッキーだ。まず `1` と `0` を作り、`.` で文字列として結合する
+(`'10'`)。これに `+` を適用すると、`string` から `int`
+への型変換が走り、`10` が生まれる (コード量に頓着しないなら、`1` を 10
+個足し合わせてももちろん 10 が作れる)。
+
+また、`error_reporting` に指定しているのは `-1` である。 これは、`!`
+によって文字列を `false` にし、`+` によって `false` を `0`
+にし、さらにビット反転して `-1` にしている。
+
+{#conditionals}
+### `if` 文なしで条件分岐
+
+三項演算子ないし `match` 式を使うことで、`if`
+を一切書かずに条件分岐ができる。 また、`&&` / `||` も使えることがある。
+遅延評価が不要なケースでは、`[$t, $f][$cond]`
+のような形で分岐することもできる。
+
+{#loops}
+### `while`、`for` 文なしでループ
+
+不動点コンビネータを使って無名再帰する
+(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に
+Z コンビネータとして知られるものを使った (`$z`)。
+
+実際のところ、`$🤡` や `$🎪`、`$🐘` は、一度 Scheme (Lisp の一種)
+で書いてから PHP に翻訳する形で記述した。
+
+なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは)
+ので、 あまりに長い brainf*ck
+プログラムを書くとスタックオーバーフローする。
+
+{#q2-riddle}
+# 第2問 riddle.php
+
+ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
+
+```php
+ vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
+cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
+Q;
+$s = str_rot13($s); $xs = [
+0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
+0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
+0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
+];
+$t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
+$t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
+$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
+printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
+```
+
+コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
+
+```shell-session
+$ php toquine.php | php | php | php | ...
+```
+
+実際にはもう少しパイプで繋げなければならない。
+
+{#commentary}
+## 解説
+
+{#quine}
+### プログラム全体
+
+コメントにもあるとおり、これは quine (風) のプログラムになっている。
+Quine
+とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。
+
+このプログラムは、実行すると自身とほとんど同じプログラムを出力する。
+異なるのはトークンになっている部分のみである。
+
+{#tokens}
+### トークン
+
+`$xs` がトークンに対応している。変換のロジックは `riddle.php`
+とほぼ同じなので省略する。
+
+{#states}
+### 状態保持
+
+トークンの何文字目まで出力したかを、ソースコードを変えずに (quine
+なので) 覚えておく必要がある。
+このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__`
+から情報を取得している。
+
+{#rot-13}
+### ROT 13
+
+Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。
+これがあまり美しくないので、`toquine.php` では、ROT 13
+変換を使って難読化した。
+
+それにしてもなぜこんなものが標準ライブラリに……。
+
+{#outro}
+# おわりに
+
+解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
+
+今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、
+来年は 5問、より面白い問題を持っていきます。
+
+実はもう作りはじめているので、どうか来年もありますように……。
diff --git a/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.ndoc b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.ndoc
deleted file mode 100644
index c0540482..00000000
--- a/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.ndoc
+++ /dev/null
@@ -1,560 +0,0 @@
----
-[article]
-uuid = "f4985d54-0907-4449-8101-0fcd382f9e02"
-title = "PHPerKaigi 2022 トークン問題の解説"
-description = "PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。"
-tags = [
- "conference",
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2022-04-09"
-remark = "公開"
-
-[[article.revisions]]
-date = "2022-04-16"
-remark = "2問目、3問目の解説を追加、1問目に加筆"
----
-
-
- はじめに
-
- 本日開始された PHPerKaigi 2022 の PHPer
- チャレンジにおいて、弊社
- デジタルサーカス株式会社 の問題を
- 3問作成した。この記事では、これらの問題の解説をおこなう。
-
-
- リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens
-
-
-
- 第1問 brainf_ck.php
-
- ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
-
-
- (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
- $id = \spl_object_id(...);
- $put = fn($c) => \printf('%c', $c);
- $mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
-
- $👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
- $👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
- $👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
- $👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
- $📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
- $🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
- +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
- $b => $loop(++$pc, ++$n),
- $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
- default => $loop(++$pc, $n),
- })($pc, -![])],
- default => [$mp, ++$pc],
- };
- $🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
- +!![] => [$mp, ++$pc],
- default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
- $e => $loop(--$pc, ++$n),
- $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
- default => $loop(--$pc, $n),
- })($pc, -![])],
- };
- $🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
- isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
- )($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
-
- $🐘([
- $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
- $🤡,
- $👉, $👍, $👍, $👍,
- $👉, $👍, $👍, $👍, $👍, $👍,
- $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
- $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
- $👈, $👈, $👈, $👈, $👎,
- $🎪,
- $👉, $👍, $👍, $👍, $👍, $👍, $📝,
- $👎, $👎, $📝,
- $👉, $👎, $👎, $👎, $📝,
- $👉, $👎, $👎, $👎, $📝,
- $👎, $👎, $📝,
- $👎, $📝,
- $👈, $📝,
- $👉, $👉, $👎, $👎, $📝,
- $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
- $👈, $👎, $👎, $👎, $👎, $📝,
- $👈, $📝,
- $👉, $👍, $👍, $📝,
- $👉, $👎, $📝,
- $👈, $📝,
- ]);
- ]]>
-
-
- この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
-
-
-
-
- 第2問 riddle.php
-
- ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
-
-
-
-
-
- さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。
- トークンを得るためには、ソースコードを読み、定数 N
- を特定する必要がある。
-
-
- ここでは、私の想定解を解説する。
-
-
- 読解
-
- まずはソースコードを読んでいく。
-
-
-
-
-
- 数値からなる $token があり、各要素をループしている。
-
-
-
-
-
- まずは排他的論理和 (xor) を取り、
-
-
-
-
-
- 二進数に変換して、
-
-
-
-
-
- 0 を空白に、1 を # にし、
-
-
-
-
-
- 5文字ごとに区切ったあと、改行で結合している。
-
-
-
- ヒント
-
- 次に、ソースコードに書いてあるヒントを読んでいく。
-
-
- N それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている
- $token の各要素は、1文字を表す
- - 1文字は 5x5 のセルからなる
- - 出力されるのは、完全な PHPer トークンである
-
-
- ここで、PHPer トークンは必ず # 記号から始まることを思いだすと、
- $token の最初の数字 0x14B499C は、変換の結果 #
- になるのではないかと予想される (なお、このことは、リポジトリの README
- ファイルに追加ヒントとして書かれている)。
-
-
-
- 解く
-
- ここまでわかれば、あと一歩で解ける。すなわち、0x14B499C が #
- に変換されるような N を見つければよい。
-
-
- N は高々
-
-
-
-
-
- なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
-
-
-
-
-
- この一連の変換に対する逆変換を考えると、次のようになる。
-
-
-
-
-
- これを実行すると、N が得られる。
-
-
-
-
- 第3問 toquine.php
-
- ソースコードはこちら。
-
-
- vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
- cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
- Q;
- $s = str_rot13($s); $xs = [
- 0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
- 0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
- 0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
- ];
- $t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
- $t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
- $ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
- printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
- ]]>
-
-
- コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
-
-
-
-
-
- 実際にはもう少しパイプで繋げなければならない。
-
-
-
-
- おわりに
-
- 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
-
-
- 今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、
- 来年は 5問、より面白い問題を持っていきます。
-
-
- 実はもう作りはじめているので、どうか来年もありますように……。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj
new file mode 100644
index 00000000..433ad383
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj
@@ -0,0 +1,97 @@
+---
+[article]
+uuid = "42cf2829-b897-4748-bc22-80dd734a3c09"
+title = "term-banner: ターミナルにバナーを表示するツールを書いた"
+description = "ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。"
+tags = []
+
+[[article.revisions]]
+date = "2022-04-24"
+remark = "公開"
+
+[[article.revisions]]
+date = "2022-04-27"
+remark = "-f オプションについて追記"
+---
+{#intro}
+# はじめに
+
+こんなものを作った。
+
+```
+$ term-banner 'Hello, World!' 'こんにちは、' '世界!'
+```
+
+
+
+コマンドライン引数として渡した文字列をターミナルに大きく表示する。
+
+リポジトリはこちら: https://github.com/nsfisis/term-banner
+
+{#motivation}
+# Motivation
+
+以前、[`big-clock-mode`](https://github.com/nsfisis/big-clock-mode)
+という似たようなプログラムを書いた。 これは tmux の `:clock-mode`
+コマンドに着想を得たもので、`:clock-mode`
+よりも大きく現在時刻を表示する。
+
+`big-clock-mode`
+を開発したのは、次のようなシチュエーションで使うためである。
+弊社では現在リモートワークが基本だが、web
+会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。
+こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。
+
+それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。
+
+しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。
+どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。
+
+そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。
+まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。
+
+{#program}
+# プログラム
+
+全体の流れは次のようになっている。
+
+1. フォントファイルを読み込む
+1. コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS 基準で並んでいるため)
+1. 1文字ずつレンダリングしていく
+
+`big-clock-mode` が Go 製なので、今回も Go で書いた。 PNG
+が標準ライブラリにあったり、Shift-JIS
+のエンコーディングが準標準ライブラリにあったりしたのは助かった。
+
+フォントファイルは `go:embed`
+で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。
+仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。
+
+{#font}
+# フォント
+
+フリーの 8x8
+ビットマップフォントである、 [美咲フォント 2021-05-05a 版](https://littlelimit.net/misaki.htm) を使わせていただいた。
+
+はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。
+同じく 8x8
+で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。
+
+美咲フォントは、平仮名・片仮名に留まらず、JIS
+第一・第二水準の漢字までサポートしている。
+第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。
+
+さらに言うと、実のところ美咲フォントは実サイズ 7x7
+で作られており、余白が設けられている。
+これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。
+おかげでコーディングまで楽になった。
+
+ゴシック体と明朝体があったが、私の好みで明朝体の方にした。
+ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。
+
+2022-04-27 追記: `-f` オプションで選べるようにした。
+
+{#outro}
+# おわりに
+
+あなたもターミナルに住んでみませんか?
diff --git a/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.ndoc b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.ndoc
deleted file mode 100644
index 1369a265..00000000
--- a/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.ndoc
+++ /dev/null
@@ -1,119 +0,0 @@
----
-[article]
-uuid = "42cf2829-b897-4748-bc22-80dd734a3c09"
-title = "term-banner: ターミナルにバナーを表示するツールを書いた"
-description = "ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。"
-tags = []
-
-[[article.revisions]]
-date = "2022-04-24"
-remark = "公開"
-
-[[article.revisions]]
-date = "2022-04-27"
-remark = "-f オプションについて追記"
----
-
-
- はじめに
-
- こんなものを作った。
-
-
-
-
-
-
- コマンドライン引数として渡した文字列をターミナルに大きく表示する。
-
-
- リポジトリはこちら: https://github.com/nsfisis/term-banner
-
-
-
- Motivation
-
- 以前、big-clock-mode
- という似たようなプログラムを書いた。 これは tmux の :clock-mode
- コマンドに着想を得たもので、:clock-mode
- よりも大きく現在時刻を表示する。
-
-
- big-clock-mode
- を開発したのは、次のようなシチュエーションで使うためである。
- 弊社では現在リモートワークが基本だが、web
- 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。
- こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。
-
-
- それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。
-
-
- しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。
- どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。
-
-
- そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。
- まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。
-
-
-
- プログラム
-
- 全体の流れは次のようになっている。
-
-
- - フォントファイルを読み込む
- - コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS 基準で並んでいるため)
- - 1文字ずつレンダリングしていく
-
-
- big-clock-mode が Go 製なので、今回も Go で書いた。 PNG
- が標準ライブラリにあったり、Shift-JIS
- のエンコーディングが準標準ライブラリにあったりしたのは助かった。
-
-
- フォントファイルは go:embed
- で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。
- 仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。
-
-
-
- フォント
-
- フリーの 8x8
- ビットマップフォントである、美咲フォント 2021-05-05a 版 を使わせていただいた。
-
-
- はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。
- 同じく 8x8
- で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。
-
-
- 美咲フォントは、平仮名・片仮名に留まらず、JIS
- 第一・第二水準の漢字までサポートしている。
- 第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。
-
-
- さらに言うと、実のところ美咲フォントは実サイズ 7x7
- で作られており、余白が設けられている。
- これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。
- おかげでコーディングまで楽になった。
-
-
- ゴシック体と明朝体があったが、私の好みで明朝体の方にした。
- ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。
-
-
- 2022-04-27 追記: -f オプションで選べるようにした。
-
-
-
- おわりに
-
- あなたもターミナルに住んでみませんか?
-
-
-
diff --git a/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.dj b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.dj
new file mode 100644
index 00000000..6758f265
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.dj
@@ -0,0 +1,132 @@
+---
+[article]
+uuid = "9211e1fe-bca3-43c4-ba4e-c67d62f3fed0"
+title = "PHPerKaigi 2022"
+description = "2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2022-05-01"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2022-04-09 から 2022-04-11 にかけて開催された、 [PHPerKaigi 2022](https://phperkaigi.jp/2022/) に、
+一般参加者として参加した。
+弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) はダイヤモンドスポンサーとなっており、
+スポンサー枠のチケットを使わせていただいた。
+
+昨年のレポートは [こちら](/posts/2021-03-30/phperkaigi-2021) 。
+
+{#comments}
+# 感想
+
+{#great-sessions}
+## 厳選おすすめトーク
+
+多くの素晴らしいトークの中から、特におすすめのものを 5つ選んだ。
+是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。
+
+[予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント](https://fortee.jp/phperkaigi-2022/proposal/ef8cf4ed-63fe-42f8-8145-b3e70054458b)
+
+> PHP はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。
+>
+> 本講演では PHP 8.1 をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。
+
+[PHPのエラーを理解して適切なエラーハンドリングを学ぼう](https://fortee.jp/phperkaigi-2022/proposal/db00d49e-0dd6-453f-b54b-f731d112f10e)
+
+> PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice 理解していますか?
+>
+> これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!
+>
+> またそれらを理解した上でのエラーハンドリングを学びましょう。
+
+[エラー監視とテスト体制への改善作戦](https://fortee.jp/phperkaigi-2022/proposal/4a7e3ded-9134-4919-955c-ec7bf4491c0d)
+
+> 毎日流れてくるエラーに皆さんはどう向き合ってますか?
+>
+> エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。
+>
+> サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、
+>
+> エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。
+
+[ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか](https://fortee.jp/phperkaigi-2022/proposal/6f47daf8-c78f-4fb1-9b99-e9656e6fe7f7)
+
+> 昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。
+>
+> 最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には
+>
+> * 「(私の思う)良い設計」を実現するための意思決定
+> * 「ISUCONの問題」という位置付けに由来する取捨選択
+> * 移植中に遭遇したトラブルとその解決策
+>
+> といった文脈や葛藤が存在しています。
+>
+> 本発表はそれらを共有することで
+>
+> * PHPアプリケーションの設計、実装事例として役立ててもらう
+> * ISUCONの言語移植に興味を持ってもらう
+> * ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう
+>
+> ことを目的とします。
+
+[チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた](https://fortee.jp/phperkaigi-2022/proposal/5a260e4e-542d-4d82-849d-ef3d6cb7c854)
+
+> サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。
+>
+> フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。
+>
+> このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。
+
+{#token-quizzes}
+## トークン問題の作成
+
+今回は、PHPer チャレンジ用に弊社のトークン問題を 3題作成した。
+こちらについては [別途記事にしている](/posts/2022-04-09/phperkaigi-2022-tokens) ので、そちらを参照されたい。
+
+{#phper-challenge}
+## PHPer チャレンジ
+
+[1位](https://fortee.jp/phperkaigi-2022/challenge) になった。
+また、賞品として [Echo Show 15](https://www.amazon.co.jp/dp/B08MQNJC9Z) をいただいた。
+
+{#conference}
+## カンファレンス全体への感想
+
+[去年の参加レポ](/posts/2021-03-30/phperkaigi-2021) では、こんなことを書いた。
+
+> 1つ個人的な反省点としては、(中略) Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、
+> 後から見返せる発表やスライドに注力してしまった、ということだ。
+> 発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。
+> まあ初カンファレンスだし、とお茶を濁しておこう。
+
+この反省を踏まえ、今年は積極的にほかの場 (公式の Discord サーバや、アンカンファレンス) にも参加した。
+これにより、参加体験の質がはるかに向上した。特に Discord に関しては、登壇者ご本人による補足や、
+質問への回答などがおこなわれる (ことが多い) ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。
+
+なお、アンカンファレンスについては、1日目の終わりに [トークン問題の解説放送](https://fortee.jp/phperkaigi-2022/unconference/view/d332797a-8921-4706-a7e2-ee72640c9b5e) もおこなった。
+
+また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。
+今年は 3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。
+
+{#next-year}
+# そして来年へ……?
+
+PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。
+
+* プロポーザルを出す
+* PHPer チャレンジのトークン問題を 5題作成する
+* 現地に行く
+* PHPer チャレンジで圧勝する
+
+----------------
+
+最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。
+
+ではまた来年。
diff --git a/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.ndoc b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.ndoc
deleted file mode 100644
index 0e1a6bc8..00000000
--- a/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.ndoc
+++ /dev/null
@@ -1,166 +0,0 @@
----
-[article]
-uuid = "9211e1fe-bca3-43c4-ba4e-c67d62f3fed0"
-title = "PHPerKaigi 2022"
-description = "2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。"
-tags = [
- "conference",
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2022-05-01"
-remark = "公開"
----
-
-
- はじめに
-
- 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に、
- 一般参加者として参加した。
- 弊社デジタルサーカス株式会社はダイヤモンドスポンサーとなっており、
- スポンサー枠のチケットを使わせていただいた。
-
-
- 昨年のレポートはこちら。
-
-
-
-
- そして来年へ……?
-
- PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。
-
-
- - プロポーザルを出す
- - PHPer チャレンジのトークン問題を 5題作成する
- - 現地に行く
- - PHPer チャレンジで圧勝する
-
-
-
- 最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。
-
-
- ではまた来年。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj
new file mode 100644
index 00000000..5701fe4d
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj
@@ -0,0 +1,99 @@
+---
+[article]
+uuid = "bb71bb5d-361b-44cb-9753-81d14583d860"
+title = "PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた"
+description = "PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。"
+tags = [
+ "conference",
+ "php",
+ "phpconokinawa",
+]
+
+[[article.revisions]]
+date = "2022-08-27"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+本日 [PHP カンファレンス沖縄 2022](https://phpcon.okinawa.jp/) が開催された (らしい)。
+
+カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。
+
+* ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
+* スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3
+
+{#solution}
+# 解
+
+細かいレギュレーションは不明だったので、勝手に定めた。
+
+* コマンドライン引数の第1引数で受けとる
+* 結果は標準出力に出す
+* コンマの直後にはスペースを1つ置く
+* 末尾コンマは禁止
+* 数字でないものは入ってこないものとする
+* 負数は入ってこないものとする
+
+書いたものがこちら:
+
+```php
+[=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
+```
+
+しめて 123 バイトとなった (末尾改行を含めずにカウント)。
+
+こちらは改行とスペースを追加したバージョン:
+
+```php
+[= $x; $n -= $x)
+ $r[] = $x;
+echo implode(', ', $r ?? []);
+
+?>]
+```
+
+{#techniques}
+# 使用したテクニック
+
+{#exponential-notation}
+## 指数表記
+
+割と多くの言語のゴルフで使えるテクニック。
+`e` を用いた指数表記で、大きな数を短く表す。
+このコードでは `10000`、`5000`、`2000`、`1000` を指数表記している。
+
+{#shorten-loop}
+## foreach や for の中身を1つの文に
+
+`foreach`、`for`、`if` などの後ろには、
+通常 `{` を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、`{` と `}` を省略できる。
+C言語などでも使える。
+
+{#omit-initialization}
+## $r に初期値を入れない
+
+PHP では、`$r[] = ......` のような配列の末尾に追加する式を実行したとき、
+`$r` が未定義だった場合は `$r` を勝手に定義して空の配列で初期化してくれる。
+これを利用すると、`$r = [];` のような初期化が不要になる。
+
+ただし、プログラムに 0 が渡されるとループを一度も回らないので、`$r` が未定義になってしまい、
+`implode()` に渡すところでエラーになる。
+それを防ぐために `$r ?? []` を使っている。
+
+もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。
+
+{#put-text-outside-php-tag}
+## PHP タグの外に文字列を置く
+
+PHP では、`<?php` `?>` で囲われた部分の外側にある文字列は、そのまま出力される。
+今回のケースでは、先頭と末尾に必ず `[` と `]` を出力するので、そのまま書いてやればよい。
+
+{#outro}
+# おわりに
+
+最後になりましたが、 [めもりー](https://twitter.com/m3m0r7) さん、楽しい問題をありがとうございました。
diff --git a/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.ndoc b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.ndoc
deleted file mode 100644
index 056ac438..00000000
--- a/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.ndoc
+++ /dev/null
@@ -1,119 +0,0 @@
----
-[article]
-uuid = "bb71bb5d-361b-44cb-9753-81d14583d860"
-title = "PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた"
-description = "PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。"
-tags = [
- "conference",
- "php",
- "phpconokinawa",
-]
-
-[[article.revisions]]
-date = "2022-08-27"
-remark = "公開"
----
-
-
- はじめに
-
- 本日 PHP カンファレンス沖縄 2022 が開催された (らしい)。
-
-
- カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。
-
-
- - ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
- - スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3
-
-
-
- 解
-
- 細かいレギュレーションは不明だったので、勝手に定めた。
-
-
- - コマンドライン引数の第1引数で受けとる
- - 結果は標準出力に出す
- - コンマの直後にはスペースを1つ置く
- - 末尾コンマは禁止
- - 数字でないものは入ってこないものとする
- - 負数は入ってこないものとする
-
-
- 書いたものがこちら:
-
-
- =$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
- ]]>
-
-
- しめて 123 バイトとなった (末尾改行を含めずにカウント)。
-
-
- こちらは改行とスペースを追加したバージョン:
-
-
- = $x; $n -= $x)
- $r[] = $x;
- echo implode(', ', $r ?? []);
-
- ?>]
- ]]>
-
-
-
- 使用したテクニック
-
- 指数表記
-
- 割と多くの言語のゴルフで使えるテクニック。
- e を用いた指数表記で、大きな数を短く表す。
- このコードでは 10000、5000、2000、1000 を指数表記している。
-
-
-
- foreach や for の中身を1つの文に
-
- foreach、for、if などの後ろには、
- 通常 { を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、{ と } を省略できる。
- C言語などでも使える。
-
-
-
- $r に初期値を入れない
-
- PHP では、$r[] = ...... のような配列の末尾に追加する式を実行したとき、
- $r が未定義だった場合は $r を勝手に定義して空の配列で初期化してくれる。
- これを利用すると、$r = []; のような初期化が不要になる。
-
-
- ただし、プログラムに 0 が渡されるとループを一度も回らないので、$r が未定義になってしまい、
- implode() に渡すところでエラーになる。
- それを防ぐために $r ?? [] を使っている。
-
-
- もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。
-
-
-
- PHP タグの外に文字列を置く
-
- PHP では、<?php ?> で囲われた部分の外側にある文字列は、そのまま出力される。
- 今回のケースでは、先頭と末尾に必ず [ と ] を出力するので、そのまま書いてやればよい。
-
-
-
-
- おわりに
-
- 最後になりましたが、めもりーさん、楽しい問題をありがとうございました。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj
new file mode 100644
index 00000000..1ba5891b
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj
@@ -0,0 +1,52 @@
+---
+[article]
+uuid = "cd16debe-8077-4edf-aec0-b1d45955a0e2"
+title = "弊社の PHP Foundation への寄付に寄せて"
+description = "先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。"
+tags = []
+
+[[article.revisions]]
+date = "2022-08-31"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+*注: これは私個人の意見であり、所属する組織を代表するものではありません。*
+
+先日、私の勤める [デジタルサーカス株式会社](https://www.dgcircus.com/) が
+[PHP Foundation](https://opencollective.com/phpfoundation) へ $2,000 の寄付をおこないました。
+
+記事: https://www.dgcircus.com/news/581
+
+本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。
+
+{#why}
+# なぜ?
+
+組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。
+
+当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:
+
+> 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、
+> ↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、
+> 寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか
+> (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。
+>
+> 追記: 「肩身が狭くなる」というのがより適切でした。
+
+※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。
+
+OSS を金銭的に支援したり、技術カンファレンスへ協賛したり
+(あるいは [CTO](https://twitter.com/tomzoh) がカンファレンスを年2で主催したり:
+[iOSDC](https://iosdc.jp) [PHPerKaigi](https://phperkaigi.jp) )
+といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。
+これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。
+
+以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。
+
+{#outro}
+# おわりに
+
+最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。
+この場を借りて感謝申し上げます。
diff --git a/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.ndoc b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.ndoc
deleted file mode 100644
index cbc99969..00000000
--- a/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.ndoc
+++ /dev/null
@@ -1,69 +0,0 @@
----
-[article]
-uuid = "cd16debe-8077-4edf-aec0-b1d45955a0e2"
-title = "弊社の PHP Foundation への寄付に寄せて"
-description = "先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。"
-tags = []
-
-[[article.revisions]]
-date = "2022-08-31"
-remark = "公開"
----
-
-
- はじめに
-
- 注: これは私個人の意見であり、所属する組織を代表するものではありません。
-
-
- 先日、私の勤める デジタルサーカス株式会社 が
- PHP Foundation へ $2,000 の寄付をおこないました。
-
-
- 記事: https://www.dgcircus.com/news/581
-
-
- 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。
-
-
-
- なぜ?
-
- 組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。
-
-
- 当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:
-
-
-
- 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、
- ↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、
- 寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか
- (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。
-
-
- 追記: 「肩身が狭くなる」というのがより適切でした。
-
-
-
- ※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。
-
-
- OSS を金銭的に支援したり、技術カンファレンスへ協賛したり
- (あるいは CTO がカンファレンスを年2で主催したり:
- iOSDC PHPerKaigi)
- といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。
- これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。
-
-
- 以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。
-
-
-
- おわりに
-
- 最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。
- この場を借りて感謝申し上げます。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj b/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj
new file mode 100644
index 00000000..c23341dc
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj
@@ -0,0 +1,617 @@
+---
+[article]
+uuid = "42f0b29b-1e44-4dbe-9864-69abe3bb1e6e"
+title = "【PHP】 fizzbuzz を書く。1行あたり2文字で。"
+description = "PHP で fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。"
+tags = [
+ "php",
+]
+
+[[article.revisions]]
+date = "2022-09-28"
+remark = "公開"
+
+[[article.revisions]]
+date = "2022-09-29"
+remark = "小さな文言の修正・変更"
+---
+{#intro}
+# 記事の構成について
+
+この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。
+最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、
+[このページ](https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings)
+にソースコードがあるので、そちらを先に見てほしい。
+
+{#regulations}
+# レギュレーション
+
+PHP で、次のような制約の下に fizzbuzz を書いた。
+
+* 1行あたりの文字数は2文字までに収めること (ただし `<?php` タグは除く)
+
+ * 厳密な定義: `<?php` タグ以降のソースコードが、2 byte ごとにラインフィード (LF) で区切られること
+
+* スペースやタブを使用しないこと
+* ループのアンロールをしないこと
+
+ * 100 回ループの代わりに 100 回コードをコピペ、というのは禁止
+
+* PHP 7.4〜8.1 で動作すること
+* 実行時に Notice や Warning が出ないこと
+* 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
+
+備考: PHP には `short_open_tag` というオプションがあり、
+これを有効にするとファイル冒頭の `<?php` の代わりに `<?`
+を使うことができ、文字どおり1行2文字で書ける。
+ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
+
+{#problems}
+# 主な障害
+
+1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
+
+特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
+
+```c
+#\
+i\
+n\
+c\
+l\
+u\
+d\
+e\
+<\
+s\
+t\
+d\
+i\
+o\
+.\
+h\
+>\
+/*
+*/
+i\
+n\
+t\
+/*
+*/
+m\
+a\
+i\
+n(
+){
+f\
+o\
+r(
+i\
+n\
+t\
+/*
+*/
+i=
+1;
+i<
+1\
+0\
+0;
+i\
++\
++)
+if
+(i
+%\
+15
+==
+0)
+p\
+r\
+i\
+n\
+t\
+f(
+"\
+F\
+i\
+z\
+z\
+B\
+u\
+z\
+z\
+%\
+c\
+",
+10
+);
+
+/* あとは同じように普通のプログラムを変形するだけなので省略 */
+```
+
+バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
+
+さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。
+これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。
+例えば、`echo` で出力することや、`for` でループすること、
+`new` でインスタンスを生成することができない。
+特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。
+
+当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:
+
+* `_`: `gettext` のエイリアス
+* `dl`: 拡張モジュールをロードする
+* `pi`: 円周率を返す
+
+(環境によって多少は変わるかも)
+
+2文字の関数を定義しまくった拡張モジュールを用意しておいて `dl()` で読み込む行為は、レギュレーションで定めた
+
+> * 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
+
+に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。
+
+また、2文字だと文字列がまともに書けないのも辛い。`''` だけで2文字使うので、
+「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
+
+```php
+$a
+='
+a'
+;;
+```
+
+とすると `$a` は `"\na"` になるのだが、余計な改行が入ってしまう。
+
+これらの障害をどのように乗り越えるのか、次節から見ていく。
+
+{#commentary}
+# 解説
+
+{#normal-fizzbuzz}
+## 普通の (?) fizzbuzz
+
+まずは普通に書くとしよう。
+
+```php
+
+printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
+);
+```
+
+`array_walk` や `range`、`printf` といった
+`for` よりも長いトークンが現れてしまったが、これは次節で直すことにする。
+なお、`echo` は文 (statement) であり式 (expression) ではないので、式である `printf` に置き換えた。
+
+{#shorten-function-invocation}
+## 関数呼び出しの短縮
+
+`range`、`array_walk`、`printf` は長すぎるのでどうにかせねばならない。
+ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
+
+```php
+
+$p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
+);
+```
+
+これで関数を呼び出している所は短くなった。
+では、`$r` や `$w` や `$p`、
+また `'Fizz'` や `'Buzz'` はどうやって 1 行 2 文字に収めるのか。
+次のテクニックへ移ろう。
+
+{#incompatible-solution}
+## 余談: PHP 8.x で動作しなくてもいいなら
+
+今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。
+
+> * PHP 7.4〜8.1 で動作すること
+
+というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。
+例えば、 `Fizz` という文字列が欲しければ、次のようにする。
+
+```php
+$f
+=F
+.i
+.z
+.z
+;;
+```
+
+こうして簡単に文字列を作れる。
+なお、この仕様は 7.x 時点でも警告を受けるので、`@` 演算子を使って抑制してやるとよい。
+
+```php
+$f
+=@
+F.
+@i
+.#
+@z
+.#
+@z
+;;
+```
+
+むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
+
+{#shorten-string-literals}
+## 文字列リテラルの短縮
+
+実際に使った手法の説明に移る。
+
+ずばり、文字列同士のビット演算を使う。
+PHP では、文字列同士でビット演算 (`&`、`|`、`^`) をした場合、
+文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。
+
+```php
+$a = "12345";
+$b = "world";
+
+// $a ^ $b は次のコードと同じ
+$result = '';
+for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
+$result .= $a[$i] ^ $b[$i];
+}
+
+echo $result;
+// => F]AXQ
+```
+
+これを踏まえ、次のコードを見てみよう。
+
+```php
+$x = "x\nOm\n";
+$y = "\nk!\no";
+$r = $x ^ $y;
+echo "$r\n";
+```
+
+実行すると、`range` が表示される。
+さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。
+書きかえてみよう。
+
+```php
+$x
+='x
+Om
+';
+$y
+='
+k!
+o'
+;
+
+$r = $x ^ $y;
+echo "$r\n";
+```
+
+さらに `#` を使って適当に調整すると、次のようになる。
+
+```php
+$x
+=#
+'x
+Om
+';
+$y
+='
+k!
+o'
+;#
+$r
+=#
+$x
+^#
+$y
+;#
+
+echo "$r\n";
+```
+
+1行あたり2文字で、`range` という文字列を生成することに成功した。
+他の必要な文字列にも、同様の処理をほどこす。
+
+備考: `Buzz` 中にある小文字の `u` は、このロジックだと non-printable な文字になってしまう。
+ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。
+
+{#stretched-fizzbuzz}
+# 完成系
+
+完成したものがこちら。
+
+```php
+
+$p
+((
+(#
+$i
+%3
+?#
+''
+:#
+$f
+).
+(#
+$i
+%5
+?#
+''
+:#
+$b
+)?
+:#
+$i
+)#
+.'
+')
+);
+```
+
+{#outro}
+# 感想など
+
+PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。
+この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
+
+みんなもプログラムを細長くしよう。
+
+{#alternative-solution}
+# 余談2: 別解
+
+PHP では、バッククォートを使ってシェルを呼び出せる。
+これは `shell_exec` 関数と等価である。
+さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える
+(当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
+
+```php
+ * スペースやタブを使用しないこと
+
+に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。
+
+もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
+
+```php
+
-
- 記事の構成について
-
- この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。
- 最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、
- このページ
- にソースコードがあるので、そちらを先に見てほしい。
-
-
-
- レギュレーション
-
- PHP で、次のような制約の下に fizzbuzz を書いた。
-
-
- -
- 1行あたりの文字数は2文字までに収めること (ただし
<?php タグは除く)
-
- -
- 厳密な定義:
<?php タグ以降のソースコードが、2 byte ごとにラインフィード (LF) で区切られること
-
-
-
- - スペースやタブを使用しないこと
- -
- ループのアンロールをしないこと
-
- - 100 回ループの代わりに 100 回コードをコピペ、というのは禁止
-
-
- - PHP 7.4〜8.1 で動作すること
- - 実行時に Notice や Warning が出ないこと
- - 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
-
-
- 備考: PHP には short_open_tag というオプションがあり、
- これを有効にするとファイル冒頭の <?php の代わりに <?
- を使うことができ、文字どおり1行2文字で書ける。
- ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
-
-
-
- 主な障害
-
- 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
-
-
- 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
-
-
- \
- /*
- */
- i\
- n\
- t\
- /*
- */
- m\
- a\
- i\
- n(
- ){
- f\
- o\
- r(
- i\
- n\
- t\
- /*
- */
- i=
- 1;
- i<
- 1\
- 0\
- 0;
- i\
- +\
- +)
- if
- (i
- %\
- 15
- ==
- 0)
- p\
- r\
- i\
- n\
- t\
- f(
- "\
- F\
- i\
- z\
- z\
- B\
- u\
- z\
- z\
- %\
- c\
- ",
- 10
- );
-
- /* あとは同じように普通のプログラムを変形するだけなので省略 */
- ]]>
-
-
- バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
-
-
- さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。
- これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。
- 例えば、echo で出力することや、for でループすること、
- new でインスタンスを生成することができない。
- 特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。
-
-
- 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:
-
-
- -
-
_: gettext のエイリアス
-
- -
-
dl: 拡張モジュールをロードする
-
- -
-
pi: 円周率を返す
-
-
-
- (環境によって多少は変わるかも)
-
-
- 2文字の関数を定義しまくった拡張モジュールを用意しておいて dl() で読み込む行為は、レギュレーションで定めた
-
-
-
- - 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
-
-
-
- に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。
-
-
- また、2文字だと文字列がまともに書けないのも辛い。'' だけで2文字使うので、
- 「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
-
-
-
-
-
- とすると $a は "\na" になるのだが、余計な改行が入ってしまう。
-
-
- これらの障害をどのように乗り越えるのか、次節から見ていく。
-
-
-
-
- 完成系
-
- 完成したものがこちら。
-
-
-
- $p
- ((
- (#
- $i
- %3
- ?#
- ''
- :#
- $f
- ).
- (#
- $i
- %5
- ?#
- ''
- :#
- $b
- )?
- :#
- $i
- )#
- .'
- ')
- );
- ]]>
-
-
-
- 感想など
-
- PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。
- この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
-
-
- みんなもプログラムを細長くしよう。
-
-
-
- 余談2: 別解
-
- PHP では、バッククォートを使ってシェルを呼び出せる。
- これは shell_exec 関数と等価である。
- さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える
- (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
-
-
-
-
-
- なお、ここでは簡単のため出力に printf をそのまま使っているが、
- 実際には printf という文字列を合成して可変関数で呼び出す。
-
-
- ただし、これでは
-
-
-
-
-
- に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。
-
-
- もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
-
-
-
-
-
- 先程と同じく、chr や printf を生成する部分は長くなるので省いた。
-
-
-
-
-
- は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。
- シェルに渡されている文字列は次のようになる。
-
-
-
-
-
- これは、前掲したコマンドと同じだ。
- かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。
- Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。
-
-
- ということでこれは別解ということにしておく。
-
-
- ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
-
-
-
-
-
- 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj
new file mode 100644
index 00000000..8567c711
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj
@@ -0,0 +1,154 @@
+---
+[article]
+uuid = "46e0d5db-b17e-464c-a723-8c3e01af7d1d"
+title = "PHPerKaigi 2023: ボツになったトークン問題 その 1"
+description = "来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。"
+tags = [
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2022-10-23"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、
+[PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、
+昨年と同様に、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) から、
+トークン問題を出題予定である。
+
+昨年のトークン問題の記事はこちら: [PHPerKaigi 2022 トークン問題の解説](/posts/2022-04-09/phperkaigi-2022-tokens)
+
+すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。
+せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+
+10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
+
+{#quiz}
+# 問題
+
+注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+
+```php
+ ABC
+```
+
+```php
+preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';
+```
+
+正規表現でマッチングしている。`\x23` は `#` と同じであることに留意すると、
+この正規表現は「`#` から始まる 2 以上の長さ (含 `#`) の文字列で、
+最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。
+
+なお、`#` を直接書いていないのは、`/#.+?) /` と書くと、
+`#.+?)` という意図せぬトークンが登録されてしまうからである。
+
+```php
+if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+ echo "Token: {$t}\n";
+} else {
+ echo "Failed.\n";
+}
+```
+
+最後にトークンのハッシュ値を見て、想定解かどうかを確認する。
+
+{#outro}
+# おわりに
+
+円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。
+
+最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた
+(ちなみに、それでも `M_PI` や `pi()` では精度が足りない)。
+見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。
+むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。
diff --git a/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.ndoc b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.ndoc
deleted file mode 100644
index cf6b1e2b..00000000
--- a/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.ndoc
+++ /dev/null
@@ -1,185 +0,0 @@
----
-[article]
-uuid = "46e0d5db-b17e-464c-a723-8c3e01af7d1d"
-title = "PHPerKaigi 2023: ボツになったトークン問題 その 1"
-description = "来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。"
-tags = [
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2022-10-23"
-remark = "公開"
----
-
-
- はじめに
-
- 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、
- PHPerKaigi 2023 において、
- 昨年と同様に、弊社 デジタルサーカス株式会社 から、
- トークン問題を出題予定である。
-
-
- 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説
-
-
- すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。
- せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
-
-
- 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
-
-
-
- 問題
-
- 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
-
-
-
-
-
- トークン入手方法
-
- ソースを見るとわかるとおり、$argv[1] を参照している。
- それを $π なる変数に代入しているので、円周率を渡してみる。
-
-
-
-
-
- 失敗してしまった。精度を上げてみる。
-
-
-
-
-
- だめだった。これを成功するまで繰り返す。
-
-
- 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。
-
-
-
-
-
- めでたくトークン「#YO」が手に入った。
-
-
-
-
- おわりに
-
- 円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。
-
-
- 最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた
- (ちなみに、それでも M_PI や pi() では精度が足りない)。
- 見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。
- むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.dj b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.dj
new file mode 100644
index 00000000..6fed329d
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.dj
@@ -0,0 +1,264 @@
+---
+[article]
+uuid = "673cb872-af2d-41a3-9fb0-60f1afcedb0d"
+title = "【備忘録】 このサイト用の VPS をセットアップしたときのメモ"
+description = "GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。"
+tags = [
+ "note-to-self",
+]
+
+[[article.revisions]]
+date = "2022-10-28"
+remark = "公開"
+
+[[article.revisions]]
+date = "2023-08-30"
+remark = "ssh_config に IdentitiesOnly yes を追加"
+---
+{#intro}
+# はじめに
+
+これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。
+そのときにおこなったサーバのセットアップ作業を書き残しておく。
+99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。
+
+未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。
+
+{#vps}
+# VPS
+
+[さくらの VPS](https://vps.sakura.ad.jp/) の 2 GB プラン。
+そこまで真面目に選定していないので、困ったら移動するかも。
+
+{#preparation}
+# 事前準備
+
+{#hostname}
+## サーバのホスト名を決める
+
+モチベーションが上がるという効能がある。今回は藤原定家から取って `teika` にした。
+たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。
+
+{#ssh-key}
+## SSH の鍵生成
+
+ローカルマシンで鍵を生成する。
+
+```shell-session
+$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
+$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
+```
+
+`teika.key` はローカルからサーバへの接続用、`github2teika.key` は、
+GitHub Actions からサーバへのデプロイ用。
+
+{#ssh-config}
+## SSH の設定
+
+`.ssh/config` に設定しておく。
+
+```ssh_config
+Host teika
+ HostName **********
+ User **********
+ Port **********
+ IdentityFile ~/.ssh/teika.key
+ IdentitiesOnly yes
+```
+
+{#basic-setup}
+# 基本のセットアップ
+
+{#login}
+## SSH 接続
+
+VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。
+
+{#user}
+## ユーザを作成する
+
+管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。
+`sudo` グループに追加して `sudo` できるようにし、`su` で切り替え。
+
+```shell-session
+$ sudo adduser **********
+$ sudo adduser ********** sudo
+$ su **********
+$ cd
+```
+
+{#hostname}
+## ホスト名を変える
+
+```shell-session
+$ sudo hostname teika
+```
+
+{#public-key}
+## 公開鍵を置く
+
+```shell-session
+$ mkdir ~/.ssh
+$ chmod 700 ~/.ssh
+$ vi ~/.ssh/authorized_keys
+```
+
+`authorized_keys` には、ローカルで生成した `~/.ssh/teika.key.pub` と
+`~/.ssh/github2teika.key.pub` の内容をコピーする。
+
+{#ssh-config}
+## SSH の設定
+
+SSH の設定を変更し、少しでも安全にしておく。
+
+```shell-session
+$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
+$ sudo vi /etc/ssh/sshd_config
+```
+
+* `Port` を変更
+* `PermitRootLogin` を `no` に
+* `PasswordAuthentication` を `no` に
+
+そして設定を反映。
+
+```shell-session
+$ sudo systemctl restart sshd
+$ sudo systemctl status sshd
+```
+
+{#ssh-connect}
+## SSH で接続確認
+
+今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。
+セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。
+
+```shell-session
+$ ssh teika
+```
+
+{#close-ports}
+## ポートの遮断
+
+デフォルトの 22 番を閉じ、設定したポートだけ空ける。
+
+```shell-session
+$ sudo ufw deny ssh
+$ sudo ufw allow *******
+$ sudo ufw enable
+$ sudo ufw reload
+$ sudo ufw status
+```
+
+ここでもう一度 SSH の接続確認を挟む。
+
+{#ssh-key-for-github}
+## GitHub 用の SSH 鍵
+
+GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
+
+```shell-session
+$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
+$ cat ~/.ssh/github.key.pub
+```
+
+[GitHub の設定画面](https://github.com/settings/ssh) から、この公開鍵を追加する。
+
+```shell-session
+$ vi ~/.ssh/config
+```
+
+設定はこう。
+
+```ssh_config
+Host github.com
+ HostName github.com
+ User git
+ Port 22
+ IdentityFile ~/.ssh/github.key
+ IdentitiesOnly yes
+```
+
+最後に接続できるか確認しておく。
+
+```shell-session
+$ ssh -T github.com
+```
+
+{#upgrade-packages}
+## パッケージの更新
+
+```shell-session
+$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt autoremove
+```
+
+{#site-hosting-setup}
+# サイトホスティング用のセットアップ
+
+{#dns}
+## DNS に IP アドレスを登録する
+
+このサーバは固定の IP アドレスがあるので、`A` レコードに直接入れるだけで済んだ。
+
+{#install-softwares}
+## 使うソフトウェアのインストール
+
+```shell-session
+$ sudo apt install docker docker-compose git make
+```
+
+{#docker}
+## メインユーザが Docker を使えるように
+
+```shell-session
+$ sudo adduser ********** docker
+```
+
+{#open-http-ports}
+## HTTP/HTTPS を通す
+
+80 番と 443 番を空ける。
+
+```shell-session
+$ sudo ufw allow 80/tcp
+$ sudo ufw allow 443/tcp
+$ sudo ufw reload
+$ sudo ufw status
+```
+
+{#clone-repositories}
+## リポジトリのクローン
+
+```shell-session
+$ cd
+$ git clone git@github.com:nsfisis/nsfisis.dev.git
+$ cd nsfisis.dev
+$ git submodule update --init
+```
+
+{#certbot}
+## certbot で証明書取得
+
+```shell-session
+$ docker-compose up -d acme-challenge
+$ make setup
+```
+
+{#run-server}
+## サーバを稼動させる
+
+```shell-session
+$ make serve
+```
+
+{#outro}
+# 感想
+
+(業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。
+とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。
+コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。
+次の式年遷宮では、手順の一部だけでも自動化したいところ。
diff --git a/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.ndoc b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.ndoc
deleted file mode 100644
index c10ad115..00000000
--- a/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.ndoc
+++ /dev/null
@@ -1,310 +0,0 @@
----
-[article]
-uuid = "673cb872-af2d-41a3-9fb0-60f1afcedb0d"
-title = "【備忘録】 このサイト用の VPS をセットアップしたときのメモ"
-description = "GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。"
-tags = [
- "note-to-self",
-]
-
-[[article.revisions]]
-date = "2022-10-28"
-remark = "公開"
-
-[[article.revisions]]
-date = "2023-08-30"
-remark = "ssh_config に IdentitiesOnly yes を追加"
----
-
-
- はじめに
-
- これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。
- そのときにおこなったサーバのセットアップ作業を書き残しておく。
- 99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。
-
-
- 未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。
-
-
-
- VPS
-
- さくらの VPS の 2 GB プラン。
- そこまで真面目に選定していないので、困ったら移動するかも。
-
-
-
- 事前準備
-
- サーバのホスト名を決める
-
- モチベーションが上がるという効能がある。今回は藤原定家から取って teika にした。
- たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。
-
-
-
- SSH の鍵生成
-
- ローカルマシンで鍵を生成する。
-
-
-
-
-
- teika.key はローカルからサーバへの接続用、github2teika.key は、
- GitHub Actions からサーバへのデプロイ用。
-
-
-
- SSH の設定
-
- .ssh/config に設定しておく。
-
-
-
-
-
-
-
- 基本のセットアップ
-
- SSH 接続
-
- VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。
-
-
-
- ユーザを作成する
-
- 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。
- sudo グループに追加して sudo できるようにし、su で切り替え。
-
-
-
-
-
-
-
- 公開鍵を置く
-
-
-
-
- authorized_keys には、ローカルで生成した ~/.ssh/teika.key.pub と
- ~/.ssh/github2teika.key.pub の内容をコピーする。
-
-
-
- SSH の設定
-
- SSH の設定を変更し、少しでも安全にしておく。
-
-
-
-
-
- Port を変更
- PermitRootLogin を no に
- PasswordAuthentication を no に
-
-
- そして設定を反映。
-
-
-
-
-
-
- SSH で接続確認
-
- 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。
- セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。
-
-
-
-
-
-
- ポートの遮断
-
- デフォルトの 22 番を閉じ、設定したポートだけ空ける。
-
-
-
-
-
- ここでもう一度 SSH の接続確認を挟む。
-
-
-
- GitHub 用の SSH 鍵
-
- GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
-
-
-
-
-
- GitHub の設定画面 から、この公開鍵を追加する。
-
-
-
-
-
- 設定はこう。
-
-
-
-
-
- 最後に接続できるか確認しておく。
-
-
-
-
-
-
-
-
- サイトホスティング用のセットアップ
-
- DNS に IP アドレスを登録する
-
- このサーバは固定の IP アドレスがあるので、A レコードに直接入れるだけで済んだ。
-
-
-
- 使うソフトウェアのインストール
-
-
-
-
-
- メインユーザが Docker を使えるように
-
-
-
-
-
- HTTP/HTTPS を通す
-
- 80 番と 443 番を空ける。
-
-
-
-
-
-
-
-
-
-
- 感想
-
- (業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。
- とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。
- コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。
- 次の式年遷宮では、手順の一部だけでも自動化したいところ。
-
-
-
diff --git a/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj
new file mode 100644
index 00000000..bd752c2e
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj
@@ -0,0 +1,139 @@
+---
+[article]
+uuid = "10fe9c47-7029-4874-82bd-b4dc50e07809"
+title = "PHPerKaigi 2023: ボツになったトークン問題 その 2"
+description = "来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。"
+tags = [
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2022-11-19"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の [PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、
+昨年と同様に、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) からトークン問題を出題予定である。
+
+昨年のトークン問題の記事はこちら: [PHPerKaigi 2022 トークン問題の解説](/posts/2022-04-09/phperkaigi-2022-tokens/)
+
+すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+
+10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
+
+その 1 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 1](/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/)
+
+{#quiz}
+# 問題
+
+注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+
+```php
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+```
+
+"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。
+
+{#how-to-obtain-token}
+# トークン入手方法
+
+実行してみると、次のような出力が得られる。
+
+```php
+#
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+```
+
+1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。
+
+```php
+#
+W
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+```
+
+今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。
+
+```php
+#
+W
+E
+L
+O
+V
+E
+P
+H
+P
+```
+
+トークン「#WELOVEPHP」が手に入った。
+
+{#commentary}
+# 解説
+
+一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。
+
+Vim で開くと次のようになる (1 行目を抜粋)。
+
+```php
+trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+```
+
+`<200b>` と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。
+
+::: note
+エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。
+:::
+
+文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。
+
+続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて `<200b>` と記載する。
+
+```php
+fn($s)=>chr(strlen($s)/3)
+```
+
+PHP の `strlen()` は文字列のバイト数を返す。1 行目の `$s` は以下の内容となっており、
+
+```php
+$s='<200b>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'
+```
+
+このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは `#` の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。
+
+デコード部以外の部分は、quine のための記述である。
+
+{#outro}
+# おわりに
+
+[CVE-2021-42574](https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html) に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。
+
+ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。
diff --git a/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.ndoc b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.ndoc
deleted file mode 100644
index e1305179..00000000
--- a/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.ndoc
+++ /dev/null
@@ -1,171 +0,0 @@
----
-[article]
-uuid = "10fe9c47-7029-4874-82bd-b4dc50e07809"
-title = "PHPerKaigi 2023: ボツになったトークン問題 その 2"
-description = "来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。"
-tags = [
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2022-11-19"
-remark = "公開"
----
-
-
-
- 問題
-
- 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
-
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- ]]>
-
-
- "And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。
-
-
-
- トークン入手方法
-
- 実行してみると、次のような出力が得られる。
-
-
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- ]]>
-
-
- 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。
-
-
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- trim($s,""):fn($s)=>chr(strlen($s)/3))($s='trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- ]]>
-
-
- 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。
-
-
-
-
-
- トークン「#WELOVEPHP」が手に入った。
-
-
-
-
- おわりに
-
- CVE-2021-42574 に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。
-
-
- ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
new file mode 100644
index 00000000..dcf7106b
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
@@ -0,0 +1,289 @@
+---
+[article]
+uuid = "89722cfb-7f4b-4e96-80bc-e0096e5eeef6"
+title = "PHPerKaigi 2023: ボツになったトークン問題 その 3"
+description = "来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。"
+tags = [
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2023-01-10"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の [PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、
+昨年と同様に、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) からトークン問題を出題予定である。
+
+昨年のトークン問題の記事はこちら: [PHPerKaigi 2022 トークン問題の解説](/posts/2022-04-09/phperkaigi-2022-tokens/)
+
+すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。
+せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+
+10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
+
+* その 1 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 1](/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/)
+* その 2 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 2](/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/)
+
+{#quiz}
+# 問題
+
+注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+
+```php
+getPrevious()) printf('%c', $e->getLine() + 23);
+ echo "\n";
+}
+function f(int $i) {
+ if ($i < 0) f();
+ try {
+ match ($i) {
+ 0 => 0 / 0,
+
+
+
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
+
+
+
+
+
+
+
+
+
+
+ 6 => 0 / 0,
+
+ 5 => 0 / 0,
+
+ 22 => 0 / 0,
+
+
+
+
+ 34, 35 => 0 / 0,
+
+
+
+
+
+
+
+
+ 25 => 0 / 0,
+ 17, 21 => 0 / 0,
+
+ 24, 32 => 0 / 0,
+
+
+
+
+
+
+
+ 33 => 0 / 0,
+
+ 16 => 0 / 0,
+
+
+ 18 => 0 / 0,
+
+
+
+
+
+
+
+
+ 7 => 0 / 0,
+
+ 2 => 0 / 0,
+ 1, 20 => 0 / 0,
+ 10, 28 => 0 / 0,
+ 8, 12, 26 => 0 / 0,
+ 4, 9, 13 => 0 / 0,
+
+
+
+
+
+ 31 => 0 / 0,
+
+ 29 => 0 / 0,
+
+ 11 => 0 / 0,
+
+
+
+ 3, 19, 23 => 0 / 0,
+
+
+ 27 => 0 / 0,
+
+ 30 => 0 / 0,
+ };
+ } finally {
+ f($i - 1);
+ }
+}
+
+
+
+
+
+
+
+function g() {
+ return __LINE__;
+}
+```
+
+"Catchline" と名付けた作品。実行するとトークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` が得られる。
+
+トークンは PHP の式になっていて、評価すると `Hello, World!` という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
+
+{#commentary}
+# 解説
+
+{#summary}
+## 概要
+
+例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。
+
+{#chain-of-exceptions}
+## 例外オブジェクトの連鎖
+
+[`Exception`](https://www.php.net/class.Exception) や [`Error`](https://www.php.net/class.Error) には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:
+
+* エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める
+* 内部エラーをラップして作られたエラーに、内部エラーの情報を含める
+
+このうち 1つ目のケースは、 `finally` 節の中でエラーを投げると PHP 処理系が勝手に `$previous` を設定してくれる。
+
+```php
+getMessage() . PHP_EOL;
+ // => Error 2
+ echo $e->getPrevious()->getMessage() . PHP_EOL;
+ // => Error 1
+}
+```
+
+この知識を元に、トークンの出力部を解析してみる。
+
+{#output}
+## 出力部の解析
+
+出力部をコメントや改行を追加して再掲する:
+
+```php
+getPrevious()) {
+ printf('%c', $e->getLine() + 23);
+ }
+ echo "\n";
+}
+```
+
+出力をおこなう `catch` 節を見てみると、 `Throwable::getPrevious()` を呼び出してエラーチェインを辿り、 `Throwable::getLine()` でエラーが発生した行数を取得している。その行数に `23` なるマジックナンバーを足し、フォーマット指定子 `%c` で出力している。
+
+フォーマット指定子 `%c` は、整数を ASCII コード[^ras-syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、
+
+[^ras-syndrome]: RAS syndrome
+
+```php
+1, 20 => 0 / 0,
+```
+
+によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。
+
+それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。
+
+{#data-construction}
+## データ構成部の解析
+
+`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):
+
+```php
+function f(int $i) {
+ if ($i < 0) f();
+ try {
+ match ($i) {
+ 0 => 0 / 0, // 12 行目
+
+
+
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
+
+ // (略)
+
+ 30 => 0 / 0, // 97 行目
+ };
+ } finally {
+ f($i - 1);
+ }
+}
+```
+
+前述のように、 `finally` 節でエラーを投げると PHP 処理系が `$previous` を設定する。ここでは、エラーを繋げるために `f()` を再帰呼び出ししている。最初に `f()` を呼び出している箇所を確認すると、
+
+```php
+
-
- はじめに
-
- 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の PHPerKaigi 2023 において、
- 昨年と同様に、弊社 デジタルサーカス株式会社 からトークン問題を出題予定である。
-
-
- 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説
-
-
- すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。
- せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
-
-
- 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
-
-
-
-
- 問題
-
- 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
-
- getPrevious()) printf('%c', $e->getLine() + 23);
- echo "\n";
- }
- function f(int $i) {
- if ($i < 0) f();
- try {
- match ($i) {
- 0 => 0 / 0,
-
-
-
- 15, 36 => 0 / 0,
- 14 => 0 / 0,
- 37 => 0 / 0,
-
-
-
-
-
-
-
-
-
-
- 6 => 0 / 0,
-
- 5 => 0 / 0,
-
- 22 => 0 / 0,
-
-
-
-
- 34, 35 => 0 / 0,
-
-
-
-
-
-
-
-
- 25 => 0 / 0,
- 17, 21 => 0 / 0,
-
- 24, 32 => 0 / 0,
-
-
-
-
-
-
-
- 33 => 0 / 0,
-
- 16 => 0 / 0,
-
-
- 18 => 0 / 0,
-
-
-
-
-
-
-
-
- 7 => 0 / 0,
-
- 2 => 0 / 0,
- 1, 20 => 0 / 0,
- 10, 28 => 0 / 0,
- 8, 12, 26 => 0 / 0,
- 4, 9, 13 => 0 / 0,
-
-
-
-
-
- 31 => 0 / 0,
-
- 29 => 0 / 0,
-
- 11 => 0 / 0,
-
-
-
- 3, 19, 23 => 0 / 0,
-
-
- 27 => 0 / 0,
-
- 30 => 0 / 0,
- };
- } finally {
- f($i - 1);
- }
- }
-
-
-
-
-
-
-
- function g() {
- return __LINE__;
- }
- ]]>
-
-
- "Catchline" と名付けた作品。実行するとトークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') が得られる。
-
-
- トークンは PHP の式になっていて、評価すると Hello, World! という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
-
-
-
-
- おわりに
-
- 「行数」というのはトークン文字列をデコードする対象として優れている。
-
-
- - トークンの一部や全部が陽に現れない
- __LINE__ で容易に取得できる
-
-
- しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 __LINE__ を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして Throwable を思いつき、続けてエラーオブジェクトには $previous があることを思い出した。
-
-
- 今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で Throwable なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、0/0 のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.dj b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.dj
new file mode 100644
index 00000000..a4ccf87e
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.dj
@@ -0,0 +1,73 @@
+---
+[article]
+uuid = "12512894-05d8-42c6-950e-8f5d60f984d8"
+title = "このブログのジェネレータを書き直した"
+description = "このブログのジェネレータを書き直したので、やったことを書き記しておく。"
+tags = []
+
+[[article.revisions]]
+date = "2023-03-10"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+このブログを構築するシステムを書き直したのは 2度目である。
+元々立ち上げた当初は、静的サイトジェネレータである [Hugo](https://gohugo.io/) を使っていた。
+それを [Asciidoctor](https://asciidoctor.org/) にいくつかのカスタムを加えた自前のジェネレータに移行したのが 2022年の11月ごろだ。
+そして今回、スクラッチから書いた [Deno](https://deno.land/) 製のジェネレータに移行した。
+
+この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。
+
+{#from-hugo-to-asciidoctor}
+# Hugo から Asciidoctor へ
+
+最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。
+実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。
+例えば、Hugo を使っていたころはサポートしていた RSS フィードの生成は、まだ実装できていない。
+
+移行先のフォーマットとして AsciiDoc を選んだのは、Markdown よりも表現力に優れるからである。Markdown は広く使われている軽量マークアップ言語だが、以下のような欠点を持つ。
+
+* CommonMark では機能が貧弱である (例: 脚注、`id` 属性の付与)
+* 拡張記法に実装間で互換性がない
+* メタデータ (公開日など) を埋め込む統一された方法がない
+
+AsciiDoc は Markdown に比べると普及していないが、上記の欠点は克服している。
+
+* ブログを書くのに十分な表現力がある
+* フォーマットを拡張するときの記法があらかじめ定められている
+* メタデータを埋め込む統一された方法がある
+
+なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。
+
+{#from-asciidoctor-to-my-own-generator}
+# Asciidoctor から自前のジェネレータへ
+
+AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、
+
+* 表現力が高く、
+* 文法が厳密であり、
+* 簡単に実装できる
+
+フォーマットが求められた。これに合致したのが、XML をベースとする [DocBook](https://docbook.org/) (今回使っているのは、そのサブセットである [Simplified DocBook](https://tdg.docbook.org/tdg/sdocbook/5.1/) ) である。
+
+実は、AsciiDoc と DocBook はおおよそ互換性がある。AsciiDoc で書かれた文書は (ほぼ) 情報ロスなしに DocBook へ変換でき、逆もまたしかりである。
+よって、DocBook には、AsciiDoc と同等の表現力がある。
+
+XML の文法の厳密さについては、説明するまでもないだろう。また、単純な文法であることから実装が容易であり、事実上 Asciidoctor へロックインされる AsciiDoc とは異なり、さまざまな言語で多くのライブラリが存在する。
+
+今回は、XML のパース自体も自分で書いている (これは何となく書きたかったからであり、合理的な理由があるわけではない。実装はサボりまくっているので XML のコメントが使えないといった制限がある)。
+
+XML という機械処理しやすいフォーマットを選ぶことには、機械的な変換や検査といった処理がおこないやすくなるといった利点もある。
+欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。
+結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。
+
+{#outro}
+# おわりに
+
+2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。
+今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。
+
+最後にもう一度書くのだが、Hugo は大変に優れた静的サイトジェネレータである。
+無駄な拘りがなければこれを使うとよい。
+私は無駄に拘ったので、ブログの記事を書く時間を潰してブログシステムを作ってしまった。
diff --git a/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.ndoc b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.ndoc
deleted file mode 100644
index 0c36e611..00000000
--- a/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.ndoc
+++ /dev/null
@@ -1,95 +0,0 @@
----
-[article]
-uuid = "12512894-05d8-42c6-950e-8f5d60f984d8"
-title = "このブログのジェネレータを書き直した"
-description = "このブログのジェネレータを書き直したので、やったことを書き記しておく。"
-tags = []
-
-[[article.revisions]]
-date = "2023-03-10"
-remark = "公開"
----
-
-
- はじめに
-
- このブログを構築するシステムを書き直したのは 2度目である。
- 元々立ち上げた当初は、静的サイトジェネレータである Hugo を使っていた。
- それを Asciidoctor にいくつかのカスタムを加えた自前のジェネレータに移行したのが 2022年の11月ごろだ。
- そして今回、スクラッチから書いた Deno 製のジェネレータに移行した。
-
-
- この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。
-
-
-
- Hugo から Asciidoctor へ
-
- 最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。
- 実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。
- 例えば、Hugo を使っていたころはサポートしていた RSS フィードの生成は、まだ実装できていない。
-
-
- 移行先のフォーマットとして AsciiDoc を選んだのは、Markdown よりも表現力に優れるからである。Markdown は広く使われている軽量マークアップ言語だが、以下のような欠点を持つ。
-
-
-
- - CommonMark では機能が貧弱である (例: 脚注、
id 属性の付与)
- - 拡張記法に実装間で互換性がない
- - メタデータ (公開日など) を埋め込む統一された方法がない
-
-
-
- AsciiDoc は Markdown に比べると普及していないが、上記の欠点は克服している。
-
-
-
- - ブログを書くのに十分な表現力がある
- - フォーマットを拡張するときの記法があらかじめ定められている
- - メタデータを埋め込む統一された方法がある
-
-
-
- なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。
-
-
-
- Asciidoctor から自前のジェネレータへ
-
- AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、
-
- - 表現力が高く、
- - 文法が厳密であり、
- - 簡単に実装できる
-
- フォーマットが求められた。これに合致したのが、XML をベースとする DocBook (今回使っているのは、そのサブセットである Simplified DocBook) である。
-
-
- 実は、AsciiDoc と DocBook はおおよそ互換性がある。AsciiDoc で書かれた文書は (ほぼ) 情報ロスなしに DocBook へ変換でき、逆もまたしかりである。
- よって、DocBook には、AsciiDoc と同等の表現力がある。
-
-
- XML の文法の厳密さについては、説明するまでもないだろう。また、単純な文法であることから実装が容易であり、事実上 Asciidoctor へロックインされる AsciiDoc とは異なり、さまざまな言語で多くのライブラリが存在する。
-
-
- 今回は、XML のパース自体も自分で書いている (これは何となく書きたかったからであり、合理的な理由があるわけではない。実装はサボりまくっているので XML のコメントが使えないといった制限がある)。
-
-
- XML という機械処理しやすいフォーマットを選ぶことには、機械的な変換や検査といった処理がおこないやすくなるといった利点もある。
- 欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。
- 結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。
-
-
-
- おわりに
-
- 2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。
- 今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。
-
-
- 最後にもう一度書くのだが、Hugo は大変に優れた静的サイトジェネレータである。
- 無駄な拘りがなければこれを使うとよい。
- 私は無駄に拘ったので、ブログの記事を書く時間を潰してブログシステムを作ってしまった。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj
new file mode 100644
index 00000000..55d1519b
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj
@@ -0,0 +1,544 @@
+---
+[article]
+uuid = "ed36e185-5bfa-42e1-8358-0b1da9b0a063"
+title = "PNG 画像の最小構成エンコーダを実装する"
+description = "PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。"
+tags = []
+
+[[article.revisions]]
+date = "2023-04-01"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。
+PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。
+また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。
+
+{#basic-structure-of-png}
+# PNG ファイルの基本構造
+
+PNG ファイルの基本構造は次のようになっている。
+
+1. PNG signature
+1. IHDR chunk
+1. 任意個の chunk
+1. IEND chunk
+
+Chunk には画像データを入れる IDAT chunk、パレットデータを入れる PLTE chunk、テキストデータを入れる tEXt chunk などがあるが、
+今回は最小構成ということで IDAT chunk (と IHDR chunk と IEND chunk) のみを用いる。
+
+次節で、それぞれの具体的な構造を確認しつつ実装していく。
+
+{#implement-png-encoder}
+# PNG のエンコーダを実装する
+
+以下のソースコードをベースにする。
+今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ `image/png` を用いる。
+
+```go
+package main
+
+import (
+ "image"
+ _ "image/png"
+ "io"
+ "os"
+)
+
+func main() {
+ inFile, err := os.Open("input.png")
+ if err != nil {
+ panic(err)
+ }
+ defer inFile.Close()
+
+ img, _, err := image.Decode(inFile)
+ if err != nil {
+ panic(err)
+ }
+
+ outFile, err := os.Create("output.png")
+ if err != nil {
+ panic(err)
+ }
+ defer outFile.Close()
+
+ writePng(outFile, img)
+}
+
+func writePng(w io.Writer, img image.Image) {
+ width := uint32(img.Bounds().Dx())
+ height := uint32(img.Bounds().Dy())
+ writeSignature(w)
+ writeChunkIhdr(w, width, height)
+ writeChunkIdat(w, width, height, img)
+ writeChunkIend(w)
+}
+```
+
+以降は、`writeSignature` や `writeChunkIhdr` などを実装していく。
+
+{#png-signature}
+## PNG signature
+
+PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。
+
+1. 0x89
+1. 0x50 (ASCII コードで「P」)
+1. 0x4E (ASCII コードで「N」)
+1. 0x47 (ASCII コードで「G」)
+1. 0x0D (ASCII コードで CR)
+1. 0x0A (ASCII コードで LF)
+1. 0x1A (ASCII コードで EOF)
+1. 0x0A (ASCII コードで LF)
+
+CRLF や LF は、送信中に改行コードの変換が誤っておこなわれていないかどうかを検知するのに使われる。
+
+`writeSignature` の実装はこちら:
+
+```go
+import "encoding/binary"
+
+func writeSignature(w io.Writer) {
+ sig := [8]uint8{
+ 0x89,
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // CR
+ 0x0A, // LF
+ 0x1A, // EOF (^Z)
+ 0x0A, // LF
+ }
+ binary.Write(w, binary.BigEndian, sig)
+}
+```
+
+`encoding/binary` パッケージの `binary.Write` を使い、固定の 8 バイトを書き込む。
+
+{#structure-of-chunk}
+## Chunk の構造
+
+IHDR chunk に進む前に、chunk 一般の構造を確認する。
+
+1. Length: chunk data のバイト長 (符号なし 4 バイト整数)
+1. Chunk type: chunk の種類を示す 4 バイトからなる名前
+1. Chunk data: 実際のデータ。0 バイトでもよい
+1. CRC: chunk type と chunk data の CRC (符号なし 4 バイト整数)
+
+CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では `hash/crc32` パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている ( [D. Sample CRC implementation](https://www.w3.org/TR/png/#D-CRCAppendix) ) ので、これを Go に移植する。
+
+```go
+var (
+ crcTable [256]uint32
+ crcTableComputed bool
+)
+
+func makeCrcTable() {
+ for n := 0; n < 256; n++ {
+ c := uint32(n)
+ for k := 0; k < 8; k++ {
+ if (c & 1) != 0 {
+ c = 0xEDB88320 ^ (c >> 1)
+ } else {
+ c = c >> 1
+ }
+ }
+ crcTable[n] = c
+ }
+ crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+ if !crcTableComputed {
+ makeCrcTable()
+ }
+
+ c := crc
+ for n := 0; n < len(buf); n++ {
+ c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+ }
+ return c
+}
+
+func crc(buf []byte) uint32 {
+ return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+```
+
+できた `crc` 関数を使って、chunk 一般を書き込む関数も用意しておこう。
+
+```go
+func writeChunk(w io.Writer, chunkType string, data []byte) {
+ typeAndData := make([]byte, 0, len(chunkType)+len(data))
+ typeAndData = append(typeAndData, []byte(chunkType)...)
+ typeAndData = append(typeAndData, data...)
+
+ binary.Write(w, binary.BigEndian, uint32(len(data)))
+ binary.Write(w, binary.BigEndian, typeAndData)
+ binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+```
+
+仕様どおり、`chunkType` と `data` から CRC を計算し、`data` の長さと合わせて書き込んでいる。
+PNG では基本的に big endian を使うことに注意する。
+
+準備ができたところで、具体的な chunk をエンコードしていく。
+
+{#ihdr-chunk}
+## IHDR chunk
+
+IHDR chunk は最初に配置される chunk である。次のようなデータからなる。
+
+1. 画像の幅 (符号なし 4 バイト整数)
+1. 画像の高さ (符号なし 4 バイト整数)
+1. ビット深度 (符号なし 1 バイト整数)
+
+ * 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる
+
+1. 色タイプ (符号なし 1 バイト整数)
+
+ * 0: グレースケール
+ * 2: Truecolor (今回はこれに決め打ち)
+ * 3: パレットのインデックス
+ * 4: グレースケール + アルファ
+ * 6: Truecolor + アルファ
+
+1. 圧縮方式 (符号なし 1 バイト整数)
+
+ * PNG の仕様書に 0 しか定義されていないので 0 で固定
+
+1. フィルタ方式 (符号なし 1 バイト整数)
+
+ * PNG の仕様書に 0 しか定義されていないので 0 で固定
+
+1. インターレース方式 (符号なし 1 バイト整数)
+
+ * 今回はインターレースしないので 0
+
+今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
+
+```go
+import "bytes"
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.BigEndian, width)
+ binary.Write(&buf, binary.BigEndian, height)
+ binary.Write(&buf, binary.BigEndian, uint8(8))
+ binary.Write(&buf, binary.BigEndian, uint8(2))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+
+ writeChunk(w, "IHDR", buf.Bytes())
+}
+```
+
+{#idat-chunk}
+## IDAT chunk
+
+IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。
+
+{#zlib}
+### Zlib
+
+まずは zlib について確認する。おおよそ次のような構造になっている。
+
+1. 固定で 0x78 (符号なし 1 バイト整数)
+1. 固定で 0x01 (符号なし 1 バイト整数)
+1. データ
+1. データの Adler-32
+
+最初の 2 バイトにも意味はあるが、PNG では固定で構わない。
+
+Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている ( [9. Appendix: Sample code](https://www.rfc-editor.org/rfc/rfc1950#section-9) ) ので、Go に移植する。
+
+```go
+const adler32Base = 65521
+
+func updateAdler32(adler uint32, buf []byte) uint32 {
+ s1 := adler & 0xFFFF
+ s2 := (adler >> 16) & 0xFFFF
+
+ for n := 0; n < len(buf); n++ {
+ s1 = (s1 + uint32(buf[n])) % adler32Base
+ s2 = (s2 + s1) % adler32Base
+ }
+ return (s2 << 16) + s1
+}
+
+func adler32(buf []byte) uint32 {
+ return updateAdler32(1, buf)
+}
+```
+
+「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。
+
+1 つの無圧縮ブロックには 65535 (2^16^ - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
+
+1. 最終ブロックなら 1、そうでなければ 0 (符号なし 1 バイト整数)
+1. ブロックのバイト長 (符号なし 2 バイト整数)
+1. ブロックのバイト長の 1 の補数、あるいはビット反転 (符号なし 2 バイト整数)
+1. データ (最大 65535 バイト)
+
+実際にこの手抜き zlib を実装したものがこちら:
+
+```go
+func encodeZlib(data []byte) []byte {
+ var buf bytes.Buffer
+
+ binary.Write(&buf, binary.BigEndian, uint8(0x78))
+ binary.Write(&buf, binary.BigEndian, uint8(0x01))
+ blockSize := 65535
+ isFinalBlock := false
+ for i := 0; !isFinalBlock; i++ {
+ var block []byte
+ if len(data) <= (i+1)*blockSize {
+ block = data[i*blockSize:]
+ isFinalBlock = true
+ } else {
+ block = data[i*blockSize : (i+1)*blockSize]
+ }
+ binary.Write(&buf, binary.BigEndian, isFinalBlock)
+ binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+ binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+ binary.Write(&buf, binary.LittleEndian, block)
+ }
+ binary.Write(&buf, binary.BigEndian, adler32(data))
+
+ return buf.Bytes()
+}
+```
+
+{#image-data}
+### 画像データ
+
+では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。
+画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。
+右下のピクセルまで行けば終わり。要は Z 字型に進んでいく。
+
+また、それぞれの行の先頭には、圧縮のためのフィルタタイプを指定する。
+ただ、今回はその実装を省略するために、常にフィルタ 0 (何も加工しない) を使う。
+
+先ほどの `encodeZlib` も使って実際に実装したものがこちら:
+
+```go
+func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+ var pixels bytes.Buffer
+ for y := uint32(0); y < height; y++ {
+ binary.Write(&pixels, binary.BigEndian, uint8(0))
+ for x := uint32(0); x < width; x++ {
+ r, g, b, _ := img.At(int(x), int(y)).RGBA()
+ binary.Write(&pixels, binary.BigEndian, uint8(r))
+ binary.Write(&pixels, binary.BigEndian, uint8(g))
+ binary.Write(&pixels, binary.BigEndian, uint8(b))
+ }
+ }
+
+ writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+}
+```
+
+{#iend-chunk}
+## IEND chunk
+
+最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。
+
+特に追加のデータはなく、必要なのは chunk type の `IEND` くらいなので実装は簡単:
+
+```go
+func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+}
+```
+
+{#outro}
+# おわりに
+
+最後に全ソースコードを再掲しておく。
+
+```go
+package main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "image"
+ _ "image/png"
+ "io"
+ "os"
+)
+
+func main() {
+ inFile, err := os.Open("input.png")
+ if err != nil {
+ panic(err)
+ }
+ defer inFile.Close()
+
+ img, _, err := image.Decode(inFile)
+ if err != nil {
+ panic(err)
+ }
+
+ outFile, err := os.Create("output.png")
+ if err != nil {
+ panic(err)
+ }
+ defer outFile.Close()
+
+ writePng(outFile, img)
+}
+
+func writePng(w io.Writer, img image.Image) {
+ width := uint32(img.Bounds().Dx())
+ height := uint32(img.Bounds().Dy())
+ writeSignature(w)
+ writeChunkIhdr(w, width, height)
+ writeChunkIdat(w, width, height, img)
+ writeChunkIend(w)
+}
+
+func writeSignature(w io.Writer) {
+ sig := [8]uint8{
+ 0x89,
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // CR
+ 0x0A, // LF
+ 0x1A, // EOF (^Z)
+ 0x0A, // LF
+ }
+ binary.Write(w, binary.BigEndian, sig)
+}
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.BigEndian, width)
+ binary.Write(&buf, binary.BigEndian, height)
+ binary.Write(&buf, binary.BigEndian, uint8(8))
+ binary.Write(&buf, binary.BigEndian, uint8(2))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+
+ writeChunk(w, "IHDR", buf.Bytes())
+}
+
+func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+ var pixels bytes.Buffer
+ for y := uint32(0); y < height; y++ {
+ binary.Write(&pixels, binary.BigEndian, uint8(0))
+ for x := uint32(0); x < width; x++ {
+ r, g, b, _ := img.At(int(x), int(y)).RGBA()
+ binary.Write(&pixels, binary.BigEndian, uint8(r))
+ binary.Write(&pixels, binary.BigEndian, uint8(g))
+ binary.Write(&pixels, binary.BigEndian, uint8(b))
+ }
+ }
+
+ writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+}
+
+func encodeZlib(data []byte) []byte {
+ var buf bytes.Buffer
+
+ binary.Write(&buf, binary.BigEndian, uint8(0x78))
+ binary.Write(&buf, binary.BigEndian, uint8(0x01))
+ blockSize := 65535
+ isFinalBlock := false
+ for i := 0; !isFinalBlock; i++ {
+ var block []byte
+ if len(data) <= (i+1)*blockSize {
+ block = data[i*blockSize:]
+ isFinalBlock = true
+ } else {
+ block = data[i*blockSize : (i+1)*blockSize]
+ }
+ binary.Write(&buf, binary.BigEndian, isFinalBlock)
+ binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+ binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+ binary.Write(&buf, binary.LittleEndian, block)
+ }
+ binary.Write(&buf, binary.BigEndian, adler32(data))
+
+ return buf.Bytes()
+}
+
+func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+}
+
+func writeChunk(w io.Writer, chunkType string, data []byte) {
+ typeAndData := make([]byte, 0, len(chunkType)+len(data))
+ typeAndData = append(typeAndData, []byte(chunkType)...)
+ typeAndData = append(typeAndData, data...)
+
+ binary.Write(w, binary.BigEndian, uint32(len(data)))
+ binary.Write(w, binary.BigEndian, typeAndData)
+ binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+
+var (
+ crcTable [256]uint32
+ crcTableComputed bool
+)
+
+func makeCrcTable() {
+ for n := 0; n < 256; n++ {
+ c := uint32(n)
+ for k := 0; k < 8; k++ {
+ if (c & 1) != 0 {
+ c = 0xEDB88320 ^ (c >> 1)
+ } else {
+ c = c >> 1
+ }
+ }
+ crcTable[n] = c
+ }
+ crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+ if !crcTableComputed {
+ makeCrcTable()
+ }
+
+ c := crc
+ for n := 0; n < len(buf); n++ {
+ c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+ }
+ return c
+}
+
+func crc(buf []byte) uint32 {
+ return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+
+const adler32Base = 65521
+
+func updateAdler32(adler uint32, buf []byte) uint32 {
+ s1 := adler & 0xFFFF
+ s2 := (adler >> 16) & 0xFFFF
+
+ for n := 0; n < len(buf); n++ {
+ s1 = (s1 + uint32(buf[n])) % adler32Base
+ s2 = (s2 + s1) % adler32Base
+ }
+ return (s2 << 16) + s1
+}
+
+func adler32(buf []byte) uint32 {
+ return updateAdler32(1, buf)
+}
+```
+
+{#references}
+# 参考
+
+* [Portable Network Graphics (PNG) Specification (Third Edition)](https://www.w3.org/TR/png)
+* [ZLIB Compressed Data Format Specification version 3.3](https://www.rfc-editor.org/rfc/rfc1950)
diff --git a/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.ndoc b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.ndoc
deleted file mode 100644
index ec39a99f..00000000
--- a/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.ndoc
+++ /dev/null
@@ -1,605 +0,0 @@
----
-[article]
-uuid = "ed36e185-5bfa-42e1-8358-0b1da9b0a063"
-title = "PNG 画像の最小構成エンコーダを実装する"
-description = "PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。"
-tags = []
-
-[[article.revisions]]
-date = "2023-04-01"
-remark = "公開"
----
-
-
- はじめに
-
- この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。
- PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。
- また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。
-
-
-
- PNG ファイルの基本構造
-
- PNG ファイルの基本構造は次のようになっている。
-
-
- - PNG signature
- - IHDR chunk
- - 任意個の chunk
- - IEND chunk
-
-
- Chunk には画像データを入れる IDAT chunk、パレットデータを入れる PLTE chunk、テキストデータを入れる tEXt chunk などがあるが、
- 今回は最小構成ということで IDAT chunk (と IHDR chunk と IEND chunk) のみを用いる。
-
-
- 次節で、それぞれの具体的な構造を確認しつつ実装していく。
-
-
-
- PNG のエンコーダを実装する
-
- 以下のソースコードをベースにする。
- 今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ image/png を用いる。
-
-
-
-
-
- 以降は、writeSignature や writeChunkIhdr などを実装していく。
-
-
- PNG signature
-
- PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。
-
-
- - 0x89
- - 0x50 (ASCII コードで「P」)
- - 0x4E (ASCII コードで「N」)
- - 0x47 (ASCII コードで「G」)
- - 0x0D (ASCII コードで CR)
- - 0x0A (ASCII コードで LF)
- - 0x1A (ASCII コードで EOF)
- - 0x0A (ASCII コードで LF)
-
-
- CRLF や LF は、送信中に改行コードの変換が誤っておこなわれていないかどうかを検知するのに使われる。
-
-
- writeSignature の実装はこちら:
-
-
-
-
-
- encoding/binary パッケージの binary.Write を使い、固定の 8 バイトを書き込む。
-
-
-
- Chunk の構造
-
- IHDR chunk に進む前に、chunk 一般の構造を確認する。
-
-
- - Length: chunk data のバイト長 (符号なし 4 バイト整数)
- - Chunk type: chunk の種類を示す 4 バイトからなる名前
- - Chunk data: 実際のデータ。0 バイトでもよい
- - CRC: chunk type と chunk data の CRC (符号なし 4 バイト整数)
-
-
- CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている (D. Sample CRC implementation) ので、これを Go に移植する。
-
-
- > 1)
- } else {
- c = c >> 1
- }
- }
- crcTable[n] = c
- }
- crcTableComputed = true
- }
-
- func updateCrc(crc uint32, buf []byte) uint32 {
- if !crcTableComputed {
- makeCrcTable()
- }
-
- c := crc
- for n := 0; n < len(buf); n++ {
- c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
- }
- return c
- }
-
- func crc(buf []byte) uint32 {
- return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
- }
- ]]>
-
-
- できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。
-
-
-
-
-
- 仕様どおり、chunkType と data から CRC を計算し、data の長さと合わせて書き込んでいる。
- PNG では基本的に big endian を使うことに注意する。
-
-
- 準備ができたところで、具体的な chunk をエンコードしていく。
-
-
-
- IHDR chunk
-
- IHDR chunk は最初に配置される chunk である。次のようなデータからなる。
-
-
- - 画像の幅 (符号なし 4 バイト整数)
- - 画像の高さ (符号なし 4 バイト整数)
- -
- ビット深度 (符号なし 1 バイト整数)
-
- - 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる
-
-
- -
- 色タイプ (符号なし 1 バイト整数)
-
- - 0: グレースケール
- - 2: Truecolor (今回はこれに決め打ち)
- - 3: パレットのインデックス
- - 4: グレースケール + アルファ
- - 6: Truecolor + アルファ
-
-
- -
- 圧縮方式 (符号なし 1 バイト整数)
-
- PNG の仕様書に 0 しか定義されていないので 0 で固定
-
-
- -
- フィルタ方式 (符号なし 1 バイト整数)
-
- PNG の仕様書に 0 しか定義されていないので 0 で固定
-
-
- -
- インターレース方式 (符号なし 1 バイト整数)
-
-
-
-
- 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
-
-
-
-
-
-
- IDAT chunk
-
- IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。
-
-
- Zlib
-
- まずは zlib について確認する。おおよそ次のような構造になっている。
-
-
- - 固定で 0x78 (符号なし 1 バイト整数)
- - 固定で 0x01 (符号なし 1 バイト整数)
- - データ
- - データの Adler-32
-
-
- 最初の 2 バイトにも意味はあるが、PNG では固定で構わない。
-
-
- Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている (9. Appendix: Sample code) ので、Go に移植する。
-
-
- > 16) & 0xFFFF
-
- for n := 0; n < len(buf); n++ {
- s1 = (s1 + uint32(buf[n])) % adler32Base
- s2 = (s2 + s1) % adler32Base
- }
- return (s2 << 16) + s1
- }
-
- func adler32(buf []byte) uint32 {
- return updateAdler32(1, buf)
- }
- ]]>
-
-
- 「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。
-
-
- 1 つの無圧縮ブロックには 65535 (216 - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
-
-
- - 最終ブロックなら 1、そうでなければ 0 (符号なし 1 バイト整数)
- - ブロックのバイト長 (符号なし 2 バイト整数)
- - ブロックのバイト長の 1 の補数、あるいはビット反転 (符号なし 2 バイト整数)
- - データ (最大 65535 バイト)
-
-
- 実際にこの手抜き zlib を実装したものがこちら:
-
-
-
-
-
-
- 画像データ
-
- では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。
- 画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。
- 右下のピクセルまで行けば終わり。要は Z 字型に進んでいく。
-
-
- また、それぞれの行の先頭には、圧縮のためのフィルタタイプを指定する。
- ただ、今回はその実装を省略するために、常にフィルタ 0 (何も加工しない) を使う。
-
-
- 先ほどの encodeZlib も使って実際に実装したものがこちら:
-
-
-
-
-
-
-
- IEND chunk
-
- 最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。
-
-
- 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単:
-
-
-
-
-
-
-
- おわりに
-
- 最後に全ソースコードを再掲しておく。
-
-
- > 1)
- } else {
- c = c >> 1
- }
- }
- crcTable[n] = c
- }
- crcTableComputed = true
- }
-
- func updateCrc(crc uint32, buf []byte) uint32 {
- if !crcTableComputed {
- makeCrcTable()
- }
-
- c := crc
- for n := 0; n < len(buf); n++ {
- c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
- }
- return c
- }
-
- func crc(buf []byte) uint32 {
- return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
- }
-
- const adler32Base = 65521
-
- func updateAdler32(adler uint32, buf []byte) uint32 {
- s1 := adler & 0xFFFF
- s2 := (adler >> 16) & 0xFFFF
-
- for n := 0; n < len(buf); n++ {
- s1 = (s1 + uint32(buf[n])) % adler32Base
- s2 = (s2 + s1) % adler32Base
- }
- return (s2 << 16) + s1
- }
-
- func adler32(buf []byte) uint32 {
- return updateAdler32(1, buf)
- }
- ]]>
-
-
-
-
diff --git a/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.dj b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.dj
new file mode 100644
index 00000000..e4047c79
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.dj
@@ -0,0 +1,141 @@
+---
+[article]
+uuid = "21ce39f0-d613-45f2-a760-89c368892d77"
+title = "PHPerKaigi 2023 参加レポ"
+description = "2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2023-04-04"
+remark = "公開"
+
+[[article.revisions]]
+date = "2023-06-28"
+remark = "トークセッションの記事版の執筆を中止"
+---
+{#intro}
+# はじめに
+
+2023-03-23 から 2023-03-25 にかけて開催された、 [PHPerKaigi 2023](https://phperkaigi.jp/2023/) に参加した。
+今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。
+
+昨年、一昨年の参加レポはこちら:
+
+* [PHPerKaigi 2022](/posts/2022-05-01/phperkaigi-2022/)
+* [PHPerKaigi 2021](/posts/2021-03-30/phperkaigi-2021/)
+
+{#as-speaker}
+# スピーカーとして
+
+これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。
+
+* 詳説「参照」:PHP 処理系の実装から参照を理解する
+
+ * [プロポーザル](https://fortee.jp/phperkaigi-2023/proposal/95e4dd94-5fc7-40fe-9e1a-230e36404cbe)
+ * [スライド](/slides/2023-03-24/phperkaigi-2023/)
+ * 解説記事 (執筆中) → 追記: 記事版の執筆は諦めた
+
+* PHPerチャレンジ解説セッション - デジタルサーカス株式会社
+
+ * [プロポーザル](https://fortee.jp/phperkaigi-2023/proposal/524c9dca-1d70-4b32-a939-9c73ffe5cb48)
+ * [スライド](/slides/2023-03-25/phperkaigi-2023-tokens/)
+ * 解説記事 (執筆中) → 追記: 記事版の執筆は諦めた
+
+PHPer チャレンジの話については後述する。
+参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。
+
+{#as-staff}
+# 当日スタッフとして
+
+今回はスピーカーのみならず当日スタッフとしても参加した。
+カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。
+
+PHPerKaigi は一般参加者の目線でもよくできたカンファレンスだなあという印象だったのだが、よりその思いを強くした。
+なんとスタッフにとってもよくできたカンファレンスなのである。
+
+反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。
+
+{#as-attendee}
+# 参加者として
+
+{#recommended-sessions}
+## おすすめセッション
+
+5つのセッションを厳選した。
+
+[ブラウザの向こう側で「200 OK」を返すまでに何が起きているのか調べてみた](https://fortee.jp/phperkaigi-2023/proposal/f7f2f18a-e6b0-47e4-ade0-e324f72428ae)
+
+Web に関わるなら、バックエンドでもフロントエンドでも知っておいてほしい知識。
+タイトルを見て「こんな話だろうな」と想像がつくレベルなら見なくてもいいかも。
+
+[PHPで学ぶ "Cacheの距離" の話](https://fortee.jp/phperkaigi-2023/proposal/280706e0-7158-4237-8202-c9d64330b96f)
+
+これも上セッションと同様に、基礎を抑えられる良いセッション。
+
+[防衛的 PHP: 多様性を生き抜くための PHP 入門](https://fortee.jp/phperkaigi-2023/proposal/ad3ba31c-0214-4557-a0df-3755db8ed8cc)
+
+静的解析ツールの話。静的解析は PHP のみならず最近の動的言語の一大潮流なので、逃れられない。
+
+[PHPの最高機能、配列を捨てよう!!](https://fortee.jp/phperkaigi-2023/proposal/e00788a4-ef25-49ee-b254-9d2b53e19633)
+
+実はこれも上のセッションと同様の話。
+PHP の静的解析ツールは配列にも (無理矢理) 型が付けられるものが多いが、実行時にも検査できるという点において専用のクラスを作る方が優れている。
+
+[時間を気にせず普通にカンニングもしつつ ISUCON12 本選問題を PHP でやってみる](https://fortee.jp/phperkaigi-2023/proposal/7e212cb2-be37-43e8-b6ee-5236d259fcbf)
+
+個人的に最も楽しみにしていたセッションであり、今回のモリアガリトーク賞 (盛り上がったセッションに運営側から贈られる賞) でもある。
+ネタバレになるが、最終的に (Go で実装された) 本戦優勝スコアを超えている。
+
+{#phper-challenge}
+## PHPer チャレンジ
+
+昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。
+また、今年はさらに作成した問題を解説するセッションにも登壇した。
+今年のトークンは、昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作した。
+
+問題の制作中は大変楽しかったが、まあやりすぎた。
+いかに超絶技巧を凝らすかに注力してしまい、解く楽しさという観点を失ってしまったきらいがある。
+
+(WIP: 解説ブログ記事執筆中。終わったらここにリンク)
+
+{#random-thoughts}
+## 雑多な感想
+
+なんかいろいろ。
+
+* マカロンおいしかった
+* \\ペチパー/
+* 名札便利
+* \\ペチパー/
+* 傘袋便利
+* \\ペチパー/
+* パーカーのデザイン良き
+
+(あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった)
+
+{#outro}
+# おわりに
+
+[去年の参加レポ](/posts/2022-05-01/phperkaigi-2022/#section--next-year) では、来年の目標として次を挙げた。
+
+> * プロポーザルを出す
+> * PHPer チャレンジのトークン問題を 5題作成する
+> * 現地に行く
+> * PHPer チャレンジで圧勝する
+
+プロポーザルに関しては採択されて登壇できたし、PHPer チャレンジは解説もおこなった。また、現地に行くだけでなく、当日スタッフとして参加した。
+4つ目の PHPer チャレンジに関しては、今年は参加していない。
+スタッフをやりながらだと入力する時間も探す時間も取れそうになかったのと、スタッフをやっている関係で少しだけ早く入手してしまうトークンがいくつか存在していたため。
+
+カンファレンス全体の感想についてだが、大規模なカンファレンスにオフラインで参加するのは今回が初めてだったので、その話をしたい。
+
+オンラインとオフラインだと体験が別物になる。そもそもが似て非なるものなのだ。
+向き不向きはあるだろうが、オンラインしか参加したことのないという方は、一度現地参加してみてはいかがだろうか。
+
+さて、参加レポは去年も一昨年もこの言葉で締め括っているので、今年もそれで終わろうと思う。
+
+ではまた来年。
diff --git a/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.ndoc b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.ndoc
deleted file mode 100644
index e9ad3d96..00000000
--- a/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.ndoc
+++ /dev/null
@@ -1,184 +0,0 @@
----
-[article]
-uuid = "21ce39f0-d613-45f2-a760-89c368892d77"
-title = "PHPerKaigi 2023 参加レポ"
-description = "2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。"
-tags = [
- "conference",
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2023-04-04"
-remark = "公開"
-
-[[article.revisions]]
-date = "2023-06-28"
-remark = "トークセッションの記事版の執筆を中止"
----
-
-
- はじめに
-
- 2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。
- 今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。
-
-
- 昨年、一昨年の参加レポはこちら:
-
-
-
-
- スピーカーとして
-
- これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。
-
-
- -
- 詳説「参照」:PHP 処理系の実装から参照を理解する
-
-
- -
- PHPerチャレンジ解説セッション - デジタルサーカス株式会社
-
-
-
-
- PHPer チャレンジの話については後述する。
- 参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。
-
-
-
- 当日スタッフとして
-
- 今回はスピーカーのみならず当日スタッフとしても参加した。
- カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。
-
-
- PHPerKaigi は一般参加者の目線でもよくできたカンファレンスだなあという印象だったのだが、よりその思いを強くした。
- なんとスタッフにとってもよくできたカンファレンスなのである。
-
-
- 反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。
-
-
-
- 参加者として
-
-
- PHPer チャレンジ
-
- 昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。
- また、今年はさらに作成した問題を解説するセッションにも登壇した。
- 今年のトークンは、昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作した。
-
-
- 問題の制作中は大変楽しかったが、まあやりすぎた。
- いかに超絶技巧を凝らすかに注力してしまい、解く楽しさという観点を失ってしまったきらいがある。
-
-
- (WIP: 解説ブログ記事執筆中。終わったらここにリンク)
-
-
-
- 雑多な感想
-
- なんかいろいろ。
-
-
- - マカロンおいしかった
- - \ペチパー/
- - 名札便利
- - \ペチパー/
- - 傘袋便利
- - \ペチパー/
- - パーカーのデザイン良き
-
-
- (あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった)
-
-
-
-
- おわりに
-
- 去年の参加レポでは、来年の目標として次を挙げた。
-
-
-
- - プロポーザルを出す
- - PHPer チャレンジのトークン問題を 5題作成する
- - 現地に行く
- - PHPer チャレンジで圧勝する
-
-
-
- プロポーザルに関しては採択されて登壇できたし、PHPer チャレンジは解説もおこなった。また、現地に行くだけでなく、当日スタッフとして参加した。
- 4つ目の PHPer チャレンジに関しては、今年は参加していない。
- スタッフをやりながらだと入力する時間も探す時間も取れそうになかったのと、スタッフをやっている関係で少しだけ早く入手してしまうトークンがいくつか存在していたため。
-
-
- カンファレンス全体の感想についてだが、大規模なカンファレンスにオフラインで参加するのは今回が初めてだったので、その話をしたい。
-
-
- オンラインとオフラインだと体験が別物になる。そもそもが似て非なるものなのだ。
- 向き不向きはあるだろうが、オンラインしか参加したことのないという方は、一度現地参加してみてはいかがだろうか。
-
-
- さて、参加レポは去年も一昨年もこの言葉で締め括っているので、今年もそれで終わろうと思う。
-
-
- ではまた来年。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.dj b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.dj
new file mode 100644
index 00000000..ba1b7d6e
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.dj
@@ -0,0 +1,64 @@
+---
+[article]
+uuid = "e1568c4c-9bdd-47b9-8b39-939ade4f3ba0"
+title = "PHP カンファレンス福岡 2023 参加レポ"
+description = "2023-06-24 に開催された、PHP カンファレンス福岡に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phpconfuk",
+]
+
+[[article.revisions]]
+date = "2023-06-25"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2023-06-24 に開催された、 [PHP カンファレンス福岡 2023](https://phpcon.fukuoka.jp/2023/) に参加した。
+また、その前日に催された、 [非公式の前夜祭](https://connpass.com/event/282285/) にも参加した。
+前夜祭では、15分の登壇もおこなった。 [登壇の方の資料はこちら。](/slides/2023-06-23/phpconfuk-2023-eve/)
+
+{#sessions-thoughts}
+# セッションの感想
+
+{#eve}
+## 前夜祭
+
+※セッションの題名と発表者名は、 [前夜祭イベントの connpass ページ](https://connpass.com/event/282285/) から引用。
+
+* スクラム(の一部)を導入してよくなったこと (asumikam さん)
+
+ * スクラムの「一部」を導入されたということでしたが、理想的な形で改善が進んでいるように見受けられました。特に、ブランチ運用やデプロイ頻度、フィードバックサイクルに大きく変化が起きているのは驚くべき成果だと感じました。
+
+* 地方の小さな勉強会を一番の活動舞台にする (tomio さん)
+
+ * すさまじいほどの「熱」を感じました。私自身、最近になってカンファレンスや勉強会への参加・登壇を活発におこなうようになったことで、頷く点が多かったです。
+
+{#conference}
+## カンファレンス
+
+※セッションの題名と発表者名は、 [カンファレンスの fortee ページ](https://fortee.jp/phpconfukuoka-2023/proposal/accepted) から引用。
+
+* [育成力 - エンジニアの才能を引き出す環境とチューターの立ち回り - (岡嵜 雄平 さん)](https://fortee.jp/phpconfukuoka-2023/proposal/df5f06e8-900e-4e71-94d7-d0c3cc57a0ac)
+
+ * ちょうど弊チームに新規メンバがジョインしたばかりで、オンボーディングプロセスについて考えていたところの発表でした。すぐにすべてを取り入れるというわけにはいきませんが、弊社での新人育成プロセスの改善につながるヒントをいくつか得られたと思います。
+
+* オブジェクト指向は本当に必要か? (たなかひさてる さん、こいほげ さん)
+
+ * ※当日 D ホールでおこなわれたアンカンファレンスセッションのため、正式タイトル・リンクなし
+ * 私自身、「オブジェクト指向」については色々と言いたいことがあるのですが、だいたいツイートしたこれとこれです。
+
+ * 「オブジェクト指向の話は、パラダイムの異なる複数の言語に触れているかどうかで見え方がまったく異なる印象がある。OOPはどうでもいいです (※個人の感想です)」 ( [Twitter のツイートへのリンク](https://twitter.com/nsfisis/status/1672502935983656960) )
+ * 「OOPは現代の言語で考える意味はほぼない古いパラダイムだよという立場ですが、OOPについてあまり大っぴらに話してると色んなところから刺されそうなんですよね (Twitterは大っぴらじゃないんですか?)」 ( [Twitter のツイートへのリンク](https://twitter.com/nsfisis/status/1672504892244787201) )
+
+* [その説明、コードコメントに書く?コミットメッセージに書く?プルリクエストに書く? (おかしょい/岡田正平 さん)](https://fortee.jp/phpconfukuoka-2023/proposal/ae71f3a7-4c3c-4c87-8816-8426bcc8d325)
+
+ * Twitter にもツイートしましたが、完全に自分の意見と一致していたので、とても共感できました。今後は社内のコードレビュー時に、こちらの資料を貼りつけることにします。
+
+{#outro}
+# おわりに
+
+居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。
+受け取った「熱」が冷める前に、自らの手を動かしていきたい。
diff --git a/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.ndoc b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.ndoc
deleted file mode 100644
index 39763514..00000000
--- a/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.ndoc
+++ /dev/null
@@ -1,106 +0,0 @@
----
-[article]
-uuid = "e1568c4c-9bdd-47b9-8b39-939ade4f3ba0"
-title = "PHP カンファレンス福岡 2023 参加レポ"
-description = "2023-06-24 に開催された、PHP カンファレンス福岡に参加した。"
-tags = [
- "conference",
- "php",
- "phpconfuk",
-]
-
-[[article.revisions]]
-date = "2023-06-25"
-remark = "公開"
----
-
-
-
- セッションの感想
-
- 前夜祭
-
- ※セッションの題名と発表者名は、前夜祭イベントの connpass ページから引用。
-
-
- -
- スクラム(の一部)を導入してよくなったこと (asumikam さん)
-
- -
- スクラムの「一部」を導入されたということでしたが、理想的な形で改善が進んでいるように見受けられました。
- 特に、ブランチ運用やデプロイ頻度、フィードバックサイクルに大きく変化が起きているのは驚くべき成果だと感じました。
-
-
-
- -
- 地方の小さな勉強会を一番の活動舞台にする (tomio さん)
-
- -
- すさまじいほどの「熱」を感じました。
- 私自身、最近になってカンファレンスや勉強会への参加・登壇を活発におこなうようになったことで、頷く点が多かったです。
-
-
-
-
-
-
- カンファレンス
-
- ※セッションの題名と発表者名は、カンファレンスの fortee ページから引用。
-
-
- -
- 育成力 - エンジニアの才能を引き出す環境とチューターの立ち回り - (岡嵜 雄平 さん)
-
- -
- ちょうど弊チームに新規メンバがジョインしたばかりで、オンボーディングプロセスについて考えていたところの発表でした。
- すぐにすべてを取り入れるというわけにはいきませんが、弊社での新人育成プロセスの改善につながるヒントをいくつか得られたと思います。
-
-
-
- -
- オブジェクト指向は本当に必要か? (たなかひさてる さん、こいほげ さん)
-
- -
- ※当日 D ホールでおこなわれたアンカンファレンスセッションのため、正式タイトル・リンクなし
-
- -
- 私自身、「オブジェクト指向」については色々と言いたいことがあるのですが、だいたいツイートしたこれとこれです。
-
- -
- 「オブジェクト指向の話は、パラダイムの異なる複数の言語に触れているかどうかで見え方がまったく異なる印象がある。OOPはどうでもいいです (※個人の感想です)」 (Twitter のツイートへのリンク)
-
- -
- 「OOPは現代の言語で考える意味はほぼない古いパラダイムだよという立場ですが、OOPについてあまり大っぴらに話してると色んなところから刺されそうなんですよね (Twitterは大っぴらじゃないんですか?)」 (Twitter のツイートへのリンク)
-
-
-
-
-
- -
- その説明、コードコメントに書く?コミットメッセージに書く?プルリクエストに書く? (おかしょい/岡田正平 さん)
-
- -
- Twitter にもツイートしましたが、完全に自分の意見と一致していたので、とても共感できました。
- 今後は社内のコードレビュー時に、こちらの資料を貼りつけることにします。
-
-
-
-
-
-
-
- おわりに
-
- 居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。
- 受け取った「熱」が冷める前に、自らの手を動かしていきたい。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj b/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj
new file mode 100644
index 00000000..da7438ab
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj
@@ -0,0 +1,286 @@
+---
+[article]
+uuid = "0ed1ccc8-d437-481c-8cca-2131ce800cc0"
+title = "PHP の処理系を Emscripten で WebAssembly にコンパイルする"
+description = "PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。"
+tags = [
+ "php",
+ "wasm",
+]
+
+[[article.revisions]]
+date = "2023-10-02"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+[Emscripten](https://emscripten.org/) を用いて [PHP の処理系](https://github.com/php/php-src) を [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
+
+なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
+
+{#version}
+# バージョン情報
+
+この記事中で使用するソフトウェア等のバージョンを記載する。
+
+* Ubuntu 22.04 on WSL2
+* Docker version 24.0.6
+* Emscripten 3.1.46
+* Node.js 20.7.0
+* PHP 8.2.10
+
+なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
+
+{#goal}
+# 本記事のゴール
+
+先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。
+このコードはこのあと使うので、`index.mjs` の名前で保存しておくこと。
+
+```javascript
+import { readFile } from 'node:fs/promises';
+import PHPWasm from './php-wasm.mjs'
+
+const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
+
+const { ccall } = await PHPWasm();
+const result = ccall(
+ 'php_wasm_run',
+ 'number', ['string'],
+ [code],
+);
+console.log(`exit code: ${result}`);
+```
+
+標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような `php-wasm.mjs` (とそこから呼び出される `php-wasm.wasm`) を作成する。
+
+{#build}
+# ビルド
+
+{#write-c-entrypoint}
+## C のエントリポイントを書く
+
+先ほどのコードでも使っていたエントリポイントである `php_wasm_run` を用意する。
+
+```c
+#include
+#include
+#include
+#include
+
+int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
+ zend_result result;
+
+ int argc = 1;
+ char* argv[] = { "php.wasm", NULL };
+
+ PHP_EMBED_START_BLOCK(argc, argv);
+
+ result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
+
+ PHP_EMBED_END_BLOCK();
+
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+
+ return result == SUCCESS ? 0 : 1;
+}
+```
+
+ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
+
+まずは `EMSCRIPTEN_KEEPALIVE` について。
+これは Emscripten が用意している特殊なマクロである。
+このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。
+もしこれを付け忘れると、未使用の関数とみなされ削除される。
+
+次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。
+出力バッファから出力させるためだけなら改行を出力させなくとも `fflush()` だけで事足りると考えたのだが、ないと動かなかったので追加した。
+これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。
+改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
+
+{#compile-to-wasm}
+## WebAssembly にコンパイルする
+
+それでは WebAssembly にコンパイルしていこう。ここからは `Dockerfile` 上のコマンドとして操作を示す。
+
+まずは [Emscripten 公式が提供している Docker イメージ](https://hub.docker.com/r/emscripten/emsdk) を使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
+
+```dockerfile
+FROM emscripten/emsdk:3.1.46 AS wasm-builder
+```
+
+次に、 [php/php-src](https://github.com/php/php-src) から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。
+有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
+
+```dockerfile
+RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
+
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ autoconf \
+ bison \
+ pkg-config \
+ re2c \
+ && \
+ :
+```
+
+続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
+
+```dockerfile
+RUN cd php-src && \
+ ./buildconf --force && \
+ emconfigure ./configure \
+ --disable-all \
+ --disable-mbregex \
+ --disable-fiber-asm \
+ --disable-cli \
+ --disable-cgi \
+ --disable-phpdbg \
+ --enable-embed=static \
+ --enable-mbstring \
+ --without-iconv \
+ --without-libxml \
+ --without-pcre-jit \
+ --without-pdo-sqlite \
+ --without-sqlite3 \
+ && \
+ EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
+ mv libs/libphp.a .. && \
+ make clean && \
+ git clean -fd && \
+ :
+```
+
+ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
+
+まず、`buildconf` は PHP 処理系をビルドするときに (Emscripten とは関係なく) 使うツールである。
+このツールの最も重要な仕事は、`configure` の生成である。
+
+次に `configure` するわけだが、ここで `emconfigure` を使う。
+これを使うことで、Emscripten が上手く諸々のツールチェインを WebAssembly のビルド向けに調整しながら `configure` してくれる。
+
+`configure` の後ろに指定してあるフラグは、通常の PHP 処理系のビルドで使う `configure` と同じなので、詳しくはそちらの `cofigure --help` を参照していただきたい。
+ほとんどは、機能の無効化のために指定している (依存するライブラリを減らし、ビルドをより簡単にするため)。
+
+通常の C のビルドなら、`configure` の次は `make` するところだが、ここでも `emmake` を使う。
+役割はほとんど `emconfigure` と同様である。
+指定してある `EMCC_CFLAGS` という環境変数は、Emscripten の C コンパイラへのフラグで、ここでは `ERROR_ON_UNDEFINED_SYMBOLS` を無効化している。
+これにより、コンパイル中に出現した解決できなかったシンボルを無視するようになる (代わりに、そのシンボルを呼ぼうとしたタイミングで実行時エラーになる)。
+すべての依存を完全に解決するのは面倒なので、あまり使わない機能については無視してもよいだろう。
+
+ここまでを実行すると `libs/libphp.a` が生成される。これは後で使うので移動させている。
+
+さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。
+`Dockerfile` と同じ場所に `php-wasm.c` という名前で保存し、次のようにする。
+
+```dockerfile
+COPY php-wasm.c /src/
+
+RUN cd php-src && \
+ emcc \
+ -c \
+ -o php-wasm.o \
+ -I . \
+ -I TSRM \
+ -I Zend \
+ -I main \
+ ../php-wasm.c \
+ && \
+ mv php-wasm.o .. && \
+ make clean && \
+ git clean -fd && \
+ :
+```
+
+`emcc` は `cc` (C コンパイラ/リンカ) の Emscripten 版で、`-c` は「コンパイル」の意。
+`-o` や `-I` は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
+
+`libphp.a` と `php-wasm.o` が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。
+これにも `emcc` コマンドを使う。
+
+```dockerfile
+RUN emcc \
+ -s ENVIRONMENT=node \
+ -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
+ -s EXPORTED_RUNTIME_METHODS='["ccall"]' \
+ -s EXPORT_ES6=1 \
+ -s INITIAL_MEMORY=16777216 \
+ -s INVOKE_RUN=0 \
+ -s MODULARIZE=1 \
+ -o php-wasm.js \
+ php-wasm.o \
+ libphp.a \
+ ;
+```
+
+それぞれのフラグについて解説する。
+
+`-s ENVIRONMENT=node` は、生成する WebAssembly/JavaScript の実行環境を指定する。
+今回は `node` を指定しているので、Node.js 向けのファイルが生成される。
+
+`-s ERROR_ON_UNDEFINED_SYMBOLS=0` についてはすでに述べたので省略する。
+
+`-s EXPORTED_RUNTIME_METHODS='["ccall"]'` は、生成される JavaScript から公開される API である。
+すでに `index.mjs` で使用しているが、`ccall('関数名', '返り値の型', ['仮引数の型', ...], ['実引数', ...])` のように使う。
+
+`-s EXPORT_ES6=1` は、JavaScript コードを ECMAScript 6 に準拠した module として生成する。
+これを指定することで、`require()` ではなく `import` できる JavaScript を生成させられる。
+
+`-s INITIAL_MEMORY=16777216` は呼んで字のごとく。用途に合わせて適当に決めてほしい。
+
+`-s INVOKE_RUN=0` は、module をロードしたときに勝手に `main()` を呼ぶかどうか (だと思う)。
+今回は `php_wasm_run()` しか使うつもりがないので切っている。
+
+`-s MODULARIZE=1` は、実質的にほぼ必須のオプションであり、1 を指定することで「WebAssembly module をインスタンス化する関数」をエクスポートするような JavaScript ファイルを生成するようになる。
+これを指定しないと、生成物の JavaScript ファイルを読み込むと WebAssembly module が即座にインスタンス化されてしまい、起動のタイミングを制御できない。
+
+ここまで実行すると、`php-wasm.js` と `php-wasm.wasm` が作られる。
+では、ここからはこれらの実行環境を作っていこう。
+
+といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。
+先ほど掲載した JavaScript のコードは、`Dockerfile` と同じディレクトリに `index.mjs` で配置すること。
+
+```dockerfile
+FROM node:20.7
+
+WORKDIR /app
+COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
+COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
+COPY index.mjs /app/
+
+ENTRYPOINT ["node", "index.mjs"]
+```
+
+{#run}
+# 実行
+
+`Dockerfile`、`php-wasm.c`、`index.mjs` を用意したら、Docker コンテナをビルドして実行する。
+
+```dockerfile
+$ docker build -t php-wasm .
+$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
+Hello, World!
+
+
+exit code: 0
+```
+
+{#outro}
+# まとめ
+
+[ここまでをまとめた Git リポジトリ](https://github.com/nsfisis/tiny-php.wasm) を用意した。
+簡単にコンパイルできるので、興味があれば試してみてほしい。
+
+{#references}
+# 参考リンク
+
+* [php/php-src: ビルドの方法について](https://github.com/php/php-src)
+* [Emscripten: チュートリアル](https://emscripten.org/docs/getting_started/Tutorial.html)
+* [Emscripten: ビルドの基本](https://emscripten.org/docs/compiling/Building-Projects.html#building-projects)
+* [Emscripten: `emcc` などのリファレンス](https://emscripten.org/docs/tools_reference/emcc.html#emccdoc)
+* [Emscripten: 生成される JavaScript の API](https://emscripten.org/docs/api_reference/module.html#module)
diff --git a/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc b/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc
deleted file mode 100644
index 19143c44..00000000
--- a/vhosts/blog/content/posts/2023-10-02/compile-php-runtime-to-wasm.ndoc
+++ /dev/null
@@ -1,335 +0,0 @@
----
-[article]
-uuid = "0ed1ccc8-d437-481c-8cca-2131ce800cc0"
-title = "PHP の処理系を Emscripten で WebAssembly にコンパイルする"
-description = "PHP の処理系 (php/php-src) を Emscripten で WebAssembly にコンパイルし、任意のコードを隔離された環境で評価できるようにした。"
-tags = [
- "php",
- "wasm",
-]
-
-[[article.revisions]]
-date = "2023-10-02"
-remark = "公開"
----
-
-
- はじめに
-
- Emscripten を用いて PHP の処理系を WebAssembly にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
-
-
- なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
-
-
-
- バージョン情報
-
- この記事中で使用するソフトウェア等のバージョンを記載する。
-
-
- - Ubuntu 22.04 on WSL2
- - Docker version 24.0.6
- - Emscripten 3.1.46
- - Node.js 20.7.0
- - PHP 8.2.10
-
-
- なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
-
-
-
- 本記事のゴール
-
- 先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。
- このコードはこのあと使うので、index.mjs の名前で保存しておくこと。
-
-
-
-
-
- 標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような php-wasm.mjs (とそこから呼び出される php-wasm.wasm) を作成する。
-
-
-
- ビルド
-
- C のエントリポイントを書く
-
- 先ほどのコードでも使っていたエントリポイントである php_wasm_run を用意する。
-
-
-
- #include
- #include
- #include
-
- int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
- zend_result result;
-
- int argc = 1;
- char* argv[] = { "php.wasm", NULL };
-
- PHP_EMBED_START_BLOCK(argc, argv);
-
- result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
-
- PHP_EMBED_END_BLOCK();
-
- fprintf(stdout, "\n");
- fflush(stdout);
- fprintf(stderr, "\n");
- fflush(stderr);
-
- return result == SUCCESS ? 0 : 1;
- }
- ]]>
-
-
- ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
-
-
- まずは EMSCRIPTEN_KEEPALIVE について。
- これは Emscripten が用意している特殊なマクロである。
- このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。
- もしこれを付け忘れると、未使用の関数とみなされ削除される。
-
-
- 次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。
- 出力バッファから出力させるためだけなら改行を出力させなくとも fflush() だけで事足りると考えたのだが、ないと動かなかったので追加した。
- これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。
- 改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
-
-
-
- WebAssembly にコンパイルする
-
- それでは WebAssembly にコンパイルしていこう。ここからは Dockerfile 上のコマンドとして操作を示す。
-
-
- まずは Emscripten 公式が提供している Docker イメージを使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
-
-
-
-
-
- 次に、php/php-src から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。
- 有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
-
-
-
-
-
- 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
-
-
-
-
-
- ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
-
-
- まず、buildconf は PHP 処理系をビルドするときに (Emscripten とは関係なく) 使うツールである。
- このツールの最も重要な仕事は、configure の生成である。
-
-
- 次に configure するわけだが、ここで emconfigure を使う。
- これを使うことで、Emscripten が上手く諸々のツールチェインを WebAssembly のビルド向けに調整しながら configure してくれる。
-
-
- configure の後ろに指定してあるフラグは、通常の PHP 処理系のビルドで使う configure と同じなので、詳しくはそちらの cofigure --help を参照していただきたい。
- ほとんどは、機能の無効化のために指定している (依存するライブラリを減らし、ビルドをより簡単にするため)。
-
-
- 通常の C のビルドなら、configure の次は make するところだが、ここでも emmake を使う。
- 役割はほとんど emconfigure と同様である。
- 指定してある EMCC_CFLAGS という環境変数は、Emscripten の C コンパイラへのフラグで、ここでは ERROR_ON_UNDEFINED_SYMBOLS を無効化している。
- これにより、コンパイル中に出現した解決できなかったシンボルを無視するようになる (代わりに、そのシンボルを呼ぼうとしたタイミングで実行時エラーになる)。
- すべての依存を完全に解決するのは面倒なので、あまり使わない機能については無視してもよいだろう。
-
-
- ここまでを実行すると libs/libphp.a が生成される。これは後で使うので移動させている。
-
-
- さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。
- Dockerfile と同じ場所に php-wasm.c という名前で保存し、次のようにする。
-
-
-
-
-
- emcc は cc (C コンパイラ/リンカ) の Emscripten 版で、-c は「コンパイル」の意。
- -o や -I は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
-
-
- libphp.a と php-wasm.o が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。
- これにも emcc コマンドを使う。
-
-
-
-
-
- それぞれのフラグについて解説する。
-
-
- -s ENVIRONMENT=node は、生成する WebAssembly/JavaScript の実行環境を指定する。
- 今回は node を指定しているので、Node.js 向けのファイルが生成される。
-
-
- -s ERROR_ON_UNDEFINED_SYMBOLS=0 についてはすでに述べたので省略する。
-
-
- -s EXPORTED_RUNTIME_METHODS='["ccall"]' は、生成される JavaScript から公開される API である。
- すでに index.mjs で使用しているが、ccall('関数名', '返り値の型', ['仮引数の型', ...], ['実引数', ...]) のように使う。
-
-
- -s EXPORT_ES6=1 は、JavaScript コードを ECMAScript 6 に準拠した module として生成する。
- これを指定することで、require() ではなく import できる JavaScript を生成させられる。
-
-
- -s INITIAL_MEMORY=16777216 は呼んで字のごとく。用途に合わせて適当に決めてほしい。
-
-
- -s INVOKE_RUN=0 は、module をロードしたときに勝手に main() を呼ぶかどうか (だと思う)。
- 今回は php_wasm_run() しか使うつもりがないので切っている。
-
-
- -s MODULARIZE=1 は、実質的にほぼ必須のオプションであり、1 を指定することで「WebAssembly module をインスタンス化する関数」をエクスポートするような JavaScript ファイルを生成するようになる。
- これを指定しないと、生成物の JavaScript ファイルを読み込むと WebAssembly module が即座にインスタンス化されてしまい、起動のタイミングを制御できない。
-
-
- ここまで実行すると、php-wasm.js と php-wasm.wasm が作られる。
- では、ここからはこれらの実行環境を作っていこう。
-
-
- といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。
- 先ほど掲載した JavaScript のコードは、Dockerfile と同じディレクトリに index.mjs で配置すること。
-
-
-
-
-
-
-
- 実行
-
- Dockerfile、php-wasm.c、index.mjs を用意したら、Docker コンテナをビルドして実行する。
-
-
-
-
-
-
-
-
diff --git a/vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj b/vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj
new file mode 100644
index 00000000..1347d901
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj
@@ -0,0 +1,22 @@
+---
+[article]
+uuid = "78419bf2-a1e6-421f-875b-3d93e777b04f"
+title = "放送大学に入学しました"
+description = "放送大学に入学しました。頑張ります。"
+tags = [
+ "ouj",
+]
+
+[[article.revisions]]
+date = "2023-10-13"
+remark = "公開"
+---
+{#i-entered-ouj}
+# 放送大学に入学しました
+
+とあるきっかけがあり、もう一度大学生をすることにしました。
+仕事のほうも、これまでどおりフルタイムで続けていきます。
+
+黙っているよりも公表したほうがモチベーションの向上に繋がるだろうと思い、このブログに記事として載せました。
+
+以上、短いですが報告でした。
diff --git a/vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.ndoc b/vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.ndoc
deleted file mode 100644
index 31927141..00000000
--- a/vhosts/blog/content/posts/2023-10-13/i-entered-the-open-university-of-japan.ndoc
+++ /dev/null
@@ -1,28 +0,0 @@
----
-[article]
-uuid = "78419bf2-a1e6-421f-875b-3d93e777b04f"
-title = "放送大学に入学しました"
-description = "放送大学に入学しました。頑張ります。"
-tags = [
- "ouj",
-]
-
-[[article.revisions]]
-date = "2023-10-13"
-remark = "公開"
----
-
-
- 放送大学に入学しました
-
- とあるきっかけがあり、もう一度大学生をすることにしました。
- 仕事のほうも、これまでどおりフルタイムで続けていきます。
-
-
- 黙っているよりも公表したほうがモチベーションの向上に繋がるだろうと思い、このブログに記事として載せました。
-
-
- 以上、短いですが報告でした。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-12-03/isucon-13.dj b/vhosts/blog/content/posts/2023-12-03/isucon-13.dj
new file mode 100644
index 00000000..991ef433
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-12-03/isucon-13.dj
@@ -0,0 +1,75 @@
+---
+[article]
+uuid = "d0c404bb-4700-4a6f-9911-621e9872d8c6"
+title = "ISUCON 13 に参加した"
+description = "ISUCON 13 に参加した。チーム名「うつしもゆ」、最終スコア 13,580 点"
+tags = [
+ "isucon",
+]
+
+[[article.revisions]]
+date = "2023-12-03"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+先日 11月25日、 [ISUCON 13](https://isucon.net/archives/57801192.html) に参加した。
+ISUCON への参加は今回が初めてとなる。
+私 nsfisis の1人チーム「うつしもゆ」として参加し、最終スコアは 13,580 点だった。使用言語は Go。
+
+::: note
+「ISUCON」は、LINEヤフー株式会社の商標または登録商標です。 [ISUCON 公式サイトはこちら。](https://isucon.net/)
+:::
+
+{#goals}
+# 目標
+
+今回は初参加ということもあり、目標を以下のように定めた。
+
+* 正のスコアを取る
+
+ * ISUCON ではサーバ動作の整合性がチェックされ、失敗するとスコア 0 となる
+
+* 速度改善以外に時間を浪費しない (= ハマらない)
+
+ * プロビジョニング、デバッグ、ミドルウェアの設定方法の調査など、性能改善に寄与しない時間を最小限にする
+
+{#strategy}
+# 戦略
+
+ISUCON で高スコアを出す戦略については、戦闘力の高い方々が良質な記事を書いてくださっている。
+ここでは、上述したような低い目標を達成するための戦略について書こうと思う。
+
+{#do-not-destroy-environment}
+## 環境を破壊しない
+
+ミドルウェアの設定やアプリケーションコードなど、変更を加えるあらゆるものは、必ずバックアップを取るか Git で管理する。
+復旧不能になって環境ごと作り直すことだけは必ず避ける。
+
+{#revert-changes-immediately}
+## すぐに変更を取り消す
+
+それでも壊してしまったときは、即座に変更を取り消す。壊れた理由を調べることに固執しない。
+
+{#do-small-deployment}
+## 小さくデプロイする
+
+一度に複数の変更を加えず、可能な限り小さな単位でデプロイする。そしてその都度ベンチマークを走らせ、整合性チェックが通るかどうかを (当然速くなっているかどうかも) 確かめる。
+
+{#use-familiar-tools}
+## 使い慣れた道具を使う
+
+使用する言語、ミドルウェア、ツール類を、使い慣れたものに限定する。
+「このツールのオプションはほとんどそらで指定できる」と言えるようなものだけを使う。
+「自分では使ったことがないが ISUCON 強者がお勧めしていた」といった理由でツールを選定しない (もちろん、本番までに練習して習熟するという選択肢は存在する)。
+
+{#performance-optimization}
+# パフォーマンスの最適化
+
+もっと強い人の記事を参考にしてほしい。
+
+{#outro}
+# おわりに
+
+事前の準備も含めて、大変楽しいイベントだった。次回があるなら是非また参加したい。その際は、順位やスコアを目標として立てられるようになりたいものである。
diff --git a/vhosts/blog/content/posts/2023-12-03/isucon-13.ndoc b/vhosts/blog/content/posts/2023-12-03/isucon-13.ndoc
deleted file mode 100644
index 30b82b9f..00000000
--- a/vhosts/blog/content/posts/2023-12-03/isucon-13.ndoc
+++ /dev/null
@@ -1,92 +0,0 @@
----
-[article]
-uuid = "d0c404bb-4700-4a6f-9911-621e9872d8c6"
-title = "ISUCON 13 に参加した"
-description = "ISUCON 13 に参加した。チーム名「うつしもゆ」、最終スコア 13,580 点"
-tags = [
- "isucon",
-]
-
-[[article.revisions]]
-date = "2023-12-03"
-remark = "公開"
----
-
-
- はじめに
-
- 先日 11月25日、ISUCON 13 に参加した。
- ISUCON への参加は今回が初めてとなる。
- 私 nsfisis の1人チーム「うつしもゆ」として参加し、最終スコアは 13,580 点だった。使用言語は Go。
-
-
- 「ISUCON」は、LINEヤフー株式会社の商標または登録商標です。ISUCON 公式サイトはこちら。
-
-
-
- 目標
-
- 今回は初参加ということもあり、目標を以下のように定めた。
-
-
- -
- 正のスコアを取る
-
- - ISUCON ではサーバ動作の整合性がチェックされ、失敗するとスコア 0 となる
-
-
- -
- 速度改善以外に時間を浪費しない (= ハマらない)
-
- - プロビジョニング、デバッグ、ミドルウェアの設定方法の調査など、性能改善に寄与しない時間を最小限にする
-
-
-
-
-
- 戦略
-
- ISUCON で高スコアを出す戦略については、戦闘力の高い方々が良質な記事を書いてくださっている。
- ここでは、上述したような低い目標を達成するための戦略について書こうと思う。
-
-
- 環境を破壊しない
-
- ミドルウェアの設定やアプリケーションコードなど、変更を加えるあらゆるものは、必ずバックアップを取るか Git で管理する。
- 復旧不能になって環境ごと作り直すことだけは必ず避ける。
-
-
-
-
- 小さくデプロイする
-
- 一度に複数の変更を加えず、可能な限り小さな単位でデプロイする。そしてその都度ベンチマークを走らせ、整合性チェックが通るかどうかを (当然速くなっているかどうかも) 確かめる。
-
-
-
-
-
-
- おわりに
-
- 事前の準備も含めて、大変楽しいイベントだった。次回があるなら是非また参加したい。その際は、順位やスコアを目標として立てられるようになりたいものである。
-
-
-
diff --git a/vhosts/blog/content/posts/2023-12-31/2023-reflections.dj b/vhosts/blog/content/posts/2023-12-31/2023-reflections.dj
new file mode 100644
index 00000000..61c09ab7
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-12-31/2023-reflections.dj
@@ -0,0 +1,76 @@
+---
+[article]
+uuid = "bfdeed72-dd32-4d55-887f-ba004701ff4c"
+title = "2023年の振り返り"
+description = "2023年にやったことを振り返る"
+tags = [
+]
+
+[[article.revisions]]
+date = "2023-12-31"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+男もすなる年末の振り返りといふものを女もしてみむとてするなり。
+
+{#conferences}
+# 登壇・カンファレンススタッフ
+
+勉強会やカンファレンスで登壇したりスタッフをしたりし始めたのは今年かららしい。
+LT 等も含めて計 11 回の登壇をおこなった。
+
+* PHP 勉強会@東京での登壇 (計 8 回)
+
+ * [第 148 回](/slides/2023-01-18/phpstudy-tokyo-148/)
+ * [第 149 回](/slides/2023-02-15/phpstudy-tokyo-149/)
+ * [第 150 回](/slides/2023-03-15/phpstudy-tokyo-150/)
+ * [第 151 回](/slides/2023-04-12/phpstudy-tokyo-151/)
+ * [第 153 回](/slides/2023-06-21/phpstudy-tokyo-153/)
+ * [第 154 回](/slides/2023-07-26/phpstudy-tokyo-154/)
+ * [第 155 回](/slides/2023-08-24/phpstudy-tokyo-155/)
+ * [第 157 回](/slides/2023-10-25/phpstudy-tokyo-157/)
+
+* PHPerKaigi 2023 での登壇
+
+ * [レギュラートーク](/slides/2023-03-24/phperkaigi-2023/)
+ * [トークン解説セッション](/slides/2023-03-25/phperkaigi-2023-tokens/)
+
+* PHPerKaigi 2023 での当日スタッフ業
+* [非公式でおこなわれた PHP カンファレンス福岡 2023 の 前夜祭イベントでの登壇](/slides/2023-06-23/phpconfuk-2023-eve/)
+* PHPerKaigi 2024 でのコアスタッフ業
+
+{#articles}
+# 書いた記事
+
+登壇が増えたためか記事を書く機会が減ってしまった。
+特に社内記事の本数が大きく減少しており、一昨年は約 100 本、昨年は約 60 本の社内記事を書いていたが、今年は 30 本強に留まった。
+その頃と比べると文章を書く筋肉が衰えているように感じる。
+
+* 社外記事 (このブログ): 8本
+* 社内記事: 34本
+
+ * 年間で最も記事を書いた人として社内表彰された
+
+{#coding}
+# 作ったもの
+
+ガラクタをいくつか作った。役には立たないが、作るのが楽しいという効用がある。
+
+* [PHPerKaigi2023-tokens](https://github.com/nsfisis/PHPerKaigi2023-tokens) : PHPerKaigi 2023 でおこなわれた PHPer チャレンジという企画で用意した問題
+* [twitter2x-quine](https://github.com/nsfisis/twitter2x-quine) : Twitter のロゴを 𝕏 にする変則 quine
+* [9-puzzle-quine.php](https://github.com/nsfisis/9-puzzle-quine.php) : 9パズルが遊べる変則 quine
+
+{#misc}
+# その他
+
+* [放送大学に入学した](/posts/2023-10-13/i-entered-the-open-university-of-japan/)
+* [ISUCON に初参加した](/posts/2023-12-03/isucon-13/)
+* データベーススペシャリストを取得した
+* 漢検2級を取得した
+
+{#outro}
+# おわりに
+
+今年も大変お世話になりました。よいお年を!
diff --git a/vhosts/blog/content/posts/2023-12-31/2023-reflections.ndoc b/vhosts/blog/content/posts/2023-12-31/2023-reflections.ndoc
deleted file mode 100644
index 8952926c..00000000
--- a/vhosts/blog/content/posts/2023-12-31/2023-reflections.ndoc
+++ /dev/null
@@ -1,92 +0,0 @@
----
-[article]
-uuid = "bfdeed72-dd32-4d55-887f-ba004701ff4c"
-title = "2023年の振り返り"
-description = "2023年にやったことを振り返る"
-tags = [
-]
-
-[[article.revisions]]
-date = "2023-12-31"
-remark = "公開"
----
-
-
- はじめに
-
- 男もすなる年末の振り返りといふものを女もしてみむとてするなり。
-
-
-
- 登壇・カンファレンススタッフ
-
- 勉強会やカンファレンスで登壇したりスタッフをしたりし始めたのは今年かららしい。
- LT 等も含めて計 11 回の登壇をおこなった。
-
-
-
-
- 書いた記事
-
- 登壇が増えたためか記事を書く機会が減ってしまった。
- 特に社内記事の本数が大きく減少しており、一昨年は約 100 本、昨年は約 60 本の社内記事を書いていたが、今年は 30 本強に留まった。
- その頃と比べると文章を書く筋肉が衰えているように感じる。
-
-
- - 社外記事 (このブログ): 8本
- - 社内記事: 34本
-
- - 年間で最も記事を書いた人として社内表彰された
-
-
-
-
-
- 作ったもの
-
- ガラクタをいくつか作った。役には立たないが、作るのが楽しいという効用がある。
-
-
-
-
-
- おわりに
-
- 今年も大変お世話になりました。よいお年を!
-
-
-
diff --git a/vhosts/blog/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj b/vhosts/blog/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj
new file mode 100644
index 00000000..483b0b9a
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj
@@ -0,0 +1,205 @@
+---
+[article]
+uuid = "05cb16e1-05bc-4359-bc06-88ac20510740"
+title = "【Neovim】 空の PHP ファイルに namespace 宣言を挿入する"
+description = "Neovim で空の PHP ファイルを開いたとき、ディレクトリの構造に基づいて自動的に namespace 宣言を挿入するようにする。"
+tags = [
+ "neovim",
+ "php",
+]
+
+[[article.revisions]]
+date = "2024-01-10"
+remark = "公開"
+---
+::: note
+この記事は [Vim 駅伝](https://vim-jp.org/ekiden/) #136 の記事です。
+:::
+
+{#intro}
+# やりたいこと
+
+Neovim で空の PHP ファイルを開いたとき、そのファイルが置かれているディレクトリの構造に基づいて、自動的に `namespace` 宣言を挿入したい。具体的には、トップレベルの名前空間が `MyNamespace` であり、ファイル `src/Foo/Bar/Baz.php` を開いたときに、そのファイルが空であるなら、次のようなテンプレートが自動的に挿入されてほしい。
+
+```php
+
-
- この記事は Vim 駅伝 #136 の記事です。
-
-
- やりたいこと
-
- Neovim で空の PHP ファイルを開いたとき、そのファイルが置かれているディレクトリの構造に基づいて、自動的に namespace 宣言を挿入したい。具体的には、トップレベルの名前空間が MyNamespace であり、ファイル src/Foo/Bar/Baz.php を開いたときに、そのファイルが空であるなら、次のようなテンプレートが自動的に挿入されてほしい。
-
-
-
-
-
-
- バージョン情報
-
-
-
-
- 今回は Lua で処理を記述したため、Vim では動作しない。以下の説明でも Neovim に絞って述べる。
- また、パス区切りがスラッシュである前提で記述したため、Windows には対応していない。
-
-
-
- ftplugin を用意する
-
- Neovim には特定のファイルタイプに対して特別な処理をおこなうための ftplugin と呼ばれる仕組みがある。
- Neovim の設定を置くディレクトリ (例えば ~/.config/nvim) の配下に ftplugin/<FILE_TYPE>.vim または ftplugin/<FILE_TYPE>.lua というファイルを配置すると、その <FILE_TYPE> が読み込まれたときにそのファイルが自動的に実行される。
-
-
- 今回は、Neovim がデフォルトで用意している PHP 用 ftplugin が動作したあとに追加の処理をおこないたいので、after/ftplugin/php.{vim,lua} というファイルを配置する。名前から察せられるとおり、after/ftplugin 以下のファイルは ftplugin 以下のファイルよりもあとに実行される。
-
-
- この記事では Lua で処理を記述するため、拡張子には .lua を用いる。
- これ以降載せるコードは、すべて after/ftplugin/php.lua の中に記述している。
-
-
-
- 二重読み込みを防ぐ
-
- ファイルタイプは読み込んだあとに変更されることもあるので、ftplugin は複数回実行されうる。
- 二重読み込みを防ぐために、did_ftplugin_<FILE_TYPE>_after というバッファローカル変数を定義しておくのが慣習となっている。
-
-
-
-
-
-
- 実装する
-
- では実装していこう。今回私は次のようなロジックとした。以降、「今 Neovim で開いた PHP ファイル」のことを「対象ファイル」と呼ぶことにする。
-
-
- - 対象ファイルが空でなければ何もしない
- - 対象ファイルが置かれたディレクトリを上に辿って、
composer.json を見つける
- composer.json の autoload.psr-4 を見て、トップレベルの名前空間とディレクトリを特定する
- - 対象ファイルが置かれたディレクトリが、トップレベルのディレクトリを基準としてどのようにネストしているか調べる
- - オートロードの設定と照らし合わせて、対象ファイルが属すべき名前空間を特定する
- - PHP の開始タグとともに
namespace 宣言を挿入する
-
-
- 実装を簡単にするため、Composer を用いない場合や PSR 4 以外のオートロード規則を使う場合には対応しない。少々長くなるが、以下にスクリプト全文を載せる。
-
-
-
-
-
-
- おわりに
-
- 簡易的な実装だが、多くのケースではうまく動いているようだ。
- 最大の問題は PSR 4 に準拠しないフレームワークを用いているとまったく役に立たないことで、今まさに職場で困っている。
- こちらはいずれ改良したい。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.dj b/vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.dj
new file mode 100644
index 00000000..89ecd7b4
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.dj
@@ -0,0 +1,144 @@
+---
+[article]
+uuid = "210673d0-c19e-4195-a280-968a0729dd41"
+title = "【備忘録】 個人用サーバに WireGuard を導入する"
+description = "個人用サービスのセルフホストに使っているサーバに WireGuard を導入する作業をしたメモ"
+tags = [
+ "note-to-self",
+ "wireguard",
+]
+
+[[article.revisions]]
+date = "2024-02-03"
+remark = "公開"
+
+[[article.revisions]]
+date = "2024-02-17"
+remark = "80 番ポートについて追記"
+---
+{#intro}
+# はじめに
+
+個人用サービスのセルフホストに使っているサーバに [WireGuard](https://www.wireguard.com/) を導入する作業をしたのでメモ。
+
+登場するホストは以下のとおり:
+
+* サーバ (Ubuntu): `10.10.1.1`
+* クライアント 1 (Windows): `10.10.1.2`
+* クライアント 2 (Android): `10.10.1.3`
+
+後ろの IP アドレスは VPN 内で使用するプライベート IP アドレス。
+
+{#install-wireguard-server}
+# WireGuard のインストール: サーバ
+
+まずは個人用サービスをホストしている Ubuntu のサーバに WireGuard をインストールする。
+
+```
+$ sudo apt install wireguard
+```
+
+次に、WireGuard で使用する鍵を生成する。
+
+```
+$ wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pub
+$ sudo chmod 600 /etc/wireguard/server.{key,pub}
+```
+
+{#install-wireguard-client}
+# WireGuard のインストール: クライアント
+
+公式サイトから各 OS 向けのクライアントソフトウェアを入手し、インストールする。次に、設定をおこなう。
+
+```ini
+# クライアント 1 の場合
+[Interface]
+Address = 10.10.1.2/32
+PrivateKey = <クライアント 1 の秘密鍵>
+
+[Peer]
+PublicKey = <サーバの公開鍵>
+AllowedIPs = <サーバの外部 IP アドレス>/32
+Endpoint = <サーバの外部 IP アドレス>:51820
+```
+
+```ini
+# クライアント 2 の場合
+[Interface]
+Address = 10.10.1.3/32
+PrivateKey = <クライアント 2 の秘密鍵>
+
+[Peer]
+PublicKey = <サーバの公開鍵>
+AllowedIPs = <サーバの外部 IP アドレス>/32
+Endpoint = <サーバの外部 IP アドレス>:51820
+```
+
+`PrivateKey` や `PublicKey` は鍵ファイルのパスではなく中身を書くことに注意。
+
+{#configure-wireguard}
+# WireGuard の設定
+
+一度サーバへ戻り、WireGuard の設定ファイルを書く。
+
+```
+$ sudo vim /etc/wireguard/wg0.conf
+```
+
+```ini
+[Interface]
+Address = 10.10.1.1/32
+SaveConfig = true
+PrivateKey = <サーバの秘密鍵>
+ListenPort = 51820
+
+[Peer]
+PublicKey = <クライアント 1 の公開鍵>
+AllowedIPs = 10.10.1.2/32
+
+[Peer]
+PublicKey = <クライアント 2 の公開鍵>
+AllowedIPs = 10.10.1.3/32
+```
+
+次に、WireGuard のサービスを起動する。
+
+```
+$ sudo systemctl enable wg-quick@wg0
+$ sudo systemctl start wg-quick@wg0
+```
+
+{#configure-firewall}
+# ファイアウォールの設定
+
+続けてファイアウォールを設定する。まずは WireGuard が使用する UDP のポートを開き、`wg0` を通る通信を許可する。
+
+```
+$ sudo ufw allow 51820/udp
+$ sudo ufw allow in on wg0
+$ sudo ufw allow out on wg0
+```
+
+次に、80 や 443 などの必要なポートについて、`wg0` を経由してのアクセスのみ許可する。
+
+```
+$ sudo ufw allow in on wg0 to any port 80 proto tcp
+$ sudo ufw allow in on wg0 to any port 443 proto tcp
+```
+
+最後に、`ufw` を有効にする。
+
+```
+$ sudo ufw status
+$ sudo ufw enable
+```
+
+{#connect-each-other}
+# 接続する
+
+これで、各クライアントで VPN を有効にすると、当該サーバの 80 ポートや 443 ポートにアクセスできるようになったはずだ。念のため VPN を切った状態でアクセスできないことも確認しておくとよいだろう。
+
+{#edit-80-port}
+# 追記: 80 番ポートについて
+
+Let's Encrypt でサーバの証明書を取得している場合、80 番ポートを空けておく必要がある。気づかないうちに証明書が切れないよう注意。
diff --git a/vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.ndoc b/vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.ndoc
deleted file mode 100644
index 9dee8fce..00000000
--- a/vhosts/blog/content/posts/2024-02-03/install-wireguard-on-personal-server.ndoc
+++ /dev/null
@@ -1,180 +0,0 @@
----
-[article]
-uuid = "210673d0-c19e-4195-a280-968a0729dd41"
-title = "【備忘録】 個人用サーバに WireGuard を導入する"
-description = "個人用サービスのセルフホストに使っているサーバに WireGuard を導入する作業をしたメモ"
-tags = [
- "note-to-self",
- "wireguard",
-]
-
-[[article.revisions]]
-date = "2024-02-03"
-remark = "公開"
-
-[[article.revisions]]
-date = "2024-02-17"
-remark = "80 番ポートについて追記"
----
-
-
- はじめに
-
- 個人用サービスのセルフホストに使っているサーバに WireGuard を導入する作業をしたのでメモ。
-
-
- 登場するホストは以下のとおり:
-
-
- - サーバ (Ubuntu):
10.10.1.1
- - クライアント 1 (Windows):
10.10.1.2
- - クライアント 2 (Android):
10.10.1.3
-
-
- 後ろの IP アドレスは VPN 内で使用するプライベート IP アドレス。
-
-
-
- WireGuard のインストール: サーバ
-
- まずは個人用サービスをホストしている Ubuntu のサーバに WireGuard をインストールする。
-
-
-
-
-
- 次に、WireGuard で使用する鍵を生成する。
-
-
-
-
-
-
- WireGuard のインストール: クライアント
-
- 公式サイトから各 OS 向けのクライアントソフトウェアを入手し、インストールする。次に、設定をおこなう。
-
-
-
-
- [Peer]
- PublicKey = <サーバの公開鍵>
- AllowedIPs = <サーバの外部 IP アドレス>/32
- Endpoint = <サーバの外部 IP アドレス>:51820
- ]]>
-
-
-
-
- [Peer]
- PublicKey = <サーバの公開鍵>
- AllowedIPs = <サーバの外部 IP アドレス>/32
- Endpoint = <サーバの外部 IP アドレス>:51820
- ]]>
-
-
- PrivateKey や PublicKey は鍵ファイルのパスではなく中身を書くことに注意。
-
-
-
-
-
-
- 接続する
-
-
- これで、各クライアントで VPN を有効にすると、当該サーバの 80 ポートや 443 ポートにアクセスできるようになったはずだ。念のため VPN を切った状態でアクセスできないことも確認しておくとよいだろう。
-
-
-
-
- 追記: 80 番ポートについて
-
-
- Let's Encrypt でサーバの証明書を取得している場合、80 番ポートを空けておく必要がある。気づかないうちに証明書が切れないよう注意。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.dj b/vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.dj
new file mode 100644
index 00000000..3153f96b
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.dj
@@ -0,0 +1,44 @@
+---
+[article]
+uuid = "230a0048-93c0-4aac-91ef-bb3108f3e587"
+title = "YAPC::Hiroshima 2024 参加レポ"
+description = "2024-02-10 に開催された、YAPC::Hiroshima 2024 に参加した。"
+tags = [
+ "conference",
+ "perl",
+ "yapc",
+]
+
+[[article.revisions]]
+date = "2024-02-10"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2024-02-10 に開催された、 [YAPC::Hiroshima 2024](https://yapcjapan.org/2024hiroshima/) に参加した。
+
+{#sessions-thoughts}
+# セッションの感想
+
+※セッションの題名と発表者名は、 [カンファレンスの fortee ページ](https://fortee.jp/yapc-hiroshima-2024) から引用。
+
+* [VISAカードの裏側と “手が掛かる” 決済システムの育て方 (三谷 さん)](https://fortee.jp/yapc-hiroshima-2024/proposal/c0e77f91-f856-48a0-9741-b9afb662cd30)
+
+ * ベストスピーカー賞にも選ばれていましたが、大変面白い発表でした。私自身はカード決済の知識がまったくなかったのですが、巧みな説明により、「わかったような気がする」状態になれました。
+
+* [awkでつくってわかる、Webアプリケーション (やんまー さん)](https://fortee.jp/yapc-hiroshima-2024/proposal/0e545260-61e1-465e-951c-91d6afb7782c)
+
+ * ゲームでもプログラミングでも縛りプレイほど楽しいものはないと思います。発表中ではさらっと流されていましたが、データベースとの通信や TLS、GitHub の SSO など、およそ awk で書かれたとは思えぬ機能が多数実装されており、カンファレンスなどの場でしかなかなか味わうことのない狂気に触れることができました。
+
+* キーノート (杜甫々 さん)
+
+ * ※ 招待講演のため fortee のプロポーザルページなし
+ * 私が小学6年生のとき、プログラミングを始めようと最初に開いたのが「 [とほほの Java 入門](https://www.tohoho-web.com/java/) 」でした。私の人生の道を決定したその第一歩目のサイトの運営者が今まさに目の前で話しているというのは、感動などという言葉ではとても言い尽くせません。これだけで、広島まで来る価値があったと断言できます。
+
+{#outro}
+# おわりに
+
+最高だった。特に、杜甫々氏の講演を生で拝聴できたのは、感慨とともに大いに刺激となった。次回の YAPC にも是非参加したい。
+
+P.S. Perl を書いたことがなくとも十二分に楽しめるイベントなので、「Perl を書かない」という理由で参加しなかったかたは、次回是非参加を検討してみてほしい。
diff --git a/vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.ndoc b/vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.ndoc
deleted file mode 100644
index 3e78dc36..00000000
--- a/vhosts/blog/content/posts/2024-02-10/yapcjapan-2024-report.ndoc
+++ /dev/null
@@ -1,55 +0,0 @@
----
-[article]
-uuid = "230a0048-93c0-4aac-91ef-bb3108f3e587"
-title = "YAPC::Hiroshima 2024 参加レポ"
-description = "2024-02-10 に開催された、YAPC::Hiroshima 2024 に参加した。"
-tags = [
- "conference",
- "perl",
- "yapc",
-]
-
-[[article.revisions]]
-date = "2024-02-10"
-remark = "公開"
----
-
-
-
- セッションの感想
-
- ※セッションの題名と発表者名は、カンファレンスの fortee ページから引用。
-
-
- - VISAカードの裏側と “手が掛かる” 決済システムの育て方 (三谷 さん)
-
- - ベストスピーカー賞にも選ばれていましたが、大変面白い発表でした。私自身はカード決済の知識がまったくなかったのですが、巧みな説明により、「わかったような気がする」状態になれました。
-
-
- - awkでつくってわかる、Webアプリケーション (やんまー さん)
-
- - ゲームでもプログラミングでも縛りプレイほど楽しいものはないと思います。発表中ではさらっと流されていましたが、データベースとの通信や TLS、GitHub の SSO など、およそ awk で書かれたとは思えぬ機能が多数実装されており、カンファレンスなどの場でしかなかなか味わうことのない狂気に触れることができました。
-
-
- - キーノート (杜甫々 さん)
-
- - ※ 招待講演のため fortee のプロポーザルページなし
- - 私が小学6年生のとき、プログラミングを始めようと最初に開いたのが「とほほの Java 入門」でした。私の人生の道を決定したその第一歩目のサイトの運営者が今まさに目の前で話しているというのは、感動などという言葉ではとても言い尽くせません。これだけで、広島まで来る価値があったと断言できます。
-
-
-
-
- おわりに
-
- 最高だった。特に、杜甫々氏の講演を生で拝聴できたのは、感慨とともに大いに刺激となった。次回の YAPC にも是非参加したい。
-
-
- P.S. Perl を書いたことがなくとも十二分に楽しめるイベントなので、「Perl を書かない」という理由で参加しなかったかたは、次回是非参加を検討してみてほしい。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.dj b/vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.dj
new file mode 100644
index 00000000..83205e13
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.dj
@@ -0,0 +1,44 @@
+---
+[article]
+uuid = "fd8fcb03-8e4d-4ca7-8499-0674accc51a9"
+title = "PHPカンファレンス関西 2024 参加レポ"
+description = "2024-02-11 に開催された、PHPカンファレンス関西 2024 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phpkansai",
+]
+
+[[article.revisions]]
+date = "2024-02-21"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2024-02-11 に開催された、 [PHPカンファレンス関西 2024](https://2024.kphpug.jp/) に参加した。
+
+{#sessions-thoughts}
+# セッションの感想
+
+※セッションの題名と発表者名は、 [カンファレンスの fortee ページ](https://fortee.jp/phpcon-kansai2024) から引用。
+
+* [RDBアンチパターンと戦う - 削除フラグ 完全攻略ガイド (曽根 壮大 さん)](https://fortee.jp/phpcon-kansai2024/proposal/4e03491c-2a97-40aa-8ff9-a68593b0e847)
+
+ * アンチパターンとして紙の上での知識だけあるものの、実際にどう設計すべきなのか、あるいは今すでに使われている場合にどう直していくべきなのかについては、知識がまったく足りていなかったため、よい機会となりました。データベース分野については、今後も知識のインプットと経験が必要だと感じています。
+
+* [PHPコミュニティ、その魅力と熱狂をあなたにも!!! (ことみん さん)](https://fortee.jp/phpcon-kansai2024/proposal/c903c4be-77bb-47b9-85a1-5bfdfd61c1aa)
+
+ * もしこの記事を読んでいるあなたがまだ一度もカンファレンスや勉強会に参加したことがないなら、この記事はどうでもいいのでスライドを見てください。伝えるべきことは以上です。
+
+* [ほげ言語にあってPHPにない機能 (田中ひさてる さん)](https://fortee.jp/phpcon-kansai2024/proposal/0e0befdb-2028-42c8-98e2-b19e434f5a82)
+
+ * 私はプログラミング言語の比較が大好きなので、非常に楽しかったです。UFCS (Uniform Function Call Syntax) の知名度の低さには驚きましたが、D言語er で会場が埋め尽くされていたらそれはそれで驚きなのでやむなしかもしれません。個人的に「ほげ言語にあってPHPにない機能」の中で一番ほしいのは代数的データ型です。
+
+{#outro}
+# おわりに
+
+[本カンファレンスの前日 2024-02-10 は YAPC::Hiroshima に参加しており](/posts/2024-02-10/yapcjapan-2024-report/) 、2日連続のカンファレンスとなった。かなり疲れはしたが、その分充実した週末となったように思う。
+
+翌3月は PHPerKaigi 2024、4月は PHPカンファレンス小田原 2024 があり、いずれもスタッフ兼スピーカーで参加予定である。
+今度は提供する側として、満足のいくカンファレンスになるようにしたい。
diff --git a/vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.ndoc b/vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.ndoc
deleted file mode 100644
index 430fbc62..00000000
--- a/vhosts/blog/content/posts/2024-02-22/phpkansai-2024-report.ndoc
+++ /dev/null
@@ -1,55 +0,0 @@
----
-[article]
-uuid = "fd8fcb03-8e4d-4ca7-8499-0674accc51a9"
-title = "PHPカンファレンス関西 2024 参加レポ"
-description = "2024-02-11 に開催された、PHPカンファレンス関西 2024 に参加した。"
-tags = [
- "conference",
- "php",
- "phpkansai",
-]
-
-[[article.revisions]]
-date = "2024-02-21"
-remark = "公開"
----
-
-
-
-
-
diff --git a/vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.dj b/vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.dj
new file mode 100644
index 00000000..29e16cc8
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.dj
@@ -0,0 +1,85 @@
+---
+[article]
+uuid = "750be5c8-ca52-4cbd-86fe-5645b06bde95"
+title = "PHPerKaigi 2024 参加レポ"
+description = "2024-03-07 から 2024-03-09 にかけて開催された、PHPerKaigi 2024 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phperkaigi",
+]
+
+[[article.revisions]]
+date = "2024-03-17"
+remark = "公開"
+
+[[article.revisions]]
+date = "2024-07-07"
+remark = "Wasm ランタイムの進捗について追記"
+---
+{#intro}
+# はじめに
+
+2024-03-07 から 2024-03-09 にかけて開催された、 [PHPerKaigi 2024](https://phperkaigi.jp/2024/) に参加した。
+今年はスピーカーとして、また、コアスタッフとして参加した。
+
+過去の参加レポはこちら:
+
+* [PHPerKaigi 2023](/posts/2023-04-04/phperkaigi-2023-report/)
+* [PHPerKaigi 2022](/posts/2022-05-01/phperkaigi-2022/)
+* [PHPerKaigi 2021](/posts/2021-03-30/phperkaigi-2021/)
+
+{#as-speaker}
+# スピーカーとして
+
+昨年に続き、スピーカーとして登壇をおこなった。
+
+* WebAssembly を理解する 〜VM の作成を通して〜
+
+ * [プロポーザル](https://fortee.jp/phperkaigi-2024/proposal/bc5dc153-17af-4079-8f1b-2660af97e2c8)
+ * [スライド](/slides/2024-03-08/phperkaigi-2024/)
+
+WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebAssembly の特徴、言語処理系を作る楽しさについて語った。
+タイトルにある「WebAssembly を理解する」という目的が達成できるようなトークだったかと言われると疑問は残るものの、実際に作った人にしかできない話をすることはできたと思う。
+
+{#as-staff}
+# コアスタッフとして
+
+昨年は当日スタッフとして参加したが、今年はコアスタッフとして運営に参加した。
+今年はコードゴルフ企画を提案し、その準備とシステムの開発、当日の運用をおこなった。
+そのシステムは現在も下記の URL から閲覧でき、当日出題された問題や参加者の方々の回答が見られる。
+
+[Albatross.PHP](https://t.nil.ninja/phperkaigi/2024/golf/)
+
+システムの開発完了や問題の作成完了はスケジュールギリギリとなったのだが、当日はそこそこ安定して稼動していたのではないかと思う。
+
+{#as-attendee}
+# 参加者として
+
+{#my-best-session}
+## マイベストセッション
+
+[RubyVM を PHP で実装する〜Hello World を出力するまで〜](https://fortee.jp/phperkaigi-2024/proposal/ac59d0dd-795a-47cb-ba59-c0b1772d00cc) (めもりー さん)
+
+今回一番楽しみにしていたセッションであり、期待どおりの面白さだった。
+私も今回 VM を作るというテーマで登壇したこともあり、高い解像度で受け取ることができたように思う。
+
+P.S. Ask the Speaker で話した、Ruby VM (written in PHP) on PHP VM (compiled to Wasm) on Wasm VM (written in PHP) on PHP というアイデアは「マジ」なので、続報をお待ちください (自作 Wasm runtime に不足している機能を鋭意実装中です)。
+
+2024-07-07 追記: [コミット a312e95](https://github.com/nsfisis/php-waddiwasi/commit/a312e95a95d243943535f94653822d6796d4637f) で、ついに Ruby VM on PHP VM on Wasm VM on PHP を実現した。現時点での動かしかたは README に記載している。
+
+{#outro}
+# おわりに
+
+今年はスピーカーとスタッフともに開発を伴うものだったので (Wasm 処理系とコードゴルフシステム)、両者がぶつかった結果として準備段階は去年よりも大変になった。
+
+[ゴリゴリに開発しなければいけないセッションのスピーカーとゴリゴリに開発しなければいけない企画のスタッフを同じカンファレンスでやってはいけない](https://twitter.com/nsfisis/status/1765366490277253502)
+
+ただ、それでもコアスタッフとして半年ほど関わっただけに、終わってみると感慨深い。
+例年どおり、お祭のような活気・熱気を感じることができた。
+
+来月は、また登壇とスタッフ (こちらは当日スタッフ) をおこなう [PHP カンファレンス小田原](https://phpcon-odawara.jp/) があるので、良いトーク・良いカンファレンスを作れるようにしたい。
+
+さて、参加レポは例年この言葉で締め括っているので、今年もそれで終わろうと思う。
+
+ではまた来年。
diff --git a/vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.ndoc b/vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.ndoc
deleted file mode 100644
index c5eee896..00000000
--- a/vhosts/blog/content/posts/2024-03-17/phperkaigi-2024-report.ndoc
+++ /dev/null
@@ -1,110 +0,0 @@
----
-[article]
-uuid = "750be5c8-ca52-4cbd-86fe-5645b06bde95"
-title = "PHPerKaigi 2024 参加レポ"
-description = "2024-03-07 から 2024-03-09 にかけて開催された、PHPerKaigi 2024 に参加した。"
-tags = [
- "conference",
- "php",
- "phperkaigi",
-]
-
-[[article.revisions]]
-date = "2024-03-17"
-remark = "公開"
-
-[[article.revisions]]
-date = "2024-07-07"
-remark = "Wasm ランタイムの進捗について追記"
----
-
-
- はじめに
-
- 2024-03-07 から 2024-03-09 にかけて開催された、PHPerKaigi 2024 に参加した。
- 今年はスピーカーとして、また、コアスタッフとして参加した。
-
-
- 過去の参加レポはこちら:
-
-
-
-
- スピーカーとして
-
- 昨年に続き、スピーカーとして登壇をおこなった。
-
-
- -
- WebAssembly を理解する 〜VM の作成を通して〜
-
-
-
-
- WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebAssembly の特徴、言語処理系を作る楽しさについて語った。
- タイトルにある「WebAssembly を理解する」という目的が達成できるようなトークだったかと言われると疑問は残るものの、実際に作った人にしかできない話をすることはできたと思う。
-
-
-
- コアスタッフとして
-
- 昨年は当日スタッフとして参加したが、今年はコアスタッフとして運営に参加した。
- 今年はコードゴルフ企画を提案し、その準備とシステムの開発、当日の運用をおこなった。
- そのシステムは現在も下記の URL から閲覧でき、当日出題された問題や参加者の方々の回答が見られる。
-
-
- Albatross.PHP
-
-
- システムの開発完了や問題の作成完了はスケジュールギリギリとなったのだが、当日はそこそこ安定して稼動していたのではないかと思う。
-
-
-
- 参加者として
-
- マイベストセッション
-
- RubyVM を PHP で実装する〜Hello World を出力するまで〜 (めもりー さん)
-
-
- 今回一番楽しみにしていたセッションであり、期待どおりの面白さだった。
- 私も今回 VM を作るというテーマで登壇したこともあり、高い解像度で受け取ることができたように思う。
-
-
- P.S. Ask the Speaker で話した、Ruby VM (written in PHP) on PHP VM (compiled to Wasm) on Wasm VM (written in PHP) on PHP というアイデアは「マジ」なので、続報をお待ちください (自作 Wasm runtime に不足している機能を鋭意実装中です)。
-
-
- 2024-07-07 追記: コミット a312e95 で、ついに Ruby VM on PHP VM on Wasm VM on PHP を実現した。現時点での動かしかたは README に記載している。
-
-
-
-
-
diff --git a/vhosts/blog/content/posts/2024-03-20/my-bucket-list.dj b/vhosts/blog/content/posts/2024-03-20/my-bucket-list.dj
new file mode 100644
index 00000000..d998cc2b
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-03-20/my-bucket-list.dj
@@ -0,0 +1,46 @@
+---
+[article]
+uuid = "6b749793-c760-4597-8a4c-b32d027b7585"
+title = "死ぬまでに作る自作○○一覧あるいは人生の TODO リスト"
+description = "駄文"
+tags = [
+]
+
+[[article.revisions]]
+date = "2024-03-20"
+remark = "公開"
+
+[[article.revisions]]
+date = "2024-04-07"
+remark = "URL slug を todos-in-my-life から my-bucket-list へ変更"
+---
+
+これは眠れない夜にノートへ書き散らした文をなんとか文章の体裁に直したものであり、およそ論理と呼べるものを期待してはならぬ。
+
+Knuth 曰くプログラミングは文芸である。断っておくが、労役に伴うプロダクティブでプラクティカルな行為を指してそう言っているのではない (Knuth がどう考えているかは知らないが、少なくとも私にとっては)。いわゆる趣味プログラミング、穢れなき自由意志の下で記述されるプログラムとはすなわち、絵描きにとっての絵、文字書きにとっての文章に等しい。プログラムとは、ソースコードとは、芸術作品の一形態なのである。
+
+この人生でプログラミングという行為に魅せられたからには、美しい作品を遺さねばならぬ。すなわち、簡潔で、理解しやすく、凝縮され、機能的で、速く、軽く、よい名前を持ち、うまく動くものをだ。
+
+何を作りたいかは各々異なるであろうが、私にとっては車輪の再発明として知られる自作○○である。
+
+車輪の再発明を恐れてはいけない。これを批判する人間というのは、プロダクティビティやプラクティカリティにフォーカスするエンジニアという人種である。今私が表現者たろうとするなら、自らの手で自らの車輪を作ることに何の恐れを抱く必要があろうか。
+
+そう、これが私の死ぬまでに作る自作○○一覧あるいは人生の TODO リストである (現時点ですでに部分的あるいは全面的に達成しているものを含む)。
+
+* 自作 C コンパイラ
+* 自作アセンブラ
+* 自作リンカ
+* 自作 Scheme 処理系
+* 自作 ML コンパイラ
+* 自作 Lua 処理系
+* 自作 JVM
+* 自作 Wasm 処理系
+* 自作正規表現エンジン
+* 自作 JavaScript 処理系
+* 自作ブラウザ
+* 自作エディタ
+* 自作 ActivityPub 実装
+
+選定理由は作りたいということのほかにない。そこに題材とキャンバスがあり絵筆と絵具があれば、生きとし生けるもの、いづれかコードを書かざりける。
+
+おお、願わくは、私にこれらを生み出すだけの時間があらんことを。
diff --git a/vhosts/blog/content/posts/2024-03-20/my-bucket-list.ndoc b/vhosts/blog/content/posts/2024-03-20/my-bucket-list.ndoc
deleted file mode 100644
index 8bef847c..00000000
--- a/vhosts/blog/content/posts/2024-03-20/my-bucket-list.ndoc
+++ /dev/null
@@ -1,57 +0,0 @@
----
-[article]
-uuid = "6b749793-c760-4597-8a4c-b32d027b7585"
-title = "死ぬまでに作る自作○○一覧あるいは人生の TODO リスト"
-description = "駄文"
-tags = [
-]
-
-[[article.revisions]]
-date = "2024-03-20"
-remark = "公開"
-
-[[article.revisions]]
-date = "2024-04-07"
-remark = "URL slug を todos-in-my-life から my-bucket-list へ変更"
----
-
-
- これは眠れない夜にノートへ書き散らした文をなんとか文章の体裁に直したものであり、およそ論理と呼べるものを期待してはならぬ。
-
-
- Knuth 曰くプログラミングは文芸である。断っておくが、労役に伴うプロダクティブでプラクティカルな行為を指してそう言っているのではない (Knuth がどう考えているかは知らないが、少なくとも私にとっては)。いわゆる趣味プログラミング、穢れなき自由意志の下で記述されるプログラムとはすなわち、絵描きにとっての絵、文字書きにとっての文章に等しい。プログラムとは、ソースコードとは、芸術作品の一形態なのである。
-
-
- この人生でプログラミングという行為に魅せられたからには、美しい作品を遺さねばならぬ。すなわち、簡潔で、理解しやすく、凝縮され、機能的で、速く、軽く、よい名前を持ち、うまく動くものをだ。
-
-
- 何を作りたいかは各々異なるであろうが、私にとっては車輪の再発明として知られる自作○○である。
-
-
- 車輪の再発明を恐れてはいけない。これを批判する人間というのは、プロダクティビティやプラクティカリティにフォーカスするエンジニアという人種である。今私が表現者たろうとするなら、自らの手で自らの車輪を作ることに何の恐れを抱く必要があろうか。
-
-
- そう、これが私の死ぬまでに作る自作○○一覧あるいは人生の TODO リストである (現時点ですでに部分的あるいは全面的に達成しているものを含む)。
-
-
- - 自作 C コンパイラ
- - 自作アセンブラ
- - 自作リンカ
- - 自作 Scheme 処理系
- - 自作 ML コンパイラ
- - 自作 Lua 処理系
- - 自作 JVM
- - 自作 Wasm 処理系
- - 自作正規表現エンジン
- - 自作 JavaScript 処理系
- - 自作ブラウザ
- - 自作エディタ
- - 自作 ActivityPub 実装
-
-
- 選定理由は作りたいということのほかにない。そこに題材とキャンバスがあり絵筆と絵具があれば、生きとし生けるもの、いづれかコードを書かざりける。
-
-
- おお、願わくは、私にこれらを生み出すだけの時間があらんことを。
-
-
diff --git a/vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.dj b/vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.dj
new file mode 100644
index 00000000..3207d3d0
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.dj
@@ -0,0 +1,76 @@
+---
+[article]
+uuid = "be9c896d-7efa-42dd-a50a-dda5fd3a7f5c"
+title = "PHP カンファレンス小田原 2024 参加レポ"
+description = "2024-04-13 に開催された、PHP カンファレンス小田原 2024 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phpcon-odawara",
+]
+
+[[article.revisions]]
+date = "2024-04-14"
+remark = "公開"
+
+[[article.revisions]]
+date = "2024-06-01"
+remark = "セッションの感想を追加"
+---
+{#intro}
+# はじめに
+
+2024-04-13 に開催された [PHP カンファレンス小田原](https://phpcon-odawara.jp/) に、スピーカーとして、また当日スタッフとして参加した。
+
+{#as-speaker}
+# スピーカーとして
+
+PHP 処理系の JIT コンパイルにおける PHP 8.4 での変更について、登壇をおこなった。
+
+* 来る新 JIT エンジンについて知った気になる
+
+ * [プロポーザル](https://fortee.jp/phpconodawara-2024/proposal/bc9669f6-6583-489c-aa6a-1b68abf7c291)
+ * [スライド](/slides/2024-04-13/phpcon-odawara-2024/)
+
+今回、どこから話を始めるか大いに迷ったのだが、最終的には PHP 処理系の opcode や VM といった概念は既知のものとし、そこから JIT コンパイルへ繋げるといった構成にした。
+
+PHP の処理系がスクリプトを opcode へ変換する過程については、ちょうど同じカンファレンスの [めもりーさんの発表](https://fortee.jp/phpconodawara-2024/proposal/21d94a60-404d-4fba-8c60-d1c8889a0138) あたりを参考にしていただくとよいだろう。
+また、新しい IR についてより詳しく知りたいという方は、スライド末尾の「参考資料」にあるリンクを参照いただくのがよいかと思う。
+
+Tracing JIT の発火条件や、IR を使って実現される最適化方法など、調べたものの発表に入らなかった話がごまんとあるので、これもどこかに持っていければと考えている。
+
+{#as-staff}
+# スタッフとして
+
+当日スタッフとして前日の準備と当日の運営をおこなった。今回はモノの移動が比較的 (比較対象: [PHPerKaigi](/posts/2024-03-17/phperkaigi-2024-report/) ) 少なく、体力にはかなり余裕があった。
+
+自分の担当範囲内では、一度タイムキーパー係のときに時間を思いきり間違えた以外は、スムーズに進められたかと思う。
+
+また、これはコアスタッフの方々のおかげだろうが、初開催としては大きなトラブルなく終わったと言えるのではないだろうか。
+
+{#as-attendee}
+# 参加者として
+
+発表タイトルと発表者名は fortee より引用
+
+* FigmaとPHPで作る、1ミリたりとも表示崩れしない最強の帳票印刷ソリューション (たつきち さん)
+
+ * プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/7c57d5ca-213a-4d7a-aaf0-26ddc44897f0
+ * 感想: 最初のアイデアから途中の泥臭いワークアラウンドまで非常におもしろかったです。帳票には何度か苦しめられているので、機会があれば試してみたいです。
+
+* PHPの次期バージョンはこの時期どうなっているのか、Internalsの開発体制について (てきめん さん)
+
+ * プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/740b034a-81f0-4b7a-90e9-cd3fa01c651f
+ * 感想: 前々から出そうとしている RFC があるので、RFC についての日本語情報が増えるのは大変ありがたいです。あとは作業を進めなければ......。
+
+* Architecture Decision Record を一年運用してみた (富所 亮 さん)
+
+ * プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/56218b4f-b724-4199-82f1-67497501a9ef
+ * 感想: 今回最も楽しみにしていた発表の一つです。設計指針の調査・共有等には課題を感じていたので、弊チームでも導入のために動いていこうと思います。
+
+{#outro}
+# おわりに
+
+怒涛の月刊 PHP カンファレンスも折り返しとなったが、まだまだ新鮮に楽しい。
+
+また今度、カンファレンスで会いましょう (震源地がよくわかっていないのだけれど、575 が流行っているらしい)。
diff --git a/vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.ndoc b/vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.ndoc
deleted file mode 100644
index b30f460e..00000000
--- a/vhosts/blog/content/posts/2024-04-14/phpcon-odawara-2024-report.ndoc
+++ /dev/null
@@ -1,102 +0,0 @@
----
-[article]
-uuid = "be9c896d-7efa-42dd-a50a-dda5fd3a7f5c"
-title = "PHP カンファレンス小田原 2024 参加レポ"
-description = "2024-04-13 に開催された、PHP カンファレンス小田原 2024 に参加した。"
-tags = [
- "conference",
- "php",
- "phpcon-odawara",
-]
-
-[[article.revisions]]
-date = "2024-04-14"
-remark = "公開"
-
-[[article.revisions]]
-date = "2024-06-01"
-remark = "セッションの感想を追加"
----
-
-
- はじめに
-
- 2024-04-13 に開催されたPHP カンファレンス小田原に、スピーカーとして、また当日スタッフとして参加した。
-
-
-
- スピーカーとして
-
- PHP 処理系の JIT コンパイルにおける PHP 8.4 での変更について、登壇をおこなった。
-
-
- -
- 来る新 JIT エンジンについて知った気になる
-
-
-
-
- 今回、どこから話を始めるか大いに迷ったのだが、最終的には PHP 処理系の opcode や VM といった概念は既知のものとし、そこから JIT コンパイルへ繋げるといった構成にした。
-
-
- PHP の処理系がスクリプトを opcode へ変換する過程については、ちょうど同じカンファレンスのめもりーさんの発表あたりを参考にしていただくとよいだろう。
- また、新しい IR についてより詳しく知りたいという方は、スライド末尾の「参考資料」にあるリンクを参照いただくのがよいかと思う。
-
-
- Tracing JIT の発火条件や、IR を使って実現される最適化方法など、調べたものの発表に入らなかった話がごまんとあるので、これもどこかに持っていければと考えている。
-
-
-
- スタッフとして
-
- 当日スタッフとして前日の準備と当日の運営をおこなった。今回はモノの移動が比較的 (比較対象: PHPerKaigi) 少なく、体力にはかなり余裕があった。
-
-
- 自分の担当範囲内では、一度タイムキーパー係のときに時間を思いきり間違えた以外は、スムーズに進められたかと思う。
-
-
- また、これはコアスタッフの方々のおかげだろうが、初開催としては大きなトラブルなく終わったと言えるのではないだろうか。
-
-
-
- 参加者として
-
- 発表タイトルと発表者名は fortee より引用
-
-
- -
- FigmaとPHPで作る、1ミリたりとも表示崩れしない最強の帳票印刷ソリューション (たつきち さん)
-
- - プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/7c57d5ca-213a-4d7a-aaf0-26ddc44897f0
- - 感想: 最初のアイデアから途中の泥臭いワークアラウンドまで非常におもしろかったです。帳票には何度か苦しめられているので、機会があれば試してみたいです。
-
-
- -
- PHPの次期バージョンはこの時期どうなっているのか、Internalsの開発体制について (てきめん さん)
-
- - プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/740b034a-81f0-4b7a-90e9-cd3fa01c651f
- - 感想: 前々から出そうとしている RFC があるので、RFC についての日本語情報が増えるのは大変ありがたいです。あとは作業を進めなければ......。
-
-
- -
- Architecture Decision Record を一年運用してみた (富所 亮 さん)
-
- - プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/56218b4f-b724-4199-82f1-67497501a9ef
- - 感想: 今回最も楽しみにしていた発表の一つです。設計指針の調査・共有等には課題を感じていたので、弊チームでも導入のために動いていこうと思います。
-
-
-
-
-
- おわりに
-
- 怒涛の月刊 PHP カンファレンスも折り返しとなったが、まだまだ新鮮に楽しい。
-
-
- また今度、カンファレンスで会いましょう (震源地がよくわかっていないのだけれど、575 が流行っているらしい)。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj
new file mode 100644
index 00000000..9872d284
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj
@@ -0,0 +1,155 @@
+---
+[article]
+uuid = "a4c326a6-5ffe-450c-abf2-45833c5efb6a"
+title = "【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている"
+description = "GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。"
+tags = [
+ "ci-cd",
+ "gitlab",
+]
+
+[[article.revisions]]
+date = "2022-11-17"
+remark = "デジタルサーカス株式会社の社内記事として公開"
+isInternal = true
+
+[[article.revisions]]
+date = "2024-04-21"
+remark = "ブログ記事として一般公開"
+---
+::: note
+この記事は、2022-11-17 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+:::
+
+ハマったのでメモ。
+
+{#background}
+# 前提
+
+{#gitlab-ci-cd}
+## GitLab CI/CD について
+
+GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
+
+例:
+
+```yaml
+hello-world:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'echo "Hello, World!"'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+```
+
+ここで、`script` に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
+
+では、次のようなケースだとどうなるか。
+
+```yaml
+hello-world:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'exit 1 | exit 0'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+```
+
+失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
+
+{#pipefail-option}
+## `pipefail` オプションについて
+
+前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、`pipefail` オプションを有効にする。
+
+```bash
+# On にする
+set -o pipefail
+# Off にする
+set +o pipefail
+```
+
+こうすると、パイプ全体が失敗するようになる。
+この設定は、デフォルトだと off になっている。
+
+{#problem}
+# 発生した問題
+
+次のような GitLab CI/CD ジョブが失敗してしまった。
+
+```yaml
+hoge:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+```
+
+`grep` コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、`pipefail` が on になっていると、このジョブは失敗する。
+現在の `pipefail` がどうなっているか確かめるため `set +o` で全オプションを出力させたところ、`pipefail` が on になっていた。
+
+しかし、先述したように Bash における `pipefail` のデフォルト値は off のはずだ。
+実際に、ローカルで `alpine:latest` を動かしてみたところ、
+
+```
+$ docker run --rm alpine:latest sh -c "set +o"
+set +o errexit
+set +o noglob
+set +o ignoreeof
+set +o monitor
+set +o noexec
+set +o xtrace
+set +o verbose
+set +o noclobber
+set +o allexport
+set +o notify
+set +o nounset
+set +o vi
+set +o pipefail
+```
+
+確かに `pipefail` は無効になっている。
+
+なぜスクリプト内で `set -o pipefail` しているわけでもないのに `pipefail` が on になっているのか。
+
+{#where-pipefail-is-enabled}
+# どこで `pipefail` が on になるか
+
+`.gitlab-ci.yml` で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。
+そう仮説を立てて [GitLab Runner のリポジトリ](https://gitlab.com/gitlab-org/gitlab-runner) を調査したところ、 [ソースコード中の以下の箇所](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/c75da0796a0e3048991dccfdf2784e3d931beda4/shells/bash.go#L276) で `set -o pipefail` していることが判明した (コメントは筆者による)。
+
+```go
+// pipefail オプションが存在しない環境にも対応するため、
+// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
+buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
+```
+
+{#how-to-solve}
+# どのように解決するか
+
+通常の Bash スクリプトを書く場合と同様に、`pipefail` が on になっていては困る場所だけ off にしてやればよい。
+
+```yaml
+ hoge:
+ stage: test
+ image: alpine:latest
+ script:
++ - 'set +o pipefail'
+ - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
++ - 'set -o pipefail' # この例の場合、ここで終わりなので戻さなくてもよい
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+```
+
+{#remarks}
+# 備考
+
+なお、上述した実装ファイルは `shells/bash.go` だが、`alpine:latest` の例でもそうであったように、シェルが `sh` である場合にも適用される。
diff --git a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc
deleted file mode 100644
index d65fffb5..00000000
--- a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc
+++ /dev/null
@@ -1,180 +0,0 @@
----
-[article]
-uuid = "a4c326a6-5ffe-450c-abf2-45833c5efb6a"
-title = "【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている"
-description = "GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。"
-tags = [
- "ci-cd",
- "gitlab",
-]
-
-[[article.revisions]]
-date = "2022-11-17"
-remark = "デジタルサーカス株式会社の社内記事として公開"
-isInternal = true
-
-[[article.revisions]]
-date = "2024-04-21"
-remark = "ブログ記事として一般公開"
----
-
-
- この記事は、2022-11-17 にデジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
-
-
- ハマったのでメモ。
-
-
- 前提
-
- GitLab CI/CD について
-
- GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
-
-
- 例:
-
-
-
-
-
- ここで、script に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
-
-
- では、次のようなケースだとどうなるか。
-
-
-
-
-
- 失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
-
-
-
- pipefail オプションについて
-
- 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。
-
-
-
-
-
- こうすると、パイプ全体が失敗するようになる。
- この設定は、デフォルトだと off になっている。
-
-
-
-
- 発生した問題
-
- 次のような GitLab CI/CD ジョブが失敗してしまった。
-
-
-
-
-
- grep コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、pipefail が on になっていると、このジョブは失敗する。
- 現在の pipefail がどうなっているか確かめるため set +o で全オプションを出力させたところ、pipefail が on になっていた。
-
-
- しかし、先述したように Bash における pipefail のデフォルト値は off のはずだ。
- 実際に、ローカルで alpine:latest を動かしてみたところ、
-
-
-
-
-
- 確かに pipefail は無効になっている。
-
-
- なぜスクリプト内で set -o pipefail しているわけでもないのに pipefail が on になっているのか。
-
-
-
- どこで pipefail が on になるか
-
- .gitlab-ci.yml で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。
- そう仮説を立てて GitLab Runner のリポジトリ を調査したところ、ソースコード中の以下の箇所 で set -o pipefail していることが判明した (コメントは筆者による)。
-
-
- /dev/null; then set -o pipefail; fi; set -o errexit\n")
- ]]>
-
-
-
- どのように解決するか
-
- 通常の Bash スクリプトを書く場合と同様に、pipefail が on になっていては困る場所だけ off にしてやればよい。
-
-
-
-
-
-
-
diff --git a/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj b/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj
new file mode 100644
index 00000000..5738de84
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj
@@ -0,0 +1,86 @@
+---
+[article]
+uuid = "9b26c1ed-45c3-4cad-9476-cbf2cf2e4de7"
+title = "【Zsh】 Composer のカスタムコマンドに対する Zsh 補完で引数にファイルを補完させる"
+description = "Zsh の Composer に対する補完はカスタムコマンドやその引数を補完しない。カスタムコマンドの引数としてファイルを補完させる方法を調べた。"
+tags = [
+ "composer",
+ "php",
+ "zsh",
+]
+
+[[article.revisions]]
+date = "2024-04-29"
+remark = "公開"
+---
+{#version-info}
+# バージョン情報
+
+* Composer: 2.7.4
+* PHP: 8.3.6
+* Zsh: 5.9
+
+{#intro}
+# はじめに
+
+[Composer](https://getcomposer.org/) は PHP のデファクトスタンダードなパッケージマネージャである。
+Zsh では、`composer` コマンドに対する補完が提供されており、`composer` と入力してタブキーを押すと、利用可能なコマンドやオプションが補完される。
+Zsh の補完はシェル関数の形で実装されており、`composer` コマンドに対応した補完をおこなうのは `_composer` である。
+[記事執筆時点での補完関数の定義は、GitHub のミラーリポジトリから参照できる。](https://github.com/zsh-users/zsh/blob/a66e92918568881af110a3e2e3018b317c054e4a/Completion/Unix/Command/_composer)
+
+{#problem}
+# 発生していた問題
+
+`composer` コマンドはカスタムコマンド (`composer.json` の `scripts` で定義されたコマンド) に対して補完をおこなわない。
+つまり、途中まで入力されたカスタムコマンドを補完しないし、カスタムコマンドの引数も補完しない。
+例えば、PHPUnit を呼び出す `phpunit` というカスタムコマンドを定義し `composer phpu` まで打ってタブキーを押しても、`composer phpunit` にはならない。
+また、`composer phpunit -- --` まで打ってタブキーを押しても、`phpunit` コマンドのオプションは補完されない。
+
+このことは、先ほどリンクを載せた `_composer` 関数を定義しているファイルの冒頭にも書かれている。
+
+```zsh
+# - @todo We don't complete custom commands (including script aliases). This is
+# easy to do in the general case, but it probably requires some clever caching
+# to avoid introducing a noticeable lag to every completion operation, due to
+# the way command resolution works and the fact that discovering custom
+# commands requires making slow calls to Composer
+```
+
+{#what-i-want-to-achive}
+# やりたいこと
+
+確かに、カスタムコマンドに対して完全な補完を提供するのは不可能か、あるいは実現できても遅くなりすぎるだろう。
+しかし、不完全なフォールバックを提供するくらいなら可能なはずだ。
+
+この記事では、これらのカスタムコマンドについて、Zsh が提供するデフォルトのファイル・ディレクトリ補完を適用する。
+つまり、`composer phpunit -- tests/` まで打ってタブキーを押すと、`tests` ディレクトリの下にあるテストファイルまたはディレクトリが補完される。
+
+{#solution}
+# 解決策
+
+まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。
+以下は `~/.zshrc` にすべて書く前提だが、`autoload` を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
+
+```zsh
+compdef _my_composer composer composer.phar
+```
+
+`compdef` は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。
+この場合は、`composer` コマンドや `composer.phar` コマンドに対して `_my_composer` を使って補完をおこなうよう定義している。
+
+次に `_my_composer` を定義する。基本的にはデフォルトの `composer` コマンドの補完関数 (つまり `_composer` 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。
+
+```zsh
+function _my_composer() {
+ _composer "$@" || _files "$@"
+}
+```
+
+`_composer` コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら `_files` を呼び出す。
+`_files` は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
+
+{#conclusion}
+# まとめ
+
+これらの設定をおこなうことで、部分的ながら Composer のカスタムコマンドに対して補完をおこなうことができる。
+特に、PHPUnit や PHPStan などの対象ファイル・ディレクトリを引数に取るようなコマンドを使う場合に有用であろう。
diff --git a/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.ndoc b/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.ndoc
deleted file mode 100644
index 33ef19d2..00000000
--- a/vhosts/blog/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.ndoc
+++ /dev/null
@@ -1,103 +0,0 @@
----
-[article]
-uuid = "9b26c1ed-45c3-4cad-9476-cbf2cf2e4de7"
-title = "【Zsh】 Composer のカスタムコマンドに対する Zsh 補完で引数にファイルを補完させる"
-description = "Zsh の Composer に対する補完はカスタムコマンドやその引数を補完しない。カスタムコマンドの引数としてファイルを補完させる方法を調べた。"
-tags = [
- "composer",
- "php",
- "zsh",
-]
-
-[[article.revisions]]
-date = "2024-04-29"
-remark = "公開"
----
-
-
- バージョン情報
-
- - Composer: 2.7.4
- - PHP: 8.3.6
- - Zsh: 5.9
-
-
-
- はじめに
-
- Composer は PHP のデファクトスタンダードなパッケージマネージャである。
- Zsh では、composer コマンドに対する補完が提供されており、composer と入力してタブキーを押すと、利用可能なコマンドやオプションが補完される。
- Zsh の補完はシェル関数の形で実装されており、composer コマンドに対応した補完をおこなうのは _composer である。
- 記事執筆時点での補完関数の定義は、GitHub のミラーリポジトリから参照できる。
-
-
-
- 発生していた問題
-
- composer コマンドはカスタムコマンド (composer.json の scripts で定義されたコマンド) に対して補完をおこなわない。
- つまり、途中まで入力されたカスタムコマンドを補完しないし、カスタムコマンドの引数も補完しない。
- 例えば、PHPUnit を呼び出す phpunit というカスタムコマンドを定義し composer phpu まで打ってタブキーを押しても、composer phpunit にはならない。
- また、composer phpunit -- -- まで打ってタブキーを押しても、phpunit コマンドのオプションは補完されない。
-
-
- このことは、先ほどリンクを載せた _composer 関数を定義しているファイルの冒頭にも書かれている。
-
-
-
-
-
-
- やりたいこと
-
- 確かに、カスタムコマンドに対して完全な補完を提供するのは不可能か、あるいは実現できても遅くなりすぎるだろう。
- しかし、不完全なフォールバックを提供するくらいなら可能なはずだ。
-
-
- この記事では、これらのカスタムコマンドについて、Zsh が提供するデフォルトのファイル・ディレクトリ補完を適用する。
- つまり、composer phpunit -- tests/ まで打ってタブキーを押すと、tests ディレクトリの下にあるテストファイルまたはディレクトリが補完される。
-
-
-
- 解決策
-
- まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。
- 以下は ~/.zshrc にすべて書く前提だが、autoload を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
-
-
-
-
-
- compdef は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。
- この場合は、composer コマンドや composer.phar コマンドに対して _my_composer を使って補完をおこなうよう定義している。
-
-
- 次に _my_composer を定義する。基本的にはデフォルトの composer コマンドの補完関数 (つまり _composer 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。
-
-
-
-
-
- _composer コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら _files を呼び出す。
- _files は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
-
-
-
- まとめ
-
- これらの設定をおこなうことで、部分的ながら Composer のカスタムコマンドに対して補完をおこなうことができる。
- 特に、PHPUnit や PHPStan などの対象ファイル・ディレクトリを引数に取るようなコマンドを使う場合に有用であろう。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.dj b/vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.dj
new file mode 100644
index 00000000..a1ec6829
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.dj
@@ -0,0 +1,64 @@
+---
+[article]
+uuid = "f13aa9d6-4533-4a15-872a-c298ab2090db"
+title = "PHP カンファレンス香川 2024 参加レポ"
+description = "2024-05-11 に開催された、PHP カンファレンス香川 2024 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phpconkagawa",
+]
+
+[[article.revisions]]
+date = "2024-05-11"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2024-05-11 に開催された [PHP カンファレンス香川 2024](https://phpcon.kagawa.jp/2024/) に参加した。
+
+{#session-thoughts}
+# セッション感想
+
+* 泥まみれの技術革新: あなたの[ PHPバージョンアップ | 新フレームワーク採用 | アーキテクチャ刷新 | … ]を後押しするために by nrslib
+
+ * fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/7f4622af-03b6-4b83-a0ef-e1cfc7b7c930
+ * 感想: ちょうどとあるマイグレーション作業をしているので、頷きながら拝聴しました。結局は誰しも移行作業は根気と腕力なのだということに勇気をもらえました。
+
+* PHP 9 に備えよ - 動的プロパティ、どうすればいぃ? by 荒瀬 泰輔
+
+ * fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/039ebb21-d104-4df2-86bb-be2680979b7b
+ * 感想: これも上と同じく移行作業の話ではあり、結局のところは「頑張って地道にやっていく」しかないところもあります (とはいえこちらは静的解析である程度潰せますが)。PHP 言語のコミュニティ全体で頑張っていきましょう。
+
+* 1人プロ・ペアプロ・モブプロの効果的な使い分け by まきまき
+
+ * fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/db3e9634-4a79-46c1-84fd-8ffa4d495a13
+ * 感想: 今会社でペアプロを部分的に取り入れているものの、迷うところが多く、楽しみにしていた発表です。まずは何か一つ変えないことには始まらないので、発表から得たヒントを自分たちのチームに反映すべく、何かやりかたを変えてみる予定です。
+
+* mb_trim関数を作りました - PHPに新しい関数を追加しました - by てきめん
+
+ * fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/0ec36f50-c4b7-4aa4-abef-006f8bab3931
+ * 感想: RFC を必要とするような機能追加のプロセスを日本語で解説する資料がどんどんと増えていくのは、ハードルを下げるという意味で非常にありがたいです。私も以前から出そう出そうと考えている書きかけの RFC があるのですが、具体的なプロセスが明示されるとやはりやる気になりますね。
+
+* (「PHPカンファレンス小田原2024」を実行委員長がふりかえる by asumikam)
+
+ * fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/c1efd828-72c9-4719-93f7-2ca3f8f20ac1
+ * 備考: ちょっとしたトラブルにより午前中の発表が見られなかったので、生で拝聴したわけではなく、スライドを拝見して感想を書いています。
+ * 感想: Thanks のスライド非常に嬉しかったです。こちらこそ素晴らしいカンファレンスの場をありがとうございました!スタッフ募集あれば来年も是非参加させてください。
+
+{#lightning-talk}
+# 懇親会 LT
+
+今回登壇者ではなかったのだが、プロポーザル募集時に用意していたスライド (LT 用に作っていたのだが、そもそも LT 枠がなかったのでお蔵入りになっていた) があったので懇親会の LT で発表した。
+
+中身は [第150回PHP勉強会@東京で登壇した内容](/slides/2023-03-15/phpstudy-tokyo-150/) とほぼ同じで、タイトルを「うどんのように細長い FizzBuzz を書く」にしただけの手抜き・一発ネタ発表である。個別にスライドはアップロードしないので、前述のリンクを参照してほしい。
+
+なお、この発表には [ブログ記事バージョン](/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/) もある。
+
+{#outro}
+# おわりに
+
+午前中の発表に間に合わなかったことがとにかく心残りなのだが、それ以外は PHP カンファレンス小田原のスタッフの方々をはじめ多くの方と交流でき、非常に楽しいカンファレンスだった。来年もあるそうなので (この分だと来年も月刊 PHP カンファレンスにならないか?)、是非参加したい。
+
+あれ、そういえば香川でうどん食べてないな......。
diff --git a/vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.ndoc b/vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.ndoc
deleted file mode 100644
index 66c07558..00000000
--- a/vhosts/blog/content/posts/2024-05-11/phpconkagawa-2024-report.ndoc
+++ /dev/null
@@ -1,107 +0,0 @@
----
-[article]
-uuid = "f13aa9d6-4533-4a15-872a-c298ab2090db"
-title = "PHP カンファレンス香川 2024 参加レポ"
-description = "2024-05-11 に開催された、PHP カンファレンス香川 2024 に参加した。"
-tags = [
- "conference",
- "php",
- "phpconkagawa",
-]
-
-[[article.revisions]]
-date = "2024-05-11"
-remark = "公開"
----
-
-
-
- セッション感想
-
- -
- 泥まみれの技術革新: あなたの[ PHPバージョンアップ | 新フレームワーク採用 | アーキテクチャ刷新 | … ]を後押しするために by nrslib
-
- -
- fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/7f4622af-03b6-4b83-a0ef-e1cfc7b7c930
-
- -
- 感想: ちょうどとあるマイグレーション作業をしているので、頷きながら拝聴しました。結局は誰しも移行作業は根気と腕力なのだということに勇気をもらえました。
-
-
-
- -
- PHP 9 に備えよ - 動的プロパティ、どうすればいぃ? by 荒瀬 泰輔
-
- -
- fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/039ebb21-d104-4df2-86bb-be2680979b7b
-
- -
- 感想: これも上と同じく移行作業の話ではあり、結局のところは「頑張って地道にやっていく」しかないところもあります (とはいえこちらは静的解析である程度潰せますが)。PHP 言語のコミュニティ全体で頑張っていきましょう。
-
-
-
- -
- 1人プロ・ペアプロ・モブプロの効果的な使い分け by まきまき
-
- -
- fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/db3e9634-4a79-46c1-84fd-8ffa4d495a13
-
- -
- 感想: 今会社でペアプロを部分的に取り入れているものの、迷うところが多く、楽しみにしていた発表です。まずは何か一つ変えないことには始まらないので、発表から得たヒントを自分たちのチームに反映すべく、何かやりかたを変えてみる予定です。
-
-
-
- -
- mb_trim関数を作りました - PHPに新しい関数を追加しました - by てきめん
-
- -
- fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/0ec36f50-c4b7-4aa4-abef-006f8bab3931
-
- -
- 感想: RFC を必要とするような機能追加のプロセスを日本語で解説する資料がどんどんと増えていくのは、ハードルを下げるという意味で非常にありがたいです。私も以前から出そう出そうと考えている書きかけの RFC があるのですが、具体的なプロセスが明示されるとやはりやる気になりますね。
-
-
-
- -
- (「PHPカンファレンス小田原2024」を実行委員長がふりかえる by asumikam)
-
- -
- fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/c1efd828-72c9-4719-93f7-2ca3f8f20ac1
-
- -
- 備考: ちょっとしたトラブルにより午前中の発表が見られなかったので、生で拝聴したわけではなく、スライドを拝見して感想を書いています。
-
- -
- 感想: Thanks のスライド非常に嬉しかったです。こちらこそ素晴らしいカンファレンスの場をありがとうございました!スタッフ募集あれば来年も是非参加させてください。
-
-
-
-
-
-
- 懇親会 LT
-
- 今回登壇者ではなかったのだが、プロポーザル募集時に用意していたスライド (LT 用に作っていたのだが、そもそも LT 枠がなかったのでお蔵入りになっていた) があったので懇親会の LT で発表した。
-
-
- 中身は第150回PHP勉強会@東京で登壇した内容とほぼ同じで、タイトルを「うどんのように細長い FizzBuzz を書く」にしただけの手抜き・一発ネタ発表である。個別にスライドはアップロードしないので、前述のリンクを参照してほしい。
-
-
- なお、この発表にはブログ記事バージョンもある。
-
-
-
- おわりに
-
- 午前中の発表に間に合わなかったことがとにかく心残りなのだが、それ以外は PHP カンファレンス小田原のスタッフの方々をはじめ多くの方と交流でき、非常に楽しいカンファレンスだった。来年もあるそうなので (この分だと来年も月刊 PHP カンファレンスにならないか?)、是非参加したい。
-
-
- あれ、そういえば香川でうどん食べてないな......。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.dj b/vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.dj
new file mode 100644
index 00000000..85d713de
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.dj
@@ -0,0 +1,48 @@
+---
+[article]
+uuid = "8d6f3690-3da3-4235-a81b-b9707cee22ad"
+title = "ScalaMatsuri 2024 参加レポ"
+description = "2024-06-08 から 2024-06-09 にかけて開催された、ScalaMatsuri 2024 に参加した。"
+tags = [
+ "conference",
+ "scala",
+ "scalamatsuri",
+]
+
+[[article.revisions]]
+date = "2024-06-19"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2024-06-08 から 2024-06-09 にかけて開催された [ScalaMatsuri 2024](https://2024.scalamatsuri.org/ja) に参加した。
+
+Day 2 には当日参加できなかったため、day 2 のセッションの感想は YouTube にアップロードされたアーカイブ動画を観て書いている。
+
+{#sessions}
+# セッション感想
+
+特に印象に残ったセッションを、day 1 と day 2 で一つずつ選んだ (タイトルと登壇者名は [公式ホームページの「プログラム」](https://2024.scalamatsuri.org/ja/programs) から引用)。
+
+* [Scala to WebAssembly: 動機と方法](https://2024.scalamatsuri.org/ja/programs/SESSION_DAY_1_02) (Rikito Taniguchi さん)
+
+ * [最近 WebAssembly の処理系を作った](/posts/2024-03-17/phperkaigi-2024-report/#section--as-speaker) こともあって、気になっていたセッションです。私の処理系は WasmGC proposal を実装していないので動かせないのですが、いつかサポートして動かしてみたいですね。
+
+* [作って学ぶ Extensible Effects](https://2024.scalamatsuri.org/ja/programs/SESSION_DAY_2_04) (Kory さん・hsjoihs さん)
+
+ * 今回一番楽しみにしていたセッションです。Day 2 当日は参加できず、後日アーカイブ動画を視聴したのですが、期待を裏切らない濃厚なセッションでした。後日開かれた [NB-Scala レトロスペクティブ (非公式後夜祭)](https://nextbeat.connpass.com/event/315988/) の発表も拝聴したのですが、どちらも非常に面白かったです。
+
+{#others}
+# その他感想
+
+* 良い会場だった。よく取り沙汰されるスライドの文字サイズの問題は、巨大なスクリーンを用意することで解決するという発見があった
+* ランチにお弁当が用意されており、おいしかった ( [参考画像](https://x.com/nsfisis/status/1799276217583260092) )
+
+{#outro}
+# おわりに
+
+私が Scala を書いたり追ったりしていたのは Scala 2 の頃で、Scala 3 はほとんど浦島太郎状態だったのだが、非常に楽しく面白いイベントだった。
+イベントに触発されて、長らく塩漬けになっていた Scala 製の趣味プロジェクトを久しぶりに触っているのだが、これもまた楽しい。
+
+ScalaMatsuri 運営の皆さま、スピーカーの皆さま、スポンサーの皆さま、最高のイベントをありがとうございました!次回も楽しみにしています。
diff --git a/vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.ndoc b/vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.ndoc
deleted file mode 100644
index ae30633b..00000000
--- a/vhosts/blog/content/posts/2024-06-19/scalamatsuri-2024-report.ndoc
+++ /dev/null
@@ -1,63 +0,0 @@
----
-[article]
-uuid = "8d6f3690-3da3-4235-a81b-b9707cee22ad"
-title = "ScalaMatsuri 2024 参加レポ"
-description = "2024-06-08 から 2024-06-09 にかけて開催された、ScalaMatsuri 2024 に参加した。"
-tags = [
- "conference",
- "scala",
- "scalamatsuri",
-]
-
-[[article.revisions]]
-date = "2024-06-19"
-remark = "公開"
----
-
-
- はじめに
-
- 2024-06-08 から 2024-06-09 にかけて開催された ScalaMatsuri 2024 に参加した。
-
-
- Day 2 には当日参加できなかったため、day 2 のセッションの感想は YouTube にアップロードされたアーカイブ動画を観て書いている。
-
-
-
- セッション感想
-
- 特に印象に残ったセッションを、day 1 と day 2 で一つずつ選んだ (タイトルと登壇者名は 公式ホームページの「プログラム」 から引用)。
-
-
-
-
- その他感想
-
- - 良い会場だった。よく取り沙汰されるスライドの文字サイズの問題は、巨大なスクリーンを用意することで解決するという発見があった
- - ランチにお弁当が用意されており、おいしかった (参考画像)
-
-
-
- おわりに
-
- 私が Scala を書いたり追ったりしていたのは Scala 2 の頃で、Scala 3 はほとんど浦島太郎状態だったのだが、非常に楽しく面白いイベントだった。
- イベントに触発されて、長らく塩漬けになっていた Scala 製の趣味プロジェクトを久しぶりに触っているのだが、これもまた楽しい。
-
-
- ScalaMatsuri 運営の皆さま、スピーカーの皆さま、スポンサーの皆さま、最高のイベントをありがとうございました!次回も楽しみにしています。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj b/vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj
new file mode 100644
index 00000000..eb63da08
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj
@@ -0,0 +1,132 @@
+---
+[article]
+uuid = "222488dd-cf07-4961-83aa-a014b05369ff"
+title = "reparojson: 文法エラーを直すだけの JSON フォーマッタを作った"
+description = "文法エラーだけを直し、空白の削除や挿入といった整形処理を一切おこなわない JSON フォーマッタを作成した。Neovim と連携させる設定例も紹介する。"
+tags = [
+ "neovim",
+ "vim",
+]
+
+[[article.revisions]]
+date = "2024-07-19"
+remark = "公開"
+---
+::: note
+この記事は [Vim 駅伝](https://vim-jp.org/ekiden/) #218 の記事です。
+:::
+
+{#intro}
+# 欲しかったもの
+
+Vim で JSON を編集しているときに、文法エラー (末尾カンマやカンマの不足) のみを修正して一切の整形をおこなわないプラグインが欲しかった。
+整形も同時におこなうプラグインは見つかっただけでも多数あったのだが、整形しないものは見つけられなかったので自作することにした。
+
+なお、作成したツール自体は単体の CLI として動作し、Vim とは無関係に使うことができる。
+この記事では Neovim と組み合わせる場合の設定を紹介するが、およそ任意のエディタで使えるだろう。
+
+{#reparojson}
+# 作ったもの
+
+作成したものがこちら: [ReparoJSON](https://github.com/nsfisis/reparojson)
+
+次のように動作する。
+
+```
+$ echo '[ 1 2 ]' | reparojson
+[ 1, 2 ]
+
+$ echo '[ 1, 2, ]' | reparojson
+[ 1, 2 ]
+
+$ echo '{ "foo": 1 "bar": 2 }' | reparojson
+{ "foo": 1, "bar": 2 }
+
+$ echo '{ "foo": 1, "bar": 2, }' | reparojson
+{ "foo": 1, "bar": 2 }
+```
+
+バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり:
+
+* 配列末尾の余計なカンマ (削除する)
+* 配列内のカンマ不足 (挿入する)
+* オブジェクト末尾の余計なカンマ (削除する)
+* オブジェクト内のカンマ不足 (挿入する)
+
+他にも自動で直せそうなエラーはいくつか思いつくが (オブジェクトのキーがクォートされていない等)、私自身があまり困っていないので優先度は低い。
+
+{#itegration-with-neovim}
+# Neovim との連携
+
+Neovim で JSON ファイルを保存したときに、上記のツールを自動で走らせるように設定する。
+
+ここでは、 [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) と [efm-langserver](https://github.com/mattn/efm-langserver) を用いた設定例を紹介する。
+
+```lua
+ local lspconfig = require('lspconfig')
+
+ lspconfig.efm.setup({
+ init_options = { documentFormatting = true },
+ settings = {
+ rootMarkers = {".git/"},
+ languages = {
+ json = {
+ {
+ formatCommand = "reparojson -q",
+ formatStdin = true,
+ },
+ },
+ },
+ }
+ })
+
+ vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(e)
+ vim.api.nvim_create_autocmd('BufWritePre', {
+ buffer = e.buf,
+ callback = function()
+ vim.lsp.buf.format({ async = false })
+ end
+ })
+ end,
+ })
+```
+
+ほとんどは nvim-lspconfig と efm-langserver を使う際のボイラープレートだが、`formatCommand` で `-q` フラグを指定していることに注意してほしい。
+このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。
+これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。
+そのため、`-q` フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
+
+{#outro}
+# おわりに
+
+このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、
+
+```json
+ {
+ "a": true,
+ "b": false
+ }
+```
+
+2行目と3行目を入れ換えて以下のように編集した。
+
+```json
+ {
+ "b": false
+ "a": true,
+ }
+```
+
+これは不正な JSON だが、このツールを通せば次のようになる。
+
+```json
+ {
+ "b": false,
+ "a": true
+ }
+```
+
+もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。
+しかし、単なる行の入れ換えであれば `ddp` の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。
+このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。
diff --git a/vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.ndoc b/vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.ndoc
deleted file mode 100644
index 5efe8bbd..00000000
--- a/vhosts/blog/content/posts/2024-07-19/reparojson-fix-only-json-formatter.ndoc
+++ /dev/null
@@ -1,153 +0,0 @@
----
-[article]
-uuid = "222488dd-cf07-4961-83aa-a014b05369ff"
-title = "reparojson: 文法エラーを直すだけの JSON フォーマッタを作った"
-description = "文法エラーだけを直し、空白の削除や挿入といった整形処理を一切おこなわない JSON フォーマッタを作成した。Neovim と連携させる設定例も紹介する。"
-tags = [
- "neovim",
- "vim",
-]
-
-[[article.revisions]]
-date = "2024-07-19"
-remark = "公開"
----
-
-
- この記事は Vim 駅伝 #218 の記事です。
-
-
- 欲しかったもの
-
- Vim で JSON を編集しているときに、文法エラー (末尾カンマやカンマの不足) のみを修正して一切の整形をおこなわないプラグインが欲しかった。
- 整形も同時におこなうプラグインは見つかっただけでも多数あったのだが、整形しないものは見つけられなかったので自作することにした。
-
-
- なお、作成したツール自体は単体の CLI として動作し、Vim とは無関係に使うことができる。
- この記事では Neovim と組み合わせる場合の設定を紹介するが、およそ任意のエディタで使えるだろう。
-
-
-
- 作ったもの
-
- 作成したものがこちら: ReparoJSON
-
-
- 次のように動作する。
-
-
-
-
-
- バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり:
-
-
- - 配列末尾の余計なカンマ (削除する)
- - 配列内のカンマ不足 (挿入する)
- - オブジェクト末尾の余計なカンマ (削除する)
- - オブジェクト内のカンマ不足 (挿入する)
-
-
- 他にも自動で直せそうなエラーはいくつか思いつくが (オブジェクトのキーがクォートされていない等)、私自身があまり困っていないので優先度は低い。
-
-
-
- Neovim との連携
-
- Neovim で JSON ファイルを保存したときに、上記のツールを自動で走らせるように設定する。
-
-
- ここでは、nvim-lspconfig と efm-langserver を用いた設定例を紹介する。
-
-
-
-
-
- ほとんどは nvim-lspconfig と efm-langserver を使う際のボイラープレートだが、formatCommand で -q フラグを指定していることに注意してほしい。
- このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。
- これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。
- そのため、-q フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
-
-
-
- おわりに
-
- このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、
-
-
-
-
-
- 2行目と3行目を入れ換えて以下のように編集した。
-
-
-
-
-
- これは不正な JSON だが、このツールを通せば次のようになる。
-
-
-
-
-
- もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。
- しかし、単なる行の入れ換えであれば ddp の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。
- このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。
-
-
-
diff --git a/vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj b/vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj
new file mode 100644
index 00000000..6a6f9c3f
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj
@@ -0,0 +1,118 @@
+---
+[article]
+uuid = "eed112e4-3227-4b3f-9991-7e11c288ee2b"
+title = "【Go】 text/template の with や range の内側から外側の \".\" にアクセスする"
+description = "Go言語の text/template における with や range は \".\" を上書きする。これらの内側から外側の \".\" にアクセスする方法を調べた。"
+tags = [
+ "go",
+]
+
+[[article.revisions]]
+date = "2024-08-19"
+remark = "公開"
+---
+{#tldr}
+# TL;DR
+
+常にトップレベルを指す特殊変数 `$` を使えばよい。
+
+{#intro}
+# はじめに
+
+Go には、標準ライブラリにテンプレートライブラリ `text/template` がある。
+この `text/template` における制御構造、`with` と `range` は次のように使われる。
+
+```
+# {{ .Title }}
+
+# User
+
+{{ with .User }}
+ {{ .Name }} ({{ .ID }})
+{{ end }}
+
+# Items
+
+{{ range .Items }}
+ - {{ . }}
+{{ end }}
+```
+
+`text/template` の `.` は、現在の操作対象を表す特殊なオブジェクトである。
+
+`with` や `range` は、`.` を変更する効果を持つ。
+`with` は引数に渡されたオブジェクトを `.` へセットして、内部のテンプレートを実行する。
+`range` は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を `.` へセットして、要素の個数だけ内部のテンプレートを実行する。
+
+つまりこのテンプレートは、次のような構造をレンダリングしている (`Execute()` の第2引数)。
+
+```go
+tmpl.Execute(out, Params{
+ Title: "foo",
+ User: User{
+ ID: 123,
+ Name: "john",
+ },
+ Items: []string{
+ "hoge",
+ "piyo",
+ "fuga",
+ },
+})
+```
+
+{#what-i-want-to-do}
+# やりたいこと
+
+今回おこないたいのは、`with` や `range` の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
+
+```
+{{ with .User }}
+ ここから .Title を参照するには?
+{{ end }}
+
+{{ range .Items }}
+ ここから .User を参照するには?
+{{ end }}
+```
+
+`with` や `range` は、`.` を自身の対象オブジェクトに変更するので、
+単に `{{ with .User }}` の中で `.Title` と書いても、それは `User` の `Title` プロパティを参照しているとみなされる。
+
+`text/template` では変数が使えるので、テンプレートの先頭で
+
+```
+{{ $params := . }}
+```
+
+とでもしておけば実現は可能である。
+
+しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
+
+{#solution}
+# 解決方法
+
+常にトップレベルを指す特殊変数 `$` を使えばよい。
+
+```
+{{ with .User }}
+ {{ $.Title }}
+{{ end }}
+
+{{ range .Items }}
+ {{ $.User.Name }}
+{{ end }}
+```
+
+`$` は、テンプレートが実行されるときに渡されたオブジェクトを指す。
+これを使えば現在の `.` に関係なくトップレベルを参照できる。
+
+このことは、[`text/template` の公式ドキュメント](https://pkg.go.dev/text/template#hdr-Variables)にも以下のように記載されている。
+
+> When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
+
+{#reference}
+# 参考
+
+* [直接の出典である Stack Overflow の回答: "In a template how do you access an outer scope while inside of a "with" or "range" scope?"](https://stackoverflow.com/questions/14800204/in-a-template-how-do-you-access-an-outer-scope-while-inside-of-a-with-or-rang)
+* [大元の出典である `text/template` の公式ドキュメント](https://pkg.go.dev/text/template#hdr-Variables)
diff --git a/vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.ndoc b/vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.ndoc
deleted file mode 100644
index ee7b0ab8..00000000
--- a/vhosts/blog/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.ndoc
+++ /dev/null
@@ -1,141 +0,0 @@
----
-[article]
-uuid = "eed112e4-3227-4b3f-9991-7e11c288ee2b"
-title = "【Go】 text/template の with や range の内側から外側の \".\" にアクセスする"
-description = "Go言語の text/template における with や range は \".\" を上書きする。これらの内側から外側の \".\" にアクセスする方法を調べた。"
-tags = [
- "go",
-]
-
-[[article.revisions]]
-date = "2024-08-19"
-remark = "公開"
----
-
-
- TL;DR
-
- 常にトップレベルを指す特殊変数 $ を使えばよい。
-
-
-
- はじめに
-
- Go には、標準ライブラリにテンプレートライブラリ text/template がある。
- この text/template における制御構造、with と range は次のように使われる。
-
-
-
-
-
- text/template の . は、現在の操作対象を表す特殊なオブジェクトである。
-
-
- with や range は、. を変更する効果を持つ。
- with は引数に渡されたオブジェクトを . へセットして、内部のテンプレートを実行する。
- range は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を . へセットして、要素の個数だけ内部のテンプレートを実行する。
-
-
- つまりこのテンプレートは、次のような構造をレンダリングしている (Execute() の第2引数)。
-
-
-
-
-
-
- やりたいこと
-
- 今回おこないたいのは、with や range の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
-
-
-
-
-
- with や range は、. を自身の対象オブジェクトに変更するので、
- 単に {{ with .User }} の中で .Title と書いても、それは User の Title プロパティを参照しているとみなされる。
-
-
- text/template では変数が使えるので、テンプレートの先頭で
-
-
-
-
-
- とでもしておけば実現は可能である。
-
-
- しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
-
-
-
- 解決方法
-
- 常にトップレベルを指す特殊変数 $ を使えばよい。
-
-
-
-
-
- $ は、テンプレートが実行されるときに渡されたオブジェクトを指す。
- これを使えば現在の . に関係なくトップレベルを参照できる。
-
-
- このことは、text/template の公式ドキュメントにも以下のように記載されている。
-
-
- When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
-
-
-
-
diff --git a/vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.dj b/vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.dj
new file mode 100644
index 00000000..f862b55e
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.dj
@@ -0,0 +1,37 @@
+---
+[article]
+uuid = "ee7289ee-ff2e-439d-b343-7f87504192fd"
+title = "MN-Core Challenge #1 参加レポ"
+description = "2024-08-28 から 2024-09-24 にかけて開催された MN-Core Challenge #1 に参加した。"
+tags = [
+ "mncore-challenge",
+]
+
+[[article.revisions]]
+date = "2024-09-28"
+remark = "公開"
+---
+::: note
+ただの参加記で解説はない。
+:::
+
+{#intro}
+# はじめに
+
+2024-08-28 から 2024-09-24 の約1ヶ月に渡り開催された [MN-Core Challenge #1](https://mncore-challenge.preferred.jp/) に参加した。私 nsfisis ([あるいは `0b0100000111111000`](https://x.com/nsfisis/status/1838276770560364977)) はスコア 1181 で、最終順位 29 位だった。
+
+この記事で解説はしないが、提出した回答はこちらのリポジトリ ([GitHub: nsfisis/mncore-challenge](https://github.com/nsfisis/mncore-challenge)) にアップロードしている。
+
+{#thought}
+# 感想
+
+MN-Core には初めて触れたが、それでも問題なく全問 (除 FizzBuzz) 解けるよう線路が敷かれており、前半の問題を解くことで自然と後半を解くだけの知識が身に付くように設計されていた。
+
+開催期間中はほぼ常に MN-Core Challenge のことを考え続けており、期間中 (前掲した回答を貯めるためのリポジトリを除き) 自分の Git リポジトリをほとんど触ることがなかった。途中更新ができずに苦しい時間もあったが、一つ気付くと一つ縮まる楽しいゴルフだった。
+
+悔しいポイントも多数あるのだが、書いているとキリがないので自分で反省するだけにしておく。
+
+{#outro}
+# おわりに
+
+最後になりましたが、運営のみなさま、素晴しいコンテストをありがとうございました!非常に楽しい時間でした!第2回を首を長くして待っています!
diff --git a/vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.ndoc b/vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.ndoc
deleted file mode 100644
index 462ccab5..00000000
--- a/vhosts/blog/content/posts/2024-09-28/mncore-challenge-1.ndoc
+++ /dev/null
@@ -1,45 +0,0 @@
----
-[article]
-uuid = "ee7289ee-ff2e-439d-b343-7f87504192fd"
-title = "MN-Core Challenge #1 参加レポ"
-description = "2024-08-28 から 2024-09-24 にかけて開催された MN-Core Challenge #1 に参加した。"
-tags = [
- "mncore-challenge",
-]
-
-[[article.revisions]]
-date = "2024-09-28"
-remark = "公開"
----
-
-
- ただの参加記で解説はない。
-
-
-
- 感想
-
- MN-Core には初めて触れたが、それでも問題なく全問 (除 FizzBuzz) 解けるよう線路が敷かれており、前半の問題を解くことで自然と後半を解くだけの知識が身に付くように設計されていた。
-
-
- 開催期間中はほぼ常に MN-Core Challenge のことを考え続けており、期間中 (前掲した回答を貯めるためのリポジトリを除き) 自分の Git リポジトリをほとんど触ることがなかった。途中更新ができずに苦しい時間もあったが、一つ気付くと一つ縮まる楽しいゴルフだった。
-
-
- 悔しいポイントも多数あるのだが、書いているとキリがないので自分で反省するだけにしておく。
-
-
-
- おわりに
-
- 最後になりましたが、運営のみなさま、素晴しいコンテストをありがとうございました!非常に楽しい時間でした!第2回を首を長くして待っています!
-
-
-
diff --git a/vhosts/blog/content/posts/2024-12-04/cohackpp-report.dj b/vhosts/blog/content/posts/2024-12-04/cohackpp-report.dj
new file mode 100644
index 00000000..80da994f
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-12-04/cohackpp-report.dj
@@ -0,0 +1,197 @@
+---
+[article]
+uuid = "ea0593d3-691c-4e08-8db4-98b8925717ec"
+title = "紅白ぺぱ合戦に参加<しました"
+description = "2024-11-30 に開催された紅白ぺぱ合戦に参加し、ぺ陣営のメンバとして LT しました。"
+tags = [
+ "cohackpp",
+ "php",
+]
+
+[[article.revisions]]
+date = "2024-12-04"
+remark = "公開"
+
+[[article.revisions]]
+date = "2024-12-05"
+remark = "「育てた」枠・「育てられた」枠を勘違いして逆に表記していたので修正"
+---
+{#intro}
+# はじめに
+
+2024-11-30 に開催された [紅白ぺぱ合戦](https://connpass.com/event/329428/) なる催しに参加しました。私は「ぺ」陣営のメンバとして LT をおこないました。
+
+紅白ぺぱ合戦のイベントページにある説明を以下に引用します。
+
+> Webエンジニアの [asumikam](https://x.com/asumikam) とWebエンジニアの [stefafafan](https://x.com/stefafafan) が2024年7月7日に結婚しました。
+>
+> せっかくなので技術トークとかで紅白戦をしませんか?いいですね!やっていきます!
+>
+> 場所はァ!小田原ァ!盛り上がっていきましょゥ!!!
+
+ざっくりと言えば、テックカンファレンスの形式をとった結婚披露宴です。タイトルの「ぺ」は PHPer、「ぱ」は Perl Monger の略です。
+
+{#thoughts}
+# 感想
+
+私は「ぺ」陣営のスピーカーとして LT をしていたのですが、その前にまずは登壇以外の感想を。
+
+いや~最高でしたね。どの枠のスピーチの方も良かったのですが、特に (asumikam さん/stefafafan さんに)「育てられた」枠のお二方が印象に残っています。
+(asumikam さん/stefafafan さんを)「育てた」枠としてお世話になった方に声をかけることはできると思うんですよ。
+それだけでなく、「自分が育てたのだ」と言える人がいて、そしてそれに 100 点で応える人がいるということ。この素晴しさ。人徳。
+
+改めて、asumikam さん、stefafafan さん、ご結婚おめでとうございます!
+
+{#lt}
+# LT
+
+{#prepare}
+## 合戦準備
+
+さて、時を合戦の前に戻しまして、両陣営の登壇者が発表され徐々に謎のイベントの輪郭が見えてきた頃、asumikam さんから次のような連絡を受けました。
+
+
+
+最初は直近のカンファレンスに出して落選したプロポーザルテーマを LT に編集して話そうとしていたのですが、この機会でなければ話せない・この機会で話すことに意味があるテーマにしようとネタ出しをおこない、最終的に次のテーマでの登壇となりました。
+
+{#battle}
+## いざ尋常に勝負
+
+当日は、「プログラミングマナー講座」と題して発表をおこないました。
+結婚式のマナー、特に「忌み言葉」へフォーカスし、これを無理やりプログラミングに適用するというものです。
+[スライドはこちらにアップロードしています。](/slides/2024-11-30/cohackpp/)
+
+最終的にお祝いのメッセージを仕込んだソースコードで締めるという構成は、我ながら綺麗にまとまったと思っています。忌み言葉の案は他にも大量にあったのですが、技術 LT かつ結婚祝いスピーチにするためにどうしても最後のソースコードが必要だったので、時間の関係上それらには犠牲となってもらいました ( [ボツになった案のひとつ](https://x.com/nsfisis/status/1862798137452327206) )。
+
+そもそも結婚式・披露宴でのスピーチ自体が初めてだったのでそれなりに緊張していたのですが、登壇時やその後の反応を伺う限り概ね好評だったようで良かったです。
+
+{#congrats}
+# ご結婚おめでとうございます
+
+https://github.com/nsfisis/cohackpp/blob/main/congrats.php
+
+```php
+"'".trim(
+chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
+Q;
+$c=['0AFOgQFOgQFOgQFOgQFOgQFEAgiBAUIECIEBQwQHgQE8AQYFBoEBOgQGBAaBAToEBwQFgQE6BQYFBIEB
+OwQHBASBATwEBgUDgQE8BQYEA4EBPQQGBAOBAT0FBgEFgQERBhsIBAQMgQERKQQFC4EBESkFAQ6BAREp
+FIEBESkUgQERKRSBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
+AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ4EPIEBDQY7gQENBjuBAQ0GO4EBDQU8gQENBTyBAQwG
+PIEBDAY8gQEMBjyBAQwGPIEBDAY8gQEMBjyBAQwHO4EBDAc7gQENBzqBAQ0IOYEBDgg4gQEOCiUCD4EB
+DwwdBw+BARAQDhEPgQERLg+BARItD4EBFCsPgQEXJRKBARsbGIEBToEBToEBToEBToEBToHQ',
+'0AFOgQFOgQFOgQEPASMFFoEBDwMhBRaBAQ4FIAUWgQEOBSAFFoEBDQUhBRaBAQ0FIQUWgQEMBSIFFoEB
+DAUiBRaBAQsFIwUWgQELBQgBGgUWgQEKBQkDGAUWgQEKBQgGBCoDgQEJBQkFBSoDgQEFAQMECQYFKgOB
+AQQDAQUJBQYqA4EBBAgJBQcqA4EBAwkJBRkFFoEBBAcJBRoFFoEBBQYIBRsFFoEBBgYHBRsFFoEBBwYF
+BRwFFoEBCAYDBR0FFoEBCQYCBR0FFoEBCgseBRaBAQsJHwUWgQEMByAFFoEBDAcFAxgFFoEBDQUFBBgF
+FoEBDQQGBRYGFoEBDAUHBAcmBYEBCwUIBQYmBYEBCwQKBAYmBYEBCgQLBQUmBYEBCQUMBS+BAQgFDAYv
+gQEDHC+BAQMdLoEBAx0ugQEDHi2BAQMJBAUIBC2BARAFCAQtgQEQBQgELYEBEAUJAS+BARAFESEHgQEQ
+BREhB4EBBwEIBQYBCiEHgQEHBAUFBAQJIQeBAQcEBQUEBAkEGAUHgQEGBQUFBAUIBBgFB4EBBgUFBQUE
+CAQYBQeBAQYFBQUFBAgEGAUHgQEGBAYFBQUHBBgFB4EBBgQGBQYEBwQYBQeBAQUFBgUGBQYEGAUHgQEF
+BQYFBwQGBBgFB4EBBQQHBQcEBgQYBQeBAQUEBwUHBQUEGAUHgQEEBQcFBwUFBBgFB4EBBAUHBQgEBQQY
+BQeBAQQECAUIBAUEGAUHgQEDBQgFCAEIBBgFB4EBAwUIBREEGAUHgQECBQkFEQQYBQeBAQIFCQURIQeB
+AQQCCgURIQeBARAFESEHgQEQBREhB4EBEAURIQeBARAFEQQYBQeBARAFEQQYBQeBARAFEQQYBQeBARAF
+EQQYBQeBAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQEOAjEBDIEBDgUqBwqBAQ0FJwwJgQENBSETCIEBDQURAQcXDIEBDQURGxCBAQ0FERcU
+gQENBBIOBAUUgQEMBRIGDAUUgQEMBRIFDQUUgQEMBRIFDgQUgQEMBRIFDgQUgQEMBBMFDgQUgQELBRMF
+DgQUgQELBRMFDgUTgQELBRMFDgUTgQEDGQcFDgUTgQEDGwUoA4EBAxsFKAOBAQMbBSgDgQEDGwUoA4EB
+CgUKBQUFDwUSgQEKBAsEBgUQBBKBAQkFCwQGBRAFEYEBCQULBAYFEAURgQEJBQoFBgUQBRGBAQkFCgUG
+BREFEIEBCQQLBQYFEQUQgQEIBQsFBgUSBQkBBYEBCAULBQYFEgUJAwOBAQgFCwUGBQoFBAUIBAKBAQgF
+CwQHBQQLBAYHAwOBAQgECwUHFAUGBQQDgQEHBQsFAxgFBwQEA4EBBwULBQMVCQ4DgQEHBQsFAw8QDQOB
+AQcEDAUDCRcLBIEBBgULBQUCHwgFgQEGBQsFKAQHgQEGBQsFM4EBBgULBTOBAQYFCgUKIgiBAQUHCQUK
+IgiBAQUICAUKIgiBAQUKBgUKIgiBAQULBAULBRgFCIEBBA0DBQsFGAUIgQEEBQIHAgULBRgFCIEBBgMD
+DAwFGAUIgQENCwwFGAUIgQEOCgwFGAUIgQEQBw0FGAUIgQERBwwFGAUIgQERCAsiCIEBEAoKIgiBARAL
+CSIIgQEPDQgiCIEBDwUCBwcFGAUIgQEOBgMHBgUYBQiBAQ0GBQcFBRgFCIEBDAcGBgUFGAUIgQELBwgE
+BgUYBQiBAQoHCgMGBRgFCIEBCQcMAQcFGAUIgQEIBxUFGAUIgQEHCBUiCIEBBggWIgiBAQQJFyIIgQEF
+BxgiCIEBBQUaBRgFCIEBBgMbBRgFCIEBJAUYBQiBAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEZBi+BARkGL4EBGgUvgQEaBS+BARoFL4EBGgUvgQEaBS+BARoF
+L4EBGgUvgQEaBRkBFYEBGgUZAxOBARoFDwEIBRKBARoFCwUIBxCBARoFBgoHCg6BARoVCQkNgQEJJgsJ
+C4EBCSYMCQqBAQohEgkIgQEKGxoIB4EBChUhCQWBARkFIwkEgQEZBSUGBYEBGQUmBAaBARkFKAIGgQEZ
+BTCBARkFMIEBGQUwgQEZBTCBARkFMIEBGQUwgQEZBTCBARkFCRAXgQEZBQQYFIEBGSMSgQEZJBGBARkS
+CAwPgQEXDhIJDoEBFQwYCA2BARMMGwcNgQESDB0HDIEBEA4eBgyBAQ8IAwQfBguBAQ4IBAQfBguBAQ0H
+BgQgBQuBAQwHBwQgBQuBAQsHCAUfBQuBAQoGCgUfBQuBAQoGCgUfBQuBAQkGCwUfBQuBAQkFDAUeBguB
+AQgGDAUeBguBAQgGDAUdBwuBAQgGDAUdBgyBAQgGDAUcBwyBAQkFDAUbBw2BAQkGCwUZCQ2BAQkHCgUY
+CQ6BAQoIBwYVCw+BAQsIBQcSDRCBAQwSChURgQENEQoTE4EBDhAKERWBARANDA4XgQESCwwLGoEBFQYO
+BSCBAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEuBhqBAS4FG4EBLgUbgQEuBRuBARICGQYbgQEPBRkGG4EBDgYZ
+BRyBAQ8FGQUcgQEPBhgFHIEBDwYXBhyBARAFFwUdgQEQBRcFHYEBEAYNERqBAREFChcXgQERBQccFYEB
+EQYEIBOBARIFAg4DExGBARIRBwYEChCBARIOCgUHCQ+BARMKDQUJCA6BARIJDgYKCA2BAREIEAUNBwyB
+ARAJEAUOBgyBAQ8KDwYPBguBAQ4MDgUQBwqBAQ4MDgURBgqBAQ0GAgYMBRMFCoEBDAYEBQwFEwYJgQEM
+BgQFCwYTBgmBAQsGBQYKBRUFCYEBCgYHBQkGFQYIgQEKBQgGCAYVBgiBAQkGCQUIBRcFCIEBCQUKBgYG
+FwUIgQEJBQsFBgUYBQiBAQgFDAYEBhgFCIEBCAUNBQMGGQUIgQEIBQ0GAgYZBQiBAQcFDwwaBQiBAQcF
+DwwaBQiBAQcFEAobBQiBAQcFEAoaBgiBAQcFEQgbBgiBAQcFEgYcBgiBAQcFEQgbBQmBAQcFEAoZBgmB
+AQcFEAoZBgmBAQcFDwwXBgqBAQcFDg4VBwqBAQcGDAcCBxQGC4EBCAULBwQEFQcLgQEIBggIBgIVBwyB
+AQgIBAkHARUHDYEBCRMcCQ2BAQoRHAkOgQELDhwKD4EBDAwbChGBAQ4HGwwSgQEsDxOBASgRFYEBKQ4X
+gQEpDBmBASoIHIEBKwMggQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQE1DwqBASkbCoEBHiYKgQETMQqBAQc9CoEBBjQU
+gQEGIQQKGYEBBhcOBxyBAQcOFAcegQEHBhsHH4EBJwYhgQEmBiKBASUGFgEMgQEkBhUDDIEBIwYWBAuB
+ASMGFwQKgQEiBg8DBgQKgQEhBg8EBwQJgQEhBhAEBwQIgQEgBhEFBgQIgQEgBRMEBwQHgQEfBhQEBgUG
+gQEfBhQEBwQGgQEfBRYEBgIIgQEeBhYEEIEBHgYXBA+BAR4FGAMQgQEeBSuBAR0GK4EBHQYrgQEdBiuB
+AR0GK4EBHQYrgQEdBiuBAR0GK4EBHQYrgQEdBiuBAR4FK4EBHgYqgQEeBiqBAR4HKYEBHwYpgQEfByiB
+ASAHJ4EBIAcngQEhByaBASEJJIEBIgkjgQEjCiGBASQLH4EBJQwdgQEmDxmBASgTE4EBKhMRgQErEhGB
+AS4PEYEBMAwSgQE0CBKBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXATaBARQENoEBEgY2gQESBzWBARMGNYEBEwc0gQEUBjSB
+ARQGNIEBFQYzgQEVBiABEoEBFgYeAxGBARYGHAYQgQEWBxoHEIEBFwYYCg+BARcHFQsQgQEYBhMLEoEB
+GAYRCxSBARkGDgsWgQEZBgwLGIEBGgYJCxqBARoHBwocgQEbBgUKHoEBGwcCCiCBARwRIYEBHA8jgQEd
+DCWBAR0KJ4EBHAoogQEbCSqBARoILIEBGQgtgQEXCC+BARYIMIEBFQgxgQEVBzKBARQHM4EBEwc0gQES
+BzWBARIGNoEBEQY3gQERBjeBAREFOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQ
+BjiBARAHN4EBEAc3gQERBzaBAREINYEBEgkzgQETCiEDDYEBEw0WCw2BARQtDYEBFisNgQEXKg2BARon
+DYEBHSARgQEjDxyBAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXBDOBARcLLIEBFxQjgQEXIRaBARchFoEBGh4WgQEiFhaBASsN
+FoEBNgEXgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEkDR2BAR4WGoEBGR0YgQEVIxaBARApFYEB
+DRcLCxSBAQ4QFAkTgQEODBoIEoEBDgkeBxKBAQ4GIgcRgQEPAiYGEYEBNwcQgQE4BhCBATgGEIEBOAYQ
+gQE4BhCBATkFEIEBOQUQgQE5BRCBATgGEIEBOAYQgQE4BhCBATgGEIEBNwcQgQE3BhGBATcGEYEBNgcR
+gQE1BxKBATUHEoEBNAcTgQEzCBOBATIIFIEBMQgVgQEvCRaBAS4JF4EBLAoYgQEqCxmBAScMG4EBJQ0c
+gQEhDx6BARwTH4EBGBQigQEZESSBARoNJ4EBGgoqgQEbBS6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFDAgmBAUEECYEBQgQIgQE7AQYFB4EBOQQGBAeBATkEBwQGgQE5BQYFBYEB
+OgQHBAWBATsEBgUEgQE7BQYEBIEBPAQGBASBATwFBgEGgQEQBhsIBAQNgQEQKQQFDIEBECkFAQ+BARAp
+FYEBECkVgQEQKRWBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
+AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ0EPYEBDAY8gQEMBjyBAQwGPIEBDAU9gQEMBT2BAQsG
+PYEBCwY9gQELBj2BAQsGPYEBCwY9gQELBj2BAQsHPIEBCwc8gQEMBzuBAQwIOoEBDQg5gQENCiUCEIEB
+DgwdBxCBAQ8QDhEQgQEQLhCBAREtEIEBEysQgQEWJROBARobGYEBToEBToEBToEBToEBToHQ',
+'0AFOgQFOgQFOgQFOgQFCAwmBAUEECYEBQQUIgQE5AwYFB4EBOAQHBAeBASkCDgQGBQaBASYGDQUGBAaB
+ASYGDgQHBAWBASYGDgUGBAWBAScFDwQHBASBAScGDwQGAwWBAScGDwUNgQEoBRAEDYEBKAUQAw6BASgG
+IIEBKQUQAw2BASkFDAcNgQEpBgYMDYEBCgMdFw2BAQsVAh8NgQELNQ6BAQsxEoEBCysYgQELJh2BARkI
+CwUdgQEsBhyBAS0FHIEBLQUcgQEuBRuBAS4FG4EBLwUagQEvBRqBATAFGYEBMAYYgQExBRiBATEGF4EB
+MgUXgQEyBhaBATMGFYEBNAUVgQE0BhSBATUGE4EBEAIWCgMHEoEBEAYSFRGBAQ8GExURgQEPBRQUEoEB
+DgYaDROBAQ4GIwQTgQEOBTuBAQ0GO4EBDQY7gQENBTyBAQ0FPIEBDQU8gQENBTyBAQ0FPIEBDQU8gQEN
+BjuBAQ0GO4EBDgY6gQEOBzmBAQ4IOIEBDwg3gQEQCh4BFYEBEQwVBxWBARInFYEBEyYVgQEVJBWBARgh
+FYEBGxoZgQFOgQFOgQFOgQFOgdA=',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEJBz6BAQkGP4EBCQY/gQEJBigDFIEB
+CQYlBhSBAQkGJQcTgQEJBiYHEoEBCQYnBhKBAQkGJwcRgQEJBigGEYEBCQYoBxCBAQkGKQYQgQEJBioG
+D4EBCQYqBg+BAQkGKgcOgQEJBisGDoEBCgUrBg6BAQoFLAYNgQEKBSwGDYEBCgUtBgyBAQoFLQYMgQEK
+BS0GDIEBCgUuBguBAQoFLgYLgQEKBS4GC4EBCgYtBguBAQoGLgYKgQEKBi4GCoEBCgYuBgqBAQoGLwYJ
+gQELBS8GCYEBCwUvBgmBAQsFLwYJgQELBhMBGgYJgQELBhMCGgYIgQELBhMDGQYIgQEMBRMEGAYIgQEM
+BhEGFwYIgQEMBhEGFwYIgQEMBhAGGQUIgQENBg8GGQUIgQENBg8GGQUIgQENBg4GGgILgQEOBg0GJ4EB
+DgcLBiiBAQ4HCgcogQEPBwgHKYEBEAcGCCmBARAKAQkqgQEREiuBARIRK4EBEhAsgQETDi2BARULLoEB
+FwcwgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQElBiOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYF
+I4EBJgUQBQ6BAQ4IEAUGDw6BAQ4yDoEBDjIOgQEOMg6BAQ4rFYEBGhIigQEmBSOBASYFI4EBJgUjgQEm
+BSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBRQCDYEBDQMWBQwKDYEBDQ8JHA2BAQ4zDYEBDjMN
+gQEOMg6BARAnF4EBJQYjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUj
+gQEmBSOBAR0EBQUjgQEXFCOBARQZIYEBEh4egQERIRyBARAJDBAZgQEPBxARF4EBDgYSExWBAQ4FEwYD
+CxSBAQ4FEwYFCxKBAQ0FFAYHChGBAQ0FFAYJCg+BAQ0FFAYKCg6BAQ0GEwYMCQ2BAQ4FEwYNCQyBAQ4G
+EQYQBg2BAQ4HDwcRBQ2BAQ8IDAgSAw6BAQ8bFAEPgQERGCWBARIWJoEBFBIogQEXDSqBAU6BAU6BAU6B
+AU6BAU6BAU6B0A==',
+'0AFOgQFOgQFOgQFOgQFOgQFOgQEpBh+BASkGH4EBKQYfgQEqBR+BASoFH4EBKgUfgQEqBR+BASoFH4EB
+KgUfgQEqBR+BARkuB4EBBkEHgQEHQAeBAQdAB4EBB0AHgQEHDBcFH4EBKgUfgQEqBR+BASoFH4EBKgUf
+gQEqBR+BASoFH4EBKgUfgQEgDx+BAR4RH4EBHBMfgQEbFB+BARoIBAkfgQEZBwkGH4EBGQYLBh6BARgG
+DQUegQEYBQ4GHYEBFwYPBR2BARcFEAUdgQEXBRAFHYEBFwUQBhyBARcFEAYcgQEXBQ8HHIEBFwUPBxyB
+ARcGDgccgQEXBg0IHIEBGAYMBx2BARgHCggdgQEZCAYKHYEBGhcdgQEbFh2BARwVHYEBHgsBBh6BASAG
+BAYegQEpBh+BASkGH4EBKAYggQEnByCBASYHIYEBJQcigQEkCCKBASIJI4EBIAokgQEeCyWBARwLJ4EB
+GQ0ogQEWDiqBARcMK4EBGAktgQEZBTCBARoCMoEBToEBToEBToEBToEBToEBToHQ',];
+$m="";for($k=0;$k"'".trim(
+chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
+```
diff --git a/vhosts/blog/content/posts/2024-12-04/cohackpp-report.ndoc b/vhosts/blog/content/posts/2024-12-04/cohackpp-report.ndoc
deleted file mode 100644
index 5d77af55..00000000
--- a/vhosts/blog/content/posts/2024-12-04/cohackpp-report.ndoc
+++ /dev/null
@@ -1,217 +0,0 @@
----
-[article]
-uuid = "ea0593d3-691c-4e08-8db4-98b8925717ec"
-title = "紅白ぺぱ合戦に参加<しました"
-description = "2024-11-30 に開催された紅白ぺぱ合戦に参加し、ぺ陣営のメンバとして LT しました。"
-tags = [
- "cohackpp",
- "php",
-]
-
-[[article.revisions]]
-date = "2024-12-04"
-remark = "公開"
-
-[[article.revisions]]
-date = "2024-12-05"
-remark = "「育てた」枠・「育てられた」枠を勘違いして逆に表記していたので修正"
----
-
-
- はじめに
-
- 2024-11-30 に開催された紅白ぺぱ合戦なる催しに参加しました。私は「ぺ」陣営のメンバとして LT をおこないました。
-
-
- 紅白ぺぱ合戦のイベントページにある説明を以下に引用します。
-
-
-
- WebエンジニアのasumikamとWebエンジニアのstefafafanが2024年7月7日に結婚しました。
-
-
- せっかくなので技術トークとかで紅白戦をしませんか?いいですね!やっていきます!
-
-
- 場所はァ!小田原ァ!盛り上がっていきましょゥ!!!
-
-
-
- ざっくりと言えば、テックカンファレンスの形式をとった結婚披露宴です。タイトルの「ぺ」は PHPer、「ぱ」は Perl Monger の略です。
-
-
-
- 感想
-
- 私は「ぺ」陣営のスピーカーとして LT をしていたのですが、その前にまずは登壇以外の感想を。
-
-
- いや~最高でしたね。どの枠のスピーチの方も良かったのですが、特に (asumikam さん/stefafafan さんに)「育てられた」枠のお二方が印象に残っています。
- (asumikam さん/stefafafan さんを)「育てた」枠としてお世話になった方に声をかけることはできると思うんですよ。
- それだけでなく、「自分が育てたのだ」と言える人がいて、そしてそれに 100 点で応える人がいるということ。この素晴しさ。人徳。
-
-
- 改めて、asumikam さん、stefafafan さん、ご結婚おめでとうございます!
-
-
-
- LT
-
- 合戦準備
-
- さて、時を合戦の前に戻しまして、両陣営の登壇者が発表され徐々に謎のイベントの輪郭が見えてきた頃、asumikam さんから次のような連絡を受けました。
-
-
-
- 最初は直近のカンファレンスに出して落選したプロポーザルテーマを LT に編集して話そうとしていたのですが、この機会でなければ話せない・この機会で話すことに意味があるテーマにしようとネタ出しをおこない、最終的に次のテーマでの登壇となりました。
-
-
-
- いざ尋常に勝負
-
- 当日は、「プログラミングマナー講座」と題して発表をおこないました。
- 結婚式のマナー、特に「忌み言葉」へフォーカスし、これを無理やりプログラミングに適用するというものです。
- スライドはこちらにアップロードしています。
-
-
- 最終的にお祝いのメッセージを仕込んだソースコードで締めるという構成は、我ながら綺麗にまとまったと思っています。忌み言葉の案は他にも大量にあったのですが、技術 LT かつ結婚祝いスピーチにするためにどうしても最後のソースコードが必要だったので、時間の関係上それらには犠牲となってもらいました (ボツになった案のひとつ)。
-
-
- そもそも結婚式・披露宴でのスピーチ自体が初めてだったのでそれなりに緊張していたのですが、登壇時やその後の反応を伺う限り概ね好評だったようで良かったです。
-
-
-
-
- ご結婚おめでとうございます
-
- https://github.com/nsfisis/cohackpp/blob/main/congrats.php
-
-
- "'".trim(
- chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
- Q;
- $c=['0AFOgQFOgQFOgQFOgQFOgQFEAgiBAUIECIEBQwQHgQE8AQYFBoEBOgQGBAaBAToEBwQFgQE6BQYFBIEB
- OwQHBASBATwEBgUDgQE8BQYEA4EBPQQGBAOBAT0FBgEFgQERBhsIBAQMgQERKQQFC4EBESkFAQ6BAREp
- FIEBESkUgQERKRSBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
- AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ4EPIEBDQY7gQENBjuBAQ0GO4EBDQU8gQENBTyBAQwG
- PIEBDAY8gQEMBjyBAQwGPIEBDAY8gQEMBjyBAQwHO4EBDAc7gQENBzqBAQ0IOYEBDgg4gQEOCiUCD4EB
- DwwdBw+BARAQDhEPgQERLg+BARItD4EBFCsPgQEXJRKBARsbGIEBToEBToEBToEBToEBToHQ',
- '0AFOgQFOgQFOgQEPASMFFoEBDwMhBRaBAQ4FIAUWgQEOBSAFFoEBDQUhBRaBAQ0FIQUWgQEMBSIFFoEB
- DAUiBRaBAQsFIwUWgQELBQgBGgUWgQEKBQkDGAUWgQEKBQgGBCoDgQEJBQkFBSoDgQEFAQMECQYFKgOB
- AQQDAQUJBQYqA4EBBAgJBQcqA4EBAwkJBRkFFoEBBAcJBRoFFoEBBQYIBRsFFoEBBgYHBRsFFoEBBwYF
- BRwFFoEBCAYDBR0FFoEBCQYCBR0FFoEBCgseBRaBAQsJHwUWgQEMByAFFoEBDAcFAxgFFoEBDQUFBBgF
- FoEBDQQGBRYGFoEBDAUHBAcmBYEBCwUIBQYmBYEBCwQKBAYmBYEBCgQLBQUmBYEBCQUMBS+BAQgFDAYv
- gQEDHC+BAQMdLoEBAx0ugQEDHi2BAQMJBAUIBC2BARAFCAQtgQEQBQgELYEBEAUJAS+BARAFESEHgQEQ
- BREhB4EBBwEIBQYBCiEHgQEHBAUFBAQJIQeBAQcEBQUEBAkEGAUHgQEGBQUFBAUIBBgFB4EBBgUFBQUE
- CAQYBQeBAQYFBQUFBAgEGAUHgQEGBAYFBQUHBBgFB4EBBgQGBQYEBwQYBQeBAQUFBgUGBQYEGAUHgQEF
- BQYFBwQGBBgFB4EBBQQHBQcEBgQYBQeBAQUEBwUHBQUEGAUHgQEEBQcFBwUFBBgFB4EBBAUHBQgEBQQY
- BQeBAQQECAUIBAUEGAUHgQEDBQgFCAEIBBgFB4EBAwUIBREEGAUHgQECBQkFEQQYBQeBAQIFCQURIQeB
- AQQCCgURIQeBARAFESEHgQEQBREhB4EBEAURIQeBARAFEQQYBQeBARAFEQQYBQeBARAFEQQYBQeBARAF
- EQQYBQeBAU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQEOAjEBDIEBDgUqBwqBAQ0FJwwJgQENBSETCIEBDQURAQcXDIEBDQURGxCBAQ0FERcU
- gQENBBIOBAUUgQEMBRIGDAUUgQEMBRIFDQUUgQEMBRIFDgQUgQEMBRIFDgQUgQEMBBMFDgQUgQELBRMF
- DgQUgQELBRMFDgUTgQELBRMFDgUTgQEDGQcFDgUTgQEDGwUoA4EBAxsFKAOBAQMbBSgDgQEDGwUoA4EB
- CgUKBQUFDwUSgQEKBAsEBgUQBBKBAQkFCwQGBRAFEYEBCQULBAYFEAURgQEJBQoFBgUQBRGBAQkFCgUG
- BREFEIEBCQQLBQYFEQUQgQEIBQsFBgUSBQkBBYEBCAULBQYFEgUJAwOBAQgFCwUGBQoFBAUIBAKBAQgF
- CwQHBQQLBAYHAwOBAQgECwUHFAUGBQQDgQEHBQsFAxgFBwQEA4EBBwULBQMVCQ4DgQEHBQsFAw8QDQOB
- AQcEDAUDCRcLBIEBBgULBQUCHwgFgQEGBQsFKAQHgQEGBQsFM4EBBgULBTOBAQYFCgUKIgiBAQUHCQUK
- IgiBAQUICAUKIgiBAQUKBgUKIgiBAQULBAULBRgFCIEBBA0DBQsFGAUIgQEEBQIHAgULBRgFCIEBBgMD
- DAwFGAUIgQENCwwFGAUIgQEOCgwFGAUIgQEQBw0FGAUIgQERBwwFGAUIgQERCAsiCIEBEAoKIgiBARAL
- CSIIgQEPDQgiCIEBDwUCBwcFGAUIgQEOBgMHBgUYBQiBAQ0GBQcFBRgFCIEBDAcGBgUFGAUIgQELBwgE
- BgUYBQiBAQoHCgMGBRgFCIEBCQcMAQcFGAUIgQEIBxUFGAUIgQEHCBUiCIEBBggWIgiBAQQJFyIIgQEF
- BxgiCIEBBQUaBRgFCIEBBgMbBRgFCIEBJAUYBQiBAU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEZBi+BARkGL4EBGgUvgQEaBS+BARoFL4EBGgUvgQEaBS+BARoF
- L4EBGgUvgQEaBRkBFYEBGgUZAxOBARoFDwEIBRKBARoFCwUIBxCBARoFBgoHCg6BARoVCQkNgQEJJgsJ
- C4EBCSYMCQqBAQohEgkIgQEKGxoIB4EBChUhCQWBARkFIwkEgQEZBSUGBYEBGQUmBAaBARkFKAIGgQEZ
- BTCBARkFMIEBGQUwgQEZBTCBARkFMIEBGQUwgQEZBTCBARkFCRAXgQEZBQQYFIEBGSMSgQEZJBGBARkS
- CAwPgQEXDhIJDoEBFQwYCA2BARMMGwcNgQESDB0HDIEBEA4eBgyBAQ8IAwQfBguBAQ4IBAQfBguBAQ0H
- BgQgBQuBAQwHBwQgBQuBAQsHCAUfBQuBAQoGCgUfBQuBAQoGCgUfBQuBAQkGCwUfBQuBAQkFDAUeBguB
- AQgGDAUeBguBAQgGDAUdBwuBAQgGDAUdBgyBAQgGDAUcBwyBAQkFDAUbBw2BAQkGCwUZCQ2BAQkHCgUY
- CQ6BAQoIBwYVCw+BAQsIBQcSDRCBAQwSChURgQENEQoTE4EBDhAKERWBARANDA4XgQESCwwLGoEBFQYO
- BSCBAU6BAU6BAU6BAU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEuBhqBAS4FG4EBLgUbgQEuBRuBARICGQYbgQEPBRkGG4EBDgYZ
- BRyBAQ8FGQUcgQEPBhgFHIEBDwYXBhyBARAFFwUdgQEQBRcFHYEBEAYNERqBAREFChcXgQERBQccFYEB
- EQYEIBOBARIFAg4DExGBARIRBwYEChCBARIOCgUHCQ+BARMKDQUJCA6BARIJDgYKCA2BAREIEAUNBwyB
- ARAJEAUOBgyBAQ8KDwYPBguBAQ4MDgUQBwqBAQ4MDgURBgqBAQ0GAgYMBRMFCoEBDAYEBQwFEwYJgQEM
- BgQFCwYTBgmBAQsGBQYKBRUFCYEBCgYHBQkGFQYIgQEKBQgGCAYVBgiBAQkGCQUIBRcFCIEBCQUKBgYG
- FwUIgQEJBQsFBgUYBQiBAQgFDAYEBhgFCIEBCAUNBQMGGQUIgQEIBQ0GAgYZBQiBAQcFDwwaBQiBAQcF
- DwwaBQiBAQcFEAobBQiBAQcFEAoaBgiBAQcFEQgbBgiBAQcFEgYcBgiBAQcFEQgbBQmBAQcFEAoZBgmB
- AQcFEAoZBgmBAQcFDwwXBgqBAQcFDg4VBwqBAQcGDAcCBxQGC4EBCAULBwQEFQcLgQEIBggIBgIVBwyB
- AQgIBAkHARUHDYEBCRMcCQ2BAQoRHAkOgQELDhwKD4EBDAwbChGBAQ4HGwwSgQEsDxOBASgRFYEBKQ4X
- gQEpDBmBASoIHIEBKwMggQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQE1DwqBASkbCoEBHiYKgQETMQqBAQc9CoEBBjQU
- gQEGIQQKGYEBBhcOBxyBAQcOFAcegQEHBhsHH4EBJwYhgQEmBiKBASUGFgEMgQEkBhUDDIEBIwYWBAuB
- ASMGFwQKgQEiBg8DBgQKgQEhBg8EBwQJgQEhBhAEBwQIgQEgBhEFBgQIgQEgBRMEBwQHgQEfBhQEBgUG
- gQEfBhQEBwQGgQEfBRYEBgIIgQEeBhYEEIEBHgYXBA+BAR4FGAMQgQEeBSuBAR0GK4EBHQYrgQEdBiuB
- AR0GK4EBHQYrgQEdBiuBAR0GK4EBHQYrgQEdBiuBAR4FK4EBHgYqgQEeBiqBAR4HKYEBHwYpgQEfByiB
- ASAHJ4EBIAcngQEhByaBASEJJIEBIgkjgQEjCiGBASQLH4EBJQwdgQEmDxmBASgTE4EBKhMRgQErEhGB
- AS4PEYEBMAwSgQE0CBKBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXATaBARQENoEBEgY2gQESBzWBARMGNYEBEwc0gQEUBjSB
- ARQGNIEBFQYzgQEVBiABEoEBFgYeAxGBARYGHAYQgQEWBxoHEIEBFwYYCg+BARcHFQsQgQEYBhMLEoEB
- GAYRCxSBARkGDgsWgQEZBgwLGIEBGgYJCxqBARoHBwocgQEbBgUKHoEBGwcCCiCBARwRIYEBHA8jgQEd
- DCWBAR0KJ4EBHAoogQEbCSqBARoILIEBGQgtgQEXCC+BARYIMIEBFQgxgQEVBzKBARQHM4EBEwc0gQES
- BzWBARIGNoEBEQY3gQERBjeBAREFOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQBjiBARAGOIEBEAY4gQEQ
- BjiBARAHN4EBEAc3gQERBzaBAREINYEBEgkzgQETCiEDDYEBEw0WCw2BARQtDYEBFisNgQEXKg2BARon
- DYEBHSARgQEjDxyBAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQEXBDOBARcLLIEBFxQjgQEXIRaBARchFoEBGh4WgQEiFhaBASsN
- FoEBNgEXgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEkDR2BAR4WGoEBGR0YgQEVIxaBARApFYEB
- DRcLCxSBAQ4QFAkTgQEODBoIEoEBDgkeBxKBAQ4GIgcRgQEPAiYGEYEBNwcQgQE4BhCBATgGEIEBOAYQ
- gQE4BhCBATkFEIEBOQUQgQE5BRCBATgGEIEBOAYQgQE4BhCBATgGEIEBNwcQgQE3BhGBATcGEYEBNgcR
- gQE1BxKBATUHEoEBNAcTgQEzCBOBATIIFIEBMQgVgQEvCRaBAS4JF4EBLAoYgQEqCxmBAScMG4EBJQ0c
- gQEhDx6BARwTH4EBGBQigQEZESSBARoNJ4EBGgoqgQEbBS6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQFOgQFOgQFDAgmBAUEECYEBQgQIgQE7AQYFB4EBOQQGBAeBATkEBwQGgQE5BQYFBYEB
- OgQHBAWBATsEBgUEgQE7BQYEBIEBPAQGBASBATwFBgEGgQEQBhsIBAQNgQEQKQQFDIEBECkFAQ+BARAp
- FYEBECkVgQEQKRWBAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6B
- AU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAU6BAQ0EPYEBDAY8gQEMBjyBAQwGPIEBDAU9gQEMBT2BAQsG
- PYEBCwY9gQELBj2BAQsGPYEBCwY9gQELBj2BAQsHPIEBCwc8gQEMBzuBAQwIOoEBDQg5gQENCiUCEIEB
- DgwdBxCBAQ8QDhEQgQEQLhCBAREtEIEBEysQgQEWJROBARobGYEBToEBToEBToEBToEBToHQ',
- '0AFOgQFOgQFOgQFOgQFCAwmBAUEECYEBQQUIgQE5AwYFB4EBOAQHBAeBASkCDgQGBQaBASYGDQUGBAaB
- ASYGDgQHBAWBASYGDgUGBAWBAScFDwQHBASBAScGDwQGAwWBAScGDwUNgQEoBRAEDYEBKAUQAw6BASgG
- IIEBKQUQAw2BASkFDAcNgQEpBgYMDYEBCgMdFw2BAQsVAh8NgQELNQ6BAQsxEoEBCysYgQELJh2BARkI
- CwUdgQEsBhyBAS0FHIEBLQUcgQEuBRuBAS4FG4EBLwUagQEvBRqBATAFGYEBMAYYgQExBRiBATEGF4EB
- MgUXgQEyBhaBATMGFYEBNAUVgQE0BhSBATUGE4EBEAIWCgMHEoEBEAYSFRGBAQ8GExURgQEPBRQUEoEB
- DgYaDROBAQ4GIwQTgQEOBTuBAQ0GO4EBDQY7gQENBTyBAQ0FPIEBDQU8gQENBTyBAQ0FPIEBDQU8gQEN
- BjuBAQ0GO4EBDgY6gQEOBzmBAQ4IOIEBDwg3gQEQCh4BFYEBEQwVBxWBARInFYEBEyYVgQEVJBWBARgh
- FYEBGxoZgQFOgQFOgQFOgQFOgdA=',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQEJBz6BAQkGP4EBCQY/gQEJBigDFIEB
- CQYlBhSBAQkGJQcTgQEJBiYHEoEBCQYnBhKBAQkGJwcRgQEJBigGEYEBCQYoBxCBAQkGKQYQgQEJBioG
- D4EBCQYqBg+BAQkGKgcOgQEJBisGDoEBCgUrBg6BAQoFLAYNgQEKBSwGDYEBCgUtBgyBAQoFLQYMgQEK
- BS0GDIEBCgUuBguBAQoFLgYLgQEKBS4GC4EBCgYtBguBAQoGLgYKgQEKBi4GCoEBCgYuBgqBAQoGLwYJ
- gQELBS8GCYEBCwUvBgmBAQsFLwYJgQELBhMBGgYJgQELBhMCGgYIgQELBhMDGQYIgQEMBRMEGAYIgQEM
- BhEGFwYIgQEMBhEGFwYIgQEMBhAGGQUIgQENBg8GGQUIgQENBg8GGQUIgQENBg4GGgILgQEOBg0GJ4EB
- DgcLBiiBAQ4HCgcogQEPBwgHKYEBEAcGCCmBARAKAQkqgQEREiuBARIRK4EBEhAsgQETDi2BARULLoEB
- FwcwgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgQFOgdA=',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQFOgQElBiOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYF
- I4EBJgUQBQ6BAQ4IEAUGDw6BAQ4yDoEBDjIOgQEOMg6BAQ4rFYEBGhIigQEmBSOBASYFI4EBJgUjgQEm
- BSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBRQCDYEBDQMWBQwKDYEBDQ8JHA2BAQ4zDYEBDjMN
- gQEOMg6BARAnF4EBJQYjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUjgQEmBSOBASYFI4EBJgUj
- gQEmBSOBAR0EBQUjgQEXFCOBARQZIYEBEh4egQERIRyBARAJDBAZgQEPBxARF4EBDgYSExWBAQ4FEwYD
- CxSBAQ4FEwYFCxKBAQ0FFAYHChGBAQ0FFAYJCg+BAQ0FFAYKCg6BAQ0GEwYMCQ2BAQ4FEwYNCQyBAQ4G
- EQYQBg2BAQ4HDwcRBQ2BAQ8IDAgSAw6BAQ8bFAEPgQERGCWBARIWJoEBFBIogQEXDSqBAU6BAU6BAU6B
- AU6BAU6BAU6B0A==',
- '0AFOgQFOgQFOgQFOgQFOgQFOgQEpBh+BASkGH4EBKQYfgQEqBR+BASoFH4EBKgUfgQEqBR+BASoFH4EB
- KgUfgQEqBR+BARkuB4EBBkEHgQEHQAeBAQdAB4EBB0AHgQEHDBcFH4EBKgUfgQEqBR+BASoFH4EBKgUf
- gQEqBR+BASoFH4EBKgUfgQEgDx+BAR4RH4EBHBMfgQEbFB+BARoIBAkfgQEZBwkGH4EBGQYLBh6BARgG
- DQUegQEYBQ4GHYEBFwYPBR2BARcFEAUdgQEXBRAFHYEBFwUQBhyBARcFEAYcgQEXBQ8HHIEBFwUPBxyB
- ARcGDgccgQEXBg0IHIEBGAYMBx2BARgHCggdgQEZCAYKHYEBGhcdgQEbFh2BARwVHYEBHgsBBh6BASAG
- BAYegQEpBh+BASkGH4EBKAYggQEnByCBASYHIYEBJQcigQEkCCKBASIJI4EBIAokgQEeCyWBARwLJ4EB
- GQ0ogQEWDiqBARcMK4EBGAktgQEZBTCBARoCMoEBToEBToEBToEBToEBToEBToHQ',];
- $m="";for($k=0;$k"'".trim(
- chunk_split(str_replace("\n","",$C),80,"\n"))."',",$c)),"\n{$m}","{$s}\nQ;\n"],$s));
- ]]>
-
-
-
diff --git a/vhosts/blog/content/posts/2024-12-33/2024-reflections.dj b/vhosts/blog/content/posts/2024-12-33/2024-reflections.dj
new file mode 100644
index 00000000..88b6c9b9
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-12-33/2024-reflections.dj
@@ -0,0 +1,84 @@
+---
+[article]
+uuid = "d7f98354-83fc-4cf1-8769-2784f0ebb6c8"
+title = "2024年の振り返り"
+description = "2024年にやったことを振り返る"
+tags = [
+]
+
+[[article.revisions]]
+date = "2025-01-02"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+ご存じのとおり、4 と 11 と 23 で割り切れる年は閏年というやつで 12 月が 33 日まである。
+1年の振り返りを書く猶予が平年よりも長くなるので大変に都合がよい。
+
+去年のやつ: [/posts/2023-12-31/2023-reflections/](/posts/2023-12-31/2023-reflections/)
+
+{#conference}
+# 登壇・カンファレンス参加
+
+参加または登壇した勉強会やカンファレンス。
+LT 等も含めて計 8 回の登壇をおこなった。
+また、4つのカンファレンスでコアスタッフまたは当日スタッフとして参加した。
+
+* PHP カンファレンス北海道 2024 オンラインで参加
+* [PHP 勉強会@東京 第 160 回 登壇](/slides/2024-01-24/phpstudy-tokyo-160/)
+* [YAPC::Hiroshima 2024 参加](/posts/2024-02-10/yapcjapan-2024-report/)
+* [PHPカンファレンス関西 2024 参加](/posts/2024-02-22/phpkansai-2024-report/)
+* PHPerKaigi 2024
+
+ * [登壇](/slides/2024-03-08/phperkaigi-2024/)
+ * コアスタッフとして参加
+
+* [Ya8 2024 登壇](/slides/2024-03-15/ya8-2024/)
+* PHP カンファレンス小田原 2024
+
+ * [登壇](/slides/2024-04-13/phpcon-odawara-2024/)
+ * 当日スタッフとして参加
+
+* [PHP 勉強会@東京 第 163 回 LT で登壇](/slides/2024-04-25/phpstudy-tokyo-163/)
+* [PHP カンファレンス香川 2024 参加](/posts/2024-05-11/phpconkagawa-2024-report/)
+* [ScalaMatsuri 2024 参加](/posts/2024-06-19/scalamatsuri-2024-report/)
+* [PHP 勉強会@東京 第 166 回 登壇](/slides/2024-07-18/phpstudy-tokyo-166/)
+* iOSDC Japan 2024 コアスタッフとして参加
+* Nix meetup #1 参加
+* [PHP 勉強会@東京 第 169 回 登壇](/slides/2024-10-30/phpstudy-tokyo-169/)
+* [紅白ぺぱ合戦 LT で登壇](/slides/2024-11-30/cohackpp/)
+* PHP カンファレンス 2024 当日スタッフとして参加
+
+{#articles}
+# 書いた記事
+
+今年はこのブログに月1記事以上の記事を書くという目標を立てていた。本数としては 12 本以上あるが、10月と11月はゼロになってしまった。
+社内記事を社外向けにリライトする作業を中々進められていないので、2025年は定期的に消化していきたい。
+
+* 社外記事 (このブログ): 15本
+* 社内記事: 22本
+
+ * 年間で最も記事を書いた人として社内表彰された
+
+{#coding}
+# 作ったもの
+
+今年は主に WebAssembly ランタイムと、カンファレンスの企画で使うシステムを作っていた。
+後者のシステムでもサンドボックス化のための技術として WebAssembly を用いているので、今年は WebAssembly と戯れた一年だったと言える。
+
+* [Waddiwasi: pure PHP で書かれた WebAssembly ランタイム](https://github.com/nsfisis/php-waddiwasi)
+* [Albatross.PHP: PHPerKaigi 2024 のコードゴルフ企画で使われたシステム](https://github.com/nsfisis/phperkaigi-2024-albatross)
+* [Albatross.swift: iOSDC Japan 2024 のコードバトル企画で使われたシステム](https://github.com/nsfisis/iosdc-japan-2024-albatross)
+* [ReparoJSON: 文法エラーを直すだけの JSON フォーマッタ](/posts/2024-07-19/reparojson-fix-only-json-formatter/)
+
+{#misc}
+# その他
+
+* [MN-Core Challenge #1 に参加](/posts/2024-09-28/mncore-challenge-1/)
+* ISUCON 14 に参加
+
+{#outro}
+# おわりに
+
+今年も大変お世話になりました。よいお年を!
diff --git a/vhosts/blog/content/posts/2024-12-33/2024-reflections.ndoc b/vhosts/blog/content/posts/2024-12-33/2024-reflections.ndoc
deleted file mode 100644
index f28c005b..00000000
--- a/vhosts/blog/content/posts/2024-12-33/2024-reflections.ndoc
+++ /dev/null
@@ -1,101 +0,0 @@
----
-[article]
-uuid = "d7f98354-83fc-4cf1-8769-2784f0ebb6c8"
-title = "2024年の振り返り"
-description = "2024年にやったことを振り返る"
-tags = [
-]
-
-[[article.revisions]]
-date = "2025-01-02"
-remark = "公開"
----
-
-
-
- 登壇・カンファレンス参加
-
- 参加または登壇した勉強会やカンファレンス。
- LT 等も含めて計 8 回の登壇をおこなった。
- また、4つのカンファレンスでコアスタッフまたは当日スタッフとして参加した。
-
-
-
-
- 書いた記事
-
- 今年はこのブログに月1記事以上の記事を書くという目標を立てていた。本数としては 12 本以上あるが、10月と11月はゼロになってしまった。
- 社内記事を社外向けにリライトする作業を中々進められていないので、2025年は定期的に消化していきたい。
-
-
- - 社外記事 (このブログ): 15本
- - 社内記事: 22本
-
- - 年間で最も記事を書いた人として社内表彰された
-
-
-
-
-
- 作ったもの
-
- 今年は主に WebAssembly ランタイムと、カンファレンスの企画で使うシステムを作っていた。
- 後者のシステムでもサンドボックス化のための技術として WebAssembly を用いているので、今年は WebAssembly と戯れた一年だったと言える。
-
-
-
-
-
- おわりに
-
- 今年も大変お世話になりました。よいお年を!
-
-
-
diff --git a/vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj b/vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj
new file mode 100644
index 00000000..c3a5eb49
--- /dev/null
+++ b/vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj
@@ -0,0 +1,350 @@
+---
+[article]
+uuid = "ce8f20e8-c79f-48f8-982d-53edd4d20483"
+title = "PHPerKaigi 2023 トークン問題解説 (1/5)"
+description = "PHPerKaigi 2023 でデジタルサーカス株式会社から出題した問題を解説する。全5問中の第1問。"
+tags = [
+ "conference",
+ "php",
+ "phperkaigi",
+ "piet",
+]
+
+[[article.revisions]]
+date = "2025-01-08"
+remark = "公開"
+
+[[article.revisions]]
+date = "2025-01-11"
+remark = "読みやすさのため一部の文言を調整"
+---
+{#intro}
+# はじめに
+
+::: note
+これは PHPerKaigi 2023 の記事です。今は 2025 年ですが、PHPerKaigi 2023 の記事です。
+:::
+
+2023-03-23 から 2023-03-25 にかけて開催された [PHPerKaigi 2023](https://phperkaigi.jp/2023/) では、PHPer チャレンジという企画がおこなわれた。
+PHPer チャレンジとは、スポンサーのパンフレットやカンファレンス会場などから「#」記号で始まる文字列を集め、景品などを得るという企画である。
+この文字列は「PHPer トークン」と呼ばれている。弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) からは、トークン問題という形で、PHP に関する問題を解くと PHPer トークンが得られるようになっている問題を出題した。
+
+[PHPerKaigi 2023 の参加レポ](/posts/2023-04-04/phperkaigi-2023-report/) でも書いたとおり、この年のトークン問題は「昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作」された。
+PHPerKaigi 当日も [PHPer チャレンジ解説セッション](/slides/2023-03-25/phperkaigi-2023-tokens/) という形で解説の機会を頂いたのだが、せっかく時間をかけて作題したので記事の形でも残しておこうと思う。
+
+この記事では、全5問ある中の第1問について解説する。他の問題については以下のリンクを参照のこと。
+
+1. [第1問 (この記事)](/posts/2025-01-08/phperkaigi-2023-tokens-q1/)
+1. 第2問 (TODO: 執筆中)
+1. 第3問 (TODO: 執筆中)
+1. 第4問 (TODO: 執筆中)
+1. 第5問 (TODO: 執筆中)
+
+それぞれの問題はこちらの GitHub リポジトリ ( [nsfisis/PHPerKaigi2023-tokens](https://github.com/nsfisis/PHPerKaigi2023-tokens) ) からも閲覧できる。
+
+{#quiz}
+# Q1: An Art of Computer Programming
+
+第1問『An Art of Computer Programming』はこちら。
+
+
+
+{#how-to-solve}
+# 解き方
+
+まずはトークンを得る方法を解説抜きで説明する。次のように実行する。
+
+```
+$ echo "#iwillblog" | php Q1.png >/dev/null
+```
+
+無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。
+
+{#commentary}
+# 解説
+
+{#read-as-image}
+## 画像として解釈する
+
+まずは素直に画像として見てみよう。
+全体は QR コードになっている。適当な QR コードリーダで読み込むと、次のようなテキストが表示されるはずだ。
+
+```
+Guess password. $ echo "password" | php Q1.png >/dev/null
+```
+
+メッセージは、この画像の実行方法とこの問題でやるべきこと (パスワードの推測) を示している。
+
+次に QR コードの中央部に目を向けると、小さな文字で「Password is one of the PHPer tokens.」と書かれているのがわかる。
+他の PHPer トークンの中から適切な1つを見つけだし、「パスワード」として渡すことで答えとなる PHPer トークンが得られるというわけだ。
+
+{#password}
+## パスワード
+
+不正なパスワードを使って実行してみると、次のようなエラーメッセージが表示される。
+
+```
+$ echo "foo" | php Q1.png >/dev/null
+401 Unauthorized
+```
+
+すでに [「解き方」の節](#section--how-to-solve) で示したように、パスワードである PHPer トークンは「#iwillblog」である。これを与えて実行すると正解のトークンが得られる。
+
+このパスワードの選択にはとある事情がある。
+今回の問題の作問は前回の開催 (PHPerKaigi 2022) 直後からスタートしており、この時点では PHPerKaigi 2023 で登録される PHPer トークンにどのようなものがあるかはまったくわからない状態であった。
+作問作業を早期に終わらせるには、次回開催でも確実に使われるであろう定番のトークンを予測して選ぶ必要があったのだ。
+かくして、私が知る限り毎回登場しているトークンである「#iwillblog」に白羽の矢が立てられた。
+
+なお、解いてくださった方の中には、先頭の「#」を入力せずに何度も試してしまい答えが得られずじまいになった方もいらっしゃるようだった。
+問題を置いていたリポジトリにヒントとしてパスワードのトークンが「i」で始まると書いていたのだが、これが意図せずミスリードになってしまった。
+これは私のミスである。
+
+{#png-steganography}
+## PNG ステガノグラフィ
+
+QR コードも言っているように、このファイルは PNG 画像であるにもかかわらず PHP で実行することができる。なぜこのようなことが可能なのか。
+
+PNG 画像のフォーマットは、次のようになっている。
+
+1. マジックナンバーなど
+1. PNG ヘッダ (`IHDR` チャンク)
+1. 実際の画像データ (`IDAT` チャンク)
+1. PNG フッタ (`IEND` チャンク)
+
+PNG フッタの後ろにあるデータは、画像ビューアには解釈されず、画像の表示には影響を与えない。したがって、PNG フッタの後ろには任意のデータを埋め込むことができる。
+
+さて、PHP には、PHP プログラムの始まりを示すための PHP タグ (`<?php` または `<?`) がある。
+CLI で実行する場合、PHP タグよりも前にあるデータは標準出力へそのまま出力される。
+
+この画像ファイルは次のような構造になっていた。
+
+1. マジックナンバーなど
+1. PNG ヘッダ (`IHDR` チャンク)
+1. 実際の画像データ (`IDAT` チャンク)
+1. PNG フッタ (`IEND` チャンク)
+1. *PHP タグ (`<?php`)*
+1. *通常の PHP ソースコード*
+
+PNG ファイルとして読むときは PNG フッタ以降は無視され、PHP スクリプトとして読むときは PHP タグ以前が無視されるという仕掛けである。
+
+`strings` コマンドを使うと、隠されたデータを簡単に閲覧できる。
+
+```
+IHDR
+-HHc
+/dev/null
+```
+
+には標準出力を捨てるよう `>/dev/null` と指定されている。
+
+なお、このように PNG 画像などに本来のデータとは異なる別のデータを隠すことを「ステガノグラフィ」( [Wikipedia「ステガノグラフィー」](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%86%E3%82%AC%E3%83%8E%E3%82%B0%E3%83%A9%E3%83%95%E3%82%A3%E3%83%BC) ) と呼ぶ。
+
+{#php-program}
+## 実行される PHP プログラム
+
+画像の正体がわかったところで、画像に隠されていた PHP プログラムについて見ていこう。
+先ほどは一部しか記載しなかったので、全体を載せる。
+なお、ある程度ゴルフしながら書いたので、空白こそ残しているものの可読性は非常に低いことと思う。
+
+```php
+ 1e5) {
+ echo "infinite loop detected\n";
+ break;
+ $c1 = $cs[$pc];
+ $y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6;
+ $x = (3 + $c1%3 - $c0%3) % 3;
+ match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) {
+ 1 => $m[] = $b,
+ 2 => array_pop($m),
+ 3 => $m[] = array_pop($m) + array_pop($m),
+ 4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)),
+ 5 => $m[] = array_pop($m) * array_pop($m),
+ 8 => $m[] = array_pop($m) === 0 ? 1 : 0,
+ 11 => $cc *= pow(-1, array_pop($m)),
+ 12 => $m[] = $m[count($m)-1],
+ 13 => $m = (fn($n, $d, $m, $l) => [
+ ...array_slice($m, 0, $l-$d),
+ ...array_reverse([
+ ...array_reverse(array_slice($m, $l-$d, $d-$n)),
+ ...array_reverse(array_slice($m, $l-$n)),
+ ]),
+ ])(array_pop($m), array_pop($m), $m, count($m)),
+ 15 => !empty($i) and $m[] = array_shift($i),
+ 16 => $o .= sprintf('%d', array_pop($m)),
+ 17 => $o .= sprintf('%c', array_pop($m)),
+ default => 'nop',
+ };
+ $c0 = $c1;
+ for ($j = 0; $j < 8; $j++) {
+ $v = [];
+ if ($c1 === 1) {
+ $x = $pc % $w;
+ $y = intdiv($pc, $w);
+ $e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp];
+ $z = [1, $w, -1, -$w][$dp];
+ for ($ep = $pc; $ep !== $e; $ep += $z)
+ if ($cs[$ep] !== 1) break;
+ $ep -= $z;
+ $pc = $ep;
+ } else {
+ $q = [$pc];
+ $ep = $pc;
+ while (!empty($q)) {
+ $qq = array_pop($q);
+ $v[$qq] = true;
+ foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) {
+ if ($cs[$qp] !== $c1) continue;
+ if (isset($v[$qp])) continue;
+ $q[] = $qp;
+ $qx = $qp % $w;
+ $qy = intdiv($qp, $w);
+ $x = $ep % $w;
+ $y = intdiv($ep, $w);
+ if (
+ ($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc)))
+ || ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc)))
+ || ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc)))
+ || ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc)))
+ )
+ $ep = $qp;
+ }
+ }
+ }
+ $np = $ep + [1, $w, -1, -$w][$dp];
+ if ($cs[$np] !== 0) {
+ $b = count(array_keys($v));
+ $pc = $np;
+ break;
+ }
+ if ($j === 7) break 2;
+ if ($j % 2 === 0) $cc = -$cc;
+ if ($j % 2 === 1) $dp = ($dp+1) % 4;
+// The original Piet image is wrong: it outputs 403 error for invalid passwords.
+// Failure of authentication should be notified by 401, not 403.
+// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
+// Piet any longer at that time.
+fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+```
+
+これは一体なんなのか。ずばり、難解プログラミング言語の一つ Piet のインタプリタである。
+Piet はピエト・モンドリアン (『赤・青・黄のコンポジション』などで知られる抽象画家) の作品にインスピレーションを受けて作られた、画像をソースコードとするプログラミング言語である。
+インタプリタは画像の各ピクセルの上を進みながら、色等に応じて特定の処理をおこなっていく。
+ここでは詳しい言語仕様については解説しないので、気になる方は [Wikipedia の記事「Piet」](https://ja.wikipedia.org/wiki/Piet) などを参照してほしい。
+
+プログラムの冒頭にあるこの箇所
+
+```php
+$b = unpack('C*', file_get_contents(__FILE__));
+```
+
+で `__FILE__` つまりこの画像ファイルを読み込んでいる。
+先ほど Piet は画像をソースコードにしていると説明した。
+そう、今回の問題の画像ファイル `Q1.png` は、PHP 製 Piet インタプリタであると同時に、Piet のソースコード画像でもあるのだ。
+QR コード中央のカラフルな部分が Piet の命令になっている。
+
+{#piet-source-code}
+## Piet のソースコード
+
+さて、Piet でどのようなコードが書かれて (いや、描かれて) いるのかを解説したいところだが、今の私にはできそうにない。
+というのも、すでに述べたように Piet は「難解プログラミング言語」である。
+およそ人が描いたり読んだりするようには作られていない。性質としては、パズルに近い代物である。
+
+というわけで、ここではあらましを説明するだけでご容赦いただきたい。
+それぞれの部分はおおよそ次のようなことをやっている (再検証・再読解はしていないので大嘘かもしれない)。
+
+* 左上: 入力受け付け
+
+ * 標準入力から1文字ずつ読み込み、入力がなくなるまでスタックに積む。多分。
+
+* 上辺、右辺: パスワードの検証
+
+ * 入力がパスワードと一致するか (= `#iwillblog` かどうか) を調べる。多分。
+
+* 下辺、左辺、上辺の3列目、右辺の3列目、下辺の2列目: トークンの出力
+
+ * パスワードと一致していればここに飛んでくる。正解のトークンを出力する。多分。
+
+* 右辺の2列目、上辺の2列目: 不正解のメッセージ出力
+
+ * パスワードと一致していなければここに飛んでくる。不正解のときのメッセージを出力する。多分。
+
+ところで、先ほど掲載した Piet のインタプリタのソースコード末尾には次のような箇所がある。
+
+```php
+// The original Piet image is wrong: it outputs 403 error for invalid passwords.
+// Failure of authentication should be notified by 401, not 403.
+// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
+// Piet any longer at that time.
+fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+```
+
+コメントにも書かれているが、この Piet のソースコード画像には誤りがあった。
+本来 HTTP のステータスコードを真似るのなら、認証の失敗には 401 を返さなければならない。
+しかし、Piet のソースは 403 を返すように書いてしまっていた。
+そのことに私が気付いたのは PHPerKaigi 2023 が開催されるひと月前で、その時点で私はこの Piet のソースコードを (ちょうどこの記事でそうなっているのと同じように) 読解できなくなっていた。
+さらに悪いことに、正しいメッセージ「401 Unauthorized」は元の「403 Forbidden」よりも3文字長い。
+3文字出力が長くなるということは、それだけ Piet で塗るべきピクセルが増えることを意味する。
+もはや3文字追加で出力するだけの余白はこの画像に残されていなかった (と思う。腕ききの Piet プログラマならできるかもしれないので挑戦してみてほしい)。
+
+これを解決するために私が選んだのは、インタプリタを改造し、本来のメッセージとは異なるメッセージを無理やり出力させて帳尻を合わせることだった。
+そういうわけでこの Piet インタプリタは完全な Piet インタプリタではなく、「403 Forbidden」というテキストを絶対に出力できない。
+
+{#misc}
+## その他小ネタ
+
+ここまでで問題の核心部分は説明し終えたので、ここからは残った小ネタを紹介しておく。
+
+この問題のタイトル『An Art of Computer Programming』は、ドナルド・クヌースの『The Art of Computer Programming』をパロディしたものである。
+
+この問題で得られるトークン「#ModernPHPisStaticallyTypedLanguage」は特に元ネタがあるわけではない。当然のような顔で嘘を主張したかったのでこうなった。
+
+{#outro}
+# おわりに
+
+この問題の自己評価はこちら。
+問題の出題順はおおよそ作成した順になっているのだが、そのせいで難易度高めの問題が1問目に配置されてしまった。
+これは反省点の一つである。
+
+* 難しさ: ★★★★
+* お気に入り度: ★★
+* 鮮やかさ: ★★★★★★★
diff --git a/vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.ndoc b/vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.ndoc
deleted file mode 100644
index ede3fdfb..00000000
--- a/vhosts/blog/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.ndoc
+++ /dev/null
@@ -1,406 +0,0 @@
----
-[article]
-uuid = "ce8f20e8-c79f-48f8-982d-53edd4d20483"
-title = "PHPerKaigi 2023 トークン問題解説 (1/5)"
-description = "PHPerKaigi 2023 でデジタルサーカス株式会社から出題した問題を解説する。全5問中の第1問。"
-tags = [
- "conference",
- "php",
- "phperkaigi",
- "piet",
-]
-
-[[article.revisions]]
-date = "2025-01-08"
-remark = "公開"
-
-[[article.revisions]]
-date = "2025-01-11"
-remark = "読みやすさのため一部の文言を調整"
----
-
-
- はじめに
-
- これは PHPerKaigi 2023 の記事です。今は 2025 年ですが、PHPerKaigi 2023 の記事です。
-
-
- 2023-03-23 から 2023-03-25 にかけて開催された PHPerKaigi 2023 では、PHPer チャレンジという企画がおこなわれた。
- PHPer チャレンジとは、スポンサーのパンフレットやカンファレンス会場などから「#」記号で始まる文字列を集め、景品などを得るという企画である。
- この文字列は「PHPer トークン」と呼ばれている。弊社デジタルサーカス株式会社からは、トークン問題という形で、PHP に関する問題を解くと PHPer トークンが得られるようになっている問題を出題した。
-
-
- PHPerKaigi 2023 の参加レポ でも書いたとおり、この年のトークン問題は「昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作」された。
- PHPerKaigi 当日も PHPer チャレンジ解説セッション という形で解説の機会を頂いたのだが、せっかく時間をかけて作題したので記事の形でも残しておこうと思う。
-
-
- この記事では、全5問ある中の第1問について解説する。他の問題については以下のリンクを参照のこと。
-
-
- - 第1問 (この記事)
- - 第2問 (TODO: 執筆中)
- - 第3問 (TODO: 執筆中)
- - 第4問 (TODO: 執筆中)
- - 第5問 (TODO: 執筆中)
-
-
- それぞれの問題はこちらの GitHub リポジトリ (nsfisis/PHPerKaigi2023-tokens) からも閲覧できる。
-
-
-
- Q1: An Art of Computer Programming
-
- 第1問『An Art of Computer Programming』はこちら。
-
-
-
-
- 解き方
-
- まずはトークンを得る方法を解説抜きで説明する。次のように実行する。
-
-
- /dev/null
- ]]>
-
-
- 無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。
-
-
-
-
- おわりに
-
- この問題の自己評価はこちら。
- 問題の出題順はおおよそ作成した順になっているのだが、そのせいで難易度高めの問題が1問目に配置されてしまった。
- これは反省点の一つである。
-
-
- - 難しさ: ★★★★
- - お気に入り度: ★★
- - 鮮やかさ: ★★★★★★★
-
-
-
diff --git a/vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj b/vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj
new file mode 100644
index 00000000..44e8a4f6
--- /dev/null
+++ b/vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj
@@ -0,0 +1,76 @@
+---
+[article]
+uuid = "da2a0cec-74b3-4c5e-b2a2-47fe79ef49f9"
+title = "【YAML】YAML 1.1 と YAML 1.2 の主な破壊的変更"
+description = "データ記述言語 YAML におけるバージョン 1.1 と 1.2 の主な破壊的変更をまとめた。"
+tags = [
+ "yaml",
+]
+
+[[article.revisions]]
+date = "2021-06-30"
+remark = "デジタルサーカス株式会社の社内記事として公開"
+isInternal = true
+
+[[article.revisions]]
+date = "2025-01-26"
+remark = "ブログ記事として一般公開"
+---
+::: note
+この記事は、2021-06-30 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+:::
+
+{#intro}
+# はじめに
+
+データ記述言語の一つ YAML には 1.0、1.1、1.2 のバージョンがある。
+これらのうち、1.1 と 1.2 の間には無視できない非互換の変更が多く、1.2 に対応していないライブラリもある (Ruby 同梱の `yaml` など)。
+この記事では、YAML 1.1 と YAML 1.2 の主な破壊的変更を紹介する (影響範囲が広いものを抜粋しており、すべての非互換を網羅してはいない)。
+
+参照した仕様書はこちら: https://yaml.org/spec/1.2.2/ext/changes/
+
+{#breaking-changes}
+# 主な破壊的変更
+
+{#boolean-literals}
+### Boolean としてパースされるトークンが `true` / `false` とその亜種のみに
+
+この変更の影響が最も大きいと思われる。
+YAML 1.1 では、boolean 値のリテラルとして `true`、`false` のほか `yes`、`no`、`y`、`n`、`on`、`off`、それらの大文字バージョンなどが認められていた。
+YAML 1.2 では、`true` と `false`、それらの大文字バージョン (`True`、`TRUE`、`False`、`FALSE`) のみが boolean としてパースされるようになった。
+
+{#octal-literals}
+### 八進数リテラルには `0o` が必須に
+
+C 言語などでは、`0` から始まる数字の列を八進数としてパースする。
+YAML 1.1 もこれに準じていたが、1.2 からは `0o` のプレフィクスが必須となった ("o" は "octal" の "o")。
+プログラミング言語では、Python や Haskell、Swift、Rust などがこの記法を採用している。
+
+{#merging}
+### `<<` によるマージが不可能に
+
+YAML 1.1 では、`<<` という文字列をキーに指定することで、マップをマージすることができた。
+
+```yaml
+x: &base
+ a: 123
+# => { "x": { "a": 123 } }
+
+y:
+ <<: *base
+ b: 456
+# => { "y": { "a": 123, "b": 456 } }
+```
+
+1.2 からはこれができなくなる。
+
+{#number-separator}
+### 数字を `_` で区切るのが禁止に
+
+`1234567` を `1_234_567` と書けなくなった。
+
+{#outro}
+# おわりに
+
+全体的に、_There's more than one way to do it._ から _There should be one - and preferably only one - obvious way to do it._ へ移行しているように思われる。
+データ記述言語としては望ましい方向性ではないかと感じる。
diff --git a/vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.ndoc b/vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.ndoc
deleted file mode 100644
index 74c2c14e..00000000
--- a/vhosts/blog/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.ndoc
+++ /dev/null
@@ -1,87 +0,0 @@
----
-[article]
-uuid = "da2a0cec-74b3-4c5e-b2a2-47fe79ef49f9"
-title = "【YAML】YAML 1.1 と YAML 1.2 の主な破壊的変更"
-description = "データ記述言語 YAML におけるバージョン 1.1 と 1.2 の主な破壊的変更をまとめた。"
-tags = [
- "yaml",
-]
-
-[[article.revisions]]
-date = "2021-06-30"
-remark = "デジタルサーカス株式会社の社内記事として公開"
-isInternal = true
-
-[[article.revisions]]
-date = "2025-01-26"
-remark = "ブログ記事として一般公開"
----
-
-
- この記事は、2021-06-30 にデジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
-
-
- はじめに
-
- データ記述言語の一つ YAML には 1.0、1.1、1.2 のバージョンがある。
- これらのうち、1.1 と 1.2 の間には無視できない非互換の変更が多く、1.2 に対応していないライブラリもある (Ruby 同梱の yaml など)。
- この記事では、YAML 1.1 と YAML 1.2 の主な破壊的変更を紹介する (影響範囲が広いものを抜粋しており、すべての非互換を網羅してはいない)。
-
-
- 参照した仕様書はこちら: https://yaml.org/spec/1.2.2/ext/changes/
-
-
-
- 主な破壊的変更
-
- Boolean としてパースされるトークンが true / false とその亜種のみに
-
- この変更の影響が最も大きいと思われる。
- YAML 1.1 では、boolean 値のリテラルとして true、false のほか yes、no、y、n、on、off、それらの大文字バージョンなどが認められていた。
- YAML 1.2 では、true と false、それらの大文字バージョン (True、TRUE、False、FALSE) のみが boolean としてパースされるようになった。
-
-
-
- 八進数リテラルには 0o が必須に
-
- C 言語などでは、0 から始まる数字の列を八進数としてパースする。
- YAML 1.1 もこれに準じていたが、1.2 からは 0o のプレフィクスが必須となった ("o" は "octal" の "o")。
- プログラミング言語では、Python や Haskell、Swift、Rust などがこの記法を採用している。
-
-
-
- << によるマージが不可能に
-
- YAML 1.1 では、<< という文字列をキーに指定することで、マップをマージすることができた。
-
-
- { "x": { "a": 123 } }
-
- y:
- <<: *base
- b: 456
- # => { "y": { "a": 123, "b": 456 } }
- ]]>
-
-
- 1.2 からはこれができなくなる。
-
-
-
- 数字を _ で区切るのが禁止に
-
- 1234567 を 1_234_567 と書けなくなった。
-
-
-
-
- おわりに
-
- 全体的に、There's more than one way to do it. から There should be one - and preferably only one - obvious way to do it. へ移行しているように思われる。
- データ記述言語としては望ましい方向性ではないかと感じる。
-
-
-
diff --git a/vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj b/vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj
new file mode 100644
index 00000000..cf5be3f4
--- /dev/null
+++ b/vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj
@@ -0,0 +1,49 @@
+---
+[article]
+uuid = "13174dc7-c1a3-465f-9ba6-14f0bc6f5961"
+title = "PHP カンファレンス名古屋 2025 参加レポ"
+description = "2025-02-22 に開催された、PHP カンファレンス名古屋 2025 に参加した。"
+tags = [
+ "conference",
+ "php",
+ "phpcon-nagoya",
+]
+
+[[article.revisions]]
+date = "2025-02-24"
+remark = "公開"
+---
+{#intro}
+# はじめに
+
+2025-02-22 に開催された [PHP カンファレンス名古屋](https://phpcon.nagoya/2025/) に参加した。
+
+{#sessions}
+# セッション感想
+
+特に印象に残ったセッションを二つピックアップした (タイトルと発表者名は fortee のプロポーザルページによる)。
+
+* [PHPで印刷所に入稿できる名札データを作る by 長谷川智希 さん](https://fortee.jp/phpcon-nagoya-2025/proposal/26795bcc-78dd-431e-9538-7450779fa2cf)
+
+ * PHPerKaigi や iOSDC の名札は品質が高いので、他の勉強会やカンファレンスでもついつい使ってしまうのですが、その裏側を覗くことができ面白かったです。カンファレンスの1セッションという形でなければ触れることのないような話が聴けるのはカンファレンスに参加する醍醐味の一つだと思います。
+
+* [PHP 製 OSS のメモリ問題を辻斬りしていく by sji さん](https://fortee.jp/phpcon-nagoya-2025/proposal/d3ecbb68-318d-4b03-abfe-9ecccc6beb81)
+
+ * 今回一番楽しみにしていた発表です。 [Reli](https://github.com/reliforp/reli-prof) は以前 [自作の WebAssembly 処理系を高速化するのに使ったのもあり](/slides/2024-03-15/ya8-2024/) その強力さについてはある程度知っていたつもりでしたが、実際に広く使われているライブラリでの調査過程を見ると唸るばかりです。これをすべて (FFI こそ使っているものの) pure PHP で実装しているとは俄に信じられません。
+
+{#my-session}
+# 登壇したセッション
+
+[「PHP 処理系の garbage collection を理解する 〜メモリはいつ解放されるのか〜」](https://fortee.jp/phpcon-nagoya-2025/proposal/24a2ec04-ca57-46f1-905c-52143a449eea) というタイトルで登壇もおこなった。タイトルどおり、PHP の garbage collection (GC) について扱った発表である。
+
+技術的な内容としては [PHP のマニュアルの GC に関する記述](https://www.php.net/manual/ja/features.gc.php) を出ていないものの、PHP 処理系の内部的な用語を使わないようにしたり、本質的でない処理を省いたりして、理解のための前提条件を減らせたのではないかと思う。
+
+ところで今回スライドのフォントサイズを大きくするために各スライドの見出し部分を消してみたのだが、結局ほとんどのスライドで見出しらしき文言が必要になったので、あまり効果はなかったかもしれない。
+
+{#outro}
+# おわりに
+
+今回もカンファレンスくらいでしか聴けないようなセッションがいくつも聴けてよかった。
+また、ちょうど連休だったのもあり名古屋も楽しむことができた。
+運営のみなさま、お疲れさまでした&ありがとうございました。
+次は PHPerKaigi 2025 で会いましょう。
diff --git a/vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.ndoc b/vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.ndoc
deleted file mode 100644
index e4b64881..00000000
--- a/vhosts/blog/content/posts/2025-02-24/phpcon-nagoya-2025-report.ndoc
+++ /dev/null
@@ -1,64 +0,0 @@
----
-[article]
-uuid = "13174dc7-c1a3-465f-9ba6-14f0bc6f5961"
-title = "PHP カンファレンス名古屋 2025 参加レポ"
-description = "2025-02-22 に開催された、PHP カンファレンス名古屋 2025 に参加した。"
-tags = [
- "conference",
- "php",
- "phpcon-nagoya",
-]
-
-[[article.revisions]]
-date = "2025-02-24"
-remark = "公開"
----
-
-
-
- セッション感想
-
- 特に印象に残ったセッションを二つピックアップした (タイトルと発表者名は fortee のプロポーザルページによる)。
-
-
-
-
-
- おわりに
-
- 今回もカンファレンスくらいでしか聴けないようなセッションがいくつも聴けてよかった。
- また、ちょうど連休だったのもあり名古屋も楽しむことができた。
- 運営のみなさま、お疲れさまでした&ありがとうございました。
- 次は PHPerKaigi 2025 で会いましょう。
-
-
-
diff --git a/vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.dj b/vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.dj
new file mode 100644
index 00000000..8c9417fa
--- /dev/null
+++ b/vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.dj
@@ -0,0 +1,93 @@
+---
+[article]
+uuid = "99111377-27e7-427b-9dc5-a23f621fa826"
+title = "zip 関数のようなコマンド paste"
+description = "zip 関数のような動きをする paste コマンドについてのメモ。"
+tags = [
+ "note-to-self",
+]
+
+[[article.revisions]]
+date = "2021-03-22"
+remark = "デジタルサーカス株式会社の社内記事として公開"
+isInternal = true
+
+[[article.revisions]]
+date = "2025-03-27"
+remark = "ブログ記事として一般公開"
+---
+::: note
+この記事は、2021-03-22 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+:::
+
+{#intro}
+# 実現したい内容
+
+次の2ファイル `a.txt` / `b.txt` から出力 `ab.txt` を得たい。
+
+`a.txt`
+
+```
+a1
+a2
+a3
+```
+
+`b.txt`
+
+```
+b1
+b2
+b3
+```
+
+`ab.txt`
+
+```
+a1
+b1
+a2
+b2
+a3
+b3
+```
+
+ちょうど Python や Haskell などにある `zip` 関数のような動きをさせたい。
+
+{#paste-command}
+# 実現方法
+
+記事タイトルに書いたように、`paste` コマンドを使うと実現できる。
+
+```
+$ paste -d '\
+' a.txt b.txt > ab.txt
+```
+
+`paste` コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら `-d` で指定した文字で区切って出力する。
+`-d` は区切り文字の指定で、デフォルトだとタブ区切りになる。
+
+ファイル名には `-` を指定でき、その場合は標準入力から読み込んで出力する。
+このとき `paste - -` のように複数回 `-` を指定すると、指定した回数の行ごとに連結することができる。
+例えば `ab.txt` だとこうなる。
+
+```
+$ paste - - < ab.txt
+a1 b1
+a2 b2
+a3 b3
+```
+
+これは標準入力を使うとき特有の挙動で、単に同じファイル名を指定してもこうはならない。
+
+```
+$ paste ab.txt ab.txt
+a1 a1
+b1 b1
+a2 a2
+b2 b2
+a3 a3
+b3 b3
+```
+
+ときどき便利。
diff --git a/vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.ndoc b/vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.ndoc
deleted file mode 100644
index 3419099b..00000000
--- a/vhosts/blog/content/posts/2025-03-27/zip-function-like-command-paste-command.ndoc
+++ /dev/null
@@ -1,111 +0,0 @@
----
-[article]
-uuid = "99111377-27e7-427b-9dc5-a23f621fa826"
-title = "zip 関数のようなコマンド paste"
-description = "zip 関数のような動きをする paste コマンドについてのメモ。"
-tags = [
- "note-to-self",
-]
-
-[[article.revisions]]
-date = "2021-03-22"
-remark = "デジタルサーカス株式会社の社内記事として公開"
-isInternal = true
-
-[[article.revisions]]
-date = "2025-03-27"
-remark = "ブログ記事として一般公開"
----
-
-
- この記事は、2021-03-22 にデジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
-
-
- 実現したい内容
-
- 次の2ファイル a.txt / b.txt から出力 ab.txt を得たい。
-
-
- a.txt
-
-
-
-
-
- b.txt
-
-
-
-
-
- ab.txt
-
-
-
-
-
- ちょうど Python や Haskell などにある zip 関数のような動きをさせたい。
-
-
-
- 実現方法
-
- 記事タイトルに書いたように、paste コマンドを使うと実現できる。
-
-
- ab.txt
- ]]>
-
-
- paste コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら -d で指定した文字で区切って出力する。
- -d は区切り文字の指定で、デフォルトだとタブ区切りになる。
-
-
- ファイル名には - を指定でき、その場合は標準入力から読み込んで出力する。
- このとき paste - - のように複数回 - を指定すると、指定した回数の行ごとに連結することができる。
- 例えば ab.txt だとこうなる。
-
-
-
-
-
- これは標準入力を使うとき特有の挙動で、単に同じファイル名を指定してもこうはならない。
-
-
-
-
-
- ときどき便利。
-
-
-
diff --git a/vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj b/vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj
new file mode 100644
index 00000000..687ddef6
--- /dev/null
+++ b/vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj
@@ -0,0 +1,102 @@
+---
+[article]
+uuid = "046e4412-bee8-4ffe-9876-6cbeaa0caf6b"
+title = "【HTTP】HTTP/1.1 で同じヘッダを2回送るとどうなるか"
+description = "HTTP/1.1 で同じヘッダを2回送ったときの挙動について仕様を読んでまとめた。"
+tags = [
+ "http",
+]
+
+[[article.revisions]]
+date = "2022-08-18"
+remark = "デジタルサーカス株式会社の社内記事として公開"
+isInternal = true
+
+[[article.revisions]]
+date = "2025-03-28"
+remark = "ブログ記事として一般公開"
+---
+::: note
+この記事は、2022-08-18 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+:::
+
+{#intro}
+# はじめに
+
+HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように解釈されるのか。仕様を確認した。
+
+今回読んだ仕様は RFC 7230 で、こちらのリンクから閲覧できる: https://datatracker.ietf.org/doc/html/rfc7230
+
+その中でも、https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.2 を主に引用する。
+
+ところで、HTTP 周りの仕様を探すときはここから飛ぶと便利:
+
+{#specification}
+# 仕様
+
+{#sender}
+### 送信側
+
+> A sender MUST NOT generate multiple header fields with the same field
+> name in a message unless either the entire field value for that
+> header field is defined as a comma-separated list [i.e., #(values)]
+> or the header field is a well-known exception (as noted below).
+
+【日本語訳 (私が訳したもので、公式なものではない)】
+送信者は、同じ field name の header field を複数生成してはならない (MUST NOT)。
+ただし、header field の値がコンマ区切りのリストとして定義されているか、header field がよく知られた例外 (後述) である場合はその限りでない。
+
+{#recipient}
+### 受信側
+
+> A recipient MAY combine multiple header fields with the same field
+> name into one "field-name: field-value" pair, without changing the
+> semantics of the message, by appending each subsequent field value to
+> the combined field value in order, separated by a comma. The order
+> in which header fields with the same field name are received is
+> therefore significant to the interpretation of the combined field
+> value; a proxy MUST NOT change the order of these field values when
+> forwarding a message.
+
+【日本語訳 (私が訳したもので、公式なものではない)】
+受信者は、同じ field name を持つ複数の header field を、メッセージの意味を変えないようにしつつ同じ順序で追加して、単一のコンマで区切られた `"field-name: field-value"` のペアに結合してよい (MAY)。
+したがって、同じ field name を持つ header field がどのような順序で受信されたかは、結合された値の解釈に影響する。
+よって、プロキシは、メッセージを転送する際、header field の順序を変えてはならない (MUST NOT)。
+
+{#exception}
+### 例外ケース: Set-Cookie
+
+> Note: In practice, the "Set-Cookie" header field ([[RFC6265](https://datatracker.ietf.org/doc/html/rfc6265)]) often
+> appears multiple times in a response message and does not use the
+> list syntax, violating the above requirements on multiple header
+> fields with the same name. Since it cannot be combined into a
+> single field-value, recipients ought to handle "Set-Cookie" as a
+> special case while processing header fields. (See Appendix A.2.3
+> of [Kri2001] for details.)
+
+【日本語訳 (私が訳したもので、公式なものではない)】
+注意: 実際には、`Set-Cookie` header field ([RFC6265](https://datatracker.ietf.org/doc/html/rfc6265)) は、しばしばレスポンスメッセージ中に複数回現れる。
+これはリストの構文を使っておらず、上述した同じ field name を持つ header field についての要件に違反している。
+この値は単一の値へ結合できないため、受信者は、header field を処理する際、`Set-Cookie` を特別扱いした方がよい。
+
+おそらく、「送信側」のところで書かれている「よく知られた例外」の一つがこれだと思われる。
+
+{#comma-separated-list}
+### どの header field がコンマ区切りのリストなのか
+
+上記のように、同じ field name を持つ header field を複数回送れるかどうかは、その header field がコンマ区切りのリストとして定義されているかどうかで決まる。では、特定の header field がその条件を満たしているかどうか知りたいときは、何を見ればよいのか。
+
+HTTP の仕様として定義されているような header field であれば、下記のリンクからそれぞれの定義を参照できる。
+
+* https://datatracker.ietf.org/doc/html/rfc7231#section-5
+* https://datatracker.ietf.org/doc/html/rfc7231#section-7
+
+そうでない場合 (たとえば `X-` から始まるもの等) は、MDN や各ベンダのドキュメントを探すことになるだろう。
+
+{#outro}
+# まとめ
+
+* 送信側: 基本的には複数回送れない。コンマ区切りのヘッダは例外
+* 受信側: 基本的には未規定。コンマ区切りのヘッダは複数回来たらその順に結合する
+* プロキシ: 順序を変えてはならない
+* `Set-Cookie` は例外ケース
diff --git a/vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.ndoc b/vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.ndoc
deleted file mode 100644
index 8fe1a9b0..00000000
--- a/vhosts/blog/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.ndoc
+++ /dev/null
@@ -1,120 +0,0 @@
----
-[article]
-uuid = "046e4412-bee8-4ffe-9876-6cbeaa0caf6b"
-title = "【HTTP】HTTP/1.1 で同じヘッダを2回送るとどうなるか"
-description = "HTTP/1.1 で同じヘッダを2回送ったときの挙動について仕様を読んでまとめた。"
-tags = [
- "http",
-]
-
-[[article.revisions]]
-date = "2022-08-18"
-remark = "デジタルサーカス株式会社の社内記事として公開"
-isInternal = true
-
-[[article.revisions]]
-date = "2025-03-28"
-remark = "ブログ記事として一般公開"
----
-
-
- この記事は、2022-08-18 にデジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
-
-
- はじめに
-
- HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように解釈されるのか。仕様を確認した。
-
-
- 今回読んだ仕様は RFC 7230 で、こちらのリンクから閲覧できる: https://datatracker.ietf.org/doc/html/rfc7230
-
-
- その中でも、https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.2 を主に引用する。
-
-
- ところで、HTTP 周りの仕様を探すときはここから飛ぶと便利: https://developer.mozilla.org/en-US/docs/Web/HTTP/Resources_and_specifications
-
-
-
- 仕様
-
- 送信側
-
- A sender MUST NOT generate multiple header fields with the same field
- name in a message unless either the entire field value for that
- header field is defined as a comma-separated list [i.e., #(values)]
- or the header field is a well-known exception (as noted below).
-
-
- 【日本語訳 (私が訳したもので、公式なものではない)】
- 送信者は、同じ field name の header field を複数生成してはならない (MUST NOT)。
- ただし、header field の値がコンマ区切りのリストとして定義されているか、header field がよく知られた例外 (後述) である場合はその限りでない。
-
-
-
- 受信側
-
- A recipient MAY combine multiple header fields with the same field
- name into one "field-name: field-value" pair, without changing the
- semantics of the message, by appending each subsequent field value to
- the combined field value in order, separated by a comma. The order
- in which header fields with the same field name are received is
- therefore significant to the interpretation of the combined field
- value; a proxy MUST NOT change the order of these field values when
- forwarding a message.
-
-
- 【日本語訳 (私が訳したもので、公式なものではない)】
- 受信者は、同じ field name を持つ複数の header field を、メッセージの意味を変えないようにしつつ同じ順序で追加して、単一のコンマで区切られた "field-name: field-value" のペアに結合してよい (MAY)。
- したがって、同じ field name を持つ header field がどのような順序で受信されたかは、結合された値の解釈に影響する。
- よって、プロキシは、メッセージを転送する際、header field の順序を変えてはならない (MUST NOT)。
-
-
-
- 例外ケース: Set-Cookie
-
- Note: In practice, the "Set-Cookie" header field ([RFC6265]) often
- appears multiple times in a response message and does not use the
- list syntax, violating the above requirements on multiple header
- fields with the same name. Since it cannot be combined into a
- single field-value, recipients ought to handle "Set-Cookie" as a
- special case while processing header fields. (See Appendix A.2.3
- of [Kri2001] for details.)
-
-
- 【日本語訳 (私が訳したもので、公式なものではない)】
- 注意: 実際には、Set-Cookie header field (RFC6265) は、しばしばレスポンスメッセージ中に複数回現れる。
- これはリストの構文を使っておらず、上述した同じ field name を持つ header field についての要件に違反している。
- この値は単一の値へ結合できないため、受信者は、header field を処理する際、Set-Cookie を特別扱いした方がよい。
-
-
- おそらく、「送信側」のところで書かれている「よく知られた例外」の一つがこれだと思われる。
-
-
-
- どの header field がコンマ区切りのリストなのか
-
- 上記のように、同じ field name を持つ header field を複数回送れるかどうかは、その header field がコンマ区切りのリストとして定義されているかどうかで決まる。では、特定の header field がその条件を満たしているかどうか知りたいときは、何を見ればよいのか。
-
-
- HTTP の仕様として定義されているような header field であれば、下記のリンクからそれぞれの定義を参照できる。
-
-
- - https://datatracker.ietf.org/doc/html/rfc7231#section-5
- - https://datatracker.ietf.org/doc/html/rfc7231#section-7
-
-
- そうでない場合 (たとえば X- から始まるもの等) は、MDN や各ベンダのドキュメントを探すことになるだろう。
-
-
-
-
- まとめ
-
- - 送信側: 基本的には複数回送れない。コンマ区切りのヘッダは例外
- - 受信側: 基本的には未規定。コンマ区切りのヘッダは複数回来たらその順に結合する
- - プロキシ: 順序を変えてはならない
- Set-Cookie は例外ケース
-
-
-
diff --git a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
index 07071441..d1559f2f 100644
--- a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
+++ b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
@@ -529,13 +529,20 @@ function processEmail(node: DjotEmail): Element {
};
}
-function processFootnoteReference(node: DjotFootnoteReference): Element {
+function processFootnoteReference(node: DjotFootnoteReference): Node {
+ void node;
+ // TODO
return {
- kind: "element",
- name: "footnoteref",
- attributes: new Map([["reference", node.text]]),
- children: [],
+ kind: "text",
+ content: "",
+ raw: false,
};
+ // return {
+ // kind: "element",
+ // name: "footnoteref",
+ // attributes: new Map([["reference", node.text]]),
+ // children: [],
+ // };
}
function processUrl(node: DjotUrl): Element {
@@ -792,24 +799,25 @@ export function djot2ndoc(doc: DjotDoc): Element {
// Process footnotes if any exist
if (doc.footnotes && Object.keys(doc.footnotes).length > 0) {
- const footnoteSection: Element = {
- kind: "element",
- name: "section",
- attributes: new Map([["class", "footnotes"]]),
- children: [],
- };
-
- for (const [id, footnote] of Object.entries(doc.footnotes)) {
- const footnoteElement: Element = {
- kind: "element",
- name: "footnote",
- attributes: new Map([["id", id]]),
- children: footnote.children.map(processBlock),
- };
- footnoteSection.children.push(footnoteElement);
- }
-
- children.push(footnoteSection);
+ // TODO
+ // const footnoteSection: Element = {
+ // kind: "element",
+ // name: "section",
+ // attributes: new Map([["class", "footnotes"]]),
+ // children: [],
+ // };
+ //
+ // for (const [id, footnote] of Object.entries(doc.footnotes)) {
+ // const footnoteElement: Element = {
+ // kind: "element",
+ // name: "footnote",
+ // attributes: new Map([["id", id]]),
+ // children: footnote.children.map(processBlock),
+ // };
+ // footnoteSection.children.push(footnoteElement);
+ // }
+ //
+ // children.push(footnoteSection);
}
return {
--
cgit v1.2.3-70-g09d2
From 4f46d262e6967c9c638b40f3b0246d21b7a9b9dc Mon Sep 17 00:00:00 2001
From: nsfisis
Date: Wed, 9 Apr 2025 20:29:15 +0900
Subject: feat(blog/nuldoc): rebuild
---
.../posts/2021-03-05/my-first-post/index.html | 4 +-
.../posts/2021-03-30/phperkaigi-2021/index.html | 122 +++++-------
.../index.html | 83 ++++----
.../python-unbound-local-error/index.html | 26 +--
.../ruby-detect-running-implementation/index.html | 143 +++-----------
.../ruby-then-keyword-and-case-in/index.html | 85 +++-----
.../rust-where-are-primitive-types-from/index.html | 85 +++-----
.../index.html | 105 +++++-----
.../vim-swap-order-of-selected-lines/index.html | 127 +++++-------
.../2022-04-09/phperkaigi-2022-tokens/index.html | 216 +++++++--------------
.../index.html | 74 +++----
.../posts/2022-05-01/phperkaigi-2022/index.html | 165 +++++++---------
.../php-conference-okinawa-code-golf/index.html | 64 ++----
.../index.html | 39 ++--
.../index.html | 203 +++++++------------
.../phperkaigi-2023-unused-token-quiz-1/index.html | 75 +++----
.../setup-server-for-this-site/index.html | 132 +++++--------
.../phperkaigi-2023-unused-token-quiz-2/index.html | 78 +++-----
.../phperkaigi-2023-unused-token-quiz-3/index.html | 106 +++-------
.../rewrite-this-blog-generator/index.html | 128 +++++-------
.../index.html | 190 +++++++-----------
.../2023-04-04/phperkaigi-2023-report/index.html | 121 ++++--------
.../2023-06-25/phpconfuk-2023-report/index.html | 66 ++++---
.../compile-php-runtime-to-wasm/index.html | 135 ++++---------
.../index.html | 10 +-
.../public/posts/2023-12-03/isucon-13/index.html | 59 +++---
.../posts/2023-12-31/2023-reflections/index.html | 66 +++----
.../index.html | 53 ++---
.../index.html | 66 ++-----
.../2024-02-10/yapcjapan-2024-report/index.html | 50 +++--
.../2024-02-22/phpkansai-2024-report/index.html | 43 ++--
.../2024-03-17/phperkaigi-2024-report/index.html | 67 +++----
.../posts/2024-03-20/my-bucket-list/index.html | 36 +---
.../phpcon-odawara-2024-report/index.html | 68 +++----
.../pipefail-option-in-gitlab-ci-cd/index.html | 81 +++-----
.../index.html | 47 ++---
.../2024-05-11/phpconkagawa-2024-report/index.html | 78 ++++----
.../2024-06-19/scalamatsuri-2024-report/index.html | 44 ++---
.../reparojson-fix-only-json-formatter/index.html | 130 ++++++-------
.../index.html | 60 ++----
.../posts/2024-09-28/mncore-challenge-1/index.html | 28 ++-
.../posts/2024-12-04/cohackpp-report/index.html | 64 +++---
.../posts/2024-12-33/2024-reflections/index.html | 67 ++-----
.../phperkaigi-2023-tokens-q1/index.html | 188 +++++++-----------
.../index.html | 41 ++--
.../phpcon-nagoya-2025-report/index.html | 37 ++--
.../index.html | 38 ++--
.../http-1-1-send-multiple-same-headers/index.html | 72 +++----
48 files changed, 1481 insertions(+), 2584 deletions(-)
(limited to 'vhosts/blog')
diff --git a/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html b/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html
index 9170e5f2..74c846cc 100644
--- a/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html
+++ b/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html
@@ -52,9 +52,9 @@
-
+
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
diff --git a/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html b/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html
index 9f0c381d..18d88c22 100644
--- a/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html
+++ b/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html
@@ -67,171 +67,141 @@
-
+
- 2021-03-26 から 2021-03-28 にかけて開催された、 PHPerKaigi 2021 に一般参加者として参加した。弊社 デジタルサーカス株式会社 (今年1月から勤務) はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。
+ 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に一般参加者として参加した。 弊社デジタルサーカス株式会社 (今年1月から勤務) はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。
-
- このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。
+ このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。
-
- 発表はトラック A、B に分かれていたのだが、今回はすべて A トラックを視聴している (切り替えるのが面倒だっただけ)。
+ 発表はトラック A、B に分かれていたのだが、今回はすべて A トラックを視聴している (切り替えるのが面倒だっただけ)。
-
-
+
-
+
- AWS Lambda のような Function as a Service はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に web サービスを作る具体的なイメージがまだ見えない (注: すべて for me として書いている)。
+ AWS Lambda のような Function as a Service はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に web サービスを作る具体的なイメージがまだ見えない (注: すべて for me として書いている)。
-
- PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。
+ PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。
-
- 勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + Laravel などでは動かなさそう) だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。
+ 勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + Laravel などでは動かなさそう) だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。
-
-
+
- SEO (Search Engine Optimization) は大して知らないので新鮮な話が多かった。その分語れることも少ない……。
+ SEO (Search Engine Optimization) は大して知らないので新鮮な話が多かった。その分語れることも少ない……。
-
-
+
- つい最近 WAI-ARIA についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。
+ つい最近 WAI-ARIA についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。
-
-
+
- PHP で FUSE
+ PHP で FUSE
-
- 個人的に楽しみだった発表。
+ 個人的に楽しみだった発表。
-
- 期待通りの興味深い発表だった。FUSE 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。
+ 期待通りの興味深い発表だった。FUSE 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。
-
-
+
-
+
- User Acceptance Test (UAT) くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。
+ User Acceptance Test (UAT) くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。
-
-
+
- 型のある世界で生きてきた身として大いに楽しみにしていた発表。
+ 型のある世界で生きてきた身として大いに楽しみにしていた発表。
-
- 昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で Ruby の typeprof には注目している。
+ 昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で Ruby の typeprof には注目している。
-
-
+
- この発表も以前から非常に楽しみにしていた。
+ この発表も以前から非常に楽しみにしていた。
-
- ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な (乖離しない) 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。
+ ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な (乖離しない) 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。
-
-
+
- 全体的に基本的な話だったので特に触れない。Cookie やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。
+ 全体的に基本的な話だったので特に触れない。Cookie やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。
-
-
+
- PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で PHP を書き始めてから 4ヶ月ほどになる)。
+ PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で PHP を書き始めてから 4ヶ月ほどになる)。
-
- 個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell などのエラーを「値として」扱う言語だと思っている。try-catch は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる (C のそれはまともな型付けではない。念のため)。
+ 個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell などのエラーを「値として」扱う言語だと思っている。try-catch は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる (C のそれはまともな型付けではない。念のため)。
-
- PHP のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。
+ PHP のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。
-
-
+
- Laravel の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。
+ Laravel の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。
-
-
+
- (発表の中でもまさに同じことをおっしゃっていたが) PHP 以外の方が向いているだろう、というのが第一の感想である。gRPC はそれ自体というよりも Protobuf というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。
+ (発表の中でもまさに同じことをおっしゃっていたが) PHP 以外の方が向いているだろう、というのが第一の感想である。gRPC はそれ自体というよりも Protobuf というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。
-
-
+
- 冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。
+ 冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。
-
- 残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。
+ 残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。
-
diff --git a/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
index d99fc02c..fee0e109 100644
--- a/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
@@ -65,14 +65,14 @@
NOTE
-
- タイトル落ち。まずはこのコードを見て欲しい。
+ タイトル落ち。まずはこのコードを見て欲しい。
-
#include <iostream>
@@ -94,109 +94,96 @@
std::cout << "Hello, World!" << std::endl;
}
-
-
-
- コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
-
-
-
- コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp
-
-
-
- この記事から得られるものはこれ以上ないので以下は蛇足になる。
+ コンパイラのバージョン
+
+
+
$ clang++ –version Apple clang version 11.0.0
+(clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model:
+posix InstalledDir:
+/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
+
+
+ コンパイルコマンド (C17指定)
+
+
+
$ clang –std=c++17 hoge.cpp
+
+
+ この記事から得られるものはこれ以上ないので以下は蛇足になる。
-
- 別件で cppreference.com の identifier のページ を読んでいた時、次の文が目に止まった。
+ 別件で cppreference.com の identifier のページを読んでいた時、次の文が目に止まった。
-
-
- キーワードでも属性として指定する場合は非キーワードとして使えるらしい。実際にやってみる。
+ キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 実際にやってみる。
-
- 同サイトの keywords のページ から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute `〇〇' ignored) がコンパイラから出力されるが、コンパイルできる。
+ 同サイトの keywords のページ から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute `〇〇’ ignored) がコンパイラから出力されるが、コンパイルできる。
-
- 上のコードでは [[using]] をコメントアウトしているが、これは using キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。
+ 上のコードでは [[using]] をコメントアウトしているが、これは using キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。
-
// using の例
[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文
-
- C++17 の仕様も見てみる (正確には標準化前のドラフト)。
+ C++17 の仕様も見てみる (正確には標準化前のドラフト)。
-
- 引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4
+ 引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4
-
- If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.
+ If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.
-
- 「identifier の構文上の要件を満たすキーワードまたは代替トークンが attribute-token に含まれている場合、identifier とみなされる」とある。どうやら間違いないようだ。
+ 「identifier の構文上の要件を満たすキーワードまたは代替トークンが attribute-token に含まれている場合、identifier とみなされる」とある。どうやら間違いないようだ。
-
- ところで、代替トークン (alternative token) とは and (&) や bitor (|) などのことだが、identifier の構文上の要件を満たさないような代替トークンなどあるのか? 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい (参考: 同ドラフト)
+ ところで、代替トークン (alternative token) とは and (&) や bitor (|) などのことだが、identifier の構文上の要件を満たさないような代替トークンなどあるのか? 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい (参考: 同ドラフト )
-
-
<% → {
-
-
%> → }
-
-
<: → [
-
-
:> → ]
-
-
%: → #
-
-
%:%: → ##
-
- 「identifier の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。
+ 「identifier の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。
-
- 調べた感想: 字句解析器か構文解析器が辛そう
+ 調べた感想: 字句解析器か構文解析器が辛そう
diff --git a/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html b/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html
index f3a11356..d3db5da6 100644
--- a/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html
@@ -65,18 +65,17 @@
NOTE
-
- 本記事は Python 3.7.6 の動作結果を元にして書かれている。
+ 本記事は Python 3.7.6 の動作結果を元にして書かれている。
-
- Python でクロージャを作ろうと、次のようなコードを書いた。
+ Python でクロージャを作ろうと、次のようなコードを書いた。
-
def f():
x = 0
@@ -86,21 +85,17 @@
f()
-
- 関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 これを実行すると x += 1 の箇所でエラーが発生する。
+ 関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 これを実行すると x += 1 の箇所でエラーが発生する。
-
- UnboundLocalError: local variable `x' referenced before assignment
+ UnboundLocalError: local variable `x’ referenced before assignment
-
- local変数 x が代入前に参照された、とある。これは、f の x を参照するのではなく、新しく別の変数を g 内に作ってしまっているため。前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。var を変数宣言のための構文として擬似的に利用している。
+ local変数 x が代入前に参照された、とある。これは、f の x を参照するのではなく、新しく別の変数を g 内に作ってしまっているため。 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。var を変数宣言のための構文として擬似的に利用している。
-
# 注: var は正しい Python の文法ではない。上記参照のこと
def f():
@@ -113,11 +108,9 @@
# 加算する前の値を参照しようとするが、まだ代入されていないためエラー
g()
-
- 当初の意図を表現するには、次のように書けばよい。
+ 当初の意図を表現するには、次のように書けばよい。
-
def f():
x = 0
@@ -126,7 +119,6 @@
x += 1
g()
-
(*) のように、nonlocal を追加する。これにより一つ外側のスコープ (g の一つ外側 = f) で定義されている x を探しに行くようになる。
diff --git a/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html
index cf2eb729..d022bf8d 100644
--- a/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html
@@ -62,26 +62,23 @@
NOTE
-
- Ruby という言語には複数の実装があるが、それらをスクリプト上からどのようにして programmatically に見分ければよいだろうか。
+ Ruby という言語には複数の実装があるが、それらをスクリプト上からどのようにして programmatically に見分ければよいだろうか。
-
Object クラスに定義されている RUBY_ENGINE という定数がこの用途に使える。
-
- 参考: Object::RUBY_ENGINE
+ 参考: Object::RUBY_ENGINE
-
- 上記ページの例から引用する:
+ 上記ページの例から引用する:
-
$ ruby-1.9.1 -ve 'p RUBY_ENGINE'
ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux]
@@ -90,129 +87,39 @@
jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]
"jruby"
-
- それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。
+ それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。
-
- What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:
+ What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:
-
-
-
-
- |
- RUBY_ENGINE
- |
-
-
- Implementation
- |
-
-
-
-
-
- |
- <undefined>
- |
-
-
- MRI < 1.9
- |
-
-
-
- |
- `ruby'
- |
-
-
- MRI >= 1.9 or REE
- |
-
-
-
- |
- `jruby'
- |
-
-
- JRuby
- |
-
-
-
- |
- `macruby'
- |
-
-
- MacRuby
- |
-
-
-
- |
- `rbx'
- |
-
-
- Rubinius
- |
-
-
-
- |
- `maglev'
- |
-
-
- MagLev
- |
-
-
-
- |
- `ironruby'
- |
-
-
- IronRuby
- |
-
-
-
- |
- `cardinal'
- |
-
-
- Cardinal
- |
-
-
-
+
+
| RUBY_ENGINE | Implementation |
+|:-----------:|:------------------|
+| <undefined> | MRI < 1.9 |
+| 'ruby' | MRI >= 1.9 or REE |
+| 'jruby' | JRuby |
+| 'macruby' | MacRuby |
+| 'rbx' | Rubinius |
+| 'maglev' | MagLev |
+| 'ironruby' | IronRuby |
+| 'cardinal' | Cardinal |
+
-
- なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。
+ なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。
-
- この表にない主要な処理系として、mruby は 'mruby' を返す。
+ この表にない主要な処理系として、 mruby は 'mruby' を返す。
-
- mruby 該当部分のソース より引用:
+ mruby 該当部分のソース より引用:
-
/*
-* Ruby engine.
-*/
+ * Ruby engine.
+ */
#define MRUBY_RUBY_ENGINE "mruby"
diff --git a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html
index 76f7058c..cf80ece0 100644
--- a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html
@@ -65,23 +65,22 @@
NOTE
-
-
+
case - in によるパターンマッチング構文でも、case - when と同じように then が使える (場合によっては使う必要がある)。
-
-
+
- 使われることは稀だが、Ruby では then がキーワードになっている。次のように使う:
+ 使われることは稀だが、Ruby では then がキーワードになっている。次のように使う:
-
if cond then
puts "Y"
@@ -89,11 +88,9 @@
puts "N"
end
-
- このキーワードが現れうる場所はいくつかあり、if、unless、rescue、case 構文がそれに当たる。 上記のように、何か条件を書いた後 then を置き、式がそこで終了していることを示すマーカーとして機能する。
+ このキーワードが現れうる場所はいくつかあり、if、unless、rescue、case 構文がそれに当たる。 上記のように、何か条件を書いた後 then を置き、式がそこで終了していることを示すマーカーとして機能する。
-
# Example:
@@ -117,21 +114,17 @@
end
-
-
+
- 普通 Ruby のコードで then を書くことはない。なぜか。次のコードを実行してみるとわかる。
+ 普通 Ruby のコードで then を書くことはない。なぜか。次のコードを実行してみるとわかる。
-
if true puts 'Hello, World!' end
-
- 次のような構文エラーが出力される。
+ 次のような構文エラーが出力される。
-
20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n'
if true puts 'Hello, World!' end
@@ -139,72 +132,58 @@
20:1: syntax error, unexpected `end', expecting end-of-input
...f true puts 'Hello, World!' end
-
- 二つ目のメッセージは無視して一つ目を読むと、then か ; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。
+ 二つ目のメッセージは無視して一つ目を読むと、then か ; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。
-
- ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。
+ ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。
-
if true
puts 'Hello, World!' end
-
- 無事 Hello, World! と出力されるようになった。
+ 無事 Hello, World! と出力されるようになった。
-
-
+
- なぜ then や ; や改行 (以下 「then 等」) が必要なのだろうか。次の例を見てほしい:
+ なぜ then や ; や改行 (以下 「then 等」) が必要なのだろうか。次の例を見てほしい:
-
-
- then も ; も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。この例は二通りに解釈できる。
+ then も ; も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 この例は二通りに解釈できる。
-
# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
if a then
b
end
-
# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
# その結果が truthy なら何もしない
if a(b) then
end
-
then 等はこの曖昧性を排除するためにあり、条件式は if から then 等までの間にある、ということを明確にする。 C系の if 後に来る (/) や、Python の :、Rust/Go/Swift などの { も同じ役割を持つ。
-
- Ruby の場合、プログラマーが書きやすいよう改行でもって then が代用できるので、ほとんどの場合 then は必要ない。
+ Ruby の場合、プログラマーが書きやすいよう改行でもって then が代用できるので、ほとんどの場合 then は必要ない。
-
-
+
- ようやく本題にたどり着いた。来る Ruby 3.0 では case と in キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして then 等が必要になる。 (現在の) Ruby には formal な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc の説明は省略)。
+ ようやく本題にたどり着いた。来る Ruby 3.0 では case と in キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして then 等が必要になる。 (現在の) Ruby には formal な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc の説明は省略)。
-
- https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986
+ https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986
-
p_case_body : keyword_in
{
@@ -233,24 +212,19 @@
}
;
-
- 簡略版:
+ 簡略版:
-
p_case_body : keyword_in p_top_expr then compstmt p_cases
;
-
- ここで、keyword_in は文字通り in、p_top_expr はいわゆるパターン、then は then キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり then キーワード、;、改行のいずれかである。
+ ここで、keyword_in は文字通り in、p_top_expr はいわゆるパターン、then は then キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり then キーワード、;、改行のいずれかである。
-
- これにより、case - when による従来の構文と同じように、then 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:
+ これにより、case - when による従来の構文と同じように、then 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:
-
case x
in 1 then a
@@ -273,11 +247,9 @@
in 3; c
end
-
- ところで、p_top_expr には if による guard clause が書けるので、その場合は if - then と似たような見た目になる。
+ ところで、p_top_expr には if による guard clause が書けるので、その場合は if - then と似たような見た目になる。
-
case x
in 0 then a
@@ -286,23 +258,22 @@
end
-
-
+
-
-
if や case の条件の後ろには then、;、改行のいずれかが必要
+
+ if や case の条件の後ろには then、;、改行のいずれかが必要
+
-
-
3.0 で入る予定の
case - in でも then 等が必要になる
-
-
Ruby の構文を正確に知るには (現状)
parse.y を直接読めばよい
diff --git a/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html
index 4ae7f235..052f674a 100644
--- a/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html
@@ -62,16 +62,16 @@
NOTE
-
-
+
- Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。
+ Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。
-
#![allow(non_camel_case_types)]
#![allow(dead_code)]
@@ -94,40 +94,30 @@
struct f64;
struct str;
-
- では、普段単に bool と書いたとき、この bool は一体どこから来ているのか。rustc のソースを追ってみた。
+ では、普段単に bool と書いたとき、この bool は一体どこから来ているのか。rustc のソースを追ってみた。
+
+
+ 前提知識: 一般的なコンパイラの構造、用語。rustc そのものの知識は不要 (というよりも筆者自身がよく知らない)
-
-
-
- 前提知識: 一般的なコンパイラの構造、用語。rustc そのものの知識は不要 (というよりも筆者自身がよく知らない)
-
-
-
-
+
- 調査に使用したソース (調査時点での最新 master)
+ 調査に使用したソース (調査時点での最新 master)
-
- https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98
+ https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98
-
- どのようにして調べるか。rustc の構造には詳しくないため、すぐに当たりをつけるのは難しい。
+ どのようにして調べるか。rustc の構造には詳しくないため、すぐに当たりをつけるのは難しい。
-
- 大雑把な構造としては、compiler フォルダ以下に rustc_* という名前のクレートが数十個入っている。これがどうやら rustc コマンドの実装部のようだ。
+ 大雑把な構造としては、compiler フォルダ以下に rustc_* という名前のクレートが数十個入っている。これがどうやら rustc コマンドの実装部のようだ。
-
- rustc はセルフホストされている (= rustc 自身が Rust で書かれている) ので、bool や char などで適当に検索をかけてもノイズが多すぎて話にならない。しかし、お誂え向きなことに i128/u128 というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って git grep してみる。
+ rustc はセルフホストされている (= rustc 自身が Rust で書かれている) ので、bool や char などで適当に検索をかけてもノイズが多すぎて話にならない。 しかし、お誂え向きなことに i128/u128 というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って git grep してみる。
-
$ git grep "\bi128\b" | wc # i128
165 1069 15790
@@ -138,22 +128,18 @@
$ git grep "\bbool\b" | wc # cf. bool の結果
3563 23577 294659
-
- 165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
+ 165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
-
$ git grep "\bi128\b"
...
rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));
...
-
rustc_resolve というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。
-
/// Interns the names of the primitive types.
///
@@ -188,21 +174,17 @@
}
}
-
- これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、
+ これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、
-
-
-
- All other types are defined somewhere and possibly imported, but the primitive ones need special handling, since they have no place of origin.
-
-
-
+
+
All other types are defined somewhere and possibly imported, but the
+primitive ones need special handling, since they have no place of
+origin.
+
- とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
+ とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
-
/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
/// (略)
@@ -226,23 +208,18 @@
None
}
-
- 関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。if ns == TypeNS のブロック内では、primitive_type_table (上記の PrimitiveTypeTable::new() で作られた変数) に含まれている識別子 (bool、i32 など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。
+ 関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。 if ns == TypeNS のブロック内では、primitive_type_table (上記の PrimitiveTypeTable::new() で作られた変数) に含まれている識別子 (bool、i32 など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。
-
- なお、ns は「名前空間」を示す変数である。Rust における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この if は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。
+ なお、ns は「名前空間」を示す変数である。Rust における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この if は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。
-
- 重要なのは、これが resolve_ident_in_lexical_scope() の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。
+ 重要なのは、これが resolve_ident_in_lexical_scope() の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。
-
- 動作がわかったところで、例として次のコードを考える。
+ 動作がわかったところで、例として次のコードを考える。
-
#![allow(non_camel_case_types)]
@@ -252,16 +229,14 @@
let _: bool = bool;
}
-
- ここで main() の bool は struct bool として解決される。なぜなら、プリミティブ型の判定をする前に bool という名前の別の型が見つかるからだ。
+ ここで main() の bool は struct bool として解決される。なぜなら、プリミティブ型の判定をする前に bool という名前の別の型が見つかるからだ。
-
-
+
- Rust のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。
+ Rust のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。
diff --git a/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html
index ddf70cae..fa0553d5 100644
--- a/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html
@@ -62,103 +62,92 @@
NOTE
-
-
+
- 違いはない。ただのエイリアス。
+ 違いはない。ただのエイリアス。
-
-
+
- Vim の autocmd events には似通った名前のものがいくつかある。大抵は :help に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。
+ Vim の autocmd events には似通った名前のものがいくつかある。大抵は :help に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。
-
-
BufRead/BufReadPost
-
-
BufWrite/BufWritePre
-
-
BufAdd/BufCreate
-
- このうち、BufAdd/BufCreate に関しては、:help BufCreate に
+ このうち、BufAdd/BufCreate に関しては、:help BufCreate に
-
- The BufCreate event is for historic reasons.
+ The BufCreate event is for historic reasons.
-
- とあり、おそらくは BufAdd のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため vim と neovim のソースコードを調査した。
+ とあり、おそらくは BufAdd のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため vim と neovim のソースコードを調査した。
-
-
-
- ソースコードへのリンク vim (調査時点での master branch) neovim (上に同じ)
-
-
-
+
+ ソースコードへのリンク
+
+
-
+
- 以下は、autocmd events の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。
+ 以下は、autocmd events の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。
-
- https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86
+ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86
-
{"BufAdd", EVENT_BUFADD},
{"BufCreate", EVENT_BUFADD},
-
- https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97
+ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97
-
{"BufRead", EVENT_BUFREADPOST},
{"BufReadCmd", EVENT_BUFREADCMD},
{"BufReadPost", EVENT_BUFREADPOST},
-
- https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
+ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
-
{"BufWrite", EVENT_BUFWRITEPRE},
{"BufWritePost", EVENT_BUFWRITEPOST},
{"BufWritePre", EVENT_BUFWRITEPRE},
-
-
-
+
- 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。
+ 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。
-
-
- ところでこの調査で知ったのだが、BufRead と BufWrite は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら Pre/Post 付きのものを使った方が分かりやすいだろう。
+ ところでこの調査で知ったのだが、BufRead と BufWrite は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら Pre/Post 付きのものを使った方が分かりやすいだろう。
diff --git a/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
index afee803f..ec7e5591 100644
--- a/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
+++ b/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
@@ -62,12 +62,13 @@
NOTE
-
-
+
" License: Public Domain
@@ -76,110 +77,96 @@
\ keeppatterns <line1>,<line2>g/^/m<line1>-1
-
-
+
- :version の一部
+ :version の一部
-
- VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS version Included patches: 1-148 Huge version without GUI.
+ VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS version Included patches: 1-148 Huge version without GUI.
-
-
+
-
+
tac や tail -r などの外部コマンドを ! を使って呼び出し、置き換える。
-
- :h v_!
+ :h v_!
-
- tac コマンドや tail の -r オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい
+ tac コマンドや tail の -r オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい
-
-
+
- こちらは外部コマンドに頼らず、Vim の機能のみを使う。g は :global コマンドの、m は :move コマンドの略
+ こちらは外部コマンドに頼らず、Vim の機能のみを使う。g は :global コマンドの、m は :move コマンドの略
-
:global コマンドは :[range]global/{pattern}/[command] のように使い、[range] で指定された範囲の行のうち、{pattern} で指定された検索パターンにマッチする行に対して、順番に [command] で指定された Ex コマンドを呼び出す。
-
- :h :global
+ :h :global
-
:move コマンドは [range]:move {address} のように使い、[range] で指定された範囲の行を {address} で指定された位置に移動させる。
-
- :h :move
+ :h :move
-
:g/^/m0 のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。
-
- なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。
+ なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。
-
command! -bar -range=%
\ Reverse
\ <line1>,<line2>g/^/m<line1>-1
-
- これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。
+ これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。
-
-
+
:global コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。^ は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。'hlsearch' オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと n コマンドなどの際に不便である。
-
- :h @/
+ :h @/
-
-
-
-
- [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した
-
-
-
+
+
+
+ NOTE
+
+
+
+ [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した
+
+
+
- 前述した :Reverse コマンドの定義を少し変えて、次のようにする:
+ 前述した :Reverse コマンドの定義を少し変えて、次のようにする:
-
function! s:reverse_lines(from, to) abort
execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1)
@@ -189,69 +176,61 @@
\ Reverse
\ call <SID>reverse_lines(<line1>, <line2>)
-
- 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。
+ 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。
-
- この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが ^ で上書きされることがなくなる。
+ この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが ^ で上書きされることがなくなる。
-
- Vim のヘルプから該当箇所を引用する (強調は筆者による)。
+ Vim のヘルプから該当箇所を引用する (強調は筆者による)。
-
- :h autocmd-searchpat
+ :h autocmd-searchpat
-
- Autocommands do not change the current search patterns. Vim saves the current search patterns before executing autocommands then restores them after the autocommands finish. This means that autocommands do not affect the strings highlighted with the `hlsearch' option.
+ Autocommands do not change the current search patterns. Vim saves the current search patterns before executing autocommands then restores them after the autocommands finish. This means that autocommands do not affect the strings highlighted with the ‘hlsearch’ option.
-
- これは autocommand の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは :nohlsearch のヘルプにある。同じく該当箇所を引用する (強調は筆者による)。
+ これは autocommand の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは :nohlsearch のヘルプにある。同じく該当箇所を引用する (強調は筆者による)。
-
- :h :nohlsearch
+ :h :nohlsearch
-
- (略) This command doesn’t work in an autocommand, because the highlighting state is saved and restored when executing autocommands |autocmd-searchpat|. Same thing for when invoking a user function.
+ (略) This command doesn’t work in an autocommand, because the highlighting state is saved and restored when executing autocommands |autocmd-searchpat|. Same thing for when invoking a user function.
-
- この仕様により、:g/^/m0 の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。
+ この仕様により、:g/^/m0 の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。
-
-
-
-
- [2020/9/28追記] より簡潔な方法を見つけたため追記する
-
-
-
+
+
+
+ NOTE
+
+
+
+ [2020/9/28追記] より簡潔な方法を見つけたため追記する
+
+
+
command! -bar -range=%
\ Reverse
\ keeppatterns <line1>,<line2>g/^/m<line1>-1
-
- まさにこのための Exコマンド、:keeppatterns が存在する。:keeppatterns {command} のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
+ まさにこのための Exコマンド、:keeppatterns が存在する。:keeppatterns {command} のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
-
- :h :keeppatterns
+ :h :keeppatterns
diff --git a/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
index 3469e8b1..ca22ff5a 100644
--- a/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
+++ b/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html
@@ -67,22 +67,19 @@
-
-
+
- ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
+ ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
-
<?php
@@ -154,34 +151,28 @@
$👈, $📝,
]);
-
- この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
+ この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
-
-
-
+
- ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
+ ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
-
<?php
@@ -372,111 +337,87 @@
echo "{$x}\n\n";
}
-
- さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。トークンを得るためには、ソースコードを読み、定数 N を特定する必要がある。
+ さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 トークンを得るためには、ソースコードを読み、定数 N を特定する必要がある。
-
- ここでは、私の想定解を解説する。
+ ここでは、私の想定解を解説する。
-
-
+
- まずはソースコードを読んでいく。
+ まずはソースコードを読んでいく。
-
$token = [
-// 略
+ // 略
];
-
- 数値からなる $token があり、各要素をループしている。
+ 数値からなる $token があり、各要素をループしている。
-
-
- まずは排他的論理和 (xor) を取り、
+ まずは排他的論理和 (xor) を取り、
-
$x = sprintf('%025b', $x);
-
- 二進数に変換して、
+ 二進数に変換して、
-
$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-
- 0 を空白に、1 を # にし、
+ 0 を空白に、1 を # にし、
-
$x = implode("\n", str_split($x, length: 5));
-
- 5文字ごとに区切ったあと、改行で結合している。
+ 5文字ごとに区切ったあと、改行で結合している。
-
-
+
- 次に、ソースコードに書いてあるヒントを読んでいく。
+ 次に、ソースコードに書いてあるヒントを読んでいく。
-
-
N それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている
-
-
$token の各要素は、1文字を表す
-
-
1文字は 5x5 のセルからなる
-
-
出力されるのは、完全な PHPer トークンである
-
- ここで、PHPer トークンは必ず # 記号から始まることを思いだすと、$token の最初の数字 0x14B499C は、変換の結果 # になるのではないかと予想される (なお、このことは、リポジトリの README ファイルに追加ヒントとして書かれている)。
+ ここで、PHPer トークンは必ず # 記号から始まることを思いだすと、 $token の最初の数字 0x14B499C は、変換の結果 # になるのではないかと予想される (なお、このことは、リポジトリの README ファイルに追加ヒントとして書かれている)。
-
-
+
- ここまでわかれば、あと一歩で解ける。すなわち、0x14B499C が # に変換されるような N を見つければよい。
+ ここまでわかれば、あと一歩で解ける。すなわち、0x14B499C が # に変換されるような N を見つければよい。
-
- N は高々
+ N は高々
-
assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
-
- なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
+ なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
-
<?php
@@ -495,11 +436,9 @@
"#####\n" .
" # # ");
-
- この一連の変換に対する逆変換を考えると、次のようになる。
+ この一連の変換に対する逆変換を考えると、次のようになる。
-
<?php
@@ -518,19 +457,16 @@
echo "N = $n\n";
-
- これを実行すると、N が得られる。
+ これを実行すると、N が得られる。
-
-
+
- ソースコードはこちら。
+ ソースコードはこちら。
-
<?php
@@ -562,71 +498,59 @@
$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
-
- コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
+ コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
-
$ php toquine.php | php | php | php | ...
-
- 実際にはもう少しパイプで繋げなければならない。
+ 実際にはもう少しパイプで繋げなければならない。
-
-
-
+
- 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
+ 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
-
- 今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、来年は 5問、より面白い問題を持っていきます。
+ 今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 来年は 5問、より面白い問題を持っていきます。
-
- 実はもう作りはじめているので、どうか来年もありますように……。
+ 実はもう作りはじめているので、どうか来年もありますように……。
diff --git a/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html
index ff722cda..09931f66 100644
--- a/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html
+++ b/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html
@@ -55,110 +55,90 @@
-
-
+
- 以前、big-clock-mode という似たようなプログラムを書いた。 これは tmux の :clock-mode コマンドに着想を得たもので、:clock-mode よりも大きく現在時刻を表示する。
+ 以前、big-clock-mode という似たようなプログラムを書いた。 これは tmux の :clock-mode コマンドに着想を得たもので、:clock-mode よりも大きく現在時刻を表示する。
-
- big-clock-mode を開発したのは、次のようなシチュエーションで使うためである。弊社では現在リモートワークが基本だが、web 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。
+ big-clock-mode を開発したのは、次のようなシチュエーションで使うためである。 弊社では現在リモートワークが基本だが、web 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。
-
- それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。
+ それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。
-
- しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。
+ しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。
-
- そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。
+ そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。
-
-
+
- 全体の流れは次のようになっている。
+ 全体の流れは次のようになっている。
-
-
+
-
フォントファイルを読み込む
-
-
コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS 基準で並んでいるため)
-
-
1文字ずつレンダリングしていく
-
big-clock-mode が Go 製なので、今回も Go で書いた。 PNG が標準ライブラリにあったり、Shift-JIS のエンコーディングが準標準ライブラリにあったりしたのは助かった。
-
- フォントファイルは go:embed で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。
+ フォントファイルは go:embed で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。 仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。
-
-
+
- フリーの 8x8 ビットマップフォントである、美咲フォント 2021-05-05a 版 を使わせていただいた。
+ フリーの 8x8 ビットマップフォントである、 美咲フォント 2021-05-05a 版 を使わせていただいた。
-
- はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。同じく 8x8 で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。
+ はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。 同じく 8x8 で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。
-
- 美咲フォントは、平仮名・片仮名に留まらず、JIS 第一・第二水準の漢字までサポートしている。第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。
+ 美咲フォントは、平仮名・片仮名に留まらず、JIS 第一・第二水準の漢字までサポートしている。 第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。
-
- さらに言うと、実のところ美咲フォントは実サイズ 7x7 で作られており、余白が設けられている。これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。おかげでコーディングまで楽になった。
+ さらに言うと、実のところ美咲フォントは実サイズ 7x7 で作られており、余白が設けられている。 これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。 おかげでコーディングまで楽になった。
-
- ゴシック体と明朝体があったが、私の好みで明朝体の方にした。ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。
+ ゴシック体と明朝体があったが、私の好みで明朝体の方にした。 ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。
-
- 2022-04-27 追記: -f オプションで選べるようにした。
+ 2022-04-27 追記: -f オプションで選べるようにした。
-
-
+
- あなたもターミナルに住んでみませんか?
+ あなたもターミナルに住んでみませんか?
diff --git a/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html b/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html
index c12be3ee..baa6e91d 100644
--- a/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html
+++ b/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html
@@ -64,199 +64,178 @@
-
-
-
+
- PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。
+ PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。
-
-
プロポーザルを出す
-
-
PHPer チャレンジのトークン問題を 5題作成する
-
-
現地に行く
-
-
PHPer チャレンジで圧勝する
-
-
- 最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。
+ 最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。
-
- ではまた来年。
+ ではまた来年。
diff --git a/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html
index ed87f02e..928d6c04 100644
--- a/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html
+++ b/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html
@@ -64,74 +64,59 @@
-
+
- 本日 PHP カンファレンス沖縄 2022 が開催された (らしい)。
+ 本日 PHP カンファレンス沖縄 2022 が開催された (らしい)。
-
- カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。
+ カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。
-
-
-
+
- 細かいレギュレーションは不明だったので、勝手に定めた。
+ 細かいレギュレーションは不明だったので、勝手に定めた。
-
-
コマンドライン引数の第1引数で受けとる
-
-
結果は標準出力に出す
-
-
コンマの直後にはスペースを1つ置く
-
-
末尾コンマは禁止
-
-
数字でないものは入ってこないものとする
-
-
負数は入ってこないものとする
-
- 書いたものがこちら:
+ 書いたものがこちら:
-
[<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
-
- しめて 123 バイトとなった (末尾改行を含めずにカウント)。
+ しめて 123 バイトとなった (末尾改行を含めずにカウント)。
-
- こちらは改行とスペースを追加したバージョン:
+ こちらは改行とスペースを追加したバージョン:
-
[<?php
@@ -144,50 +129,43 @@
?>]
-
-
+
-
+
- 割と多くの言語のゴルフで使えるテクニック。e を用いた指数表記で、大きな数を短く表す。このコードでは 10000、5000、2000、1000 を指数表記している。
+ 割と多くの言語のゴルフで使えるテクニック。 e を用いた指数表記で、大きな数を短く表す。 このコードでは 10000、5000、2000、1000 を指数表記している。
-
-
+
- foreach、for、if などの後ろには、通常 { を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、{ と } を省略できる。C言語などでも使える。
+ foreach、for、if などの後ろには、 通常 { を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、{ と } を省略できる。 C言語などでも使える。
-
-
+
- PHP では、$r[] = ...... のような配列の末尾に追加する式を実行したとき、$r が未定義だった場合は $r を勝手に定義して空の配列で初期化してくれる。これを利用すると、$r = []; のような初期化が不要になる。
+ PHP では、$r[] = ...... のような配列の末尾に追加する式を実行したとき、 $r が未定義だった場合は $r を勝手に定義して空の配列で初期化してくれる。 これを利用すると、$r = []; のような初期化が不要になる。
-
- ただし、プログラムに 0 が渡されるとループを一度も回らないので、$r が未定義になってしまい、implode() に渡すところでエラーになる。それを防ぐために $r ?? [] を使っている。
+ ただし、プログラムに 0 が渡されるとループを一度も回らないので、$r が未定義になってしまい、 implode() に渡すところでエラーになる。 それを防ぐために $r ?? [] を使っている。
-
- もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。
+ もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。
-
-
+
- PHP では、<?php ?> で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず [ と ] を出力するので、そのまま書いてやればよい。
+ PHP では、<?php ?> で囲われた部分の外側にある文字列は、そのまま出力される。 今回のケースでは、先頭と末尾に必ず [ と ] を出力するので、そのまま書いてやればよい。
-
-
+
- 最後になりましたが、めもりーさん、楽しい問題をありがとうございました。
+ 最後になりましたが、 めもりー さん、楽しい問題をありがとうございました。
diff --git a/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html b/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html
index 9409eb8d..a86a94d3 100644
--- a/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html
+++ b/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html
@@ -52,61 +52,50 @@
-
-
+
- 組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。
+ 組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。
-
- 当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:
+ 当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:
-
- 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。
+ 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、 ↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、 寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。
-
- 追記: 「肩身が狭くなる」というのがより適切でした。
+ 追記: 「肩身が狭くなる」というのがより適切でした。
-
- ※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。
+ ※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。
-
- OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは CTO がカンファレンスを年2で主催したり: iOSDC PHPerKaigi) といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。
+ OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは CTO がカンファレンスを年2で主催したり: iOSDC PHPerKaigi ) といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。 これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。
-
- 以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。
+ 以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。
-
-
+
- 最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。この場を借りて感謝申し上げます。
+ 最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。 この場を借りて感謝申し上げます。
diff --git a/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
index 44e3dc2e..9b1bdde2 100644
--- a/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
+++ b/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
@@ -61,69 +61,62 @@
-
+
- この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、このページ にソースコードがあるので、そちらを先に見てほしい。
+ この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。 最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 このページ にソースコードがあるので、そちらを先に見てほしい。
-
-
+
- PHP で、次のような制約の下に fizzbuzz を書いた。
+ PHP で、次のような制約の下に fizzbuzz を書いた。
-
-
- 備考: PHP には short_open_tag というオプションがあり、これを有効にするとファイル冒頭の <?php の代わりに <? を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
+ 備考: PHP には short_open_tag というオプションがあり、 これを有効にするとファイル冒頭の <?php の代わりに <? を使うことができ、文字どおり1行2文字で書ける。 ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
-
-
+
- 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
+ 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
-
- 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
+ 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
-
#\
i\
@@ -200,41 +193,32 @@
/* あとは同じように普通のプログラムを変形するだけなので省略 */
-
- バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
+ バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
-
- さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、echo で出力することや、for でループすること、new でインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。
+ さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。 これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。 例えば、echo で出力することや、for でループすること、 new でインスタンスを生成することができない。 特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。
-
- 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:
+ 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:
-
-
-
_: gettext のエイリアス
+ _: gettext のエイリアス
-
-
-
dl: 拡張モジュールをロードする
+ dl: 拡張モジュールをロードする
-
-
-
pi: 円周率を返す
+ pi: 円周率を返す
-
- (環境によって多少は変わるかも)
+ (環境によって多少は変わるかも)
-
- 2文字の関数を定義しまくった拡張モジュールを用意しておいて dl() で読み込む行為は、レギュレーションで定めた
+ 2文字の関数を定義しまくった拡張モジュールを用意しておいて dl() で読み込む行為は、レギュレーションで定めた
-
-
- に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。
+ に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。
-
- また、2文字だと文字列がまともに書けないのも辛い。'' だけで2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
+ また、2文字だと文字列がまともに書けないのも辛い。'' だけで2文字使うので、 「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
-
-
- とすると $a は "\na" になるのだが、余計な改行が入ってしまう。
+ とすると $a は "\na" になるのだが、余計な改行が入ってしまう。
-
- これらの障害をどのように乗り越えるのか、次節から見ていく。
+ これらの障害をどのように乗り越えるのか、次節から見ていく。
-
-
-
+
- 完成したものがこちら。
+ 完成したものがこちら。
-
<?php
@@ -631,24 +581,20 @@
);
-
-
+
- PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
+ PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。 この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
-
- みんなもプログラムを細長くしよう。
+ みんなもプログラムを細長くしよう。
-
-
+
- PHP では、バッククォートを使ってシェルを呼び出せる。これは shell_exec 関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
+ PHP では、バッククォートを使ってシェルを呼び出せる。 これは shell_exec 関数と等価である。 さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
-
<?php
@@ -663,15 +609,12 @@
3\
`);
-
- なお、ここでは簡単のため出力に printf をそのまま使っているが、実際には printf という文字列を合成して可変関数で呼び出す。
+ なお、ここでは簡単のため出力に printf をそのまま使っているが、 実際には printf という文字列を合成して可変関数で呼び出す。
-
- ただし、これでは
+ ただし、これでは
-
-
- に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。
+ に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。
-
- もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
+ もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
-
<?php
@@ -719,21 +659,17 @@
3\
`);
-
- 先程と同じく、chr や printf を生成する部分は長くなるので省いた。
+ 先程と同じく、chr や printf を生成する部分は長くなるので省いた。
-
-
- は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。
+ は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。 シェルに渡されている文字列は次のようになる。
-
e\
c\
@@ -744,27 +680,22 @@
2\
3\
-
- これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。
+ これは、前掲したコマンドと同じだ。 かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。 Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。
-
- ということでこれは別解ということにしておく。
+ ということでこれは別解ということにしておく。
-
- ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
+ ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
-
-
- 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。
+ 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。
diff --git a/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html
index efc80674..e3d5145f 100644
--- a/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html
+++ b/vhosts/blog/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html
@@ -61,30 +61,25 @@
-
+
- 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、PHPerKaigi 2023 において、昨年と同様に、弊社 デジタルサーカス株式会社 から、トークン問題を出題予定である。
+ 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、 PHPerKaigi 2023 において、 昨年と同様に、弊社 デジタルサーカス株式会社 から、 トークン問題を出題予定である。
-
- 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説
+ 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説
-
- すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+ すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。 せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
-
- 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
+ 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
-
-
+
- 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
<?php
@@ -109,51 +104,41 @@
}
-
-
+
- ソースを見るとわかるとおり、$argv[1] を参照している。それを $π なる変数に代入しているので、円周率を渡してみる。
+ ソースを見るとわかるとおり、$argv[1] を参照している。 それを $π なる変数に代入しているので、円周率を渡してみる。
-
-
- 失敗してしまった。精度を上げてみる。
+ 失敗してしまった。精度を上げてみる。
-
$ php Q.php 3.1415
Failed.
-
- だめだった。これを成功するまで繰り返す。
+ だめだった。これを成功するまで繰り返す。
-
- 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。
+ 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。
-
$ php Q.php 3.1415926535897932
Token: #YO
-
- めでたくトークン「#YO」が手に入った。
+ めでたくトークン「#YO」が手に入った。
-
-
-
+
- 円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。
+ 円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。
-
- 最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた (ちなみに、それでも M_PI や pi() では精度が足りない)。見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。
+ 最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた (ちなみに、それでも M_PI や pi() では精度が足りない)。 見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。 むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。
diff --git a/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html b/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html
index cce709ac..ceba0dcd 100644
--- a/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html
+++ b/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html
@@ -61,54 +61,46 @@
-
+
- これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。そのときにおこなったサーバのセットアップ作業を書き残しておく。99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。
+ これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。 そのときにおこなったサーバのセットアップ作業を書き残しておく。 99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。
-
- 未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。
+ 未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。
-
-
+
- さくらの VPS の 2 GB プラン。そこまで真面目に選定していないので、困ったら移動するかも。
+ さくらの VPS の 2 GB プラン。 そこまで真面目に選定していないので、困ったら移動するかも。
-
-
+
-
+
- モチベーションが上がるという効能がある。今回は藤原定家から取って teika にした。たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。
+ モチベーションが上がるという効能がある。今回は藤原定家から取って teika にした。 たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。
-
-
+
- ローカルマシンで鍵を生成する。
+ ローカルマシンで鍵を生成する。
-
$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
-
- teika.key はローカルからサーバへの接続用、github2teika.key は、GitHub Actions からサーバへのデプロイ用。
+ teika.key はローカルからサーバへの接続用、github2teika.key は、 GitHub Actions からサーバへのデプロイ用。
-
-
+
.ssh/config に設定しておく。
-
Host teika
HostName **********
@@ -119,22 +111,19 @@
-
-
+
-
+
- VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。
+ VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。
-
-
+
- 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。sudo グループに追加して sudo できるようにし、su で切り替え。
+ 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。 sudo グループに追加して sudo できるようにし、su で切り替え。
-
$ sudo adduser **********
$ sudo adduser ********** sudo
@@ -142,79 +131,65 @@
$ cd
-
-
-
+
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
$ vi ~/.ssh/authorized_keys
-
authorized_keys には、ローカルで生成した ~/.ssh/teika.key.pub と ~/.ssh/github2teika.key.pub の内容をコピーする。
-
-
+
- SSH の設定を変更し、少しでも安全にしておく。
+ SSH の設定を変更し、少しでも安全にしておく。
-
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
$ sudo vi /etc/ssh/sshd_config
-
-
Port を変更
-
-
PermitRootLogin を no に
-
-
PasswordAuthentication を no に
-
- そして設定を反映。
+ そして設定を反映。
-
$ sudo systemctl restart sshd
$ sudo systemctl status sshd
-
-
+
- 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。
+ 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。 セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。
-
-
-
+
- デフォルトの 22 番を閉じ、設定したポートだけ空ける。
+ デフォルトの 22 番を閉じ、設定したポートだけ空ける。
-
$ sudo ufw deny ssh
$ sudo ufw allow *******
@@ -222,35 +197,28 @@
$ sudo ufw reload
$ sudo ufw status
-
- ここでもう一度 SSH の接続確認を挟む。
+ ここでもう一度 SSH の接続確認を挟む。
-
-
+
- GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
+ GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
-
$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
$ cat ~/.ssh/github.key.pub
-
GitHub の設定画面 から、この公開鍵を追加する。
-
-
- 設定はこう。
+ 設定はこう。
-
Host github.com
HostName github.com
@@ -259,18 +227,15 @@
IdentityFile ~/.ssh/github.key
IdentitiesOnly yes
-
- 最後に接続できるか確認しておく。
+ 最後に接続できるか確認しておく。
-
-
-
+
$ sudo apt update
$ sudo apt upgrade
@@ -280,36 +245,31 @@
-
-
+
-
+
- このサーバは固定の IP アドレスがあるので、A レコードに直接入れるだけで済んだ。
+ このサーバは固定の IP アドレスがあるので、A レコードに直接入れるだけで済んだ。
-
-
+
$ sudo apt install docker docker-compose git make
-
-
+
$ sudo adduser ********** docker
-
-
+
- 80 番と 443 番を空ける。
+ 80 番と 443 番を空ける。
-
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
@@ -317,9 +277,8 @@
$ sudo ufw status
-
-
+
$ cd
$ git clone git@github.com:nsfisis/nsfisis.dev.git
@@ -327,27 +286,24 @@
$ git submodule update --init
-
-
+
$ docker-compose up -d acme-challenge
$ make setup
-
-
-
+
- (業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。次の式年遷宮では、手順の一部だけでも自動化したいところ。
+ (業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。 とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。 コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。 次の式年遷宮では、手順の一部だけでも自動化したいところ。
diff --git a/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html b/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html
index e093f669..63c928b6 100644
--- a/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html
+++ b/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html
@@ -61,34 +61,28 @@
-
-
+
- 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -101,18 +95,15 @@
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
- "And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。
+ “And Then There Were None” (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。
-
-
+
- 実行してみると、次のような出力が得られる。
+ 実行してみると、次のような出力が得られる。
-
#
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -125,11 +116,9 @@
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
- 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。
+ 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。
-
#
W
@@ -142,11 +131,9 @@
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
- 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。
+ 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。
-
#
W
@@ -159,78 +146,63 @@
H
P
-
- トークン「#WELOVEPHP」が手に入った。
+ トークン「#WELOVEPHP」が手に入った。
-
-
-
+
CVE-2021-42574 に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。
-
- ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。
+ ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。
diff --git a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
index 6cad79cb..01a08b04 100644
--- a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
+++ b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
@@ -61,40 +61,33 @@
-
+
- 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の PHPerKaigi 2023 において、昨年と同様に、弊社 デジタルサーカス株式会社 からトークン問題を出題予定である。
+ 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の PHPerKaigi 2023 において、 昨年と同様に、弊社 デジタルサーカス株式会社 からトークン問題を出題予定である。
-
- 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説
+ 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説
-
- すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+ すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。 せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
-
- 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
+ 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
-
-
-
+
- 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
<?php
try {
@@ -209,45 +202,37 @@
return __LINE__;
}
-
- "Catchline" と名付けた作品。実行するとトークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') が得られる。
+ “Catchline” と名付けた作品。実行するとトークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') が得られる。
-
- トークンは PHP の式になっていて、評価すると Hello, World! という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
+ トークンは PHP の式になっていて、評価すると Hello, World! という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
-
-
-
+
- 「行数」というのはトークン文字列をデコードする対象として優れている。
+ 「行数」というのはトークン文字列をデコードする対象として優れている。
-
-
トークンの一部や全部が陽に現れない
-
-
__LINE__ で容易に取得できる
-
- しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 __LINE__ を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして Throwable を思いつき、続けてエラーオブジェクトには $previous があることを思い出した。
+ しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 __LINE__ を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして Throwable を思いつき、続けてエラーオブジェクトには $previous があることを思い出した。
-
- 今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で Throwable なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、0/0 のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
+ 今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で Throwable なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、0/0 のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
diff --git a/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html b/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html
index f3eb6ae2..0749a217 100644
--- a/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html
+++ b/vhosts/blog/public/posts/2023-03-10/rewrite-this-blog-generator/index.html
@@ -52,112 +52,90 @@
-
+
- このブログを構築するシステムを書き直したのは 2度目である。元々立ち上げた当初は、静的サイトジェネレータである Hugo を使っていた。それを Asciidoctor にいくつかのカスタムを加えた自前のジェネレータに移行したのが 2022年の11月ごろだ。そして今回、スクラッチから書いた Deno 製のジェネレータに移行した。
+ このブログを構築するシステムを書き直したのは 2度目である。 元々立ち上げた当初は、静的サイトジェネレータである Hugo を使っていた。 それを Asciidoctor にいくつかのカスタムを加えた自前のジェネレータに移行したのが 2022年の11月ごろだ。 そして今回、スクラッチから書いた Deno 製のジェネレータに移行した。
-
- この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。
+ この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。
-
-
+
- 最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。例えば、Hugo を使っていたころはサポートしていた RSS フィードの生成は、まだ実装できていない。
+ 最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。 実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。 例えば、Hugo を使っていたころはサポートしていた RSS フィードの生成は、まだ実装できていない。
-
- 移行先のフォーマットとして AsciiDoc を選んだのは、Markdown よりも表現力に優れるからである。Markdown は広く使われている軽量マークアップ言語だが、以下のような欠点を持つ。
+ 移行先のフォーマットとして AsciiDoc を選んだのは、Markdown よりも表現力に優れるからである。Markdown は広く使われている軽量マークアップ言語だが、以下のような欠点を持つ。
-
-
-
- -
- CommonMark では機能が貧弱である (例: 脚注、
id 属性の付与)
-
-
- -
- 拡張記法に実装間で互換性がない
-
-
- -
- メタデータ (公開日など) を埋め込む統一された方法がない
-
-
-
-
-
- AsciiDoc は Markdown に比べると普及していないが、上記の欠点は克服している。
-
-
+
+ -
+ CommonMark では機能が貧弱である (例: 脚注、
id 属性の付与)
+
+ -
+ 拡張記法に実装間で互換性がない
+
+ -
+ メタデータ (公開日など) を埋め込む統一された方法がない
+
+
-
- -
- ブログを書くのに十分な表現力がある
-
-
- -
- フォーマットを拡張するときの記法があらかじめ定められている
-
-
- -
- メタデータを埋め込む統一された方法がある
-
-
+ AsciiDoc は Markdown に比べると普及していないが、上記の欠点は克服している。
-
+
+ -
+ ブログを書くのに十分な表現力がある
+
+ -
+ フォーマットを拡張するときの記法があらかじめ定められている
+
+ -
+ メタデータを埋め込む統一された方法がある
+
+
- なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。
+ なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。
-
-
+
+
+ AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、
+
+
+ -
+ 表現力が高く、
+
+ -
+ 文法が厳密であり、
+
+ -
+ 簡単に実装できる
+
+
- AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、
-
- -
- 表現力が高く、
-
-
- -
- 文法が厳密であり、
-
-
- -
- 簡単に実装できる
-
-
- フォーマットが求められた。これに合致したのが、XML をベースとする DocBook (今回使っているのは、そのサブセットである Simplified DocBook) である。
+ フォーマットが求められた。これに合致したのが、XML をベースとする DocBook (今回使っているのは、そのサブセットである Simplified DocBook ) である。
-
- 実は、AsciiDoc と DocBook はおおよそ互換性がある。AsciiDoc で書かれた文書は (ほぼ) 情報ロスなしに DocBook へ変換でき、逆もまたしかりである。よって、DocBook には、AsciiDoc と同等の表現力がある。
+ 実は、AsciiDoc と DocBook はおおよそ互換性がある。AsciiDoc で書かれた文書は (ほぼ) 情報ロスなしに DocBook へ変換でき、逆もまたしかりである。 よって、DocBook には、AsciiDoc と同等の表現力がある。
-
- XML の文法の厳密さについては、説明するまでもないだろう。また、単純な文法であることから実装が容易であり、事実上 Asciidoctor へロックインされる AsciiDoc とは異なり、さまざまな言語で多くのライブラリが存在する。
+ XML の文法の厳密さについては、説明するまでもないだろう。また、単純な文法であることから実装が容易であり、事実上 Asciidoctor へロックインされる AsciiDoc とは異なり、さまざまな言語で多くのライブラリが存在する。
-
- 今回は、XML のパース自体も自分で書いている (これは何となく書きたかったからであり、合理的な理由があるわけではない。実装はサボりまくっているので XML のコメントが使えないといった制限がある)。
+ 今回は、XML のパース自体も自分で書いている (これは何となく書きたかったからであり、合理的な理由があるわけではない。実装はサボりまくっているので XML のコメントが使えないといった制限がある)。
-
- XML という機械処理しやすいフォーマットを選ぶことには、機械的な変換や検査といった処理がおこないやすくなるといった利点もある。欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。
+ XML という機械処理しやすいフォーマットを選ぶことには、機械的な変換や検査といった処理がおこないやすくなるといった利点もある。 欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。 結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。
-
-
+
- 2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。
+ 2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。 今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。
-
- 最後にもう一度書くのだが、Hugo は大変に優れた静的サイトジェネレータである。無駄な拘りがなければこれを使うとよい。私は無駄に拘ったので、ブログの記事を書く時間を潰してブログシステムを作ってしまった。
+ 最後にもう一度書くのだが、Hugo は大変に優れた静的サイトジェネレータである。 無駄な拘りがなければこれを使うとよい。 私は無駄に拘ったので、ブログの記事を書く時間を潰してブログシステムを作ってしまった。
diff --git a/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html b/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html
index 11831bdf..502a7e3a 100644
--- a/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html
+++ b/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html
@@ -52,51 +52,42 @@
-
+
- この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。
+ この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。 PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。 また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。
-
-
+
- PNG ファイルの基本構造は次のようになっている。
+ PNG ファイルの基本構造は次のようになっている。
-
-
PNG signature
-
-
IHDR chunk
-
-
任意個の chunk
-
-
IEND chunk
-
- Chunk には画像データを入れる IDAT chunk、パレットデータを入れる PLTE chunk、テキストデータを入れる tEXt chunk などがあるが、今回は最小構成ということで IDAT chunk (と IHDR chunk と IEND chunk) のみを用いる。
+ Chunk には画像データを入れる IDAT chunk、パレットデータを入れる PLTE chunk、テキストデータを入れる tEXt chunk などがあるが、 今回は最小構成ということで IDAT chunk (と IHDR chunk と IEND chunk) のみを用いる。
-
- 次節で、それぞれの具体的な構造を確認しつつ実装していく。
+ 次節で、それぞれの具体的な構造を確認しつつ実装していく。
-
-
+
- 以下のソースコードをベースにする。今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ image/png を用いる。
+ 以下のソースコードをベースにする。 今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ image/png を用いる。
-
package main
@@ -137,59 +128,46 @@
writeChunkIend(w)
}
-
- 以降は、writeSignature や writeChunkIhdr などを実装していく。
+ 以降は、writeSignature や writeChunkIhdr などを実装していく。
-
-
+
- PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。
+ PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。
-
-
0x89
-
-
0x50 (ASCII コードで「P」)
-
-
0x4E (ASCII コードで「N」)
-
-
0x47 (ASCII コードで「G」)
-
-
0x0D (ASCII コードで CR)
-
-
0x0A (ASCII コードで LF)
-
-
0x1A (ASCII コードで EOF)
-
-
0x0A (ASCII コードで LF)
-
- CRLF や LF は、送信中に改行コードの変換が誤っておこなわれていないかどうかを検知するのに使われる。
+ CRLF や LF は、送信中に改行コードの変換が誤っておこなわれていないかどうかを検知するのに使われる。
-
- writeSignature の実装はこちら:
+ writeSignature の実装はこちら:
-
import "encoding/binary"
@@ -207,40 +185,32 @@
binary.Write(w, binary.BigEndian, sig)
}
-
encoding/binary パッケージの binary.Write を使い、固定の 8 バイトを書き込む。
-
-
+
- IHDR chunk に進む前に、chunk 一般の構造を確認する。
+ IHDR chunk に進む前に、chunk 一般の構造を確認する。
-
-
Length: chunk data のバイト長 (符号なし 4 バイト整数)
-
-
Chunk type: chunk の種類を示す 4 バイトからなる名前
-
-
Chunk data: 実際のデータ。0 バイトでもよい
-
-
CRC: chunk type と chunk data の CRC (符号なし 4 バイト整数)
-
- CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている (D. Sample CRC implementation) ので、これを Go に移植する。
+ CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている ( D. Sample CRC implementation ) ので、これを Go に移植する。
-
var (
crcTable [256]uint32
@@ -278,11 +248,9 @@
return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
}
-
- できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。
+ できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。
-
func writeChunk(w io.Writer, chunkType string, data []byte) {
typeAndData := make([]byte, 0, len(chunkType)+len(data))
@@ -294,91 +262,91 @@
binary.Write(w, binary.BigEndian, crc(typeAndData))
}
-
- 仕様どおり、chunkType と data から CRC を計算し、data の長さと合わせて書き込んでいる。PNG では基本的に big endian を使うことに注意する。
+ 仕様どおり、chunkType と data から CRC を計算し、data の長さと合わせて書き込んでいる。 PNG では基本的に big endian を使うことに注意する。
-
- 準備ができたところで、具体的な chunk をエンコードしていく。
+ 準備ができたところで、具体的な chunk をエンコードしていく。
-
-
+
- IHDR chunk は最初に配置される chunk である。次のようなデータからなる。
+ IHDR chunk は最初に配置される chunk である。次のようなデータからなる。
-
-
画像の幅 (符号なし 4 バイト整数)
-
-
画像の高さ (符号なし 4 バイト整数)
-
-
- ビット深度 (符号なし 1 バイト整数)
+
+ ビット深度 (符号なし 1 バイト整数)
+
-
1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる
-
-
- 色タイプ (符号なし 1 バイト整数)
+
+ 色タイプ (符号なし 1 バイト整数)
+
-
0: グレースケール
-
-
2: Truecolor (今回はこれに決め打ち)
-
-
3: パレットのインデックス
-
-
4: グレースケール + アルファ
-
-
6: Truecolor + アルファ
-
-
- 圧縮方式 (符号なし 1 バイト整数)
+
+ 圧縮方式 (符号なし 1 バイト整数)
+
- PNG の仕様書に 0 しか定義されていないので 0 で固定
+ -
+ PNG の仕様書に 0 しか定義されていないので 0 で固定
+
-
-
- フィルタ方式 (符号なし 1 バイト整数)
+
+ フィルタ方式 (符号なし 1 バイト整数)
+
- PNG の仕様書に 0 しか定義されていないので 0 で固定
+ -
+ PNG の仕様書に 0 しか定義されていないので 0 で固定
+
-
-
- インターレース方式 (符号なし 1 バイト整数)
+
+ インターレース方式 (符号なし 1 バイト整数)
+
- 今回はインターレースしないので 0
+ -
+ 今回はインターレースしないので 0
+
-
- 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
+ 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
-
import "bytes"
@@ -396,45 +364,36 @@
}
-
-
+
- IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。
+ IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。
-
-
+
- まずは zlib について確認する。おおよそ次のような構造になっている。
+ まずは zlib について確認する。おおよそ次のような構造になっている。
-
-
固定で 0x78 (符号なし 1 バイト整数)
-
-
固定で 0x01 (符号なし 1 バイト整数)
-
-
データ
-
-
データの Adler-32
-
- 最初の 2 バイトにも意味はあるが、PNG では固定で構わない。
+ 最初の 2 バイトにも意味はあるが、PNG では固定で構わない。
-
- Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている (9. Appendix: Sample code) ので、Go に移植する。
+ Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている ( 9. Appendix: Sample code ) ので、Go に移植する。
-
const adler32Base = 65521
@@ -453,37 +412,29 @@
return updateAdler32(1, buf)
}
-
- 「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。
+ 「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。
-
- 1 つの無圧縮ブロックには 65535 (216 - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
+ 1 つの無圧縮ブロックには 65535 (216 - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
-
-
最終ブロックなら 1、そうでなければ 0 (符号なし 1 バイト整数)
-
-
ブロックのバイト長 (符号なし 2 バイト整数)
-
-
ブロックのバイト長の 1 の補数、あるいはビット反転 (符号なし 2 バイト整数)
-
-
データ (最大 65535 バイト)
-
- 実際にこの手抜き zlib を実装したものがこちら:
+ 実際にこの手抜き zlib を実装したものがこちら:
-
func encodeZlib(data []byte) []byte {
var buf bytes.Buffer
@@ -511,21 +462,17 @@
}
-
-
+
- では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。右下のピクセルまで行けば終わり。要は Z 字型に進んでいく。
+ では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。 画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。 右下のピクセルまで行けば終わり。要は Z 字型に進んでいく。
-
- また、それぞれの行の先頭には、圧縮のためのフィルタタイプを指定する。ただ、今回はその実装を省略するために、常にフィルタ 0 (何も加工しない) を使う。
+ また、それぞれの行の先頭には、圧縮のためのフィルタタイプを指定する。 ただ、今回はその実装を省略するために、常にフィルタ 0 (何も加工しない) を使う。
-
- 先ほどの encodeZlib も使って実際に実装したものがこちら:
+ 先ほどの encodeZlib も使って実際に実装したものがこちら:
-
func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
var pixels bytes.Buffer
@@ -544,17 +491,14 @@
-
-
+
- 最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。
+ 最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。
-
- 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単:
+ 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単:
-
func writeChunkIend(w io.Writer) {
writeChunk(w, "IEND", nil)
@@ -562,13 +506,11 @@
-
-
+
- 最後に全ソースコードを再掲しておく。
+ 最後に全ソースコードを再掲しておく。
-
package main
@@ -746,14 +688,12 @@
}
-
-
+
- 2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。
+ 2023-03-23 から 2023-03-25 にかけて開催された、 PHPerKaigi 2023 に参加した。 今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。
-
- 昨年、一昨年の参加レポはこちら:
+ 昨年、一昨年の参加レポはこちら:
-
-
-
+
- これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。
+ これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。
-
-
- PHPer チャレンジの話については後述する。参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。
+ PHPer チャレンジの話については後述する。 参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。
-
-
+
- 今回はスピーカーのみならず当日スタッフとしても参加した。カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。
+ 今回はスピーカーのみならず当日スタッフとしても参加した。 カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。
-
- PHPerKaigi は一般参加者の目線でもよくできたカンファレンスだなあという印象だったのだが、よりその思いを強くした。なんとスタッフにとってもよくできたカンファレンスなのである。
+ PHPerKaigi は一般参加者の目線でもよくできたカンファレンスだなあという印象だったのだが、よりその思いを強くした。 なんとスタッフにとってもよくできたカンファレンスなのである。
-
- 反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。
+ 反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。
-
-
+
-
+
- 5つのセッションを厳選した。
+ 5つのセッションを厳選した。
-
ブラウザの向こう側で「200 OK」を返すまでに何が起きているのか調べてみた
-
- Web に関わるなら、バックエンドでもフロントエンドでも知っておいてほしい知識。タイトルを見て「こんな話だろうな」と想像がつくレベルなら見なくてもいいかも。
+ Web に関わるなら、バックエンドでもフロントエンドでも知っておいてほしい知識。 タイトルを見て「こんな話だろうな」と想像がつくレベルなら見なくてもいいかも。
-
- PHPで学ぶ "Cacheの距離" の話
+ PHPで学ぶ “Cacheの距離” の話
-
- これも上セッションと同様に、基礎を抑えられる良いセッション。
+ これも上セッションと同様に、基礎を抑えられる良いセッション。
-
防衛的 PHP: 多様性を生き抜くための PHP 入門
-
- 静的解析ツールの話。静的解析は PHP のみならず最近の動的言語の一大潮流なので、逃れられない。
+ 静的解析ツールの話。静的解析は PHP のみならず最近の動的言語の一大潮流なので、逃れられない。
-
PHPの最高機能、配列を捨てよう!!
-
- 実はこれも上のセッションと同様の話。PHP の静的解析ツールは配列にも (無理矢理) 型が付けられるものが多いが、実行時にも検査できるという点において専用のクラスを作る方が優れている。
+ 実はこれも上のセッションと同様の話。 PHP の静的解析ツールは配列にも (無理矢理) 型が付けられるものが多いが、実行時にも検査できるという点において専用のクラスを作る方が優れている。
-
時間を気にせず普通にカンニングもしつつ ISUCON12 本選問題を PHP でやってみる
-
- 個人的に最も楽しみにしていたセッションであり、今回のモリアガリトーク賞 (盛り上がったセッションに運営側から贈られる賞) でもある。ネタバレになるが、最終的に (Go で実装された) 本戦優勝スコアを超えている。
+ 個人的に最も楽しみにしていたセッションであり、今回のモリアガリトーク賞 (盛り上がったセッションに運営側から贈られる賞) でもある。 ネタバレになるが、最終的に (Go で実装された) 本戦優勝スコアを超えている。
-
-
+
- 昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。また、今年はさらに作成した問題を解説するセッションにも登壇した。今年のトークンは、昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作した。
+ 昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。 また、今年はさらに作成した問題を解説するセッションにも登壇した。 今年のトークンは、昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作した。
-
- 問題の制作中は大変楽しかったが、まあやりすぎた。いかに超絶技巧を凝らすかに注力してしまい、解く楽しさという観点を失ってしまったきらいがある。
+ 問題の制作中は大変楽しかったが、まあやりすぎた。 いかに超絶技巧を凝らすかに注力してしまい、解く楽しさという観点を失ってしまったきらいがある。
-
- (WIP: 解説ブログ記事執筆中。終わったらここにリンク)
+ (WIP: 解説ブログ記事執筆中。終わったらここにリンク)
-
-
+
- なんかいろいろ。
+ なんかいろいろ。
-
-
マカロンおいしかった
-
-
\ペチパー/
-
-
名札便利
-
-
\ペチパー/
-
-
傘袋便利
-
-
\ペチパー/
-
-
パーカーのデザイン良き
-
- (あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった)
+ (あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった)
-
-
+
- 去年の参加レポでは、来年の目標として次を挙げた。
+ 去年の参加レポ では、来年の目標として次を挙げた。
-
-
プロポーザルを出す
-
-
PHPer チャレンジのトークン問題を 5題作成する
-
-
現地に行く
-
-
PHPer チャレンジで圧勝する
-
- プロポーザルに関しては採択されて登壇できたし、PHPer チャレンジは解説もおこなった。また、現地に行くだけでなく、当日スタッフとして参加した。4つ目の PHPer チャレンジに関しては、今年は参加していない。スタッフをやりながらだと入力する時間も探す時間も取れそうになかったのと、スタッフをやっている関係で少しだけ早く入手してしまうトークンがいくつか存在していたため。
+ プロポーザルに関しては採択されて登壇できたし、PHPer チャレンジは解説もおこなった。また、現地に行くだけでなく、当日スタッフとして参加した。 4つ目の PHPer チャレンジに関しては、今年は参加していない。 スタッフをやりながらだと入力する時間も探す時間も取れそうになかったのと、スタッフをやっている関係で少しだけ早く入手してしまうトークンがいくつか存在していたため。
-
- カンファレンス全体の感想についてだが、大規模なカンファレンスにオフラインで参加するのは今回が初めてだったので、その話をしたい。
+ カンファレンス全体の感想についてだが、大規模なカンファレンスにオフラインで参加するのは今回が初めてだったので、その話をしたい。
-
- オンラインとオフラインだと体験が別物になる。そもそもが似て非なるものなのだ。向き不向きはあるだろうが、オンラインしか参加したことのないという方は、一度現地参加してみてはいかがだろうか。
+ オンラインとオフラインだと体験が別物になる。そもそもが似て非なるものなのだ。 向き不向きはあるだろうが、オンラインしか参加したことのないという方は、一度現地参加してみてはいかがだろうか。
-
- さて、参加レポは去年も一昨年もこの言葉で締め括っているので、今年もそれで終わろうと思う。
+ さて、参加レポは去年も一昨年もこの言葉で締め括っているので、今年もそれで終わろうと思う。
-
- ではまた来年。
+ ではまた来年。
diff --git a/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html b/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html
index 8e2d179f..3a60d442 100644
--- a/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html
+++ b/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html
@@ -64,95 +64,97 @@
-
-
-
+
- 居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。受け取った「熱」が冷める前に、自らの手を動かしていきたい。
+ 居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。 受け取った「熱」が冷める前に、自らの手を動かしていきたい。
diff --git a/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html b/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
index 35252f62..7fa0ebe4 100644
--- a/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
+++ b/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html
@@ -61,55 +61,45 @@
-
+
- Emscripten を用いて PHP の処理系を WebAssembly にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
+ Emscripten を用いて PHP の処理系 を WebAssembly にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
-
- なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
+ なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
-
-
+
- この記事中で使用するソフトウェア等のバージョンを記載する。
+ この記事中で使用するソフトウェア等のバージョンを記載する。
-
-
Ubuntu 22.04 on WSL2
-
-
Docker version 24.0.6
-
-
Emscripten 3.1.46
-
-
Node.js 20.7.0
-
-
PHP 8.2.10
-
- なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
+ なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
-
-
+
- 先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。このコードはこのあと使うので、index.mjs の名前で保存しておくこと。
+ 先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。 このコードはこのあと使うので、index.mjs の名前で保存しておくこと。
-
import { readFile } from 'node:fs/promises';
import PHPWasm from './php-wasm.mjs'
@@ -124,20 +114,17 @@
);
console.log(`exit code: ${result}`);
-
- 標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような php-wasm.mjs (とそこから呼び出される php-wasm.wasm) を作成する。
+ 標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような php-wasm.mjs (とそこから呼び出される php-wasm.wasm) を作成する。
-
-
+
-
+
- 先ほどのコードでも使っていたエントリポイントである php_wasm_run を用意する。
+ 先ほどのコードでも使っていたエントリポイントである php_wasm_run を用意する。
-
#include <stdio.h>
#include <emscripten.h>
@@ -164,38 +151,30 @@
return result == SUCCESS ? 0 : 1;
}
-
- ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
+ ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。
-
- まずは EMSCRIPTEN_KEEPALIVE について。これは Emscripten が用意している特殊なマクロである。このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。もしこれを付け忘れると、未使用の関数とみなされ削除される。
+ まずは EMSCRIPTEN_KEEPALIVE について。 これは Emscripten が用意している特殊なマクロである。 このマクロが付与されている関数は、どこからも使用されていなくともコンパイル後の WebAssembly バイナリから削除されない。 もしこれを付け忘れると、未使用の関数とみなされ削除される。
-
- 次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。出力バッファから出力させるためだけなら改行を出力させなくとも fflush() だけで事足りると考えたのだが、ないと動かなかったので追加した。これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
+ 次に、コードを評価したあとに呼んでいる標準出力と標準エラー出力に対する改行の出力について。 出力バッファから出力させるためだけなら改行を出力させなくとも fflush() だけで事足りると考えたのだが、ないと動かなかったので追加した。 これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。 改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
-
-
+
- それでは WebAssembly にコンパイルしていこう。ここからは Dockerfile 上のコマンドとして操作を示す。
+ それでは WebAssembly にコンパイルしていこう。ここからは Dockerfile 上のコマンドとして操作を示す。
-
- まずは Emscripten 公式が提供している Docker イメージを使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
+ まずは Emscripten 公式が提供している Docker イメージ を使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。
-
FROM emscripten/emsdk:3.1.46 AS wasm-builder
-
- 次に、php/php-src から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
+ 次に、 php/php-src から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。 有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。
-
RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
@@ -208,11 +187,9 @@
&& \
:
-
- 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
+ 続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。
-
RUN cd php-src && \
./buildconf --force && \
@@ -237,35 +214,27 @@
git clean -fd && \
:
-
- ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
+ ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。
-
- まず、buildconf は PHP 処理系をビルドするときに (Emscripten とは関係なく) 使うツールである。このツールの最も重要な仕事は、configure の生成である。
+ まず、buildconf は PHP 処理系をビルドするときに (Emscripten とは関係なく) 使うツールである。 このツールの最も重要な仕事は、configure の生成である。
-
- 次に configure するわけだが、ここで emconfigure を使う。これを使うことで、Emscripten が上手く諸々のツールチェインを WebAssembly のビルド向けに調整しながら configure してくれる。
+ 次に configure するわけだが、ここで emconfigure を使う。 これを使うことで、Emscripten が上手く諸々のツールチェインを WebAssembly のビルド向けに調整しながら configure してくれる。
-
- configure の後ろに指定してあるフラグは、通常の PHP 処理系のビルドで使う configure と同じなので、詳しくはそちらの cofigure --help を参照していただきたい。ほとんどは、機能の無効化のために指定している (依存するライブラリを減らし、ビルドをより簡単にするため)。
+ configure の後ろに指定してあるフラグは、通常の PHP 処理系のビルドで使う configure と同じなので、詳しくはそちらの cofigure --help を参照していただきたい。 ほとんどは、機能の無効化のために指定している (依存するライブラリを減らし、ビルドをより簡単にするため)。
-
- 通常の C のビルドなら、configure の次は make するところだが、ここでも emmake を使う。役割はほとんど emconfigure と同様である。指定してある EMCC_CFLAGS という環境変数は、Emscripten の C コンパイラへのフラグで、ここでは ERROR_ON_UNDEFINED_SYMBOLS を無効化している。これにより、コンパイル中に出現した解決できなかったシンボルを無視するようになる (代わりに、そのシンボルを呼ぼうとしたタイミングで実行時エラーになる)。すべての依存を完全に解決するのは面倒なので、あまり使わない機能については無視してもよいだろう。
+ 通常の C のビルドなら、configure の次は make するところだが、ここでも emmake を使う。 役割はほとんど emconfigure と同様である。 指定してある EMCC_CFLAGS という環境変数は、Emscripten の C コンパイラへのフラグで、ここでは ERROR_ON_UNDEFINED_SYMBOLS を無効化している。 これにより、コンパイル中に出現した解決できなかったシンボルを無視するようになる (代わりに、そのシンボルを呼ぼうとしたタイミングで実行時エラーになる)。 すべての依存を完全に解決するのは面倒なので、あまり使わない機能については無視してもよいだろう。
-
- ここまでを実行すると libs/libphp.a が生成される。これは後で使うので移動させている。
+ ここまでを実行すると libs/libphp.a が生成される。これは後で使うので移動させている。
-
- さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。Dockerfile と同じ場所に php-wasm.c という名前で保存し、次のようにする。
+ さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。 Dockerfile と同じ場所に php-wasm.c という名前で保存し、次のようにする。
-
COPY php-wasm.c /src/
@@ -284,15 +253,12 @@
git clean -fd && \
:
-
- emcc は cc (C コンパイラ/リンカ) の Emscripten 版で、-c は「コンパイル」の意。-o や -I は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
+ emcc は cc (C コンパイラ/リンカ) の Emscripten 版で、-c は「コンパイル」の意。 -o や -I は普通の C コンパイラと同様、出力ファイルの指定とインクルードパスの指定である。
-
- libphp.a と php-wasm.o が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。これにも emcc コマンドを使う。
+ libphp.a と php-wasm.o が手に入ったので、これらをリンクして WebAssembly のバイナリとそのラッパである JavaScript ファイルを生成する。 これにも emcc コマンドを使う。
-
RUN emcc \
-s ENVIRONMENT=node \
@@ -307,47 +273,36 @@
libphp.a \
;
-
- それぞれのフラグについて解説する。
+ それぞれのフラグについて解説する。
-
- -s ENVIRONMENT=node は、生成する WebAssembly/JavaScript の実行環境を指定する。今回は node を指定しているので、Node.js 向けのファイルが生成される。
+ -s ENVIRONMENT=node は、生成する WebAssembly/JavaScript の実行環境を指定する。 今回は node を指定しているので、Node.js 向けのファイルが生成される。
-
-s ERROR_ON_UNDEFINED_SYMBOLS=0 についてはすでに述べたので省略する。
-
- -s EXPORTED_RUNTIME_METHODS='["ccall"]' は、生成される JavaScript から公開される API である。すでに index.mjs で使用しているが、ccall('関数名', '返り値の型', ['仮引数の型', ...], ['実引数', ...]) のように使う。
+ -s EXPORTED_RUNTIME_METHODS='["ccall"]' は、生成される JavaScript から公開される API である。 すでに index.mjs で使用しているが、ccall('関数名', '返り値の型', ['仮引数の型', ...], ['実引数', ...]) のように使う。
-
- -s EXPORT_ES6=1 は、JavaScript コードを ECMAScript 6 に準拠した module として生成する。これを指定することで、require() ではなく import できる JavaScript を生成させられる。
+ -s EXPORT_ES6=1 は、JavaScript コードを ECMAScript 6 に準拠した module として生成する。 これを指定することで、require() ではなく import できる JavaScript を生成させられる。
-
-s INITIAL_MEMORY=16777216 は呼んで字のごとく。用途に合わせて適当に決めてほしい。
-
- -s INVOKE_RUN=0 は、module をロードしたときに勝手に main() を呼ぶかどうか (だと思う)。今回は php_wasm_run() しか使うつもりがないので切っている。
+ -s INVOKE_RUN=0 は、module をロードしたときに勝手に main() を呼ぶかどうか (だと思う)。 今回は php_wasm_run() しか使うつもりがないので切っている。
-
- -s MODULARIZE=1 は、実質的にほぼ必須のオプションであり、1 を指定することで「WebAssembly module をインスタンス化する関数」をエクスポートするような JavaScript ファイルを生成するようになる。これを指定しないと、生成物の JavaScript ファイルを読み込むと WebAssembly module が即座にインスタンス化されてしまい、起動のタイミングを制御できない。
+ -s MODULARIZE=1 は、実質的にほぼ必須のオプションであり、1 を指定することで「WebAssembly module をインスタンス化する関数」をエクスポートするような JavaScript ファイルを生成するようになる。 これを指定しないと、生成物の JavaScript ファイルを読み込むと WebAssembly module が即座にインスタンス化されてしまい、起動のタイミングを制御できない。
-
- ここまで実行すると、php-wasm.js と php-wasm.wasm が作られる。では、ここからはこれらの実行環境を作っていこう。
+ ここまで実行すると、php-wasm.js と php-wasm.wasm が作られる。 では、ここからはこれらの実行環境を作っていこう。
-
- といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。先ほど掲載した JavaScript のコードは、Dockerfile と同じディレクトリに index.mjs で配置すること。
+ といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。 先ほど掲載した JavaScript のコードは、Dockerfile と同じディレクトリに index.mjs で配置すること。
-
FROM node:20.7
@@ -360,13 +315,11 @@
-
-
+
Dockerfile、php-wasm.c、index.mjs を用意したら、Docker コンテナをビルドして実行する。
-
$ docker build -t php-wasm .
$ echo 'echo "Hello, World!", PHP_EOL;' | docker run --rm -i php-wasm
@@ -376,33 +329,27 @@
exit code: 0
-
-
-
+
- とあるきっかけがあり、もう一度大学生をすることにしました。仕事のほうも、これまでどおりフルタイムで続けていきます。
+ とあるきっかけがあり、もう一度大学生をすることにしました。 仕事のほうも、これまでどおりフルタイムで続けていきます。
-
- 黙っているよりも公表したほうがモチベーションの向上に繋がるだろうと思い、このブログに記事として載せました。
+ 黙っているよりも公表したほうがモチベーションの向上に繋がるだろうと思い、このブログに記事として載せました。
-
- 以上、短いですが報告でした。
+ 以上、短いですが報告でした。
diff --git a/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html b/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html
index 63f6e8f4..f2cc9533 100644
--- a/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html
+++ b/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html
@@ -58,39 +58,41 @@
-
+
- 先日 11月25日、ISUCON 13 に参加した。ISUCON への参加は今回が初めてとなる。私 nsfisis の1人チーム「うつしもゆ」として参加し、最終スコアは 13,580 点だった。使用言語は Go。
+ 先日 11月25日、 ISUCON 13 に参加した。 ISUCON への参加は今回が初めてとなる。 私 nsfisis の1人チーム「うつしもゆ」として参加し、最終スコアは 13,580 点だった。使用言語は Go。
-
-
-
+
- 今回は初参加ということもあり、目標を以下のように定めた。
+ 今回は初参加ということもあり、目標を以下のように定めた。
-
-
-
+
- ISUCON で高スコアを出す戦略については、戦闘力の高い方々が良質な記事を書いてくださっている。ここでは、上述したような低い目標を達成するための戦略について書こうと思う。
+ ISUCON で高スコアを出す戦略については、戦闘力の高い方々が良質な記事を書いてくださっている。 ここでは、上述したような低い目標を達成するための戦略について書こうと思う。
-
-
+
- ミドルウェアの設定やアプリケーションコードなど、変更を加えるあらゆるものは、必ずバックアップを取るか Git で管理する。復旧不能になって環境ごと作り直すことだけは必ず避ける。
+ ミドルウェアの設定やアプリケーションコードなど、変更を加えるあらゆるものは、必ずバックアップを取るか Git で管理する。 復旧不能になって環境ごと作り直すことだけは必ず避ける。
-
-
-
+
- 一度に複数の変更を加えず、可能な限り小さな単位でデプロイする。そしてその都度ベンチマークを走らせ、整合性チェックが通るかどうかを (当然速くなっているかどうかも) 確かめる。
+ 一度に複数の変更を加えず、可能な限り小さな単位でデプロイする。そしてその都度ベンチマークを走らせ、整合性チェックが通るかどうかを (当然速くなっているかどうかも) 確かめる。
-
-
-
-
+
- 事前の準備も含めて、大変楽しいイベントだった。次回があるなら是非また参加したい。その際は、順位やスコアを目標として立てられるようになりたいものである。
+ 事前の準備も含めて、大変楽しいイベントだった。次回があるなら是非また参加したい。その際は、順位やスコアを目標として立てられるようになりたいものである。
diff --git a/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html b/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html
index 355f11bd..535f4b6a 100644
--- a/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html
+++ b/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html
@@ -52,96 +52,85 @@
-
+
- 男もすなる年末の振り返りといふものを女もしてみむとてするなり。
+ 男もすなる年末の振り返りといふものを女もしてみむとてするなり。
-
-
+
- 勉強会やカンファレンスで登壇したりスタッフをしたりし始めたのは今年かららしい。LT 等も含めて計 11 回の登壇をおこなった。
+ 勉強会やカンファレンスで登壇したりスタッフをしたりし始めたのは今年かららしい。 LT 等も含めて計 11 回の登壇をおこなった。
-
-
-
+
- 登壇が増えたためか記事を書く機会が減ってしまった。特に社内記事の本数が大きく減少しており、一昨年は約 100 本、昨年は約 60 本の社内記事を書いていたが、今年は 30 本強に留まった。その頃と比べると文章を書く筋肉が衰えているように感じる。
+ 登壇が増えたためか記事を書く機会が減ってしまった。 特に社内記事の本数が大きく減少しており、一昨年は約 100 本、昨年は約 60 本の社内記事を書いていたが、今年は 30 本強に留まった。 その頃と比べると文章を書く筋肉が衰えているように感じる。
-
-
社外記事 (このブログ): 8本
-
-
- 社内記事: 34本
+
+ 社内記事: 34本
+
-
年間で最も記事を書いた人として社内表彰された
@@ -150,53 +139,44 @@
-
-
+
- ガラクタをいくつか作った。役には立たないが、作るのが楽しいという効用がある。
+ ガラクタをいくつか作った。役には立たないが、作るのが楽しいという効用がある。
-
-
-
-
+
- 今年も大変お世話になりました。よいお年を!
+ 今年も大変お世話になりました。よいお年を!
diff --git a/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html b/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html
index 07043627..68eb1fe9 100644
--- a/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html
+++ b/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html
@@ -65,58 +65,51 @@
NOTE
- この記事は
Vim 駅伝 #136 の記事です。
+
+ この記事は Vim 駅伝 #136 の記事です。
+
-
-
+
- Neovim で空の PHP ファイルを開いたとき、そのファイルが置かれているディレクトリの構造に基づいて、自動的に namespace 宣言を挿入したい。具体的には、トップレベルの名前空間が MyNamespace であり、ファイル src/Foo/Bar/Baz.php を開いたときに、そのファイルが空であるなら、次のようなテンプレートが自動的に挿入されてほしい。
+ Neovim で空の PHP ファイルを開いたとき、そのファイルが置かれているディレクトリの構造に基づいて、自動的に namespace 宣言を挿入したい。具体的には、トップレベルの名前空間が MyNamespace であり、ファイル src/Foo/Bar/Baz.php を開いたときに、そのファイルが空であるなら、次のようなテンプレートが自動的に挿入されてほしい。
-
<?php
namespace MyNamespace\Foo\Bar;
-
-
+
$ nvim --version
NVIM v0.9.2
Build type: Release
LuaJIT 2.1.1693350652
-
- 今回は Lua で処理を記述したため、Vim では動作しない。以下の説明でも Neovim に絞って述べる。また、パス区切りがスラッシュである前提で記述したため、Windows には対応していない。
+ 今回は Lua で処理を記述したため、Vim では動作しない。以下の説明でも Neovim に絞って述べる。 また、パス区切りがスラッシュである前提で記述したため、Windows には対応していない。
-
-
+
- Neovim には特定のファイルタイプに対して特別な処理をおこなうための ftplugin と呼ばれる仕組みがある。Neovim の設定を置くディレクトリ (例えば ~/.config/nvim) の配下に ftplugin/<FILE_TYPE>.vim または ftplugin/<FILE_TYPE>.lua というファイルを配置すると、その <FILE_TYPE> が読み込まれたときにそのファイルが自動的に実行される。
+ Neovim には特定のファイルタイプに対して特別な処理をおこなうための ftplugin と呼ばれる仕組みがある。 Neovim の設定を置くディレクトリ (例えば ~/.config/nvim) の配下に ftplugin/<FILE_TYPE>.vim または ftplugin/<FILE_TYPE>.lua というファイルを配置すると、その <FILE_TYPE> が読み込まれたときにそのファイルが自動的に実行される。
-
- 今回は、Neovim がデフォルトで用意している PHP 用 ftplugin が動作したあとに追加の処理をおこないたいので、after/ftplugin/php.{vim,lua} というファイルを配置する。名前から察せられるとおり、after/ftplugin 以下のファイルは ftplugin 以下のファイルよりもあとに実行される。
+ 今回は、Neovim がデフォルトで用意している PHP 用 ftplugin が動作したあとに追加の処理をおこないたいので、after/ftplugin/php.{vim,lua} というファイルを配置する。名前から察せられるとおり、after/ftplugin 以下のファイルは ftplugin 以下のファイルよりもあとに実行される。
-
- この記事では Lua で処理を記述するため、拡張子には .lua を用いる。これ以降載せるコードは、すべて after/ftplugin/php.lua の中に記述している。
+ この記事では Lua で処理を記述するため、拡張子には .lua を用いる。 これ以降載せるコードは、すべて after/ftplugin/php.lua の中に記述している。
-
-
+
- ファイルタイプは読み込んだあとに変更されることもあるので、ftplugin は複数回実行されうる。二重読み込みを防ぐために、did_ftplugin_<FILE_TYPE>_after というバッファローカル変数を定義しておくのが慣習となっている。
+ ファイルタイプは読み込んだあとに変更されることもあるので、ftplugin は複数回実行されうる。 二重読み込みを防ぐために、did_ftplugin_<FILE_TYPE>_after というバッファローカル変数を定義しておくのが慣習となっている。
-
if vim.b.did_ftplugin_php_after then
return
@@ -127,43 +120,34 @@
vim.b.did_ftplugin_php_after = true
-
-
+
- では実装していこう。今回私は次のようなロジックとした。以降、「今 Neovim で開いた PHP ファイル」のことを「対象ファイル」と呼ぶことにする。
+ では実装していこう。今回私は次のようなロジックとした。以降、「今 Neovim で開いた PHP ファイル」のことを「対象ファイル」と呼ぶことにする。
-
-
対象ファイルが空でなければ何もしない
-
-
対象ファイルが置かれたディレクトリを上に辿って、
composer.json を見つける
-
-
composer.json の autoload.psr-4 を見て、トップレベルの名前空間とディレクトリを特定する
-
-
対象ファイルが置かれたディレクトリが、トップレベルのディレクトリを基準としてどのようにネストしているか調べる
-
-
オートロードの設定と照らし合わせて、対象ファイルが属すべき名前空間を特定する
-
-
PHP の開始タグとともに
namespace 宣言を挿入する
-
- 実装を簡単にするため、Composer を用いない場合や PSR 4 以外のオートロード規則を使う場合には対応しない。少々長くなるが、以下にスクリプト全文を載せる。
+ 実装を簡単にするため、Composer を用いない場合や PSR 4 以外のオートロード規則を使う場合には対応しない。少々長くなるが、以下にスクリプト全文を載せる。
-
if vim.b.did_ftplugin_php_after then
return
@@ -280,11 +264,10 @@
vim.b.did_ftplugin_php_after = true
-
-
+
- 簡易的な実装だが、多くのケースではうまく動いているようだ。最大の問題は PSR 4 に準拠しないフレームワークを用いているとまったく役に立たないことで、今まさに職場で困っている。こちらはいずれ改良したい。
+ 簡易的な実装だが、多くのケースではうまく動いているようだ。 最大の問題は PSR 4 に準拠しないフレームワークを用いているとまったく役に立たないことで、今まさに職場で困っている。 こちらはいずれ改良したい。
diff --git a/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html b/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html
index f7a6f601..05ad3423 100644
--- a/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html
+++ b/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html
@@ -64,60 +64,49 @@
-
+
- 個人用サービスのセルフホストに使っているサーバに WireGuard を導入する作業をしたのでメモ。
+ 個人用サービスのセルフホストに使っているサーバに WireGuard を導入する作業をしたのでメモ。
-
- 登場するホストは以下のとおり:
+ 登場するホストは以下のとおり:
-
-
サーバ (Ubuntu):
10.10.1.1
-
-
クライアント 1 (Windows):
10.10.1.2
-
-
クライアント 2 (Android):
10.10.1.3
-
- 後ろの IP アドレスは VPN 内で使用するプライベート IP アドレス。
+ 後ろの IP アドレスは VPN 内で使用するプライベート IP アドレス。
-
-
+
- まずは個人用サービスをホストしている Ubuntu のサーバに WireGuard をインストールする。
+ まずは個人用サービスをホストしている Ubuntu のサーバに WireGuard をインストールする。
-
$ sudo apt install wireguard
-
- 次に、WireGuard で使用する鍵を生成する。
+ 次に、WireGuard で使用する鍵を生成する。
-
$ wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pub
$ sudo chmod 600 /etc/wireguard/server.{key,pub}
-
-
+
- 公式サイトから各 OS 向けのクライアントソフトウェアを入手し、インストールする。次に、設定をおこなう。
+ 公式サイトから各 OS 向けのクライアントソフトウェアを入手し、インストールする。次に、設定をおこなう。
-
# クライアント 1 の場合
[Interface]
@@ -129,7 +118,6 @@
AllowedIPs = <サーバの外部 IP アドレス>/32
Endpoint = <サーバの外部 IP アドレス>:51820
-
# クライアント 2 の場合
[Interface]
@@ -141,22 +129,18 @@
AllowedIPs = <サーバの外部 IP アドレス>/32
Endpoint = <サーバの外部 IP アドレス>:51820
-
PrivateKey や PublicKey は鍵ファイルのパスではなく中身を書くことに注意。
-
-
-
-
+
- これで、各クライアントで VPN を有効にすると、当該サーバの 80 ポートや 443 ポートにアクセスできるようになったはずだ。念のため VPN を切った状態でアクセスできないことも確認しておくとよいだろう。
+ これで、各クライアントで VPN を有効にすると、当該サーバの 80 ポートや 443 ポートにアクセスできるようになったはずだ。念のため VPN を切った状態でアクセスできないことも確認しておくとよいだろう。
-
-
+
- Let's Encrypt でサーバの証明書を取得している場合、80 番ポートを空けておく必要がある。気づかないうちに証明書が切れないよう注意。
+ Let’s Encrypt でサーバの証明書を取得している場合、80 番ポートを空けておく必要がある。気づかないうちに証明書が切れないよう注意。
diff --git a/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html b/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html
index 62debe04..8ed35d82 100644
--- a/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html
@@ -64,61 +64,59 @@
-
-
-
+
- 最高だった。特に、杜甫々氏の講演を生で拝聴できたのは、感慨とともに大いに刺激となった。次回の YAPC にも是非参加したい。
+ 最高だった。特に、杜甫々氏の講演を生で拝聴できたのは、感慨とともに大いに刺激となった。次回の YAPC にも是非参加したい。
-
- P.S. Perl を書いたことがなくとも十二分に楽しめるイベントなので、「Perl を書かない」という理由で参加しなかったかたは、次回是非参加を検討してみてほしい。
+ P.S. Perl を書いたことがなくとも十二分に楽しめるイベントなので、「Perl を書かない」という理由で参加しなかったかたは、次回是非参加を検討してみてほしい。
diff --git a/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html b/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html
index e47eef88..dbe5dde8 100644
--- a/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html
@@ -64,57 +64,56 @@
-
-
diff --git a/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html b/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html
index 80ec045e..bb00a6ce 100644
--- a/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html
@@ -67,117 +67,98 @@
-
+
- 2024-03-07 から 2024-03-09 にかけて開催された、PHPerKaigi 2024 に参加した。今年はスピーカーとして、また、コアスタッフとして参加した。
+ 2024-03-07 から 2024-03-09 にかけて開催された、 PHPerKaigi 2024 に参加した。 今年はスピーカーとして、また、コアスタッフとして参加した。
-
- 過去の参加レポはこちら:
+ 過去の参加レポはこちら:
-
-
-
+
- 昨年に続き、スピーカーとして登壇をおこなった。
+ 昨年に続き、スピーカーとして登壇をおこなった。
-
-
- WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebAssembly の特徴、言語処理系を作る楽しさについて語った。タイトルにある「WebAssembly を理解する」という目的が達成できるようなトークだったかと言われると疑問は残るものの、実際に作った人にしかできない話をすることはできたと思う。
+ WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebAssembly の特徴、言語処理系を作る楽しさについて語った。 タイトルにある「WebAssembly を理解する」という目的が達成できるようなトークだったかと言われると疑問は残るものの、実際に作った人にしかできない話をすることはできたと思う。
-
-
+
- 昨年は当日スタッフとして参加したが、今年はコアスタッフとして運営に参加した。今年はコードゴルフ企画を提案し、その準備とシステムの開発、当日の運用をおこなった。そのシステムは現在も下記の URL から閲覧でき、当日出題された問題や参加者の方々の回答が見られる。
+ 昨年は当日スタッフとして参加したが、今年はコアスタッフとして運営に参加した。 今年はコードゴルフ企画を提案し、その準備とシステムの開発、当日の運用をおこなった。 そのシステムは現在も下記の URL から閲覧でき、当日出題された問題や参加者の方々の回答が見られる。
-
Albatross.PHP
-
- システムの開発完了や問題の作成完了はスケジュールギリギリとなったのだが、当日はそこそこ安定して稼動していたのではないかと思う。
+ システムの開発完了や問題の作成完了はスケジュールギリギリとなったのだが、当日はそこそこ安定して稼動していたのではないかと思う。
-
-
+
-
+
- RubyVM を PHP で実装する〜Hello World を出力するまで〜 (めもりー さん)
+ RubyVM を PHP で実装する〜Hello World を出力するまで〜 (めもりー さん)
-
- 今回一番楽しみにしていたセッションであり、期待どおりの面白さだった。私も今回 VM を作るというテーマで登壇したこともあり、高い解像度で受け取ることができたように思う。
+ 今回一番楽しみにしていたセッションであり、期待どおりの面白さだった。 私も今回 VM を作るというテーマで登壇したこともあり、高い解像度で受け取ることができたように思う。
-
- P.S. Ask the Speaker で話した、Ruby VM (written in PHP) on PHP VM (compiled to Wasm) on Wasm VM (written in PHP) on PHP というアイデアは「マジ」なので、続報をお待ちください (自作 Wasm runtime に不足している機能を鋭意実装中です)。
+ P.S. Ask the Speaker で話した、Ruby VM (written in PHP) on PHP VM (compiled to Wasm) on Wasm VM (written in PHP) on PHP というアイデアは「マジ」なので、続報をお待ちください (自作 Wasm runtime に不足している機能を鋭意実装中です)。
-
- 2024-07-07 追記: コミット a312e95 で、ついに Ruby VM on PHP VM on Wasm VM on PHP を実現した。現時点での動かしかたは README に記載している。
+ 2024-07-07 追記: コミット a312e95 で、ついに Ruby VM on PHP VM on Wasm VM on PHP を実現した。現時点での動かしかたは README に記載している。
-
-
+
- 今年はスピーカーとスタッフともに開発を伴うものだったので (Wasm 処理系とコードゴルフシステム)、両者がぶつかった結果として準備段階は去年よりも大変になった。
+ 今年はスピーカーとスタッフともに開発を伴うものだったので (Wasm 処理系とコードゴルフシステム)、両者がぶつかった結果として準備段階は去年よりも大変になった。
-
ゴリゴリに開発しなければいけないセッションのスピーカーとゴリゴリに開発しなければいけない企画のスタッフを同じカンファレンスでやってはいけない
-
- ただ、それでもコアスタッフとして半年ほど関わっただけに、終わってみると感慨深い。例年どおり、お祭のような活気・熱気を感じることができた。
+ ただ、それでもコアスタッフとして半年ほど関わっただけに、終わってみると感慨深い。 例年どおり、お祭のような活気・熱気を感じることができた。
-
- 来月は、また登壇とスタッフ (こちらは当日スタッフ) をおこなう PHP カンファレンス小田原 があるので、良いトーク・良いカンファレンスを作れるようにしたい。
+ 来月は、また登壇とスタッフ (こちらは当日スタッフ) をおこなう PHP カンファレンス小田原 があるので、良いトーク・良いカンファレンスを作れるようにしたい。
-
- さて、参加レポは例年この言葉で締め括っているので、今年もそれで終わろうと思う。
+ さて、参加レポは例年この言葉で締め括っているので、今年もそれで終わろうと思う。
-
- ではまた来年。
+ ではまた来年。
diff --git a/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html b/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html
index 2b874bf3..68109153 100644
--- a/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html
+++ b/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html
@@ -55,89 +55,69 @@
- これは眠れない夜にノートへ書き散らした文をなんとか文章の体裁に直したものであり、およそ論理と呼べるものを期待してはならぬ。
+ これは眠れない夜にノートへ書き散らした文をなんとか文章の体裁に直したものであり、およそ論理と呼べるものを期待してはならぬ。
-
- Knuth 曰くプログラミングは文芸である。断っておくが、労役に伴うプロダクティブでプラクティカルな行為を指してそう言っているのではない (Knuth がどう考えているかは知らないが、少なくとも私にとっては)。いわゆる趣味プログラミング、穢れなき自由意志の下で記述されるプログラムとはすなわち、絵描きにとっての絵、文字書きにとっての文章に等しい。プログラムとは、ソースコードとは、芸術作品の一形態なのである。
+ Knuth 曰くプログラミングは文芸である。断っておくが、労役に伴うプロダクティブでプラクティカルな行為を指してそう言っているのではない (Knuth がどう考えているかは知らないが、少なくとも私にとっては)。いわゆる趣味プログラミング、穢れなき自由意志の下で記述されるプログラムとはすなわち、絵描きにとっての絵、文字書きにとっての文章に等しい。プログラムとは、ソースコードとは、芸術作品の一形態なのである。
-
- この人生でプログラミングという行為に魅せられたからには、美しい作品を遺さねばならぬ。すなわち、簡潔で、理解しやすく、凝縮され、機能的で、速く、軽く、よい名前を持ち、うまく動くものをだ。
+ この人生でプログラミングという行為に魅せられたからには、美しい作品を遺さねばならぬ。すなわち、簡潔で、理解しやすく、凝縮され、機能的で、速く、軽く、よい名前を持ち、うまく動くものをだ。
-
- 何を作りたいかは各々異なるであろうが、私にとっては車輪の再発明として知られる自作○○である。
+ 何を作りたいかは各々異なるであろうが、私にとっては車輪の再発明として知られる自作○○である。
-
- 車輪の再発明を恐れてはいけない。これを批判する人間というのは、プロダクティビティやプラクティカリティにフォーカスするエンジニアという人種である。今私が表現者たろうとするなら、自らの手で自らの車輪を作ることに何の恐れを抱く必要があろうか。
+ 車輪の再発明を恐れてはいけない。これを批判する人間というのは、プロダクティビティやプラクティカリティにフォーカスするエンジニアという人種である。今私が表現者たろうとするなら、自らの手で自らの車輪を作ることに何の恐れを抱く必要があろうか。
-
- そう、これが私の死ぬまでに作る自作○○一覧あるいは人生の TODO リストである (現時点ですでに部分的あるいは全面的に達成しているものを含む)。
+ そう、これが私の死ぬまでに作る自作○○一覧あるいは人生の TODO リストである (現時点ですでに部分的あるいは全面的に達成しているものを含む)。
-
-
自作 C コンパイラ
-
-
自作アセンブラ
-
-
自作リンカ
-
-
自作 Scheme 処理系
-
-
自作 ML コンパイラ
-
-
自作 Lua 処理系
-
-
自作 JVM
-
-
自作 Wasm 処理系
-
-
自作正規表現エンジン
-
-
自作 JavaScript 処理系
-
-
自作ブラウザ
-
-
自作エディタ
-
-
自作 ActivityPub 実装
-
- 選定理由は作りたいということのほかにない。そこに題材とキャンバスがあり絵筆と絵具があれば、生きとし生けるもの、いづれかコードを書かざりける。
+ 選定理由は作りたいということのほかにない。そこに題材とキャンバスがあり絵筆と絵具があれば、生きとし生けるもの、いづれかコードを書かざりける。
-
- おお、願わくは、私にこれらを生み出すだけの時間があらんことを。
+ おお、願わくは、私にこれらを生み出すだけの時間があらんことを。
diff --git a/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html b/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html
index 92983841..0c8dda5c 100644
--- a/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html
@@ -67,101 +67,93 @@
-
-
+
- PHP 処理系の JIT コンパイルにおける PHP 8.4 での変更について、登壇をおこなった。
+ PHP 処理系の JIT コンパイルにおける PHP 8.4 での変更について、登壇をおこなった。
-
-
- 今回、どこから話を始めるか大いに迷ったのだが、最終的には PHP 処理系の opcode や VM といった概念は既知のものとし、そこから JIT コンパイルへ繋げるといった構成にした。
+ 今回、どこから話を始めるか大いに迷ったのだが、最終的には PHP 処理系の opcode や VM といった概念は既知のものとし、そこから JIT コンパイルへ繋げるといった構成にした。
-
- PHP の処理系がスクリプトを opcode へ変換する過程については、ちょうど同じカンファレンスのめもりーさんの発表あたりを参考にしていただくとよいだろう。また、新しい IR についてより詳しく知りたいという方は、スライド末尾の「参考資料」にあるリンクを参照いただくのがよいかと思う。
+ PHP の処理系がスクリプトを opcode へ変換する過程については、ちょうど同じカンファレンスの めもりーさんの発表 あたりを参考にしていただくとよいだろう。 また、新しい IR についてより詳しく知りたいという方は、スライド末尾の「参考資料」にあるリンクを参照いただくのがよいかと思う。
-
- Tracing JIT の発火条件や、IR を使って実現される最適化方法など、調べたものの発表に入らなかった話がごまんとあるので、これもどこかに持っていければと考えている。
+ Tracing JIT の発火条件や、IR を使って実現される最適化方法など、調べたものの発表に入らなかった話がごまんとあるので、これもどこかに持っていければと考えている。
-
-
+
- 当日スタッフとして前日の準備と当日の運営をおこなった。今回はモノの移動が比較的 (比較対象: PHPerKaigi) 少なく、体力にはかなり余裕があった。
+ 当日スタッフとして前日の準備と当日の運営をおこなった。今回はモノの移動が比較的 (比較対象: PHPerKaigi ) 少なく、体力にはかなり余裕があった。
-
- 自分の担当範囲内では、一度タイムキーパー係のときに時間を思いきり間違えた以外は、スムーズに進められたかと思う。
+ 自分の担当範囲内では、一度タイムキーパー係のときに時間を思いきり間違えた以外は、スムーズに進められたかと思う。
-
- また、これはコアスタッフの方々のおかげだろうが、初開催としては大きなトラブルなく終わったと言えるのではないだろうか。
+ また、これはコアスタッフの方々のおかげだろうが、初開催としては大きなトラブルなく終わったと言えるのではないだろうか。
-
-
+
- 発表タイトルと発表者名は fortee より引用
+ 発表タイトルと発表者名は fortee より引用
-
-
- FigmaとPHPで作る、1ミリたりとも表示崩れしない最強の帳票印刷ソリューション (たつきち さん)
+
+ FigmaとPHPで作る、1ミリたりとも表示崩れしない最強の帳票印刷ソリューション (たつきち さん)
+
-
-
- PHPの次期バージョンはこの時期どうなっているのか、Internalsの開発体制について (てきめん さん)
+
+ PHPの次期バージョンはこの時期どうなっているのか、Internalsの開発体制について (てきめん さん)
+
-
-
- Architecture Decision Record を一年運用してみた (富所 亮 さん)
+
+ Architecture Decision Record を一年運用してみた (富所 亮 さん)
+
-
-
+
- 怒涛の月刊 PHP カンファレンスも折り返しとなったが、まだまだ新鮮に楽しい。
+ 怒涛の月刊 PHP カンファレンスも折り返しとなったが、まだまだ新鮮に楽しい。
-
- また今度、カンファレンスで会いましょう (震源地がよくわかっていないのだけれど、575 が流行っているらしい)。
+ また今度、カンファレンスで会いましょう (震源地がよくわかっていないのだけれど、575 が流行っているらしい)。
diff --git a/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html b/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html
index 3e41bbf7..197c6074 100644
--- a/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html
+++ b/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html
@@ -68,27 +68,25 @@
NOTE
- この記事は、2022-11-17 に
デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
+ この記事は、2022-11-17 に デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
-
- ハマったのでメモ。
+ ハマったのでメモ。
-
-
+
-
+
- GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
+ GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
-
- 例:
+ 例:
-
-
+
hello-world:
stage: test
image: alpine:latest
@@ -98,16 +96,13 @@
- if: '$CI_MERGE_REQUEST_IID'
when: always
-
- ここで、script に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
+ ここで、script に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
-
- では、次のようなケースだとどうなるか。
+ では、次のようなケースだとどうなるか。
-
-
+
hello-world:
stage: test
image: alpine:latest
@@ -117,38 +112,32 @@
- if: '$CI_MERGE_REQUEST_IID'
when: always
-
- 失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
+ 失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
-
-
+
- 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。
+ 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。
-
# On にする
set -o pipefail
# Off にする
set +o pipefail
-
- こうすると、パイプ全体が失敗するようになる。この設定は、デフォルトだと off になっている。
+ こうすると、パイプ全体が失敗するようになる。 この設定は、デフォルトだと off になっている。
-
-
+
- 次のような GitLab CI/CD ジョブが失敗してしまった。
+ 次のような GitLab CI/CD ジョブが失敗してしまった。
-
-
+
hoge:
stage: test
image: alpine:latest
@@ -158,15 +147,12 @@
- if: '$CI_MERGE_REQUEST_IID'
when: always
-
- grep コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、pipefail が on になっていると、このジョブは失敗する。現在の pipefail がどうなっているか確かめるため set +o で全オプションを出力させたところ、pipefail が on になっていた。
+ grep コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、pipefail が on になっていると、このジョブは失敗する。 現在の pipefail がどうなっているか確かめるため set +o で全オプションを出力させたところ、pipefail が on になっていた。
-
- しかし、先述したように Bash における pipefail のデフォルト値は off のはずだ。実際に、ローカルで alpine:latest を動かしてみたところ、
+ しかし、先述したように Bash における pipefail のデフォルト値は off のはずだ。 実際に、ローカルで alpine:latest を動かしてみたところ、
-
$ docker run --rm alpine:latest sh -c "set +o"
set +o errexit
@@ -183,36 +169,30 @@
set +o vi
set +o pipefail
-
- 確かに pipefail は無効になっている。
+ 確かに pipefail は無効になっている。
-
- なぜスクリプト内で set -o pipefail しているわけでもないのに pipefail が on になっているのか。
+ なぜスクリプト内で set -o pipefail しているわけでもないのに pipefail が on になっているのか。
-
-
+
- .gitlab-ci.yml で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。そう仮説を立てて GitLab Runner のリポジトリ を調査したところ、ソースコード中の以下の箇所 で set -o pipefail していることが判明した (コメントは筆者による)。
+ .gitlab-ci.yml で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。 そう仮説を立てて GitLab Runner のリポジトリ を調査したところ、 ソースコード中の以下の箇所 で set -o pipefail していることが判明した (コメントは筆者による)。
-
// pipefail オプションが存在しない環境にも対応するため、
// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
-
-
+
- 通常の Bash スクリプトを書く場合と同様に、pipefail が on になっていては困る場所だけ off にしてやればよい。
+ 通常の Bash スクリプトを書く場合と同様に、pipefail が on になっていては困る場所だけ off にしてやればよい。
-
-
+
hoge:
stage: test
image: alpine:latest
@@ -225,11 +205,10 @@
when: always
-
diff --git a/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html b/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html
index c1390ced..7cd16d21 100644
--- a/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html
+++ b/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html
@@ -64,39 +64,33 @@
-
+
-
Composer: 2.7.4
-
-
PHP: 8.3.6
-
-
Zsh: 5.9
-
-
-
+
- composer コマンドはカスタムコマンド (composer.json の scripts で定義されたコマンド) に対して補完をおこなわない。つまり、途中まで入力されたカスタムコマンドを補完しないし、カスタムコマンドの引数も補完しない。例えば、PHPUnit を呼び出す phpunit というカスタムコマンドを定義し composer phpu まで打ってタブキーを押しても、composer phpunit にはならない。また、composer phpunit -- -- まで打ってタブキーを押しても、phpunit コマンドのオプションは補完されない。
+ composer コマンドはカスタムコマンド (composer.json の scripts で定義されたコマンド) に対して補完をおこなわない。 つまり、途中まで入力されたカスタムコマンドを補完しないし、カスタムコマンドの引数も補完しない。 例えば、PHPUnit を呼び出す phpunit というカスタムコマンドを定義し composer phpu まで打ってタブキーを押しても、composer phpunit にはならない。 また、composer phpunit -- -- まで打ってタブキーを押しても、phpunit コマンドのオプションは補完されない。
-
- このことは、先ほどリンクを載せた _composer 関数を定義しているファイルの冒頭にも書かれている。
+ このことは、先ほどリンクを載せた _composer 関数を定義しているファイルの冒頭にも書かれている。
-
# - @todo We don't complete custom commands (including script aliases). This is
# easy to do in the general case, but it probably requires some clever caching
@@ -105,51 +99,42 @@
# commands requires making slow calls to Composer
-
-
+
- 確かに、カスタムコマンドに対して完全な補完を提供するのは不可能か、あるいは実現できても遅くなりすぎるだろう。しかし、不完全なフォールバックを提供するくらいなら可能なはずだ。
+ 確かに、カスタムコマンドに対して完全な補完を提供するのは不可能か、あるいは実現できても遅くなりすぎるだろう。 しかし、不完全なフォールバックを提供するくらいなら可能なはずだ。
-
- この記事では、これらのカスタムコマンドについて、Zsh が提供するデフォルトのファイル・ディレクトリ補完を適用する。つまり、composer phpunit -- tests/ まで打ってタブキーを押すと、tests ディレクトリの下にあるテストファイルまたはディレクトリが補完される。
+ この記事では、これらのカスタムコマンドについて、Zsh が提供するデフォルトのファイル・ディレクトリ補完を適用する。 つまり、composer phpunit -- tests/ まで打ってタブキーを押すと、tests ディレクトリの下にあるテストファイルまたはディレクトリが補完される。
-
-
+
- まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。以下は ~/.zshrc にすべて書く前提だが、autoload を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
+ まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。 以下は ~/.zshrc にすべて書く前提だが、autoload を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
-
compdef _my_composer composer composer.phar
-
- compdef は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。この場合は、composer コマンドや composer.phar コマンドに対して _my_composer を使って補完をおこなうよう定義している。
+ compdef は Zsh が用意している関数で、第一引数に補完関数の名前、第二引数以降に補完を適用するコマンド名を並べる。 この場合は、composer コマンドや composer.phar コマンドに対して _my_composer を使って補完をおこなうよう定義している。
-
- 次に _my_composer を定義する。基本的にはデフォルトの composer コマンドの補完関数 (つまり _composer 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。
+ 次に _my_composer を定義する。基本的にはデフォルトの composer コマンドの補完関数 (つまり _composer 関数) を使い、それが何も返さなかった場合に限り、Zsh のファイル・ディレクトリ補完へフォールバックする。
-
function _my_composer() {
_composer "$@" || _files "$@"
}
-
- _composer コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら _files を呼び出す。_files は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
+ _composer コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら _files を呼び出す。 _files は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
-
-
+
- これらの設定をおこなうことで、部分的ながら Composer のカスタムコマンドに対して補完をおこなうことができる。特に、PHPUnit や PHPStan などの対象ファイル・ディレクトリを引数に取るようなコマンドを使う場合に有用であろう。
+ これらの設定をおこなうことで、部分的ながら Composer のカスタムコマンドに対して補完をおこなうことができる。 特に、PHPUnit や PHPStan などの対象ファイル・ディレクトリを引数に取るようなコマンドを使う場合に有用であろう。
diff --git a/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html b/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html
index 1e9634dd..a9bb4710 100644
--- a/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-05-11/phpconkagawa-2024-report/index.html
@@ -64,109 +64,103 @@
-
-
+
-
- 泥まみれの技術革新: あなたの[ PHPバージョンアップ | 新フレームワーク採用 | アーキテクチャ刷新 | … ]を後押しするために by nrslib
+
+ 泥まみれの技術革新: あなたの[ PHPバージョンアップ | 新フレームワーク採用 | アーキテクチャ刷新 | … ]を後押しするために by nrslib
+
-
-
- PHP 9 に備えよ - 動的プロパティ、どうすればいぃ? by 荒瀬 泰輔
+
+ PHP 9 に備えよ - 動的プロパティ、どうすればいぃ? by 荒瀬 泰輔
+
-
-
- 1人プロ・ペアプロ・モブプロの効果的な使い分け by まきまき
+
+ 1人プロ・ペアプロ・モブプロの効果的な使い分け by まきまき
+
-
-
- mb_trim関数を作りました - PHPに新しい関数を追加しました - by てきめん
+
+ mb_trim関数を作りました - PHPに新しい関数を追加しました - by てきめん
+
-
-
- (「PHPカンファレンス小田原2024」を実行委員長がふりかえる by asumikam)
+
+ (「PHPカンファレンス小田原2024」を実行委員長がふりかえる by asumikam)
+
-
-
+
- 今回登壇者ではなかったのだが、プロポーザル募集時に用意していたスライド (LT 用に作っていたのだが、そもそも LT 枠がなかったのでお蔵入りになっていた) があったので懇親会の LT で発表した。
+ 今回登壇者ではなかったのだが、プロポーザル募集時に用意していたスライド (LT 用に作っていたのだが、そもそも LT 枠がなかったのでお蔵入りになっていた) があったので懇親会の LT で発表した。
-
- 中身は第150回PHP勉強会@東京で登壇した内容とほぼ同じで、タイトルを「うどんのように細長い FizzBuzz を書く」にしただけの手抜き・一発ネタ発表である。個別にスライドはアップロードしないので、前述のリンクを参照してほしい。
+ 中身は 第150回PHP勉強会@東京で登壇した内容 とほぼ同じで、タイトルを「うどんのように細長い FizzBuzz を書く」にしただけの手抜き・一発ネタ発表である。個別にスライドはアップロードしないので、前述のリンクを参照してほしい。
-
- なお、この発表にはブログ記事バージョンもある。
+ なお、この発表には ブログ記事バージョン もある。
-
-
+
- 午前中の発表に間に合わなかったことがとにかく心残りなのだが、それ以外は PHP カンファレンス小田原のスタッフの方々をはじめ多くの方と交流でき、非常に楽しいカンファレンスだった。来年もあるそうなので (この分だと来年も月刊 PHP カンファレンスにならないか?)、是非参加したい。
+ 午前中の発表に間に合わなかったことがとにかく心残りなのだが、それ以外は PHP カンファレンス小田原のスタッフの方々をはじめ多くの方と交流でき、非常に楽しいカンファレンスだった。来年もあるそうなので (この分だと来年も月刊 PHP カンファレンスにならないか?)、是非参加したい。
-
- あれ、そういえば香川でうどん食べてないな......。
+ あれ、そういえば香川でうどん食べてないな……。
diff --git a/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html b/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html
index 5e3263bf..906051cc 100644
--- a/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html
+++ b/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html
@@ -64,60 +64,60 @@
-
+
- 2024-06-08 から 2024-06-09 にかけて開催された ScalaMatsuri 2024 に参加した。
+ 2024-06-08 から 2024-06-09 にかけて開催された ScalaMatsuri 2024 に参加した。
-
- Day 2 には当日参加できなかったため、day 2 のセッションの感想は YouTube にアップロードされたアーカイブ動画を観て書いている。
+ Day 2 には当日参加できなかったため、day 2 のセッションの感想は YouTube にアップロードされたアーカイブ動画を観て書いている。
-
-
-
+
-
良い会場だった。よく取り沙汰されるスライドの文字サイズの問題は、巨大なスクリーンを用意することで解決するという発見があった
-
-
- ランチにお弁当が用意されており、おいしかった (参考画像)
+ ランチにお弁当が用意されており、おいしかった ( 参考画像 )
-
-
+
- 私が Scala を書いたり追ったりしていたのは Scala 2 の頃で、Scala 3 はほとんど浦島太郎状態だったのだが、非常に楽しく面白いイベントだった。イベントに触発されて、長らく塩漬けになっていた Scala 製の趣味プロジェクトを久しぶりに触っているのだが、これもまた楽しい。
+ 私が Scala を書いたり追ったりしていたのは Scala 2 の頃で、Scala 3 はほとんど浦島太郎状態だったのだが、非常に楽しく面白いイベントだった。 イベントに触発されて、長らく塩漬けになっていた Scala 製の趣味プロジェクトを久しぶりに触っているのだが、これもまた楽しい。
-
- ScalaMatsuri 運営の皆さま、スピーカーの皆さま、スポンサーの皆さま、最高のイベントをありがとうございました!次回も楽しみにしています。
+ ScalaMatsuri 運営の皆さま、スピーカーの皆さま、スポンサーの皆さま、最高のイベントをありがとうございました!次回も楽しみにしています。
diff --git a/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html b/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html
index 6fb4f0d0..05050332 100644
--- a/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html
+++ b/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html
@@ -65,31 +65,28 @@
NOTE
- この記事は
Vim 駅伝 #218 の記事です。
+
+ この記事は Vim 駅伝 #218 の記事です。
+
-
-
+
- Vim で JSON を編集しているときに、文法エラー (末尾カンマやカンマの不足) のみを修正して一切の整形をおこなわないプラグインが欲しかった。整形も同時におこなうプラグインは見つかっただけでも多数あったのだが、整形しないものは見つけられなかったので自作することにした。
+ Vim で JSON を編集しているときに、文法エラー (末尾カンマやカンマの不足) のみを修正して一切の整形をおこなわないプラグインが欲しかった。 整形も同時におこなうプラグインは見つかっただけでも多数あったのだが、整形しないものは見つけられなかったので自作することにした。
-
- なお、作成したツール自体は単体の CLI として動作し、Vim とは無関係に使うことができる。この記事では Neovim と組み合わせる場合の設定を紹介するが、およそ任意のエディタで使えるだろう。
+ なお、作成したツール自体は単体の CLI として動作し、Vim とは無関係に使うことができる。 この記事では Neovim と組み合わせる場合の設定を紹介するが、およそ任意のエディタで使えるだろう。
-
-
+
- 作成したものがこちら: ReparoJSON
+ 作成したものがこちら: ReparoJSON
-
- 次のように動作する。
+ 次のように動作する。
-
$ echo '[ 1 2 ]' | reparojson
[ 1, 2 ]
@@ -103,116 +100,99 @@
$ echo '{ "foo": 1, "bar": 2, }' | reparojson
{ "foo": 1, "bar": 2 }
-
- バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり:
+ バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり:
-
-
配列末尾の余計なカンマ (削除する)
-
-
配列内のカンマ不足 (挿入する)
-
-
オブジェクト末尾の余計なカンマ (削除する)
-
-
オブジェクト内のカンマ不足 (挿入する)
-
- 他にも自動で直せそうなエラーはいくつか思いつくが (オブジェクトのキーがクォートされていない等)、私自身があまり困っていないので優先度は低い。
+ 他にも自動で直せそうなエラーはいくつか思いつくが (オブジェクトのキーがクォートされていない等)、私自身があまり困っていないので優先度は低い。
-
-
+
- Neovim で JSON ファイルを保存したときに、上記のツールを自動で走らせるように設定する。
+ Neovim で JSON ファイルを保存したときに、上記のツールを自動で走らせるように設定する。
-
- ここでは、nvim-lspconfig と efm-langserver を用いた設定例を紹介する。
+ ここでは、 nvim-lspconfig と efm-langserver を用いた設定例を紹介する。
-
-
local lspconfig = require('lspconfig')
+ local lspconfig = require('lspconfig')
-lspconfig.efm.setup({
- init_options = { documentFormatting = true },
- settings = {
- rootMarkers = {".git/"},
- languages = {
- json = {
- {
- formatCommand = "reparojson -q",
- formatStdin = true,
+ lspconfig.efm.setup({
+ init_options = { documentFormatting = true },
+ settings = {
+ rootMarkers = {".git/"},
+ languages = {
+ json = {
+ {
+ formatCommand = "reparojson -q",
+ formatStdin = true,
+ },
},
},
- },
- }
-})
+ }
+ })
-vim.api.nvim_create_autocmd('LspAttach', {
- callback = function(e)
- vim.api.nvim_create_autocmd('BufWritePre', {
- buffer = e.buf,
- callback = function()
- vim.lsp.buf.format({ async = false })
- end
- })
- end,
-})
+ vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(e)
+ vim.api.nvim_create_autocmd('BufWritePre', {
+ buffer = e.buf,
+ callback = function()
+ vim.lsp.buf.format({ async = false })
+ end
+ })
+ end,
+ })
-
- ほとんどは nvim-lspconfig と efm-langserver を使う際のボイラープレートだが、formatCommand で -q フラグを指定していることに注意してほしい。このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。そのため、-q フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
+ ほとんどは nvim-lspconfig と efm-langserver を使う際のボイラープレートだが、formatCommand で -q フラグを指定していることに注意してほしい。 このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。 これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。 そのため、-q フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
-
-
+
- このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、
+ このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、
-
-
{
- "a": true,
- "b": false
-}
+
{
+ "a": true,
+ "b": false
+ }
-
- 2行目と3行目を入れ換えて以下のように編集した。
+ 2行目と3行目を入れ換えて以下のように編集した。
-
-
{
- "b": false
- "a": true,
-}
+
{
+ "b": false
+ "a": true,
+ }
-
- これは不正な JSON だが、このツールを通せば次のようになる。
+ これは不正な JSON だが、このツールを通せば次のようになる。
-
-
{
- "b": false,
- "a": true
-}
+
{
+ "b": false,
+ "a": true
+ }
-
- もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。しかし、単なる行の入れ換えであれば ddp の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。
+ もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。 しかし、単なる行の入れ換えであれば ddp の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。 このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。
diff --git a/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html b/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
index aa6d2afb..2c7cae67 100644
--- a/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
+++ b/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
@@ -58,18 +58,16 @@
-
+
- 常にトップレベルを指す特殊変数 $ を使えばよい。
+ 常にトップレベルを指す特殊変数 $ を使えばよい。
-
-
+
- Go には、標準ライブラリにテンプレートライブラリ text/template がある。この text/template における制御構造、with と range は次のように使われる。
+ Go には、標準ライブラリにテンプレートライブラリ text/template がある。 この text/template における制御構造、with と range は次のように使われる。
-
# {{ .Title }}
@@ -85,19 +83,15 @@
- {{ . }}
{{ end }}
-
text/template の . は、現在の操作対象を表す特殊なオブジェクトである。
-
- with や range は、. を変更する効果を持つ。with は引数に渡されたオブジェクトを . へセットして、内部のテンプレートを実行する。range は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を . へセットして、要素の個数だけ内部のテンプレートを実行する。
+ with や range は、. を変更する効果を持つ。 with は引数に渡されたオブジェクトを . へセットして、内部のテンプレートを実行する。 range は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を . へセットして、要素の個数だけ内部のテンプレートを実行する。
-
- つまりこのテンプレートは、次のような構造をレンダリングしている (Execute() の第2引数)。
+ つまりこのテンプレートは、次のような構造をレンダリングしている (Execute() の第2引数)。
-
tmpl.Execute(out, Params{
Title: "foo",
@@ -113,13 +107,11 @@
})
-
-
+
- 今回おこないたいのは、with や range の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
+ 今回おこないたいのは、with や range の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
-
{{ with .User }}
ここから .Title を参照するには?
@@ -129,34 +121,27 @@
ここから .User を参照するには?
{{ end }}
-
- with や range は、. を自身の対象オブジェクトに変更するので、単に {{ with .User }} の中で .Title と書いても、それは User の Title プロパティを参照しているとみなされる。
+ with や range は、. を自身の対象オブジェクトに変更するので、 単に {{ with .User }} の中で .Title と書いても、それは User の Title プロパティを参照しているとみなされる。
-
- text/template では変数が使えるので、テンプレートの先頭で
+ text/template では変数が使えるので、テンプレートの先頭で
-
-
- とでもしておけば実現は可能である。
+ とでもしておけば実現は可能である。
-
- しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
+ しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
-
-
+
- 常にトップレベルを指す特殊変数 $ を使えばよい。
+ 常にトップレベルを指す特殊変数 $ を使えばよい。
-
{{ with .User }}
{{ $.Title }}
@@ -166,27 +151,24 @@
{{ $.User.Name }}
{{ end }}
-
- $ は、テンプレートが実行されるときに渡されたオブジェクトを指す。これを使えば現在の . に関係なくトップレベルを参照できる。
+ $ は、テンプレートが実行されるときに渡されたオブジェクトを指す。 これを使えば現在の . に関係なくトップレベルを参照できる。
-
- このことは、text/template の公式ドキュメントにも以下のように記載されている。
+ このことは、text/template の公式ドキュメントにも以下のように記載されている。
-
- When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
+
+ When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
+
-
-
+
-
- 直接の出典である Stack Overflow の回答: "In a template how do you access an outer scope while inside of a "with" or "range" scope?"
+ 直接の出典である Stack Overflow の回答: In a template how do you access an outer scope while inside of a “with” or “range” scope?
-
-
大元の出典である
text/template の公式ドキュメント
diff --git a/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html b/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html
index bb998a4b..d1eed50f 100644
--- a/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html
+++ b/vhosts/blog/public/posts/2024-09-28/mncore-challenge-1/index.html
@@ -62,40 +62,36 @@
NOTE
- ただの参加記で解説はない。
+
+ ただの参加記で解説はない。
+
-
-
-
+
- MN-Core には初めて触れたが、それでも問題なく全問 (除 FizzBuzz) 解けるよう線路が敷かれており、前半の問題を解くことで自然と後半を解くだけの知識が身に付くように設計されていた。
+ MN-Core には初めて触れたが、それでも問題なく全問 (除 FizzBuzz) 解けるよう線路が敷かれており、前半の問題を解くことで自然と後半を解くだけの知識が身に付くように設計されていた。
-
- 開催期間中はほぼ常に MN-Core Challenge のことを考え続けており、期間中 (前掲した回答を貯めるためのリポジトリを除き) 自分の Git リポジトリをほとんど触ることがなかった。途中更新ができずに苦しい時間もあったが、一つ気付くと一つ縮まる楽しいゴルフだった。
+ 開催期間中はほぼ常に MN-Core Challenge のことを考え続けており、期間中 (前掲した回答を貯めるためのリポジトリを除き) 自分の Git リポジトリをほとんど触ることがなかった。途中更新ができずに苦しい時間もあったが、一つ気付くと一つ縮まる楽しいゴルフだった。
-
- 悔しいポイントも多数あるのだが、書いているとキリがないので自分で反省するだけにしておく。
+ 悔しいポイントも多数あるのだが、書いているとキリがないので自分で反省するだけにしておく。
-
-
+
- 最後になりましたが、運営のみなさま、素晴しいコンテストをありがとうございました!非常に楽しい時間でした!第2回を首を長くして待っています!
+ 最後になりましたが、運営のみなさま、素晴しいコンテストをありがとうございました!非常に楽しい時間でした!第2回を首を長くして待っています!
diff --git a/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html b/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html
index 97f8e6b3..516b0c77 100644
--- a/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html
+++ b/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html
@@ -64,87 +64,73 @@
-
+
- 2024-11-30 に開催された紅白ぺぱ合戦なる催しに参加しました。私は「ぺ」陣営のメンバとして LT をおこないました。
+ 2024-11-30 に開催された 紅白ぺぱ合戦 なる催しに参加しました。私は「ぺ」陣営のメンバとして LT をおこないました。
-
- 紅白ぺぱ合戦のイベントページにある説明を以下に引用します。
+ 紅白ぺぱ合戦のイベントページにある説明を以下に引用します。
-
- WebエンジニアのasumikamとWebエンジニアのstefafafanが2024年7月7日に結婚しました。
+ Webエンジニアの asumikam とWebエンジニアの stefafafan が2024年7月7日に結婚しました。
-
- せっかくなので技術トークとかで紅白戦をしませんか?いいですね!やっていきます!
+ せっかくなので技術トークとかで紅白戦をしませんか?いいですね!やっていきます!
-
- 場所はァ!小田原ァ!盛り上がっていきましょゥ!!!
+ 場所はァ!小田原ァ!盛り上がっていきましょゥ!!!
-
- ざっくりと言えば、テックカンファレンスの形式をとった結婚披露宴です。タイトルの「ぺ」は PHPer、「ぱ」は Perl Monger の略です。
+ ざっくりと言えば、テックカンファレンスの形式をとった結婚披露宴です。タイトルの「ぺ」は PHPer、「ぱ」は Perl Monger の略です。
-
-
+
- 私は「ぺ」陣営のスピーカーとして LT をしていたのですが、その前にまずは登壇以外の感想を。
+ 私は「ぺ」陣営のスピーカーとして LT をしていたのですが、その前にまずは登壇以外の感想を。
-
- いや~最高でしたね。どの枠のスピーチの方も良かったのですが、特に (asumikam さん/stefafafan さんに)「育てられた」枠のお二方が印象に残っています。(asumikam さん/stefafafan さんを)「育てた」枠としてお世話になった方に声をかけることはできると思うんですよ。それだけでなく、「自分が育てたのだ」と言える人がいて、そしてそれに 100 点で応える人がいるということ。この素晴しさ。人徳。
+ いや~最高でしたね。どの枠のスピーチの方も良かったのですが、特に (asumikam さん/stefafafan さんに)「育てられた」枠のお二方が印象に残っています。 (asumikam さん/stefafafan さんを)「育てた」枠としてお世話になった方に声をかけることはできると思うんですよ。 それだけでなく、「自分が育てたのだ」と言える人がいて、そしてそれに 100 点で応える人がいるということ。この素晴しさ。人徳。
-
- 改めて、asumikam さん、stefafafan さん、ご結婚おめでとうございます!
+ 改めて、asumikam さん、stefafafan さん、ご結婚おめでとうございます!
-
-
+
-
+
- さて、時を合戦の前に戻しまして、両陣営の登壇者が発表され徐々に謎のイベントの輪郭が見えてきた頃、asumikam さんから次のような連絡を受けました。
+ さて、時を合戦の前に戻しまして、両陣営の登壇者が発表され徐々に謎のイベントの輪郭が見えてきた頃、asumikam さんから次のような連絡を受けました。
-
-
-
-
- 最初は直近のカンファレンスに出して落選したプロポーザルテーマを LT に編集して話そうとしていたのですが、この機会でなければ話せない・この機会で話すことに意味があるテーマにしようとネタ出しをおこない、最終的に次のテーマでの登壇となりました。
+
+
+
+
+ 最初は直近のカンファレンスに出して落選したプロポーザルテーマを LT に編集して話そうとしていたのですが、この機会でなければ話せない・この機会で話すことに意味があるテーマにしようとネタ出しをおこない、最終的に次のテーマでの登壇となりました。
-
-
+
- 当日は、「プログラミングマナー講座」と題して発表をおこないました。結婚式のマナー、特に「忌み言葉」へフォーカスし、これを無理やりプログラミングに適用するというものです。スライドはこちらにアップロードしています。
+ 当日は、「プログラミングマナー講座」と題して発表をおこないました。 結婚式のマナー、特に「忌み言葉」へフォーカスし、これを無理やりプログラミングに適用するというものです。 スライドはこちらにアップロードしています。
-
- 最終的にお祝いのメッセージを仕込んだソースコードで締めるという構成は、我ながら綺麗にまとまったと思っています。忌み言葉の案は他にも大量にあったのですが、技術 LT かつ結婚祝いスピーチにするためにどうしても最後のソースコードが必要だったので、時間の関係上それらには犠牲となってもらいました (ボツになった案のひとつ)。
+ 最終的にお祝いのメッセージを仕込んだソースコードで締めるという構成は、我ながら綺麗にまとまったと思っています。忌み言葉の案は他にも大量にあったのですが、技術 LT かつ結婚祝いスピーチにするためにどうしても最後のソースコードが必要だったので、時間の関係上それらには犠牲となってもらいました ( ボツになった案のひとつ )。
-
- そもそも結婚式・披露宴でのスピーチ自体が初めてだったのでそれなりに緊張していたのですが、登壇時やその後の反応を伺う限り概ね好評だったようで良かったです。
+ そもそも結婚式・披露宴でのスピーチ自体が初めてだったのでそれなりに緊張していたのですが、登壇時やその後の反応を伺う限り概ね好評だったようで良かったです。
-
-
+
- https://github.com/nsfisis/cohackpp/blob/main/congrats.php
+ https://github.com/nsfisis/cohackpp/blob/main/congrats.php
-
<?php
$s=<<<'Q'
diff --git a/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html b/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html
index a6e0f3f6..78c87cc5 100644
--- a/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html
+++ b/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html
@@ -52,120 +52,103 @@
-
-
+
- 参加または登壇した勉強会やカンファレンス。LT 等も含めて計 8 回の登壇をおこなった。また、4つのカンファレンスでコアスタッフまたは当日スタッフとして参加した。
+ 参加または登壇した勉強会やカンファレンス。 LT 等も含めて計 8 回の登壇をおこなった。 また、4つのカンファレンスでコアスタッフまたは当日スタッフとして参加した。
-
-
-
+
- 今年はこのブログに月1記事以上の記事を書くという目標を立てていた。本数としては 12 本以上あるが、10月と11月はゼロになってしまった。社内記事を社外向けにリライトする作業を中々進められていないので、2025年は定期的に消化していきたい。
+ 今年はこのブログに月1記事以上の記事を書くという目標を立てていた。本数としては 12 本以上あるが、10月と11月はゼロになってしまった。 社内記事を社外向けにリライトする作業を中々進められていないので、2025年は定期的に消化していきたい。
-
-
社外記事 (このブログ): 15本
-
-
- 社内記事: 22本
+
+ 社内記事: 22本
+
-
年間で最も記事を書いた人として社内表彰された
@@ -174,49 +157,41 @@
-
-
+
- 今年は主に WebAssembly ランタイムと、カンファレンスの企画で使うシステムを作っていた。後者のシステムでもサンドボックス化のための技術として WebAssembly を用いているので、今年は WebAssembly と戯れた一年だったと言える。
+ 今年は主に WebAssembly ランタイムと、カンファレンスの企画で使うシステムを作っていた。 後者のシステムでもサンドボックス化のための技術として WebAssembly を用いているので、今年は WebAssembly と戯れた一年だったと言える。
-
-
-
-
+
- 今年も大変お世話になりました。よいお年を!
+ 今年も大変お世話になりました。よいお年を!
diff --git a/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html b/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html
index 892c7f34..5da025ff 100644
--- a/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html
+++ b/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html
@@ -70,199 +70,162 @@
-
+
NOTE
- これは PHPerKaigi 2023 の記事です。今は 2025 年ですが、PHPerKaigi 2023 の記事です。
+
+ これは PHPerKaigi 2023 の記事です。今は 2025 年ですが、PHPerKaigi 2023 の記事です。
+
-
- 2023-03-23 から 2023-03-25 にかけて開催された PHPerKaigi 2023 では、PHPer チャレンジという企画がおこなわれた。PHPer チャレンジとは、スポンサーのパンフレットやカンファレンス会場などから「#」記号で始まる文字列を集め、景品などを得るという企画である。この文字列は「PHPer トークン」と呼ばれている。弊社デジタルサーカス株式会社からは、トークン問題という形で、PHP に関する問題を解くと PHPer トークンが得られるようになっている問題を出題した。
+ 2023-03-23 から 2023-03-25 にかけて開催された PHPerKaigi 2023 では、PHPer チャレンジという企画がおこなわれた。 PHPer チャレンジとは、スポンサーのパンフレットやカンファレンス会場などから「#」記号で始まる文字列を集め、景品などを得るという企画である。 この文字列は「PHPer トークン」と呼ばれている。弊社 デジタルサーカス株式会社 からは、トークン問題という形で、PHP に関する問題を解くと PHPer トークンが得られるようになっている問題を出題した。
-
- PHPerKaigi 2023 の参加レポ でも書いたとおり、この年のトークン問題は「昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作」された。PHPerKaigi 当日も PHPer チャレンジ解説セッション という形で解説の機会を頂いたのだが、せっかく時間をかけて作題したので記事の形でも残しておこうと思う。
+ PHPerKaigi 2023 の参加レポ でも書いたとおり、この年のトークン問題は「昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作」された。 PHPerKaigi 当日も PHPer チャレンジ解説セッション という形で解説の機会を頂いたのだが、せっかく時間をかけて作題したので記事の形でも残しておこうと思う。
-
- この記事では、全5問ある中の第1問について解説する。他の問題については以下のリンクを参照のこと。
+ この記事では、全5問ある中の第1問について解説する。他の問題については以下のリンクを参照のこと。
-
-
第1問 (この記事)
-
-
第2問 (TODO: 執筆中)
-
-
第3問 (TODO: 執筆中)
-
-
第4問 (TODO: 執筆中)
-
-
第5問 (TODO: 執筆中)
-
- それぞれの問題はこちらの GitHub リポジトリ (nsfisis/PHPerKaigi2023-tokens) からも閲覧できる。
+ それぞれの問題はこちらの GitHub リポジトリ ( nsfisis/PHPerKaigi2023-tokens ) からも閲覧できる。
-
-
+
- 第1問『An Art of Computer Programming』はこちら。
+ 第1問『An Art of Computer Programming』はこちら。
+
+
+
+
-
-
-
-
-
+
- まずはトークンを得る方法を解説抜きで説明する。次のように実行する。
+ まずはトークンを得る方法を解説抜きで説明する。次のように実行する。
-
$ echo "#iwillblog" | php Q1.png >/dev/null
-
- 無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。
+ 無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。
-
-
-
+
- この問題の自己評価はこちら。問題の出題順はおおよそ作成した順になっているのだが、そのせいで難易度高めの問題が1問目に配置されてしまった。これは反省点の一つである。
+ この問題の自己評価はこちら。 問題の出題順はおおよそ作成した順になっているのだが、そのせいで難易度高めの問題が1問目に配置されてしまった。 これは反省点の一つである。
-
-
難しさ: ★★★★
-
-
お気に入り度: ★★
-
-
鮮やかさ: ★★★★★★★
diff --git a/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html b/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html
index 87efea12..37cb8206 100644
--- a/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html
+++ b/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html
@@ -65,43 +65,39 @@
NOTE
- この記事は、2021-06-30 に
デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
+ この記事は、2021-06-30 に デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
-
-
+
- データ記述言語の一つ YAML には 1.0、1.1、1.2 のバージョンがある。これらのうち、1.1 と 1.2 の間には無視できない非互換の変更が多く、1.2 に対応していないライブラリもある (Ruby 同梱の yaml など)。この記事では、YAML 1.1 と YAML 1.2 の主な破壊的変更を紹介する (影響範囲が広いものを抜粋しており、すべての非互換を網羅してはいない)。
+ データ記述言語の一つ YAML には 1.0、1.1、1.2 のバージョンがある。 これらのうち、1.1 と 1.2 の間には無視できない非互換の変更が多く、1.2 に対応していないライブラリもある (Ruby 同梱の yaml など)。 この記事では、YAML 1.1 と YAML 1.2 の主な破壊的変更を紹介する (影響範囲が広いものを抜粋しており、すべての非互換を網羅してはいない)。
-
- 参照した仕様書はこちら: https://yaml.org/spec/1.2.2/ext/changes/
+ 参照した仕様書はこちら: https://yaml.org/spec/1.2.2/ext/changes/
-
-
+
-
+
- この変更の影響が最も大きいと思われる。YAML 1.1 では、boolean 値のリテラルとして true、false のほか yes、no、y、n、on、off、それらの大文字バージョンなどが認められていた。YAML 1.2 では、true と false、それらの大文字バージョン (True、TRUE、False、FALSE) のみが boolean としてパースされるようになった。
+ この変更の影響が最も大きいと思われる。 YAML 1.1 では、boolean 値のリテラルとして true、false のほか yes、no、y、n、on、off、それらの大文字バージョンなどが認められていた。 YAML 1.2 では、true と false、それらの大文字バージョン (True、TRUE、False、FALSE) のみが boolean としてパースされるようになった。
-
-
+
- C 言語などでは、0 から始まる数字の列を八進数としてパースする。YAML 1.1 もこれに準じていたが、1.2 からは 0o のプレフィクスが必須となった ("o" は "octal" の "o")。プログラミング言語では、Python や Haskell、Swift、Rust などがこの記法を採用している。
+ C 言語などでは、0 から始まる数字の列を八進数としてパースする。 YAML 1.1 もこれに準じていたが、1.2 からは 0o のプレフィクスが必須となった (“o” は “octal” の “o”)。 プログラミング言語では、Python や Haskell、Swift、Rust などがこの記法を採用している。
-
-
+
- YAML 1.1 では、<< という文字列をキーに指定することで、マップをマージすることができた。
+ YAML 1.1 では、<< という文字列をキーに指定することで、マップをマージすることができた。
-
x: &base
a: 123
@@ -112,24 +108,21 @@
b: 456
# => { "y": { "a": 123, "b": 456 } }
-
- 1.2 からはこれができなくなる。
+ 1.2 からはこれができなくなる。
-
-
+
1234567 を 1_234_567 と書けなくなった。
-
-
+
- 全体的に、There's more than one way to do it. から There should be one - and preferably only one - obvious way to do it. へ移行しているように思われる。データ記述言語としては望ましい方向性ではないかと感じる。
+ 全体的に、There’s more than one way to do it. から There should be one - and preferably only one - obvious way to do it. へ移行しているように思われる。 データ記述言語としては望ましい方向性ではないかと感じる。
diff --git a/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html b/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html
index ee924cdc..11c4db0e 100644
--- a/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html
+++ b/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html
@@ -64,58 +64,55 @@
-
-
+
- 特に印象に残ったセッションを二つピックアップした (タイトルと発表者名は fortee のプロポーザルページによる)。
+ 特に印象に残ったセッションを二つピックアップした (タイトルと発表者名は fortee のプロポーザルページによる)。
-
-
-
-
+
- 今回もカンファレンスくらいでしか聴けないようなセッションがいくつも聴けてよかった。また、ちょうど連休だったのもあり名古屋も楽しむことができた。運営のみなさま、お疲れさまでした&ありがとうございました。次は PHPerKaigi 2025 で会いましょう。
+ 今回もカンファレンスくらいでしか聴けないようなセッションがいくつも聴けてよかった。 また、ちょうど連休だったのもあり名古屋も楽しむことができた。 運営のみなさま、お疲れさまでした&ありがとうございました。 次は PHPerKaigi 2025 で会いましょう。
diff --git a/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html b/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html
index c1d25c00..abc10b53 100644
--- a/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html
+++ b/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html
@@ -65,40 +65,35 @@
NOTE
- この記事は、2021-03-22 に
デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
+ この記事は、2021-03-22 に デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
-
-
+
- 次の2ファイル a.txt / b.txt から出力 ab.txt を得たい。
+ 次の2ファイル a.txt / b.txt から出力 ab.txt を得たい。
-
a.txt
-
-
b.txt
-
-
ab.txt
-
a1
b1
@@ -107,42 +102,34 @@
a3
b3
-
- ちょうど Python や Haskell などにある zip 関数のような動きをさせたい。
+ ちょうど Python や Haskell などにある zip 関数のような動きをさせたい。
-
-
+
- 記事タイトルに書いたように、paste コマンドを使うと実現できる。
+ 記事タイトルに書いたように、paste コマンドを使うと実現できる。
-
$ paste -d '\
' a.txt b.txt > ab.txt
-
- paste コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら -d で指定した文字で区切って出力する。-d は区切り文字の指定で、デフォルトだとタブ区切りになる。
+ paste コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら -d で指定した文字で区切って出力する。 -d は区切り文字の指定で、デフォルトだとタブ区切りになる。
-
- ファイル名には - を指定でき、その場合は標準入力から読み込んで出力する。このとき paste - - のように複数回 - を指定すると、指定した回数の行ごとに連結することができる。例えば ab.txt だとこうなる。
+ ファイル名には - を指定でき、その場合は標準入力から読み込んで出力する。 このとき paste - - のように複数回 - を指定すると、指定した回数の行ごとに連結することができる。 例えば ab.txt だとこうなる。
-
$ paste - - < ab.txt
a1 b1
a2 b2
a3 b3
-
- これは標準入力を使うとき特有の挙動で、単に同じファイル名を指定してもこうはならない。
+ これは標準入力を使うとき特有の挙動で、単に同じファイル名を指定してもこうはならない。
-
$ paste ab.txt ab.txt
a1 a1
@@ -152,9 +139,8 @@
a3 a3
b3 b3
-
- ときどき便利。
+ ときどき便利。
diff --git a/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html b/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html
index ff3c7a86..e6829d92 100644
--- a/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html
+++ b/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html
@@ -65,109 +65,97 @@
NOTE
- この記事は、2022-08-18 に
デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
+ この記事は、2022-08-18 に デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+
-
-
-
+
-
+
- A sender MUST NOT generate multiple header fields with the same field name in a message unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known exception (as noted below).
+
+ A sender MUST NOT generate multiple header fields with the same field name in a message unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known exception (as noted below).
+
-
- 【日本語訳 (私が訳したもので、公式なものではない)】 送信者は、同じ field name の header field を複数生成してはならない (MUST NOT)。ただし、header field の値がコンマ区切りのリストとして定義されているか、header field がよく知られた例外 (後述) である場合はその限りでない。
+ 【日本語訳 (私が訳したもので、公式なものではない)】 送信者は、同じ field name の header field を複数生成してはならない (MUST NOT)。 ただし、header field の値がコンマ区切りのリストとして定義されているか、header field がよく知られた例外 (後述) である場合はその限りでない。
-
-
+
- A recipient MAY combine multiple header fields with the same field name into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma. The order in which header fields with the same field name are received is therefore significant to the interpretation of the combined field value; a proxy MUST NOT change the order of these field values when forwarding a message.
+
+ A recipient MAY combine multiple header fields with the same field name into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma. The order in which header fields with the same field name are received is therefore significant to the interpretation of the combined field value; a proxy MUST NOT change the order of these field values when forwarding a message.
+
-
- 【日本語訳 (私が訳したもので、公式なものではない)】 受信者は、同じ field name を持つ複数の header field を、メッセージの意味を変えないようにしつつ同じ順序で追加して、単一のコンマで区切られた "field-name: field-value" のペアに結合してよい (MAY)。したがって、同じ field name を持つ header field がどのような順序で受信されたかは、結合された値の解釈に影響する。よって、プロキシは、メッセージを転送する際、header field の順序を変えてはならない (MUST NOT)。
+ 【日本語訳 (私が訳したもので、公式なものではない)】 受信者は、同じ field name を持つ複数の header field を、メッセージの意味を変えないようにしつつ同じ順序で追加して、単一のコンマで区切られた "field-name: field-value" のペアに結合してよい (MAY)。 したがって、同じ field name を持つ header field がどのような順序で受信されたかは、結合された値の解釈に影響する。 よって、プロキシは、メッセージを転送する際、header field の順序を変えてはならない (MUST NOT)。
-
-
+
- Note: In practice, the "Set-Cookie" header field ([RFC6265]) often appears multiple times in a response message and does not use the list syntax, violating the above requirements on multiple header fields with the same name. Since it cannot be combined into a single field-value, recipients ought to handle "Set-Cookie" as a special case while processing header fields. (See Appendix A.2.3 of [Kri2001] for details.)
+
+ Note: In practice, the “Set-Cookie” header field ([RFC6265]) often appears multiple times in a response message and does not use the list syntax, violating the above requirements on multiple header fields with the same name. Since it cannot be combined into a single field-value, recipients ought to handle “Set-Cookie” as a special case while processing header fields. (See Appendix A.2.3 of [Kri2001] for details.)
+
-
- 【日本語訳 (私が訳したもので、公式なものではない)】 注意: 実際には、Set-Cookie header field (RFC6265) は、しばしばレスポンスメッセージ中に複数回現れる。これはリストの構文を使っておらず、上述した同じ field name を持つ header field についての要件に違反している。この値は単一の値へ結合できないため、受信者は、header field を処理する際、Set-Cookie を特別扱いした方がよい。
+ 【日本語訳 (私が訳したもので、公式なものではない)】 注意: 実際には、Set-Cookie header field (RFC6265) は、しばしばレスポンスメッセージ中に複数回現れる。 これはリストの構文を使っておらず、上述した同じ field name を持つ header field についての要件に違反している。 この値は単一の値へ結合できないため、受信者は、header field を処理する際、Set-Cookie を特別扱いした方がよい。
-
- おそらく、「送信側」のところで書かれている「よく知られた例外」の一つがこれだと思われる。
+ おそらく、「送信側」のところで書かれている「よく知られた例外」の一つがこれだと思われる。
-
-
+
- 上記のように、同じ field name を持つ header field を複数回送れるかどうかは、その header field がコンマ区切りのリストとして定義されているかどうかで決まる。では、特定の header field がその条件を満たしているかどうか知りたいときは、何を見ればよいのか。
+ 上記のように、同じ field name を持つ header field を複数回送れるかどうかは、その header field がコンマ区切りのリストとして定義されているかどうかで決まる。では、特定の header field がその条件を満たしているかどうか知りたいときは、何を見ればよいのか。
-
- HTTP の仕様として定義されているような header field であれば、下記のリンクからそれぞれの定義を参照できる。
+ HTTP の仕様として定義されているような header field であれば、下記のリンクからそれぞれの定義を参照できる。
-
-
- そうでない場合 (たとえば X- から始まるもの等) は、MDN や各ベンダのドキュメントを探すことになるだろう。
+ そうでない場合 (たとえば X- から始まるもの等) は、MDN や各ベンダのドキュメントを探すことになるだろう。
-
-
+
-
送信側: 基本的には複数回送れない。コンマ区切りのヘッダは例外
-
-
受信側: 基本的には未規定。コンマ区切りのヘッダは複数回来たらその順に結合する
-
-
プロキシ: 順序を変えてはならない
-
-
Set-Cookie は例外ケース
--
cgit v1.2.3-70-g09d2
- Day 2 - にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも - (特に初参加者として) 嬉しいポイントだった。 -
-- 今回、雑談/登壇者への質問等向けに Discord - サーバもあったのだが、こちらは参加こそしたものの ROM - のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord - 表示に - 1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった - (さらにいうと Zoom - でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。 -
-- 1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord - しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 - まあ初カンファレンスだし、とお茶を濁しておこう。 -
-- さて、カンファレンスで一つ気になったことがある。それは、Discord - という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord - の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord - があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。 -
--
- 最後になりましたが、毎年の PHPerKaigi - 開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! - (ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い) -
-- ではまた来年。 -
-