Working with external files for the TBX action code

Hi,

I played a bit with external files for my Tinderbox code. Since I want to use my functions in more than one TBX file, copying the sources into the TBX files is only the second best way. I would rather have a central place and manage my code there. Then I can easily use BBEdit for editing and GIT for version control.

The basis for this approach is a “watch folder” in TBX. This monitors a folder in the finder with the sources as individual files and creates a note in TBX for every file found. Unfortunately, these notes are not yet usable for my functions (TBX does not see the external text as action code). I first have to create a copy of each note in /Hints/Library/. This can be done quite easily with an $Edict. However, there are some problems that need to be worked around:

  • TBX does not know an event that is triggered by a change in the watch folder. But I don’t want to copy the contents over and over again. This is not easy to solve. I have now found a solution in that there is a checkbox “ScanFolder”. If a change has been made in the sources, I set the checkbox manually to false and the folder is processed again.
  • If an external file is removed, the Watch Folder does not delete the corresponding note, only the name is crossed out. But I can query this and then delete the note myself. You have to be careful here, because if a collect is subsequently run, the last status is not necessarily recorded. Therefore, I have now modified the scanning so that it automatically runs twice over the folder. First clean up, then copy. That works reliably.
  • If the content of an external file changes, it can take quite a long time until this change is visible in the watch folder. That’s why I built a stamp and it simply empties the Watch Folder. A short time later, all the files are there again - with the changes. Now I can start “ScanFolder”…
  • Very strange: I edit a file in BBedit. The file is read in. But I can’t call up the functions. Why ever. What helps (and it took me a long time to discover this): copy the code from BBEdit into a new note in the Library folder. Then from there back into BBedit, save and the code runs. Without any visible change. I have no idea what is happening here.

There is another note “gExternalCode”. There is only one attribute that is really exciting: $ExternalRunInit. If that is set to true, my function looks for a function filename_Init_db for each imported file and calls it after the import. This allows all the required attributes and notes for a package to be installed automatically.

function OpenAI_Init_db(){
	create("/gAIPreferences");
	create("/gAIStopWords");
	create("/gAIReplaceWords");
	createAttribute("OpenAI_Model", "string");
	createAttribute("OpenAILinkRun", "boolean");
...
	
	if($OpenAI_Model("/gAIPreferences") == ""){$OpenAI_Model("/gAIPreferences") = "gpt-3.5-turbo";};
	if($OpenAI_Max_Tokens("/gAIPreferences") == 0){$OpenAI_Max_Tokens("/gAIPreferences") = 500;};
...
	$DisplayedAttributes("/gAIPreferences") = "OpenAI_Model;OpenAI_Temperature;OpenAI_Max_Tokens;OpenAI_Top_p;OpenAI_Frequency_Penalty;OpenAI_Presence_Penalty;OpenAI_Key;OpenAIisON;OpenAI_Prompt;OpenAI_ImagePath";
	
	create("/Prototypes/pOpenAI");
	if($OpenAI_Prompt("/Prototypes/pOpenAI") == ""){$OpenAI_Prompt("/Prototypes/pOpenAI") = "Extract not more then 15 of the most significant keywords from this text as comma seperated values";};
	// $OpenAILinkRun("/Prototypes/pOpenAI")       = false;
	// $OpenAI_Tags("/Prototypes/pOpenAI")         = "";
	$DisplayedAttributes("/Prototypes/pOpenAI") = "OpenAI_Tags;OpenAILinkRun;OpenAI_Prompt";
	$Edict("/Prototypes/pOpenAI")               = "if(!$IsPrototype){runOpenAIKeywordExtractor_db();};"
};

Here I am still a little unsure. I only want to call these functions once. This currently works reasonably safely - but I can certainly improve it.

That’s how I did it until now. But that’s not a perfect setup IMHO. If I work on file A and there is a function B I will modify this function in file A. Now I do have to update my “starting” TBX file as well as all other files where I use function B.
Using external files I have exactly one place to edit the function and all TBX files will use the new version… :wink:

But I’m still struggling with the external files. I don’t really know what is going on. Maybe if I write function after function in a TBX note(s) , the code get’s interpreted separately. If I import the complete file and there is a small error (missing ; at the end of a line) not a single function in the file seems to work… but I will have to evaluate this further.

If you collect more and more useful action code functions over time, it is more convenient to store this collection only once and still be able to use it in all your Tinderbox files. If you copy and paste the action code back and forth between the TBX files, it quickly becomes confusing. Instead, you can put the code into external text files and then use these files, which are only stored once, in all your TBX files.

In addition, you can now use BBEdit together with Pete’s great Langugage module for editing the action code. This also makes your work easier.

