Port the Python build script over to JS

`npm run build` now builds both the site.json file and the React app.
Two new run scripts are available:

* build_site - just build the site.json file
* build_code - build the React app
This commit is contained in:
Nick Pegg 2017-10-14 17:16:15 -07:00
parent 55610cb0ba
commit 3eedb94180
5 changed files with 139 additions and 195 deletions

65
package-lock.json generated
View file

@ -165,6 +165,11 @@
"color-convert": "1.9.0"
}
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
},
"anymatch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
@ -1902,7 +1907,7 @@
"integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==",
"requires": {
"is-directory": "0.3.1",
"js-yaml": "3.7.0",
"js-yaml": "3.10.0",
"minimist": "1.2.0",
"object-assign": "4.1.1",
"os-homedir": "1.0.2",
@ -2961,9 +2966,9 @@
}
},
"esprima": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE="
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
},
"esquery": {
"version": "1.0.0",
@ -4333,7 +4338,7 @@
"istanbul-lib-report": "1.1.1",
"istanbul-lib-source-maps": "1.2.1",
"istanbul-reports": "1.1.2",
"js-yaml": "3.7.0",
"js-yaml": "3.10.0",
"mkdirp": "0.5.1",
"once": "1.4.0"
}
@ -4690,12 +4695,12 @@
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
"requires": {
"argparse": "1.0.9",
"esprima": "2.7.3"
"esprima": "4.0.0"
}
},
"jsbn": {
@ -5284,6 +5289,16 @@
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
},
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"requires": {
"any-promise": "1.3.0",
"object-assign": "4.1.1",
"thenify-all": "1.6.0"
}
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -8212,6 +8227,22 @@
"mkdirp": "0.5.1",
"sax": "1.2.4",
"whet.extend": "0.9.9"
},
"dependencies": {
"esprima": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE="
},
"js-yaml": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"requires": {
"argparse": "1.0.9",
"esprima": "2.7.3"
}
}
}
},
"sw-precache": {
@ -8301,6 +8332,22 @@
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
},
"thenify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
"integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
"requires": {
"any-promise": "1.3.0"
}
},
"thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
"requires": {
"thenify": "3.3.0"
}
},
"throat": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-3.2.0.tgz",

View file

