From 8ab4f6abbd6926ef1fafc0b2800dc1155ec717ae Mon Sep 17 00:00:00 2001 From: Neil Kirsopp Date: Fri, 7 Aug 2026 05:13:07 +0200 Subject: [PATCH 1/2] First draft of a plugin mechanism --- EWC/AddResource.aplf | 21 +++++++++++++ EWC/GetClass.aplf | 9 +++--- EWC/Init.aplf | 1 + EWC/RegisterPlugin.aplf | 36 +++++++++++++++++++++++ EWC/WaitForWG.aplf | 9 ++++-- EWC/dWC.aplf | 8 +++-- EWC/dWX.aplf | 7 +++-- EWC/ensurePlugins.aplf | 15 ++++++++++ EWC/findClass.aplf | 19 ++++++++++++ EWC/getConnection.aplf | 25 +++++----------- EWC/getObj.aplf | 6 ++-- EWC/injectPlugin.aplf | 35 ++++++++++++++++++++++ EWC/sessionNS.aplf | 28 ++++++++++++++++++ EWC/setDefaults.aplf | 1 + demo/DemoPluginRegistry.aplf | 57 ++++++++++++++++++++++++++++++++++++ 15 files changed, 245 insertions(+), 32 deletions(-) create mode 100644 EWC/AddResource.aplf create mode 100644 EWC/RegisterPlugin.aplf create mode 100644 EWC/ensurePlugins.aplf create mode 100644 EWC/findClass.aplf create mode 100644 EWC/injectPlugin.aplf create mode 100644 EWC/sessionNS.aplf create mode 100644 demo/DemoPluginRegistry.aplf diff --git a/EWC/AddResource.aplf b/EWC/AddResource.aplf new file mode 100644 index 00000000..c5d5a019 --- /dev/null +++ b/EWC/AddResource.aplf @@ -0,0 +1,21 @@ + alias AddResource folder;m;rc +⍝ Serve a folder over HTTP as a virtual folder, e.g. +⍝ 'kendo' AddResource '/path/to/plugin/assets' +⍝ makes that folder's contents available at /kendo/. Takes effect immediately - +⍝ wss reads its mappings per request - so this works before or after Init. +⍝ +⍝ An alias is a single path segment: wss matches on the first '/' of the URL. + 'Alias must not contain "/"'⎕SIGNAL('/'∊alias)/11 + :If 2≠⎕NC'RESOURCES' ⋄ RESOURCES←0 2⍴⊂'' ⋄ :EndIf + :If 2≠⍴⍴RESOURCES ⋄ RESOURCES←1 2⍴RESOURCES ⋄ :EndIf + + m←(0≠≢¨RESOURCES[;1])∧~RESOURCES[;1]∊⊂alias + RESOURCES←(m⌿RESOURCES)⍪alias folder + + :If 9=⌊⎕NC'WSS' + WSS.Aliases←RESOURCES + rc←WSS.CheckAliases + :If 0≠⊃rc + 'W'Log'AddResource ',alias,': ',2⊃rc + :EndIf + :EndIf diff --git a/EWC/GetClass.aplf b/EWC/GetClass.aplf index 6863d776..4bf54569 100644 --- a/EWC/GetClass.aplf +++ b/EWC/GetClass.aplf @@ -1,8 +1,9 @@ -r←GetClass (ns name);t;c;m +r←GetClass (ns name);t;m +r←⎕NULL :If 9=⎕NC 'ns' :AndIf ∨/m←0≠ns.⎕NC t←name∘,¨'.type' '.Type' -:AndIf 9=⎕NC c←'classes.',⎕C ns⍎⊃m/t - r←⍎c -:Else + r←findClass ns⍎⊃m/t +:EndIf +:If ⎕NULL≡r ('Not a supported GUI object: ',name) ⎕SIGNAL 6 :EndIf diff --git a/EWC/Init.aplf b/EWC/Init.aplf index 64c35e05..e857a17f 100644 --- a/EWC/Init.aplf +++ b/EWC/Init.aplf @@ -74,6 +74,7 @@ ⎕←'Using client found at ',JSClientFolder setDefaults + ensurePlugins ⍝ Plugins may already have registered; keep them detached from Link :If DIRECT 'D' Log 'Direct mode enabled' diff --git a/EWC/RegisterPlugin.aplf b/EWC/RegisterPlugin.aplf new file mode 100644 index 00000000..1d06ed39 --- /dev/null +++ b/EWC/RegisterPlugin.aplf @@ -0,0 +1,36 @@ + {r}←RegisterPlugin plugin;name;nm;target;res;alias;folder +⍝ Register a plugin with EWC. Safe before or after Init. +⍝ +⍝ plugin is a namespace ref providing: +⍝ Name plugin id; also the key its classes are tagged with +⍝ Classes ref to a namespace whose sub-namespaces are EWC class definitions +⍝ Folder the plugin's root folder on disk +⍝ Inject (optional) niladic fn returning the JavaScript to evaluate in the +⍝ client, the first time one of the plugin's classes is created +⍝ Resources (optional) an (alias folder) pair, or a 2-column matrix of them +⍝ +⍝ Class definitions are copied rather than referenced, so a plugin can be +⍝ re-registered after editing its source without stale state. + ensurePlugins + 'Plugin must supply a Name'⎕SIGNAL(2≠plugin.⎕NC'Name')/11 + 'Plugin must supply Classes'⎕SIGNAL(9≠plugin.⎕NC'Classes')/11 + r←name←,plugin.Name + + ⍎'PLUGINS.',name,'←plugin' + + :For nm :In plugin.Classes.⎕NL ¯9 + target←(⍕PLUGINS),'.classes.',nm + target ⎕NS plugin.Classes⍎nm + (⍎target).PluginName←name + :EndFor + ensurePlugins ⍝ the class namespaces are new, so detach those too + + :If 2=plugin.⎕NC'Resources' + res←plugin.Resources + :If 2≠⍴⍴res ⋄ res←1 2⍴res ⋄ :EndIf + :For (alias folder) :In ↓res + alias AddResource folder + :EndFor + :EndIf + + 'D'Log'Registered plugin "',name,'", classes: ',⍕plugin.Classes.⎕NL ¯9 diff --git a/EWC/WaitForWG.aplf b/EWC/WaitForWG.aplf index 864c2ae3..948e758a 100644 --- a/EWC/WaitForWG.aplf +++ b/EWC/WaitForWG.aplf @@ -1,12 +1,15 @@ - R←msg WaitForWG(Props ID WGID);j;⎕TRAP;p;seqok;nl;nlc;m;t;token;wx;z;ref;ok + R←msg WaitForWG arg;Props;ID;WGID;timeout;j;⎕TRAP;p;seqok;nl;nlc;m;t;token;wx;z;ref;ok +⍝ Right argument is (Props ID WGID), with an optional 4th element giving the +⍝ seconds to wait. Defaults to WG_TIMEOUT. + (Props ID WGID timeout)←4↑arg,⊂WG_TIMEOUT wx←Props≡'WX' ⍝ A WX rather than a WG token←WG_TOKENBASE+TOKENSTEP×1⌈ID×MODE=2 AGAIN: - j←WG_TIMEOUT ⎕TGET token + j←timeout ⎕TGET token :If 0=≢j - 'W' Log 'Client did not respond within ',(⍕WG_TIMEOUT),' seconds.' + 'W' Log 'Client did not respond within ',(⍕timeout),' seconds.' 'W' Log 'Request was: ',msg ⎕SIGNAL 6 :EndIf diff --git a/EWC/dWC.aplf b/EWC/dWC.aplf index d71bd5dd..3acbc7da 100644 --- a/EWC/dWC.aplf +++ b/EWC/dWC.aplf @@ -17,7 +17,8 @@ →0⊣R←nsname caller.⎕WC Props :EndIf - :If 9≠classes.⎕NC type + class←findClass type + :If ⎕NULL≡class Warning 'WC ',Name,' - unsupported class "',type,'"' ⎕←⎕SE.UCMD 'disp ⍪Props' nsname caller.⎕NS'' @@ -25,7 +26,10 @@ →0 :EndIf - class←classes⍎type + :If 2=class.⎕NC'PluginName' ⍝ First use of a plugin class loads its JS + caller injectPlugin class.PluginName Name + :EndIf + proplist←⎕C PropList←class.PropList valid←(lcp←⎕C propnames)∊allprops←⎕C AllProps←PropList,objProps,deprecatedprops values←valid↓¨Props diff --git a/EWC/dWX.aplf b/EWC/dWX.aplf index e004c531..19ad1835 100644 --- a/EWC/dWX.aplf +++ b/EWC/dWX.aplf @@ -1,5 +1,8 @@ - {R}←dWX args;Name;Method;Info;caller;ID;conn;WX;wx;msg;z;⎕TRAP;txt + {R}←{timeout}dWX args;Name;Method;Info;caller;ID;conn;WX;wx;msg;z;⎕TRAP;txt +⍝ Optional left argument: seconds to wait for the client, defaulting to +⍝ WG_TIMEOUT. Loading a plugin's JavaScript can take longer than a property read. ⎕TRAP←0 'S' + :If 0=⎕NC'timeout' ⋄ timeout←WG_TIMEOUT ⋄ :EndIf (Name Method)←2↑args ⋄ Info←2↓args :If 'gettextsize'≡⎕C Method @@ -25,7 +28,7 @@ :EndIf :Trap 6 ⍝ If we get no response - R←msg WaitForWG 'WX' ID(⍕WGID) + R←msg WaitForWG 'WX' ID(⍕WGID)timeout :Else ⎕DMX.EM ⎕SIGNAL ⎕DMX.EN :EndTrap diff --git a/EWC/ensurePlugins.aplf b/EWC/ensurePlugins.aplf new file mode 100644 index 00000000..d7ec0ac7 --- /dev/null +++ b/EWC/ensurePlugins.aplf @@ -0,0 +1,15 @@ + {r}←ensurePlugins +⍝ The plugin registry: PLUGINS.classes holds class definitions contributed by +⍝ plugins, PLUGINS. the record for each registered plugin. +⍝ +⍝ It is detached from Link. #.EWC is Link-managed, so without this a plugin's +⍝ classes would be serialised into the EWC source tree. This function is the +⍝ single place that decides where the registry lives; if breaking a child of a +⍝ live link ever proves unreliable, move it out of #.EWC here and nothing else +⍝ needs to change - everything reaches it through the PLUGINS ref. + :If 9≠⎕NC'PLUGINS' + 'PLUGINS'⎕NS'' + 'PLUGINS.classes'⎕NS'' + :EndIf + {}{0::⍬ ⋄ ⎕SE.Link.Break ⍵}'#.EWC.PLUGINS' ⍝ reports "Not linked" when already detached + r←PLUGINS diff --git a/EWC/findClass.aplf b/EWC/findClass.aplf new file mode 100644 index 00000000..02eb052a --- /dev/null +++ b/EWC/findClass.aplf @@ -0,0 +1,19 @@ + r←findClass type;names;i +⍝ Resolve an EWC class name to its definition namespace, or ⎕NULL. +⍝ +⍝ Core classes live in #.EWC.classes under lowercase names. Plugin classes live +⍝ in PLUGINS.classes and keep the casing their author gave them, so they are +⍝ matched case-insensitively. See RegisterPlugin and ensurePlugins. + type←⎕C type + :If 9=classes.⎕NC type + →0⊣r←classes⍎type + :EndIf + + r←⎕NULL + :If 9=⎕NC'PLUGINS' + :AndIf 9=PLUGINS.⎕NC'classes' + names←PLUGINS.classes.⎕NL ¯9 + :If 0≠≢i←⍸(⎕C names)∊⊂type + r←PLUGINS.classes⍎(⊃i)⊃names + :EndIf + :EndIf diff --git a/EWC/getConnection.aplf b/EWC/getConnection.aplf index a7bf64b5..bbf84921 100644 --- a/EWC/getConnection.aplf +++ b/EWC/getConnection.aplf @@ -1,23 +1,14 @@ - r←getConnection(caller names);name;top;n;p - name←,⊃⊆names - n←≢p←(⍕caller),'.' - name←(n×p≡n↑name)↓name ⍝ drop caller prefix - top←(¯1+name⍳'.')↑name + r←getConnection(caller names);ns + ns←caller sessionNS names - :If 9=caller.⎕NC n←top,'.HR._EWC' - r←(caller⍎n).(ID conn) - - :ElseIf MODE=2 ⍝ Multi - :If 9=caller.⎕NC'_EWC' - r←caller._EWC.(ID conn) - :Else - ∘∘∘ ⍝ Unable to locate app session - :EndIf - - :Else + :If ⎕NULL≢ns :Trap 0 - r←CODELOCATION._EWC.(ID conn) + r←ns.(ID conn) :Else r←0 ¯1 ⍝ no connection: ¯1 is an invalid conn (0 is a VALID Conga connection) :EndTrap + :ElseIf MODE=2 ⍝ Multi + ∘∘∘ ⍝ Unable to locate app session + :Else + r←0 ¯1 :EndIf diff --git a/EWC/getObj.aplf b/EWC/getObj.aplf index a4f56247..ca22e452 100644 --- a/EWC/getObj.aplf +++ b/EWC/getObj.aplf @@ -1,11 +1,9 @@ - (type class objref)←getObj Name;t + (type class objref)←getObj Name :Trap 0 objref←caller⍎Name type←objref.Type - :If 9=classes.⎕NC t←⎕C type - class←classes⍎t - :Else + :If ⎕NULL≡class←findClass type Warning'Unsupported class: ',type ⎕←Props →0 diff --git a/EWC/injectPlugin.aplf b/EWC/injectPlugin.aplf new file mode 100644 index 00000000..b6afaf03 --- /dev/null +++ b/EWC/injectPlugin.aplf @@ -0,0 +1,35 @@ + caller injectPlugin(name objname);sns;plugin;js;r;top +⍝ Send a plugin's JavaScript to this session's client, once per session. +⍝ +⍝ Called from ∆WC the first time one of the plugin's classes is created, before +⍝ that object's WC message goes out. dWX blocks until the client replies, and +⍝ the client holds every later frame until the injected code has resolved - so +⍝ the component is registered by the time the WC is handled. +⍝ +⍝ Load state lives in the session's own _EWC namespace: per clone in Multi mode, +⍝ per HTMLRenderer in Desktop, one in CODELOCATION otherwise. + sns←caller sessionNS objname + :If ⎕NULL≡sns ⋄ →0 ⋄ :EndIf ⍝ no client yet + :If 0=sns.⎕NC'PLUGINS_LOADED' ⋄ sns.PLUGINS_LOADED←⍬ ⋄ :EndIf + +⍝ Keyed by connection, not just name: outside Multi mode _EWC outlives any one +⍝ browser, and a reload gives the client a fresh, empty registry. Entries for +⍝ connections that have gone are dropped rather than accumulating. + sns.PLUGINS_LOADED←(sns.conn=⊃¨sns.PLUGINS_LOADED)/sns.PLUGINS_LOADED + :If (⊂sns.conn name)∊sns.PLUGINS_LOADED ⋄ →0 ⋄ :EndIf + :If 9≠PLUGINS.⎕NC name ⋄ →0 ⋄ :EndIf + + plugin←PLUGINS⍎name + sns.PLUGINS_LOADED,←⊂sns.conn name ⍝ marked before injecting: a failure must not retry per object + :If ~(⌊plugin.⎕NC'Inject')∊2 3 ⋄ →0 ⋄ :EndIf + + js←,⊆plugin.Inject ⍝ a niladic function, or the JavaScript itself + top←(¯1+objname⍳'.')↑objname + :Trap 6 85 + r←PLUGIN_TIMEOUT dWX(top 'EvalJS'),js + :If ∨/¯1=⊃¨,⊆r + 'E'Log'Plugin "',name,'" injection failed: ',⍕r + :EndIf + :Else + 'E'Log'Plugin "',name,'" injection got no response from the client' + :EndTrap diff --git a/EWC/sessionNS.aplf b/EWC/sessionNS.aplf new file mode 100644 index 00000000..f0c0b199 --- /dev/null +++ b/EWC/sessionNS.aplf @@ -0,0 +1,28 @@ + r←caller sessionNS names;name;top;n;p +⍝ The per-session _EWC namespace for an object name, or ⎕NULL if there is none. +⍝ +⍝ Desktop gives each Form's HTMLRenderer its own copy; Multi clones the whole +⍝ application namespace per connection; otherwise there is a single one, in +⍝ CODELOCATION. getConnection reads (ID conn) out of whatever this returns. + name←,⊃⊆names + n←≢p←(⍕caller),'.' + name←(n×p≡n↑name)↓name ⍝ drop caller prefix + top←(¯1+name⍳'.')↑name + + :If 9=caller.⎕NC n←top,'.HR._EWC' + r←caller⍎n + + :ElseIf MODE=2 ⍝ Multi + :If 9=caller.⎕NC'_EWC' + r←caller._EWC + :Else + r←⎕NULL + :EndIf + + :Else + :Trap 0 + r←CODELOCATION._EWC + :Else + r←⎕NULL + :EndTrap + :EndIf diff --git a/EWC/setDefaults.aplf b/EWC/setDefaults.aplf index 63ef6387..f356bb5a 100644 --- a/EWC/setDefaults.aplf +++ b/EWC/setDefaults.aplf @@ -10,3 +10,4 @@ setDefaults 'BREAK' default ⍬ ⍬ ⍝ IDs Events 'SHOWDEVTOOLS' default 0 'FONTMAP' default FontMap + 'PLUGIN_TIMEOUT' default 30 ⍝ Seconds to wait while a plugin's JS loads diff --git a/demo/DemoPluginRegistry.aplf b/demo/DemoPluginRegistry.aplf new file mode 100644 index 00000000..c2c925ae --- /dev/null +++ b/demo/DemoPluginRegistry.aplf @@ -0,0 +1,57 @@ + DemoPluginRegistry connected;plugin;cls;js;r +⍝ The plugin mechanism end to end, with no external dependency: an EWC class +⍝ registered at runtime, its React component injected as JavaScript the first +⍝ time the class is used, and an event travelling from that component to APL. + EWC.CONNECTED←connected + + CBPluginBox←{'F1.STATUS'eWS'Caption'('Select from ',(⊃⍵),' at ',⍕3↑3↓⎕TS)} + + 'F1'eWC'Form' 'Plugin registry'(50 50)(220 460)('Coord' 'Pixel') + 'F1.L1'eWC'Label' 'The box below is drawn by injected JavaScript.'('Posn' 10 10)('Size' 20 440) + 'F1.STATUS'eWC'Label' 'no event yet'('Posn' 170 10)('Size' 20 440) + + plugin←⎕NS'' + plugin.Name←'DemoBox' + plugin.Folder←EWC.FOLDER + + plugin.Classes←⎕NS'' + 'PluginBox'plugin.Classes.⎕NS'' + cls←plugin.Classes.PluginBox + cls.ClassName←'PluginBox' + cls.PropList←'Type' 'Posn' 'Size' 'Caption' 'Event' 'MethodList' 'ChildList' 'EventList' 'PropList' + cls.Defaults←('PluginBox')(10 10)(50 100)('')(⍬)(⍬)(⍬)(⍬)(cls.PropList) + cls.Supported←cls.PropList + cls.Dynamic←⍬ + cls.methodlist←⍬ + cls.SupportedEvents←,⊂'Select' + +⍝ The delay is deliberate: it proves the client waits for an asynchronous +⍝ injection before handling the WC that follows. + js←'(async () => {' + js,←' await new Promise(r => setTimeout(r, 250));' + js,←' const R = window.EWC.React;' + js,←' window.EWC.registerComponent("PluginBox", ({ data, ewc }) => {' + js,←' const P = data.Properties || {};' + js,←' const style = Object.assign({}, ewc.utils.setStyle(P), {' + js,←' background: "#cfe8ff", border: "2px solid #0066cc", borderRadius: "6px",' + js,←' display: "flex", alignItems: "center", justifyContent: "center",' + js,←' cursor: "pointer", userSelect: "none" });' + js,←' const fire = () => ewc.socket.send(JSON.stringify(' + js,←' { Event: { EventName: "Select", ID: data.ID } }));' + js,←' return R.createElement("div", { id: data.ID, style, onClick: fire }, P.Caption || "");' + js,←' });' + js,←' return "PluginBox registered";' + js,←'})()' + plugin.Inject←js + + EWC.RegisterPlugin plugin + + 'F1.BOX'eWC'PluginBox'('Posn' 60 10)('Size' 55 440)('Caption' 'Click me - I am a plugin')('Event' 'Select' 'CBPluginBox') + +⍝ A second object of the same class must reuse the loaded plugin: the log +⍝ should show one EvalJS, not two. + 'F1.BOX2'eWC'PluginBox'('Posn' 120 10)('Size' 40 440)('Caption' 'and so am I')('Event' 'Select' 'CBPluginBox') + +⍝ An injection that rejects must come back as ¯1, not hang until the timeout. + r←2 eNQ'F1' 'EvalJS' 'import("/no-such-plugin-bundle.js")' + 'F1.REJECT'eWC'Label'('Caption'('rejected import rc=',⍕⊃⊃,⊆r))('Posn' 195 10)('Size' 20 440) From f9c293b75f0bea2213867b4018262095345fe4a5 Mon Sep 17 00:00:00 2001 From: Neil Kirsopp Date: Fri, 7 Aug 2026 05:57:46 +0200 Subject: [PATCH 2/2] Plugin mechanism: survive registration before Init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registering a plugin before EWC.Init failed: the plugin path needs RESOURCES to mount against and LOGMODES to log through, and neither exists until Init runs setDefaults. ensurePlugins now calls setDefaults itself, which is safe because it only fills in names that are undefined. An application is also free to assign EWC.RESOURCES wholesale - demo/Run does exactly that - which silently dropped mounts made by a plugin that had registered earlier. Init re-applies every registered plugin's resources once RESOURCES has settled. Also stop ⎕SE.Link.Break's "Not linked" result printing to the session: it is a returned value, not an error, so it needs discarding. --- EWC/Init.aplf | 1 + EWC/RegisterPlugin.aplf | 10 ++-------- EWC/applyPluginResources.aplf | 12 ++++++++++++ EWC/applyResources.aplf | 9 +++++++++ EWC/ensurePlugins.aplf | 6 ++++++ 5 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 EWC/applyPluginResources.aplf create mode 100644 EWC/applyResources.aplf diff --git a/EWC/Init.aplf b/EWC/Init.aplf index e857a17f..d335742a 100644 --- a/EWC/Init.aplf +++ b/EWC/Init.aplf @@ -103,6 +103,7 @@ :EndIf WSS.HTMLFolder←JSClientFolder WSS.ShowHTMLRenderer←0 + applyPluginResources ⍝ RESOURCES may have been reassigned since a plugin registered WSS.Aliases←RESOURCES WSS.OnConnect←'' WSS.OnWSUpgrade←(⍕⎕THIS),'.newSession' diff --git a/EWC/RegisterPlugin.aplf b/EWC/RegisterPlugin.aplf index 1d06ed39..e24d7d58 100644 --- a/EWC/RegisterPlugin.aplf +++ b/EWC/RegisterPlugin.aplf @@ -1,4 +1,4 @@ - {r}←RegisterPlugin plugin;name;nm;target;res;alias;folder + {r}←RegisterPlugin plugin;name;nm;target ⍝ Register a plugin with EWC. Safe before or after Init. ⍝ ⍝ plugin is a namespace ref providing: @@ -25,12 +25,6 @@ :EndFor ensurePlugins ⍝ the class namespaces are new, so detach those too - :If 2=plugin.⎕NC'Resources' - res←plugin.Resources - :If 2≠⍴⍴res ⋄ res←1 2⍴res ⋄ :EndIf - :For (alias folder) :In ↓res - alias AddResource folder - :EndFor - :EndIf + applyResources plugin 'D'Log'Registered plugin "',name,'", classes: ',⍕plugin.Classes.⎕NL ¯9 diff --git a/EWC/applyPluginResources.aplf b/EWC/applyPluginResources.aplf new file mode 100644 index 00000000..a994546b --- /dev/null +++ b/EWC/applyPluginResources.aplf @@ -0,0 +1,12 @@ + applyPluginResources;nm +⍝ Re-mount every registered plugin's resources. +⍝ +⍝ RESOURCES is an ordinary variable and applications assign it wholesale +⍝ (demo/Run.aplf does), which would drop mounts made by a plugin that registered +⍝ before Init. Init calls this once RESOURCES has settled. + :If 9≠⎕NC'PLUGINS' ⋄ →0 ⋄ :EndIf + :For nm :In PLUGINS.⎕NL ¯9 + :If 'classes'≢nm + applyResources PLUGINS⍎nm + :EndIf + :EndFor diff --git a/EWC/applyResources.aplf b/EWC/applyResources.aplf new file mode 100644 index 00000000..0b3a8f48 --- /dev/null +++ b/EWC/applyResources.aplf @@ -0,0 +1,9 @@ + applyResources plugin;res;alias;folder +⍝ Mount whatever a plugin declared in Resources: an (alias folder) pair, or a +⍝ 2-column matrix of them. + :If 2≠plugin.⎕NC'Resources' ⋄ →0 ⋄ :EndIf + res←plugin.Resources + :If 2≠⍴⍴res ⋄ res←1 2⍴res ⋄ :EndIf + :For (alias folder) :In ↓res + alias AddResource folder + :EndFor diff --git a/EWC/ensurePlugins.aplf b/EWC/ensurePlugins.aplf index d7ec0ac7..ee303d1b 100644 --- a/EWC/ensurePlugins.aplf +++ b/EWC/ensurePlugins.aplf @@ -7,6 +7,12 @@ ⍝ single place that decides where the registry lives; if breaking a child of a ⍝ live link ever proves unreliable, move it out of #.EWC here and nothing else ⍝ needs to change - everything reaches it through the PLUGINS ref. +⍝ Registration is allowed before Init, so the globals the plugin path touches - +⍝ RESOURCES to mount against, LOGMODES to log through - may not exist yet. +⍝ setDefaults only fills in names that are undefined, so this cannot clobber +⍝ anything the application has already set. + setDefaults + :If 9≠⎕NC'PLUGINS' 'PLUGINS'⎕NS'' 'PLUGINS.classes'⎕NS''