Cli feature (#29)

* Move all cli options to single bin file, export fns from each file instead

* Make username/title required in cli itself

* fix username not being parsed, set desc separately

* read outDir from env var, defaults to dist

* set outDir env var in cli to cwd

* Move default configs to default dir

* handle blog.json not existing

* fix assets path

* make build function async

* update clean command

* added defaults to CLI
This commit is contained in:
Rohit Gohri 2019-05-23 10:30:49 +05:30 committed by imfunny
parent 2ec61a9132
commit e62e4c05b3
10 changed files with 169 additions and 99 deletions

View File

@ -50,6 +50,7 @@
},800);
},1500);
$.getJSON("blog.json", function(blog){
blog = blog || [];
if(blog.length == 0){
return document.getElementById("blog_section").style.display = "none";
}
@ -68,6 +69,8 @@
</a>
`);
}
}).fail(function(){
return document.getElementById("blog_section").style.display = "none";
});
</script>
</body>

44
bin/gitfolio.js Normal file
View File

@ -0,0 +1,44 @@
#! /usr/bin/env node
/* Argument parser */
const program = require('commander');
process.env.OUT_DIR = process.env.OUT_DIR || process.cwd();
const {buildCommand} = require('../build');
const {updateCommand} = require('../update');
const {blogCommand} = require('../blog');
const {version} = require('../package.json');
program
.command('build <username>')
.description('Build site with your GitHub username. This will be used to customize your site')
.option('-t, --theme [theme]', 'specify a theme to use', 'light')
.option('-b, --background [background]', 'set the background image')
.option('-f, --fork', 'includes forks with repos')
.option('-s, --sort [sort]', 'set default sort for repository', 'created')
.option('-o, --order [order]', 'set default order on sort', 'asc')
.action(buildCommand)
program
.command('update')
.action(updateCommand);
program
.command('blog <title>')
.description('Create blog with specified title')
.option('-s, --subtitle [subtitle]', 'give blog a subtitle', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
.option('-p, --pagetitle [pagetitle]', 'give blog page a title')
.option('-f, --folder [folder]', 'give folder a title (use "-" instead of spaces)')
.action(blogCommand);
program.on('command:*', () => {
console.log('Unknown Command: ' + program.args.join(' '))
program.help()
});
program
.version(version, '-v --version')
.usage('<command> [options]')
.parse(process.argv);
if (program.args.length === 0) program.help();

51
blog.js
View File

@ -1,31 +1,23 @@
const program = require('commander');
const fs = require('fs');
const jsdom = require('jsdom').JSDOM,
options = {
resources: "usable"
};
program
.version('0.1.2')
.option('-t, --title [title]', 'give blog a title')
.option('-s, --subtitle [subtitle]', 'give blog a subtitle', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
.option('-p, --pagetitle [pagetitle]', 'give blog page a title')
.option('-f, --folder [folder]', 'give folder a title (use "-" instead of spaces)')
.parse(process.argv);
const {getBlog, outDir} = require('./utils');
function createBlog(title, subtitle, pagetitle, folder) {
// Checks to make sure this directory actually exists
// and creates it if it doesn't
if (!fs.existsSync(`./dist/blog/`)){
fs.mkdirSync(`./dist/blog/`, { recursive: true }, err => {});
if (!fs.existsSync(`${outDir}/blog/`)){
fs.mkdirSync(`${outDir}/blog/`, { recursive: true }, err => {});
}
if (!fs.existsSync(`./dist/blog/${folder}`)){
fs.mkdirSync(`./dist/blog/${folder}`, { recursive: true });
if (!fs.existsSync(`${outDir}/blog/${folder}`)){
fs.mkdirSync(`${outDir}/blog/${folder}`, { recursive: true });
}
fs.copyFile('./assets/blog/blogTemplate.html', `./dist/blog/${folder}/index.html`, (err) => {
fs.copyFile(`${__dirname}/assets/blog/blogTemplate.html`, `${outDir}/blog/${folder}/index.html`, (err) => {
if (err) throw err;
jsdom.fromFile(`./dist/blog/${folder}/index.html`, options).then(function (dom) {
jsdom.fromFile(`${outDir}/blog/${folder}/index.html`, options).then(function (dom) {
let window = dom.window, document = window.document;
var style = document.createElement("link");
style.setAttribute("rel","stylesheet")
@ -36,7 +28,7 @@ function createBlog(title, subtitle, pagetitle, folder) {
document.getElementById("blog_title").textContent = title;
document.getElementById("blog_sub_title").textContent = subtitle;
fs.writeFile(`./dist/blog/${folder}/index.html`, '<!DOCTYPE html>'+window.document.documentElement.outerHTML, function (error){
fs.writeFile(`${outDir}/blog/${folder}/index.html`, '<!DOCTYPE html>'+window.document.documentElement.outerHTML, async function (error){
if (error) throw error;
var blog_data = {
"url_title": pagetitle,
@ -44,14 +36,11 @@ function createBlog(title, subtitle, pagetitle, folder) {
"sub_title": subtitle,
"top_image": "https://images.unsplash.com/photo-1553748024-d1b27fb3f960?w=1450",
"visible": true }
fs.readFile("./dist/blog.json", function (err , data) {
const old_blogs = await getBlog();
old_blogs.push(blog_data);
fs.writeFile(`${outDir}/blog.json`, JSON.stringify(old_blogs, null, ' '), function(err){
if (err) throw err;
var old_blogs = JSON.parse(data);
old_blogs.push(blog_data);
fs.writeFile('./dist/blog.json', JSON.stringify(old_blogs, null, ' '), function(err){
if (err) throw err;
console.log('Blog Created Successfully in "blog" folder.');
});
console.log('Blog Created Successfully in "blog" folder.');
});
});
}).catch(function(error){
@ -60,18 +49,20 @@ function createBlog(title, subtitle, pagetitle, folder) {
});
}
if (program.title) {
function blogCommand(title, program) {
/* Check if build has been executed before blog this will prevent it from giving "link : index.css" error */
if (!fs.existsSync(`./dist/index.html`) || !fs.existsSync(`./dist/index.css`)){
if (!fs.existsSync(`${outDir}/index.html`) || !fs.existsSync(`${outDir}/index.css`)){
return console.log("You need to run build command before using blog one");
}
if (!program.pagetitle) {
program.pagetitle = program.title;
program.pagetitle = title;
}
if (!program.folder) {
program.folder = program.title;
program.folder = title;
}
createBlog(program.title, program.subtitle, program.pagetitle, program.folder);
} else {
console.log("Provide a title to create a new blog");
createBlog(title, program.subtitle, program.pagetitle, program.folder);
}
module.exports = {
blogCommand,
};

View File

@ -1,5 +1,3 @@
/* Argument parser */
const program = require('commander');
/* Filepath utilities */
const path = require('path');
/* Promise library */
@ -9,22 +7,10 @@ const hbs = require('handlebars');
from callback-passed async functions */
const fs = bluebird.promisifyAll(require('fs'));
const { updateHTML } = require('./populate');
const { getConfig, outDir } = require('./utils');
/* Specify the options the program uses */
program
.version('0.1.2')
.option('-n, --name [username]', 'your GitHub username. This will be used to customize your site')
.option('-t, --theme [theme]', 'specify a theme to use')
.option('-b, --background [background]', 'set the background image')
.option('-f, --fork', 'includes forks with repos')
.option('-s, --sort [sort]', 'set default sort for repository')
.option('-o, --order [order]', 'set default order on sort')
.parse(process.argv);
const config = './dist/config.json';
const assetDir = path.resolve('./assets/');
const outDir = path.resolve('./dist/');
const assetDir = path.resolve(`${__dirname}/assets/`);
const config = path.join(outDir, 'config.json');
/**
* Creates the stylesheet used by the site from a template stylesheet.
@ -32,9 +18,12 @@ const outDir = path.resolve('./dist/');
* Theme styles are added to the new stylesheet depending on command line
* arguments.
*/
async function populateCSS() {
async function populateCSS({
theme = 'light',
background = 'https://images.unsplash.com/photo-1553748024-d1b27fb3f960?w=1450',
} = {}) {
/* Get the theme the user requests. Defaults to 'light' */
let theme = `${program.theme || 'light'}.css`; /* Site theme, defaults to 'light' */
theme = `${theme}.css`;
let template = path.resolve(assetDir, 'index.css');
let stylesheet = path.join(outDir, 'index.css');
@ -58,41 +47,41 @@ async function populateCSS() {
themeSource = themeSource.toString('utf-8');
let themeTemplate = hbs.compile(themeSource);
let styles = themeTemplate({
'background': `${program.background || 'https://images.unsplash.com/photo-1553748024-d1b27fb3f960?w=1450'}`
'background': `${background}`
})
/* Add the user-specified styles to the new stylesheet */
await fs.appendFileAsync(stylesheet, styles);
/* Update the config file with the user's theme choice */
let data = await fs.readFileAsync(config);
data = JSON.parse(data);
const data = await getConfig();
data[0].theme = theme;
await fs.writeFileAsync(config, JSON.stringify(data, null, ' '));
}
async function populateConfig(sort, order, includeFork) {
let data = await fs.readFileAsync(config);
data = JSON.parse(data);
const data = await getConfig();
data[0].sort = sort;
data[0].order = order;
data[0].includeFork = includeFork;
await fs.writeFileAsync(config, JSON.stringify(data, null, ' '));
}
populateCSS();
if (typeof program.name === 'string' && program.name.trim() !== '') {
async function buildCommand(username, program) {
await populateCSS(program);
let sort = program.sort ? program.sort : 'created';
let order = "asc";
let includeFork = false;
if(program.order){
order = ('%s', program.order);
if(program.order){
order = ('%s', program.order);
}
if(program.fork){
includeFork = true;
}
populateConfig(sort, order, includeFork);
updateHTML(('%s', program.name), sort, order, includeFork);
} else {
console.error("Error: Please provide a GitHub username.");
}
await populateConfig(sort, order, includeFork);
updateHTML(('%s', username), sort, order, includeFork);
}
module.exports = {
buildCommand,
};

View File

@ -1,20 +1,22 @@
{
"name": "gitfolio",
"version": "0.1.2",
"description": "portfolio website for showcasing your work",
"main": "build.js",
"scripts": {
"clean": "rm -f dist/index.*",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "imfunny",
"license": "ISC",
"dependencies": {
"bluebird": "^3.5.4",
"commander": "^2.20.0",
"github-emoji": "^1.1.0",
"got": "^9.6.0",
"handlebars": "^4.1.2",
"jsdom": "^15.1.0"
}
}
{
"name": "gitfolio",
"version": "0.1.2",
"description": "portfolio website for showcasing your work",
"main": "build.js",
"bin": "bin/gitfolio.js",
"scripts": {
"cli": "OUT_DIR='./dist' node bin/gitfolio.js",
"clean": "rm -rf ./dist/*",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "imfunny",
"license": "ISC",
"dependencies": {
"bluebird": "^3.5.4",
"commander": "^2.20.0",
"github-emoji": "^1.1.0",
"got": "^9.6.0",
"handlebars": "^4.1.2",
"jsdom": "^15.1.0"
}
}

View File

@ -5,6 +5,7 @@ const jsdom = require('jsdom').JSDOM,
options = {
resources: "usable"
};
const { getConfig, outDir } = require('./utils');
function convertToEmoji(text) {
if (text == null) return;
@ -29,7 +30,7 @@ function convertToEmoji(text) {
module.exports.updateHTML = (username, sort, order, includeFork) => {
//add data to assets/index.html
jsdom.fromFile("./assets/index.html", options).then(function (dom) {
jsdom.fromFile(`${__dirname}/assets/index.html`, options).then(function (dom) {
let window = dom.window, document = window.document;
(async () => {
try {
@ -115,18 +116,15 @@ module.exports.updateHTML = (username, sort, order, includeFork) => {
<span style="display:${user.location == null || !user.location ? 'none' : 'block'};"><i class="fas fa-map-marker-alt"></i> &nbsp;&nbsp; ${user.location}</span>
<span style="display:${user.hireable == false || !user.hireable ? 'none' : 'block'};"><i class="fas fa-user-tie"></i> &nbsp;&nbsp; Available for hire</span>`;
//add data to config.json
fs.readFile("./dist/config.json", function (err, data) {
const data = await getConfig();
data[0].username = user.login;
data[0].name = user.name;
data[0].userimg = user.avatar_url;
fs.writeFile(`${outDir}/config.json`, JSON.stringify(data, null, ' '), function (err) {
if (err) throw err;
data = JSON.parse(data);
data[0].username = user.login;
data[0].name = user.name;
data[0].userimg = user.avatar_url;
fs.writeFile('./dist/config.json', JSON.stringify(data, null, ' '), function (err) {
if (err) throw err;
console.log("Config file updated.");
});
console.log("Config file updated.");
});
fs.writeFile('dist/index.html', '<!DOCTYPE html>' + window.document.documentElement.outerHTML, function (error) {
fs.writeFile(`${outDir}/index.html`, '<!DOCTYPE html>' + window.document.documentElement.outerHTML, function (error) {
if (error) throw error;
console.log("Build Complete");
});

View File

@ -1,9 +1,9 @@
const fs = require('fs');
const {getConfig, outDir} = require('./utils');
const {updateHTML} = require('./populate');
fs.readFile("./dist/config.json", function (err , data) {
if (err) throw err;
data = JSON.parse(data);
async function updateCommand() {
const data = await getConfig();
var username = data[0].username;
var sort = data[0].sort;
var order = data[0].order;
@ -13,4 +13,8 @@ fs.readFile("./dist/config.json", function (err , data) {
return;
}
updateHTML(username, sort, order, includeFork);
});
}
module.exports = {
updateCommand,
};

39
utils.js Normal file
View File

@ -0,0 +1,39 @@
const path = require('path');
const bluebird = require('bluebird');
const fs = bluebird.promisifyAll(require('fs'));
const outDir = path.resolve(process.env.OUT_DIR || './dist/');
const configPath = path.join(outDir, 'config.json');
const blogPath = path.join(outDir, 'blog.json');
const defaultConfigPath = path.resolve(`${__dirname}/default/config.json`);
const defaultBlogPath = path.resolve(`${__dirname}/default/blog.json`);
/**
* Tries to read file from out dir,
* if not present returns default file contents
*/
async function getFileWithDefaults(file, defaultFile) {
try {
await fs.accessAsync(file, fs.constants.F_OK);
} catch (err) {
const defaultData = await fs.readFileAsync(defaultFile);
return JSON.parse(defaultData);
}
const data = await fs.readFileAsync(file);
return JSON.parse(data);
}
async function getConfig() {
return getFileWithDefaults(configPath, defaultConfigPath);
}
async function getBlog() {
return getFileWithDefaults(blogPath, defaultBlogPath);
}
module.exports = {
outDir,
getConfig,
getBlog,
};