diff --git a/README.md b/README.md index 2b7827b..366813d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ * [Make a table of contents](#make-a-table-of-contents) * [How To Install](#how-to-install) * [How To Use](#how-to-use) + * [From The Command Line](#from-the-command-line) + * [As A Module](#as-a-module) * [markdown.json](#markdownjson) * [How It Works](#how-it-works) @@ -61,10 +63,22 @@ Use the `-g` flag if you wish to install markdown-include globally on your syste # How To Use -markdown-include is very easy to use. Just include a `markdown.json` file in your project root with your options and run from the command line to compile your documents like so: +markdown-include is very easy to use whether on the command line or in your own node project. Each can help you compile your markdown files as you see fit. markdown-include does require that you define a `markdown.json` file with your options for compile. See below for all of the options available to you. + +## From The Command Line + +Run from the command line to compile your documents like so: + +``` +node_modules/bin/cli.js path/to/markdown.json +``` + +## As A Module + +Just require in your node project: ``` -node path/to/markdown-include.js path/to/markdown.json +var markdownInclude = require('markdown-include'); ``` ## markdown.json diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 0000000..d6163ef --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../markdown-include').compileFiles(process.argv[2]); \ No newline at end of file diff --git a/docs/how_to_use.md b/docs/how_to_use.md index 4400e32..fd34442 100644 --- a/docs/how_to_use.md +++ b/docs/how_to_use.md @@ -1,9 +1,21 @@ # How To Use !heading -markdown-include is very easy to use. Just include a `markdown.json` file in your project root with your options and run from the command line to compile your documents like so: +markdown-include is very easy to use whether on the command line or in your own node project. Each can help you compile your markdown files as you see fit. markdown-include does require that you define a `markdown.json` file with your options for compile. See below for all of the options available to you. + +## From The Command Line !heading + +Run from the command line to compile your documents like so: + +``` +node_modules/bin/cli.js path/to/markdown.json +``` + +## As A Module !heading + +Just require in your node project: ``` -node path/to/markdown-include.js path/to/markdown.json +var markdownInclude = require('markdown-include'); ``` ## markdown.json !heading diff --git a/markdown-include.js b/markdown-include.js index 1fc3b94..f800709 100644 --- a/markdown-include.js +++ b/markdown-include.js @@ -3,355 +3,350 @@ * @description Include markdown files into other markdown files * @version 0.1.0 */ -(function () { - "use strict"; - - var exec = require('child_process').exec; - var fs = require('fs'); - var build = {}; - var includePattern = /^#include\s"(.+\/|\/|\w|-|\/)+.md"/gm; - var ignorePattern = /^#include\s"(.+\/|\/|\w|-|\/)+.md" !ignore/gm; - var headingPattern = /^#+\s.+ !heading/gm; - var tableOfContents = ''; - var options; +var exec = require('child_process').exec; +var fs = require('fs'); +var build = {}; +var includePattern = /^#include\s"(.+\/|\/|\w|-|\/)+.md"/gm; +var ignorePattern = /^#include\s"(.+\/|\/|\w|-|\/)+.md" !ignore/gm; +var headingPattern = /^#+\s.+ !heading/gm; +var tableOfContents = ''; +var options; + +/** + * Builds links for table of contents + * @param {String} str String to test and transform + * @return {String} String for link + */ +exports.buildLinkString = function (str) { + var linkPatterns = { + dot: /\./g, + stick: /\|/g + }; + var key; + + for (key in linkPatterns) { + var pattern = linkPatterns[key]; + + if (pattern.test(str)) { + str = str.replace(pattern, ''); + } + } + + return str.trim().split(' ').join('-').toLowerCase(); +}; + +/** + * Build content item for navigation + * @param {Object} obj Object containing count and headingTag + * @return {String} String for markdown navigation item + */ +exports.buildContentItem = function (obj) { + var headingTag = obj.headingTag; + var count = obj.count; + var item = headingTag.substring(count + 1); + var index = headingTag.indexOf(item); + var headingTrimmed = this.buildLinkString(headingTag.substring(index)); + var lead = options.tableOfContents.lead && options.tableOfContents.lead === 'number' ? '1.' : '*'; + var navItem; /** - * Builds links for table of contents - * @param {String} str String to test and transform - * @return {String} String for link + * Small utility function for building links for navigation items + * @param {String} heading Navigation item + * @return {String} Navigation item that's linked */ - function buildLinkString(str) { - var linkPatterns = { - dot: /\./g, - stick: /\|/g - }; - var key; + function buildNavItem(heading) { + return '[' + item + '](#' + heading + ')\n'; + } + + switch (obj.count) { + case 1: + navItem = lead + ' ' + buildNavItem(headingTrimmed); + break; + case 2: + navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); + break; + case 3: + navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); + break; + case 4: + navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); + break; + case 5: + navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); + break; + case 6: + navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); + break; + } - for (key in linkPatterns) { - var pattern = linkPatterns[key]; + return navItem; +}; - if (pattern.test(str)) { - str = str.replace(pattern, ''); - } +/** + * Compile files from markdown.json + * @param {String} path File path to markdown.json + */ +exports.compileFiles = function (path) { + var self = this; + fs.readFile(path, function (err, data) { + if (err) { + throw err; } - return str.trim().split(' ').join('-').toLowerCase(); - } + options = JSON.parse(data.toString()); + var files = options.files; + var i; - /** - * Build content item for navigation - * @param {Object} obj Object containing count and headingTag - * @return {String} String for markdown navigation item - */ - function buildContentItem(obj) { - var headingTag = obj.headingTag; - var count = obj.count; - var item = headingTag.substring(count + 1); - var index = headingTag.indexOf(item); - var headingTrimmed = buildLinkString(headingTag.substring(index)); - var lead = options.tableOfContents.lead && options.tableOfContents.lead === 'number' ? '1.' : '*'; - var navItem; - - /** - * Small utility function for building links for navigation items - * @param {String} heading Navigation item - * @return {String} Navigation item that's linked - */ - function buildNavItem(heading) { - return '[' + item + '](#' + heading + ')\n'; - } + for (i = 0; i < files.length; i += 1) { + var file = files[i]; - switch (obj.count) { - case 1: - navItem = lead + ' ' + buildNavItem(headingTrimmed); - break; - case 2: - navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); - break; - case 3: - navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); - break; - case 4: - navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); - break; - case 5: - navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); - break; - case 6: - navItem = ' ' + lead + ' ' + buildNavItem(headingTrimmed); - break; - } + self.processFile(file); + build[file].parsedData = self.stripFileTags({ + data: build[file].parsedData, + pattern: ignorePattern, + string: ' !ignore' + }); - return navItem; - } + if (options.tableOfContents) { + self.compileHeadingTags(file); - /** - * Compile files from markdown.json - * @param {String} path File path to markdown.json - */ - function compileFiles(path) { - fs.readFile(path, function (err, data) { - if (err) { - throw err; + if (options.tableOfContents.heading) { + build[file].parsedData = options.tableOfContents.heading + '\n\n' + tableOfContents + '\n\n' + build[file].parsedData; + } + else { + build[file].parsedData = tableOfContents + '\n\n' + build[file].parsedData; + } } - options = JSON.parse(data.toString()); - var files = options.files; - var i; - - for (i = 0; i < files.length; i += 1) { - var file = files[i]; - - processFile(file); - build[file].parsedData = stripFileTags({ - data: build[file].parsedData, - pattern: ignorePattern, - string: ' !ignore' - }); - - if (options.tableOfContents) { - compileHeadingTags(file); - - if (options.tableOfContents.heading) { - build[file].parsedData = options.tableOfContents.heading + '\n\n' + tableOfContents + '\n\n' + build[file].parsedData; - } - else { - build[file].parsedData = tableOfContents + '\n\n' + build[file].parsedData; - } - } + self.writeFile(build[file].parsedData); + } + }); +}; - writeFile(build[file].parsedData); - } - }); + +/** + * Compiling heading tags in a parsed file + * @param {String} file File path + */ +exports.compileHeadingTags = function (file) { + var headingTags = this.findHeadingTags(build[file].parsedData); + var replacedHeadingTag; + var parsedHeading; + var i; + + for (i = 0; i < headingTags.length; i += 1) { + replacedHeadingTag = headingTags[i].replace(' !heading', ''); + parsedHeading = this.parseHeadingTag(replacedHeadingTag); + tableOfContents += this.buildContentItem(parsedHeading); } + build[file].parsedData = this.stripFileTags({ + data: build[file].parsedData, + pattern: headingPattern, + string: ' !heading' + }); +}; - /** - * Compiling heading tags in a parsed file - * @param {String} file File path - */ - function compileHeadingTags(file) { - var headingTags = findHeadingTags(build[file].parsedData); - var replacedHeadingTag; - var parsedHeading; - var i; +/** + * Finding heading tags that have !heading + * @param {String} parsedData Parsed data from includes + * @return {Array} Array of matching heading tags + */ +exports.findHeadingTags = function (parsedData) { + return parsedData.match(headingPattern) || []; +}; - for (i = 0; i < headingTags.length; i += 1) { - replacedHeadingTag = headingTags[i].replace(' !heading', ''); - parsedHeading = parseHeadingTag(replacedHeadingTag); - tableOfContents += buildContentItem(parsedHeading); - } +/** + * Finds include tags in file content based on a regular expression + * @param {String} rawData Raw data from file + * @return {Array} Array containing found include tags + */ +exports.findIncludeTags = function (rawData) { + var ignores; + var includes = rawData.match(includePattern) || []; - build[file].parsedData = stripFileTags({ - data: build[file].parsedData, - pattern: headingPattern, - string: ' !heading' - }); + if (ignorePattern.test(rawData)) { + ignores = rawData.match(ignorePattern); } - /** - * Finding heading tags that have !heading - * @param {String} parsedData Parsed data from includes - * @return {Array} Array of matching heading tags - */ - function findHeadingTags(parsedData) { - return parsedData.match(headingPattern) || []; - } + if (includes.length > 0 && ignores) { + var i; - /** - * Finds include tags in file content based on a regular expression - * @param {String} rawData Raw data from file - * @return {Array} Array containing found include tags - */ - function findIncludeTags(rawData) { - var ignores; - var includes = rawData.match(includePattern) || []; + for (i = 0; i < ignores.length; i += 1) { + var ignoreTest = includes[i] + ' !ignore'; - if (ignorePattern.test(rawData)) { - ignores = rawData.match(ignorePattern); + if (ignoreTest && includes.length === 1) { + includes = []; + } + else if (ignoreTest) { + includes.shift(); + } } + } - if (includes.length > 0 && ignores) { - var i; + return includes; +}; - for (i = 0; i < ignores.length; i += 1) { - var ignoreTest = includes[i] + ' !ignore'; +/** + * Processes file for the life cycle + * @param {String} file A file path + * @param {String} currentFile Current file that an include was found in + */ +exports.processFile = function (file, currentFile) { + if (file in build) { + replaceIncludeTags(file); + } + else { + var rawData = fs.readFileSync(file).toString(); + var includeTags = this.findIncludeTags(rawData); + var files = includeTags.length ? this.processIncludeTags(file, currentFile, includeTags) : null; + + build[file] = { + files: files, + includeTags: includeTags, + rawData: rawData, + }; - if (ignoreTest && includes.length === 1) { - includes = []; - } - else if (ignoreTest) { - includes.shift(); - } - } + if (files && includeTags) { + build[file].parsedData = this.replaceIncludeTags(file); + } + else { + build[file].parsedData = rawData; } - - return includes; } +}; - /** - * Processes file for the life cycle - * @param {String} file A file path - * @param {String} currentFile Current file that an include was found in - */ - function processFile(file, currentFile) { - if (file in build) { - replaceIncludeTags(file); +/** + * Parses an include tag to get a file path + * @param {String} tag An include tag + * @return {String} A file path + */ +exports.parseIncludeTag = function (tag) { + var firstQuote = tag.indexOf('"') + 1; + var lastQuote = tag.lastIndexOf('"'); + + return tag.substring(firstQuote, lastQuote); +}; + +/** + * [parseHeadingTag description] + * @param {[type]} headingTag [description] + * @return {[type]} [description] + */ +exports.parseHeadingTag = function (headingTag) { + var count = 0; + var i; + + for (i = 0; i < headingTag.length; i += 1) { + if (headingTag[i] === '#') { + count += 1; } else { - var rawData = fs.readFileSync(file).toString(); - var includeTags = findIncludeTags(rawData); - var files = includeTags.length ? processIncludeTags(file, currentFile, includeTags) : null; - - build[file] = { - files: files, - includeTags: includeTags, - rawData: rawData, - }; - - if (files && includeTags) { - build[file].parsedData = replaceIncludeTags(file); - } - else { - build[file].parsedData = rawData; - } + break; } } - /** - * Parses an include tag to get a file path - * @param {String} tag An include tag - * @return {String} A file path - */ - function parseIncludeTag(tag) { - var firstQuote = tag.indexOf('"') + 1; - var lastQuote = tag.lastIndexOf('"'); + return { + count: count, + headingTag: headingTag + }; +}; - return tag.substring(firstQuote, lastQuote); - } +/** + * Processes array of include tags and passes file for recursion + * @param {String} file File passed for additional processing to check for more includes + * @param {String} currentFile Current file passed on recursion to check for circular dependencies + * @param {Array} tags Array of include tags + * @return {Array} Collection of files parsed from include tags + */ +exports.processIncludeTags = function (file, currentFile, tags) { + var collection = []; + var i; - /** - * [parseHeadingTag description] - * @param {[type]} headingTag [description] - * @return {[type]} [description] - */ - function parseHeadingTag(headingTag) { - var count = 0; - var i; + for (i = 0; i < tags.length; i += 1) { + var includeFile = this.parseIncludeTag(tags[i]); - for (i = 0; i < headingTag.length; i += 1) { - if (headingTag[i] === '#') { - count += 1; - } - else { - break; - } + if (includeFile === currentFile) { + throw new Error('Circular injection ' + file + ' -> ' + includeFile + ' -> ' + file); } - return { - count: count, - headingTag: headingTag - }; + collection.push(includeFile); + this.processFile(includeFile, file); } - /** - * Processes array of include tags and passes file for recursion - * @param {String} file File passed for additional processing to check for more includes - * @param {String} currentFile Current file passed on recursion to check for circular dependencies - * @param {Array} tags Array of include tags - * @return {Array} Collection of files parsed from include tags - */ - function processIncludeTags(file, currentFile, tags) { - var collection = []; - var i; + return collection; +}; - for (i = 0; i < tags.length; i += 1) { - var includeFile = parseIncludeTag(tags[i]); +/** + * Replaces include tags with actual content from files + * @param {String} file File content + * @return {String} Replaced file content + */ +exports.replaceIncludeTags = function (file, cached) { + var obj = build[file]; + var replacedData; + var i; - if (includeFile === currentFile) { - throw new Error('Circular injection ' + file + ' -> ' + includeFile + ' -> ' + file); - } + for (i = 0; i < obj.includeTags.length; i += 1) { + var includeTag = obj.includeTags[i]; + var currentFile = obj.files[i]; - collection.push(includeFile); - processFile(includeFile, file); + if (cached) { + replacedData = obj.parsedData; + } + else if (replacedData) { + replacedData = replacedData.replace(includeTag, build[currentFile].parsedData); + } + else { + replacedData = obj.rawData.replace(includeTag, build[currentFile].parsedData); } - - return collection; } - /** - * Replaces include tags with actual content from files - * @param {String} file File content - * @return {String} Replaced file content - */ - function replaceIncludeTags(file, cached) { - var obj = build[file]; - var replacedData; + return replacedData; +}; + +/** + * Strips tags in a given file + * @param {Object} obj Object containing file path, pattern to match and string to replace + * @return {String} Replaced data from object keys + */ +exports.stripFileTags = function (obj) { + var replacedData; + + if (obj.pattern.test(obj.data)) { + var patterns = obj.data.match(obj.pattern); var i; - for (i = 0; i < obj.includeTags.length; i += 1) { - var includeTag = obj.includeTags[i]; - var currentFile = obj.files[i]; + for (i = 0; i < patterns.length; i += 1) { + var currentPattern = patterns[i]; + var index = obj.data.indexOf(currentPattern); + var stringLength = obj.string.length; + var currentPatternTagLength = patterns[i].length; + var replacedTag = currentPattern.substring(0, currentPatternTagLength - stringLength); - if (cached) { - replacedData = obj.parsedData; - } - else if (replacedData) { - replacedData = replacedData.replace(includeTag, build[currentFile].parsedData); + if (replacedData) { + replacedData = replacedData.replace(currentPattern, replacedTag); } else { - replacedData = obj.rawData.replace(includeTag, build[currentFile].parsedData); + replacedData = obj.data.replace(currentPattern, replacedTag); } } return replacedData; } +}; - /** - * Strips tags in a given file - * @param {Object} obj Object containing file path, pattern to match and string to replace - * @return {String} Replaced data from object keys - */ - function stripFileTags(obj) { - var replacedData; - - if (obj.pattern.test(obj.data)) { - var patterns = obj.data.match(obj.pattern); - var i; - - for (i = 0; i < patterns.length; i += 1) { - var currentPattern = patterns[i]; - var index = obj.data.indexOf(currentPattern); - var stringLength = obj.string.length; - var currentPatternTagLength = patterns[i].length; - var replacedTag = currentPattern.substring(0, currentPatternTagLength - stringLength); - - if (replacedData) { - replacedData = replacedData.replace(currentPattern, replacedTag); - } - else { - replacedData = obj.data.replace(currentPattern, replacedTag); - } - } - - return replacedData; +/** + * Write file wrapper + * @param {String} path Path to build new file + * @param {String} data Data to write into file + */ +exports.writeFile = function (parsedData) { + fs.writeFile(options.build, parsedData, function (err) { + if (err) { + throw err; } - } - - /** - * Write file wrapper - * @param {String} path Path to build new file - * @param {String} data Data to write into file - */ - function writeFile(parsedData) { - fs.writeFile(options.build, parsedData, function (err) { - if (err) { - throw err; - } - - console.info(options.build + ' has been built successfully'); - }); - } - compileFiles(process.argv[2]); -}()); \ No newline at end of file + console.info(options.build + ' has been built successfully'); + }); +}; \ No newline at end of file diff --git a/package.json b/package.json index 9fae592..7a1edd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "markdown-include", - "version": "0.2.2", + "version": "0.3.0", "description": "Include markdown files into other markdown files with C style syntax.", "main": "markdown-include.js", "repository": {