@sphido/core 1.0.11 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  MIT License
2
2
  -----------
3
3
 
4
- Copyright (c) 2021 Roman Ožana (https://ozana.cz/)
4
+ Copyright (c) 2022 Roman Ožana (https://ozana.cz/)
5
5
  Permission is hereby granted, free of charge, to any person
6
6
  obtaining a copy of this software and associated documentation
7
7
  files (the "Software"), to deal in the Software without
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Page generator that flatten pages structure
3
+ * @param {array} pages
4
+ */
5
+ export function * allPages(pages) {
6
+ for (const page of pages) {
7
+ if (page?.children) {
8
+ yield * allPages(page.children);
9
+ } else {
10
+ yield page;
11
+ }
12
+ }
13
+ }
package/lib/get-pages.js CHANGED
@@ -1,13 +1,33 @@
1
- import {getPage} from './get-page.js';
1
+ import {join, parse} from 'node:path';
2
+ import {readdir} from 'node:fs/promises';
3
+ import {isPage} from './is-page.js';
2
4
 
3
5
  /**
4
- * Return list of pages from directory
5
- * @param {Array} files
6
- * @param {Array} extenders
7
- * @returns {Promise<any[]>}
6
+ * Retrieve an array tree of pages from path
7
+ * @param {string} path
8
+ * @param {Function} include
9
+ * @param extenders
10
+ * @returns {Promise<Awaited<unknown>[{name, path}]>}
8
11
  */
9
- export const getPages = async (files, ...extenders) => Promise.all(
10
- files.map(
11
- file => getPage(file, ...extenders),
12
- ),
13
- );
12
+ export async function getPages({path = 'content', include = isPage} = {}, ...extenders) {
13
+ const dir = await readdir(path, {withFileTypes: true});
14
+
15
+ return Promise.all(
16
+ dir
17
+ .filter(dirent => include(dirent))
18
+ .map(async dirent => {
19
+ // Page
20
+ const page = {name: parse(dirent.name).name, path: join(path, dirent.name)};
21
+
22
+ // Read subdirectory recursively
23
+ if (dirent.isDirectory()) {
24
+ page.children = await getPages({path: page.path, isPage: include}, ...extenders);
25
+ }
26
+
27
+ // Calling callbacks
28
+ await Promise.all(extenders.filter(f => typeof f === 'function').map(f => f(page, dirent, path)));
29
+
30
+ // Assign objects with page
31
+ return Object.assign(page, ...extenders.filter(o => typeof o === 'object'));
32
+ }));
33
+ }
package/lib/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export {getPages} from './get-pages.js';
2
- export {getPage} from './get-page.js';
2
+ export {allPages} from './all-pages.js';
3
+ export {readFile} from './read-file.js';
4
+ export {writeFile} from './write-file.js';
package/lib/is-page.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Default page filter
3
+ *
4
+ * - skip hidden files (starts with .)
5
+ * - skip and directories (starts with .)
6
+ * - skip drafts files with underscore at the beginning
7
+ * - accept only *.html and *.md files
8
+ *
9
+ * @param {object} dirent
10
+ * @returns {boolean}
11
+ */
12
+ export function isPage(dirent) {
13
+ // Accept only *.md, *.html files
14
+ if (dirent.isFile() && !dirent.name.startsWith('_') && !dirent.name.startsWith('.')) {
15
+ return dirent.name.endsWith('.md') || dirent.name.endsWith('.html');
16
+ }
17
+
18
+ // Or not hidden directory
19
+ return dirent.isDirectory() && !dirent.name.startsWith('.');
20
+ }
@@ -0,0 +1,10 @@
1
+ import {readFile as readFileAsync} from 'node:fs/promises';
2
+
3
+ /**
4
+ * Read file content as string
5
+ * @param {string} path
6
+ * @returns {Promise<*>}
7
+ */
8
+ export async function readFile(path) {
9
+ return readFileAsync(path, 'utf8');
10
+ }
@@ -0,0 +1,18 @@
1
+ import {mkdir, writeFile as writeFileAsync} from 'node:fs/promises';
2
+ import {existsSync} from 'node:fs';
3
+ import {dirname} from 'node:path';
4
+
5
+ /**
6
+ * Write content to the file and create directory if not exists
7
+ *
8
+ * @param {string} file
9
+ * @param {string} content
10
+ * @returns {Promise<*>}
11
+ */
12
+ export async function writeFile(file, content) {
13
+ if (!existsSync(dirname(file))) {
14
+ await mkdir(dirname(file), {recursive: true});
15
+ }
16
+
17
+ return writeFileAsync(file, content);
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphido/core",
3
- "version": "1.0.11",
3
+ "version": "2.0.1",
4
4
  "author": {
5
5
  "name": "Roman Ožana",
6
6
  "email": "roman@ozana.cz",
@@ -11,7 +11,7 @@
11
11
  "type": "module",
12
12
  "exports": "./lib/index.js",
13
13
  "engines": {
14
- "node": ">=12"
14
+ "node": ">=14"
15
15
  },
16
16
  "keywords": [
17
17
  "markdown",
@@ -33,10 +33,12 @@
33
33
  "url": "git@github.com:sphido/sphido.git"
34
34
  },
35
35
  "devDependencies": {
36
- "ava": "^4.2.0"
36
+ "@sindresorhus/slugify": "^2.1.0",
37
+ "ava": "^4.2.0",
38
+ "marked": "^4.0.17"
37
39
  },
38
40
  "scripts": {
39
41
  "test": "ava"
40
42
  },
41
- "gitHead": "538e6fa2ee43383c6728ceaf23e6f8fdd7f4ace4"
43
+ "gitHead": "75cf4126bc6027237f192873c51c0d636aacd0ab"
42
44
  }
package/readme.md CHANGED
@@ -1,25 +1,117 @@
1
1
  # @sphido/core
2
2
 
3
- Core contains most important [`getPage()`](https://github.com/sphido/sphido/blob/main/packages/sphido-core/lib/get-page.js)
4
- and [`getPages()`](https://github.com/sphido/sphido/blob/main/packages/sphido-core/lib/get-pages.js) functions.
3
+ Sphido core package contains two most important function `getPages()` and `allPages()`.
4
+ The `getPages()` function scans directories for all `*.md` and `*.html` files.
5
+ Second function `allPages()` is [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)
6
+ that allow to iterate over all pages.
5
7
 
6
- * `getPage(file, [...extenders])` - returns a `page` object generated from file.
7
- * `getPages(files, [...extenders])` - returns array of `page` objects from list of files.
8
+ ```javascript
9
+ const pages = await getPages({path: 'content'}, /* ...exteners */);
10
+ ```
8
11
 
9
- Basic page object looks like bellow:
12
+ Returned structure is very simple and looks like follow:
10
13
 
11
14
  ```json
12
- {
13
- "file":"path to the file",
14
- "dir": " directory to the file",
15
- "base" : "file basename without extension",
16
- "content": "content of file"
15
+ [
16
+ {
17
+ "name": "Main page",
18
+ "path": "content/Main page.md"
19
+ },
20
+ {
21
+ "name": "Directory",
22
+ "children": [
23
+ {
24
+ "name": "Subpage one",
25
+ "path": "content/Directory/Subpage one.md"
26
+ },
27
+ {
28
+ "name": "Subpage two",
29
+ "path": "content/Directory/Subpage two.md"
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ ```
35
+
36
+ Then iterate over pages like follow:
37
+
38
+ ```javascript
39
+ for (const page of allPages(pages)) {
40
+ console.log(page);
41
+ }
42
+ ```
43
+
44
+ ## Extend
45
+
46
+ Every single `page` object inside structure can be modified with extender. Extenders are set as additional parameters of the `getPages()` function.
47
+ There are two types of extenders:
48
+
49
+ ### *callback* extenders
50
+
51
+ Callback extender is a function that is called during recursion over each page with three
52
+ parameters passed to the function `page`, `path` and [`dirent`](https://nodejs.org/api/fs.html#class-fsdirent).
53
+
54
+ ```javascript
55
+ const callbackExtender = (page, path, dirent) => {
56
+ // do anything with page object
57
+ }
58
+
59
+ const pages = await getPages({path: 'content'}, callbackExtender);
60
+ ```
61
+
62
+ ### *object* extenders
63
+
64
+ This extender is just a simple JavaScript object that is combined with the `page` object using the [Object.assign()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) function.
65
+
66
+ ```javascript
67
+ const objectExtender = {
68
+ author: 'Roman Ožana'
17
69
  }
70
+
71
+ const pages = await getPages({path: 'content'}, objectExtender);
72
+ ```
73
+
74
+ There is no limit to the number of extenders, you can combine as many as you want.
75
+ Let's have the following code:
76
+
77
+ ```javascript
78
+ const extenders = [
79
+ // callback extender will be called during iteration
80
+ (page) => {
81
+ // add property
82
+ page.title = `${page.name} | my best website`;
83
+ // or function
84
+ page.getDate = () => new Date();
85
+ },
86
+
87
+ // object extender will be merged with page object
88
+ {
89
+ "author": "Roman Ožana",
90
+ "getLink": function () {
91
+ return this.path.replace('content', 'public');
92
+ }
93
+ }
94
+ ];
95
+
96
+ const pages = getPages({path: 'content'}, ...extenders);
18
97
  ```
19
98
 
20
- Pages are merged inside `getPage()` function together with `extenders`. Extender can be `object` or `function`.
99
+ then you get this structure:
21
100
 
22
- ## Install
101
+ ```json
102
+ [
103
+ {
104
+ "name": "Main page",
105
+ "path": "content/Main page.md",
106
+ "title": "Main page | my best website",
107
+ "author": "Roman Ožana",
108
+ "getDate": "[Function: getDate]",
109
+ "getLink": "[Function: getLink]"
110
+ }
111
+ ]
112
+ ```
113
+
114
+ ## Installation
23
115
 
24
116
  ```bash
25
117
  yarn add @sphido/core
@@ -27,25 +119,36 @@ yarn add @sphido/core
27
119
 
28
120
  ## Example
29
121
 
122
+ Following example read all `*.md` files in `content` directory and process them with [marked](https://github.com/markedjs/marked) to HTML files
123
+
30
124
  ```javascript
31
- import globby from 'globby';
32
- import {getPages} from '@sphido/core';
125
+ #!/usr/bin/env node
33
126
 
34
- (async () => {
35
- const pages = await getPages(
36
- await globby('content/**/*.{md,html}')
37
- );
38
- })();
39
- ```
127
+ import {dirname, relative, join} from 'node:path';
128
+ import {getPages, allPages, readFile, writeFile} from '@sphido/core';
129
+ import slugify from '@sindresorhus/slugify';
130
+ import {marked} from 'marked';
40
131
 
41
- You can also process single page like follow:
132
+ function getHtml({name, content, path}) {
133
+ return `<!DOCTYPE html>
134
+ <html lang="cs" dir="ltr">
135
+ <head>
136
+ <meta charset="UTF-8">
137
+ <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
138
+ <title>${name} | Sphido Example page</title>
139
+ </head>
140
+ <body class="prose mx-auto my-6">${content}</body>
141
+ <!-- Generated with Sphido from ${path} -->
142
+ </html>`;
143
+ }
42
144
 
43
- ```javascript
44
- import {getPage} from '@sphido/core';
145
+ const pages = await getPages({path: 'content'});
45
146
 
46
- (async () => {
47
- const page = await getPage('example.md');
48
- })();
147
+ for (const page of allPages(pages)) {
148
+ page.output = join('public', relative('content', dirname(page.path)), slugify(page.name) + '.html');
149
+ page.content = marked(await readFile(page.path));
150
+ await writeFile(page.output, getHtml(page));
151
+ }
49
152
  ```
50
153
 
51
154
  ## Source codes
package/lib/get-page.js DELETED
@@ -1,29 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import {promisify} from 'node:util';
4
-
5
- const readFile = promisify(fs.readFile);
6
-
7
- /**
8
- * Return {page} object
9
- * @param {String} file
10
- * @param {Object.<string, number>} extenders
11
- * @returns {Promise<*>}
12
- */
13
- export async function getPage(file, ...extenders) {
14
- const ext = path.extname(file);
15
-
16
- const page = {
17
- file,
18
- dir: path.relative('.', path.dirname(file)),
19
- ext,
20
- base: path.basename(file, ext),
21
- content: await readFile(file, 'utf8'),
22
- };
23
-
24
- // Callbacks only
25
- extenders.filter(f => typeof f === 'function').map(f => f(page));
26
-
27
- // Assign objects
28
- return Object.assign(page, ...extenders.filter(f => typeof f === 'object'));
29
- }