To use my external code lib you have to:

  • copy the note “cExternalCode” into your /Hints/Library“ folder in your TBX file (it has to be stored inside of TBX)

  • copy two stamps “ClearWatchFolder” and “InitializeExternalCode” into your TBX file

  • edit the path in the note „cExternalCode“ in the function “externalCode_Init_db” look for this line:

if($WatchFolder("/ExternalCode") == ""){$WatchFolder("/ExternalCode") = "/Users/YourName/Documents/Tinderbox/Code";};

Change “/Users/YourName/Documents/Tinderbox/Code” to the path of any folder on your Mac. This folder should contain all your external action code files. Do not proceed until you change the path here!

  • run the stamp „InitializeExternalCode“ once

That’s it!

Some additional comments:

  • your external Code files should be named like “This”. For example I have files like “Debugger”, “LinkManager” or “Tools” there. Don’t use a file extension like “.txt” (you are on a Mac…).

  • If a new file will be added to you external code folder in the Finder, everything in TBX will run automatically. The new file will get added to your Library (click on the /ExternalCode note to run the code in the $Edict immediately).

  • If a file will be deleted in you external folder in the Finder - again everything in TBX will run automatically. The existing note in your Library will be removed.

  • If you update the code in an external file, you should delete the corresponding note in the /ExternalCode container in your TBX file. This forces the script to grab the external file again and update the Library. More easy: run the Stamp “ClearWatchFolder” on the /ExternalCode container. That’s all you need to get the latest version of your code into TBX.

  • If your code file contains a function named “filename_Init_db()” (for example for my file named “Debugger” it would become “debugger_Init_db()”) this function could be used to initialise a new file. There is a flag called “ExternalRunInit” in the “gExternalCode” note. You have to check this option to get a call to the initialise function. This option is in an early state of development…

  • check the action code before you copy it into an external file. One error and the complete code inside of the file will be ignored in TBX.

Have fun.

ExternalCode.tbx (182.3 KB)

4 Likes

This is great!! Love the approach and concept, going to incorporate it. Thanks much, @webline :pray:

1 Like

just a minor update to the last version. Now it works with external code and external template files too. You will have to adjust a 2nd path to the folder with your template files (text only).
This needed some adjustments because the WatchFolder will try to interpret HTML or Markup in the external files. So a copy of the $Text for those files will no do it. I use runCommand and “cat” for the templates.

Have fun.

ExternalCode.tbx (186.9 KB)

2 Likes

Thanks for all the hard work on this and for sharing it. much appreciated!

Just so I understand correctly, you’re saying files must not have an extension or they won’t work?

I ask as the tide of history has gone a different way and file extensions are now the norm on modern Macs. So not supporting files with extensionss may not be helpful to the general user. Might it be possible to just ignore the extension if there is one. That might be less old-Mac purist, but perhaps more intuitive for less code-skilled users. :slight_smile:

yes - you understood my strange readme correctly. But where do you get the statement that file extensions have now become fashionable on the Mac? In macOS, the file name suffixes are normally hidden and that’s the way to go with the current version of my lib.
Maybe I will add something like .replace(‘/^(.+)(.[^ .]+)?$/’,‘’) to the code - but this is tricky. TBX keeps the extension. So the note name in the WatchFolder will reflect the filename including the suffix. I create a duplicate (I have to) but I don’t like to see the suffix here too. Since there is the need to synchronise the two folders I need to take care of “there might be an extension”… not impossible to do… maybe later

Here is a version that will work with file extensions too (and includes the suggestions made by @mwra)

ExternalCode.tbx (184.6 KB)

I think you can choose to hide filenames, though I’m not sure where that’s controlled in System Settings. I was making a different point that short of files that I make in Terminal—not a place most users ever visit—files tend to get an extension (whether visible or not) by default, i.e. whether I add one explicitly or not. But let’s leave this - users can ask if they get stuck

I take your point re the regex angle when copying names between OS (files) and the TBX (notes).

To avoid the user scan through code for the line with the external path, it might be simpler (for =others) is it were set in a variable declared that the top of cExternalCode() and then used as needed within the function. Whilst doing do is functionally the same as at present, it is easier to describe/find for others.

I’d also suggest a slight re-wording of the readme text:
your external Code files should be named like "This"
to
your external Code files should be named like this: "Filename". Note that the files must not be given a file extension.

For the /Templates note, if added (and I do realise the latter is work in progress :slight_smile: ), I’d suggest the following settings be applied to the newly made container (i.e. to mimic the the built-in version’s customisations):

$HTMLDontExport = true;
$HTMLExportChildren = false;
$OnAdd='$Prototype="HTML";\n$IsTemplate=true;\n$NeverComposite=true;';

Please don’t read any of the above as critical :slight_smile: . I’m just noting areas where sign-posting can be made a little easier for a new user of the library.

1 Like