mirror of
https://github.com/meld-cp/obsidian-build.git
synced 2026-07-22 07:30:25 +00:00
wip
This commit is contained in:
parent
00db6be084
commit
7f1575a849
10 changed files with 248 additions and 102 deletions
|
|
@ -16,6 +16,7 @@ Write and execute JavaScript in code blocks to render templates, query DataView
|
|||
- [ ] better docs and examples
|
||||
- [ ] alpha release
|
||||
- [ ] test on mobile
|
||||
- [ ] better file logging
|
||||
|
||||
## Manually installing the plugin
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ esbuild.build({
|
|||
...builtins],
|
||||
format: 'cjs',
|
||||
watch: !prod,
|
||||
target: 'es2018',
|
||||
target: 'es2021',
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : 'inline',
|
||||
treeShaking: true,
|
||||
|
|
|
|||
53
main.ts
53
main.ts
|
|
@ -11,12 +11,19 @@ const DEFAULT_SETTINGS: MeldBuildPluginSettings = {
|
|||
mySetting: 'default'
|
||||
}
|
||||
|
||||
const CODE_BLOCK_LANG_TOOLBAR = 'meld-build-toolbar';
|
||||
|
||||
export default class MeldBuildPlugin extends Plugin {
|
||||
settings: MeldBuildPluginSettings;
|
||||
|
||||
|
||||
async onload() {
|
||||
|
||||
//console.debug((app as any).commands.commands);
|
||||
|
||||
await this.loadSettings();
|
||||
|
||||
await this.reloadActiveViewsWithToolbars();
|
||||
|
||||
this.registerHandlebarHelpers();
|
||||
|
||||
|
|
@ -27,7 +34,7 @@ export default class MeldBuildPlugin extends Plugin {
|
|||
// });
|
||||
// });
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor('meld-build-toolbar', (source, el, ctx) => {
|
||||
this.registerMarkdownCodeBlockProcessor( CODE_BLOCK_LANG_TOOLBAR, (source, el, ctx) => {
|
||||
const lines = source.split('\n');
|
||||
const valueMap = new Map<string,string>();
|
||||
lines.forEach(line => {
|
||||
|
|
@ -44,14 +51,8 @@ export default class MeldBuildPlugin extends Plugin {
|
|||
const showHelpButton = helpButtonLabel !== '';
|
||||
|
||||
if (showRunButton){
|
||||
el.createEl('button', { text: runButtonLabel ?? 'Run'}, el =>{
|
||||
el.on('click', '*', ev=>{
|
||||
const view = app.workspace.getActiveViewOfType( MarkdownView );
|
||||
if (!view){
|
||||
return;
|
||||
}
|
||||
this.buildAndRun(view.editor, view);
|
||||
});
|
||||
el.createEl('button', { text: runButtonLabel ?? 'Run'}, el => {
|
||||
el.on('click', '*', ev => this.buildAndRunActiveView() );
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +76,37 @@ export default class MeldBuildPlugin extends Plugin {
|
|||
|
||||
}
|
||||
|
||||
private buildAndRun( editor:Editor, view: MarkdownView ){
|
||||
private async reloadActiveViewsWithToolbars(){
|
||||
app.workspace.iterateAllLeaves( leaf =>{
|
||||
const view = leaf.view;
|
||||
if ( view instanceof MarkdownView ){
|
||||
|
||||
if (!view.editor.getValue().contains(CODE_BLOCK_LANG_TOOLBAR)){
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug( `Meld-Build::Rebuilding view for file '${view.file.path}'` );
|
||||
|
||||
(view.leaf as any).rebuildView();
|
||||
}
|
||||
});
|
||||
//const view = app.workspace.getActiveViewOfType( MarkdownView );
|
||||
//if (view == null){
|
||||
// return;
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async buildAndRunActiveView(){
|
||||
const view = app.workspace.getActiveViewOfType( MarkdownView );
|
||||
if (!view){
|
||||
return;
|
||||
}
|
||||
await this.buildAndRun( view.editor, view);
|
||||
}
|
||||
|
||||
private async buildAndRun( editor:Editor, view: MarkdownView ){
|
||||
const logger = new RunLogger();
|
||||
try{
|
||||
//await view.save();
|
||||
|
|
@ -83,7 +114,7 @@ export default class MeldBuildPlugin extends Plugin {
|
|||
const runner = compiler.compile(logger, editor, view);
|
||||
runner();
|
||||
}catch(e){
|
||||
logger.error(e);
|
||||
await logger.error(e);
|
||||
new Notice(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
src/code-block-info.ts
Normal file
8
src/code-block-info.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export class CodeBlockInfo{
|
||||
public language:string;
|
||||
public params:string[];
|
||||
constructor( language: string, params?:string[] ){
|
||||
this.language = language;
|
||||
this.params = params ?? [];
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import { DataSet, IDataSetCollection } from "src/data-set";
|
|||
import { Parser } from "src/parser";
|
||||
import { RunLogger } from 'src/run-logger';
|
||||
import { TRunContext } from "src/run-context";
|
||||
import { NamedCodeBlock } from "./named-code-block";
|
||||
|
||||
export class Compiler{
|
||||
private templateLanguages = ['html', 'css'];
|
||||
|
|
@ -23,6 +24,8 @@ export class Compiler{
|
|||
// build context
|
||||
const context = this.build_run_context(logger, editor, fileCache, view);
|
||||
|
||||
//console.debug(context.sourceCode);
|
||||
|
||||
// return runner
|
||||
return () => this.buildSandboxedRunnerFunction(context);
|
||||
//return () => this.buildRunnerFunction(sourceCode, context);
|
||||
|
|
@ -45,8 +48,14 @@ export class Compiler{
|
|||
// templates
|
||||
const templateBlocks = pzr.fetchCodeBlocks( editor, fileCache, this.templateLanguages );
|
||||
|
||||
|
||||
// source code
|
||||
const sourceCode = codeBlocks.map( e=>e.content ).join('\n');
|
||||
|
||||
//console.debug({sourceCode});
|
||||
|
||||
return {
|
||||
sourceCode: codeBlocks.join('\n'),
|
||||
sourceCode: sourceCode,
|
||||
|
||||
data: data,
|
||||
templates: templateBlocks,
|
||||
|
|
@ -54,10 +63,15 @@ export class Compiler{
|
|||
logger : log,
|
||||
log: async (...x) => await log.info( ...x ),
|
||||
|
||||
render( template, data ) {
|
||||
const templateBuilder = HB.compile( template );
|
||||
const result = templateBuilder(data);
|
||||
return result;
|
||||
render( template:string|NamedCodeBlock, data:any ) {
|
||||
|
||||
if( typeof template == 'string' ){
|
||||
return HB.compile( template )(data);
|
||||
}else if ( template instanceof NamedCodeBlock ){
|
||||
return HB.compile( template.content )(data);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
ui: {
|
||||
|
|
@ -255,7 +269,7 @@ export class Compiler{
|
|||
private async import(
|
||||
log: RunLogger,
|
||||
data: IDataSetCollection,
|
||||
templates: string[],
|
||||
templates: NamedCodeBlock[],
|
||||
path:string
|
||||
) : Promise<boolean>{
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export class DataSet extends Array<DataSetRow> {
|
||||
// TODO: is this needed?
|
||||
|
||||
}
|
||||
|
||||
export interface IDataSetCollection {
|
||||
|
|
|
|||
14
src/named-code-block.ts
Normal file
14
src/named-code-block.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { CodeBlockInfo } from "./code-block-info";
|
||||
|
||||
export class NamedCodeBlock{
|
||||
|
||||
public info:CodeBlockInfo;
|
||||
public name:string;
|
||||
public content:string;
|
||||
|
||||
constructor( info:CodeBlockInfo, name:string, content:string ){
|
||||
this.info = info,
|
||||
this.name = name;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
230
src/parser.ts
230
src/parser.ts
|
|
@ -1,14 +1,136 @@
|
|||
import { DataSet, DataSetRow, IDataSetCollection } from "src/data-set";
|
||||
import { CachedMetadata, Editor } from "obsidian";
|
||||
import { NamedCodeBlock } from "./named-code-block";
|
||||
import { CodeBlockInfo } from "./code-block-info";
|
||||
|
||||
export class Parser {
|
||||
|
||||
public fetchCodeBlocks(
|
||||
editor: Editor,
|
||||
fileCache: CachedMetadata | undefined,
|
||||
languages :string[]
|
||||
): NamedCodeBlock[] {
|
||||
const result: NamedCodeBlock[] = [];
|
||||
|
||||
if ( fileCache == undefined ){
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( fileCache.sections == undefined ){
|
||||
return result;
|
||||
}
|
||||
|
||||
//console.log(fileCache.sections);
|
||||
const codeBlocks = fileCache.sections?.filter( s => ['heading','code'].contains( s.type ) ) ?? [];
|
||||
//console.debug({codeBlocks});
|
||||
|
||||
let cbName = '';
|
||||
codeBlocks.forEach( codeBlockSection => {
|
||||
|
||||
const from = editor.offsetToPos( codeBlockSection.position.start.offset );
|
||||
const to = editor.offsetToPos( codeBlockSection.position.end.offset );
|
||||
const content = editor.getRange( from, to );
|
||||
|
||||
//console.debug(content);
|
||||
|
||||
if ( codeBlockSection.type == 'heading' ){
|
||||
cbName = this.extractHeaderName(content);
|
||||
return;
|
||||
}
|
||||
|
||||
// code block
|
||||
const lines = content.split('\n');
|
||||
if ( lines.length <= 2 ){
|
||||
return;
|
||||
}
|
||||
|
||||
// filter languages
|
||||
const codeBlockInfo = this.extractCodeBlockInfo(lines[0]);
|
||||
if (!codeBlockInfo){
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !languages.contains( codeBlockInfo.language ) ){
|
||||
return;
|
||||
}
|
||||
|
||||
// remove first and last lines
|
||||
const cbContent = lines.slice(1,-1).join('\n');
|
||||
|
||||
const codeBlock = new NamedCodeBlock( codeBlockInfo, cbName, cbContent );
|
||||
|
||||
result.push( codeBlock );
|
||||
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private extractCodeBlockInfo( line:string ) : CodeBlockInfo | null {
|
||||
/*
|
||||
match tests
|
||||
```lang => { language: 'lang', params:[] }
|
||||
```lang with space => { language: 'lang', params:['with','space'] }
|
||||
``` whitespaced => { language: 'whitespaced', params:[] }
|
||||
```` multitick => { language: 'multitick', params:[] }
|
||||
````multitick with space => { language: 'multitick', params:['with','space'] }
|
||||
*/
|
||||
const matches = line.match(/````*\s*([a-z0-9-]+)(?:\s*([a-z0-9-]+))*/i);
|
||||
|
||||
//console.debug(matches);
|
||||
|
||||
if ( matches == null ){
|
||||
return null;
|
||||
}
|
||||
|
||||
const lang = matches.at(1);
|
||||
if ( lang === undefined ){
|
||||
return null;
|
||||
}
|
||||
|
||||
const params:string[] = matches.slice(2).filter( e => e );
|
||||
//console.debug({params});
|
||||
|
||||
return new CodeBlockInfo( lang, params );
|
||||
}
|
||||
|
||||
// private matchesCodeBlockStart( line:string, languages:string[] ) : boolean {
|
||||
|
||||
// const cbInfo = this.extractCodeBlockInfo(line);
|
||||
|
||||
// if ( cbInfo == null ){
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return languages.contains( cbInfo?.language );
|
||||
// }
|
||||
|
||||
public loadCsv( csvContent:string ) : DataSet {
|
||||
const lines = csvContent.split('\n').map( e=>e.trim() );
|
||||
if (lines.length == 0){
|
||||
return new DataSet();
|
||||
}
|
||||
|
||||
const columns = lines.first()?.split(',').map( e => this.extractAsColumnName(e) ) ?? [];
|
||||
|
||||
const rows = new Array<DataSetRow>();
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const rowData = lines[i].split(',').map( d => this.extractValueFromString(d));
|
||||
rows.push( new DataSetRow(columns, rowData));
|
||||
}
|
||||
|
||||
const ds = new DataSet( ...rows );
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
public applyMarkdownContent(
|
||||
name:string,
|
||||
content:string,
|
||||
data:IDataSetCollection,
|
||||
templates:string[],
|
||||
templateLanguages: string[]
|
||||
templates:NamedCodeBlock[],
|
||||
templateLanguageFilter: string[]
|
||||
) : void {
|
||||
|
||||
const lines = content.split('\n');
|
||||
|
|
@ -17,12 +139,15 @@ export class Parser {
|
|||
let currentHeader = '';
|
||||
for ( let i = 0; i < lines.length; i++ ) {
|
||||
const trimLine = lines[i].trim();
|
||||
if (trimLine.startsWith('#')){
|
||||
|
||||
const codeBlockInfo = this.extractCodeBlockInfo(trimLine);
|
||||
|
||||
if ( trimLine.startsWith('#') ) {
|
||||
|
||||
// header
|
||||
currentHeader = this.extractHeader(trimLine);
|
||||
currentHeader = this.extractHeaderName(trimLine);
|
||||
|
||||
}else if ( trimLine.startsWith('|') ){
|
||||
} else if ( trimLine.startsWith('|') ){
|
||||
|
||||
// extract table
|
||||
const tableLines: string[] = [];
|
||||
|
|
@ -31,10 +156,10 @@ export class Parser {
|
|||
tableLines.push(tableLine);
|
||||
i++;
|
||||
}
|
||||
const dataPropName = this.convertToTableName( currentHeader.length > 0 ? currentHeader : name );
|
||||
data[dataPropName] = this.parseAsMdTable(tableLines);
|
||||
const dataPropName = this.extractAsTableName( currentHeader.length > 0 ? currentHeader : name );
|
||||
data[dataPropName] = this.parseMdTable(tableLines);
|
||||
|
||||
}else if ( templateLanguages.find( v => trimLine.toLowerCase().startsWith('```' + v) ) !== undefined ){
|
||||
} else if ( codeBlockInfo && templateLanguageFilter.contains( codeBlockInfo.language ) ){
|
||||
|
||||
// extract template codeblock
|
||||
const templateLines: string[] = [];
|
||||
|
|
@ -44,17 +169,24 @@ export class Parser {
|
|||
templateLines.push(templateLine);
|
||||
i++;
|
||||
}
|
||||
templates.push( templateLines.join('\n') );
|
||||
|
||||
const codeBlock = new NamedCodeBlock(
|
||||
codeBlockInfo,
|
||||
name,
|
||||
templateLines.join('\n')
|
||||
);
|
||||
|
||||
templates.push( codeBlock );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extractHeader(line:string) : string{
|
||||
return this.convertToTableName(line.replace('#','').trim());
|
||||
private extractHeaderName(line:string) : string{
|
||||
return line.replaceAll('#','').trim();
|
||||
}
|
||||
|
||||
private parseAsMdTable( tableLines:string[] ): DataSet {
|
||||
private parseMdTable( tableLines:string[] ): DataSet {
|
||||
const data: DataSet = new DataSet();
|
||||
|
||||
const tlines = tableLines
|
||||
|
|
@ -63,7 +195,7 @@ export class Parser {
|
|||
//console.debug(tableLines);
|
||||
const columns = tlines
|
||||
.first()?.split('|')
|
||||
.map( e=> this.convertToColumnName(e) )
|
||||
.map( e=> this.extractAsColumnName(e) )
|
||||
.filter( e=>e.length > 0)
|
||||
?? []
|
||||
;
|
||||
|
|
@ -77,7 +209,7 @@ export class Parser {
|
|||
if (rowLine.startsWith('|') && rowLine.endsWith('|')){
|
||||
continue;
|
||||
}
|
||||
const rowValues : unknown[] = rowLine.split('|').map( e => this.covertFromString(e) );
|
||||
const rowValues : unknown[] = rowLine.split('|').map( e => this.extractValueFromString(e) );
|
||||
data.push( new DataSetRow( columns, rowValues ) );
|
||||
}
|
||||
|
||||
|
|
@ -113,9 +245,9 @@ export class Parser {
|
|||
const to = editor.offsetToPos(section.position.end.offset);
|
||||
const table = editor.getRange( from, to );
|
||||
const tableLines = table.split('\n');
|
||||
const data = this.parseAsMdTable(tableLines);
|
||||
const data = this.parseMdTable(tableLines);
|
||||
|
||||
const tableName = this.convertToTableName(lastHeading);
|
||||
const tableName = this.extractAsTableName(lastHeading);
|
||||
result[tableName] = data;
|
||||
//result.push( data );
|
||||
|
||||
|
|
@ -152,15 +284,15 @@ export class Parser {
|
|||
|
||||
}
|
||||
|
||||
private convertToTableName( str:string ) : string {
|
||||
return str.trim().replace(/\W/ig, '_').toLowerCase();
|
||||
private extractAsTableName( str:string ) : string {
|
||||
return str.trim().replaceAll(/\W/ig, '_').toLowerCase();
|
||||
}
|
||||
|
||||
private convertToColumnName( str:string ) : string {
|
||||
return str.trim().replace(/\W/ig, '_').toLowerCase();
|
||||
private extractAsColumnName( str:string ) : string {
|
||||
return str.trim().replaceAll(/\W/ig, '_').toLowerCase();
|
||||
}
|
||||
|
||||
private covertFromString( str:string ) : string | number | Date {
|
||||
private extractValueFromString( str:string ) : string | number | Date {
|
||||
const trimmed = str.trim();
|
||||
//console.debug({trimmed});
|
||||
//see: https://rgxdb.com/r/526K7G5W
|
||||
|
|
@ -183,61 +315,5 @@ export class Parser {
|
|||
return trimmed;
|
||||
}
|
||||
|
||||
public fetchCodeBlocks(
|
||||
editor: Editor,
|
||||
fileCache: CachedMetadata | undefined,
|
||||
languages :string[]
|
||||
): string[] {
|
||||
const result: string[] = [];
|
||||
|
||||
if ( fileCache == undefined ){
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( fileCache.sections == undefined ){
|
||||
return result;
|
||||
}
|
||||
|
||||
fileCache.sections.forEach( section => {
|
||||
if (section.type != 'code'){
|
||||
return;
|
||||
}
|
||||
const from = editor.offsetToPos(section.position.start.offset);
|
||||
const to = editor.offsetToPos(section.position.end.offset);
|
||||
const code = editor.getRange( from, to );
|
||||
let lines = code.split('\n');
|
||||
if (lines.length <= 2){
|
||||
return;
|
||||
}
|
||||
const lineStarts = languages.map( l=> '```'+l );
|
||||
if ( !lineStarts.find( e=> e.startsWith( lines[0].toLowerCase() ) )){
|
||||
return;
|
||||
}
|
||||
// remove first and last lines
|
||||
lines = lines.slice(1,-1)
|
||||
result.push( lines.join('\n') );
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public loadCsv( csvdata:string ) : DataSet {
|
||||
const lines = csvdata.split('\n').map( e=>e.trim());
|
||||
if (lines.length == 0){
|
||||
return new DataSet();
|
||||
}
|
||||
//console.debug(lines);
|
||||
const columns = lines.first()?.split(',').map( e=> this.convertToColumnName(e) ) ?? [];
|
||||
|
||||
const rows = new Array<DataSetRow>();
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const rowData = lines[i].split(',').map( d=>this.covertFromString(d));
|
||||
rows.push( new DataSetRow(columns, rowData));
|
||||
}
|
||||
|
||||
const ds = new DataSet( ...rows );
|
||||
//ds.columns = columns;
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import { DataSet, IDataSetCollection } from "src/data-set";
|
||||
import { DataviewApi } from "obsidian-dataview";
|
||||
import { RunLogger } from "src/run-logger";
|
||||
import { NamedCodeBlock } from "./named-code-block";
|
||||
|
||||
export type TRunContext = {
|
||||
|
||||
sourceCode: string;
|
||||
|
||||
data: IDataSetCollection;
|
||||
templates: string[];
|
||||
templates: NamedCodeBlock[];
|
||||
|
||||
logger: RunLogger,
|
||||
log( ...params: any[] ) : Promise<void>;
|
||||
render( template:string, data:any ) : string;
|
||||
render( template:string|NamedCodeBlock, data:any ) : string;
|
||||
|
||||
ui: TUiRunContext;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"target": "ES2021",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
|
|
@ -13,9 +13,10 @@
|
|||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
//"ES5",
|
||||
//"ES6",
|
||||
//"ES7"
|
||||
"ES2021"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
|
|
|||
Loading…
Reference in a new issue