]> git.r.bdr.sh - rbdr/blog/commitdiff
✨📝🔧 Add generator bin and files
authorBen Beltran <redacted>
Mon, 3 Jul 2017 05:26:08 +0000 (00:26 -0500)
committerBen Beltran <redacted>
Mon, 3 Jul 2017 05:26:08 +0000 (00:26 -0500)
Includes config for eslint, and jsdoc, plus the executable

Squashed commit of the following:

commit 7726a2743994800f5c0afbc23e5b529107b79450
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:25:43 2017 -0500

    Update changelog

commit 78ad8ab788020c09848a852e48827781aa17341a
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:25:38 2017 -0500

    Remove unused image

commit 1cd659307a39669cf0fc8d0f676b25625fd16ba2
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:23:53 2017 -0500

    Remove trailing slash on index

commit 944160fcbc364e4a829be91e6ad32521b4f94987
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:21:50 2017 -0500

    Add example blog post

commit 70645d0214d5d363accfedc3e2583e97232c930e
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:20:44 2017 -0500

    ✨ Add binary / generator to lib

commit bb8f8bb6e12c89bc4b9b3766fd2ef8e924422005
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:20:29 2017 -0500

    🔧 Add package.json

commit 7e9b6f52c1c91bf963319ddeb1696bfac4130b95
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:19:57 2017 -0500

    🔧 Add eslint config

commit 26ee06bbf1c2f20620c81fa94c4a9b40460bd401
Author: Ben Beltran <redacted>
Date:   Mon Jul 3 00:19:31 2017 -0500

    Move static files to template / static

commit db84351cbb8e8c4729123650d1cb3a6150da0502
Author: Ben Beltran <redacted>
Date:   Sun Jul 2 21:39:20 2017 -0500

    Use a hidden name for posts directory

17 files changed:
.eslintrc [new file with mode: 0644]
.gitignore
CHANGELOG.md
bin/blog.js [new file with mode: 0755]
config/config.js [new file with mode: 0644]
config/env.dist [new file with mode: 0644]
config/jsdoc.json [new file with mode: 0644]
example/test-blog-post/assets/example_image.png [moved from images/example_image.png with 100% similarity]
example/test-blog-post/assets/ok/ok.txt [new file with mode: 0644]
example/test-blog-post/test-blog-post.md [new file with mode: 0644]
index.html [deleted file]
lib/blog.js [new file with mode: 0644]
package.json [new file with mode: 0644]
static/css/style.css [moved from css/style.css with 100% similarity]
static/images/header_background.png [moved from images/header_background.png with 100% similarity]
static/images/header_foreground.png [moved from images/header_foreground.png with 100% similarity]
templates/index.html [new file with mode: 0644]

diff --git a/.eslintrc b/.eslintrc
new file mode 100644 (file)
index 0000000..0a1dc80
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,14 @@
+{
+  "extends": "eslint-config-hapi",
+  "parserOptions": {
+    "ecmaVersion": 2017
+  },
+  "rules": {
+    "indent": [
+      2,
+      2
+    ],
+    "no-undef": 2,
+    "require-yield": 0
+  }
+}
index 719089dc13e988a27dedd95ba9a5cd939eff7258..997ace3b331b9aaaf08c851a69a2b9d87afaef87 100644 (file)
@@ -61,7 +61,7 @@ typings/
 .DS_Store
 
 # Data store
 .DS_Store
 
 # Data store
-_posts
+.posts
 
 # Generated files
 static/assets
 
 # Generated files
 static/assets
index a69595260b94158b518441851199df8400f38459..728047b5a502c4d40c01482a223dc6137478cb0e 100644 (file)
@@ -6,7 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
 ### Added
 
 ## [Unreleased]
 ### Added
-- Static HTML / CSS
+- Binary to add and update blog posts
+- Template for index
+- Static Files
 - Simple contributing guidelines
 - This CHANGELOG
 - A Readme
 - Simple contributing guidelines
 - This CHANGELOG
 - A Readme
