diff options
author | Patryk Niedźwiedziński <pniedzwiedzinski19@gmail.com> | 2020-01-28 18:16:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-28 18:16:03 +0100 |
commit | 42c089293fed2db9c8a09d8e55f046169b5f0844 (patch) | |
tree | 18a053fe7f1e03fc5419a8c3d2a8bc8193cb27f0 /lib | |
parent | 7b8d75cd87e392d9ffb3defa538a041c4f7e0f0c (diff) | |
parent | acfd4d0c722ceeca6aad51be64baf117e73f286c (diff) | |
download | kronikarz-42c089293fed2db9c8a09d8e55f046169b5f0844.tar.gz kronikarz-42c089293fed2db9c8a09d8e55f046169b5f0844.zip |
Merge pull request #8 from pniedzwiedzinski/feature/generateApi v2.0
Feature/generate api
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Post.ts | 73 | ||||
-rw-r--r-- | lib/generateApi.ts | 99 | ||||
-rw-r--r-- | lib/getPost.ts | 5 | ||||
-rw-r--r-- | lib/getPosts.ts | 5 | ||||
-rw-r--r-- | lib/index.ts | 7 | ||||
-rw-r--r-- | lib/interfaces.ts | 35 | ||||
-rw-r--r-- | lib/parsePost.ts | 46 | ||||
-rw-r--r-- | lib/utils.ts | 14 |
8 files changed, 218 insertions, 66 deletions
diff --git a/lib/Post.ts b/lib/Post.ts new file mode 100644 index 0000000..f99ef83 --- /dev/null +++ b/lib/Post.ts @@ -0,0 +1,73 @@ +import * as fs from 'fs'; +import { JSDOM } from 'jsdom'; + +const frontmatter = require('front-matter'); +const md = require('markdown-it')(); + +import { dateToPath, dateToString } from './utils'; +import { Date, FrontMatterObject, PostApiEntry, Meta } from './interfaces'; + +export default class Post { + date: Date; + fileTitle: string; + fileContent: string; + post: FrontMatterObject; + + constructor(filePath: string) { + let [year, month, day, title] = filePath.split('/').splice(-4, 4); + this.fileTitle = title.substr(0, title.lastIndexOf('.')); + this.date = { year, month, day }; + this.fileContent = fs.readFileSync(filePath, 'utf-8'); + this.post = frontmatter(this.fileContent); + } + + toApi(): PostApiEntry { + const { author, title, additionalMeta } = this.getMeta(); + const { description, html } = this.getPostContent(); + return { + date: dateToString(this.date), + author, + title, + path: `/kronika/${dateToPath(this.date)}/${this.fileTitle}`, + description, + meta: additionalMeta, + content: html, + }; + } + + getMeta(): Meta { + const { attributes } = this.post; + const author: string = attributes.author; + if (!author) throw 'Error while parsing the author'; + const title: string = attributes.title; + if (!title) throw 'Error while parsing the title'; + const additionalMeta = Object.keys(attributes) + .filter((key) => key != 'title' && key != 'author') + .reduce((acc, key) => ({ ...acc, [key]: attributes[key] }), {}); + return { + author, + title, + additionalMeta, + }; + } + + getPostContent() { + const { body } = this.post; + const html = `<div>${md.render(body)}</div>`; + const description = this.getDescription(html); + + return { + html, + description, + }; + } + + getDescription(html: string): string { + const { document } = new JSDOM(`<div>${html}</div>`).window; + const elements = document.getElementsByTagName('p'); + + const description = elements[1].textContent; + + return description || ''; + } +} diff --git a/lib/generateApi.ts b/lib/generateApi.ts new file mode 100644 index 0000000..0024d9f --- /dev/null +++ b/lib/generateApi.ts @@ -0,0 +1,99 @@ +import fs from 'fs'; +import path from 'path'; +import Post from './Post'; +import { dateToPath } from './utils'; +import { Object as PlainObject } from './interfaces'; + +function mkDirByPathSync(targetDir: string) { + const sep = path.sep; + const initDir = path.isAbsolute(targetDir) ? sep : ''; + const baseDir = '.'; + + return targetDir.split(sep).reduce((parentDir, childDir) => { + const curDir = path.resolve(baseDir, parentDir, childDir); + try { + fs.mkdirSync(curDir); + } catch (err) { + if (err.code === 'EEXIST') { + // curDir already exists! + return curDir; + } + + // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. + if (err.code === 'ENOENT') { + // Throw the original parentDir error on curDir `ENOENT` failure. + throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); + } + + const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; + if (!caughtErr || (caughtErr && targetDir === curDir)) { + throw err; // Throw if it's just the last created dir. + } + } + + return curDir; + }, initDir); +} + +export function saveApiEntry(post: Post, path: string) { + const dir = `${path}/api/posts/${dateToPath(post.date)}`; + + mkDirByPathSync(dir); + + const apiEntry = post.toApi(); + + fs.writeFile( + `${dir}/${post.fileTitle}.json`, + JSON.stringify(apiEntry), + (err) => (err ? console.log(err) : null) + ); +} + +/* + * This function generates paged api index on path `/api/posts` + */ +function generateApiIndex(posts: Array<Post>, path: string) { + const parsedPosts = posts.map((post) => { + const apiEntry = post.toApi(); + delete apiEntry.content; + return apiEntry; + }); + const filePath = `${path}/api/posts.json`; + fs.writeFile(filePath, JSON.stringify(parsedPosts), (err) => + err ? console.log(err) : null + ); +} + +function generateCategories(posts: Array<Post>, path: string) { + const categories: PlainObject = {}; + posts.forEach((post) => { + const apiEntry = post.toApi(); + delete apiEntry.content; + if (apiEntry.meta['category']) { + apiEntry.meta['category'].forEach((category: string) => + categories[category] + ? categories[category].push(apiEntry) + : (categories[category] = [apiEntry]) + ); + } + }); + + const dir = `${path}/api/category`; + mkDirByPathSync(dir); + Object.keys(categories).forEach((category) => { + const filePath = `${dir}/${category}.json`; + fs.writeFile(filePath, JSON.stringify(categories[category]), (err) => + err ? console.log(err) : null + ); + }); +} + +export function generateApi(posts: Array<Post>, path: string) { + try { + generateApiIndex(posts, path); + generateCategories(posts, path); + posts.forEach((post) => saveApiEntry(post, path)); + } catch (err) { + console.log(err); + } +} diff --git a/lib/getPost.ts b/lib/getPost.ts index 2555215..4860ff9 100644 --- a/lib/getPost.ts +++ b/lib/getPost.ts @@ -1,5 +1,4 @@ -import { parsePost } from "./parsePost"; -import { Post } from "./interfaces"; +import Post from "./Post"; interface getPostArgument { year: string; @@ -14,6 +13,6 @@ export function getPost( ): Post { const filePath = `${path}/${year}/${month}/${day}/${title}.md`; - const post = parsePost(filePath); + const post = new Post(filePath); return post; } diff --git a/lib/getPosts.ts b/lib/getPosts.ts index c363c67..814f6d8 100644 --- a/lib/getPosts.ts +++ b/lib/getPosts.ts @@ -1,5 +1,4 @@ -import { Post } from "./interfaces"; -import { parsePost } from "./parsePost"; +import Post from "./Post"; import { readDir } from "./utils"; function getPosts(path: string): Array<Post> { @@ -14,7 +13,7 @@ function getPosts(path: string): Array<Post> { files.forEach((file: string) => { const fsRoute = `${path}/${year}/${month}/${day}/${file}`; try { - const post = parsePost(fsRoute); + const post = new Post(fsRoute); routesArray.push(post); } catch (err) { console.log(err); diff --git a/lib/index.ts b/lib/index.ts index ade7180..4bf4dd3 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,6 +1,7 @@ import { getPost as apiGetPost } from "./getPost"; import { getPosts as apiGetPosts } from "./getPosts"; -import { Post } from "./interfaces"; +import { generateApi as apiGenerateApi } from "./generateApi" +import Post from "./Post"; export default class Kronikarz { postPath: string; @@ -15,4 +16,8 @@ export default class Kronikarz { getPost(year: string, month: string, day: string, title: string): Post { return apiGetPost({ year, month, day, title }, this.postPath); } + generateApi(path: string) { + const posts = this.getPosts(); + apiGenerateApi(posts, path) + } } diff --git a/lib/interfaces.ts b/lib/interfaces.ts index 1d598c6..1fbb226 100644 --- a/lib/interfaces.ts +++ b/lib/interfaces.ts @@ -1,24 +1,33 @@ -interface Date { +export interface Date { year: string; month: string; day: string; } -interface Meta { - [key: string]: string; +export interface Object { + [key: string]: any; } -export interface PostContent { - html: string; - markdown: string; - description: string; - meta: Meta; +export interface Meta { + title: string; + author: string; + additionalMeta: Object; +} + +export interface FrontMatterObject { + body: string; + attributes: Object; } -export interface Post { - date: Date; +export interface PostApiListEntry { + date: string; + author: string; title: string; - content: PostContent; - filePath: string; - route: string; + path: string; + description: string; + meta: Object; +} + +export interface PostApiEntry extends PostApiListEntry { + content: string; } diff --git a/lib/parsePost.ts b/lib/parsePost.ts deleted file mode 100644 index 2f5f2b9..0000000 --- a/lib/parsePost.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as fs from "fs"; -import { JSDOM } from "jsdom"; -import { Post, PostContent } from "./interfaces"; - -const frontmatter = require("front-matter"); -const md = require("markdown-it")(); - -function getDescription(html: string): string { - const { document } = new JSDOM(`<div>${html}</div>`).window; - const elements = document.getElementsByTagName("p"); - - const description = elements[1].textContent; - - return description || ""; -} - -function getPostContent(fileContent: string): PostContent { - const post = frontmatter(fileContent); - - const markdown = post.body; - const html = `<div>${md.render(markdown)}</div>`; - const description = getDescription(html); - - return { - html, - markdown, - description, - meta: post.attributes - }; -} - -export function parsePost(filePath: string): Post { - let [year, month, day, title] = filePath.split("/").splice(-4, 4); - title = title.substr(0, title.lastIndexOf(".")); - const date = { year, month, day }; - const fileContent = fs.readFileSync(filePath, "utf-8"); - const content = getPostContent(fileContent); - - return { - date, - title, - content, - filePath, - route: `/kronika/${year}/${month}/${day}/${title}` - }; -} diff --git a/lib/utils.ts b/lib/utils.ts index 23a3947..f75c0e4 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,4 +1,6 @@ import * as fs from "fs"; +import { Date } from './interfaces'; +import Post from './Post' export function readDir(path: string): Array<string> { if (fs.existsSync(path)) { @@ -7,3 +9,15 @@ export function readDir(path: string): Array<string> { throw `Path "${path}" doesn't exist`; } } + +export function sortPosts(posts: Array<Post>): Array<Post> { + return posts; +} + +export function dateToString({ year, month, day }: Date): string { + return `${year}-${month}-${day}` +} + +export function dateToPath({ year, month, day }: Date): string { + return `${year}/${month}/${day}` +} |