@ -4,6 +4,8 @@
"private": true,
"dependencies": {
"highlight.js": "^9.12.0",
"js-yaml": "^3.10.0",
"mz": "^2.7.0",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
@ -15,7 +17,9 @@
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"build_site": "scripts/build_site.js",
"build_code": "react-scripts build",
"build": "scripts/build_site.js && react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}

File diff suppressed because one or more lines are too long

77
scripts/build_site.js Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env node
'use strict';
const fs = require('mz/fs');
const path = require('path');
const yaml = require('js-yaml');
async function build() {
let site = {
pages: [],
posts: [],
tags: [],
};
site.pages = await fs.readdir('_pages')
.then(files => (
Promise.all(files.map(f => fs.readFile(path.join('_pages', f), 'utf8')))
))
.then(files => {
return files.map(contents => {
let [_, meta, body] = contents.split("---\n");
let page = yaml.safeLoad(meta);
if (page.parent === undefined) {
page.parent = null;
}
page.body = body.trim();
return page;
})
})
.catch(err => console.log('Failure while fetching pages:', err));
[site.posts, site.tags] = await fs.readdir('_posts')
.then(files => (
Promise.all(files.map(f => fs.readFile(path.join('_posts', f), 'utf8')))
))
.then(files => {
let tags = new Set();
let posts = files.map(contents => {
let parts = contents.split("---\n");
let post = yaml.safeLoad(parts[0]);
if (parts.length === 2) {
post.blurb = parts[1];
post.body = post.blurb;
} else if (parts.length === 3) {
post.blurb = parts[1];
post.body = parts.slice(1).join("\n");
}
if (post.tags === undefined) {
post.tags = [];
}
post.tags.forEach(tag => {
tags.add(tag);
});
return post;
})
return [posts, tags];
})
.catch(err => console.log('Failure while fetching posts:', err));
site.pages.sort((a, b) => a.title.localeCompare(b.title));
// sort posts newest to oldest
site.posts.sort((a, b) => (b.date - a.date));
site.tags = Array.from(site.tags.values());
fs.writeFile('public/site.json', JSON.stringify(site))
.catch(err => console.log('Unable to write site.json:', err));
}
build();

184
site.py
View file

@ -1,184 +0,0 @@
#!/usr/bin/env python
"""
CLI command to manage my site
Imports posts from Posty, builds YAML files into JSON blobs, etc.
This may be the start of Posty 2.0, who knows.
"""
import click
from dateutil import parser as date_parser
import json
import markdown
import os
import shutil
import sys
import yaml
@click.group()
def cli():
pass
@cli.command()
def init():
"""
Initialize a site in the current directory
"""
for directory in ('_media', '_pages', '_posts'):
if not os.path.exists(directory):
os.mkdir(directory)
@cli.command()
@click.option(
'--path',
help='Path to output JSON file',
default='site.json',
show_default=True
)
def build(path):
"""
Build posts and pages JSON files
Takes all of the YAML in _pages and _posts, combines them into JSON blobs
and writes them out to disk.
"""
if not all([os.path.exists('_pages'), os.path.exists('_posts')]):
raise click.UsageError('You must run `init` first!')
tags = set()
blob = {
'pages': [],
'posts': [],
'tags': [],
}
pages = []
for filename in os.listdir('_pages'):
contents = open(os.path.join('_pages', filename)).read()
_, meta_yaml, body = contents.split("---\n")
page = yaml.load(meta_yaml)
# page['body'] = render(body.strip())
page['body'] = body.strip()
page.setdefault('parent')
pages.append(page)
blob['pages'] = sorted(pages, key=lambda x: x['title'].lower())
posts = []
for filename in os.listdir('_posts'):
contents = open(os.path.join('_posts', filename)).read()
parts = contents.split("---\n")
post = yaml.load(parts[0])
post['date'] = post['date'].isoformat()
post.setdefault('tags', [])
if len(parts[1:]) == 1:
post['blurb'] = parts[1]
post['body'] = parts[1]
elif len(parts[1:]) == 2:
post['blurb'] = parts[1]
post['body'] = "\n".join(parts[1:])
else:
raise click.UsageError("Got too many YAML documents in {}".format(filename))
# post['blurb'] = render(post['blurb'].strip())
# post['body'] = render(post['body'].strip())
post['blurb'] = post['blurb'].strip()
post['body'] = post['body'].strip()
for tag in post['tags']:
tags.add(tag)
posts.append(post)
blob['posts'] = sorted(posts, key=lambda x: x['date'], reverse=True)
blob['tags'] = list(tags)
with open(path, 'w') as f:
f.write(json.dumps(blob))
@cli.command()
@click.option(
'--path',
help='path to the Posty site',
required=True
)
def posty_import(path):
"""
Import posts and pages from an existing Posty 1.x site
All YAML files are read in and in the case of posts, a blurb is generated
if one doesn't already exist by singling out the first paragraph.
"""
if not all(os.path.exists('_pages'), os.path.exists('_posts')):
raise click.UsageError('You must run `init` first!')
click.echo('Importing site at {} ...'.format(path))
# Simply copy pages over, nothing special to do
for page in os.listdir(os.path.join(path, '_pages')):
orig_path = os.path.join(path, '_pages', page)
new_path = os.path.join('_pages', page)
shutil.copy(orig_path, new_path)
old_posts_path = os.path.join(path, '_posts')
for post in os.listdir(old_posts_path):
old_post = open(os.path.join(old_posts_path, post)).read()
click.echo(post)
new_post = convert_from_posty(old_post)
with open(os.path.join('_posts', post), 'w') as f:
f.write(new_post)
click.echo('Done!')
# Utility functions
def convert_from_posty(old_post):
"""
Converts an old Posty post (a string) into a new-style post with a blurb
and everything. Returns a string containing the three YAML documents.
"""
old_post = old_post.replace("\r\n", "\n")
docs = old_post.split("---\n")
new_post = ''
# Convert the metadata
meta = yaml.load(docs[1])
meta.setdefault('tags', [])
new_post += yaml.dump(meta)
# Create a blurb out of the first paragraph
body = docs[2].strip().split("\n\n")
blurb = body[0]
rest_of_post = "\n\n".join(body[1:])
new_post += "---\n"
new_post += blurb
# Drop in the rest of the post
new_post += "\n---\n"
new_post += rest_of_post
return new_post
def render(thing):
"""
Renders a specific thing using Markdown
"""
return markdown.markdown(thing, extensions=[
'markdown.extensions.fenced_code',
])
if __name__ == '__main__':
cli()