Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/foam/core/reflow/Script.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ foam.CLASS({
imports: [
'data as block',
'eval_',
'scope'
'scope',
'notify?'
],

documentation: `
Expand All @@ -24,6 +25,19 @@ foam.CLASS({
`,

properties: [
{
Comment thread
kgrgreer marked this conversation as resolved.
class: 'Long',
name: 'id',
visibility: 'RO'
},
{
class: 'String',
name: 'scriptName'
},
{
class: 'String',
name: 'description'
},
{
class: 'String',
name: 'code',
Expand Down Expand Up @@ -61,10 +75,16 @@ foam.CLASS({
actions: [
function run() {
let self = this;
with ( this.scope ) {
var _scope = this.scope || {};
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have _scope?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because when i hit run from flow, scope returns undefined

with ( _scope ) {
with ( { log: this.log.bind(this) } ) {
var ret = eval('(async function() {' + self.code + '})').call(self.block);
ret.then(v => this.log(v), v => this.log(v)).catch(e => this.log(e.stack));
ret
.then(v => {
this.log(v);
try { this.notify && this.notify(`Script${this.scriptName ? ' "' + this.scriptName + '"' : ''} executed successfully`); } catch (e) {}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kgrgreer Just wanted to add some feedback: when I tried it, there was no way to tell if the script had run.

}, v => this.log(v))
.catch(e => this.log(e.stack));
return ret;
}
}
Expand Down
119 changes: 119 additions & 0 deletions src/foam/core/reflow/cmd/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,47 @@ foam.CLASS({
});


foam.CLASS({
package: 'foam.core.reflow.cmd',
name: 'Scripts',
extends: 'foam.core.reflow.cmd.Command',

requires: [ 'foam.core.reflow.Script', 'foam.core.reflow.cmd.DAORowView' ],

imports: [ 'libDAO', 'scope' ],



properties: [
[ 'description', 'Display available scripts' ]
],

methods: [
function execute(opt_nameQuery) {
var self = this;
var dao = this.libDAO;
var count = foam.lang.SimpleSlot.create({value: 0});
if ( opt_nameQuery ) dao = dao.where(
this.OR(
this.CONTAINS_IC(this.Script.SCRIPT_NAME, opt_nameQuery),
));
this.out.tag('br');
this.out.start('table').attr('width', '100%').
select(dao, function(n) {
count.value++;

this.start('tr').
start('td').attr('align', 'left').add(n.scriptName).end().
start('td').attr('align', 'left').add(n.description).end().
start('td').attr('align', 'left').start(foam.u2.Link).add('Run').on('click', function() { n.run(); }).end().
end();
}).
end().
start('b').add(count, ' selected').end();
}
]
})

foam.CLASS({
package: 'foam.core.reflow.cmd',
name: 'DAOS',
Expand Down Expand Up @@ -391,6 +432,84 @@ foam.CLASS({
]
});

foam.CLASS({
package: 'foam.core.reflow.cmd',
name: 'RunScript',
extends: 'foam.core.reflow.cmd.Command',

mixins: [ 'foam.mlang.Expressions' ],

imports: [ 'libDAO', 'notify?' ],

properties: [
{ name: 'id', value: 'runScript' },
[ 'description', 'Run a saved script by name: runScript <scriptName>' ]
],

methods: [
async function execute(scriptName) {
if ( ! scriptName ) {
this.notify && this.notify('Usage: runScript <scriptName>');
this.out && this.out.add('Usage: runScript <scriptName>');
return;
}

let script = null;
try {
// Try exact match on scriptName field
const result = await this.libDAO
.where(this.EQ(foam.core.reflow.Script.SCRIPT_NAME, scriptName))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do find() instead of limit(1).select().

.limit(1)
.select();
script = result && result.array && result.array[0];
} catch (e) {}

if ( ! script ) {
try {
// Fallback: try DAO find by id
script = await this.libDAO.find(scriptName);
} catch (e) {}
}

if ( ! script ) {
const msg = `Script "${scriptName}" not found`;
(this.notify && this.notify(msg)) || (this.out && this.out.add(msg));
return;
}

try {
await script.run();
this.notify && this.notify(`Script "${script.scriptName || scriptName}" executed successfully`);
} catch (e) {
const errMsg = `Script "${scriptName}" failed: ${e && e.message ? e.message : e}`;
(this.notify && this.notify(errMsg)) || (this.out && this.out.add(errMsg));
}
}
]
});

foam.CLASS({
package: 'foam.core.reflow.cmd',
name: 'HelpScript',
extends: 'foam.core.reflow.cmd.Command',

properties: [
{ name: 'id', value: 'helpScript' },
[ 'description', 'Display usage for scripts-related commands' ]
],

methods: [
function execute() {
this.out
.start('div')
.start('h3').add('Script Commands').end()
.start('p').add('scripts: Display available scripts').end()
.start('p').add('runScript <scriptName>: Run a saved script by name').end()
.end();
}
]
});

/*
foam.CLASS({
package: 'foam.core.reflow',
Expand Down
3 changes: 3 additions & 0 deletions src/foam/core/reflow/cmd/cmds.jrl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ p({"class":"foam.core.reflow.cmd.Clear", "id": "clear"})
p({"class":"foam.core.reflow.cmd.DAO", "id": "dao"})
p({"class":"foam.core.reflow.cmd.DAOCreate", "id": "add", linkable: false})
p({"class":"foam.core.reflow.cmd.DAOS", "id": "daos"})
p({"class":"foam.core.reflow.cmd.Scripts", "id": "scripts"})
p({"class":"foam.core.reflow.cmd.RunScript", "id": "run"})
p({"class":"foam.core.reflow.cmd.HelpScript", "id": "helpScript"})
p({"class":"foam.core.reflow.cmd.Describe", "id": "describe", linkable: false})
p({"class":"foam.core.reflow.cmd.Flows", "id": "flows"})
p({"class":"foam.core.reflow.cmd.Help", "id": "help"})
Expand Down
2 changes: 1 addition & 1 deletion src/foam/core/reflow/pom.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ foam.POM({
{ name: 'DynamicReflowData', flags: 'js' },
{ name: 'DynamicReflowComponents', flags: 'js' },
{ name: 'DynamicReflowHelp', flags: 'js' },
{ name: 'Script', flags: 'js' },
{ name: 'Script', flags: 'js|java' },
{ name: 'Signature', flags: 'js' },
{ name: 'SinkView', flags: 'js' },
{ name: 'CopyFromBorder', flags: 'js' },
Expand Down
26 changes: 26 additions & 0 deletions src/foam/core/reflow/services.jrl
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,29 @@ p({
}
"""
})

p({
"class": "foam.core.boot.CSpec",
"name": "libDAO",
Copy link
Copy Markdown
Collaborator

@jlhughes jlhughes Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this called 'libDAO' when it stores reflow scripts?
This is not intuitive and not consistent with the rest of FOAM.

The Script model, when used elsewhere was extended - Cron, Test,
Realize that's not happening here, but perhaps the dao can be reflowScriptDAO.

Copy link
Copy Markdown
Collaborator Author

@AnasAmil1 AnasAmil1 Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was a request from Kevin to name it libDAO

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't want to name it scriptDAO because we already have the CORE scriptDAO, which is something different. The idea is that we don't want to have most of our end-users be able to write FLOW Scripts, but we do want to let them run them. So rather than embedding them inside FLOWs, we'll make a separate DAO of library functions, which they can run, hence the libDAO. This separates the ability to create or edit Library Scripts, from the ability to run them.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnasAmil1 Can you rename this to reflowLibDAO?

"description": "Stores Reflow Scripts",
"serve":true,
"authenticate":false,
"serviceScript":`
return new foam.dao.EasyDAO.Builder(x)
.setPm(true)
.setAuthorize(true)
.setPermissioned(true)
.setJournalType(foam.dao.JournalType.SINGLE_JOURNAL)
.setJournalName("libs")
.setOf(foam.core.reflow.Script.getOwnClassInfo())
.build();
`,
"client":"""
{
"of":"foam.core.reflow.Script",
"cache":true,
"ttlSelectPurgeTime": 0,
"ttlPurgeTime": 0
}
"""
})