diff --git a/docs/api.md b/docs/api.md index d43805f..5bc3bfa 100644 --- a/docs/api.md +++ b/docs/api.md @@ -197,6 +197,15 @@ const result = await $.markers.apply(); ``` ```` +## Markdown Helper (`$.md`) + +- `$.md.table( headers:[], rows:[[]])` + - Build a markdown table + - e.g. `$.md.table( ['header 1', 'header2'], rows:[ ['row 1 col 1', 'row 1 col 2'], ['row 2 col 1', 'row 2 col 2'], ])` +- `$.md.table( data )` + - Build a markdown table from imported data + - e.g. `$.md.table( $.data.customers )` + ## Assert (`$.assert`) You can use `$.assert` to stop the run and show a message if a test fails. diff --git a/src/compiler.ts b/src/compiler.ts index 386ce95..ebcfd40 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6,10 +6,11 @@ import { RunLogger } from 'src/run-logger'; import { TRunContext } from "src/run-context"; import { NamedCodeBlock } from "./named-code-block"; import { CodeBlockInfoHelper } from "./code-block-info"; -import { AssertRunContextImplemention } from "./rci-assert"; -import { UiRunContextImplemention } from "./rci-ui"; -import { IoRunContextImplemention } from "./rci-io"; -import { MarkerRunContextImplemention } from "./rci-markers"; +import { AssertRunContextImplementation } from "./rci-assert"; +import { UiRunContextImplementation } from "./rci-ui"; +import { IoRunContextImplementation } from "./rci-io"; +import { MarkerRunContextImplementation } from "./rci-markers"; +import { MarkdownHelperRunContextImplementation } from "./rci-markdown"; export class Compiler{ @@ -142,11 +143,11 @@ export class Compiler{ return ''; }, - assert: new AssertRunContextImplemention(), + assert: new AssertRunContextImplementation(), - ui: new UiRunContextImplemention(), + ui: new UiRunContextImplementation(), - io: new IoRunContextImplemention( + io: new IoRunContextImplementation( view.app.vault, view.app.workspace, log, @@ -154,13 +155,15 @@ export class Compiler{ consumableBlocks ), - markers: new MarkerRunContextImplemention( + markers: new MarkerRunContextImplementation( view.app.vault, log, view.file.path ), dv: dvGetAPI(), + + md: new MarkdownHelperRunContextImplementation( log ) } } diff --git a/src/data-set.ts b/src/data-set.ts index 4938c07..4dabd1e 100644 --- a/src/data-set.ts +++ b/src/data-set.ts @@ -1,5 +1,23 @@ export class DataSet extends Array { + public columns:string[] = []; + + constructor( columnNames:string[] ){ + + super(); + + this.columns = columnNames.map( cn => cn.trim() ); + + } + + private extractAsColumnName( str:string ) : string { + return str.trim().replaceAll(/\W/ig, '_').toLowerCase().trim(); + } + + public getPropertyName ( colName:string ) : string | undefined { + return this.extractAsColumnName( colName ); + } + } export interface IDataSetCollection { @@ -14,12 +32,13 @@ export class DataSetRow implements IRowDataValueCollection{ [column: string]: unknown; - constructor(columnNames:string[], data: unknown[]){ - for (let colIdx = 0; colIdx < columnNames.length; colIdx++) { - const value = data.at(colIdx); - const colName = columnNames[colIdx].trim(); - if ( colName != '' ){ - this[colName] = value; + constructor( dataSet:DataSet, values: unknown[] ){ + const columns = dataSet.columns; + for ( let colIdx = 0; colIdx < columns.length; colIdx++ ) { + const rawColName = columns[colIdx]; + const propColName = dataSet.getPropertyName(rawColName); + if ( propColName !== undefined ){ + this[propColName] = values.at(colIdx); } } } diff --git a/src/parser.ts b/src/parser.ts index 65f5e94..7a0983f 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -104,20 +104,20 @@ export class Parser { public loadCsv( csvContent:string ) : DataSet { const lines = csvContent.split('\n').map( e=>e.trim() ); if (lines.length == 0){ - return new DataSet(); + return new DataSet( [] ); } - const columns = lines.first()?.split(',').map( e => this.extractAsColumnName(e) ) ?? []; + const columns = lines.first()?.split(',') ?? []; - const rows = new Array(); + const ds = new DataSet( columns ); + + //const rows = new Array(); for (let i = 1; i < lines.length; i++) { const rowData = lines[i].split(',').map( d => this.extractValueFromString(d)); - rows.push( new DataSetRow(columns, rowData)); + ds.push( new DataSetRow(ds, rowData)); } - const ds = new DataSet( ...rows ); - return ds; } @@ -181,19 +181,15 @@ export class Parser { } private parseMdTable( tableLines:string[] ): DataSet { - const data: DataSet = new DataSet(); - const tlines = tableLines - .map( e=>e.trim().slice(1,-1).trim()); + const tlines = tableLines.map( e=>e.trim().slice(1,-1).trim()); + + const columns = tlines.first()?.split('|') ?? []; + + const data: DataSet = new DataSet( columns ); - const columns = tlines - .first()?.split('|') - .map( e=> this.extractAsColumnName(e) ) - .filter( e=>e.length > 0) - ?? [] - ; - for (let i = 1; i < tlines.length; i++) { + for (let i = 2; i < tlines.length; i++) { const rowLine = tlines[i]; if (rowLine.startsWith('---') ){ continue; @@ -202,7 +198,7 @@ export class Parser { continue; } const rowValues : unknown[] = rowLine.split('|').map( e => this.extractValueFromString(e) ); - data.push( new DataSetRow( columns, rowValues ) ); + data.push( new DataSetRow( data, rowValues ) ); } return data; @@ -287,10 +283,6 @@ export class Parser { return str.trim().replaceAll(/\W/ig, '_').toLowerCase(); } - private extractAsColumnName( str:string ) : string { - return str.trim().replaceAll(/\W/ig, '_').toLowerCase(); - } - private extractValueFromString( str:string ) : string | number | Date { const trimmed = str.trim(); diff --git a/src/rci-assert.ts b/src/rci-assert.ts index 212debd..3eda7a8 100644 --- a/src/rci-assert.ts +++ b/src/rci-assert.ts @@ -1,7 +1,7 @@ import { MessageModal } from "./modal-message"; import { TAssertRunContext } from "./run-context"; -export class AssertRunContextImplemention implements TAssertRunContext { +export class AssertRunContextImplementation implements TAssertRunContext { private async showFailMessage( label:string, msg:string ) : Promise{ const title = '❗ Assert Failed' + ( label.length > 0 ? ` - ${label}` : '' ); diff --git a/src/rci-io.ts b/src/rci-io.ts index 4582281..7904c60 100644 --- a/src/rci-io.ts +++ b/src/rci-io.ts @@ -7,7 +7,7 @@ import { TIoRunContext } from "./run-context"; import { RunLogger } from "./run-logger"; import { Utils } from "./utils"; -export class IoRunContextImplemention implements TIoRunContext { +export class IoRunContextImplementation implements TIoRunContext { private vault: Vault; private workspace: Workspace; @@ -32,25 +32,27 @@ export class IoRunContextImplemention implements TIoRunContext { } private async data_loader(filepath:string, name?:string) : Promise{ - let resultDataSet = new DataSet(); - + const absFilepath = this.getAbsoluteFilepathFromActiveFile(filepath); if (!absFilepath){ - return resultDataSet; + return new DataSet([]); } - + const file = this.vault.getAbstractFileByPath(absFilepath); - + const pzr = new Parser(); + if (file instanceof TFile){ - if (file.extension == 'csv'){ + + if ( file.extension == 'csv'){ const csvdata = await this.vault.read( file ); - resultDataSet = pzr.loadCsv(csvdata); - this.data[name??file.basename] = resultDataSet; + const dataSet = pzr.loadCsv(csvdata); + this.data[name??file.basename] = dataSet; + return dataSet; } } - return resultDataSet; + return new DataSet([]); } async import(path: string): Promise { diff --git a/src/rci-markdown.ts b/src/rci-markdown.ts new file mode 100644 index 0000000..74d633c --- /dev/null +++ b/src/rci-markdown.ts @@ -0,0 +1,114 @@ +import { DataSet } from "./data-set"; +import { TMarkdownHelperRunContext } from "./run-context"; +import { RunLogger } from "./run-logger"; + +export class MarkdownHelperRunContextImplementation implements TMarkdownHelperRunContext{ + + private log: RunLogger; + + constructor( log: RunLogger ){ + this.log = log; + } + + private buildTable(headings: string[], rows: any[][]): string{ + return MarkdownTableHelper.buildTable( headings, rows ); + } + + buildTableFromDataSet(data: DataSet): string { + const headings = data.columns; + const rowValues : any[][] = []; + + for (const row of data) { + const values : any[] = []; + + for (const heading of headings) { + const prop = data.getPropertyName(heading); + const value = prop == undefined ? undefined : row[prop]; + values.push(value); + } + + rowValues.push( values ); + } + + return MarkdownTableHelper.buildTable( headings, rowValues ); + } + + public table(arg1: unknown, arg2?: unknown): string { + if (arg1 instanceof DataSet){ + return this.buildTableFromDataSet(arg1); + } + if ( Array.isArray(arg1) && Array.isArray(arg2) ){ + return this.buildTable(arg1, arg2); + } + throw new Error('Invalid args for markdown table'); + } + +} + +class MarkdownTableHelper { + + + public static buildTable(headings: string[], rowValues: any[][]): string{ + + const headers = MarkdownTableHelper.buildHeaderRows( headings ); + const rows = MarkdownTableHelper.buildValueRows( rowValues ); + + const lines = [ + '', + ...headers, + ...rows, + '', + ] + + console.debug({lines}); + + return lines.join('\n'); + + } + + + + private static removeHeaderFormat( header: string ) : string { + if ( header.startsWith('<') || header.startsWith('>') ){ + return header.slice(1); + } + return header; + } + + private static buildFormatRowCell( header: string ): string { + let leftMarker = ' '; + let rightMarker = ' '; + + if ( header.startsWith('<') ){ + leftMarker = ':'; + header = header.slice(1); + } else if ( header.startsWith('>') ){ + rightMarker = ':'; + header = header.slice(1); + }else{ + leftMarker = ':'; + rightMarker = ':'; + header = header; + } + + return leftMarker + '-'.repeat( header.length ) + rightMarker; + } + + private static buildHeaderRows( formattedHeadings: string[] ) : string[] { + var result : string[] = []; + result.push( '|' + formattedHeadings.map( header => ` ${MarkdownTableHelper.removeHeaderFormat(header)} ` ).join('|') + '|' ); + result.push( '|' + formattedHeadings.map( header => MarkdownTableHelper.buildFormatRowCell(header) ).join('|') + '|' ); + return result; + } + + private static buildValueRow( rowValues: any[] ) : string { + return '|' + rowValues.map( c => ` ${c} ` ).join('|') + '|'; + } + + + private static buildValueRows( valueRows: any[][] ) : string[] { + return valueRows.map( rowValues => MarkdownTableHelper.buildValueRow(rowValues) ); + } + + +} \ No newline at end of file diff --git a/src/rci-markers.ts b/src/rci-markers.ts index f47b64c..a2d0452 100644 --- a/src/rci-markers.ts +++ b/src/rci-markers.ts @@ -2,7 +2,7 @@ import { TFile, Vault } from "obsidian"; import { MarkerChange, MarkerValue, TMarkerRunContext } from "./run-context"; import { RunLogger } from "./run-logger"; -export class MarkerRunContextImplemention implements TMarkerRunContext { +export class MarkerRunContextImplementation implements TMarkerRunContext { private markerStartPrefix = '%%'; private markerStartSuffix = '=%%'; diff --git a/src/rci-ui.ts b/src/rci-ui.ts index b434fae..5d573f7 100644 --- a/src/rci-ui.ts +++ b/src/rci-ui.ts @@ -3,7 +3,7 @@ import { AskModal } from "./modal-ask"; import { MessageModal } from "./modal-message"; import { TUiRunContext } from "./run-context"; -export class UiRunContextImplemention implements TUiRunContext { +export class UiRunContextImplementation implements TUiRunContext { notice( message: string | DocumentFragment, diff --git a/src/run-context.ts b/src/run-context.ts index 2ab01fc..94dfaca 100644 --- a/src/run-context.ts +++ b/src/run-context.ts @@ -24,6 +24,8 @@ export type TRunContext = { dv: DataviewApi | undefined; + md: TMarkdownHelperRunContext; + } export type TUiRunContext = { @@ -116,3 +118,9 @@ export class MarkerChange{ } } + + +export type TMarkdownHelperRunContext = { + table( headings:Array, rows:Array> ) : string; + table( data:DataSet ) : string; +} \ No newline at end of file