Preparing core package for use by other packages.

This commit is contained in:
Joshua Bemenderfer
2022-11-12 14:31:11 -05:00
parent 0196cd5c87
commit a0791b0c69
16 changed files with 504 additions and 291 deletions

View File

@@ -1,13 +1,13 @@
import { LineData, parseLine } from './parser.js'
import { LineData, parseLine } from './parser'
export function document(nextFn, indent = ' ') {
let line = null
export function document (nextFn: () => Promise<string|null>, indent: string = ' ') {
let line: string|null = null
let lineData = LineData()
let ended = false
async function next() {
line = await nextFn()
if (line == null) ended = true
if (line === null) ended = true
else parseLine(line, lineData, indent)
return { line, lineData, ended, next, current }
}

View File

@@ -0,0 +1,2 @@
export * from './parser'
export * from './document'

View File

@@ -1,4 +1,4 @@
import { assert, describe, expect, it } from 'vitest'
import { describe, expect, it } from 'vitest'
import { LineData, parseLine } from './parser'
describe(`LineData`, () => {
@@ -7,9 +7,9 @@ describe(`LineData`, () => {
expect(lineData).toBeTypeOf(`object`)
})
it(`has three properties`, () => {
it(`has four properties`, () => {
const lineData = LineData()
expect(Object.keys(lineData).length).to.equal(3)
expect(Object.keys(lineData).length).to.equal(4)
})
it(`'type' is an integer initialized to zero`, () => {
@@ -22,9 +22,14 @@ describe(`LineData`, () => {
expect(lineData.type).to.equal(0)
})
it(`'offset' is an integer initialized to zero`, () => {
it(`'offsetHead' is an integer initialized to zero`, () => {
const lineData = LineData()
expect(lineData.offset).to.equal(0)
expect(lineData.offsetHead).to.equal(0)
})
it(`'offsetTail' is an integer initialized to zero`, () => {
const lineData = LineData()
expect(lineData.offsetTail).to.equal(0)
})
})
@@ -32,106 +37,109 @@ describe(`parseLine`, () => {
it(`Requres 'line' to be a string`, () => {
const lineData = LineData()
expect(() => parseLine(0, lineData)).toThrowError(`'line' must be a string`)
// @ts-ignore
expect(() => parseLine([], lineData)).toThrowError(`'line' must be a string`)
// @ts-ignore
expect(() => parseLine({}, lineData)).toThrowError(`'line' must be a string`)
// @ts-ignore
expect(() => parseLine(null, lineData)).toThrowError(`'line' must be a string`)
// @ts-ignore
expect(() => parseLine(true, lineData)).toThrowError(`'line' must be a string`)
// @ts-ignore
expect(() => parseLine(() => {}, lineData)).toThrowError(`'line' must be a string`)
})
it(`Requres 'lineData' to be an object with a numeric level and type property`, () => {
it(`Requres 'lineData' to be an object with numeric level and type properties`, () => {
const lineData = LineData()
// @ts-ignore
expect(() => parseLine(``, 0)).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, [])).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, {})).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, null)).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, true)).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, () => {})).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, { level: '', type: 0 })).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
// @ts-ignore
expect(() => parseLine(``, { level: 0, type: null })).toThrowError(`'lineData' must be an object with 'type' and 'level' integer properties`)
})
it(`Requres 'indent' to be a single-character string`, () => {
const lineData = LineData()
// @ts-ignore
expect(() => parseLine(``, lineData, 0)).toThrowError(`'indent' must be a single-character string`)
// @ts-ignore
expect(() => parseLine(``, lineData, [])).toThrowError(`'indent' must be a single-character string`)
// @ts-ignore
expect(() => parseLine(``, lineData, {})).toThrowError(`'indent' must be a single-character string`)
// @ts-ignore
expect(() => parseLine(``, lineData, null)).toThrowError(`'indent' must be a single-character string`)
// @ts-ignore
expect(() => parseLine(``, lineData, true)).toThrowError(`'indent' must be a single-character string`)
// @ts-ignore
expect(() => parseLine(``, lineData, () => {})).toThrowError(`'indent' must be a single-character string`)
expect(() => parseLine(``, lineData, ` `)).toThrowError(`'indent' must be a single-character string`)
})
it(`Outputs { type: 0, level: 0, offset: 0 } for a blank line at indent level 0`, () => {
it(`Handles a blank line at indent level 0`, () => {
const line = ``
const lineData = LineData()
parseLine(line, lineData)
expect(lineData.type).to.equal(0)
expect(lineData.level).to.equal(0)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 0, level: 0, offsetHead: 0, offsetTail: 0 })
})
it(`Outputs { type: 1, level: 1, offset: 0 } for line with a single space at indent level 1`, () => {
it(`Handles a line with a single space at indent level 1`, () => {
const line = ` `
const lineData = LineData()
parseLine(line, lineData)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(1)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 1, offsetHead: 1, offsetTail: 1 })
})
it(`Outputs { type: 1, level: 2, offset: 0 } for line with two spaces`, () => {
it(`Handles a line with two spaces`, () => {
const line = ` `
const lineData = LineData()
parseLine(line, lineData)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(2)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 2, offsetHead: 2, offsetTail: 2 })
})
it(`Outputs { type: 1, level: 0, offset: 0 } for a normal line at indent level 0`, () => {
it(`Handles a normal line at indent level 0`, () => {
const line = `line 1`
const lineData = LineData()
parseLine(line, lineData)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(0)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 0, offsetHead: 0, offsetTail: 4 })
})
it(`Outputs { type: 1, level: 1, offset: 0 } for a normal line at indent level 1`, () => {
it(`Handles a normal line at indent level 1`, () => {
const line = ` line 1`
const lineData = LineData()
parseLine(line, lineData)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(1)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 1, offsetHead: 1, offsetTail: 5 })
})
it(`Outputs { type: 1, level: 1, offset: 0 } for a normal line at indent level 1`, () => {
it(`Handles a normal line at indent level 2`, () => {
const line = ` line 1`
const lineData = LineData()
parseLine(line, lineData)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(2)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 2, offsetHead: 2, offsetTail: 6 })
})
it(`Outputs { type: 1, level: 1, offset: 0 } for a normal line at indent level 1 indented with tabs`, () => {
it(`Handles a normal line at indent level 1 indented with tabs`, () => {
const line = `\tline 1`
const lineData = LineData()
parseLine(line, lineData, `\t`)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(1)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 1, offsetHead: 1, offsetTail: 5 })
})
it(`Outputs { type: 1, level: 2, offset: 0 } for a normal line at indent level 1 indented with tabs`, () => {
it(`Handles a normal line at indent level 2 indented with tabs`, () => {
const line = `\t\tline 1`
const lineData = LineData()
parseLine(line, lineData, `\t`)
expect(lineData.type).to.equal(1)
expect(lineData.level).to.equal(2)
expect(lineData.offset).to.equal(0)
expect(lineData).to.deep.equal({ type: 1, level: 2, offsetHead: 2, offsetTail: 6})
})
it(`Nests a normal line under a preceding normal line`, () => {
@@ -147,8 +155,8 @@ describe(`parseLine`, () => {
})
expect(results).to.deep.equal([
{ type: 1, level: 0, offset: 0 },
{ type: 1, level: 1, offset: 0 }
{ type: 1, level: 0, offsetHead: 0, offsetTail: 4 },
{ type: 1, level: 1, offsetHead: 1, offsetTail: 5 }
])
})
@@ -167,10 +175,10 @@ describe(`parseLine`, () => {
})
expect(results).to.deep.equal([
{ type: 1, level: 0, offset: 0 },
{ type: 1, level: 1, offset: 0 },
{ type: 1, level: 1, offset: 0 },
{ type: 1, level: 1, offset: 0 }
{ type: 1, level: 0, offsetHead: 0, offsetTail: 4 },
{ type: 1, level: 1, offsetHead: 1, offsetTail: 5 },
{ type: 1, level: 1, offsetHead: 1, offsetTail: 5 },
{ type: 1, level: 1, offsetHead: 1, offsetTail: 5 }
])
})
@@ -187,8 +195,8 @@ describe(`parseLine`, () => {
})
expect(results).to.deep.equal([
{ type: 1, level: 0, offset: 0 },
{ type: 0, level: 1, offset: 0 }
{ type: 1, level: 0, offsetHead: 0, offsetTail: 4 },
{ type: 0, level: 1, offsetHead: 0, offsetTail: 0 }
])
})
@@ -207,10 +215,43 @@ describe(`parseLine`, () => {
})
expect(results).to.deep.equal([
{ type: 1, level: 0, offset: 0 },
{ type: 0, level: 1, offset: 0 },
{ type: 0, level: 1, offset: 0 },
{ type: 0, level: 1, offset: 0 }
{ type: 1, level: 0, offsetHead: 0, offsetTail: 4 },
{ type: 0, level: 1, offsetHead: 0, offsetTail: 0 },
{ type: 0, level: 1, offsetHead: 0, offsetTail: 0 },
{ type: 0, level: 1, offsetHead: 0, offsetTail: 0 }
])
})
it(`Handle head and tail matching for lines with head and tail`, () => {
const line = ` head tail1 tail2 tail3`
const lineData = LineData()
parseLine(line, lineData)
const head = line.slice(lineData.offsetHead, lineData.offsetTail)
const tail = line.slice(lineData.offsetTail + 1)
expect(head).to.equal(`head`)
expect(tail).to.equal(`tail1 tail2 tail3`)
})
it(`Handle head and tail matching for lines with head but no tail`, () => {
const line = ` head`
const lineData = LineData()
parseLine(line, lineData)
const head = line.slice(lineData.offsetHead, lineData.offsetTail)
const tail = line.slice(lineData.offsetTail + 1)
expect(head).to.equal(`head`)
expect(tail).to.equal(``)
})
it(`Handle head and tail matching for lines with head and trailing space`, () => {
const line = ` head `
const lineData = LineData()
parseLine(line, lineData)
const head = line.slice(lineData.offsetHead, lineData.offsetTail)
const tail = line.slice(lineData.offsetTail + 1)
expect(head).to.equal(`head`)
expect(tail).to.equal(``)
})
})

