Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

markdown-notes-tree

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

markdown-notes-tree - npm Package Compare versions

Comparing version 1.7.0 to 1.8.0

13

package.json
{
"name": "markdown-notes-tree",
"version": "1.7.0",
"version": "1.8.0",
"description": "Generate Markdown trees that act as a table of contents for a folder structure with Markdown notes",

@@ -10,4 +10,5 @@ "main": "src/index.js",

"scripts": {
"start": "node src/index.js",
"test": "jest"
"test": "jest --collectCoverage",
"check-formatting": "prettier-check *.js src/*.js",
"build": "npm run check-formatting && npm run test"
},

@@ -26,10 +27,12 @@ "keywords": [

"devDependencies": {
"codecov": "^3.6.5",
"dedent": "^0.7.0",
"fs-extra": "^8.1.0",
"jest": "^24.9.0",
"prettier": "^1.19.1"
"prettier": "^1.19.1",
"prettier-check": "^2.0.0"
},
"dependencies": {
"minimatch": "^3.0.4",
"minimist": "^1.2.0"
"minimist": "^1.2.5"
},

@@ -36,0 +39,0 @@ "files": [

# markdown-notes-tree
[![Codecov Coverage](https://img.shields.io/codecov/c/github/mistermicheels/markdown-notes-tree?style=flat)](https://codecov.io/gh/mistermicheels/markdown-notes-tree/)
[![npm](https://img.shields.io/npm/v/markdown-notes-tree?style=flat)](https://www.npmjs.com/package/markdown-notes-tree)
If you have a folder structure with Markdown notes, you can use this tool to generate Markdown trees that act as a table of contents for the folder structure.

@@ -38,2 +41,6 @@

## Subdirectory descriptions
The generated `README.md` files for subdirectories provide some space to add a description for the directory. If a description is provided, it will be preserved and it will also be included in generated trees containing the directory.
## Command line arguments

@@ -57,3 +64,13 @@

- Example: `markdown-notes-tree --silent`
- `--subdirectoryDescriptionOnNewLine`: If subdirectory descriptions are provided, put them on a new line in the tree.
- Example: `markdown-notes-tree --subdirectoryDescriptionOnNewLine`
- `--useTabs`: Use tabs (instead of the standard four spaces) for indentation.
- Example: `markdown-notes-tree --useTabs`
## Development
This project is using Prettier to format the JavaScript code. Installing the VS Code plugin recommended through the `extensions.json` file should make this easy.
The build script verifies that the formatting actually matches Prettier style and that the unit and integration tests are passing.
During development, you can run the tool on a folder by navigating to the folder in the command line and then executing `node path/to/cli.js`, adding arguments as needed. Example: `node ../../../cli.js --silent`

@@ -7,5 +7,16 @@ "use strict";

getNewMainReadmeContents,
getDirectoryReadmeContents
getNewDirectoryReadmeContents,
getDirectoryDescriptionFromCurrentContents
};
const markers = {
mainReadmeTreeStart: "<!-- auto-generated notes tree starts here -->",
mainReadmeTreeEnd: "<!-- auto-generated notes tree ends here -->",
directoryReadmeGeneratedStart: "<!-- this entire file is auto-generated -->",
directoryReadmeDescriptionStart:
"<!-- optional markdown-notes-tree directory description starts here -->",
directoryReadmeDescriptionEnd:
"<!-- optional markdown-notes-tree directory description ends here -->"
};
function getTitleFromMarkdownContents(contents) {

@@ -22,7 +33,7 @@ const firstLine = contents.split(/\r\n|\r|\n/, 1)[0];

function getMarkdownForTree(tree, endOfLine, options) {
const lines = getMarkdownLinesForTree(tree, [], options);
const lines = getMarkdownLinesForTree(tree, [], endOfLine, options);
return lines.join(endOfLine);
}
function getMarkdownLinesForTree(tree, parentPathParts, options) {
function getMarkdownLinesForTree(tree, parentPathParts, endOfLine, options) {
const markdownLines = [];

@@ -32,4 +43,11 @@ const indentationUnit = getIndentationUnit(options);

for (const treeNode of tree) {
markdownLines.push(getMarkdownLineForTreeNode(treeNode, parentPathParts, options));
const markdownForTreeNode = getMarkdownForTreeNode(
treeNode,
parentPathParts,
endOfLine,
options
);
markdownLines.push(...markdownForTreeNode.split(endOfLine));
if (treeNode.isDirectory) {

@@ -41,2 +59,3 @@ const fullPathParts = [...parentPathParts, treeNode.filename];

fullPathParts,
endOfLine,
options

@@ -62,6 +81,14 @@ );

function getMarkdownLineForTreeNode(treeNode, parentPath, options) {
function getMarkdownForTreeNode(treeNode, parentPath, endOfLine, options) {
const linkText = getLinkTextForTreeNode(treeNode);
const linkTarget = getLinkTargetForTreeNode(treeNode, parentPath, options);
return `- [${linkText}](${linkTarget})`;
const basicLine = `- [${linkText}](${linkTarget})`;
if (treeNode.description) {
const descriptionSeparator = getDescriptionSeparator(endOfLine, options);
return basicLine + descriptionSeparator + treeNode.description;
} else {
return basicLine;
}
}

@@ -88,46 +115,63 @@

function getDescriptionSeparator(endOfLine, options) {
if (options.subdirectoryDescriptionOnNewLine) {
return " " + endOfLine + getIndentationUnit(options);
} else {
return " - ";
}
}
function getNewMainReadmeContents(currentContents, markdownForTree, endOfLine) {
const treeStartMarker = "<!-- auto-generated notes tree starts here -->";
const treeEndMarker = "<!-- auto-generated notes tree ends here -->";
const indexTreeStartMarker = currentContents.indexOf(markers.mainReadmeTreeStart);
let contentsBeforeTree;
const indexStartMarker = currentContents.indexOf(treeStartMarker);
let contentsBeforeStartMarker;
if (indexStartMarker >= 0) {
contentsBeforeStartMarker = currentContents.substring(0, indexStartMarker);
if (indexTreeStartMarker >= 0) {
contentsBeforeTree = currentContents.substring(0, indexTreeStartMarker);
} else {
contentsBeforeStartMarker = currentContents + endOfLine.repeat(2);
contentsBeforeTree = currentContents + endOfLine.repeat(2);
}
const indexEndMarker = currentContents.indexOf(treeEndMarker);
let contentsAfterEndMarker;
const indexTreeEndMarker = currentContents.indexOf(markers.mainReadmeTreeEnd);
let contentsAfterTree;
if (indexEndMarker >= 0 && indexEndMarker < indexStartMarker) {
if (indexTreeEndMarker >= 0 && indexTreeEndMarker < indexTreeStartMarker) {
throw new Error("Invalid file structure: tree end marker found before tree start marker");
} else if (indexEndMarker >= 0) {
contentsAfterEndMarker = currentContents.substring(indexEndMarker + treeEndMarker.length);
} else if (indexTreeEndMarker >= 0) {
contentsAfterTree = currentContents.substring(
indexTreeEndMarker + markers.mainReadmeTreeEnd.length
);
} else {
contentsAfterEndMarker = endOfLine;
contentsAfterTree = endOfLine;
}
return (
contentsBeforeStartMarker +
treeStartMarker +
contentsBeforeTree +
markers.mainReadmeTreeStart +
endOfLine.repeat(2) +
markdownForTree +
endOfLine.repeat(2) +
treeEndMarker +
contentsAfterEndMarker
markers.mainReadmeTreeEnd +
contentsAfterTree
);
}
function getDirectoryReadmeContents(name, markdownForTree, endOfLine) {
const autoGenerationComment = "<!-- this entire file is auto-generated -->";
function getNewDirectoryReadmeContents(name, currentContents, markdownForTree, endOfLine) {
const title = `# ${name}`;
const description = getDirectoryDescriptionFromCurrentContents(currentContents);
let partBetweenDescriptionMarkers = endOfLine.repeat(2);
if (description) {
partBetweenDescriptionMarkers = endOfLine.repeat(2) + description + endOfLine.repeat(2);
}
return (
autoGenerationComment +
markers.directoryReadmeGeneratedStart +
endOfLine.repeat(2) +
title +
endOfLine.repeat(2) +
markers.directoryReadmeDescriptionStart +
partBetweenDescriptionMarkers +
markers.directoryReadmeDescriptionEnd +
endOfLine.repeat(2) +
markdownForTree +

@@ -137,1 +181,31 @@ endOfLine

}
function getDirectoryDescriptionFromCurrentContents(currentContents) {
const indexDescriptionStartMarker = currentContents.indexOf(
markers.directoryReadmeDescriptionStart
);
const indexDescriptionEndMarker = currentContents.indexOf(
markers.directoryReadmeDescriptionEnd
);
const validMarkers =
indexDescriptionStartMarker >= 0 &&
indexDescriptionEndMarker >= 0 &&
indexDescriptionEndMarker > indexDescriptionStartMarker;
if (validMarkers) {
const descriptionStart =
indexDescriptionStartMarker + markers.directoryReadmeDescriptionStart.length;
const descriptionEnd = indexDescriptionEndMarker;
return currentContents.substring(descriptionStart, descriptionEnd).trim();
} else if (indexDescriptionStartMarker >= 0 || indexDescriptionEndMarker >= 0) {
throw new Error(
"Invalid file structure: only one description marker found or end marker found before start marker"
);
} else {
return "";
}
}

@@ -49,5 +49,2 @@ "use strict";

test("it should generate a tree with proper formatting and indentation", () => {
const expected =
"- [**sub1**](sub1)" + endOfLine + " - [Title for file1a](sub1/file1a.md)";
const result = fileContents.getMarkdownForTree(tree, endOfLine, {

@@ -58,2 +55,5 @@ linkToSubdirectoryReadme: false,

const expected =
"- [**sub1**](sub1)" + endOfLine + " - [Title for file1a](sub1/file1a.md)";
expect(result).toEqual(expected);

@@ -63,2 +63,7 @@ });

test("it should allow linking directly to subdirectory README files", () => {
const result = fileContents.getMarkdownForTree(tree, endOfLine, {
linkToSubdirectoryReadme: true,
useTabs: false
});
const expected =

@@ -69,7 +74,2 @@ "- [**sub1**](sub1/README.md)" +

const result = fileContents.getMarkdownForTree(tree, endOfLine, {
linkToSubdirectoryReadme: true,
useTabs: false
});
expect(result).toEqual(expected);

@@ -79,5 +79,2 @@ });

test("it should allow using tabs instead of spaces", () => {
const expected =
"- [**sub1**](sub1)" + endOfLine + "\t- [Title for file1a](sub1/file1a.md)";
const result = fileContents.getMarkdownForTree(tree, endOfLine, {

@@ -88,4 +85,78 @@ linkToSubdirectoryReadme: false,

const expected =
"- [**sub1**](sub1)" + endOfLine + "\t- [Title for file1a](sub1/file1a.md)";
expect(result).toEqual(expected);
});
describe("for a tree with description", () => {
const treeIncludingFolderDescription = [
{
isDirectory: true,
title: "sub1",
description: "description1",
filename: "sub1",
children: [
{
isDirectory: true,
title: "sub1a",
description: "description1a",
filename: "sub1a",
children: []
},
{
isDirectory: false,
title: "Title for file1a",
filename: "file1a.md"
}
]
}
];
test("it should include the description", () => {
const result = fileContents.getMarkdownForTree(
treeIncludingFolderDescription,
endOfLine,
{
linkToSubdirectoryReadme: false,
subdirectoryDescriptionOnNewLine: false,
useTabs: false
}
);
const expected =
"- [**sub1**](sub1) - description1" +
endOfLine +
" - [**sub1a**](sub1/sub1a) - description1a" +
endOfLine +
" - [Title for file1a](sub1/file1a.md)";
expect(result).toEqual(expected);
});
test("it should allow putting the description on a new line", () => {
const result = fileContents.getMarkdownForTree(
treeIncludingFolderDescription,
endOfLine,
{
linkToSubdirectoryReadme: false,
subdirectoryDescriptionOnNewLine: true,
useTabs: false
}
);
const expected =
"- [**sub1**](sub1) " +
endOfLine +
" description1" +
endOfLine +
" - [**sub1a**](sub1/sub1a) " +
endOfLine +
" description1a" +
endOfLine +
" - [Title for file1a](sub1/file1a.md)";
expect(result).toEqual(expected);
});
});
});

@@ -189,6 +260,9 @@

describe("getDirectoryReadmeContents", () => {
test("it should return the contents with marker, title and tree", () => {
const result = fileContents.getDirectoryReadmeContents(
describe("getNewDirectoryReadmeContents", () => {
test("it should handle empty current contents", () => {
const currentContents = "";
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",

@@ -202,2 +276,6 @@ endOfLine

# name
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->

@@ -208,3 +286,126 @@ markdownForTree`) + endOfLine;

});
test("it should handle current contents without description markers (as generated by older version)", () => {
const currentContents =
dedent(`<!-- this entire file is auto-generated -->
# name
markdownForTree`) + endOfLine;
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
endOfLine
);
const expected =
dedent(`<!-- this entire file is auto-generated -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
expect(result).toBe(expected);
});
test("it should handle current contents without description between markers", () => {
const currentContents =
dedent(`<!-- this entire file is auto-generated -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
endOfLine
);
const expected =
dedent(`<!-- this entire file is auto-generated -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
expect(result).toBe(expected);
});
test("it should handle current contents with description between markers", () => {
const currentContents =
dedent(`<!-- this entire file is auto-generated -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
This is a description.
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
const result = fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
endOfLine
);
const expected =
dedent(`<!-- this entire file is auto-generated -->
# name
<!-- optional markdown-notes-tree directory description starts here -->
This is a description.
<!-- optional markdown-notes-tree directory description ends here -->
markdownForTree`) + endOfLine;
expect(result).toBe(expected);
});
test("it should fail for current contents having invalid markers", () => {
const currentContents =
dedent(`<!-- this entire file is auto-generated -->
# name
<!-- optional markdown-notes-tree directory description ends here -->
<!-- optional markdown-notes-tree directory description starts here -->
markdownForTree`) + endOfLine;
expect(() =>
fileContents.getNewDirectoryReadmeContents(
"name",
currentContents,
"markdownForTree",
endOfLine
)
).toThrow(
"Invalid file structure: only one description marker found or end marker found before start marker"
);
});
});
});

@@ -18,2 +18,3 @@ "use strict";

silent: parsedArguments.silent || false,
subdirectoryDescriptionOnNewLine: parsedArguments.subdirectoryDescriptionOnNewLine || false,
useTabs: parsedArguments.useTabs || false

@@ -20,0 +21,0 @@ };

@@ -16,2 +16,3 @@ "use strict";

silent: false,
subdirectoryDescriptionOnNewLine: false,
useTabs: false

@@ -18,0 +19,0 @@ };

@@ -45,2 +45,3 @@ "use strict";

title: directory.name,
description: getDescriptionFromDirectoryReadme(relativePath),
filename: directory.name,

@@ -60,5 +61,7 @@ children: buildTreeStartingAt(relativePath, options)

if (!ignores.shouldIgnoreFile(file.name, relativeParentPath, options)) {
const relativePath = path.join(relativeParentPath, file.name);
treeNodes.push({
isDirectory: false,
title: getTitleFromMarkdownFileOrThrow(path.join(relativeParentPath, file.name)),
title: getTitleFromMarkdownFileOrThrow(relativePath),
filename: file.name

@@ -87,1 +90,12 @@ });

}
function getDescriptionFromDirectoryReadme(relativeDirectoryPath) {
const absolutePath = pathUtils.getAbsolutePath(path.join(relativeDirectoryPath, "README.md"));
if (fs.existsSync(absolutePath)) {
const contents = fs.readFileSync(absolutePath, { encoding: "utf-8" });
return fileContents.getDirectoryDescriptionFromCurrentContents(contents);
} else {
return "";
}
}

@@ -58,5 +58,2 @@ "use strict";

function writeTreeToDirectoryReadme(pathParts, name, treeForDirectory, endOfLine, options, logger) {
const markdownForTree = fileContents.getMarkdownForTree(treeForDirectory, endOfLine, options);
const contents = fileContents.getDirectoryReadmeContents(name, markdownForTree, endOfLine);
const filePathParts = [...pathParts, "README.md"];

@@ -66,4 +63,19 @@ const relativeFilePath = path.join(...filePathParts);

let currentContents = "";
if (fs.existsSync(absoluteFilePath)) {
currentContents = fs.readFileSync(absoluteFilePath, { encoding: "utf-8" });
}
const markdownForTree = fileContents.getMarkdownForTree(treeForDirectory, endOfLine, options);
const newContents = fileContents.getNewDirectoryReadmeContents(
name,
currentContents,
markdownForTree,
endOfLine
);
logger(`Writing to ${absoluteFilePath}`);
fs.writeFileSync(absoluteFilePath, contents, { encoding: "utf-8" });
fs.writeFileSync(absoluteFilePath, newContents, { encoding: "utf-8" });
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc