From 59e955f3d9a6b9fa8182f1a5e451089fdc35337d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Drago=C8=99=20Str=C4=83inu?= Date: Mon, 20 Apr 2020 12:50:11 +0300 Subject: [PATCH] feat: add position option --- README.md | 1 + examples/position.md | 100 ++++++++++++++++++++++++++++++++ src/cli.ts | 38 +++++++----- src/constants.ts | 2 + src/transform.ts | 51 +++++++++++++--- src/{interfaces.ts => types.ts} | 3 + src/utils.ts | 21 +++++++ 7 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 examples/position.md rename src/{interfaces.ts => types.ts} (74%) diff --git a/README.md b/README.md index bfc753f..07998b8 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ Options: - [maxHeaderLevel](examples/maxHeaderLevel.md) - [Emoji choice](examples/emojiChoice.md) - [Start and end](examples/startEnd.md) +- [Change link position](examples/position.md) ## TODO[⬆](#jump2header️⃣) diff --git a/examples/position.md b/examples/position.md new file mode 100644 index 0000000..81ff6c2 --- /dev/null +++ b/examples/position.md @@ -0,0 +1,100 @@ +# Basic + +```bash +jump2header -f examples/position.md --position "start" +``` + +Some intro text + +## Section1 + +[⬆](#basic) + +Section1 is most important section + +### Subsection11 + +[⬆](#basic) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ornare dapibus turpis sed egestas. Cras est nunc, pellentesque ac imperdiet aliquet, tristique sed lectus. Quisque lectus lorem, pulvinar non enim nec, aliquam condimentum urna. Morbi porttitor sit amet lectus ut rhoncus. Suspendisse finibus tincidunt justo. Donec consectetur sit amet odio ut molestie. Quisque molestie orci nec tincidunt fermentum. Pellentesque viverra quam ut dolor varius pulvinar. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas enim tortor, commodo lacinia augue sit amet, viverra egestas nibh. Vestibulum leo ex, gravida at mi quis, ultricies pulvinar justo. Aenean ornare leo leo, malesuada suscipit lectus ornare vitae. Duis et ornare neque, id imperdiet augue. Morbi tincidunt erat id pellentesque dignissim. Nullam non sagittis magna. Suspendisse in pellentesque massa. + +## Section2 with many words + +[⬆](#basic) + +Cras vitae diam placerat, interdum augue vel, vehicula magna. + +Etiam diam nisl, sagittis vel commodo id, fermentum eu augue. +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus venenatis laoreet purus, nec pulvinar mauris faucibus vel. Vivamus non elementum nisl. Sed sit amet mauris urna. Quisque purus mauris, egestas vel pellentesque quis, sollicitudin et urna. + +### Subsection21 + +[⬆](#basic) + +Duis fermentum purus a sodales iaculis. Nunc diam turpis, hendrerit ut elit quis, euismod laoreet velit. + +Pellentesque laoreet sem id nunc luctus, vitae interdum ipsum commodo. Vivamus semper a magna sollicitudin aliquam. + +Mauris a felis nec velit egestas maximus sed et enim. Quisque eget auctor quam, interdum lacinia arcu. Nulla sollicitudin lobortis arcu ultricies scelerisque. Donec faucibus ipsum at libero dictum cursus. +Cras dictum sodales feugiat. Nulla vestibulum metus eu cursus semper. + +Phasellus tincidunt varius eleifend. Curabitur fringilla, neque quis ultricies tincidunt, orci ligula blandit sapien, quis iaculis ipsum enim a sapien. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras porta porttitor congue. + +Morbi mattis odio at sem rutrum, ut ornare tortor porttitor. + +#### Header 4 + +[⬆](#basic) + +Aenean et velit non nisl lobortis tempor. Ut felis augue, luctus quis pellentesque a, aliquet quis eros. Phasellus ut mollis eros. Pellentesque dictum urna eu massa lacinia, vitae ultricies elit facilisis. +Quisque gravida auctor turpis et placerat. Nulla ut pharetra elit, ac tincidunt nulla. Vivamus ullamcorper odio sit amet aliquam porttitor. Pellentesque odio tortor, semper a dictum sed, hendrerit a ligula. Proin convallis felis ac sapien fermentum dignissim. Ut eget nibh ut nulla ullamcorper dignissim molestie id magna. + +Pellentesque ac sem eget tellus sagittis lobortis. In convallis suscipit leo et ornare. Sed vel hendrerit neque, non placerat tortor. Aenean placerat tortor orci, ac scelerisque diam consequat a. Suspendisse malesuada viverra mauris sed tincidunt. + +## Section3 + +[⬆](#basic) + +Phasellus interdum sapien sed consectetur maximus. Fusce nec condimentum arcu. In aliquet magna lectus, eu pretium erat aliquam sed. Curabitur sodales, ligula eget porta euismod, justo ex sagittis odio, eu elementum velit ex sit amet tellus. Etiam at aliquam ex. Morbi in sodales ligula, nec volutpat lacus. Pellentesque quis aliquet lorem. Morbi vel blandit metus, luctus venenatis massa. Sed a varius ante, at laoreet ipsum. + +## Markdown + +[⬆](#basic) + +### List + +[⬆](#basic) + +- item 1 +- item 2 + - subitem21 +- item 3 + +### Links + +[⬆](#basic) + +[link](https://github.com/strdr4605) +https://github.com/strdr4605 + +```js +const text = "JavaScript syntax highlighting"; +alert(text); +``` + +### Table + +[⬆](#basic) + +| Tables | Are | Cool | +| ------------- |:-------------:| -----:| +| col 3 is | right-aligned | $1600 | +| col 2 is | centered | $12 | +| zebra stripes | are neat | $1 | + +### Blockquotes + +[⬆](#basic) + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. diff --git a/src/cli.ts b/src/cli.ts index 8c8fca5..6654f6f 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,7 +2,7 @@ import { writeFileSync } from "fs"; import yargs from "yargs"; -import { CliArgv } from "./interfaces"; +import { CliArgv } from "./types"; import { createNewFileContent } from "./transform"; import { getFileContent } from "./utils"; @@ -11,42 +11,53 @@ const argv: CliArgv = yargs file: { alias: "f", default: "README.md", - describe: "File to be parsed\nNote: file shoud have .md extension", + describe: "File to be parsed\nNote: file shoud have .md extension\n\n", type: "string", }, output: { alias: "o", describe: - "File to write new content\nNote: input file will be overwritten if not provided", + "File to write new content\nNote: input file will be overwritten if not provided\n\n", type: "string", }, slug: { alias: ["s", "header", "h"], describe: `Specify header slug to jump to. Note: use text after "#" in url. - https://github.com//#api -> api - `, + https://github.com//#api -> api\n\n`, + type: "string", + }, + position: { + alias: ["p"], + default: "header", + describe: `Specify position of the link + "header" -> Link will be in header + "start" -> Link will be at the start of the section + "end" -> Link will be at the end of the section + + Caution: may be some bugs with "end"\n\n`, + choices: ["header", "start", "end"], type: "string", }, start: { describe: `Specify header from where to start adding links. Notes: - multiple words should be wrapped in quotes " - will much by RegExp`, + multiple words should be wrapped in quotes "" + will much by RegExp\n\n`, type: "string", }, end: { describe: `Specify header to where to end adding links. Notes: - multiple words should be wrapped in quotes " - will much by RegExp`, + multiple words should be wrapped in quotes "" + will much by RegExp\n\n`, type: "string", }, maxLevel: { alias: ["l", "max-level"], default: 6, describe: - "Specify maximal header level to insert links.\nNote: value between 1 and 6", + "Specify maximal header level to insert links.\nNote: value between 1 and 6\n\n", choices: [1, 2, 3, 4, 5, 6], type: "number", }, @@ -58,14 +69,14 @@ const argv: CliArgv = yargs 2 -> 🔝 3 -> 🔙 4 -> 🆙 - 5 -> 🔼`, + 5 -> 🔼\n\n`, choices: [1, 2, 3, 4, 5], type: "number", }, silent: { boolean: true, describe: - "By default jump2header will add comment to created links.\nUse this flag if you don't want the comment", + "By default jump2header will add comment to created links.\nUse this flag if you don't want the comment\n\n", type: "boolean", }, }) @@ -74,7 +85,8 @@ const argv: CliArgv = yargs throw new Error('Input file should be markdown format, ".md"'); } return true; - }).argv; + }) + .wrap(85).argv; const fileContent: string = getFileContent(argv.file); diff --git a/src/constants.ts b/src/constants.ts index 1929c00..d499760 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -10,3 +10,5 @@ export const MARKDOWN_ANY_LINK_REGEXP = /\[.+\]\(.+\)$/g; export const MARKDOWN_HEADER_REGEXP = /^(?\#{1,6})\s+/; export const MARKDOWN_CODE_BLOCK_REGEXP = /^\`\`\`/; export const SPICIAL_CHARS_REGEXP = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~’]/g; + +export const LINK_OFFSET = "\n\n"; diff --git a/src/transform.ts b/src/transform.ts index 318b642..5d90366 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,12 +1,13 @@ -import { CliArgv, HeaderType } from "./interfaces"; +import { CliArgv, HeaderType } from "./types"; import { getHeaderLevel, getSlugFromHeader, + isAnchorLinkInSection, isAnchorLinkInText, isCodeBlock, isHeader, } from "./utils"; -import { EMOJIS, LINK_COMMENT } from "./constants"; +import { EMOJIS, LINK_COMMENT, LINK_OFFSET } from "./constants"; export function createNewFileContent( fileContent: string, @@ -59,6 +60,9 @@ export function createNewFileContent( endHeaderIndex = Infinity; } else { endHeaderIndex++; // To include last header when slicing the array. + if ("end" === argv.position) { + endHeaderIndex++; + } } } else { endHeaderIndex = Infinity; @@ -69,14 +73,47 @@ export function createNewFileContent( .filter( (header) => !isAnchorLinkInText(header.text) && + !isAnchorLinkInSection( + header.index, + argv.position, + fileContentByLine, + ) && header.level <= argv.maxLevel && header.slug !== argv.slug, ) - .forEach((header) => { - fileContentByLine[header.index] += `[${ - EMOJIS[argv.emoji - 1] - }](#${anchorSlug})${argv.silent ? "" : LINK_COMMENT}`; + .forEach((header, index) => { + switch (argv.position) { + case "header": + fileContentByLine[header.index] += `[${ + EMOJIS[argv.emoji - 1] + }](#${anchorSlug})${argv.silent ? "" : LINK_COMMENT}`; + break; + case "start": + fileContentByLine[header.index] = `${ + fileContentByLine[header.index] + }${LINK_OFFSET}[${EMOJIS[argv.emoji - 1]}](#${anchorSlug})${ + argv.silent ? "" : LINK_COMMENT + }`; + break; + case "end": + if (0 === index) { + break; + } + fileContentByLine[header.index] = `[${ + EMOJIS[argv.emoji - 1] + }](#${anchorSlug})${argv.silent ? "" : LINK_COMMENT}${LINK_OFFSET}${ + fileContentByLine[header.index] + }`; + break; + } }); - return fileContentByLine.join("\n"); + let newFileContent = fileContentByLine.join("\n"); + if (!Number.isFinite(endHeaderIndex) && "end" === argv.position) { + newFileContent += `${LINK_OFFSET}[${ + EMOJIS[argv.emoji - 1] + }](#${anchorSlug})\n`; + } + + return newFileContent; } diff --git a/src/interfaces.ts b/src/types.ts similarity index 74% rename from src/interfaces.ts rename to src/types.ts index fbb4fa8..f862092 100644 --- a/src/interfaces.ts +++ b/src/types.ts @@ -1,7 +1,10 @@ +export type PositionType = "header" | "start" | "end"; + export interface CliArgv { file: string; output?: string; slug?: string; + position: PositionType | string; start?: string; end?: string; maxLevel: number; diff --git a/src/utils.ts b/src/utils.ts index a524e09..85160ca 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import emojiRegex from "emoji-regex"; import { readFileSync } from "fs"; import { + LINK_OFFSET, MARKDOWN_ANCHOR_LINK_REGEXP, MARKDOWN_ANY_LINK_REGEXP, MARKDOWN_CODE_BLOCK_REGEXP, @@ -8,6 +9,7 @@ import { SPICIAL_CHARS_REGEXP, WHITE_SPASE_REGEXP, } from "./constants"; +import { PositionType } from "./types"; export function isHeader(line: string): boolean { return MARKDOWN_HEADER_REGEXP.test(line); @@ -29,6 +31,25 @@ export function isAnchorLinkInText(text: string): boolean { return MARKDOWN_ANCHOR_LINK_REGEXP.test(text); } +export function isAnchorLinkInSection( + headerIndex: number, + linkPosition: PositionType | string, + fileContentByLine: string[], +): boolean { + const OFFSET = LINK_OFFSET.length; + return ( + { + header: false, + start: MARKDOWN_ANCHOR_LINK_REGEXP.test( + fileContentByLine[headerIndex + OFFSET], + ), + end: MARKDOWN_ANCHOR_LINK_REGEXP.test( + fileContentByLine[headerIndex - OFFSET], + ), + }[linkPosition as PositionType] || false + ); +} + export function getSlugFromHeader(header: string): string { return header .trim()