Comparing version 0.0.1 to 0.0.2
@@ -5,6 +5,8 @@ const logger = require('./src/logger') | ||
const commands = process.argv.splice(process.execArgv.length + 2) | ||
logger.log(logo) | ||
main().catch(err => { | ||
main(commands).catch(err => { | ||
logger.error('Generation finished with error: ', err) | ||
}) |
{ | ||
"name": "symply", | ||
"version": "0.0.1", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "node index.js", | ||
"test": "tape test/**/*.js" | ||
}, | ||
"version": "0.0.2", | ||
"description": "A dead-simple Bootstrap static site generator.", | ||
"author": "Oleg Legun <oleg.legun@gmail.com>", | ||
"homepage": "https://github.com/oleglegun/symply#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/oleglegun/static-site-templating-engine.git" | ||
"url": "git+https://https://github.com/oleglegun/symply.git" | ||
}, | ||
"author": "Oleg Legun <oleg.legun@gmail.com>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/oleglegun/static-site-templating-engine/issues" | ||
"url": "https://github.com/oleglegun/symply/issues" | ||
}, | ||
"homepage": "https://github.com/oleglegun/static-site-templating-engine#readme", | ||
"license": "MIT", | ||
"bin": { | ||
"symply": "./bin/symply.js" | ||
}, | ||
"preferGlobal": true, | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "node index.js", | ||
"test": "tape test/**/*.js | tap-spec" | ||
}, | ||
"dependencies": { | ||
"fs-extra": "^8.1.0", | ||
"handlebars": "^4.7.2", | ||
"js-yaml": "^3.13.1" | ||
"js-yaml": "^3.13.1", | ||
"prettier": "^1.19.1" | ||
}, | ||
"devDependencies": { | ||
"tap-spec": "^5.0.0", | ||
"tape": "^4.13.0" | ||
} | ||
} |
@@ -1,22 +0,26 @@ | ||
<div align="center"> | ||
<img src="./symply-logo.jpg" width="50%"/> | ||
</div> | ||
![Logo](./assets/logo.png) | ||
> A dead simple Bootstrap static site generator for personal sites, landing pages and other small projects, where you want to be in control of everything. | ||
A dead-simple **Bootstrap static site generator** with great flexibility and control. | ||
Main advantages: | ||
- Minimum magic - maximum transparency | ||
- Total control of the generation process | ||
- Only few dependencies | ||
## Quick Start | ||
**Symply** is based on well-known fast and reliable [Handlebars.js](https://github.com/wycats/handlebars.js) templating engine. | ||
### Partials | ||
Great for personal sites, landing pages and other small projects where you **want to be in control of everything**. | ||
partials/block.html | ||
## Installation | ||
```html | ||
`npm -g install symply` | ||
``` | ||
## Quick Start | ||
source/index.html | ||
```shell | ||
symply init # generate file structure | ||
```html | ||
{{> block }} | ||
symply generate | ||
``` | ||
Coming soon... |
@@ -14,7 +14,6 @@ const fs = require('fs') | ||
const DEFAULT_CONFIGURATION = { | ||
SOURCE_DIR_NAME: 'source', | ||
TEMPLATE_DIR_NAME: 'templates', | ||
VIEWS_DIR_NAME: 'views', | ||
SOURCE_DIR_NAME: 'src', | ||
DISTRIBUTION_DIR_NAME: 'dist', | ||
PARTIALS_DIR_NAME: 'partials', | ||
VIEWS_DIR_NAME: 'views', | ||
} | ||
@@ -27,16 +26,14 @@ | ||
{ | ||
name: HELPERS_FILE_NAME, | ||
name: GLOBALS_FILE_NAME, | ||
dir: '.', | ||
contents: 'module.exports = {\n\t\n}' | ||
contents: "module.exports = {\n\tsiteName: 'My new site',\n}", | ||
}, | ||
{ | ||
name: GLOBALS_FILE_NAME, | ||
name: HELPERS_FILE_NAME, | ||
dir: '.', | ||
contents: 'module.exports = {\n\t\n}' | ||
} | ||
contents: 'module.exports = {\n\tmyCustomHelper: (data) => { return data }\n}', | ||
}, | ||
] | ||
function getConfiguration(configurationFilename) { | ||
function getConfiguration(configurationFilename) { | ||
try { | ||
@@ -57,4 +54,5 @@ const configuration = yaml.safeLoad( | ||
logger.info( | ||
`Configuration file (${configurationFilename}) is not found. Using default configuration:\n`, | ||
JSON.stringify(DEFAULT_CONFIGURATION, null, 2) | ||
`Configuration file (${configurationFilename}) is not found. Using default configuration:\n${getPrintableConfigurationRepresentation( | ||
DEFAULT_CONFIGURATION | ||
)}` | ||
) | ||
@@ -70,3 +68,6 @@ return DEFAULT_CONFIGURATION | ||
logger.info(err.message) | ||
logger.info(`Using default configuration:\n`, JSON.stringify(DEFAULT_CONFIGURATION, null, 2)) | ||
logger.info( | ||
`Using default configuration:\n`, | ||
getPrintableConfigurationRepresentation(DEFAULT_CONFIGURATION) | ||
) | ||
return DEFAULT_CONFIGURATION | ||
@@ -97,5 +98,24 @@ } else { | ||
/** | ||
* @param {Object<string, any>} configObject | ||
* @returns {string} | ||
*/ | ||
function getPrintableConfigurationRepresentation(configObject) { | ||
let result = '' | ||
let paddingChars = ' ' | ||
Object.keys(configObject).forEach(key => { | ||
const value = typeof configObject[key] === 'string' ? `'${configObject[key]}'` : configObject[key] | ||
result += `${paddingChars}${key}: ${value}\n` | ||
}) | ||
return result | ||
} | ||
module.exports = { | ||
getConfiguration, | ||
systemFilesToBeCreated | ||
} | ||
systemFilesToBeCreated, | ||
GLOBALS_FILE_NAME, | ||
HELPERS_FILE_NAME, | ||
} |
@@ -22,5 +22,5 @@ const fs = require('fs-extra') | ||
if (removeScanPath) { | ||
return scanFilesInDirectory(scanPath, '', readFileContents, scanNestedDirectories) | ||
return scanFilesInDirectory(scanPath, scanPath, readFileContents, scanNestedDirectories) | ||
} | ||
return scanFilesInDirectory(scanPath, scanPath, readFileContents, scanNestedDirectories) | ||
return scanFilesInDirectory(scanPath, '', readFileContents, scanNestedDirectories) | ||
} | ||
@@ -96,8 +96,15 @@ | ||
/** | ||
* | ||
* @param {string} directoryPath | ||
* | ||
* @returns {boolean} Returns true if the directory is created | ||
*/ | ||
function createDirectoryIfNotExists(directoryPath) { | ||
if (fs.existsSync(directoryPath)) { | ||
return | ||
return false | ||
} | ||
fs.mkdirSync(directoryPath) | ||
return true | ||
} | ||
@@ -109,4 +116,4 @@ | ||
* @param {string} [contents] | ||
* | ||
* @returns {boolean} Returns true if file is created | ||
* | ||
* @returns {boolean} Returns true if the file is created | ||
*/ | ||
@@ -118,2 +125,4 @@ function createFileIfNotExists(filePath, contents) { | ||
fs.ensureFileSync(filePath) | ||
fs.writeFileSync(filePath, contents, { encoding: 'utf8' }) | ||
@@ -123,2 +132,17 @@ return true | ||
/** | ||
* Joins path parts and returns an absolute path | ||
* @param {string[]} pathParts | ||
* @returns {string} | ||
*/ | ||
function joinAndResolvePath(...pathParts) { | ||
const joinedPath = path.join(...pathParts) | ||
if (path.isAbsolute(joinedPath)) { | ||
return joinedPath | ||
} else { | ||
return path.join(process.cwd(), joinedPath) | ||
} | ||
} | ||
module.exports = { | ||
@@ -132,2 +156,3 @@ scanFiles, | ||
createFileIfNotExists, | ||
joinAndResolvePath, | ||
} |
120
src/main.js
const fs = require('fs') | ||
const path = require('path') | ||
const prettier = require('prettier') | ||
const Handlebars = require('handlebars') | ||
const config = require('./config') | ||
const logger = require('./logger') | ||
const config = require('./config') | ||
const strings = require('./strings') | ||
const { loadPartials } = require('./partials') | ||
const { loadViews } = require('./views') | ||
const { loadTemplates } = require('./templates') | ||
const strings = require('./strings') | ||
const { scanFiles, isFileExtensionValid, getFileContents, createFileIfNotExists } = require('./fs-helpers') | ||
const { | ||
scanFiles, | ||
isFileExtensionValid, | ||
getFileContents, | ||
createFileIfNotExists, | ||
createDirectoryIfNotExists, | ||
clearDirectoryContents, | ||
copyFileAsync, | ||
joinAndResolvePath, | ||
} = require('./fs-helpers') | ||
const Handlebars = require('handlebars') | ||
const CONFIGURATION_FILE_NAME = 'configuration.yaml' | ||
async function main(command) { | ||
command = 'init' | ||
switch (command) { | ||
async function main(commands) { | ||
switch (commands[0]) { | ||
case 'init': | ||
@@ -24,3 +31,3 @@ logger.info('Initializing project...') | ||
case 'start': | ||
generate() | ||
await generate() | ||
break | ||
@@ -32,3 +39,3 @@ default: | ||
function generate() { | ||
async function generate() { | ||
/*----------------------------------------------------------------------------- | ||
@@ -41,3 +48,3 @@ * Load configuration | ||
/*----------------------------------------------------------------------------- | ||
* Register partials | ||
* Load and register partials | ||
*----------------------------------------------------------------------------*/ | ||
@@ -52,21 +59,25 @@ | ||
/*----------------------------------------------------------------------------- | ||
* Load views | ||
* Load views and globals | ||
*----------------------------------------------------------------------------*/ | ||
const views = loadViews(configuration.VIEWS_DIR_NAME) | ||
// console.log(views) | ||
const globals = require(joinAndResolvePath(config.GLOBALS_FILE_NAME)) | ||
/*----------------------------------------------------------------------------- | ||
* Register helpers | ||
* Load and register helpers; Inject views | ||
*----------------------------------------------------------------------------*/ | ||
if (fs.existsSync(configuration.HELPERS_FILE_NAME)) { | ||
const helpersPath = path.join(process.cwd(), configuration.HELPERS_FILE_NAME) | ||
if (fs.existsSync(config.HELPERS_FILE_NAME)) { | ||
const helpersPath = joinAndResolvePath(config.HELPERS_FILE_NAME) | ||
const helpers = require(helpersPath) | ||
Object.keys(helpers).forEach(helperName => { | ||
Handlebars.registerHelper(helperName, injectViewDataDecorator(helpers[helperName], views)) | ||
Handlebars.registerHelper(helperName, injectHelperContextDecorator(helpers[helperName], views, globals)) | ||
}) | ||
} | ||
/*----------------------------------------------------------------------------- | ||
* Scan source files, detect template files for processing | ||
*----------------------------------------------------------------------------*/ | ||
const allSourceFiles = scanFiles(configuration.SOURCE_DIR_NAME, false, true, true) | ||
@@ -82,11 +93,52 @@ | ||
/*----------------------------------------------------------------------------- | ||
* Compile templates with passing globals | ||
* Format HTML output | ||
* Save results to the distribution directory | ||
*----------------------------------------------------------------------------*/ | ||
clearDirectoryContents(joinAndResolvePath(configuration.DISTRIBUTION_DIR_NAME)) | ||
templateSourceFiles.forEach(file => { | ||
const templateContents = getFileContents(path.join(file.dirname, file.name)) | ||
const result = Handlebars.compile(templateContents)({}) | ||
console.log(result) | ||
const templateContents = getFileContents( | ||
joinAndResolvePath(configuration.SOURCE_DIR_NAME, file.dirname, file.name) | ||
) | ||
let formattedHTML = '' | ||
try { | ||
const result = Handlebars.compile(templateContents)(globals) | ||
formattedHTML = prettier.format(result, { parser: 'html' }) | ||
} catch (err) { | ||
if (err instanceof RangeError) { | ||
logger.error( | ||
'Recursive partial structure detected. Check your partials and source files. ' + | ||
'Make sure that partials are not calling each other using {{> partialName}}.' | ||
) | ||
} else { | ||
logger.error(err) | ||
} | ||
process.exit(1) | ||
} | ||
createFileIfNotExists( | ||
joinAndResolvePath(configuration.DISTRIBUTION_DIR_NAME, file.dirname, file.name), | ||
formattedHTML | ||
) | ||
}) | ||
// copy other source files to the destination folder | ||
// | ||
/*----------------------------------------------------------------------------- | ||
* Copy other source files to the distribution directory | ||
*----------------------------------------------------------------------------*/ | ||
const copyPromises = [] | ||
otherSourceFiles.forEach(file => { | ||
const srcFilePath = joinAndResolvePath(configuration.SOURCE_DIR_NAME, file.dirname, file.name) | ||
const distFilePath = joinAndResolvePath(configuration.DISTRIBUTION_DIR_NAME, file.dirname, file.name) | ||
copyPromises.push(copyFileAsync(srcFilePath, distFilePath)) | ||
}) | ||
await Promise.all(copyPromises) | ||
logger.info('Generation successfully finished.') | ||
@@ -116,12 +168,20 @@ } | ||
.filter(key => key.endsWith('DIR_NAME')) | ||
.forEach(dirName => {}) | ||
.forEach(dirNameKey => { | ||
const dirName = configuration[dirNameKey] | ||
const dirIsCreated = createDirectoryIfNotExists(dirName) | ||
if (dirIsCreated) { | ||
logger.info(`Created directory: ${dirName}/`) | ||
} | ||
}) | ||
} | ||
function injectViewDataDecorator(helperFunction, views) { | ||
return function(context) { | ||
const viewName = context && context.hash && context.hash.view | ||
function injectHelperContextDecorator(helperFunction, views, globals) { | ||
return function(params) { | ||
const viewName = params && params.hash && params.hash.view | ||
if (viewName) { | ||
return new Handlebars.SafeString(helperFunction(views[viewName])) | ||
return new Handlebars.SafeString(helperFunction({ globals, view: views[viewName], params: params.hash })) | ||
} else { | ||
return new Handlebars.SafeString(helperFunction(context)) | ||
return new Handlebars.SafeString(helperFunction({ globals, params: params.hash })) | ||
} | ||
@@ -131,4 +191,2 @@ } | ||
function registerHelpers() {} | ||
module.exports = main |
module.exports = { | ||
logo: ` | ||
███████╗██╗ ██╗███╗ ███╗██████╗ ██╗ ██╗ ██╗ | ||
██╔════╝╚██╗ ██╔╝████╗ ████║██╔══██╗██║ ╚██╗ ██╔╝ | ||
███████╗ ╚████╔╝ ██╔████╔██║██████╔╝██║ ╚████╔╝ | ||
╚════██║ ╚██╔╝ ██║╚██╔╝██║██╔═══╝ ██║ ╚██╔╝ | ||
███████║ ██║ ██║ ╚═╝ ██║██║ ███████╗██║ | ||
╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ | ||
======= Bootstrap Static Site Generator ======= | ||
███████╗ ████████╗ ███╗ ███╗ ████████╗ ██╗ ████████╗ | ||
████/══╝ ╚██████╔╝ ████╗ ████║ ███╔═══██╗ ██║ ╚██████╔╝ | ||
███████╗ ╚████╔╝ █████╗ █████║ ████████╔╝ ██║ ╚████╔╝ | ||
╚═/████║ ╚██╔╝ ████████████║ ███╔════╝ ██║ ╚██╔╝ | ||
███████║ ██║ ████████████║ ███║ ███████╗██║ | ||
╚══════╝ ╚═╝ ╚═══════════╝ ╚══╝ ╚══════╝╚═╝ | ||
======= Bootstrap Static Site Generator ======= | ||
`, | ||
@@ -16,9 +17,11 @@ help: ` | ||
symply init Initialize project, creating necessary files and directories | ||
symply init Initialize project creating necessary files and directories | ||
symply generate Complile and generate static site files | ||
alias: symply start | ||
symply generate Compile and generate static site files | ||
alias: symply start | ||
symply serve Start development server | ||
symply configuration Print current generator configuration (default + configuration.yaml) | ||
symply serve Start development server in distribution folder | ||
`, | ||
} |
const test = require('tape') | ||
const path = require('path') | ||
const {scanFiles, isFileExtensionValid} = require('../src/fs-helpers') | ||
const { scanFiles, isFileExtensionValid, joinAndResolvePath } = require('../src/fs-helpers') | ||
test('––––––––––––––––––– fs-helpers.js tests ––––––––––––––––––––', function(t) { | ||
t.test('isFileExtensionValid()', function(st) { | ||
t.test('isFileExtensionValid(fileName, validExtensionsList)', function(st) { | ||
const testCases = [ | ||
@@ -11,9 +11,9 @@ { | ||
expected: true, | ||
message: '', | ||
message: 'correctly detect valid file extension', | ||
}, | ||
{ | ||
input: ['filename.html', ['js', 'md']], | ||
expected: false, | ||
message: '', | ||
message: 'correctly detect invalid file extension', | ||
}, | ||
@@ -24,6 +24,34 @@ ] | ||
testCases.forEach(({input, expected, message}) => { | ||
testCases.forEach(({ input, expected, message }) => { | ||
st.equal(isFileExtensionValid(...input), expected, message) | ||
}) | ||
}) | ||
t.test('joinAndResolvePath(...pathParts)', function(st) { | ||
const testCases = [ | ||
{ | ||
inputArgs: ['/path1', 'path2'], | ||
expected: '/path1/path2', | ||
message: 'correctly resolve absolute path', | ||
}, | ||
{ | ||
inputArgs: ['path1', 'path2'], | ||
expected: process.cwd() + '/path1/path2', | ||
message: 'correctly resolve relative path', | ||
}, | ||
{ | ||
inputArgs: ['path1', '.', 'path2', 'path3'], | ||
expected: process.cwd() + '/path1/path2/path3', | ||
message: 'correctly resolve relative path with dot character', | ||
}, | ||
] | ||
st.plan(testCases.length) | ||
testCases.forEach(({ inputArgs, expected, message }) => { | ||
st.equal(joinAndResolvePath(...inputArgs), expected, message) | ||
}) | ||
}) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
142404
600
0
1
26
4
2
+ Addedprettier@^1.19.1
+ Addedprettier@1.19.1(transitive)