From e72ff2eccff97df3b6b851ad371a8e82fc700fd6 Mon Sep 17 00:00:00 2001 From: Joshua Bemenderfer Date: Tue, 7 Feb 2023 16:38:23 -0500 Subject: [PATCH] First attempt at Python port, expand tests. --- package.json | 2 +- packages/js/test/index.js | 35 +++++-- packages/python/.gitignore | 1 + packages/python/package.json | 8 ++ packages/python/parser.py | 35 +++++++ packages/python/test/index.py | 58 ++++++++++++ pnpm-lock.yaml | 102 +++++++++++++++++++-- repo/build.js | 6 +- repo/package.tce | 2 +- test/test-runner.test.js | 32 ++++--- test/tests.tce | 166 +++++++++++++++++++++++++--------- 11 files changed, 365 insertions(+), 82 deletions(-) create mode 100644 packages/python/.gitignore create mode 100644 packages/python/package.json create mode 100644 packages/python/parser.py create mode 100644 packages/python/test/index.py diff --git a/package.json b/package.json index 60ef45a..fac652c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build:repo": "node ./repo/build.js", "build": "turbo run build --cache-dir=.turbo", "dev": "turbo run dev --no-cache --force", - "test": "turbo run test --no-cache --force" + "test": "turbo run test --filter=@terrace/test --no-cache --force" }, "devDependencies": { "@terrace/core": "workspace:*", diff --git a/packages/js/test/index.js b/packages/js/test/index.js index 5459686..6436843 100644 --- a/packages/js/test/index.js +++ b/packages/js/test/index.js @@ -6,18 +6,39 @@ const testName = process.argv[2] const tests = { 'linedata:basic': async () => { - const { level, line, head, tail, next } = useDocument(createStdinReader()) - while(await next()) { - console.log(`level: ${level()} | head: ${head()} | tail: ${tail()} | line: ${line()}`) + const lineData = createLineData('') + const next = createStdinReader() + + while ((lineData.line = await next()) != null) { + parseLine(lineData) + const { level, indent, offsetHead, offsetTail, line } = lineData + console.log(`| level ${level} | indent ${indent} | offsetHead ${offsetHead} | offsetTail ${offsetTail} | line ${line} |`) } }, 'linedata:tabs': async () => { - const { level, line, head, tail, next } = useDocument(createStdinReader(), '\t') - while(await next()) { - console.log(`level: ${level()} | head: ${head()} | tail: ${tail()} | line: ${line()}`) + const lineData = createLineData('', '\t') + const next = createStdinReader() + + while ((lineData.line = await next()) != null) { + parseLine(lineData) + const { level, indent, offsetHead, offsetTail, line } = lineData + console.log(`| level ${level} | indent ${indent} | offsetHead ${offsetHead} | offsetTail ${offsetTail} | line ${line} |`) + } + }, + 'linedata:head-tail': async () => { + const lineData = createLineData('') + const next = createStdinReader() + + while ((lineData.line = await next()) != null) { + parseLine(lineData) + const { level, indent, offsetHead, offsetTail, line } = lineData + const head = line.slice(offsetHead, offsetTail) + const tail = line.slice(offsetTail + 1) + + console.log(`| head ${head} | tail ${tail} |`) } } } const test = tests[testName] -await test() \ No newline at end of file +await test() diff --git a/packages/python/.gitignore b/packages/python/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/packages/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/packages/python/package.json b/packages/python/package.json new file mode 100644 index 0000000..6aa0267 --- /dev/null +++ b/packages/python/package.json @@ -0,0 +1,8 @@ +{ + "name": "@terrace/python", + "version": "0.0.1", + "license": "MIT", + "scripts": { + "test": "python3 ./test/index.py" + } +} diff --git a/packages/python/parser.py b/packages/python/parser.py new file mode 100644 index 0000000..ebddc1d --- /dev/null +++ b/packages/python/parser.py @@ -0,0 +1,35 @@ +from typing import TypedDict + +class LineData(TypedDict): + line: str + indent: str + level: int + offsetHead: int + offsetTail: int + +def createLineData(line: str = '', indent: str = ' ') -> LineData: + return { "line": line, "indent": indent, "level": 0, "offsetHead": 0, "offsetTail": 0 } + +def parseLine(lineData: LineData) -> LineData: + # if ((typeof lineData !== 'object' || !lineData) || typeof lineData.level !== 'number') throw new Error(`'lineData' must be an object with string line and numeric level properties`) + # if (typeof lineData.indent !== 'string' || lineData.indent.length === 0 || lineData.indent.length > 1) throw new Error(`'lineData.indent' must be a single-character string`) + # if (typeof lineData.line !== 'string') throw new Error(`'lineData.line' must be a string`) + + level = 0 + + # Repeat previous level for blank lines. + if len(lineData['line']) == 0: + lineData['level'] = lineData['level'] + lineData['offsetHead'] = 0 + lineData['offsetTail'] = 0 + else: + while level < len(lineData['line']) and lineData['line'][level] == lineData['indent'] and level <= lineData['level'] + 1: + level += 1 + lineData['level'] = level + lineData['offsetHead'] = level + lineData['offsetTail'] = level + + while lineData['offsetTail'] < len(lineData['line']) and lineData['line'][lineData['offsetTail']] != ' ': + lineData['offsetTail'] += 1 + + return lineData diff --git a/packages/python/test/index.py b/packages/python/test/index.py new file mode 100644 index 0000000..4e49e18 --- /dev/null +++ b/packages/python/test/index.py @@ -0,0 +1,58 @@ +import sys +import os + +sys.path.insert(1, os.path.join(sys.path[0], '..')) + +from parser import createLineData, parseLine + +def next(): + return sys.stdin.readline().rstrip('\n') + +def linedata_basic (): + lineData = createLineData('') + + while l := next(): + lineData['line'] = l + parseLine(lineData) + print("| level {level} | indent {indent} | offsetHead {offsetHead} | offsetTail {offsetTail} | line {line} |".format( + level = lineData['level'], indent = lineData['indent'], offsetHead = lineData['offsetHead'], offsetTail = lineData['offsetTail'], line = lineData['line'] + )) + +def linedata_tabs (): + lineData = createLineData('', '\t') + + while l := next(): + lineData['line'] = l + parseLine(lineData) + print("| level {level} | indent {indent} | offsetHead {offsetHead} | offsetTail {offsetTail} | line {line} |".format( + level = lineData['level'], indent = lineData['indent'], offsetHead = lineData['offsetHead'], offsetTail = lineData['offsetTail'], line = lineData['line'] + )) + +def linedata_head_tail (): + lineData = createLineData('') + + while l := next(): + lineData['line'] = l + parseLine(lineData) + head = lineData['line'][lineData['offsetHead']:lineData['offsetTail']] if len(lineData['line']) > lineData['offsetTail'] else '' + tail = lineData['line'][lineData['offsetTail'] + 1:] if len(lineData['line']) > lineData['offsetTail'] + 1 else '' + + print("| head {head} | tail {tail} |".format( + head = head, tail = tail + )) + + +tests = { + 'linedata:basic': linedata_basic, + 'linedata:tabs': linedata_tabs, + 'linedata:head-tail': linedata_head_tail +} + + +def main(): + testName = sys.argv[1] + test = tests[testName] + test() + +if __name__ == "__main__": + main() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 539d574..1e2c0c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,20 @@ importers: jest: 29.4.1 turbo: 1.7.3 + experiments/lesson-plans: + specifiers: + '@terrace/core': workspace:* + ejs: ^3.1.8 + humanize-duration: ^3.27.3 + nunjucks: ^3.2.3 + parse-duration: ^1.0.2 + dependencies: + '@terrace/core': link:../../packages/js + ejs: 3.1.8 + humanize-duration: 3.28.0 + nunjucks: 3.2.3 + parse-duration: 1.0.2 + packages/js: specifiers: vite: ^3.2.3 @@ -752,6 +766,10 @@ packages: '@types/yargs-parser': 21.0.0 dev: true + /a-sync-waterfall/1.0.1: + resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} + dev: false + /acorn/8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} @@ -782,7 +800,6 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles/5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} @@ -803,10 +820,18 @@ packages: sprintf-js: 1.0.3 dev: true + /asap/2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: false + /assertion-error/1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: false + /babel-jest/29.4.1_@babel+core@7.20.12: resolution: {integrity: sha512-xBZa/pLSsF/1sNpkgsiT3CmY7zV1kAsZ9OxxtrFqYucnOuRftXAfcJqcDVyOPeN4lttWTwhLdu0T9f8uvoPEUg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -881,14 +906,18 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -965,7 +994,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /char-regex/1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} @@ -1014,7 +1042,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name/1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -1022,11 +1049,14 @@ packages: /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true + + /commander/5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + dev: false /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true /convert-source-map/1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -1083,6 +1113,14 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /ejs/3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.5 + dev: false + /electron-to-chromium/1.4.286: resolution: {integrity: sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==} dev: true @@ -1374,6 +1412,12 @@ packages: bser: 2.1.1 dev: true + /filelist/1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + dependencies: + minimatch: 5.1.6 + dev: false + /fill-range/7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -1457,7 +1501,6 @@ packages: /has-flag/4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -1475,6 +1518,10 @@ packages: engines: {node: '>=10.17.0'} dev: true + /humanize-duration/3.28.0: + resolution: {integrity: sha512-jMAxraOOmHuPbffLVDKkEKi/NeG8dMqP8lGRd6Tbf7JgAeG33jjgPWDbXXU7ypCI0o+oNKJFgbSB9FKVdWNI2A==} + dev: false + /import-local/3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} engines: {node: '>=8'} @@ -1580,6 +1627,17 @@ packages: istanbul-lib-report: 3.0.0 dev: true + /jake/10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + dev: false + /jest-changed-files/29.4.0: resolution: {integrity: sha512-rnI1oPxgFghoz32Y8eZsGJMjW54UlqT17ycQeCEktcxxwqqKdlj9afl8LNeO0Pbu+h2JQHThQP0BzS67eTRx4w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2135,7 +2193,13 @@ packages: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - dev: true + + /minimatch/5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -2171,6 +2235,21 @@ packages: path-key: 3.1.1 dev: true + /nunjucks/3.2.3: + resolution: {integrity: sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==} + engines: {node: '>= 6.9.0'} + hasBin: true + peerDependencies: + chokidar: ^3.3.0 + peerDependenciesMeta: + chokidar: + optional: true + dependencies: + a-sync-waterfall: 1.0.1 + asap: 2.0.6 + commander: 5.1.0 + dev: false + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -2210,6 +2289,10 @@ packages: engines: {node: '>=6'} dev: true + /parse-duration/1.0.2: + resolution: {integrity: sha512-Dg27N6mfok+ow1a2rj/nRjtCfaKrHUZV2SJpEn/s8GaVUSlf4GGRCRP1c13Hj+wfPKVMrFDqLMLITkYKgKxyyg==} + dev: false + /parse-json/5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -2456,7 +2539,6 @@ packages: engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - dev: true /supports-color/8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} diff --git a/repo/build.js b/repo/build.js index 81350d1..053bd7b 100644 --- a/repo/build.js +++ b/repo/build.js @@ -1,6 +1,6 @@ import fs from 'node:fs/promises' import { useDocument } from '@terrace/core' -import { createReadlineReader } from '@terrace/core/readers/node-readline' +import { createFileReader } from '@terrace/core/readers/node-readline' function useHelpers({ next, level, head, tail }) { @@ -18,7 +18,7 @@ function useHelpers({ next, level, head, tail }) { } async function buildPackage() { - const { next, level, head, tail, match } = useDocument(createReadlineReader('./repo/package.tce')) + const { next, level, head, tail, match } = useDocument(createFileReader('./repo/package.tce')) const { kvObject } = useHelpers({ next, level, head, tail }) const pkg = {} @@ -38,7 +38,7 @@ async function buildPackage() { } async function buildTurbo() { - const { next, level, head, tail, match } = useDocument(createReadlineReader('./repo/turbo.tce')) + const { next, level, head, tail, match } = useDocument(createFileReader('./repo/turbo.tce')) const { kvObject } = useHelpers({ next, level, head, tail }) const turbo = {} diff --git a/repo/package.tce b/repo/package.tce index fb54cd9..8e6609d 100644 --- a/repo/package.tce +++ b/repo/package.tce @@ -8,7 +8,7 @@ scripts build:repo node ./repo/build.js build turbo run build --cache-dir=.turbo dev turbo run dev --no-cache --force - test turbo run test --no-cache --force + test turbo run test --filter=@terrace/test --no-cache --force devDependencies @terrace/core workspace:* diff --git a/test/test-runner.test.js b/test/test-runner.test.js index f494a6f..9cea699 100644 --- a/test/test-runner.test.js +++ b/test/test-runner.test.js @@ -28,29 +28,30 @@ async function loadTests(path) { const propertyLevel = level() if (match('input')) { // TODO: Find a way to handle newlines better. - if (tail().startsWith('literal')) { + if (tail()) { test.input = tail() - .split('literal ').join('') - .replaceAll('\\n', '\n') - .replaceAll('\\t', '\t') continue } - while (await next(propertyLevel)) test.input.push(line(propertyLevel)) - test.input = test.input.join('\n').trim() + while (await next(propertyLevel)) test.input.push(line(propertyLevel + 1)) + test.input = test.input.join('\n').trimEnd() } if (match('output')) { - // TODO: Find a way to handle newlines better. - if (tail().startsWith('literal')) { - test.output = tail().split('literal ').join('').replace('\\n', '\n') - continue - } - - while (await next(propertyLevel)) test.output.push(line()) + while (await next(propertyLevel)) test.output.push(line(propertyLevel + 1)) } } - test.output = test.output.join('\n').trim() + + test.input = test.input + .replaceAll('\\n', '\n') + .replaceAll('\\t', '\t') + .replaceAll('\\s', ' ') + + test.output = test.output.join('\n').trimEnd() + .replaceAll('\\n', '\n') + .replaceAll('\\t', '\t') + .replaceAll('\\s', ' ') + } } @@ -64,6 +65,7 @@ function callTest(pkg, name, input) { input }) + // TODO: Find a way to clean all this trimming up. Most caused by VSCode happily trimming empty spaces off the ends of lines. resolve(stdout.toString().trim()) }) } @@ -80,4 +82,4 @@ for (const [name, tests] of Object.entries(descriptions)) { }) } }) -} \ No newline at end of file +} diff --git a/test/tests.tce b/test/tests.tce index 04f46a5..0b83a3e 100644 --- a/test/tests.tce +++ b/test/tests.tce @@ -1,54 +1,130 @@ #schema test describe LineData - it Handles a blank line at indent level 0 - key linedata:basic - packages js - input literal \n - output - level: 0 | head: | tail: | line: + it Handles a blank line at indent level 0 + key linedata:basic + packages js python + input \n + output + | level 0 | indent | offsetHead 0 | offsetTail 0 | line | - it Handles a blank line with a single space at indent level 1 - key linedata:basic - packages js - input literal \n - output - level: 1 | head: | tail: | line: + it Handles a blank line with a single space + key linedata:basic + packages js python + input \s + output + | level 1 | indent | offsetHead 1 | offsetTail 1 | line | - it Handles a blank line with two spaces - key linedata:basic - packages js - input literal \n - output - level: 2 | head: | tail: | line: + it Handles a blank line with two spaces + key linedata:basic + packages js python + input \s\s + output + | level 2 | indent | offsetHead 2 | offsetTail 2 | line | - it Handles a normal line at indent level 1 - key linedata:basic - packages js - input literal line 1 - output - level: 1 | head: line | tail: 1 | line: line 1 + it Handles a normal line at indent level 0 + key linedata:basic + packages js python + input + line 1 + output + | level 0 | indent | offsetHead 0 | offsetTail 4 | line line 1 | - it Handles a normal line at indent level 1 indented with tabs - key linedata:tabs - packages js - input literal \tline 1 - output - level: 1 | head: line | tail: 1 | line: line 1 + it Handles a normal line at indent level 1 + key linedata:basic + packages js python + input + line 1 + output + | level 1 | indent | offsetHead 1 | offsetTail 5 | line line 1 | - it Handles a normal line at indent level 2 indented with tabs - key linedata:tabs - packages js - input literal \t\tline 1 - output - level: 2 | head: line | tail: 1 | line: line 1 + it Handles a normal line at indent level 2 + key linedata:basic + packages js python + input + line 1 + output + | level 2 | indent | offsetHead 2 | offsetTail 6 | line line 1 | - it Nests a normal line under a preceding normal line - key linedata:basic - packages js - input - line 1 - line 2 - output - level: 0 | head: line | tail: 1 | line: line 1 - level: 1 | head: line | tail: 2 | line: line 2 + it Handles a normal line at indent level 1 indented with tabs + key linedata:tabs + packages js python + input + \tline 1 + output + | level 1 | indent \t | offsetHead 1 | offsetTail 5 | line \tline 1 | + + it Handles a normal line at indent level 2 indented with tabs + key linedata:tabs + packages js python + input + \t\tline 1 + output + | level 2 | indent \t | offsetHead 2 | offsetTail 6 | line \t\tline 1 | + + it Nests a normal line under a preceding normal line + key linedata:basic + packages js python + input + line 1 + line 2 + output + | level 0 | indent | offsetHead 0 | offsetTail 4 | line line 1 | + | level 1 | indent | offsetHead 1 | offsetTail 5 | line line 2 | + + it Nests multiple normal lines under a preceding normal line + key linedata:basic + packages js python + input + line 1 + line 2 + line 3 + line 4 + output + | level 0 | indent | offsetHead 0 | offsetTail 4 | line line 1 | + | level 1 | indent | offsetHead 1 | offsetTail 5 | line line 2 | + | level 1 | indent | offsetHead 1 | offsetTail 5 | line line 3 | + | level 1 | indent | offsetHead 1 | offsetTail 5 | line line 4 | + + it Does not nest an empty line under a preceding normal line + key linedata:basic + packages js python + comment Two newlines are needed here. A single newline will look to readline as if the input is finished. + input line 1\n\n + output + | level 0 | indent | offsetHead 0 | offsetTail 4 | line line 1 | + | level 0 | indent | offsetHead 0 | offsetTail 0 | line | + + it Does not nest multiple empty lines under a preceding normal line + key linedata:basic + packages js python + comment Four newlines are needed here. A single newline will look to readline as if the input is finished. + input line 1\n\n\n\n + output + | level 0 | indent | offsetHead 0 | offsetTail 4 | line line 1 | + | level 0 | indent | offsetHead 0 | offsetTail 0 | line | + | level 0 | indent | offsetHead 0 | offsetTail 0 | line | + | level 0 | indent | offsetHead 0 | offsetTail 0 | line | + + it Handles head and tail matching for lines with head and tail + key linedata:head-tail + packages js python + input + head1 tail1 tail2 tail3 + output + | head head1 | tail tail1 tail2 tail3 | + + it Handles head and tail matching for lines with head but no tail + key linedata:head-tail + packages js python + input + head1 + output + | head head1 | tail | + + it Handles head and tail matching for lines with head and trailing space + key linedata:head-tail + packages js python + input head1 \n + output + | head head1 | tail |