├── .gitignore ├── .npmrc ├── .travis.yml ├── example ├── .gitignore ├── .npmrc ├── assets │ └── basic.png ├── content │ ├── about │ │ ├── example.png │ │ ├── example.png.txt │ │ └── index.txt │ └── index.txt ├── index.js ├── package.json └── src │ ├── design │ └── index.js │ ├── stores │ └── content.js │ └── views │ ├── custom.js │ ├── index.js │ ├── main.js │ └── notfound.js ├── greenkeeper.json ├── index.js ├── lib ├── defaults.js ├── index.js ├── readFile.js ├── readFileSync.js ├── readFiles.js ├── readFilesSync.js ├── readPage.js ├── readPageSync.js ├── readSite.js └── readSiteSync.js ├── package.json ├── readme.md ├── test.js ├── transform.js └── utils ├── content.js ├── file.js └── page.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - '7' 3 | - '8' 4 | sudo: false 5 | language: node_js 6 | env: 7 | - CXX=g++-4.8 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-4.8 14 | script: npm run test -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ -------------------------------------------------------------------------------- /example/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /example/assets/basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jondashkyle/nanocontent/0549fefc53960344fa68fd463854400c48408357/example/assets/basic.png -------------------------------------------------------------------------------- /example/content/about/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jondashkyle/nanocontent/0549fefc53960344fa68fd463854400c48408357/example/content/about/example.png -------------------------------------------------------------------------------- /example/content/about/example.png.txt: -------------------------------------------------------------------------------- 1 | title: Custom meta -------------------------------------------------------------------------------- /example/content/about/index.txt: -------------------------------------------------------------------------------- 1 | title: About 2 | ---- 3 | view: custom 4 | ---- 5 | text: 6 | 7 | Some example text -------------------------------------------------------------------------------- /example/content/index.txt: -------------------------------------------------------------------------------- 1 | title: Example 2 | ---- 3 | text: yeah *whatsup* -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var css = require('sheetify') 2 | var hypha = require('hypha') 3 | var choo = require('choo') 4 | css('./src/design/index.js') 5 | 6 | var site = hypha.readSiteSync('./content', { 7 | parent: '/content' 8 | }) 9 | 10 | var app = choo() 11 | 12 | // content and routes 13 | app.use(require('./src/stores/content')(site)) 14 | app.route('*', require('./src/views/notfound')) 15 | 16 | // export and mount 17 | if (module.parent) module.exports = app 18 | else app.mount('body') 19 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hypha-example", 3 | "version": "1.0.0", 4 | "description": "nice", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "bankai start" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bel": "^6.0.0", 14 | "choo": "^6.5.1", 15 | "gr8": "^3.1.3", 16 | "markdown-it": "^8.4.0", 17 | "object-keys": "^1.0.11", 18 | "object-values": "^2.0.0", 19 | "sheetify": "^7.3.2", 20 | "xtend": "^4.0.1" 21 | }, 22 | "devDependencies": { 23 | "bankai": "^9.0.2" 24 | }, 25 | "browserify": { 26 | "transform": [ 27 | "../transform.js" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/src/design/index.js: -------------------------------------------------------------------------------- 1 | var gr8 = require('gr8') 2 | 3 | var utils = [ ] 4 | 5 | var colors = { 6 | white: '#fff', 7 | black: '#000' 8 | } 9 | 10 | utils.push({ 11 | prop: 'font-family', 12 | join: '-', 13 | vals: { 14 | sans: '-apple-system, BlinkMacSystemFont, sans-serif' 15 | } 16 | }) 17 | 18 | utils.push({ 19 | prop: { bgc: 'background-color' }, 20 | join: '-', 21 | vals: colors 22 | }) 23 | 24 | utils.push({ 25 | prop: { fc: 'color' }, 26 | join: '-', 27 | vals: colors 28 | }) 29 | 30 | var gr8css = gr8({ 31 | utils: utils 32 | }) 33 | 34 | module.exports = gr8css 35 | -------------------------------------------------------------------------------- /example/src/stores/content.js: -------------------------------------------------------------------------------- 1 | var objectKeys = require('object-keys') 2 | var xtend = require('xtend') 3 | var views = require('../views') 4 | 5 | module.exports = store 6 | 7 | function store (site) { 8 | return function content (state, emitter, app) { 9 | state.content = { } 10 | 11 | objectKeys(site).forEach(function (path) { 12 | // set view and extend state 13 | var page = site[path] 14 | var view = views[page.view] || views.main 15 | state.content[page.url] = page 16 | 17 | app.route(page.url, function (state, emit) { 18 | return view(xtend(state, { page: page }), emit) 19 | }) 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/src/views/custom.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | 3 | module.exports = view 4 | 5 | function view (state, emit) { 6 | var page = state.page || { } 7 | return html` 8 | 9 |
custom ${page.name}
10 | back 11 | 12 | ` 13 | } 14 | -------------------------------------------------------------------------------- /example/src/views/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | main: require('./main'), 3 | notfound: require('./notfound'), 4 | custom: require('./custom') 5 | } 6 | -------------------------------------------------------------------------------- /example/src/views/main.js: -------------------------------------------------------------------------------- 1 | var objectValues = require('object-values') 2 | var Markdown = require('markdown-it') 3 | var html = require('choo/html') 4 | var raw = require('bel/raw') 5 | var md = new Markdown() 6 | 7 | module.exports = view 8 | 9 | function view (state, emit) { 10 | var page = state.page || { } 11 | 12 | return html` 13 | 14 |
15 |
name ${page.name || 'no name'}
16 |
title ${page.title || 'Untitled'}
17 |
text ${raw(md.render(page.text || 'No content'))}
18 | ${page.pages ? pages() : ''} 19 |
20 | 21 | ` 22 | 23 | function pages () { 24 | return objectValues(page.pages).map(function (subpage) { 25 | return html`${subpage.name}` 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/src/views/notfound.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | 3 | module.exports = view 4 | 5 | function view (state, emit) { 6 | return html` 7 | 8 | not found 9 | 10 | ` 11 | } 12 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "example/package.json", 6 | "package.json" 7 | ] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var xtend = require('xtend') 2 | var fs = require('fs') 3 | var lib = require('./lib') 4 | 5 | module.exports = { 6 | readFile: readFile, 7 | readFileSync: readFileSync, 8 | readFiles: readFiles, 9 | readFilesSync: readFilesSync, 10 | readPage: readPage, 11 | readPageSync: readPageSync, 12 | readSite: readSite, 13 | readSiteSync: readSiteSync 14 | } 15 | 16 | function readFile (pathFile, opts, callback) { 17 | opts = xtend({ fs: fs }, opts) 18 | return lib.readFile(pathFile, opts, callback) 19 | } 20 | 21 | function readFileSync (pathFile, opts, callback) { 22 | opts = xtend({ fs: fs }, opts) 23 | return lib.readFileSync(pathFile, opts) 24 | } 25 | 26 | function readFiles (files, pathSite, opts, callback) { 27 | opts = xtend({ fs: fs }, opts) 28 | return lib.readFiles(files, pathSite, opts, callback) 29 | } 30 | 31 | function readFilesSync (pathFile, opts, callback) { 32 | opts = xtend({ fs: fs }, opts) 33 | return lib.readFilesSync(pathFile, opts) 34 | } 35 | 36 | function readPage (pathPage, opts, callback) { 37 | opts = xtend({ fs: fs }, opts) 38 | return lib.readPage(pathPage, opts, callback) 39 | } 40 | 41 | function readPageSync (pathPage, opts, callback) { 42 | opts = xtend({ fs: fs }, opts) 43 | return lib.readPageSync(pathPage, opts) 44 | } 45 | 46 | function readSite (pathSite, opts, callback) { 47 | opts = xtend({ fs: fs }, opts) 48 | return lib.readSite(pathSite, opts, callback) 49 | } 50 | 51 | function readSiteSync (pathSite, opts, callback) { 52 | opts = xtend(opts, { fs: fs }) 53 | return lib.readSiteSync(pathSite, opts) 54 | } 55 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reservedKeys: ['files', 'pages', 'url', 'name', 'path'], 3 | ignore: /(^[.#]|(?:__|~)$)/, 4 | encoding: 'utf8', 5 | file: 'index.txt', 6 | filetypes: { 7 | asset: ['.css', '.js'], 8 | archive: ['.zip'], 9 | audio: ['.mp3', '.wav', '.aiff'], 10 | document: ['.pdf'], 11 | image: ['.gif', '.jpg', '.jpeg', '.png', '.svg'], 12 | video: ['.mp4', '.mov'], 13 | font: ['.ttf', '.otf', '.woff', '.woff2'] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | readPage: require('./readPage'), 3 | readPageSync: require('./readPageSync'), 4 | readFile: require('./readFile'), 5 | readFiles: require('./readFiles'), 6 | readSite: require('./readSite'), 7 | readSiteSync: require('./readSiteSync') 8 | } 9 | -------------------------------------------------------------------------------- /lib/readFile.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | var utilFile = require('../utils/file') 4 | var readPage = require('./readPage') 5 | 6 | module.exports = readFile 7 | 8 | async function readFile (pathFile, opts) { 9 | assert.equal(typeof pathFile, 'string', 'arg1: pathFile must be type string') 10 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 11 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 12 | 13 | if (!utilFile.isFile(pathFile)) { 14 | return readPage(pathFile, opts) 15 | } else { 16 | return false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/readFileSync.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | var readPageSync = require('./readPageSync') 4 | var utilFile = require('../utils/file') 5 | 6 | module.exports = readFileSync 7 | 8 | function readFileSync (pathFile, opts) { 9 | assert.equal(typeof pathFile, 'string', 'arg1: pathFile must be type string') 10 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 11 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 12 | 13 | if (!utilFile.isFile(pathFile)) { 14 | return readPageSync(pathFile, opts) 15 | } else { 16 | return false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/readFiles.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | var defaults = require('./defaults') 4 | var readFile = require('./readFile') 5 | 6 | module.exports = readFiles 7 | 8 | async function readFiles (files, pathSite, opts) { 9 | assert.equal(typeof files, 'object', 'arg1: files must be type object') 10 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 11 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 12 | 13 | var output = { } 14 | 15 | // read the index 16 | if (files.indexOf(pathSite) < 0) { 17 | files.push(pathSite) 18 | } 19 | 20 | await Promise.all(files.map(read)) 21 | return output 22 | 23 | async function read (pathFile) { 24 | var content = await readFile(pathFile, opts) 25 | if (content && !content.name.match(defaults.ignore)) { 26 | output[content.url] = content 27 | } 28 | return content 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/readFilesSync.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | var readFileSync = require('./readFileSync') 4 | var defaults = require('./defaults') 5 | 6 | module.exports = readFilesSync 7 | 8 | function readFilesSync (files, pathSite, opts) { 9 | assert.equal(typeof files, 'object', 'arg1: files must be type object') 10 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 11 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 12 | 13 | var output = { } 14 | 15 | // read the index 16 | if (files.indexOf(pathSite) < 0) { 17 | files.push(pathSite) 18 | } 19 | 20 | files.forEach(function (pathFile) { 21 | if (typeof opts.onFile === 'function') opts.onFile(pathFile) 22 | var content = readFileSync(pathFile, opts) 23 | if (content && !content.name.match(defaults.ignore)) { 24 | output[content.url] = content 25 | } 26 | }) 27 | 28 | return output 29 | } 30 | -------------------------------------------------------------------------------- /lib/readPage.js: -------------------------------------------------------------------------------- 1 | var slash = require('normalize-path') 2 | var assert = require('assert') 3 | var smarkt = require('smarkt') 4 | var xtend = require('xtend') 5 | var path = require('path') 6 | var pify = require('pify') 7 | 8 | var utilFile = require('../utils/file') 9 | var defaults = require('./defaults') 10 | 11 | module.exports = readPage 12 | 13 | async function readPage (pathPage, opts) { 14 | assert.equal(typeof pathPage, 'string', 'arg1: pathPage must be type string') 15 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 16 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 17 | 18 | var fs = opts.fs.url ? opts.fs : pify(opts.fs) // web api or node 19 | var parse = typeof opts.parse === 'function' ? opts.parse : smarkt.parse 20 | var fileIndex = opts.file || defaults.file 21 | var fileExtname = path.extname(fileIndex) 22 | var filetypes = opts.filetypes || defaults.filetypes 23 | var pathRoot = opts.pathRoot || '' 24 | var pathUrl = utilFile.formatUrl(pathPage, pathRoot, opts.parent) 25 | var encoding = opts.encoding || defaults.encoding 26 | var content = await getContent() 27 | var childrenInput = await getChildren() 28 | var children = childrenInput 29 | .filter(file => utilFile.filterFile(file, fileIndex)) 30 | .reduce(utilFile.sortChildren, { files: [], pages: [] }) 31 | var files = await getFiles(children.files) 32 | var pages = getPages(children.pages) 33 | 34 | return xtend(content, { 35 | name: path.basename(pathPage), 36 | path: utilFile.formatUrl(pathPage, pathRoot), 37 | url: pathUrl, 38 | files: files, 39 | pages: pages 40 | }) 41 | 42 | async function getChildren () { 43 | try { 44 | return await fs.readdir(pathPage) 45 | } catch (err) { 46 | return [] 47 | } 48 | } 49 | 50 | async function getContent () { 51 | try { 52 | var content 53 | content = await fs.readFile(slash(path.join(pathPage, fileIndex)), encoding) 54 | content = parse(content) 55 | return content 56 | } catch (err) { 57 | return '' 58 | } 59 | } 60 | 61 | async function getFiles (files) { 62 | var result = { } 63 | await Promise.all(files.map(read)) 64 | return result 65 | 66 | async function read (pathFile) { 67 | var fileParsed = utilFile.getFileMeta({ 68 | pathFile: pathFile, 69 | pathRoot: pathRoot, 70 | filetypes: filetypes, 71 | pathParent: pathPage, 72 | pathSource: opts.source, 73 | pathSiteParent: opts.parent 74 | }) 75 | 76 | try { 77 | var fileMeta = pathFile + fileExtname 78 | var pathMeta = path.join(pathPage, fileMeta) 79 | var text = await fs.readFile(pathMeta, encoding) 80 | // set 81 | result[fileParsed.filename] = xtend(parse(text), fileParsed) 82 | files.splice(files.indexOf(fileMeta), 1) 83 | // cleanup 84 | delete result[fileMeta] 85 | return text 86 | } catch (err) { 87 | if (fileParsed.filename) { 88 | result[fileParsed.filename] = fileParsed 89 | } 90 | return Promise.resolve() 91 | } 92 | } 93 | } 94 | 95 | function getPages (pages) { 96 | return pages.reduce(function (result, pathSubpage) { 97 | var fileParsed = utilFile.getFileMeta({ 98 | pathRoot: pathRoot, 99 | pathFile: pathSubpage, 100 | filetypes: filetypes, 101 | pathParent: pathPage, 102 | pathSiteParent: opts.parent 103 | }) 104 | 105 | if (fileParsed.name) result[fileParsed.name] = fileParsed 106 | return result 107 | }, {}) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/readPageSync.js: -------------------------------------------------------------------------------- 1 | var slash = require('normalize-path') 2 | var assert = require('assert') 3 | var smarkt = require('smarkt') 4 | var xtend = require('xtend') 5 | var path = require('path') 6 | 7 | var utilFile = require('../utils/file') 8 | var defaults = require('./defaults') 9 | 10 | module.exports = readPageSync 11 | 12 | function readPageSync (pathPage, opts) { 13 | assert.equal(typeof pathPage, 'string', 'arg1: pathPage must be type string') 14 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 15 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 16 | 17 | var fs = opts.fs 18 | var parse = typeof opts.parse === 'function' ? opts.parse : smarkt.parse 19 | var fileIndex = opts.file || defaults.file 20 | var fileExtname = path.extname(fileIndex) 21 | var filetypes = opts.filetypes || defaults.filetypes 22 | var pathRoot = opts.pathRoot || '' 23 | var pathUrl = utilFile.formatUrl(pathPage, pathRoot, opts.parent) 24 | var encoding = opts.encoding || defaults.encoding 25 | var content = getContent() 26 | var children = getChildren() 27 | .filter(file => utilFile.filterFile(file, fileIndex)) 28 | .reduce(utilFile.sortChildren, { files: [ ], pages: [ ] }) 29 | var files = getFiles(children.files) 30 | var pages = getPages(children.pages) 31 | 32 | return xtend(content, { 33 | name: path.basename(pathPage), 34 | path: utilFile.formatUrl(pathPage, pathRoot), 35 | url: pathUrl, 36 | files: files, 37 | pages: pages 38 | }) 39 | 40 | function getChildren () { 41 | try { 42 | return fs.readdirSync(pathPage) 43 | } catch (err) { 44 | return [ ] 45 | } 46 | } 47 | 48 | function getContent () { 49 | try { 50 | var content 51 | content = fs.readFileSync(slash(path.join(pathPage, fileIndex)), encoding) 52 | content = parse(content) 53 | return content 54 | } catch (err) { 55 | return '' 56 | } 57 | } 58 | 59 | function getFiles (files) { 60 | return files.reduce(function (result, pathFile) { 61 | var fileParsed = utilFile.getFileMeta({ 62 | pathFile: pathFile, 63 | pathRoot: pathRoot, 64 | filetypes: filetypes, 65 | pathParent: pathPage, 66 | pathSiteParent: opts.parent 67 | }) 68 | 69 | try { 70 | var fileMeta = pathFile + fileExtname 71 | var text = fs.readFileSync(slash(path.join(pathPage, fileMeta)), encoding) 72 | // set 73 | result[fileParsed.filename] = xtend(parse(text), fileParsed) 74 | files.splice(files.indexOf(fileMeta), 1) 75 | // cleanup 76 | delete result[fileMeta] 77 | } catch (err) { 78 | if (fileParsed.filename) { 79 | result[fileParsed.filename] = fileParsed 80 | } 81 | } 82 | 83 | return result 84 | }, { }) 85 | } 86 | 87 | function getPages (pages) { 88 | return pages.reduce(function (result, pathSubpage) { 89 | var fileParsed = utilFile.getFileMeta({ 90 | pathRoot: pathRoot, 91 | pathFile: pathSubpage, 92 | filetypes: filetypes, 93 | pathParent: pathPage, 94 | pathSource: opts.source, 95 | pathSiteParent: opts.parent 96 | }) 97 | 98 | if (fileParsed.name) result[fileParsed.name] = fileParsed 99 | return result 100 | }, { }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/readSite.js: -------------------------------------------------------------------------------- 1 | var slash = require('normalize-path') 2 | var assert = require('assert') 3 | var path = require('path') 4 | var pify = require('pify') 5 | var glob = pify(require('glob')) 6 | 7 | var readFiles = require('./readFiles') 8 | 9 | module.exports = readSite 10 | 11 | async function readSite (pathSite, opts) { 12 | assert.equal(typeof pathSite, 'string', 'arg1: pathSite must be type string') 13 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 14 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 15 | 16 | // parent 17 | if (opts.parent === true) opts.parent = pathSite 18 | 19 | // loop through the files 20 | var files = await glob(slash(path.join(pathSite, '/**/*'))) 21 | return readFiles(files, pathSite, opts) 22 | } 23 | -------------------------------------------------------------------------------- /lib/readSiteSync.js: -------------------------------------------------------------------------------- 1 | var slash = require('normalize-path') 2 | var assert = require('assert') 3 | var path = require('path') 4 | var glob = require('glob') 5 | 6 | var readFilesSync = require('./readFilesSync') 7 | 8 | module.exports = readSiteSync 9 | 10 | function readSiteSync (pathSite, opts) { 11 | assert.equal(typeof pathSite, 'string', 'arg1: pathSite must be type string') 12 | assert.equal(typeof opts, 'object', 'arg2: opts must be type object') 13 | assert.equal(typeof opts.fs, 'object', 'arg2: opts.fs must be type object') 14 | 15 | // parent 16 | if (opts.parent === true) opts.parent = pathSite 17 | 18 | // loop through the files 19 | var files = glob.sync(slash(path.join(pathSite, '/**/*'))) 20 | return readFilesSync(files, pathSite, opts) 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanocontent", 3 | "version": "0.4.6", 4 | "description": "read a directory of content into an object", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard; ava" 8 | }, 9 | "keywords": [], 10 | "author": "Jon-Kyle (http://jon-kyle.com)", 11 | "license": "Apache-2.0", 12 | "bugs": { 13 | "url": "https://github.com/jondashkyle/nanocontent/issues" 14 | }, 15 | "homepage": "https://github.com/jondashkyle/nanocontent#readme", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/jondashkyle/nanocontent.git" 19 | }, 20 | "dependencies": { 21 | "glob": "^7.1.2", 22 | "js-yaml": "^3.10.0", 23 | "normalize-path": "^3.0.0", 24 | "object-keys": "^1.0.11", 25 | "object-values": "^2.0.0", 26 | "pify": "^3.0.0", 27 | "smarkt": "0.0.6", 28 | "static-module": "^2.2.5", 29 | "through2": "^2.0.3", 30 | "xtend": "^4.0.1" 31 | }, 32 | "devDependencies": { 33 | "ava": "^0.25.0", 34 | "standard": "^11.0.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

nanocontent

2 | 3 |
4 | 5 | Stability 6 | 7 | 8 | NPM version 9 | 10 |
11 | 12 |
13 | 14 | **work in progress...** 15 | 16 | Introducing flat content state. Read directories of content into an object. 17 | 18 | - Store all of your content in a directory 19 | - Each directory is a page 20 | - The content for each page is stored in a text file 21 | - Your file system becomes a router! 22 | - Pairs nicely with [`nanopage`](https://github.com/jondashkyle/nanopage) for traversing object structure 23 | 24 | ``` 25 | npm install nanocontent --save 26 | ``` 27 | 28 | ## Usage 29 | 30 | Format some plain text files using [smarkt](https://github.com/jondashkyle/smarkt) fields. 31 | 32 | ``` 33 | title: Technopastoral 34 | ---- 35 | date: January 19, 2038 36 | ---- 37 | tags: 38 | - garden 39 | - engineering 40 | ---- 41 | text: To deprogram oneself necessitates keeping to very specific schedules, which are what Foucault, once again, described as techniques of the self, echoing Seneca. 42 | ``` 43 | 44 | Organize them within a directory structure alongside media assets. 45 | 46 | ``` 47 | /content 48 | /about 49 | index.txt 50 | /blog 51 | /38-01-19-technopastoral 52 | index.txt 53 | header.jpg 54 | index.txt 55 | ``` 56 | 57 | Turn the directory into an object. 58 | 59 | ```js 60 | var nanocontent = require('nanocontent') 61 | var site = nanocontent.readSiteSync('./content') 62 | ``` 63 | 64 | Each directory becomes a path containing a sub-object of the content in your text file. 65 | 66 | ``` 67 | { 68 | '/': { }, 69 | '/about': { }, 70 | '/blog': { }, 71 | '/blog/30-01-19-technopastoral': { } 72 | } 73 | ``` 74 | 75 | Map over the object keys to add routes to a router, then pass the content object. Huzzah. 76 | 77 | ## API 78 | 79 | #### `.readFile(path, [options])` 80 | 81 | #### `.readFiles(files, pathSite, [options])` 82 | 83 | #### `.readPage(path, [options])` 84 | 85 | #### `.readSite(path, [options])` 86 | 87 | #### `.readFileSync(path, [options])` 88 | 89 | #### `.readFilesSync(files, pathSite, [options])` 90 | 91 | #### `.readPageSync(path, [options])` 92 | 93 | #### `.readSiteSync(path, [options])` 94 | 95 | ## Options 96 | 97 | #### `fs` 98 | 99 | Provide a custom implementation of `fs`. Ensure the `mkdir` `readdir` `writeFile` and `readFile` methods are available. This is useful for replacing Node’s `fs` with Dat’s API, for instance. 100 | 101 | #### `parse` 102 | 103 | Substitute `smarkt` with your own parser. Must be able to transform a plain text file into a JSON object. 104 | 105 | #### `parent` 106 | 107 | Remove part of the `url` for pretty printing. For example, if your content lives in `/content`, but you don’t want to prefix all of your `urls` with `/content`, use `parent` to clean it up. Value can be a string or boolean. If `true`, the `path` of your initial call is used. 108 | 109 | ## Transform 110 | 111 | ``` 112 | browserify -t nanocontent/transform 113 | ``` 114 | 115 | A browserify transform located at `nanocontent/transform` is included to staticly inline the module output. 116 | 117 | ## Example 118 | 119 | A demo site is included. Open the `nanocontent/example` dir and `npm install`. The example uses [Bankai](https://github.com/choojs/bankai). Run `npm start` to spin up a Bankai server and mess around. Run `bankai build` to build a fully static site. 120 | 121 | ## Todo 122 | 123 | - [ ] Tests 124 | - [ ] Async callback/promise fallback 125 | - [x] Modularize read function for JSON/Smarkt/Custom 126 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('ava') 2 | var hypha = require('.') 3 | 4 | test('readPageSync works', function (t) { 5 | var page = hypha.readPageSync('example/content/about') 6 | t.is(page.title, 'About') 7 | t.is(page.view, 'custom') 8 | }) 9 | 10 | test('readPage works', async function (t) { 11 | var page = await hypha.readPage('example/content/about') 12 | t.is(page.title, 'About') 13 | t.is(page.view, 'custom') 14 | }) 15 | 16 | test('readPageSync and readPage outputs are the same', async function (t) { 17 | var syncPage = hypha.readPageSync('example/content/about') 18 | var asyncPage = await hypha.readPage('example/content/about') 19 | t.deepEqual(syncPage, asyncPage) 20 | }) 21 | 22 | test('readSiteSync works', function (t) { 23 | var site = hypha.readSiteSync('example/content') 24 | t.is(site['/example/content'].title, 'Example') 25 | t.is(site['/example/content/about'].title, 'About') 26 | }) 27 | 28 | test('readSiteSync and readSite outputs are the same', async function (t) { 29 | var syncSite = hypha.readSiteSync('example/content') 30 | var asyncSite = await hypha.readSite('example/content') 31 | t.deepEqual(syncSite, asyncSite) 32 | }) 33 | 34 | test('readSiteSync and readSite outputs are the same with parent option', async function (t) { 35 | var syncSite = hypha.readSiteSync('example/content', { parent: true }) 36 | var asyncSite = await hypha.readSite('example/content', { parent: true }) 37 | t.deepEqual(syncSite, asyncSite) 38 | }) 39 | -------------------------------------------------------------------------------- /transform.js: -------------------------------------------------------------------------------- 1 | var staticModule = require('static-module') 2 | var through = require('through2') 3 | var path = require('path') 4 | 5 | var nanocontent = require('.') 6 | 7 | module.exports = transform 8 | 9 | function transform (filename) { 10 | if (/\.json$/.test(filename)) return through() 11 | 12 | var vars = { 13 | __filename: filename, 14 | __dirname: path.dirname(filename) 15 | } 16 | 17 | var sm = staticModule({ 18 | nanocontent: { 19 | readPageSync: function (pathPage, opts) { 20 | opts = opts || { } 21 | opts.pathRoot = opts.pathRoot || vars.__dirname 22 | opts.parent = typeof opts.parent !== 'undefined' ? opts.parent : true 23 | 24 | var pathDir = path.isAbsolute(pathPage) ? pathPage : path.join(vars.__dirname, pathPage) 25 | var pageSync = nanocontent.readPageSync(pathDir, opts) 26 | 27 | var stream = through() 28 | stream.push(JSON.stringify(pageSync, { }, 2)) 29 | stream.push(null) 30 | return stream 31 | }, 32 | readSiteSync: function readDir (pathSite, opts) { 33 | opts = opts || { } 34 | opts.pathRoot = opts.pathRoot || vars.__dirname 35 | opts.parent = typeof opts.parent !== 'undefined' ? opts.parent : true 36 | 37 | opts.onFile = function (pathFile) { 38 | sm.emit('file', pathFile) 39 | } 40 | 41 | var pathDir = path.isAbsolute(pathSite) ? pathSite : path.join(vars.__dirname, pathSite) 42 | var siteSync = nanocontent.readSiteSync(pathDir, opts) 43 | 44 | var stream = through() 45 | stream.push(JSON.stringify(siteSync, { }, 2)) 46 | stream.push(null) 47 | return stream 48 | } 49 | } 50 | }, { 51 | vars: vars, 52 | varModules: { path: path } 53 | }) 54 | 55 | return sm 56 | } 57 | -------------------------------------------------------------------------------- /utils/content.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jondashkyle/nanocontent/0549fefc53960344fa68fd463854400c48408357/utils/content.js -------------------------------------------------------------------------------- /utils/file.js: -------------------------------------------------------------------------------- 1 | var objectKeys = require('object-keys') 2 | var assert = require('assert') 3 | var slash = require('normalize-path') 4 | var path = require('path') 5 | 6 | module.exports = { 7 | formatUrl: formatUrl, 8 | filterFile: filterFile, 9 | getFileType: getFileType, 10 | getFileMeta: getFileMeta, 11 | sortChildren: sortChildren, 12 | isFile: isFile 13 | } 14 | 15 | function sortChildren (result, active) { 16 | var ext = path.extname(active) 17 | result = result || { } 18 | result.files = result.files || [ ] 19 | result.pages = result.pages || [ ] 20 | 21 | if (ext) result.files.push(active) 22 | else result.pages.push(active) 23 | 24 | return result 25 | } 26 | 27 | function filterFile (file, index) { 28 | if (file === '.DS_Store') return false 29 | if (/(^[.#]|(?:__|~)$)/.test(file)) return false 30 | if (file.indexOf(index) >= 0) return false 31 | if (file.indexOf('src') >= 0) return false 32 | return true 33 | } 34 | 35 | function getFileType (extension, filetypes) { 36 | return objectKeys(filetypes) 37 | .reduce(function (result, value, i, source) { 38 | if (result) return result 39 | 40 | if ( 41 | filetypes[value] && 42 | filetypes[value].indexOf(extension) >= 0 43 | ) { 44 | return value 45 | } 46 | 47 | if (i >= source.length) return 'unknown' 48 | }, '') 49 | } 50 | 51 | function isFile (pathFile) { 52 | assert.equal(typeof pathFile, 'string', 'enoki: arg1 pathFile must be type string') 53 | return path.extname(pathFile) !== '' 54 | } 55 | 56 | function getFileMeta (opts) { 57 | assert.equal(typeof opts, 'object', 'enoki: arg1 opts must be type object') 58 | assert.equal(typeof opts.pathFile, 'string', 'enoki: arg1 opts.pathFile must be type string') 59 | assert.equal(typeof opts.pathParent, 'string', 'enoki: arg1 opts.pathParent must be type string') 60 | assert.equal(typeof opts.pathRoot, 'string', 'enoki: arg1 opts.pathRoot must be type string') 61 | assert.equal(typeof opts.filetypes, 'object', 'enoki: arg1 opts.filetypes must be type string') 62 | 63 | var output = { } 64 | var ext = path.extname(opts.pathFile) 65 | output.name = path.basename(opts.pathFile, ext) 66 | output.path = formatUrl(path.join('/', opts.pathParent, '/', opts.pathFile), opts.pathRoot) 67 | output.url = formatUrl(path.join('/', opts.pathParent, '/', opts.pathFile), opts.pathRoot, opts.pathSiteParent) 68 | output.source = opts.pathSource ? (opts.pathSource + output.path) : output.path 69 | 70 | if (ext) { 71 | output.extension = ext.toLowerCase() 72 | output.filename = path.basename(opts.pathFile) 73 | output.type = getFileType(output.extension, opts.filetypes) 74 | } 75 | 76 | return output 77 | } 78 | 79 | function formatUrl (pathFile, pathRoot, pathSiteParent) { 80 | pathFile = pathFile.replace(pathRoot, '') 81 | if (pathSiteParent) pathFile = pathFile.replace(pathSiteParent, '') 82 | pathFile = slash(path.join('/', pathFile)) 83 | return pathFile || '/' 84 | } 85 | -------------------------------------------------------------------------------- /utils/page.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jondashkyle/nanocontent/0549fefc53960344fa68fd463854400c48408357/utils/page.js --------------------------------------------------------------------------------