Implement group command + fallback error handling for invalid tables in output
This commit is contained in:
13
HELP.md
13
HELP.md
@@ -272,16 +272,21 @@ Destructure the current subject based on the given delimiter.
|
||||
If a `limit` is provided, split the subject no more than the given number of times.
|
||||
Example: `foo::bar::baz::ban` -> `split :: 2` -> `[foo, bar, baz::ban]`
|
||||
|
||||
#### `chunk <every> <line|word|char>`
|
||||
Destructure the current subject into chunks based on `every` Nth line/word/character.
|
||||
Example: `a b c d e f` -> `chunk 2 word` -> `[a b, c d, e f]`
|
||||
|
||||
#### `join [<on>]`
|
||||
Join the current destructured subject back together using the given delimiter.
|
||||
If no delimiter is provided, it will preserve the existing delimiters between substrings.
|
||||
If string is not destructured, joins the lines in the string.
|
||||
Example: `[foo, bar]` -> `join ::` -> `foo::bar`
|
||||
|
||||
#### `chunk <every> <line|word|char>`
|
||||
Destructure the current subject into chunks based on `every` Nth line/word/character.
|
||||
Example: `a b c d e f` -> `chunk 2 word` -> `[a b, c d, e f]`
|
||||
|
||||
#### `group <callable>`
|
||||
Group the current destructured subject based on some mapped value.
|
||||
The `callable` is called for each destructured part and parts are bucketed together by the output value.
|
||||
Example: `[aa, ab, ba, bb]` -> `group (rsub 1)` => `[[aa, ab], [ba, bb]]`
|
||||
|
||||
#### `sort [<asc|desc>]`
|
||||
Sort the items in the destructured subject alphabetically either `asc`ending (default) or `desc`ending.
|
||||
If string is not destructured, sorts the lines in the string.
|
||||
|
||||
56
src/vm/commands/group.ts
Normal file
56
src/vm/commands/group.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {Command, hashStrRVal, ParseContext, StrRVal, StrTerm, wrapDestructured} from "./command.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Call} from "./call.js";
|
||||
|
||||
type GroupData = {
|
||||
callable: StrTerm,
|
||||
}
|
||||
|
||||
export class Group extends Command<GroupData> {
|
||||
async attemptParse(context: ParseContext): Promise<GroupData> {
|
||||
return {
|
||||
callable: await context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'group')
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'group'
|
||||
}
|
||||
|
||||
async execute(vm: StrVM, data: GroupData): Promise<StrVM> {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
destructured: async sub => {
|
||||
const callable = ctx.resolveLambda(data.callable)
|
||||
const groups: Record<string, StrRVal[]> = {}
|
||||
|
||||
for ( const part of sub ) {
|
||||
// Run the exec in a child VM and group on the hashed result:
|
||||
await vm.runInChild(async (childVM, childCtx) => {
|
||||
await childCtx.replaceSubjectMatchingTerm({ override: part.value })
|
||||
|
||||
await (new Call).execute(childVM, {
|
||||
callable,
|
||||
params: [],
|
||||
})
|
||||
|
||||
const groupVal = hashStrRVal(childCtx.getSubject())
|
||||
groups[groupVal] ??= []
|
||||
groups[groupVal].push(part.value)
|
||||
})
|
||||
}
|
||||
|
||||
const groupValues = Object.values(groups)
|
||||
.map(group =>
|
||||
wrapDestructured(
|
||||
group.map(value => ({ value }))))
|
||||
|
||||
return groupValues.map(value => ({ value }))
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import {Call} from "./call.js";
|
||||
import {Chunk} from "./chunk.js";
|
||||
import {Script} from "./script.js";
|
||||
import {Take} from "./take.js";
|
||||
import {Group} from "./group.js";
|
||||
|
||||
export type Commands = Command<CommandData>[]
|
||||
export const commands: Commands = [
|
||||
@@ -68,6 +69,7 @@ export const commands: Commands = [
|
||||
new Enclose,
|
||||
new Exit,
|
||||
new From,
|
||||
new Group,
|
||||
new Help,
|
||||
new History,
|
||||
new Indent,
|
||||
|
||||
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
||||
import {tempFile} from "../util/fs.js";
|
||||
import {table} from "table";
|
||||
import * as ansi from 'ansis';
|
||||
import {log} from "../log.js";
|
||||
|
||||
export const getSubjectDisplay = (sub: StrRVal, prefix: string = '', firstLinePrefix?: string): string => {
|
||||
if ( typeof firstLinePrefix === 'undefined' ) {
|
||||
@@ -48,10 +49,15 @@ export const getSubjectDisplay = (sub: StrRVal, prefix: string = '', firstLinePr
|
||||
...annotatedTable,
|
||||
]
|
||||
|
||||
return table(annotatedTable, config)
|
||||
+ `${prefix}├────────────────────────────────────────────────`
|
||||
+`\n${prefix}│ :: destructured (:: destructured (:: string))`
|
||||
+ `\n${prefix}└────────────────────────────────────────────────`
|
||||
try {
|
||||
return table(annotatedTable, config)
|
||||
+ `${prefix}├────────────────────────────────────────────────`
|
||||
+`\n${prefix}│ :: destructured (:: destructured (:: string))`
|
||||
+ `\n${prefix}└────────────────────────────────────────────────`
|
||||
} catch (e: unknown) {
|
||||
log.error('output', e)
|
||||
// Intentionally fall through to the default output
|
||||
}
|
||||
}
|
||||
|
||||
let annotated = firstLinePrefix + '┌───────────────'
|
||||
|
||||
Reference in New Issue
Block a user