Skip to content

Commit

Permalink
feat: getters for title and summary (#12)
Browse files Browse the repository at this point in the history
* feat: getters for title and summary

* chore: increase default summary length to 150

* chore: use truncate

* chore: rename length to truncate

* chore: remove default truncate

* feat: add getTags getter

* chore: fix comment

* chore: refactor into getProperty func
  • Loading branch information
marbemac authored Jun 19, 2019
1 parent 56b2c02 commit 484058f
Show file tree
Hide file tree
Showing 18 changed files with 1,657 additions and 871 deletions.
30 changes: 16 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"build": "sl-scripts build",
"build.docs": "sl-scripts build:typedoc",
"commit": "git-cz",
"lint": "tslint 'src/**/*.{ts,tsx}'",
"lint": "tslint 'src/**/*.ts'",
"lint.fix": "yarn lint --fix",
"release": "sl-scripts release",
"release.docs": "sl-scripts release:docs",
Expand All @@ -39,25 +39,27 @@
"test.watch": "yarn test --watch"
},
"dependencies": {
"js-yaml": "3.13.x",
"lodash": ">=4.17",
"remark-frontmatter": "1.3.x",
"remark-parse": "6.0.x",
"remark-stringify": "6.0.x",
"unified": "7.1.x"
"js-yaml": "^3.13",
"lodash": ">=4.17.5",
"mdast-util-to-string": "~1.0",
"remark-frontmatter": "~1.3",
"remark-parse": "~6.0",
"remark-stringify": "~6.0",
"unified": "~7.1",
"unist-util-select": "~2.0"
},
"devDependencies": {
"@stoplight/scripts": "5.1.0",
"@stoplight/types": "4.1.0",
"@types/jest": "^24.0.12",
"@stoplight/scripts": "~5.1",
"@stoplight/types": "^9",
"@types/jest": "^24.0",
"@types/js-yaml": "3.12.1",
"@types/lodash": "^4.14.123",
"@types/lodash": "^4.14",
"@types/unist": "2.0.3",
"jest": "^24.7.1",
"jest": "^24.8",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"tslint": "^5.17",
"tslint-config-stoplight": "^1.2.0",
"typescript": "3.4.5"
"typescript": "3.5.2"
},
"lint-staged": {
"*.{ts,tsx}$": [
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/stringify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { stringify } from '../stringify';
describe('stringify', () => {
it('should work', () => {
expect(
stringify(JSON.parse(fs.readFileSync(path.resolve(__dirname, './fixtures/simple.json'), 'utf-8')))
stringify(JSON.parse(fs.readFileSync(path.resolve(__dirname, './fixtures/simple.json'), 'utf-8'))),
).toMatchSnapshot();
});

Expand Down
2 changes: 1 addition & 1 deletion src/frontmatter/__tests__/fronmatter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ Coolio.
instance.set('new item', 2);

expect(parsed.ast.children[0]!.value).toEqual(
'title: Graphite Introduction\ntags: [introductions, guides]\nnew item: 2'
'title: Graphite Introduction\ntags: [introductions, guides]\nnew item: 2',
);
});

Expand Down
13 changes: 8 additions & 5 deletions src/frontmatter/frontmatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ const safeParse = (value: string) => {
};

export class Frontmatter<T extends object = any> implements IFrontmatter<T> {
public readonly document: Unist.Parent;
public readonly document: Unist.Node;
private readonly node: Unist.Literal;
private properties: Partial<T> | null;

constructor(data: Unist.Parent | string, mutate = false) {
constructor(data: Unist.Node | string, mutate = false) {
const root =
typeof data === 'string' ? parseWithPointers(data).data : mutate ? data : JSON.parse(JSON.stringify(data));
if (root.type !== 'root') {
Expand Down Expand Up @@ -95,7 +95,10 @@ export class Frontmatter<T extends object = any> implements IFrontmatter<T> {
}

private updateDocument() {
const index = this.document.children.indexOf(this.node);
const children = this.document.children as Unist.Parent['children'] | undefined;
if (!children) return;

const index = children.indexOf(this.node);

this.node.value = this.isEmpty
? ''
Expand All @@ -108,10 +111,10 @@ export class Frontmatter<T extends object = any> implements IFrontmatter<T> {

if (this.isEmpty) {
if (index !== -1) {
this.document.children.splice(index, 1);
children.splice(index, 1);
}
} else if (index === -1) {
this.document.children.unshift(this.node);
children.unshift(this.node);
}
}
}
2 changes: 1 addition & 1 deletion src/frontmatter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type PropertyPath = PropertyKey | PropertyKey[];
import * as Unist from 'unist';

export interface IFrontmatter<T extends object = any> {
document: Unist.Parent;
document: Unist.Node;
getAll(): Partial<T> | void;
get<V = unknown>(prop: PropertyPath): V | void;
set(prop: PropertyPath, value: unknown): void;
Expand Down
51 changes: 51 additions & 0 deletions src/getters/__tests__/get-summary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { parse } from '../../parse';
import { getSummary } from '../get-summary';

describe('get-summary', () => {
it('should return frontmatter summary if present', () => {
const summary = getSummary(
parse(`---
summary: my summary
---
# Other Title
Yo
`),
);

expect(summary).toBe('my summary');
});

it('should return first paragraph if present', () => {
const summary = getSummary(
parse(`# Other Title
Paragraph 1.
Paragraph 2.
`),
);

expect(summary).toBe('Paragraph 1.');
});

it('should strip markdown out of paragraph', () => {
const summary = getSummary(parse(`this has a [link](./foo.json)`));

expect(summary).toBe('this has a link');
});

it('should accept a length option', () => {
const summary = getSummary(parse(`the dog barks`), {
truncate: 3,
});

expect(summary).toBe('the...');
});

it('should return undefined if no frontmatter summary and no paragraphs', () => {
const summary = getSummary(parse(`## Hello`));
expect(summary).toBe(undefined);
});
});
49 changes: 49 additions & 0 deletions src/getters/__tests__/get-tags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { parse } from '../../parse';
import { getTags } from '../get-tags';

describe('get-tags', () => {
it('should return frontmatter tags if present', () => {
const tags = getTags(
parse(`---
tags: [t1, t2]
---
# Other Title
Yo
`),
);

expect(tags).toEqual(['t1', 't2']);
});

it('should remove nil tags', () => {
const tags = getTags(
parse(`---
tags: [t1, undefined]
---`),
);

expect(tags).toEqual(['t1']);
});

it('should not fail if tags is undefined', () => {
const tags = getTags(
parse(`---
tags: undefined
---`),
);

expect(tags).toEqual([]);
});

it('should not fail if tags is null', () => {
const tags = getTags(
parse(`---
tags: null
---`),
);

expect(tags).toEqual([]);
});
});
52 changes: 52 additions & 0 deletions src/getters/__tests__/get-title.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { parse } from '../../parse';
import { getTitle } from '../get-title';

describe('get-title', () => {
it('should return frontmatter title if present', () => {
const title = getTitle(
parse(`---
title: hello
---
# Other Title
Yo
`),
);

expect(title).toBe('hello');
});

it('should return heading if present', () => {
const title = getTitle(
parse(`---
summary: what it is about
---
# Other Title
Yo
`),
);

expect(title).toBe('Other Title');
});

it('should return headings other than h1 if present', () => {
const title = getTitle(
parse(`what it is about
## Other Title 2
Yo
`),
);

expect(title).toBe('Other Title 2');
});

it('should return undefined if no frontmatter title and no headings', () => {
const title = getTitle(parse(`what it is about`));
expect(title).toBe(undefined);
});
});
29 changes: 29 additions & 0 deletions src/getters/get-property.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as Unist from 'unist';

const { select } = require('unist-util-select');
const toString = require('mdast-util-to-string');

import { Frontmatter } from '../frontmatter';

// Priority: yaml title, then first heading
export const getProperty = (propName: string, element?: string, data?: Unist.Node) => {
let target: string | void | undefined;

if (data) {
try {
const frontmatter = new Frontmatter(data, true);
target = frontmatter.get(propName);

if (element && !target) {
const elem = select(element, data);
if (elem) {
target = toString(elem);
}
}
} catch (e) {
console.warn(`Error getting ${propName} from markdown document`, e);
}
}

return target;
};
20 changes: 20 additions & 0 deletions src/getters/get-summary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { truncate } from 'lodash';
import * as Unist from 'unist';

import { getProperty } from './get-property';

export interface IGetSummaryOpts {
truncate?: number;
}

// Priority: yaml summary, then first paragraph
export const getSummary = (data?: Unist.Node, opts: IGetSummaryOpts = {}) => {
let summary = getProperty('summary', 'paragraph', data);

if (summary && opts.truncate) {
// +3 to account for ellipsis
summary = truncate(summary, { length: opts.truncate + 3 });
}

return summary;
};
29 changes: 29 additions & 0 deletions src/getters/get-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as Unist from 'unist';

import { Frontmatter } from '../frontmatter';

// Priority: yaml tags
export const getTags = (data?: Unist.Node): string[] => {
const tags: string[] = [];

if (data) {
try {
const frontmatter = new Frontmatter(data, true);
const dataTags = frontmatter.get('tags');

if (dataTags && Array.isArray(dataTags)) {
return dataTags.reduce<string[]>((filteredTags, tag) => {
if (tag && typeof tag === 'string' && tag !== 'undefined' && tag !== 'null') {
filteredTags.push(String(tag));
}

return filteredTags;
}, []);
}
} catch (e) {
console.warn('Error getting tags from markdown document', e);
}
}

return tags;
};
8 changes: 8 additions & 0 deletions src/getters/get-title.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Unist from 'unist';

import { getProperty } from './get-property';

// Priority: yaml title, then first heading
export const getTitle = (data?: Unist.Node) => {
return getProperty('title', 'heading', data);
};
4 changes: 4 additions & 0 deletions src/getters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './get-summary';
export * from './get-tags';
export * from './get-title';
export * from './get-property';
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './parse';
export * from './parseWithPointers';
export * from './types';
export * from './frontmatter';
export * from './getters';
2 changes: 1 addition & 1 deletion src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const defaultProcessor = unified()
export const parse = (
input: string,
opts: IParseOpts = defaultOpts,
processor: unified.Processor = defaultProcessor
processor: unified.Processor = defaultProcessor,
): Unist.Node => {
// return the parsed remark ast
return processor()
Expand Down
2 changes: 1 addition & 1 deletion src/parseWithPointers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MarkdownParserResult } from './types';
export const parseWithPointers = (
value: string,
opts?: IParseOpts,
processor?: unified.Processor
processor?: unified.Processor,
): MarkdownParserResult => {
const tree = parse(value, opts, processor) as Unist.Parent;
return {
Expand Down
2 changes: 1 addition & 1 deletion src/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const defaultProcessor = unified()
export const stringify = (
tree: Node,
opts: IStringifyOpts = defaultOpts,
processor: unified.Processor = defaultProcessor
processor: unified.Processor = defaultProcessor,
) => {
return processor()
.data('settings', opts)
Expand Down
Loading

0 comments on commit 484058f

Please sign in to comment.