View File

@@ -1,8 +1,15 @@
export function LineData() {
return { type: 0, level: 0, offset: 0 }
type LineData = {
type: number;
level: number;
offsetHead: number;
offsetTail: number;
}
export function parseLine(line, lineData, indent = ' ') {
export function LineData(): LineData {
return { type: 0, level: 0, offsetHead: 0, offsetTail: 0 }
}
export function parseLine(line: string, lineData: LineData, indent: string = ' '): LineData {
if (typeof line !== 'string') throw new Error(`'line' must be a string`)
if ((typeof lineData !== 'object' || !lineData) || typeof lineData.type !== 'number' || typeof lineData.level !== 'number') throw new Error(`'lineData' must be an object with 'type' and 'level' integer properties`)
if (typeof indent !== 'string' || indent.length === 0 || indent.length > 1) throw new Error(`'indent' must be a single-character string`)
@@ -13,12 +20,22 @@ export function parseLine(line, lineData, indent = ' ') {
if (!line.length) {
if (lineData.type === 1) level += 1
if (lineData.type === 0) level = lineData.level
lineData.type = type
lineData.level = level
lineData.offsetHead = 0
lineData.offsetTail = 0
} else {
type = 1
while (line[level] === indent && level <= lineData.level + 1) ++level
lineData.type = type
lineData.level = level
lineData.offsetHead = level
lineData.offsetTail = level
while (line[lineData.offsetTail] && line[lineData.offsetTail] !== ' ') ++lineData.offsetTail
}
lineData.type = type
lineData.level = level
return lineData
}