-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(remark-table-of-content): don't overwrite existing property
- Loading branch information
Showing
9 changed files
with
198 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,108 @@ | ||
import { visit } from 'unist-util-visit' | ||
import slugify from '@sindresorhus/slugify' | ||
import { is_object_literal } from 'mixme' | ||
|
||
const get = (obj, keys, strict = false) => { | ||
for (const key of keys) { | ||
if (obj.hasOwnProperty(key)) { | ||
obj = obj[key] | ||
} else if (!strict) { | ||
return undefined | ||
} else { | ||
throw Error('REMARK_TABLE_OF_CONTENT: property does not exists in strict mode.') | ||
} | ||
} | ||
return obj | ||
} | ||
|
||
const set = (obj, keys, value, overwrite = false) => { | ||
if( obj === null || typeof obj !== 'object' ) { | ||
throw Error('REMARK_TABLE_OF_CONTENT: argument is not an object.') | ||
} | ||
for (let i = 0; i < keys.length; i++) { | ||
const last = i === keys.length - 1 | ||
const key = keys[i] | ||
if (!last) { | ||
if (obj.hasOwnProperty(key)) { | ||
// Move into the child | ||
if (is_object_literal(obj[key])) { | ||
obj = obj[key] | ||
} else { | ||
// Never overwrite a parent | ||
throw Error( | ||
'REMARK_TABLE_OF_CONTENT: cannot overwrite parent property.' | ||
) | ||
} | ||
} else { | ||
// Create the parent property | ||
obj[key] = {} | ||
obj = obj[key] | ||
} | ||
} else { | ||
if (obj.hasOwnProperty(key)) { | ||
// Move into the child | ||
if (overwrite) { | ||
obj[key] = value | ||
} else { | ||
// do nothing and preserve the origin value | ||
} | ||
} else { | ||
// Create the parent property | ||
obj[key] = value | ||
} | ||
} | ||
} | ||
} | ||
|
||
export default function remarkToc({ | ||
depth_min = 1, | ||
depth_max = 3, | ||
property = ['toc'], | ||
} = {}) { | ||
if (typeof property === 'string'){ | ||
if (typeof property === 'string') { | ||
property = [property] | ||
} | ||
return function (tree, vfile) { | ||
// if(get(vfile.data, property) === false) { | ||
// return | ||
// } | ||
const toc = [] | ||
visit(tree, 'heading', function (node) { | ||
if (node.depth < depth_min || node.depth > depth_max) return | ||
const title = node.children | ||
.filter((child) => child.type === 'text' || child.type === 'strong' || child.type === 'emphasis' || child.type === 'inlineCode') | ||
.map((child) => { | ||
.filter( | ||
(child) => | ||
child.type === 'text' || | ||
child.type === 'strong' || | ||
child.type === 'emphasis' || | ||
child.type === 'inlineCode' | ||
) | ||
.map((child) => { | ||
// when the text is bold or italic, the node.children[0] is not a text node but a strong or emphasis node | ||
// so we need to check the type of the node.children[0] to get the text value | ||
if (child.type === 'strong' || child.type === 'emphasis') { | ||
// case strong AND emphasis (***italic bold***) : the node.children[0] as an embedded node with the type strong or emphasis | ||
if (child.children[0].type === 'strong' || child.children[0].type === 'emphasis') { | ||
return child.children[0].children[0].value; | ||
if ( | ||
child.children[0].type === 'strong' || | ||
child.children[0].type === 'emphasis' | ||
) { | ||
return child.children[0].children[0].value | ||
} | ||
return child.children[0].value; | ||
} | ||
return child.children[0].value | ||
} | ||
// but for inlineCode and text node, the value is directly in the child.value | ||
return child.value; | ||
return child.value | ||
}) | ||
.join(''); | ||
if (!title) return; | ||
.join('') | ||
if (!title) return | ||
toc.push({ | ||
title: title, | ||
depth: node.depth, | ||
anchor: slugify(title), | ||
}); | ||
}) | ||
}) | ||
let mount = vfile | ||
for(let i = 0; i < property.length - 1; i++){ | ||
const prop = property[i]; | ||
if(!mount[prop]){ | ||
mount[prop] = {} | ||
} | ||
mount = mount[prop] | ||
} | ||
mount[property[property.length - 1]] = toc | ||
set(vfile, property, toc, false) | ||
} | ||
} | ||
|
||
export { get, set } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { get } from '../lib/index.js' | ||
|
||
describe('Utils `get`', () => { | ||
// Note, get is not yet used by the library | ||
|
||
it('valid path - level 1', async () => { | ||
get({ a: 'value' }, ['a']).should.eql('value') | ||
}) | ||
|
||
it('valid path - level 3 - value string', async () => { | ||
get({ a: { b: { c: 'value' } } }, ['a', 'b', 'c']).should.eql('value') | ||
}) | ||
|
||
it('valid path - level 3 - value `false`', async () => { | ||
get({ a: { b: { c: false } } }, ['a', 'b', 'c']).should.eql(false) | ||
}) | ||
|
||
it('get missing path - level 3 - relax', async () => { | ||
should(get({}, ['a', 'b', 'c'])).eql(undefined) | ||
}) | ||
|
||
it('get missing path - level 3 - strict', async () => { | ||
;(() => get({}, ['a', 'b', 'c'], true)).should.throw( | ||
'REMARK_TABLE_OF_CONTENT: property does not exists in strict mode.' | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import each from 'each' | ||
import fs from 'fs' | ||
import path from 'path' | ||
import { exec } from 'child_process' | ||
|
||
const __dirname = new URL('.', import.meta.url).pathname | ||
const dir = path.resolve(__dirname, '../samples') | ||
const samples = fs.readdirSync(dir) | ||
|
||
describe('Samples', () => { | ||
|
||
each(samples, true, (sample) => { | ||
if (!/\.js$/.test(sample)) return | ||
it(`Sample ${sample}`, (callback) => { | ||
exec(`node ${path.resolve(dir, sample)}`, (err) => callback(err)) | ||
}) | ||
}) | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { set } from '../lib/index.js' | ||
|
||
describe('Utils `set`', () => { | ||
it('valid path - property not yet defined - level 1', async () => { | ||
const input = {} | ||
set(input, ['a'], 'value') | ||
input.should.eql({ a: 'value' }) | ||
}) | ||
|
||
it('valid path - property not yet defined - level 3', async () => { | ||
const input = {} | ||
set(input, ['a', 'b', 'c'], 'value') | ||
input.should.eql({ a: { b: { c: 'value' } } }) | ||
}) | ||
|
||
it('fail to overwrite parent path', async () => { | ||
const input = {'a': { 'b': true }} | ||
;( () => | ||
set(input, ['a', 'b', 'c'], 'value') | ||
).should.throw('REMARK_TABLE_OF_CONTENT: cannot overwrite parent property.') | ||
}) | ||
|
||
it('value exists - level 3 - overwrite false', async () => { | ||
const input = {'a': { 'b': { 'c': true} }} | ||
set(input, ['a', 'b', 'c'], 'value') | ||
input.should.eql({ a: { b: { c: true } } }) | ||
}) | ||
|
||
it('value exists - level 3 - overwrite false', async () => { | ||
const input = {'a': { 'b': { 'c': true} }} | ||
set(input, ['a', 'b', 'c'], 'value', { overwrite: true }) | ||
input.should.eql({ a: { b: { c: 'value' } } }) | ||
}) | ||
|
||
}) |