Commit cf630290 authored by Rubén Beltran del Río's avatar Rubén Beltran del Río 🐝

📝🔧 Add generator bin and files

Includes config for eslint, and jsdoc, plus the executable

Squashed commit of the following:

commit 7726a2743994800f5c0afbc23e5b529107b79450
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:25:43 2017 -0500

    Update changelog

commit 78ad8ab788020c09848a852e48827781aa17341a
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:25:38 2017 -0500

    Remove unused image

commit 1cd659307a39669cf0fc8d0f676b25625fd16ba2
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:23:53 2017 -0500

    Remove trailing slash on index

commit 944160fcbc364e4a829be91e6ad32521b4f94987
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:21:50 2017 -0500

    Add example blog post

commit 70645d0214d5d363accfedc3e2583e97232c930e
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:20:44 2017 -0500

     Add binary / generator to lib

commit bb8f8bb6e12c89bc4b9b3766fd2ef8e924422005
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:20:29 2017 -0500

    🔧 Add package.json

commit 7e9b6f52c1c91bf963319ddeb1696bfac4130b95
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:19:57 2017 -0500

    🔧 Add eslint config

commit 26ee06bbf1c2f20620c81fa94c4a9b40460bd401
Author: Ben Beltran <ben@nsovocal.com>
Date:   Mon Jul 3 00:19:31 2017 -0500

    Move static files to template / static

commit db84351cbb8e8c4729123650d1cb3a6150da0502
Author: Ben Beltran <ben@nsovocal.com>
Date:   Sun Jul 2 21:39:20 2017 -0500

    Use a hidden name for posts directory
parent 7a5a585e
{
"extends": "eslint-config-hapi",
"parserOptions": {
"ecmaVersion": 2017
},
"rules": {
"indent": [
2,
2
],
"no-undef": 2,
"require-yield": 0
}
}
......@@ -61,7 +61,7 @@ typings/
.DS_Store
# Data store
_posts
.posts
# Generated files
static/assets
......
......@@ -6,7 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Static HTML / CSS
- Binary to add and update blog posts
- Template for index
- Static Files
- Simple contributing guidelines
- This CHANGELOG
- A Readme
......
#!/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();
'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')))
};
{
"plugins": ["plugins/markdown"],
"opts": {
"destination": "doc",
"readme": "README.md",
"template": "node_modules/docdash",
"recurse": true
}
}
# 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
<!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>
'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);
}
};
{
"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"
}
}
<!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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment