[gimp] ScriptFu: Add script-fu-interpreter akin to other interpreters
- From: Lloyd Konneker <lkonneker src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] ScriptFu: Add script-fu-interpreter akin to other interpreters
- Date: Thu, 30 Jun 2022 15:22:40 +0000 (UTC)
commit d5a83429b464df4c87aaffe8779ddc3c570a0f49
Author: lloyd konneker <konnekerl gmail com>
Date: Sun Jun 19 16:03:06 2022 -0400
ScriptFu: Add script-fu-interpreter akin to other interpreters
Why:
1) users can install .scm scripts to plug-ins dir
2) Crashing scripts do not crash extension-script-fu
Scripts (.scm files) have a shebang and are executable
and in a same-named subdir of plugin dir.
Interpreter/scripts create PDB procs of type PLUGIN unlike extension-script-fu
which creates PDB procs of type TEMPORARY, owned by extension-script-fu.
Unlike other interpreters, the interpreter is-a plugin outright,
not by virtue of the script subclassing GimpPlugin and using GI.
More details in /plug-ins/script-fu/interpreter/README
build/windows/installer/gimp3264.iss | 72 +++++---
configure.ac | 1 +
plug-ins/script-fu/Makefile.am | 2 +-
plug-ins/script-fu/interpreter/.gitignore | 6 +
plug-ins/script-fu/interpreter/Makefile.am | 102 ++++++++++
plug-ins/script-fu/interpreter/README | 178 ++++++++++++++++++
plug-ins/script-fu/interpreter/meson.build | 42 +++++
.../interpreter/script-fu-interpreter-plugin.c | 140 ++++++++++++++
.../script-fu/interpreter/script-fu-interpreter.c | 192 +++++++++++++++++++
.../script-fu/interpreter/script-fu-interpreter.h | 29 +++
plug-ins/script-fu/libscriptfu/Makefile.am | 4 +-
plug-ins/script-fu/libscriptfu/meson.build | 1 +
plug-ins/script-fu/libscriptfu/script-fu-lib.c | 53 +++++-
plug-ins/script-fu/libscriptfu/script-fu-lib.h | 6 +
.../script-fu/libscriptfu/script-fu-proc-factory.c | 205 +++++++++++++++++++++
.../script-fu/libscriptfu/script-fu-proc-factory.h | 27 +++
plug-ins/script-fu/libscriptfu/script-fu-script.c | 46 ++++-
plug-ins/script-fu/libscriptfu/script-fu-script.h | 6 +
plug-ins/script-fu/libscriptfu/script-fu-scripts.c | 154 ++++++++++++----
plug-ins/script-fu/libscriptfu/script-fu-scripts.h | 8 +
plug-ins/script-fu/libscriptfu/script-fu-types.h | 5 +
plug-ins/script-fu/libscriptfu/script-fu.def | 2 +
plug-ins/script-fu/meson.build | 1 +
plug-ins/script-fu/scripts/Makefile.am | 12 +-
plug-ins/script-fu/scripts/meson.build | 21 ++-
plug-ins/script-fu/scripts/test/README | 5 +
plug-ins/script-fu/scripts/test/test0/test0.scm | 31 ++++
plug-ins/script-fu/scripts/test/test1/test1.scm | 43 +++++
plug-ins/script-fu/scripts/test/test1/test3.scm | 31 ++++
plug-ins/script-fu/scripts/test/test4/test4.scm | 25 +++
plug-ins/script-fu/scripts/test/test5/test5.scm | 16 ++
plug-ins/script-fu/scripts/test/test6/test6.scm | 12 ++
plug-ins/script-fu/scripts/test/test7/test7.scm | 28 +++
plug-ins/script-fu/scripts/test/test8/test8.scm | 39 ++++
plug-ins/script-fu/scripts/ts-helloworld.scm | 2 +
35 files changed, 1473 insertions(+), 74 deletions(-)
---
diff --git a/build/windows/installer/gimp3264.iss b/build/windows/installer/gimp3264.iss
index ebeaf320fe..7b90639c3d 100755
--- a/build/windows/installer/gimp3264.iss
+++ b/build/windows/installer/gimp3264.iss
@@ -25,7 +25,7 @@
;Install script for GIMP and GTK+
;requires Inno Setup 6
;
-;See directories.isi
+;See directories.isi
;
;Changelog:
;
@@ -751,7 +751,7 @@ begin
begin
DebugMsg('DoConfigOverride', 'First call');
-
+
Result := False;
ConfigOverride := coDontOverride;
@@ -819,7 +819,7 @@ begin
'/usr/bin/python=' + ExpandConstant('{app}\bin\{#PYTHON}') + #10 +
'/usr/bin/python3=' + ExpandConstant('{app}\bin\{#PYTHON}') + #10 +
':Python:E::py::python:'#10;
-
+
if not SaveStringToUTF8File(InterpFile,InterpContent,False) then
begin
DebugMsg('PrepareInterp','Problem writing the file. [' + InterpContent + ']');
@@ -840,7 +840,7 @@ begin
'/usr/bin/luajit=' + ExpandConstant('{app}\bin\luajit.exe') + #10 +
'/usr/bin/lua=' + ExpandConstant('{app}\bin\luajit.exe') + #10 +
':Lua:E::lua::luajit:'#10;
-
+
if not SaveStringToUTF8File(InterpFile,InterpContent,False) then
begin
DebugMsg('PrepareInterp','Problem writing the file. [' + InterpContent + ']');
@@ -848,13 +848,32 @@ begin
end;
end;
#endif
+
+// not optional
+// !!! use comma for binfmt delimiter and full Windows path in interpreter field of binfmt
+begin
+ InterpFile :=
ExpandConstant('{app}\lib\gimp\{#DIR_VER}\interpreters\gimp-script-fu-interpreter.interp');
+ DebugMsg('PrepareInterp','Writing interpreter file for gimp-script-fu-interpreter: '
+ InterpFile);
+
+ InterpContent := 'gimp-script-fu-interpreter=' +
ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + #10 +
+ 'gimp-script-fu-interpreter-3.0=' +
ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + #10 +
+ '/usr/bin/gimp-script-fu-interpreter=' +
ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + #10 +
+ ',ScriptFu,E,,scm,,' +
ExpandConstant('{app}\bin\gimp-script-fu-interpreter-3.0.exe') + ','#10;
+
+ if not SaveStringToUTF8File(InterpFile,InterpContent,False) then
+ begin
+ DebugMsg('PrepareInterp','Problem writing the file. [' + InterpContent + ']');
+ SuppressibleMsgBox(CustomMessage('ErrorUpdatingScriptFu') + ' (2)',mbInformation,mb_ok,IDOK);
+ end;
+end;
+
end;
procedure PrepareGimpEnvironment();
var EnvFile,Env: String;
begin
-
+
StatusLabel(CustomMessage('SettingUpEnvironment'),'');
//set PATH to be used by plug-ins
@@ -862,7 +881,7 @@ begin
DebugMsg('PrepareGimpEnvironment','Setting environment in ' + EnvFile);
Env := #10'PATH=${gimp_installation_dir}\bin';
-
+
if IsComponentSelected('gimp32on64') then
begin
@@ -941,7 +960,7 @@ procedure CustomizeOnClick(Sender: TObject);
begin
DebugMsg('Install mode','Custom');
InstallMode := imCustom;
-
+
CleanUpCustomWelcome();
WizardForm.NextButton.OnClick(TNewButton(Sender).Parent);
@@ -1044,9 +1063,9 @@ begin
else
exit;
DebugMsg('SelectComponentsFaceLift','2');
-
+
WizardForm.ComponentsList.OnClick := @ComponentsListOnClick;
-
+
lblDescription := TNewStaticText.Create(WizardForm.ComponentsList.Parent)
with lblDescription do
begin
@@ -1055,7 +1074,7 @@ begin
AutoSize := True;
Caption := CustomMessage('ComponentsDescription');
end;
-
+
pnlDescription := TPanel.Create(WizardForm.ComponentsList.Parent);
with pnlDescription do
begin
@@ -1069,7 +1088,7 @@ begin
end;
lblDescription.Parent := WizardForm.ComponentsList.Parent; //place lblDescription above pnlDescription
-
+
lblComponentDescription := TNewStaticText.Create(pnlDescription);
with lblComponentDescription do
begin
@@ -1081,7 +1100,7 @@ begin
Height := Parent.Height - ScaleY(20);
Top := ScaleY(12);
end;
-
+
end;
@@ -1090,7 +1109,7 @@ var rtfNewReadyMemo: TRichEditViewer;
begin
DebugMsg('ReadyFaceLift','');
WizardForm.ReadyMemo.Visible := False;
-
+
rtfNewReadyMemo := TRichEditViewer.Create(WizardForm.ReadyMemo.Parent);
with rtfNewReadyMemo do
begin
@@ -1214,7 +1233,7 @@ begin
Components := ['Gimp','Deps','Debug','Translations','MyPaint','Python','Ghostscript','Lua','Gimp32'];
ComponentDesc := '';
-
+
for i := 0 to TNewCheckListBox(pSender).Items.Count - 1 do
if TNewCheckListBox(pSender).Selected[i] then
begin
@@ -1277,7 +1296,7 @@ var sText: String;
begin
DebugMsg('UpdateReadyMemo','');
(* Prepare the text for new Ready Memo *)
-
+
sText := RTFHeader;
if pMemoDirInfo <> '' then
sText := sText + ParseReadyMemoText(pSpace,pMemoDirInfo) + '\sb100';
@@ -1286,7 +1305,7 @@ begin
If pMemoTasksInfo<>'' then
sText := sText + '\sb100' + ParseReadyMemoText(pSpace,pMemoTasksInfo);
-
+
ReadyMemoRichText := Copy(sText,1,Length(sText)-6) + '}';
Result := 'If you see this, something went wrong';
@@ -1299,7 +1318,7 @@ var NewBitmap1,NewBitmap2: TFileStream;
begin
WelcomeBitmapBottom := TBitmapImage.Create(WizardForm);
with WelcomeBitmapBottom do
- begin
+ begin
Left := 0;
Top := 0;
Parent := WizardForm;
@@ -1309,7 +1328,7 @@ begin
end;
DebugMsg('UpdateWizardImages','Height: ' + IntToStr(WizardForm.WizardBitmapImage.Height));
-
+
if WizardForm.WizardBitmapImage.Height < 386 then //use smaller image when not using Large Fonts
begin
try
@@ -1375,7 +1394,7 @@ begin
DebugMsg('DoUninstall','Install directory doesn'#39't exist: ' + InstallDir + ',
resuming install');
oResult := InResult
end else
- begin
+ begin
oResult := rogUninstallFailed;
end;
@@ -1472,7 +1491,7 @@ begin
UninstallString := UninstallString + ' /NORESTART';
DoUninstall(UninstallString, OldPath, lblInfo2, Result);
-
+
end;
end;
@@ -1509,7 +1528,7 @@ begin
end;
-procedure CurPageChanged(pCurPageID: Integer);
+procedure CurPageChanged(pCurPageID: Integer);
begin
DebugMsg('CurPageChanged','ID: '+IntToStr(pCurPageID));
case pCurPageID of
@@ -1678,15 +1697,15 @@ begin
SetArrayLength(Buttons,2);
Message[0] := CustomMessage('Require32BPP');
Buttons[0] := CustomMessage('Require32BPPContinue');
- Buttons[1] := CustomMessage('Require32BPPExit');
- if (not WizardSilent) and
+ Buttons[1] := CustomMessage('Require32BPPExit');
+ if (not WizardSilent) and
(MessageWithURL(Message, CustomMessage('Require32BPPTitle'), Buttons, mbError, 2, 2) = 2)
then
Result := False
else
Result := True;
end
else
- Result := True;
+ Result := True;
end;
@@ -1733,7 +1752,7 @@ begin
Result := RestartSetupAfterReboot(); //resume install after reboot - skip all setting pages, and
install directly
if Result then
- Result := BPPTooLowWarning();
+ Result := BPPTooLowWarning();
if not Result then //no need to do anything else
exit;
@@ -1743,7 +1762,7 @@ begin
SetArrayLength(Buttons,2);
Buttons[0] := CustomMessage('DevelopmentButtonContinue');
Buttons[1] := CustomMessage('DevelopmentButtonExit');
- if (not WizardSilent) and
+ if (not WizardSilent) and
(MessageWithURL(Message, CustomMessage('DevelopmentWarningTitle'), Buttons, mbError, 2, 2) = 2)
then
begin
Result := False;
@@ -1810,4 +1829,3 @@ end;
#include "uninst.isi"
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
-
diff --git a/configure.ac b/configure.ac
index 00e1f1e612..e370c1319c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3106,6 +3106,7 @@ plug-ins/script-fu/libscriptfu/Makefile
plug-ins/script-fu/libscriptfu/ftx/Makefile
plug-ins/script-fu/libscriptfu/tinyscheme/Makefile
plug-ins/script-fu/server/Makefile
+plug-ins/script-fu/interpreter/Makefile
plug-ins/script-fu/scripts/Makefile
plug-ins/script-fu/scripts/images/Makefile
plug-ins/selection-to-path/Makefile
diff --git a/plug-ins/script-fu/Makefile.am b/plug-ins/script-fu/Makefile.am
index cb6def24e5..af6d5c8632 100644
--- a/plug-ins/script-fu/Makefile.am
+++ b/plug-ins/script-fu/Makefile.am
@@ -49,7 +49,7 @@ AM_LDFLAGS = \
$(framework_cocoa) \
$(xnone)
-SUBDIRS = libscriptfu scripts server
+SUBDIRS = libscriptfu scripts server interpreter
# Each plugin must be installed in a dir of the same name as the executable
script_fudir = $(gimpplugindir)/plug-ins/script-fu
diff --git a/plug-ins/script-fu/interpreter/.gitignore b/plug-ins/script-fu/interpreter/.gitignore
new file mode 100644
index 0000000000..361a0bb007
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/.gitignore
@@ -0,0 +1,6 @@
+/Makefile.in
+/Makefile
+/.deps
+/_libs
+/.libs
+/script-fu-interpreter.exe
diff --git a/plug-ins/script-fu/interpreter/Makefile.am b/plug-ins/script-fu/interpreter/Makefile.am
new file mode 100644
index 0000000000..d957c3acf8
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/Makefile.am
@@ -0,0 +1,102 @@
+## Process this file with automake to produce Makefile.in
+
+# derived from app/Makefile.am, another installed binary
+
+if PLATFORM_OSX
+xobjective_c = "-xobjective-c"
+xobjective_cxx = "-xobjective-c++"
+xnone = "-xnone"
+framework_cocoa = -framework Cocoa
+endif
+
+if OS_WIN32
+win32_ldflags = -mwindows -Wl,--tsaware $(WIN32_LARGE_ADDRESS_AWARE)
+
+if HAVE_EXCHNDL
+exchndl = -lexchndl
+endif
+
+else
+libm = -lm
+endif
+
+if ENABLE_RELOCATABLE_RESOURCES
+munix = -Wl,-rpath '-Wl,$$ORIGIN/../lib'
+endif
+
+if HAVE_WINDRES
+include $(top_srcdir)/build/windows/gimprc-plug-ins.rule
+script_fu_interpreter_RC = script-fu-interpreter.rc.o
+endif
+
+libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
+libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
+libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
+libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
+libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
+libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
+libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la $(libm)
+
+# link to libscriptfu
+libgimp_scriptfu = $(top_builddir)/plug-ins/script-fu/libscriptfu/libgimp-scriptfu-$(GIMP_API_VERSION).la
+
+# include srcdir parent to find libscriptfu include files
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ $(GTK_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ -I$(includedir) \
+ -I$(srcdir)/.. \
+ -DG_LOG_DOMAIN=\"scriptfu\"
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(munix) \
+ $(win32_ldflags) \
+ $(framework_cocoa) \
+ $(xnone)
+
+# interpreter is-a plugin but is-a bin_PROGRAM
+# i.e. installs to usual place of executables e.g. /usr/bin so shebangs find it.
+bin_PROGRAMS = gimp-script-fu-interpreter-@GIMP_API_VERSION@
+
+gimp_script_fu_interpreter_@GIMP_API_VERSION@_SOURCES = \
+ script-fu-interpreter.c \
+ script-fu-interpreter.h \
+ script-fu-interpreter-plugin.c
+
+# link with libgimp-scriptfu
+# link with the usual gimp suspects
+
+gimp_script_fu_interpreter_@GIMP_API_VERSION@_LDADD = \
+ $(libgimp_scriptfu) \
+ $(libgimpmath) \
+ $(libgimp) \
+ $(libgimpbase) \
+ $(libgimpui) \
+ $(libgimpwidgets) \
+ $(libgimpconfig) \
+ $(libgimpcolor) \
+ $(GTK_LIBS) \
+ $(GTK_MAC_INTEGRATION_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS) \
+ $(exchndl) \
+ $(script_fu_interpreter_RC)
+
+install-exec-hook:
+if DEFAULT_BINARY
+ cd $(DESTDIR)$(bindir) \
+ && rm -f gimp-script-fu-interpreter$(EXEEXT) \
+ && $(LN_S) gimp-script-fu-interpreter-$(GIMP_APP_VERSION)$(EXEEXT) gimp-script-fu-interpreter$(EXEEXT)
+endif
+
+uninstall-local:
+if DEFAULT_BINARY
+ rm -f $(DESTDIR)$(bindir)/gimp-script-fu-interpreter$(EXEEXT)
+endif
diff --git a/plug-ins/script-fu/interpreter/README b/plug-ins/script-fu/interpreter/README
new file mode 100644
index 0000000000..370077aa4f
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/README
@@ -0,0 +1,178 @@
+# About script-fu-interpreter
+
+It is GIMP's Scheme interpreter akin to any other language interpreter,
+rather than a GIMP extension as is the plugin extension-script-fu.
+This interpreter (like all the ScriptFu plugins) embeds a TinyScheme interpreter.
+
+It is an executable program that is passed the name of a .scm file.
+The combination defines one or more PDB procedures of type PLUGIN.
+
+Differences from extension-script-fu
+====================================
+
+Since gimp-script-fu-interpreter and extension-script-fu use the same
+embedded interpreter (they both wrap TinyScheme)
+there is no difference in the language interpreted.
+Any differences are in the larger behavior of plugins.
+
+### PDB Procedure type
+
+Defines PDB procedure(s) of type PLUGIN.
+Unlike for the scriptfu extension,
+where a .scm file defines PDB procedure(s) of type TEMPORARY
+(owned by the PDB procedure of type PLUGIN named extension-script-fu)
+
+### Protocol
+
+Uses protocol to GIMP like other interpreters e.g. Python
+(query, create, and run phases.)
+The protocol to GIMP is unlike the protocol to the GIMP extension extension-script-fu.
+
+(Note that "extension" has many meanings. It can denote: a protocol,
+or "suffix of a filename", or "a resource that extends GIMP."
+extension-script-fu is "a resource that extends GIMP" and it also uses
+the "extension" protocol,
+while gimp-script-fu-interpreter is a "a resource that extends GIMP" but uses
+the "plugin" protocol.)
+
+### Process lifetimes
+
+Executed many times, for many phases,
+unlike extension-script-fu which stays executing and gets a remote procedure call
+from GIMP to run a PDB procedure.
+
+### Process concurrency
+
+Each invocation of a plugin is in a separate process.
+One plugin process crash does not affect others.
+Unlike extension-script-fu, where a crash means the GIMP app must be restarted
+to restart extension-script-fu.
+
+### GUI concurrency
+
+Each plugin can have its own GUI visible concurrently
+with the GUI of other ScriptFu plugins.
+
+For extension-script-fu, an open dialog
+prevents other plugins in /scripts (implemented by extension-script-fu)
+from opening a dialog.
+Instead extension-script-fu opens such dialogs sequentially.
+
+This difference is not very important,
+since most users work sequentially.
+Most dialogs for plugins do not do anything substantive
+until a user closes the dialog with the OK button.
+
+### Calls between scripts
+
+In extension-script-fu, a call to another PDB procedure
+implemented by TEMPORARY procedure owned by extension-script-fu
+does not leave the process.
+
+In gimp-script-fu-interpreter, a call to another PDB procedure
+implemented in another plugin file starts another process.
+
+For other plugins, most calls to another PDB procedure starts another process.
+The exception is when one plugin file implements many PDB procedures.
+One common case is when one plugin file implements its own TEMPORARY PDB procedures that
+exist only for the duration of the plugin's lifetime.
+
+Naming
+======
+
+script-fu-interpreter is the informal name.
+
+Source is located in /plug-ins/script-fu/interpreter
+
+Filename of the executable is gimp-script-fu-interpreter-3.0.
+The name is versioned by a number corresponding to the API
+and the major version of GIMP (when script-fu-interpreter was introduced).
+We expect plugin authors to be insulated from changes to script-fu-interpreter,
+for the duration of the GIMP 3 version.
+
+
+About .scm scripts for script-fu-interpreter
+============================================
+
+The contents of a .scm file queried by script-fu-interpreter
+are the same as those handled by extension-script-fu
+except for the addition of a shebang:
+
+ #!/usr/bin/env gimp-script-fu-interpreter-3.0
+ (define (script-fu-test img drawable)
+ ...
+ (script-fu-register "script-fu-test"
+ ...
+
+### Query of scripts
+
+As for other interpreters, a plugin script file must have certain attributes
+to be queried by GIMP. But a queried file may define many PDB procedures.
+
+A .scm file queried by script-fu-interpreter:
+
+- must have permission to execute.
+- must be in a directory names the same as the file's base name (less suffix.)
+
+A directory containing an .scm file queried by script-fu-interpreter
+is usually a subdirectory of one of the /plug-ins directories,
+unlike for extension-script-fu, where the .scm files are in a /scripts dir.
+
+A plugin directory should contain only one queriable .scm file:
+only one file can have the same base name as the directory and
+GIMP will query that file.
+
+### Defining many PDB procedures per directory
+
+When GIMP queries, script-fu-interpreter will load *ALL* the .scm files
+in the same directory (regardless of shebang or execute permission.)
+Any of the .scm files can declare and register a PDB procedure.
+Any single .scm file can declare and register many PDB procedures.
+
+Similarly, when GIMP runs a named PDB procedure defined in an .scm file,
+script-fu-interpreter will actually load *ALL* the .scm files
+in the same directory, but run only the define run function so named.
+
+A plugin directory that contains many .scm files having a shebang
+will also work, since only one can be named the same as the parent directory,
+and GIMP will only query it,
+but find the other .scm files with a shebang.
+
+### Requery of scripts
+
+As with other plugins, GIMP caches plugin definitions between sessions.
+GIMP queries plugin files at startup.
+GIMP will not requery any plugin files which have not changed since cached.
+GIMP will not query a plugin file that is added to the file system
+until GIMP is restarted.
+(Filters>Development>ScriptFu>Refresh Scripts will not requery script files
+handled by gimp-script-fu-interpreter, only those handled by extension-script-fu.)
+
+### Errors during query
+
+Most errors occurring during query appear only in the console.
+If you are a plugin author, you should start GIMP in a console
+so you can see such errors.
+
+A script may be malformed because it does not define
+a "run" function having the same name as the PDB procedure name declared
+in the call to script-fu-register().
+Formerly, such a malformed script was successfully queried but
+the script would throw a "undefined variable" error at run time.
+Now, such a malformed script is not queried successfully,
+but throws a warning to the console:
+"Run function not defined, or does not match PDB procedure name:"
+
+
+
+Test scripts
+============
+
+Small test scripts for gimp-script-fu-interpreter
+are located in /plug-ins/script-fu/scripts/test
+
+The script ts-helloworld.scm,
+formerly installed in /scripts and handled by extension-script-fu,
+is now installed in /plug-ins/ts-helloworld/ts-helloworld.scm
+and is now handled by gimp-script-fu-interpreter.
+It appears as "Filters>Development>Script-Fu>Test>Hello World..."
diff --git a/plug-ins/script-fu/interpreter/meson.build b/plug-ins/script-fu/interpreter/meson.build
new file mode 100644
index 0000000000..b02523d104
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/meson.build
@@ -0,0 +1,42 @@
+
+scriptfuInclude = include_directories('..')
+
+executable_name = 'gimp-script-fu-interpreter-' + gimp_api_version
+
+plugin_sources = [
+ 'script-fu-interpreter.c',
+ 'script-fu-interpreter-plugin.c',
+]
+
+if platform_windows
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+ '--define', 'ORIGINALFILENAME_STR="@0@"'.format(executable_name+'.exe'),
+ '--define', 'INTERNALNAME_STR="@0@"' .format(executable_name),
+ '--define', 'TOP_SRCDIR="@0@"' .format(meson.source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+ ],
+ )
+endif
+
+# !!! Installs as a usual binary say to /usr/bin, unlike extension-script-fu
+# GIMP queries scripts with shebangs, which invokes gimp-script-fu-interpreter-3.0.
+
+executable(executable_name,
+ plugin_sources,
+ dependencies: [
+ libgimpui_dep,
+ math,
+ ],
+ c_args: [
+ '-DG_LOG_DOMAIN="scriptfu"',
+ ],
+ include_directories: [
+ scriptfuInclude,
+ ],
+ link_with : libscriptfu,
+ install: true,
+)
diff --git a/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c
b/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c
new file mode 100644
index 0000000000..1ea2492397
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c
@@ -0,0 +1,140 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file understands how to make a GimpPlugin of the interpreter.
+ * This is mostly boilerplate for any plugin.
+ * It understands little about ScriptFu internals,
+ * hidden by script-fu-interpreter.[ch] and libscriptfu.
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <libgimp/gimp.h>
+
+#include "libscriptfu/script-fu-intl.h"
+
+#include "script-fu-interpreter.h"
+
+
+/* ScriptFuInterpreter subclasses GimpPlugIn */
+
+#define SCRIPT_FU_INTERPRETER_TYPE (script_fu_interpreter_get_type ())
+G_DECLARE_FINAL_TYPE (ScriptFuInterpreter, script_fu_interpreter, SCRIPT, FU_INTERPRETER, GimpPlugIn)
+
+struct _ScriptFuInterpreter
+{
+ GimpPlugIn parent_instance;
+};
+
+static GList * script_fu_interpreter_query_procedures (GimpPlugIn *plug_in);
+static GimpProcedure * script_fu_interpreter_create_procedure (GimpPlugIn *plug_in,
+ const gchar *name);
+
+G_DEFINE_TYPE (ScriptFuInterpreter, script_fu_interpreter, GIMP_TYPE_PLUG_IN)
+
+/* An alias to argv[1], which is the path to the .scm file.
+ * Each instance of ScriptFuInterpreter is specialized by the script passed in argv[1]
+ * This var need not belong to the class or to an instance,
+ * because there will only be one instance of ScriptFuInterpreter per plugin process.
+ */
+static gchar * path_to_this_script;
+
+/* Connect to Gimp. See libgimp/gimp.c.
+ *
+ * Can't use GIMP_MAIN macro, it doesn't omit argv[0].
+ *
+ * First arg is app cited in the shebang.
+ * Second arg is the .scm file containing the shebang.
+ * Second to last arg is the "phase" e.g. -query or -run
+ * Last arg is the mode for crash dumps.
+ * Typical argv:
+ * gimp-script-fu-interpreter-3.0 ~/.config/GIMP/2.99/plug-ins/fu/fu
+ * -gimp 270 12 11 -query 1
+ */
+int main (int argc, char *argv[])
+{
+ g_debug ("Enter script-fu-interpreter main");
+
+ /* Alias path to this plugin's script file. */
+ path_to_this_script = argv[1];
+
+ /* gimp_main will create an instance of the class given by the first arg, a GType.
+ * The class is a subclass of GimpPlugIn (with overridden query and create methods.)
+ * GIMP will subsequently callback the query or create methods,
+ * or the run_func of the PDB procedure of the plugin,
+ * depending on the "phase" arg in argv,
+ * which is set by the gimp plugin manager, which is invoking this interpreter.
+ */
+ /* Omit argv[0] when passing to gimp */
+ gimp_main (SCRIPT_FU_INTERPRETER_TYPE, argc-1, &argv[1] );
+
+ g_debug ("Exit script-fu-interpreter.");
+}
+
+DEFINE_STD_SET_I18N
+
+static void
+script_fu_interpreter_class_init (ScriptFuInterpreterClass *klass)
+{
+ GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
+
+ plug_in_class->query_procedures = script_fu_interpreter_query_procedures;
+ plug_in_class->create_procedure = script_fu_interpreter_create_procedure;
+ plug_in_class->set_i18n = STD_SET_I18N;
+}
+
+
+/* called by the GType system to initialize instance of the class. */
+static void
+script_fu_interpreter_init (ScriptFuInterpreter *script_fu)
+{
+ /* Nothing to do. */
+}
+
+
+/* Return the names of PDB procedures implemented. A callback from GIMP. */
+static GList *
+script_fu_interpreter_query_procedures (GimpPlugIn *plug_in)
+{
+ GList *result = NULL;
+
+ g_debug ("queried");
+
+ result = script_fu_interpreter_list_defined_proc_names (plug_in, path_to_this_script);
+ if (g_list_length (result) < 1)
+ g_warning ("No procedures defined in %s", path_to_this_script);
+
+ /* Caller is GIMP and it will free the list. */
+ return result;
+}
+
+
+/* Create and return a GimpPDBProcedure,
+ * for the named one of the PDB procedures that the script implements.
+ * A callback from GIMP.
+ *
+ * Also set attributes on the procedure, most importantly, menu items (optional.)
+ * Also create any menus/submenus that the script defines e.g. Filters>My
+ */
+static GimpProcedure *
+script_fu_interpreter_create_procedure (GimpPlugIn *plug_in,
+ const gchar *proc_name)
+{
+ return script_fu_interpreter_create_proc_at_path (plug_in,
+ proc_name,
+ path_to_this_script);
+}
diff --git a/plug-ins/script-fu/interpreter/script-fu-interpreter.c
b/plug-ins/script-fu/interpreter/script-fu-interpreter.c
new file mode 100644
index 0000000000..4113565d30
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/script-fu-interpreter.c
@@ -0,0 +1,192 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <libgimp/gimp.h>
+
+#include "libscriptfu/script-fu-lib.h"
+
+#include "script-fu-interpreter.h"
+
+/* Implementation of the outer ScriptFuInterpreter.
+ * This understands ScriptFu internals
+ * i.e. uses libscriptfu shared with other ScriptFu plugins e.g. extension-script-fu.
+ */
+
+/* We don't need to load (into the interpreter) any .scm files handled by extension-script-fu.
+ * Namely, the .scm in the GIMP installation /scripts or in the user local /scripts dirs.
+ *
+ * During startup, GIMP might call gimp-script-fu-interpreter
+ * to query new files such as /plug-ins/fu/fu.scm.
+ * This is before extension-script-fu starts.
+ * But all the .scm files handled by extension-script-fu are type TEMPORARY and not needed
+ * for a /plug-ins/fu.scm to be queried.
+ * The only Scheme executed during query are calls to script-fu-register.
+ * Later, when a /plug-ins/fu.scm is run, it can call temporary PDB procedures
+ * that extension-script-fu provides.
+ *
+ * When we call script_fu_init_embedded_interpreter(),
+ * the passed paths should include the path to /scripts
+ * because that is the location of scripts for initialization and compatibility
+ * (script-fu.init, plug-in-compat.init and script-fu-compat.init,
+ * which are really scheme files.)
+ *
+ * scrip-fu-interpreter always inits embedded interpreter(allow_register=TRUE)
+ * In the "run" phase, you don't need script-fu-register to be defined, but its harmless.
+ */
+
+static GFile *script_fu_get_plugin_parent_path (const gchar *path_to_this_script);
+static void script_fu_free_path_list (GList **list);
+
+
+/* Return a list of PDB procedure names defined in all .scm files in
+ * the parent dir of the given path, which is a filename of the one being queried.
+ *
+ * Each .scm file may contain many calls to script-fu-register, which defines a PDB procedure.
+ * All .scm files in the parent dir are searched.
+ *
+ * This executable is named script-fu-interpreter
+ * but no PDB procedure is named "script-fu-interpreter".
+ * Instead, the interpreter registers PDB procs named from name strings
+ * give in the in script-fu-register() calls in the interpreted scripts.
+ *
+ * Caller must free the list.
+ */
+GList *
+script_fu_interpreter_list_defined_proc_names (GimpPlugIn *plug_in,
+ const gchar *path_to_this_script)
+{
+ GList *name_list = NULL; /* list of strings */
+ GList *path_list = NULL; /* list of GFile */
+
+ /* path_list is /scripts dir etc. from which we will load compat and init scripts.
+ * second argument TRUE means define script-fu-register into the interpreter.
+ */
+ path_list = script_fu_search_path ();
+ script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE);
+ script_fu_free_path_list (&path_list);
+
+ /* Reuse path_list, now a list of one path, the parent dir of the queried script. */
+ path_list = g_list_append (path_list,
+ script_fu_get_plugin_parent_path (path_to_this_script));
+ name_list = script_fu_find_scripts_list_proc_names (plug_in, path_list);
+ script_fu_free_path_list (&path_list);
+
+ /* Usually name_list is not NULL i.e. not empty.
+ * But an .scm file that is not an actual GIMP plugin, or broken, may yield empty list.
+ */
+ return name_list;
+}
+
+
+/* Create a PDB proc of type PLUGIN with the given name.
+ * Unlike extension-script-fu, create proc of type PLUGIN.
+ *
+ * We are in "create procedure" phase of call from GIMP.
+ * Create a PDB procedure that the script-fu-interpreter wraps.
+ *
+ * A GimpPDBProcedure has a run function, here script_fu_script_proc()
+ * of this outer interpreter.
+ * Sometime after the create, GIMP calls the run func, passing a name aka command.
+ * In ScriptFu, the same name is used for the PDB proc and the Scheme function
+ * which is the inner run func defined in the script.
+ * script_fu_script_proc calls the TinyScheme interpreter to evaluate
+ * the inner run func in the script.
+ */
+GimpProcedure *
+script_fu_interpreter_create_proc_at_path (GimpPlugIn *plug_in,
+ const gchar *proc_name,
+ const gchar *path_to_this_script
+ )
+{
+ GimpProcedure *procedure = NULL;
+ GList *path_list = NULL; /* list of GFile */
+
+ g_debug ("script_fu_interpreter_create_proc_at_path, name: %s", proc_name);
+
+ /* Require proc_name is a suitable name for a PDB procedure eg "script-fu-test".
+ * (Not tested for canonical name "script-fu-<something>")
+ * Require proc_name is a name that was queried earlier.
+ * Require the proc_name was defined in some .scm file
+ * in the same directory as the .scm file passed as argv[0].
+ * The name of the .scm file eg "/plug-ins/fu/fu.scm"
+ * can be entirely different from proc_name.
+ *
+ * Otherwise, we simply won't find the proc_name defined in any .scm file,
+ * and will fail gracefully, returning NULL.
+ */
+
+ path_list = script_fu_search_path ();
+ path_list = g_list_append (path_list,
+ script_fu_get_plugin_parent_path (path_to_this_script));
+ /* path_list are the /scripts dir, for .init and compat.scm, plus the path to this.
+ * second arg TRUE means define script-fu-register so it is effective.
+ */
+ script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE);
+
+ /* Reuse path_list, now a list of only the path to this script. */
+ script_fu_free_path_list (&path_list);
+ path_list = g_list_append (path_list,
+ script_fu_get_plugin_parent_path (path_to_this_script));
+
+ procedure = script_fu_find_scripts_create_PDB_proc_plugin (plug_in, path_list, proc_name);
+ script_fu_free_path_list (&path_list);
+
+ /* When procedure is not NULL, assert:
+ * some .scm was evaluated.
+ * the script defined many PDB procedures locally, i.e. in script-tree
+ * we created a single PDB procedure (but not put it in the GIMP PDB)
+ *
+ * Ensure procedure is-a GimpProcedure or NULL.
+ * GIMP is the caller and will put non-NULL procedure in the PDB.
+ */
+ return procedure;
+}
+
+
+/* Return GFile of the parent directory of this plugin, whose filename is given.
+ *
+ * Caller must free the GFile.
+ */
+static GFile *
+script_fu_get_plugin_parent_path (const gchar *path_to_this_script)
+{
+ GFile *path = NULL;
+ GFile *parent_path = NULL;
+
+ /* A libgimp GimpPlugin does not know its path,
+ * but its path was passed in argv to this interpreter.
+ * The path is to a file being queried e.g. "~/.config/GIMP/2.99/plug-ins/fu/fu.scm"
+ */
+ g_debug ("path to this plugin %s", path_to_this_script);
+ path = g_file_new_for_path (path_to_this_script);
+ parent_path = g_file_get_parent (path);
+ g_object_unref (path);
+ return parent_path;
+}
+
+/* Free a list of paths at the given handle.
+ * Ensures that the pointer to the list is NULL, prevents "dangling."
+ * g_list_free_full alone does not do that.
+ */
+static void
+script_fu_free_path_list (GList **list)
+{
+ /* !!! g_steal_pointer takes a handle. */
+ g_list_free_full (g_steal_pointer (list), g_object_unref);
+}
diff --git a/plug-ins/script-fu/interpreter/script-fu-interpreter.h
b/plug-ins/script-fu/interpreter/script-fu-interpreter.h
new file mode 100644
index 0000000000..ec48ce52fb
--- /dev/null
+++ b/plug-ins/script-fu/interpreter/script-fu-interpreter.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SCRIPT_FU_INTERPRETER_H__
+#define __SCRIPT_FU_INTERPRETER_H__
+
+GList *script_fu_interpreter_list_defined_proc_names (
+ GimpPlugIn *plug_in,
+ const gchar *path_to_this_plugin);
+GimpProcedure *script_fu_interpreter_create_proc_at_path (
+ GimpPlugIn *plug_in,
+ const gchar *proc_name,
+ const gchar *path_to_this_script);
+
+#endif /* __SCRIPT_FU_INTERPRETER_H__ */
diff --git a/plug-ins/script-fu/libscriptfu/Makefile.am b/plug-ins/script-fu/libscriptfu/Makefile.am
index 4f8c329a4f..3800578fe6 100644
--- a/plug-ins/script-fu/libscriptfu/Makefile.am
+++ b/plug-ins/script-fu/libscriptfu/Makefile.am
@@ -119,7 +119,9 @@ libgimp_scriptfu_@GIMP_API_VERSION@_la_SOURCES = \
scheme-wrapper.c \
scheme-wrapper.h \
script-fu-lib.c \
- script-fu-lib.h
+ script-fu-lib.h \
+ script-fu-proc-factory.h \
+ script-fu-proc-factory.c
EXTRA_libgimp_scriptfu_@GIMP_API_VERSION@_la_DEPENDENCIES = $(scriptfu_def)
diff --git a/plug-ins/script-fu/libscriptfu/meson.build b/plug-ins/script-fu/libscriptfu/meson.build
index a7f1ecf03a..ffad8b51bf 100644
--- a/plug-ins/script-fu/libscriptfu/meson.build
+++ b/plug-ins/script-fu/libscriptfu/meson.build
@@ -14,6 +14,7 @@ libscriptfu_sources = [
'script-fu-errors.c',
'script-fu-compat.c',
'script-fu-lib.c',
+ 'script-fu-proc-factory.c',
]
# !! just "library(...)" which means shared versus static depends on configuration of project.
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-lib.c b/plug-ins/script-fu/libscriptfu/script-fu-lib.c
index 93b988e256..235b2d35a7 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-lib.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-lib.c
@@ -19,7 +19,7 @@
#include "config.h"
#include <libgimp/gimp.h>
-/* FIXME script-fu-types.h refers to GtkAdjustment. */
+/* FIXME We only need gimpui because script-fu-types.h refers to GtkAdjustment. */
#include <libgimp/gimpui.h>
#include "script-fu-lib.h"
@@ -28,6 +28,7 @@
#include "scheme-wrapper.h" /* tinyscheme_init etc, */
#include "script-fu-scripts.h" /* script_fu_find_scripts */
#include "script-fu-interface.h" /* script_fu_interface_is_active */
+#include "script-fu-proc-factory.h"
/*
@@ -67,13 +68,43 @@ script_fu_find_and_register_scripts ( GimpPlugIn *plugin,
script_fu_find_scripts (plugin, paths);
}
+/*
+ * Init the embedded interpreter.
+ *
+ * allow_register:
+ * TRUE: allow loaded scripts to register PDB procedures.
+ * The scheme functions script-fu-register and script-fu-menu-register are
+ * defined to do something.
+ * FALSE: The scheme functions script-fu-register and script-fu-menu-register are
+ * defined but do nothing.
+ *
+ * Note that the embedded interpreter always defines scheme functions
+ * for all PDB procedures already existing when the interpreter starts
+ * (currently bound at startup, but its possible to lazy bind.)
+ * allow_register doesn't affect that.
+ */
void
script_fu_init_embedded_interpreter ( GList *paths,
gboolean allow_register,
GimpRunMode run_mode)
{
+ g_debug ("script_fu_init_embedded_interpreter");
tinyscheme_init (paths, allow_register);
ts_set_run_mode (run_mode);
+ /*
+ * Ensure the embedded interpreter is running
+ * and has loaded its internal Scheme scripts
+ * and has defined existing PDB procs as Scheme foreign functions
+ * (is ready to interpret PDB-like function calls in scheme scripts.)
+ *
+ * scripts/...init and scripts/...compat.scm are loaded
+ * iff paths includes the "/scripts" dir.
+ *
+ * The .scm file(s) for plugins are loaded
+ * iff paths includes their parent directory (e.g. /scripts)
+ * Loaded does not imply yet registered in the PDB
+ * (yet, they soon might be for some phases of the plugin.)
+ */
}
void
@@ -155,7 +186,6 @@ script_fu_search_path (void)
GList *path = NULL;
path_str = gimp_gimprc_query ("script-fu-path");
-
if (path_str)
{
GError *error = NULL;
@@ -170,6 +200,23 @@ script_fu_search_path (void)
g_clear_error (&error);
}
}
-
return path;
}
+
+
+GimpProcedure *
+script_fu_find_scripts_create_PDB_proc_plugin (GimpPlugIn *plug_in,
+ GList *paths,
+ const gchar *name)
+{
+ /* Delegate to factory. */
+ return script_fu_proc_factory_make_PLUGIN (plug_in, paths, name);
+}
+
+GList *
+script_fu_find_scripts_list_proc_names (GimpPlugIn *plug_in,
+ GList *paths)
+{
+ /* Delegate to factory. */
+ return script_fu_proc_factory_list_names (plug_in, paths);
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-lib.h b/plug-ins/script-fu/libscriptfu/script-fu-lib.h
index 09ac45a1b3..4b22445f12 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-lib.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-lib.h
@@ -42,4 +42,10 @@ void script_fu_run_read_eval_print_loop (void);
void script_fu_register_quit_callback (void (*func) (void));
void script_fu_register_post_command_callback (void (*func) (void));
+GimpProcedure *script_fu_find_scripts_create_PDB_proc_plugin (GimpPlugIn *plug_in,
+ GList *paths,
+ const gchar *name);
+GList *script_fu_find_scripts_list_proc_names (GimpPlugIn *plug_in,
+ GList *paths);
+
#endif /* __SCRIPT_FU_LIB_H__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c
b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c
new file mode 100644
index 0000000000..5c74a755ae
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c
@@ -0,0 +1,205 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "tinyscheme/scheme-private.h"
+#include "script-fu-types.h"
+#include "script-fu-scripts.h"
+#include "script-fu-script.h"
+
+#include "script-fu-proc-factory.h"
+
+/* Local functions */
+static void script_fu_add_menu_to_procedure (GimpProcedure *procedure,
+ SFScript *script);
+
+
+/* Methods to register PDB procs. A factory makes objects, here PDB procedures.
+ *
+ * Used by the outer script-fu-interpreter
+ *
+ * This is in libscriptfu to hide the SFScript type from outer plugins.
+ * These methods use instances of type SFScript as specs for procedures.
+ *
+ * FUTURE: migrate code.
+ * There are two flavors of factory-like code: for PDBProcType TEMPORARY and PLUGIN.
+ * extension-script-fu outer plugin only makes TEMPORARY
+ * script-fu-interpreter outer plugin only makes PLUGIN type
+ * This source file supports only script-fu-interpreter.
+ * script_fu_find_scripts() in script-fu-scripts.c is also a factory-like method,
+ * and could be extracted to a separate source file.
+ * Maybe more code sharing between the two flavors.
+ */
+
+
+/* Create and return a single PDB procedure of type PLUGIN,
+ * for the given proc name, by reading the script file in the given paths.
+ * Also add a menu for the procedure.
+ *
+ * PDB proc of type PLUGIN has permanent lifetime, unlike type TEMPORARY.
+ *
+ * The list of paths is usually just one directory, a subdir of /plug-ins.
+ * The directory may contain many .scm files.
+ * The plugin manager only queries one .scm file,
+ * having the same name as its parent dir and and having execute permission.
+ * But here we read all the .scm files in the directory.
+ * Each .scm file may register (and define run func for) many PDB procedures.
+ *
+ * Here, one name is passed, and though we load all the .scm files,
+ * we only create a PDB procedure for the passed name.
+ */
+GimpProcedure *
+script_fu_proc_factory_make_PLUGIN (GimpPlugIn *plug_in,
+ GList *paths,
+ const gchar *proc_name)
+{
+ SFScript * script = NULL;
+ GimpProcedure * procedure = NULL;
+
+ /* Reads all .scm files at paths, even though only one is pertinent.
+ * The returned script_tree is also in the state of the interpreter,
+ * we don't need the result here.
+ */
+ (void) script_fu_find_scripts_into_tree (plug_in, paths);
+
+ /* Get the pertinent script from the tree. */
+ script = script_fu_find_script (proc_name);
+
+ if (script)
+ {
+ procedure = script_fu_script_create_PDB_procedure (
+ plug_in,
+ script,
+ script_fu_script_proc, /* run_func */
+ GIMP_PDB_PROC_TYPE_PLUGIN);
+ script_fu_add_menu_to_procedure (procedure, script);
+ }
+ else
+ {
+ g_warning ("Failed to find script: %s.", proc_name);
+ }
+ return procedure;
+}
+
+ /* Traverse the list of scripts, for each defined name of a PDB proc,
+ * add it list whose handle is given.
+ *
+ * Order is not important. Could just as well prepend.
+ *
+ * This is a GTraverseFunction
+ */
+static gboolean
+script_fu_append_script_names (gpointer *foo G_GNUC_UNUSED,
+ GList *scripts,
+ GList **name_list)
+{
+ for (GList * list = scripts; list; list = g_list_next (list))
+ {
+ SFScript *script = list->data;
+
+ if ( !script_fu_is_defined (script->name))
+ {
+ g_warning ("Run function not defined, or does not match PDB procedure name: %s",
+ script->name);
+ continue;
+ }
+
+ /* Must assign result from g_list_append back to name_list */
+ *name_list = g_list_append ( (GList *) *name_list, g_strdup (script->name));
+ }
+ return FALSE; /* We traversed all. */
+ }
+
+/* Load script texts (.scm files) in the given paths.
+ * Iterate over all loaded scripts to get the PDB proc names they define.
+ * Return a list of the names.
+ */
+GList *
+script_fu_proc_factory_list_names (GimpPlugIn *plug_in,
+ GList *paths)
+{
+ GList * result_list = NULL;
+ GTree * script_tree = NULL;
+
+ /* Load (eval) all .scm files in all dirs in paths. */
+ script_tree = script_fu_find_scripts_into_tree (plug_in, paths);
+
+ /* Iterate over the tree, adding each script name to result list */
+ g_tree_foreach (script_tree,
+ (GTraverseFunc) script_fu_append_script_names,
+ &result_list);
+
+ return result_list;
+}
+
+/* From scriptfu's internal data, add any menu to given procedure in the PDB.
+ * Requires that a script was just eval'ed so that scriptfu's list of menus
+ * declared in a script is valid.
+ * Requires the proc exists in PDB.
+ *
+ * Not ensure the PDB proc has a menu, when no menu was defined in the script.
+ *
+ * Derived from script_fu_install_menu, but that is specific to TEMPORARY procs.
+ * Also, unlike script_fu_install_menu, we don't nuke the menu list as we proceed.
+ *
+ * For each "create" of a procedure, the gimp-script-fu-interpreter is started anew,
+ * and a new script_menu_list is derived from the .scm file.
+ * We don't traverse the menu list more than once per session, which soon exits.
+ */
+static void
+script_fu_add_menu_to_procedure (GimpProcedure *procedure,
+ SFScript *script)
+{
+ GList *menu_list;
+ gboolean did_add_menu = FALSE;
+
+ menu_list = script_fu_get_menu_list ();
+ /* menu_list might be NULL: for loop will have no iterations. */
+
+ /* Each .scm file can declare many menu paths.
+ * Traverse the list to find the menu path defined for the procedure.
+ * Each SFMenu points to the procedure (SFScript) it belongs to.
+ */
+ for (GList * traverser = menu_list; traverser; traverser = g_list_next (traverser))
+ {
+ SFMenu *menu = traverser->data;
+ if (menu->script == script)
+ {
+ g_debug ("Add menu: %s", menu->menu_path);
+ gimp_procedure_add_menu_path (procedure, menu->menu_path);
+ did_add_menu = TRUE;
+ break;
+ }
+ }
+
+ /* Some procedures don't have menu path.
+ * It is normal, but not common, to define procs of type PLUGIN that don't appear in the menus.
+ * No part of GIMP defaults a menu path for procedures.
+ * A menu label without a menu path is probably a mistake by the script author.
+ */
+ if ( ! did_add_menu )
+ {
+ /* Unusual for a .scm file to have no menu paths, but not an error. */
+ g_debug ("No menu paths! Does the procedure name in script-fu-menu-register match?");
+ /* FUTURE if the script defines a menu *label*, declare an error. */
+ }
+ /* script_menu_list is a reference we do not need to free. */
+ }
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h
b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h
new file mode 100644
index 0000000000..5daa7cc2b9
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h
@@ -0,0 +1,27 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SCRIPT_FU_PDB_PROC_FACTORY_H__
+#define __SCRIPT_FU_PDB_PROC_FACTORY_H__
+
+GimpProcedure *script_fu_proc_factory_make_PLUGIN (GimpPlugIn *plug_in,
+ GList *paths,
+ const gchar *name);
+GList *script_fu_proc_factory_list_names (GimpPlugIn *plug_in,
+ GList *paths);
+
+#endif /* __SCRIPT_FU_PDB_PROC_FACTORY__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-script.c
b/plug-ins/script-fu/libscriptfu/script-fu-script.c
index d9fa92eb6a..c1da3838e5 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-script.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-script.c
@@ -43,6 +43,8 @@ static gboolean script_fu_script_param_init (SFScript *script,
gint n);
+
+
/*
* Function definitions
*/
@@ -165,26 +167,59 @@ script_fu_script_free (SFScript *script)
g_slice_free (SFScript, script);
}
+
+/*
+ * From the script, create a temporary PDB procedure,
+ * and install it as owned by the scriptfu extension PDB proc.
+ */
void
script_fu_script_install_proc (GimpPlugIn *plug_in,
SFScript *script,
GimpRunFunc run_func)
{
GimpProcedure *procedure;
- const gchar *menu_label = NULL;
- gint arg_count[SF_DISPLAY] = { 0, };
- gint i;
g_return_if_fail (GIMP_IS_PLUG_IN (plug_in));
g_return_if_fail (script != NULL);
g_return_if_fail (run_func != NULL);
+ procedure = script_fu_script_create_PDB_procedure (plug_in,
+ script,
+ run_func,
+ GIMP_PDB_PROC_TYPE_TEMPORARY);
+
+ gimp_plug_in_add_temp_procedure (plug_in, procedure);
+ g_object_unref (procedure);
+}
+
+
+/*
+ * Create and return a GimpProcedure.
+ * Caller typically either:
+ * install it owned by self as TEMPORARY type procedure
+ * OR return it as the result of a create_procedure callback from GIMP (PLUGIN type procedure.)
+ *
+ * Caller must unref the procedure.
+ */
+GimpProcedure *
+script_fu_script_create_PDB_procedure (GimpPlugIn *plug_in,
+ SFScript *script,
+ GimpRunFunc run_func,
+ GimpPDBProcType plug_in_type)
+{
+ GimpProcedure *procedure;
+ const gchar *menu_label = NULL;
+ gint arg_count[SF_DISPLAY] = { 0, };
+ gint i;
+
+ g_debug ("script_fu_script_create_PDB_procedure: %s of type %i", script->name, plug_in_type);
+
/* Allow scripts with no menus */
if (strncmp (script->menu_label, "<None>", 6) != 0)
menu_label = script->menu_label;
procedure = gimp_procedure_new (plug_in, script->name,
- GIMP_PDB_PROC_TYPE_TEMPORARY,
+ plug_in_type,
run_func, script, NULL);
gimp_procedure_set_image_types (procedure, script->image_types);
@@ -509,8 +544,7 @@ script_fu_script_install_proc (GimpPlugIn *plug_in,
gimp_procedure_add_argument (procedure, pspec);
}
- gimp_plug_in_add_temp_procedure (plug_in, procedure);
- g_object_unref (procedure);
+ return procedure;
}
void
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-script.h
b/plug-ins/script-fu/libscriptfu/script-fu-script.h
index c9a16f607a..a9901cd200 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-script.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-script.h
@@ -46,5 +46,11 @@ gchar * script_fu_script_get_command (SFScript *scrip
gchar * script_fu_script_get_command_from_params (SFScript *script,
const GimpValueArray *args);
+GimpProcedure * script_fu_script_create_PDB_procedure (GimpPlugIn *plug_in,
+ SFScript *script,
+ GimpRunFunc run_func,
+ GimpPDBProcType plug_in_type);
+
+
#endif /* __SCRIPT_FU_SCRIPT__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
b/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
index eac9d0f49d..914ea1a6c6 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
@@ -43,13 +43,6 @@
#include "script-fu-intl.h"
-typedef struct
-{
- SFScript *script;
- gchar *menu_path;
-} SFMenu;
-
-
/*
* Local Functions
*/
@@ -65,11 +58,6 @@ static void script_fu_install_menu (SFMenu *menu);
static gboolean script_fu_remove_script (gpointer foo,
GList *scripts,
gpointer data);
-static GimpValueArray * script_fu_script_proc (GimpProcedure *procedure,
- const GimpValueArray *args,
- gpointer data);
-
-static SFScript * script_fu_find_script (const gchar *name);
static gchar * script_fu_menu_map (const gchar *menu_path);
static gint script_fu_menu_compare (gconstpointer a,
@@ -88,13 +76,25 @@ static GList *script_menu_list = NULL;
* Function definitions
*/
-void
-script_fu_find_scripts (GimpPlugIn *plug_in,
- GList *path)
+/* Traverse list of paths, finding .scm files.
+ * Load and eval any found script texts.
+ * Script texts will call Scheme functions script-fu-register()
+ * and script-fu-menu-register(),
+ * which insert a SFScript record into script_tree,
+ * and insert a SFMenu record into script_menu_list.
+ * These are side effects on the state of the outer (SF) interpreter.
+ *
+ * Return the tree of scripts, as well as keeping a local pointer to the tree.
+ * The other result (script_menu_list) is not returned, see script_fu_get_menu_list().
+ *
+ * Caller should free script_tree and script_menu_list,
+ * This should only be called once.
+ */
+GTree *
+script_fu_find_scripts_into_tree ( GimpPlugIn *plug_in,
+ GList *paths)
{
- GList *list;
-
- /* Make sure to clear any existing scripts */
+ /* Clear any existing scripts */
if (script_tree != NULL)
{
g_tree_foreach (script_tree,
@@ -103,16 +103,46 @@ script_fu_find_scripts (GimpPlugIn *plug_in,
g_tree_destroy (script_tree);
}
- if (! path)
- return;
-
script_tree = g_tree_new ((GCompareFunc) g_utf8_collate);
- for (list = path; list; list = g_list_next (list))
+ if (paths)
{
- script_fu_load_directory (list->data);
+ GList *list;
+
+ for (list = paths; list; list = g_list_next (list))
+ {
+ script_fu_load_directory (list->data);
+ }
}
+ /*
+ * Assert result is not NULL, but may be an empty tree.
+ * When paths is NULL, or no scripts found at paths.
+ */
+
+ g_debug ("script_fu_find_scripts_into_tree found %i scripts", g_tree_nnodes (script_tree));
+ return script_tree;
+}
+
+/*
+ * Return list of SFMenu for recently loaded scripts.
+ * List is non-empty only after a call to script_fu_find_scripts_into_tree().
+ */
+GList *
+script_fu_get_menu_list (void)
+{
+ return script_menu_list;
+}
+
+/* Find scripts, create and install TEMPORARY PDB procedures,
+ * owned by self PDB procedure (e.g. extension-script-fu.)
+ */
+void
+script_fu_find_scripts (GimpPlugIn *plug_in,
+ GList *path)
+{
+ script_fu_find_scripts_into_tree (plug_in, path);
+
/* Now that all scripts are read in and sorted, tell gimp about them */
g_tree_foreach (script_tree,
(GTraverseFunc) script_fu_install_script,
@@ -571,12 +601,13 @@ script_fu_run_command (const gchar *command,
GString *output;
gboolean success = FALSE;
+ g_debug ("script_fu_run_command: %s", command);
output = g_string_new (NULL);
ts_register_output_func (ts_gstring_output_func, output);
if (ts_interpret_string (command))
{
- g_set_error (error, 0, 0, "%s", output->str);
+ g_set_error (error, GIMP_PLUG_IN_ERROR, 0, "%s", output->str);
}
else
{
@@ -593,6 +624,8 @@ script_fu_load_directory (GFile *directory)
{
GFileEnumerator *enumerator;
+ g_debug ("Load dir: %s", g_file_get_parse_name (directory));
+
enumerator = g_file_enumerate_children (directory,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
@@ -665,8 +698,10 @@ script_fu_load_script (GFile *file)
}
}
-/*
- * The following function is a GTraverseFunction.
+/* This is-a GTraverseFunction.
+ *
+ * Traverse. For each, install TEMPORARY PDB proc.
+ * Returning FALSE means entire list was traversed.
*/
static gboolean
script_fu_install_script (gpointer foo G_GNUC_UNUSED,
@@ -680,8 +715,12 @@ script_fu_install_script (gpointer foo G_GNUC_UNUSED,
{
SFScript *script = list->data;
- script_fu_script_install_proc (plug_in, script,
- script_fu_script_proc);
+ const gchar* name = script->name;
+ if (script_fu_is_defined (name))
+ script_fu_script_install_proc (plug_in, script,
+ script_fu_script_proc);
+ else
+ g_warning ("Run function not defined, or does not match PDB procedure name: %s", name);
}
return FALSE;
@@ -690,13 +729,14 @@ script_fu_install_script (gpointer foo G_GNUC_UNUSED,
static void
script_fu_install_menu (SFMenu *menu)
{
- GimpPlugIn *plug_in = gimp_get_plug_in ();
- GimpProcedure *procedure;
+ GimpPlugIn *plug_in = gimp_get_plug_in ();
+ GimpProcedure *procedure = NULL;
procedure = gimp_plug_in_get_temp_procedure (plug_in,
menu->script->name);
- gimp_procedure_add_menu_path (procedure, menu->menu_path);
+ if (procedure)
+ gimp_procedure_add_menu_path (procedure, menu->menu_path);
g_free (menu->menu_path);
g_slice_free (SFMenu, menu);
@@ -726,7 +766,15 @@ script_fu_remove_script (gpointer foo G_GNUC_UNUSED,
return FALSE;
}
-static GimpValueArray *
+/* This is the outer "run func" for this plugin.
+ * When called, the name of the inner run func (code in Scheme language)
+ * is the first element of the value array.
+ * Form a command (text in Scheme language) that is a call to the the inner run func,
+ * evaluate it, and return the result, marshalled into a GimpValueArray.
+ *
+ * In the name 'script_fu_script_proc', 'proc' is a verb meaning 'process the script'
+ */
+GimpValueArray *
script_fu_script_proc (GimpProcedure *procedure,
const GimpValueArray *args,
gpointer data)
@@ -841,7 +889,7 @@ script_fu_lookup_script (gpointer *foo G_GNUC_UNUSED,
return FALSE;
}
-static SFScript *
+SFScript *
script_fu_find_script (const gchar *name)
{
gconstpointer script = name;
@@ -916,3 +964,43 @@ script_fu_menu_compare (gconstpointer a,
return retval;
}
+
+/* Is name a defined symbol in the interpreter state?
+ * (Defined in any script already loaded.)
+ * Where "symbol" has the usual lisp meaning: a unique name associated with
+ * a variable or function.
+ *
+ * The most common use is
+ * test the name of a PDB proc, which in ScriptFu must match
+ * a defined function that is the inner run function.
+ * I.E. check for typos by author of script.
+ * Used during query, to preflight so that we don't install a PDB proc
+ * that won't run later (during the run phase)
+ * giving "undefined symbol" for extension-script-fu.
+ * Note that if instead we create a PDB proc having no defined run func,
+ * script-fu-interpreter would load and define a same-named scheme function
+ * that calls the PDB, and can enter an infinite loop.
+ */
+gboolean
+script_fu_is_defined (const gchar * name)
+{
+ gchar *scheme_text;
+ GError *error = NULL;
+ gboolean result;
+
+ /* text to be interpreted is a call to an internal scheme function. */
+ scheme_text = g_strdup_printf (" (symbol? %s ) ", name);
+
+ /* Use script_fu_run_command, it correctly handles the string yielded.
+ * But we don't need the string yielded.
+ * If defined, string yielded is "#t", else is "Undefined symbol" or "#f"
+ */
+ result = script_fu_run_command (scheme_text, &error);
+ if (!result)
+ {
+ g_debug ("script_fu_is_defined returns false");
+ /* error contains string yielded by interpretation. */
+ g_error_free (error);
+ }
+ return result;
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
b/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
index b0079a39d3..b81e779ab1 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
@@ -26,5 +26,13 @@ pointer script_fu_add_script (scheme *sc,
pointer script_fu_add_menu (scheme *sc,
pointer a);
+GTree * script_fu_find_scripts_into_tree (GimpPlugIn *plug_in,
+ GList *path);
+SFScript * script_fu_find_script (const gchar *name);
+GList * script_fu_get_menu_list (void);
+GimpValueArray * script_fu_script_proc (GimpProcedure *procedure,
+ const GimpValueArray *args,
+ gpointer data);
+gboolean script_fu_is_defined (const gchar *name);
#endif /* __SCRIPT_FU_SCRIPTS__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-types.h
b/plug-ins/script-fu/libscriptfu/script-fu-types.h
index f0231b549e..981e450e0e 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-types.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-types.h
@@ -103,5 +103,10 @@ typedef struct
SFArg *args;
} SFScript;
+typedef struct
+{
+ SFScript *script; // script which defined this menu path and label
+ gchar *menu_path;
+} SFMenu;
#endif /* __SCRIPT_FU_TYPES__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu.def b/plug-ins/script-fu/libscriptfu/script-fu.def
index 4d1f385df0..1c625bf088 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu.def
+++ b/plug-ins/script-fu/libscriptfu/script-fu.def
@@ -13,3 +13,5 @@ EXPORTS
script_fu_register_quit_callback
script_fu_register_post_command_callback
script_fu_search_path
+ script_fu_find_scripts_create_PDB_proc_plugin
+ script_fu_find_scripts_list_proc_names
diff --git a/plug-ins/script-fu/meson.build b/plug-ins/script-fu/meson.build
index c65cefcda7..c19d040f03 100644
--- a/plug-ins/script-fu/meson.build
+++ b/plug-ins/script-fu/meson.build
@@ -10,6 +10,7 @@
subdir('libscriptfu')
subdir('scripts')
subdir('server')
+subdir('interpreter')
executable_name = 'script-fu'
diff --git a/plug-ins/script-fu/scripts/Makefile.am b/plug-ins/script-fu/scripts/Makefile.am
index 75d89327a6..c9564d8f78 100644
--- a/plug-ins/script-fu/scripts/Makefile.am
+++ b/plug-ins/script-fu/scripts/Makefile.am
@@ -59,9 +59,17 @@ scripts = \
test_scripts = \
contactsheet.scm \
- test-sphere.scm \
+ test-sphere.scm
+
+# scripts interpreted by gimp-script-fu-interpreter
+# Each installs to a subdir of /plug-ins
+# Each should have a shebang and execute permission
+independent_scripts = \
ts-helloworld.scm
+ts_helloworlddir = $(gimpplugindir)/plug-ins/ts-helloworld
+ts_helloworld_SCRIPTS = ts-helloworld.scm
+
scriptdata_DATA = $(scripts)
@@ -69,4 +77,4 @@ if GIMP_UNSTABLE
scriptdata_DATA += $(test_scripts)
endif
-EXTRA_DIST = $(scripts) $(test_scripts)
+EXTRA_DIST = $(scripts) $(test_scripts) $(independent_scripts)
diff --git a/plug-ins/script-fu/scripts/meson.build b/plug-ins/script-fu/scripts/meson.build
index 840f954240..5dc33b52b1 100644
--- a/plug-ins/script-fu/scripts/meson.build
+++ b/plug-ins/script-fu/scripts/meson.build
@@ -1,5 +1,6 @@
subdir('images')
+# scripts interpreted by extension-script-fu, installed to /scripts
scripts = [
'add-bevel.scm',
'addborder.scm',
@@ -58,7 +59,6 @@ if not stable
scripts += [
'contactsheet.scm',
'test-sphere.scm',
- 'ts-helloworld.scm',
]
endif
@@ -66,3 +66,22 @@ install_data(
scripts,
install_dir: gimpdatadir / 'scripts',
)
+
+# scripts interpreted by gimp-script-fu-interpreter
+# Each installed in subdirectory of /plug-in
+# Each have a shebang and executable permission.
+# Like other interpreted plugins.
+# Lacking a shebang, a .interp file is needed to associate .scm suffix
+
+scripts_independent = [
+ { 'name': 'ts-helloworld' },
+]
+
+foreach plugin : scripts_independent
+ name = plugin.get('name')
+ srcs = plugin.get('srcs', name + '.scm')
+
+ install_data(srcs,
+ install_dir: gimpplugindir / 'plug-ins' / name,
+ install_mode: 'rwxr-xr-x')
+endforeach
diff --git a/plug-ins/script-fu/scripts/test/README b/plug-ins/script-fu/scripts/test/README
new file mode 100644
index 0000000000..4e2f613210
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/README
@@ -0,0 +1,5 @@
+Scripts to test script-fu-interpreter.
+
+Not usually installed.
+
+To use, copy the test<x> dirs into /plugins and ensure the .scm files have execute permission.
diff --git a/plug-ins/script-fu/scripts/test/test0/test0.scm b/plug-ins/script-fu/scripts/test/test0/test0.scm
new file mode 100644
index 0000000000..2680258873
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test0/test0.scm
@@ -0,0 +1,31 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; Basic test of a .scm file interpreted by script-fu-interpreter
+;
+; Setup: copy this file w/ executable permission, and its parent dir to /plug-ins
+; Example: to ~/.gimp-2.99/plug-ins/test0/test0.scm
+; (That is custom to one user.)
+
+; Expect "Test>Test SF interpreter 0" in the menus
+; Expect when chosen, message on GIMP message bar.
+
+; Also, remove the execute permission.
+; Then expect not appear in GIMP menus (not queried.)
+
+; Also, make the name different from its parent dir.
+; Then expect not appear in GIMP menus (not queried.)
+
+(define (script-fu-test0)
+ (gimp-message "Hello script-fu-test0")
+)
+
+(script-fu-register "script-fu-test0"
+ "Test SF interpreter 0"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+(script-fu-menu-register "script-fu-test0" "<Image>/Test")
diff --git a/plug-ins/script-fu/scripts/test/test1/test1.scm b/plug-ins/script-fu/scripts/test/test1/test1.scm
new file mode 100644
index 0000000000..04f27af67a
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test1/test1.scm
@@ -0,0 +1,43 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; Basic test that a second .scm file is also queried.
+; Expect "Test>Test SF interpreter 1" in the menus
+; Expect when chosen, message on GIMP message bar.
+
+; Also tests that one .scm file can define two PDB procedures
+; File is queried once, yielding two names.
+; Two separate procedures created.
+
+
+(define (script-fu-test1)
+ (gimp-message "Hello script-fu-test1")
+)
+
+(script-fu-register "script-fu-test1"
+ "Test SF interpreter 01"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+(script-fu-menu-register "script-fu-test1" "<Image>/Test")
+
+
+
+
+(define (script-fu-test2)
+ (gimp-message "Hello script-fu-test2")
+)
+
+(script-fu-register "script-fu-test2"
+ "Test SF interpreter 02"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+(script-fu-menu-register "script-fu-test2" "<Image>/Test")
diff --git a/plug-ins/script-fu/scripts/test/test1/test3.scm b/plug-ins/script-fu/scripts/test/test1/test3.scm
new file mode 100644
index 0000000000..49a89a5447
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test1/test3.scm
@@ -0,0 +1,31 @@
+; !!! No shebang here
+
+; Test a second .scm file in the same directory as a queried .scm.
+; The second .scm file need not be executable.
+; The second .scm file need not have a shebang.
+; The gimp-script-fu-interpreter will nevertheless load the second .scm
+; while it is querying the first, executable .scm in the dir.
+; The plugin manager queries the first executable,
+; and gimp-script-fu-interpreter loads (and returns defined names in)
+; the second during the query of the first.
+
+; Expect "Test>Test SF interpreter 3" in the menus
+; Expect when chosen, message on GIMP message bar.
+
+; plug-ins/test1/test1.scm is executable
+; plug-ins/test1/test3.scm is NOT executable
+
+(define (script-fu-test3)
+ (gimp-message "Hello script-fu-test3")
+)
+
+(script-fu-register "script-fu-test3"
+ "Test SF interpreter 3"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+(script-fu-menu-register "script-fu-test3" "<Image>/Test")
diff --git a/plug-ins/script-fu/scripts/test/test4/test4.scm b/plug-ins/script-fu/scripts/test/test4/test4.scm
new file mode 100644
index 0000000000..de515831dd
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test4/test4.scm
@@ -0,0 +1,25 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; Test a .scm file that does not call script-fu-menu-register
+; The menu will NOT default.
+; Expect "Test SF interpreter 4" to NOT EXIST in any menu
+; Expect the PDB proc "script-fu-test4" does appear in the PDB Brower
+
+; Two test cases:
+; Alongside an executable script: plug-ins/test4/test4.scm NOT executable
+; Executable, in its own directory: plug-ins/test1/test4.scm is executable
+
+(define (script-fu-test4)
+ (gimp-message "Hello script-fu-test4")
+)
+
+(script-fu-register "script-fu-test4"
+ "Test SF interpreter 4"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+; !!! No call to script-fu-menu-register
diff --git a/plug-ins/script-fu/scripts/test/test5/test5.scm b/plug-ins/script-fu/scripts/test/test5/test5.scm
new file mode 100644
index 0000000000..79f3937886
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test5/test5.scm
@@ -0,0 +1,16 @@
+#!/usr/bin/env gimp-script-fu-interpreter
+
+; Test a .scm file with an invalid shebang
+; Note "-3.0" missing above.
+
+; The test depends on platform and env and .interp
+; Must not be a file system link from gimp-script-fu-interpreter to gimp-script-fu-interpreter-3.0
+; Must not be a .interp file having "gimp-script-fu-interpreter=gimp-script-fu-interpreter-3.0"
+
+; Expect in the console: "/usr/bin/env: 'script-fu-interpreter': No such file or directory"
+
+(define (script-fu-test5)
+ (gimp-message "Hello script-fu-test5")
+)
+
+; !!! No call to script-fu-menu-register
diff --git a/plug-ins/script-fu/scripts/test/test6/test6.scm b/plug-ins/script-fu/scripts/test/test6/test6.scm
new file mode 100644
index 0000000000..f5035f2a97
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test6/test6.scm
@@ -0,0 +1,12 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; Test a .scm file that does not register any procedure
+
+; Expect in the console:
+; "(test6.scm:164): scriptfu-WARNING **: 10:06:07.966: No procedures defined in
/work/.home/.config/GIMP/2.99/plug-ins/test6/test6.scm"
+
+(define (script-fu-test6)
+ (gimp-message "Hello script-fu-test6")
+)
+
+; !!! No call to script-fu-register
diff --git a/plug-ins/script-fu/scripts/test/test7/test7.scm b/plug-ins/script-fu/scripts/test/test7/test7.scm
new file mode 100644
index 0000000000..913772eb43
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test7/test7.scm
@@ -0,0 +1,28 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; Test non-canonical name for PDB procedure
+; gimp-script-fu-interpreter does not enforce canonical name.
+; Other parts of GIMP (PDB) does not enforce canonical name
+; for PDB procedures defined by .scm scripts.
+
+; Canonical means starts with "script-fu-"
+; Here the name doesn't, its just "test7"
+
+; Expect "Test>Test SF interpreter 7" in the menus
+; Expect when chosen, message on GIMP message bar.
+
+
+(define (test7)
+ (gimp-message "Hello test7")
+)
+
+(script-fu-register "test7"
+ "Test SF interpreter 7"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+(script-fu-menu-register "test7" "<Image>/Test")
diff --git a/plug-ins/script-fu/scripts/test/test8/test8.scm b/plug-ins/script-fu/scripts/test/test8/test8.scm
new file mode 100644
index 0000000000..f7cd41998b
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/test8/test8.scm
@@ -0,0 +1,39 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; Test mismatch between name of defined run function and name for PDB procedure
+; Not a high priority: a rare syntax error in a plugin text.
+; If authors follow a template, they won't make this mistake.
+
+; The names must match exactly.
+; Here, "mismatch" the name of the defined run function
+; does not match "script-fu-test8" the name of the PDB proc.
+
+; Expect a warning in the text console as the plugin text is queried:
+; script-fu: WARNING: Run function not defined, or does not match PDB procedure name: script-fu-test8.
+; Expect the PDB procedure to not exist
+
+; If we don't detect this syntax error:
+; A PDB procedure is created.
+; When invoked from Test>Test SF interpreter 8"
+; the interpreter enters an infinite loop.
+; There is no harm to the GIMP app, but the interpreter process can only be killed.
+; During the run phase, the "(define foo)"
+; should re-define an existing definition in the interpreter state.
+; Instead, since the name is mismatched,
+; the foo function remains defined to be a call to the PDB procedure named foo.
+; So script-fu-script-proc instead calls the PDB again, an infinite loop.
+
+(define (mismatch)
+ (gimp-message "mismatch")
+)
+
+(script-fu-register "script-fu-test8"
+ "Test SF interpreter 8"
+ _"Just gives a message from Gimp"
+ "lkk"
+ "lkk"
+ "2022"
+ "" ; all image types
+)
+
+(script-fu-menu-register "script-fu-test8" "<Image>/Test")
diff --git a/plug-ins/script-fu/scripts/ts-helloworld.scm b/plug-ins/script-fu/scripts/ts-helloworld.scm
index 075c1ef1ed..9611d520e3 100644
--- a/plug-ins/script-fu/scripts/ts-helloworld.scm
+++ b/plug-ins/script-fu/scripts/ts-helloworld.scm
@@ -1,3 +1,5 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
; "Hello, World" Test v1.00 February 29, 2004
; by Kevin Cozens <kcozens interlog com>
;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]