diff --git a/bin/blog.js b/bin/blog.js
new file mode 100755 (executable)
index 0000000..c88a8aa
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+'use strict';
+
+const Config = require('../config/config');
+const Blog = require('..');
+const Minimist = require('minimist');
+
+const internals = {
+  blog: new Blog(Config),
+  expectedKeys: ['add', 'update', 'publish'],
+
+  // Application entry point. Reads arguments and calls the
+  // corresponding method from the blog lib
+
+  async main() {
+
+    try {
+      const parsedArguments = this._parseArguments();
+
+      for (const argument in parsedArguments) {
+        if (parsedArguments.hasOwnProperty(argument)) {
+
+          const value = parsedArguments[argument];
+
+          if (argument === 'add') {
+            await internals.blog.add(value);
+            return;
+          }
+
+          if (argument === 'update') {
+            await internals.blog.update(value);
+            return;
+          }
+
+          if (argument === 'publish') {
+            await internals.blog.update(value);
+            return;
+          }
+        }
+      }
+      console.log('Not yet implemented');
+    }
+    catch (err) {
+      console.error(err.message || err);
+      this._printUsage();
+      process.exit(1);
+    }
+  },
+
+  // Parses arguments and returns them if valid. otherwise Throws
+
+  _parseArguments() {
+
+    const parsedArguments = Minimist(process.argv.slice(2));
+
+    if (!this._areArgumentsValid(parsedArguments)) {
+      throw new Error(internals.strings.invalidArguments);
+    }
+
+    return parsedArguments;
+  },
+
+  // Checks if the arguments are valid, returns a boolean value.
+
+  _areArgumentsValid(parsedArguments) {
+
+    const argumentKeys = Object.keys(parsedArguments);
+
+    return argumentKeys.some((key) => internals.expectedKeys.indexOf(key) >= 0);
+  },
+
+  // Prints the usage to stderr
+
+  _printUsage() {
+
+    console.error('\nUsage:\n');
+    console.error('blog --add path/to/blog_post\t\t(creates new blog post)');
+    console.error('blog --update path/to/blog_post\t(updates latest blog post)');
+    console.error('blog --publish \t\t\t(publishes the blog)');
+  }
+};
+
+// Add the strings, added after declaration so they can consume the
+// internals object.
+
+internals.strings = {
+  invalidArguments: `Invalid Arguments, expecting one of: ${internals.expectedKeys.join(', ')}`
+};
+
+
+
+internals.main();
diff --git a/config/config.js b/config/config.js
new file mode 100644 (file)
index 0000000..d16ff2d
--- /dev/null
@@ -0,0 +1,31 @@
+'use strict';
+
+const Path = require('path');
+const Getenv = require('getenv');
+
+const internals = {};
+
+/**
+ * The main configuration object for Blog. It will be used to
+ * initialize all of the sub-components. It can extend any property of
+ * the blog object.
+ *
+ * @memberof Blog
+ * @typedef {object} tConfiguration
+ * @property {number} maxPosts=3 the max number of posts that can exist
+ * at one time
+ * @property {string} postsDirectory=<project_root>/.posts the location of
+ * the directory where the posts will be stored.
+ * @property {string} staticDirectory=<project_root>/static the location of
+ * the directory where the generated files will be placed. NOTE: There
+ * are some pre-built style files in the default directory, if you
+ * select another one, make sure you include them manually.
+ * @property {string} templatesDirectory=<project_root>/templates the
+ * location of the templates we'll use to generate the index.html
+ */
+module.exports = internals.Config = {
+  maxPosts: Getenv.int('BLOG_MAX_POSTS', 3),
+  postsDirectory: Getenv('BLOG_POSTS_DIRECTORY', Path.resolve(Path.join(__dirname, '../.posts'))),
+  staticDirectory: Getenv('BLOG_STATIC_DIRECTORY', Path.resolve(Path.join(__dirname, '../static'))),
+  templatesDirectory: Getenv('BLOG_TEMPLATES_DIRECTORY', Path.resolve(Path.join(__dirname, '../templates')))
+};
diff --git a/config/env.dist b/config/env.dist
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/config/jsdoc.json b/config/jsdoc.json
new file mode 100644 (file)
index 0000000..9e05753
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "plugins": ["plugins/markdown"],
+  "opts": {
+    "destination": "doc",
+    "readme": "README.md",
+    "template": "node_modules/docdash",
+    "recurse": true
+  }
+}
diff --git a/example/test-blog-post/assets/ok/ok.txt b/example/test-blog-post/assets/ok/ok.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/example/test-blog-post/test-blog-post.md b/example/test-blog-post/test-blog-post.md
new file mode 100644 (file)
index 0000000..88209ea
--- /dev/null
@@ -0,0 +1,57 @@
+# This is the title of another entry
+
+An entry will have paragraphs, these paragraphs will contain text. The
+text should be formatted correctly: visitors will want to read whatever
+is in the blog, so it should be readable. It should account for several
+types of tags, like **strong**, or *emphasised*. It should even support
+[links](/)
+
+An entry will have paragraphs, these paragraphs will contain text. The
+text should be formatted correctly: visitors will want to read whatever
+is in the blog, so it should be readable. It should account for several
+types of tags, like **strong**, or *emphasised*. It
+should even support [links](/)
+
+An entry will have paragraphs, these paragraphs will contain text. The
+text should be formatted correctly: visitors will want to read whatever
+is in the blog, so it should be readable. It should account for several
+types of tags, like **strong**, or *emphasised*. It
+should even support [links](/)
+
+## Subheading 1 (h2)
+
+An entry will have paragraphs, these paragraphs will contain text. The
+text should be formatted correctly: visitors will want to read whatever
+is in the blog, so it should be readable. It should account for several
+types of tags, like **strong**, or *emphasised*. It
+should even support [links](/)
+
+* There will be **lists**
+* Lists will have *tags*
+* And everything else [in the world](/)
+
+An entry will have paragraphs, these paragraphs will contain text. The
+text should be formatted correctly: visitors will want to read whatever
+is in the blog, so it should be readable. It should account for several
+types of tags, like **strong**, or *emphasised*. It
+should even support [links](/)
+
+![Picture: two persons in a ceremony][example-image]
+
+### Other types of subheadings, other types of lists (h3)
+
+1. There will be **lists**
+2. Lists will have *tags*
+3. And everything else [in the world](/)
+
+An entry will have paragraphs, these paragraphs will contain text. The
+text should be formatted correctly: visitors will want to read whatever
+is in the blog, so it should be readable. It should account for several
+types of tags, like **strong**, or *emphasised*. It
+should even support [links](/)
+
+#### Finally there are hfours (h4)
+
+And that's about it!
+
+[example-image]: /assets/example_image.png
diff --git a/index.html b/index.html
deleted file mode 100644 (file)
index 10d4919..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <meta name="description" content="This is the blog at unlimited.pizza">
-
-    <title>blog 🍕</title>
-
-    <link href="css/style.css" rel="stylesheet">
-
-  </head>
-  <body>
-    <header class="main-header">
-      <a href="/">Blog</a>
-    </header>
-    <main>
-      <article id="1">
-        <h1>This is the title of an entry</h1>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <h2>Subheading 1 (h2)</h2>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <ul>
-          <li>There will be <strong>lists</strong></li>
-          <li>Lists will have <em>tags</em></li>
-          <li>And everything else <a href="/">in the world</a></li>
-        </ul>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <h3>Other types of subheadings, other types of lists (h3)</h3>
-        <ol>
-          <li>There will be <strong>lists</strong></li>
-          <li>Lists will have <em>tags</em></li>
-          <li>And everything else <a href="/">in the world</a></li>
-        </ol>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-      </article>
-      <hr>
-      <article id="2">
-        <h1>This is the title of another entry</h1>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <h2>Subheading 1 (h2)</h2>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <ul>
-          <li>There will be <strong>lists</strong></li>
-          <li>Lists will have <em>tags</em></li>
-          <li>And everything else <a href="/">in the world</a></li>
-        </ul>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <img src="/images/example_image.png" alt="Picture: two persons in a ceremony">
-        <h3>Other types of subheadings, other types of lists (h3)</h3>
-        <ol>
-          <li>There will be <strong>lists</strong></li>
-          <li>Lists will have <em>tags</em></li>
-          <li>And everything else <a href="/">in the world</a></li>
-        </ol>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-      </article>
-      <hr>
-      <article id="3">
-        <h1>This is the title of the last entry</h1>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <h2>Subheading 1 (h2)</h2>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <ul>
-          <li>There will be <strong>lists</strong></li>
-          <li>Lists will have <em>tags</em></li>
-          <li>And everything else <a href="/">in the world</a></li>
-        </ul>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-        <h3>Other types of subheadings, other types of lists (h3)</h3>
-        <ol>
-          <li>There will be <strong>lists</strong></li>
-          <li>Lists will have <em>tags</em></li>
-          <li>And everything else <a href="/">in the world</a></li>
-        </ol>
-        <p>
-          An entry will have paragraphs, these paragraphs will contain text. The
-          text should be formatted correctly: visitors will want to read whatever
-          is in the blog, so it should be readable. It should account for several
-          types of tags, like <strong>strong</strong>, or <em>emphasised</em>. It
-          should even support <a href="/">links</a>
-        </p>
-      </article>
-    </main>
-    <footer>
-      <p>Only 3 entries kept at any time. Press 1, 2, and 3 to switch. <a href="https://unlimited.pizza">unlimited.pizza</a></p>
-    </footer>
-  </body>
-</html>
-
diff --git a/lib/blog.js b/lib/blog.js
new file mode 100644 (file)
index 0000000..483577f
--- /dev/null
@@ -0,0 +1,220 @@
+'use strict';
+
+const Fs = require('fs');
+const Markdown = require('markdown');
+const Mustache = require('mustache');
+const Ncp = require('ncp');
+const Path = require('path');
+const Rimraf = require('rimraf');
+const Util = require('util');
+
+const internals = {
+
+  // Promisified functions
+
+  fs: {
+    access: Util.promisify(Fs.access),
+    mkdir: Util.promisify(Fs.mkdir),
+    readdir: Util.promisify(Fs.readdir),
+    readFile: Util.promisify(Fs.readFile),
+    writeFile: Util.promisify(Fs.writeFile)
+  },
+  ncp: Util.promisify(Ncp.ncp),
+  rimraf: Util.promisify(Rimraf),
+  debuglog: Util.debuglog('blog'),
+
+  // constants
+
+  kAssetsDirectoryName: 'assets',
+  kIndexName: 'index.html',
+  kFileNotFoundError: 'ENOENT',
+  kMarkdownRe: /\.md$/i,
+
+  // Strings
+
+  strings: {
+    markdownNotFound: 'Markdown file was not found in blog directory. Please update.'
+  }
+};
+
+/**
+ * The Blog class is the blog generator, it's in charge of adding and
+ * updating posts, and handling the publishing.
+ *
+ * @class Blog
+ * @param {Potluck.tConfiguration} config the initialization options to
+ * extend the instance
+ */
+module.exports = class Blog {
+
+  constructor(config) {
+
+    Object.assign(this, config);
+  }
+
+  /**
+   * Shifts the blog posts, adds the passed path to slot 0, and
+   * generates files.
+   *
+   * @function add
+   * @memberof Blog
+   * @param {string} postLocation the path to the directory containing
+   * the post structure
+   * @return {Promise<undefined>} empty promise, returns no value
+   * @instance
+   */
+  async add(postLocation) {
+
+    await this._shift();
+    await this.update(postLocation);
+  }
+
+  /**
+   * Adds the passed path to slot 0, and generates files.
+   *
+   * @function update
+   * @memberof Blog
+   * @param {string} postLocation the path to the directory containing
+   * the post structure
+   * @return {Promise<undefined>} empty promise, returns no value
+   * @instance
+   */
+  async update(postLocation) {
+
+    await this._copyPost(postLocation);
+    await this._generate();
+  }
+
+  /**
+   * Publishes the files to a static host.
+   *
+   * @function publish
+   * @memberof Blog
+   * @return {Promise<undefined>} empty promise, returns no value
+   * @instance
+   */
+  async publish() {
+
+    console.error('Publishing not yet implemented');
+  }
+
+  // Parses markdown for each page, copies assets and generates index.
+
+  async _generate() {
+
+    const assetsTarget = Path.join(this.staticDirectory, internals.kAssetsDirectoryName);
+    const indexTarget = Path.join(this.staticDirectory, internals.kIndexName);
+    const indexLocation = Path.join(this.templatesDirectory, internals.kIndexName);
+    const posts = [];
+
+    internals.debuglog(`Removing ${assetsTarget}`);
+    await internals.rimraf(assetsTarget);
+
+    for (let i = 0; i < this.maxPosts; ++i) {
+      const sourcePath = Path.join(this.postsDirectory, `${i}`);
+      const assetsSource = Path.join(sourcePath, internals.kAssetsDirectoryName);
+      const postContentPath = await this._findBlogContent(sourcePath);
+
+      internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`);
+      await internals.ncp(assetsSource, assetsTarget);
+
+      internals.debuglog(`Reading ${postContentPath}`);
+      const postContent = await internals.fs.readFile(postContentPath, { encoding: 'utf8' });
+
+      internals.debuglog('Parsing markdown');
+      posts.push({
+        html: Markdown.markdown.toHTML(postContent),
+        id: i + 1
+      });
+    }
+
+    internals.debuglog(`Reading ${indexLocation}`);
+    const indexTemplate = await internals.fs.readFile(indexLocation, { encoding: 'utf8' });
+
+    internals.debuglog('Generating HTML');
+    const indexHtml = Mustache.render(indexTemplate, { posts });
+    await internals.fs.writeFile(indexTarget, indexHtml);
+  }
+
+  // Shift the posts, delete any remainder.
+
+  async _shift() {
+
+    await this._ensurePostsDirectoryExists();
+
+    for (let i = this.maxPosts - 1; i > 0; --i) {
+      const targetPath = Path.join(this.postsDirectory, `${i}`);
+      const sourcePath = Path.join(this.postsDirectory, `${i - 1}`);
+
+      try {
+        await internals.fs.access(sourcePath);
+
+        internals.debuglog(`Removing ${targetPath}`);
+        await internals.rimraf(targetPath);
+
+        internals.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`);
+        await internals.ncp(sourcePath, targetPath);
+      }
+      catch (error) {
+        if (error.code === internals.kFileNotFoundError) {
+          internals.debuglog(`Skipping ${sourcePath}: Does not exist.`);
+          continue;
+        }
+
+        throw error;
+      }
+    }
+  }
+
+  // Copies a post directory to the latest slot.
+
+  async _copyPost(postLocation) {
+
+    await this._ensurePostsDirectoryExists();
+
+    const targetPath = Path.join(this.postsDirectory, '0');
+
+    internals.debuglog(`Removing ${targetPath}`);
+    await internals.rimraf(targetPath);
+
+    internals.debuglog(`Adding ${postLocation} to ${targetPath}`);
+    await internals.ncp(postLocation, targetPath);
+  }
+
+  // Ensures the posts directory exists.
+
+  async _ensurePostsDirectoryExists() {
+
+    internals.debuglog(`Checking if ${this.postsDirectory} exists.`);
+    try {
+      await internals.fs.access(this.postsDirectory);
+    }
+    catch (error) {
+      if (error.code === internals.kFileNotFoundError) {
+        internals.debuglog('Creating posts directory');
+        await internals.fs.mkdir(this.postsDirectory);
+        return;
+      }
+
+      throw error;
+    }
+  }
+
+  // Looks for a `.md` file in the blog directory, and returns the path
+
+  async _findBlogContent(directory) {
+
+    const entries = await internals.fs.readdir(directory);
+
+    const markdownEntries = entries
+      .filter((entry) => internals.kMarkdownRe.test(entry))
+      .map((entry) => Path.join(directory, entry));
+
+    if (markdownEntries.length > 0) {
+      internals.debuglog(`Found markdown file: ${markdownEntries[0]}`);
+      return markdownEntries[0];
+    }
+
+    throw new Error(internals.strings.markdownNotFound);
+  }
+};
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..e5303b1
--- /dev/null
@@ -0,0 +1,42 @@
+{
+  "name": "blog",
+  "version": "1.0.0",
+  "description": "An ephemeral blog",
+  "main": "lib/blog.js",
+  "bin": {
+    "blog": "./bin/blog.js"
+  },
+  "scripts": {
+    "document": "jsdoc -c ./config/jsdoc.json lib config",
+    "lint": "eslint .",
+    "test": "echo \":(\""
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/rbdr/blog.git"
+  },
+  "author": "Ben Beltran <ben@nsovocal.com>",
+  "license": "Apache-2.0",
+  "bugs": {
+    "url": "https://github.com/rbdr/blog/issues"
+  },
+  "homepage": "https://github.com/rbdr/blog#readme",
+  "dependencies": {
+    "getenv": "0.7.x",
+    "markdown": "0.5.x",
+    "minimist": "1.2.x",
+    "mustache": "2.3.x",
+    "ncp": "2.0.x",
+    "rimraf": "2.6.x"
+  },
+  "devDependencies": {
+    "docdash": "0.4.x",
+    "eslint": "4.1.x",
+    "eslint-config-hapi": "10.0.x",
+    "eslint-plugin-hapi": "4.0.x",
+    "jsdoc": "3.4.x"
+  },
+  "engines": {
+    "node": ">=8.0.0"
+  }
+}
similarity index 100%
rename from css/style.css
rename to static/css/style.css
diff --git a/templates/index.html b/templates/index.html
new file mode 100644 (file)
index 0000000..858396b
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="description" content="This is the blog at unlimited.pizza">
+
+    <title>blog 🍕</title>
+
+    <link href="css/style.css" rel="stylesheet">
+
+  </head>
+  <body>
+    <header class="main-header">
+      <a href="/">Blog</a>
+    </header>
+    <main>
+    {{#posts}}
+      <article id="{{id}}">
+        {{{html}}}
+      </article>
+      <hr>
+    {{/posts}}
+    {{^posts}}
+      <h1>This is a fresh blog!</h1>
+      <p>There are no posts yet.</p>
+    {{/posts}}
+    </main>
+    <footer>
+      <p>Only 3 entries kept at any time. Press 1, 2, and 3 to switch. <a href="https://unlimited.pizza">unlimited.pizza</a></p>
+    </footer>
+  </body>
+</html>