217 IX: IX bash utilities, IXf and IX macros (217.html)

Keywords

REPL minicom MicroPython SeeedStudio "Wio Terminal" ICH180RR "IX Software" IX "IX Macros" IXc IXf IXp FRUM MehInCharge PiR2 .pyix .htix .txix disambiguation FRUN(changed meaning) nsxio sxio(deprecated) stub "stub module" "macro stub module" preprocessor "macro preprocessor" "IX macro preprocessor" idiom __name__ __main__ macro entity "IX Software System" IXf shebang hashbang "new FRUN" bu_zip.sh duDesktop.sh uname "NVMe Base" PCIe Gentoo Bader underscore "special system variable" OUTPUT "Tim Robertson" "NVMe Base" MPW7 MCUzone AI "WiFi 7" "Brad Linder" "An IX Entity Warehouse" "IX Cache" "reserved entity names" "reserved entities" ix_eDict.txt eDict Dunder dunders Euclid "FPC ribbon cable" FPC Bader "Tim Richardson" Pimoroni Geekworm X876 "NVMe M.2 SSD Shield" I2S Phillips MEMS "MAK Pi ADC" "Red Book audio" LPCM DAC WAV .wav RIFF "FRUM literal" literal "regular expression" grep thoughtnov namedtuple immutable

/KeywordsEnd

(To enlarge .....Click it)
thumb: IXimage.jpg
IX or DC (IX by DC) or "|><"


This IXf.py software is a part of the IX family of software.

Introduction

Sometimes, it is necessary to communicate with a Raspberry Pi at a lower level than Python. The Linux OS provides such a tool, called "bash". This is the acronym for "Bourne Again SHell" which is invoked at the Raspberry Pi Terminal prompt which is ">". Scripts for bash have an extension of "sh" and are invoked using the Terminal command "sh " followed by the name of the bash script. This article describes most of the bash scripts that the author has created. This article also describes some of the non-Python actions that are made possible by bash. It is possible to invoke bash commands in a Python program using the osCommand() (or commandOS() ) function, which is used as an example below. Such bash commands support piping, stdInput and stdOutput.

It will soon be possible to use the IXf macro preprocessor. This article describes the future use of this preprocessor and lists some of the existing IX macros previously mentioned in the IX Articles. The FRUM function (Python statement) is used in numerous batch files that are examples of IXf processing. The FRUM name deprecates the old FRUN name used in previous articles (eg Article 209). The IXf macro preprocessor also preprocesses any "new" FRUN statements that it identifies. A "new" FRUN statement is similar to a FRUM statement but each "new" FRUN statement does NOT mention modules that must be loaded from outside the written code containing the "new" FRUN statement.

Disambiguation of "IX Software" by ICH180RR:
The "IX Software" described here (in the articles by D@CC) is not to be confused with the following:
	iX Software by Beijer (graphics)
	IX Software by BMW Forums
	IX Software Industries by SignalHire
	iX software by HOMAG (3D CAD/CAM)
	iX Software by Konzept (Android apps)
	iX software Archives (wood and panel)
	Ix Software AS Company (Norway)
	IX Software - bancarias (Spanish)
	Ix Software LTD (Mauritius) AfricaBizInfo
	IX Software Technologies (Turkey)
	IX-SOFT Aiphone
	Dolphin IX software (PCI Express switch)
	IX5000 IX software by Cisco
	PC/IX (UNIX WinWorld)
	PIIC iX Software by Philips (patients)
	QUANT IX SOFTWARE (investment software)
	Series IX Software (sampling) by Instron
	Zootos IX Software (Image Analysis)
	
The IX Software (not in the articles listed immediately above) is by "David@ColeCanada.com" and ICH180RR.

Table Of Contents

	Keywords
	Introduction
	Disambiguation of "IX Software"
	Table of Contents
	List of IX bash scripts
	  programs/sites invoked/used by bash scripts
	  Devices Invoked by bash Commands
	IX Software: IX Macros
	REPL on the Pico
	  import sys
	  import os
	  import machine
	Reboot the Wio Terminal
        Using A Simple Python Macro Entity 
	An Example of a Case for a html IX Macro
	The FRUM function for IX Software
	  Analysis of The IXf program
	  Example 1: t01_batch.txt
	  Example 2: test2_py.pyix
	  Example 3: test3_py.pyix using showIt.pyix and show() v00
	The FRUM("=", . . ) Literal
	The FRUN statement
	The IXf.py Program
	Code Modules in Packages for Example 3
	Running a List That Contains Python Program Statements
	An Entity Warehouse
	Daily SSD Backups
	A namedtuple Is A class
	Other Thoughts
	  The Pimoroni NVMe Base
	Perfect Numbers
	
	Sources
	  Video Sources
	  Web Sources
	
/TableOfContentsEnd

List of IX bash scripts

All of the IX bash scripts are stored in ../Desktop/IX_assets folder on my Raspberry Pi computers. They make use of IX data and functions that are found in the ../Desktop/IX_assets and in its ix folder. Some bash scripts may use the "shebang" described in more detail, later in this article. The major IX scripts are:

	bash script name	Description
	----------------	----------------------------------
	Aikon_summ.sh		Aikon is a benchmark script
	Alarm.sh		demonstrates the IX audio and visual alarms (used by IX_log.sh)
	bu_zip.sh		daily zip to backup the whole RPi Desktop folder
	CPU_temp.sh		displays the temperature of the Pi processor (used by IX_log.sh)
	duDesktop.sh		lists the size of the Desktop and backup ("pi/bu") folders
	Environ.sh		displays some of the environment of the Raspberry Pi (used by IX_log.sh)
                                (This is often used by the author to identify the current Pi status. It
                                  describes the uSD/SSD housing the OS, the RPi in use, CPU temperature etc.)
	IX_bench.sh		runs a benchmark on the Raspberry Pi
	IX_ICH.sh		displays a minimal environment report (deprecated by IX_log.sh)
	IX_log.sh		runs the IX log to show in detail, the current environment of any Raspberry Pi
	minicom_startup.sh	starts up the minicom terminal emulator on the Raspberry Pi
				(Useful to communicate with a Pico or the WioTerminal)
	n.sh			displays a simple overview of a Python program or package using grep
	PiR2.sh			starts up the IX PiR2 A/C area environement controller
	StationDump.sh		displays some current statistics for the router traffic
	stress.sh		runs a 100% CPU stress test on the Raspberry Pi
	textpack.sh		packs/unpacks code modules in IX packages (eg in fPkg_py.py)
	

programs/sites invoked/used by bash scripts

	program name		Description
	----------------	----------------------------------
	ePhotoCaption.com	a web-site that houses all of the author's articles
	iGalri.com		a web-site that houses many of the author's photos
	MehInCharge.com		a web-site to house/access the PiR2 log files
	-
	article 215		a repository of all of the author's Raspberry software code
	IXf.py			the IXf macro pre-processor (invokes FRUM() )
	IXp.py			the IX macro pre-processor (with prepIX)
	minicom			a terminal emulator (for pico, Wio Terminal etc)
	n.py			provides an extensive overview of a Python program/package
	nsxio			displays an image on the RPi window (deprecates sxio)
	pir2_checkUp.py		exercises/tests the PiR2 hardware prior to running pir2
	pir2_main.py		PiR2 main program
	prepIX.py		program used by IXp
	requires()		function used by IXp macro preprocessor and prepIX
	textpack.py		main program for IX package maintenance
	vcgencmd		program to access the internal RPi hardware (runs in Terminal)	
	

Devices Invoked by bash Commands

	IX voltmeter
	Mak ADC toolbox for the pico
	Mak Pi ADC
	PiR2 controller family (eg PiR2A)
	Raspberry Pico
	WioTerminal (by SeeedStudio)
	

IX Software: IX Macros

Various versions of IX Software (IX, IXc, IXf and IXp) will process macro code. However, the IX Software is not fully functional (as of 2024CMar17). Many IX macros (listed below) are mentioned in various Articles written by the author (Articles: 155, 164, 182, 183, 196, 181, 182, 183, 196, 204, 206, 207, 208, 209, 210, 211, 212, 215 and 217.) In all cases, the macro is an example. This is because the author has not yet released a full-function IX macro preprocessor. In these articles, some of the macros in the following list may have deprecated names with the extension ".pyix" or ".htix". These names of these macros will soon be updated and added to Article 215. The definitions of these macros can be easily found using freefind in www.ephotocaption.com .
	anyPreTest1_ix.pyix
	abc.pyix
	clickableImageA.htix
	commandOS.pyix
	def.pyix
	f3.pyix
	ix_eDict.txt
	osCommand.pyix
	selectCode.htix
	show.pyix (deprecated)
	showIt.pyix
	test1.pyix
	

REPL on the Pico

The easiest way to begin to use the Raspberry Pico is to run individual Python statements on a command line in real time. This is called the REPL mode, which is the acronym for "Read, Eval, Print, Loop". This REPL facility is often referred to as interpretive mode. The REPL prompt is ">>>". See the bash script (in the Article 215 repository) named "minicom_StartUp.sh". It can be used to enter REPL mode on the Pico, when the Pico is connected to a Raspberry Pi USB port.
	Press any key to enter the REPL. 
	REPL will respond to any of the following commands 

	>>>Enter	to redisplay the REPL prompt >>>
	CTRL-B		to see the MicroPython blurb
	CTRL-D		to soft reboot. and see the MicroPython blurb
 	import sys	to import the sys modules
                          then
	dir(sys)	to display the sys classes
			  argv, . . . platform . .  version, version_info etc
	help(sys)	lists ~20 commands
	sys.version	'3.4.0; MicroPython v1.20.0 on 2023-04-26'
	import os	to import the os modules
	  then
	dir(os)		to display the os classes
	os.getcwd()	displays '/'
	help(os)	lists the os functions (modules)
	os.urandom(1)	returns a random character
	for s in os.ilistdir():
	    print(s)	prints ('newDC', 16384 , 0, 0 )
	#for end
	import machine	to import the machine modules
	dir(machine)	lists  ADC etc
	help(machine)	lists obj & modules
	import board	********** fails ************	
	

import sys

	help(sys)
	----------
	objext          -- <module 'sys'> is of type module
	__name__        -- sys
	path            -- ['','/','.frozen','/lib']
	argv            -- []
	version         --  '3.4.0'
	version_info    -- (3, 4, 0)
	implementation  -- (name='microPython', version=(1, 11, 0))
	platform        -- Afmel SAMD51
	byteorder       -- little
	maxsize         -- 2147483647
	exit            -- <function>
	stdin           -- <ioFileIO 0>
	stdout          -- <ioFileIO 1>
	stderr          -- <ioFileIO 2>
	modules         -- {}
	print_exception -- <function>
	

import os

	help(os)
	---------
	objext   -- <module 'uos'> is of type module
	__name__ -- uos
	uname    -- <function>
	chdir    -- <function>
	getcwd   -- <function>
	ilistdir -- <function>
	mkdir    -- <function>
	remove   -- <function>
	rmdir    -- <function>
	stat     -- <function>
	statvfs  -- <function>
	unlink   -- <function>
	sync     -- <function>
	sep      -- /
	mount    -- <function>
	umount   -- <function>
	VfsFat   -- <class 'VfsFat'>
	

import machine

	dir(machine)
	--------------
	[ '__name__',
	'ADC'.
	'DCA',
	'LCD',
	'Map',
	'PWM',
	'Pin',
	'Sprite',
	'UART']
	
	help(machine)
	--------------
	objext   -- <module 'machine'> is of type module
	__name__ -- machine
	Pin      -- < class 'Pin'>
	ADC      -- < class 'ADC'>
	DCA      -- < class 'DCA'>
	LCD      -- < class 'LCD'>
	Map      -- < class 'Map'>
	PWM      -- < class 'PWM'>
	UART     -- < class 'UART'>
	Sprite   -- < class 'Sprite'>
	

Reboot the Wio Terminal

The Terminal Window finally says:
	Press any key to enter the REPL, Use CTRL-D to soft reboot

	Auto load main.py by saving files over USB to execute them or to use REPL to disable.
	
	main.py output:
	
	B:isInUse0: False
	
	C in loop, monitoring button(s)
	device is not yet "Wio Terminal", enter a char eg 'e' then Enter
	
The Terminal window said:

	Welcome to minicom 2.8
	OPTIONS: I18N
	Port /dev/ttyACM0, 02:37:18

	Press CTRL-A Z for help on special keys.

I hit CTRL-A Z then W 
It showed the menu and then erased it
I hit CTRL-D
It said:

	IX_23E18.py
	MicroPython e2f4a00-dirty on 2020-08-14; Ardupy with seeed
	Type "help

I hit CTRL-D
it said:
	soft reboot
	enter REPL to disable

	main.py output:

	IX_23E18.py
	MicroPython e2f4a00-dirty on 2020-08-14; Ardupy with seeed
	Type "help

I hit CTRL-D
It said:
	soft reboot
	enter REPL to disable

	main.py output:

	IX_23E18.py
	MicroPython e2f4a00-dirty on 2020-08-14; Ardupy with seeed
	Type "help

Apparently, CTRL-D always initiates a soft reboot and then restarts the "main.py" program .

An easy way to tell if MicroPython is usable on a Wio Terminal (instead of CircuitPython) is to go into the REPL mode, then type:
	import machine
	dir(machine)
	then hit CTRL-D numerous times
	
If dir (machine) lists some objects
and
CTRL-D eventually says:
	MicroPython e2f4a00-dirty on 2020-08-14; Ardupy with seeed
	Type "help
	
then the Wio Terminal is running MicroPython and "machine" and "try,import machine" should be used
instead of "import board" in "test_waitbuttons_i.py" to test if it indeed is a Wio Terminal not an RPi that is running the code.

The "test_anyButton_i.py program incorrectly uses "import board" with MicroPython to test the buttons.

My current issue/question is: How do I run program: main.py under CircuitPython? Perhaps it cannot be done.

Using A Simple Python Macro Entity

A macro entity is not a new concept. But many junior Python users might never have heard of it before. Consider the following 3 lines of Python code:
	print("My favorite function is len(s).")
	print("Or use the function as len(strA).")
	lenA=len(strA)
	
In some programs, a function reference might be coded multiple times in the same program. It would be simpler if the phrase "len(s)" could be defined once early in the code and then used repeatedly afterwards. Of course, the phrase "len(" could be put in a string variable named fOpen and ")" put in another variable named fClose. Then it could be used as:
	print("My favorite function is "+fOpen+"s"+fClose+".")
	print("Or use the function as "+fOpen+"strA"+fClose+".")
	lenA=fOpen+"strA"+fClose
	
Serious Python programmers will immediately notice that the third statement will throw an error. String concatenation cannot be used to generate a statement. The Python developers did NOT allow for this syntax (or usage) although it works fine in a print() statement. The use of an entity offers a solution. The phrases "len(" and ")" can be assigned to a special type of variable called an entity. This is done using the following macro entity definition statements:
	&fOpen|=len(
	&fClose|=)
	
Notice that each entity name is prefixed by "&" and suffixed by "|". Furthermore, double quotes need not enclose the defined string. The three initial statements can now be coded as:
	print("My favorite function is &fOpen|s&fClose|.")
	print("Or use the function as &fOpen|strA&fClose|.")
	lenA=&fOpen|strA&fClose|
	
Of course, Python will find errors in the 3 lines of code shown immediately above. But a macro preprocessor can transform these 5 lines of non-Python code above into the 3 lines of Python code below:
	print("My favorite function is len(s).")
	print("Or use the function as len(strA).")
	lenA=len(strA)
	
which, in fact, is valid Python code and will NOT cause Python to throw an error.

This type of preprocessor is called a macro preprocessor. Its job is to replace every reference to an entity by the phrase defined for the entity. A macro preprocessor can even replace entities in multiple lines of code in a manner similar to a function. Such multiple lines of code are called a macro, hence the name "macro preprocessor". Normal Python does not allow such macros, but the IX Software System does. One such macro preprocessor is called "IXf". Furthermore, the value of an entity can change, but only prior to the use of the resulting Python code. Much more is said about the IX Software System's macro preprocessor, its macros and entities in the remainder of this article and in other articles by the author of this article.

An Example of a Case for a html IX Macro

When writing an article for ePhotoCaption.com in html code, it is often desired to include a code module and its description in a way that the full code listing is not all visible but can be "copied" in its entirety. A good example is in article 155 where a number of functions are described in detail. A good example is the commandOS() Python function. See it represented as "selectCode 04" in Article 155 (and also in the image below). In the case of "selectCode", it is very important that the full syntax be correct. To do this, a Python macro named "commandOS_selectCode.py" could be used to generate a macro text file such as seen in Source 01.

(To enlarge .....Click it)
thumb: commandOS_selectCode.jpg
commandOS_selectCode.jpg


This macro preprocessing has not yet been done (as of 2024CMar12), but the example of commandOS() is the type of complex code situation that prompted the author to begin developing the IXp (and subsequently the IXf) macro preprocessor. There are a few typographical errors in the image above, but this shows why a macro preprocessor can be advantageous when coding html. Those readers familiar with macro preprocessors will notice more than 10 possible entities (above) that should be used to define such a macro.

This example of a future macro is probably more suited to Article 216, but has been included here to demonstrate the usability of a macro preprocessor when documenting bash scripts such as "stress.sh" . See Source 01, Source 02 and Source 03 for some related code examples.

The author has created the second version of "a simple macro processor for Python". It can be found in Source 05 (Article 182). It is a revision of IXc, Source 06 (Article 183). The publication of these 2 articles was indeed in the "wrong order".

The FRUM function for IXf in the IX Software System

One of the last major functions in the IX Software system is the FRUM statement which the user codes in a batch Python source file (*_batch.py) which can be processed by the IXf program. The acronym FRUM stands for "Fetch . . Requires . . Uses . . Module". It uses three parameters as input into the FRUM function. The IXf program assembles a number of code modules with the goal of forming a valid Python program. The IXf program is given the name of an IX batch file (*_batch.py) , that will be used to create the final code module. The batch file (eg appA_batch.py) is mainly comprised of the names of one or more code modules, each code module being defined by a FRUM function. The purpose of each FRUM function (or statement) is to load a specific code module, either from the current directory or from a code package. The first parameter given to FRUM defines the source of a macro definition module. This source is usually the current folder or the name of a code package. The second parameter specifies the name of the code module to be loaded from the specified package or folder. The presence of the third parameter occurs at the end of the module definition. The final defining code module is always assigned a name, usually related to the name of the batch file that defines it. For example, appA_batch.py, will produce a final Python code module named appA.py. If the third parameter is present, it is the name that will be given to a duplicate copy of the final code module. This name is often "main.py".

In Article 209, one can see many examples of batch files used to define results of the IXf program. In Article 209 they can be found in headings "FRUM: Fetch . . Requires . . Uses . . Module (A single complex example)" and "Fetch . . Requires . . Uses . . Module (Another example)" . Most of the time, one or two FRUM statements are all that is necessary in the batch file. The following short example will be a valid introduction to FRUM and how it "tells" the IXf program what to do.

Example 1: t01_batch.txt The batch program is created by the Terminal commands below:
	> cd /home/pi/Desktop/a/sandBox
	cat >>t01_py.pyix
	#by D@CC on 2024CMar16          prev. statement creates a .pyix file in the current folder
	print("Hello World from t01_py.py!")
	^D
	> cat >>t01_batch.py
	# t01_batch.py			prev. statement creates a batch file in the current folder
	FRUM("","t01_py.pyix","")
	FRUN("python","run","end")
	# end of t01_batch.py
	^D
	> python IXf.py t01_batch.txt
	#				prev. statement runs the batch file in the current folder
	IXf created t01_py.py
	python running t01_py.py
	Hello World from t01_py.py!
	>
	
The Terminal commands shown above will:
	1. create a Python IX program module named t01_py.pyix that will print a "Hello World" message
	    This resembles a valid Python program, but without a .py extension. (Its extension is "pyix" because
		it can contain some entity (or macro) definitions and invocations.)
	2. create an IXf batch file named t01_batch.py that defines the [list of 1] module(s)
		needed to create the t01_py.py source code module which is:

	### IXf t01_py_batch.py on 2024CMar19 04:41 PM   #name of the batch file
	#   FRUM("","t01_py.pyix","")		#s first FRUM statement creates the next two statements
	#by D@CC on 2024CMar16			#the first "" says that the t01_py.pyix module is in the current folder
	print("Hello World from t01_py.py!")
	FRUN("python","run","end")		# FRUN statement which invokes Python
	# end of t01_batch.py			# last "comment" line is the /t01_batch.py
	### NOTE FROM IXf: good end of t01_py.py generated by IXf 
						#     IXf using t01_batch.py
	### running the program . . .
	Hello World from t01_py.py!		#t the resulting t01_py.py program will print this message
	
The second statement, that invokes FRUN(), is merely a trigger to tell IXf to pass the module through Python, then run the resulting module, then end the process.

The preceding example is very trivial but it shows how the IXf.py program processes a *_batch.py file and then runs it. This example is trivial because it lacks both a macro and the use of a "requires(funcA. . .)" statement. For any newcomer to the IX Software System, a few errors will have been thrown if this example is run today. This is because the program IXf and the functions FRUM() & FRUN() are not yet available for your computer. They will soon be available in Source xx (fut) and can be downloaded to ../Desktop/a/sandBox.

Analysis of The IXf program

The IXf program can generate many various different blocks of code which are:
Block-d		the list of entity definitions input manually by the user, at runtime, if any (future)
Block-e		the entity definition block, if any, in the first few modules
Block-m		the macro code definition files, if any, are in the next few modules
Block-f		the required function definitions, if any, are mentioned in a 
			"requires() statement.  This is often empty.
Block-i		import statements for the IX Software-specific functions, if any
Block-l		the in-line function definition modules, if any, appear in these modules
Block-r		in-line FRUN statements that do not refer to a loadable module
Block-s		the first stub of code mentioned in the *.batch.py file
Block-s		additional stubs of code mentioned in the *.batch.py file, if any
Block-n*	the last stub of code mentioned in the *.batch.py file.  The 3rd parameter
			can specify a 2nd name eg "main.py" for the full final program
		#Note: At least one of the Block-l, Block-s or Block-n blocks must not be empty
Block-o		#ENTITY & REQUIRED FUNCTIONS LISTS (comments automatically generated by FRUN by IXf)
Block-r		#The Block r is a FRUN statement that does not reference any code modules to load
Block-p		the FRUN python,run,end statement (often generated by IXf)
Block-t		the trailing block that can add text/comments to the resulting code, if any

       *	The Block-n is the only absolutely necessary block in an IX batch file
The letter(s) representing each block (+" ") should appear as a comment in the batch code.
For a simple program like example 1 most of the blocks do not need to be defined. The first example is the very short program shown below:
### t01_batch.py on 2024CMar19 04:41 PM #  name of the batch file
#FRUM("",t01_py.py)			#n a FRUM statement that (if uncommented) will create the next two statements
#by D@CC on 2024CMar16			#  line 1: the "" says that the t01_py.py module is in the current folder
print("Hello World from t01_py.py!")	#  line 2: a normal Python print statement
FRUN("python","run","end")		#p FRUN statement which invokes Python (if absent, is generated )
Hello World from t01_py.py!		#  the resulting program should print this message
###/t01_py.py				# last line of code of the module. This comment is generated by 
					#     IXf using t01_batch.py
### NOTE FROM IXf: end of good t01_py.py
### end of t01_batch.py			# above are the last 2 "comment" lines of the t01_batch.py
					# terminating comment above line precedes all the output.
### running the program . . .
Hello World from t01_py.py!		# the running program prints this message
The final code output of IXf, shown above, has many empty (non-existent) blocks of code. This is because there were no entities, no macros and no functions used to define the resulting program. As can be seen, every one of the additional comments inserted by IXf was preceded by "### ".

Example 2: test2_py.pyix

This second example is a slightly more complex program (named test2_batch.py stored in the current folder) that uses two entity definitions and a stub (which is the main program) but no macros and no required functions. This example was previously introduced in Article 210.
	# program test2_batch.py
	#&| &count| =5 &|				#e2 coded here defines the values of 2 entities
	#&| &count2|=4 &|
	FRUM("m","showIt.pyix","")			#m from the mPkg_py.pyix package
	FRUM("","test2_py.pyix","main.py")		#n stub module from the current folder will also be named "main.py"
	FRUN("python","run","end")			#p Python and run (which do not reference a module to load
	#/test2_py_batch.py
	
The above code can be coded, in detail, without citing any FRUM statements, as shown below. Even the FRUN-r statement can be omitted. If omitted, this last FRUN-r statement will be automatically generated.
	# program test2_py_batch.py
	# by D@CC on 2023KNov11	
	#&| &count| =5 &|				#e2 coded here
	#&| &count2|=4 &|
	# most recent entity values are used i.e.	#l these statements become L01
	print("ent.values in this macro def. will be used")

	###FRUM("m","showIt.pyix","")			#m from the mPkg_py.pyix package, can be coded as

	#&| &macroIX|:showIt.pyix &|		# equivalent code as "m" above
	show('"&v|:"',&v|,&isToPrint|)?			# contains 2 undefined 
							#   entities: &v| and &isToPrint|

	# program test2.pyix				#2 these statements become L02
	print("count:"+str(&count|)+":")
	print("count2:"+str(&count2|)+":")
	print("before for . . &count|+&count2|")
	for i in range(&count|+&count2|):
	    print("for loop:",i+.1)
	    print("In for, count2:"+str(&count2|)+":")
	#for end					# this is the end of test2_py.py

	FRUN("python","run","end")			#p Python and run (automatically generated)
							#the previous line of (FRUM or FRUN) code is the
							# the only line of FRU? code that is absolutely needed.
	/test2_py_batch.py
	
The user can enter the following Terminal commands to run this code:
	> cd /home/pi/Desktop/a/sandBox
	> IXf test2_py_batch.py
	
While IXf processes the above batch file, it produces the following intermediate result:

	### IXf test2_py_batch.txt on 2024CMar19 04:41 PM 
	### Undefined entities prompted for definition by user
	&v|=count
	&isToPrint|=1
	# by D@CC on 2023KNov11
	FRUN("",L01,2)						# FRUN L01 is a list containing 2 lines of code (below)
	### FRUM("m","showIt.pyix","")				#m from the mPkg_py.pyix package
								# 
	FRUN("",L02,7)						# FRUN L02 is a list containing 9 lines of code (below)

	#FRUM("","test2.pyix","main.py")			#n specifies, names and saves the resulting code
	# most recent entity values are used i.e.		
	FRUN("r","","full")					#r FRUN results (summary) specifying a full listing
	### *** ENTITY LIST (showing the final value of each)
	### &v|=count
	### &isToPrint|=1
	### &count|=4
	### &count2|=5
	### NOTE FROM IXf: every entity invocation was replaced by a value
	### ***LIST OF REQUIRED IX FUNCTION VERSIONS*****
	### Highest	Loaded	Required	   MD5
	### Vsn Avail.	Vsn 	Vsn		   hash
	### ----	----	------------------ ----
	### *********************************************
	### NOTE FROM IXf: There was/were 0 "required" function(s)
	### 
	### saved as test2_py.py
	### saved as main.py
	FRUN("python","run","end")				#p FRUN statement:Python, run and end
	### NOTE FROM IXf: end of good test2.py generated by IXf
	### running the program . . .

	
The batch file listed above only contains comments, FRUM() and FRUN() statements. Note that:
	FRUN("",L01,2) 
		and 
	FRUN("",L02,7)
	
have replaced the 2 groups of "Python" code respectively, as shown below:
	#&| &count| =5 &|					#e2
	#&| &count2|=4 &|
	
		and

	print("ent.values in this macro def. will be used")
	print("count:"+str(&count|)+":")
	print("count2:"+str(&count2|)+":")
	print("before for . . &count|+&count2|")
	for i in range(&count|+&count2|):
	    print("for loop:",i+.1)
	    print("In for, count2:"+str(&count2|)+":")
	#&| &macroIX|:showIt.pyix &|
	show('"&v|:"',&v|,&isToPrint|)?

	
This intermediate result (shown above) is analyzed and separated into blocks of code that will be processed by prepIX_I.py which automatically generates the following final source code with a "full" listing (where FRUM L1 and FRUM L2 no longer appear):
	### IXf test2_py_batch.py on 2024CMar19 04:41 PM 
	# program test2_py.pyix
	# by D@CC on 2023KNov11
	# in-line entity definitions				#e2 in-line  
	### FRUM("m","showIt.pyix","")				#m from the mPkg_py.pyix package
	### #&| &macroIX|:showIt.pyix &|			#     ent commented out
	### show('"&v|:"',&v|,&isToPrint|)?			#     ent commented out
	### FRUM("","test2_py.pyix","main.py")			#n from current folder
	# most recent entity values are used i.e.		#l L01
	print("ent.values in this macro def. will be used")
	# becomes
	# program test2_py.py					#l L02
	print("count:"+str(5)+":")
	print("count2:"+str(4)+":")
	print("before for . . 5+4")
	for i in range(5+4):
	    print("for loop:",i+.1)
	    print("In for, count2:"+str(4)+":")
	#for end
	show('count:',count,1)
	### FRUN("r","","full")					#r results (summary)
	### ***ENTITY LIST (listing the final value of each)
	### &count|=4
	### &count2|=5
	### &v|=count
	### &isToPrint|=1
	### NOTE FROM IXf: every entity invocation was replaced by a value
	###
	### ***LIST OF REQUIRED IX FUNCTION VERSIONS*****
	### Highest	Loaded	Required	   MD5
	### Vsn Avail.	Vsn 	Vsn		   hash
	### ----	----	------------------ ----
	### *********************************************
	### NOTE FROM IXf: There was/were 0 "required" function(s)
	### saved as test2_py.py
	### saved as main.py
	### 
	FRUN("python","run","end")				#p
	### NOTE FROM IXf: end of good test2_py.py generated by IXf
	
The above code has been completely converted into the valid Python program named "test2_py.py"

More is said about the FRUM macro in Article 209. But that information is not as recent as what is provided here.

Example 3: test3_py.pyix using showIt.pyix and show() v00

The third example is a program that uses an external macro and a required function. The macro is "showIt" which uses 2 entities. The required function is the function show(). The show() function is a debugging aid that the author has created and uses. The author has never actually used the showIt macro because (at time of writing 2024CMar17) the macro preprocessor is unavailable for normal use. Note that (as of 2024CMar17) the only articles that make mention of showIt() are Articles 183, 207, 209, 210, 215 and this article 217. Example 3 mentions no FRUN statements directly.

As of 2024CMar22, the IX Software defines and makes use of one new proprietary file extension (.pyix):

    _batch.py  which identifies an IX batch file eg test3_batch.py
        this batch file invokes test3_py.pyix which
        cannot be processed by Python asis, because
        it needs to be processed by the IXf macro preprocessor
    .pyix which identifies an IX macro/entity file eg showIt.pyix

The user enters one of the 3 following Terminal command strings to run this code using the
IXf macro preprocessor (note that "test3_py_batch.txt" fully defines "test3_py.py"
but the former is stored in the Desktop/IX_assets/ix/bPkg_py.py package) :
   i)
	> cd /home/pi/Desktop/a/sandBox
	> IXf test3_py_batch.py
or ii)
	# current folder is presumed to be the /home/pi/Desktop/
	# where the "b" in FRUM "b" (below) is Desktop/IX_assets/ix/bPkg_py.py
	> IXf FRUM("b","test3_batch.py","a/sandBox")

(Of course, the second is the simpler, more concise terminal command. Note that the third parameter
of ii specifies the folder (relative to Desktop/) that IXf should use as its current folder.)

where test3_batch.py (as coded by the user and stored in the "b" package) is:

	### NOTE FROM IXf: test3_batch.py on 2024CMar19 04:41 PM :
	#::::::::::textpak=>test3_batch.py  			#the marker comment is not needed here
	#By: D@CC on 2024CMar19
	s=[]
	#repository: bPkg_py.py					#  test3_py.pyix can be from sPkg
	s=s+FRUM("ePkg_py.pyix","test3_py_entDefs.pyix")		#e2 from e pkg
	s=s+FRUM("mPkg_py.pyix","showIt.pyix")			#m from m pkg
	s=s+FRUM("fPkg_py.py",'requires("show(s,s,s,'v==00'")') 	#f from f pkg
					#needs f show() v00
	# no import function definitions exist			#i
	# no in-line function definitions exist			#l
	#from fPkg_py.py import show00      #if requires is absent: this will import show00
	s=s+FRUM("","test3_py.pyix","main.py")			#n from current folder, calls show()
	toSerialFileFromList(s,"test3_py.pyix","")
	#                                               	#    also named as main.py
	argStr=""
	strResults = commandOS(runPython(IXf,"test3_py.pyix",argStr))
	print(strResults)
	#							#t no text is added
	#/ test3_batch.py end

(Note that there are only 5 active (non-comment) statements in the batch file above. Only one
FRUN statement is necessary.  This necessary FRUN statement will be generated automatically, if absent.)

and where test3_py_entDefs.pyix is the following entity definition module. It is located in ePkg_py.pyix:

	#&| &count| =5 &|
	#&| &count2|=4 &|

and where showIt.pyix is the following macro module. It is located in mPkg_py.pyix:

	#&| &macroIX|:showIt.pyix &|
	show('"&v|:"',&v|,&isToPrint|)?
Note that &v|=count and &isToPrint|=1 were not defined in the code but will be needed. The IXf preprocessor is user-friendly in that, it does NOT throw an error in the case of missing entities. Instead, their values are requested and entered by the user when prompted at run time, just before the program begins to run. If the user does not respond within 60 seconds, an error will eventually be thrown. Using these values, if entered, the showIt macro will be used to generate the necessary Python code. If the code generation is overly complicated with errors, perhaps the running of the program will be aborted. In the case of no errors, the code generated will be:

	show("count:",count,1)

which (if count is 5), will print the value of "count" for debugging purposes:

	count: 5

(Note: the third parameter given to show is 1 or True.  Any other value will suppress printing.)

The IXf preprocessor summarizes the resulting code (after preprocessing & code generation) into:

	
	### NOTE FROM IXf: IXf test3_py.bix on 2024CMar19 04:41 PM :
	#By D@CC on 2024CMar19
	### entity prompts 2:					#e1 (entered by the user)					
	# &v|=count
	# &isToPrint|=1
	FRUM("ePkg_py.pyix","test3_py_entDefs.pyix")		#e2 pkg
	FRUM("mPkg_py.pyix","showIt.pyix")			#m
	FRUM("fPkg_py.py",'requires("show(s,s,s,'v==00'")') 	#f
				#needs f show() v00
	# no in-line function definitions exist			#i
	# no in-line code exists				#l
	#from fPkg_py.py import show00 
	#if requires is absent:	 the above statement will import show00
	FRUM("","test3_py.pyix","main.py")			#n from current folder, calls show()
	#                              #also named as main.py
	#   ----------------------------------------------
	#   The Entity List and requires List will follow:
	#   if the FRUM("r","prep_IX","full") #i is coded, a full program listing will be provided.
	#/ test3_batch.py end
	### FRUN("r","prepIX_I","")				#r results (summary)
	### ***ENTITY LIST (listing the final value of each)
	### &v|=count
	### &isToPrint|=1
	### &count|=5
	### &count2|=4
	### NOTE FROM IXf: every entity invocation was replaced by a value
	###
	### ***LIST OF REQUIRED IX FUNCTION VERSIONS*****
	### Highest	Loaded	Required	    MD5
	### Vsn Avail.	Vsn 	Vsn		    hash
	### ----	----	------------------- ----
	### v00		v00	show(s,s,s,'v==00') none
	### *********************************************
	### NOTE FROM IXf: There was/were 1 "required" function(s)
	### saved as 1710880472_F_test3.py  in /home/pi/ixCache/
	###    where 1710880472 is 2024CMar19 04:41 PM (Article 183)
	### saved as (or overwrites) test3.py
	### saved as (or overwrites) main.py
	### 
	FRUN("python","run","end")				#p cache & run, using Python
	### passing control to Python, to run test3_py.py
	### NOTE FROM IXf: good end of test3_py.py
	
(Note that there are STILL only 5 active Python statements in the above. All others are (informative) comments! However the complete generated Python code module named test3_py.py has been saved in the current folder. The FRUM statements help the user understand this summary.

The user can specifiy a FRUN-r statement as shown below before the FRUN-Python statement.
	FRUN("r","","full") #r
	
In the FRUN-r statement, if the 3rd parameter is coded as "full" or "all" or "listfull" or "listall" instead of "", a full program listing will be provided at the time of program generation. Another option is "min", which would produce a minimum listing. A minimum listing only shows the FRUM and FRUN: statements and error messages, nothing else, even most of the comment lines are expunged. Normally a FRUN("r","prep_IX_I","") statement is automatically generated before the 'FRUN("python","run","end") #p' statement.)

An example of the "min" IXf listing of Example 3 is shown below:
	### NOTE FROM IXf: IXf test3_py.bix on 2024CMar19 04:41 PM :
	### NOTE FROM IXf: entity prompts 2:			#e1 (entered by the user)
	FRUM("ePkg_py.pyix","test3_py_entDefs.pyix")		#e2 pkg
	FRUM("mPkg_py.pyix","showIt.pyix")			#m
	FRUM("fPkg_py.py",'requires("show(s,s,s,'v==00'")') 	#f

	FRUM("","test3_py.pyix","main.py")			#n from current folder, calls show() indirectly
	FRUN("r","","min") 					#r "min" results in a minimal listing

	FRUN("python","run","end")				#p cache & run, using Python (FRUN mentions no module to load)
	### NOTE FROM IXf: end of good test3_py.py
	
The user-coded test3_py.pyix stub module (shown below) is in either the current folder or in the sPkg_py.py package. Because it is a stub module, it must be preprocessed by the IXf macro preprocessor. A stub module differs from a macro module in that a stub module is usually a non-re-usable part of code for an application. A macro module is usually re-usable in multiple stub modules, often in various different applications. Of course, any so-called "Python code" that "needs" a preprocessor should NOT have an extension of ".py" .

	#Python:	test3_py.pyix
	#by:		D@CC
	#on:		2024CMar19
	#stub:		because it has some entity invocations
	#batch:		test3_batch.py
	#needs:		IXf macro preprocessor
	#purpose:	example test3_py.pyix in Article 217
	#repository:	current folder or ix/sPkg_py.pyix
	#SSD:		KIOXIA_GTLL
	#uses:		test3_py_entDefs.pyix
	#	  	entities: count, count2
	#		ix macro: showIt.pyix
	#	  	entities: v, isToPrint
	#		requires("show(s,s,s,'v==00'")
	# most recent entity values are used i.e.
	print("ent.values in test3_py_entDefs.pyix will be used")
	# becomes
	# program 	test3_py.py
	#print("count:"+&count|+":")
	count=&count|					#macros entity def must NOT be an entity
	showIt.pyix?v=count,isToPrint=1			#invokes show00("count",count,1)
	#						# but v and isToPrint are prompted for values
	print("&count2|:"+str(&count2|)+":")	#can print an entity value
	print("before for . . count+int(&count2|)") # here,neither is an entity invocation
	for i in range(count+int(&count2|)):		#count is not type str but &count2 is
	    print("for loop:",i+.1)
	    print("In for, count2:"+&count2|+":")       # prints the value of entity &count2
	#for end
	#/test3_py.pyix
	
The body of code of the test3_py.pyix stub program (shown immediately above) is the heart of this test3 program. The other parts of the test3_py.bix batch file simply define the necessary complementary code necessary to provide the entities, macros and functions needed by the test3 program. The test3 program is one of the simplest programs that illustrates full use of the entities, macros and required functions that are all provided only by the IX Software system.

The FRUM("=", . . ) Literal

The FRUM() function has another very powerful component. It is a literal capability. The example below illustrates it very effectively:

	strA='    print("text line")\n    fOut=open("fileA.txt","w")\n    fOut.write("12,347")\n'
	FRUM("=",strA,"")
	
In the first statement, the variable "strA" is loaded with 3 lines of Python code, each terminated by "\n" which is a line-feed. The 2nd statement invokes the FRUM() function but with a single "=" as its first argument. This instructs the FRUM() function to convert the 3 Python statements (in strA, the second argument) into a Python list containing 3 elements. The FRUM() function inserts this 3-element list of 3 executable statements into the resulting main program. These "literal" statements (not limited to 3) will become an integral part of the body of the resulting executable program. In this way, the coder of an IX batch file can easily alter the resulting main program by adding any specific statements as FRUM() literals. The programmer must take care to precede each statement with the correct number of spaces to satisfy Python's need for proper indentation.

The FRUM() function is also designed to be used in real time, during the batch preprocessing. For example, a programmer might need to define an entity to contain the name of a special file but doesn't know the exact entity name (or definition) to use. Perhaps this file was mentioned in a recent IX Python program. If the program was generated using the IXf preprocessor, all recent programs will be stored in the IX Cache. The IX Cache is stored in the /home/pi/IXcache/ folder (known as "IXcache"). This real-time FRUM() function can be used as a REPL tool. The following FRUM() statement will list all of the IX entity definitions recently used:

	FRUM("IXcache",grep,"=")
	
This statement will NOT generate any statements in the main program being constructed, but instead, will grep the IX Cache for all entity definitions using a regular expression that will "grep" all statements containing entity definitions. A null string in the third argument is interpreted to be any valid IX entity definition phrase. As with the normal CLI "grep", each statement that is found is fullly listed in the current window (preceded by the fileName containing the target phrase). This "grep" capability (and the literal capability) has already been coded into the IXf system and is fully usable. In IX Software, a valid IX entity definition must be composed of the following characters:
	"&" a anyAlphaNumeric or "_" "|" spaces "="
	 |  |  --------------------   |    |     |--- the assignment operator
	 |  |           |             |    |     
	 |  |           |             |    |--------- 0 or more spaces
	 |  |           |             |
	 |  |           |             |-------------- a single "|" (vertical bar) to end an element name
	 |  |           |
	 |  |           |---------------------------- any number of alphanumeric characters or "_" (underscores)
	 |  |
	 |  |---------------------------------------- any single lowercase alphabetic character
	 |
	 |------------------------------------------- an "&" (ampersand)

	The syntax (a regular expression) that defines any valid IX entity definition.
	
The intention is to allow the programmer, in the future, to "grep" any source code file (or package) for a specific target string defined by the regular expression in the third argument of the FRUM() function.

A test version of the FRUM() function named FRUM_C_py.py.txt is available in the IX software repository in Article 215 as of 2024DApr06.

The FRUN Statement

Most of the statements in an IXf batch file are FRUM statements that cause code modules to be loaded. The remainder of the statements will usually be Python code or stubs (containing entity references) that do not specify a module to load but define in-line code instead. Each group of such statements that do not specify a module to load, will be replaced by a FRUN function call similar to that shown below:
	FRUN("",L01,3)
	
Most FRUN statement are generated to reference a unique list of defined statements in the code that do not specify a module to load. Each such statement assigns a list name (eg L01, L02 etc) as the second parameter. Therefore every program preprocessed by IXf will only contain comments and either FRUM or FRUN statements (or both), permitting IXf to easily preprocess each of these types of statements. Often a FRUN statement will only replace normal Python (not .pyix statements). These Python statements are usually trivial to preprocess. Each FRUN list might merely contain a small number of Python statements or stubs. Each FRUM or FRUN statement is fully listed when a "full" listing is requested. The third parameter of a FRUN statement is the number of entity (macro or entity) statements that exist in the FRUN code. Only this number of statements (if greater than 0) will require the use of a preprocessor. Often no FRUN staements are necessary. This permits any line(s) of macro code in each FRUN list to be recognized and easily preprocessed. It is important to know that any entity name reference must not be broken by a Python continued statement. Often, it is not necessary for the user to code any FRUN statements. The IXf processor will automatically generate all three types of FRUN statement if the user has not coded them. The user will code then only to overriden any of the options. The default options are automatically provided by the FRUN statements that are automatically generated.

The third parameter for the FRUN function is the number of statements that need to be preprocessed.

The IXf.py Program

It is the IXf.py program that is the "engine" that will actually do the job of replacing each "FRUM" and "FRUN" statement with the statements specified by them and will pass the resulting program (eg test.py) to Python, then Python can run the resulting program. Note that a dated (non-overwritable) copy of the resulting Python program will usually be saved in the IX Cache as described in Source 06. In this example, it is stored in the IX Cache using the name: 1710880472_F_test3.py . The complete "full" source code of test3.py is not listed above, so as to make this article more readable, but the "full" source code generated is stored in the IX Cache. Of course, if Python encounters any code problems or issues, an error will be thrown and this process will not be fully completed.

It is often possible to use a single 1-line Terminal command to run a stub Python program, even if it requires one or more undefined entities. The syntax to do this is:
	> python IXf.py
	 | FRUM("s", test3_py.pyix,"a/sandBox") #s
	
Of course, this exact command will fail to run test3 because it is missing the definition of the showIt macro, the show() function etc.

In fact, there is a way to use a simpler terminal command that does the same thing. It is:
	> ./IXf.py < FRUM("s", test3_py.pyix,"a/sandBox") #s
	
Variants of the above command can be created to accomplish it with even less keystrokes. The construct above works under Linux or Unix using the "shebang" or "hashbang" Unix kludge described in Source 07 below; be sure to search that article for the heading beginning with 'Use the "shebang" ' .

In the last FRUM statement, the third parameter is "end". If, instead, this third parameter is "", meaning null, then a second program can be generated by extending this batch file by adding more statements. In this manner, many programs can be generated by a single batch file. Following the code, there is space for additional text. This "text" part of the batch file can be used to list help information, external sources, known issues or related user manuals. Of course, proper version control of such application enhancements is always recommended. This type of simple batch file easily lends itself to a .json format for program definition and documention.

If there is sufficient demand, the author (or an assistant) might upload the IX Software System to GitHub. But bear in mind, the IX Software System has been initially developed solely for in-house use by the author.

It is recommended that each code stub (defining main portions of code) be defined as a function. This would encourage better version control perhaps also using the IX requires(), but also permitting the "if __name__ . . " idiom to follow each function with some test code that minimally exercises the function preceding it. Note that the renaming of each resulting module, permits IXf to also save the result using the name "main.py" without losing the advantage of different names and versions. This is especially useful when devices like the WioTerminal force a unique name like "main.py" to be used to load the code into the device. The only main.py in the folder will, of course, be the most recently generated code.

The reader is urged to examine the latter portion of the summary code that was generated by IXf (in Example 3 above). The first line of special interest begins with "***ENTITY LIST". It precedes the list of entities that were used during macro processing. This is followed by a heading "***LIST OF REQUIRED IX FUNCTIONS". This is a summary of the "versionned functions" that were required by this application. The requested version number of each "versionned function" is shown along with the highest version available. If the code being generated is NOT using the highest version available (of this function), the user is hereby notified. Future revisions of the IX "requires" function will permit the use of alternate versions (other than that specified in the "requires" statement). In these cases, the user will see which version was actually used. A future revision of the IX Software will also validate the hash total of each module to see if it has been altered (often by a sloppy or forgotten edit.) This part of the IX Software system will hopefully simplify the coding of complex applications.

Note also that (cudos to Wolfram's "mathematica" for this idea) the dated results of every use of IXf are all stored in the IX Cache. Today's inexpensive storage space certainly supports this!

The FRUM() function will usually make major use of the author's unpackToList() function that was recently coded and tested. The unpackToList() function accepts 2 parameters from FRUM(). These two parameters are the pathed package name (if specified) and the name of the module to be fetched by FRUM(). To facilitate calling FRUM() from IXf, the unpackToList() function returns a single Python list containing all the code for each module. In case of error, an empty Python list is returned. Each such code module can easily be concatenated to other code modules that were unpacked by the unpackToList() function. The unpackToList() function is a big step closer to a simple working IXf application. Two more functions have been coded (with minor testing completed) and added to the repository in article 215. They are:
	toSerialFileFromList()
	packFromSerialFile()
	
Now hopefully, the coding of FRUM() and FRUN() can be finalized.

The example 3 described above is the first detailled description of how to invoke the IXf macro preprocessor. Between now (2024CMar24) and the release of IXf, some additional minor procedural and syntax changes will be discovered. But the reader can now see how to use this IXf version of the IX macro preprocessor, developed by the author, David @ ColeCanada.com and ICH180RR.

Code Modules in Packages for Example 3

In order for IXf to create a final Python (.py) code module in Example 3 above, it was necessary to create all of the related code modules it needs to work on. Each of the modules listed below had to be created because they are all needed by IXf. An example of the resulting Python module (test3_py.py), the target application, was also created. They were created on SSD named _KIOXIA_GTLL. These modules have all been created and stored in the relevant packages on this SSD. They were all stored in the folder named Desktop/IX_assets/ix/ . Note that each package has the same extension as all of the modules stored in it. Also note that the order in which the modules define the application (Abbr.) is also their alphabetical order. Each of these code modules is stored in the IX repository in Article 215. The list of these modules is shown below:
	Abbr.  Package Name  Module Name
	-----  ------------  ---------------------
	 b     bPkg_py.py    test3_batch.py
	 e     ePkg_py.pyix  test3_py_entDefs.pyix
	 f     fPkg_py.py    requires.py, show.py
	 m     mPkg_py.pyix  showIt.pyix
	 s     sPkg_py.pyix  test3_py.pyix

	 a     aPkg_py.py    test3_py.py
	
Note that none of the necessary modules have an extension of ".py", except the batch file and the functions. Of course, the final "application name" of the resulting Python application program, "test3_py.py", also has an extension of ".py" . Now (as of 2024CMar26) that the necessary code modules are complete, the final coding of IXf.py (an offshoot IXc.py) will soon begin.

Running a List That Contains Python Program Statements

The FRUM() and FRUN() functions each create a Python list of items. Each item is a valid Python statement. Such lists can be appended together (creating a single list named sList). The resulting list (named sList) can be written out as a serial text file using the toSerialFileFromList() function. A valid batch file will result in a text file that is a valid Python program. The commandOS() function can be used to run the resulting Python program. To do this, the batch Python program should be of the following format:
	progName="testX_batch.py"
	appName="testX"
	#by D@CC
	#on 2024DApr02
	
	pyStub=appName+"_py.py"+"ix"
	# create and run pyStub w/o "ix"
	sList=[]
	sList=sList + (FRUM(...))
	sList=sList + (FRUM(...))
	sList=sList + (FRUN(...))
	sList=sList + (FRUM(...))
	sList=sList + (FRUN(...))
	toSerialFileFromList(sList,appName+"_py.pyix")
	argStr=""
	strResults = commandOS(runPython(IXf,appName+"_py.pyix",argStr))
	print(strResults)
	#/testX_batch.py
	
### intermediate "thoughtnov" by d@cc as of 2024DApr07 where stubName1= "test1.py" ### A thoughtnov (©by D@CC) is a new or novel thought (possibly important) being introduced into a document or argument. # s=FRUM(pkg,stubName1+"ix",arg3) #name2 moved as argument 3 into FRUN # def FRUN(sList,stubName1,stubExt1,argList,name2,listingType): 32024DApr07 by D@CC # #eg FRUN(s,"test1.py","ix","","main","default") #"default" is a normal listing (not "min" & not "full") # toSerialFileFromList(sList,stubName1) # toSerialFileFromList(sList,name2+ext(stubName1)) # strResults = commandOS("Python3 IXf,"+stubName1+","+argList+","+listingType) # print(strResults) # return # #def end FRUN ### /end of intermediate thoughtnov Of course, the necessary arguments must be provided to each FRUM() and FRUN() function (instead of the "...") . The last statement prints out the results of the stub program named testX_py.pyix that is defined by testX_batch.py . The argStr is a string containing the arguments that will be sent to (used by) testX_py.py. Note that the valid Python program named testX_batch.py is the full definition of the testX_py.pyix program. As of 2024DApr02, the only undefined function is runPython(). The function runPython("IXf",pyResult,argStr) simply needs to create the statement that invokes IXf. The statement to be created by runPython() is merely:
	python3 IXf.py testX_py.pyix argStr
	
A valid example of an IX batch file that creates an application can be found in Source 22. As of 2024DApr05, the IXf.py program is not yet fully functional.

An Entity Warehouse

The IX macro system introduces its users to a novel concept of an "inventory of entities" and the notion of "acquiring entities" being analagous to "shopping for entities". Entities are analagous to products that you can purchase in an old-fashioned brick and mortar store. Those entities that are "invoked" are on your "shopping list". Those entities that are "defined" (ie assigned a value) are in your "shopping cart". Those entities that are "defined" and "invoked" will appear on your "receipt" at the end of your "shopping trip". This analogy (as described here) has not (yet) contributed anything useful to the notion of "entities". . . . But if there were some entities on the "back room shelves" where they could be found and used, they might become useful. The IX "back room" of entities is an entity "warehouse" called "ix_eDict.txt" (Source 24). The IX warehouse, called "ix_eDict.txt", is an "entity dictionary" that is maintained by the programmer. Its entity "items" are available for use when writing code. The ix_eDict.txt warehouse can house many "often used" entities such as "&myFullName|", "&myInitials|", "&myEmail|", "&myCompany|", "&currProject|", "&lipsum|", "&loremipsum|" etc. The "ix_eDict.txt" warehouse can be stored in the current folder where it will be easily found.

But taking this analogy a little further, why not include "regional warehouses" of entities? A "regional warehouse" could include all the entities recently defined in all your code. Another regional warehouse could be the entities that you have used more than 10 times in the past 30 days. The IX Software system includes an "IX Cache" of all your recent code that could be searched. When should it be searched? Suppoe you have written code that needs an entity definition. When prompting you for the entity definition, the IXf preprocessor could "google" your recent code and suggest entity definitions. Perhaps it would show entities that have almost the same (or identical) spelling and showing their definition(s) and how recently and how often each was used. Another useful criteria to suggest might be whether an entity has only one value across programs or during the preprocessing of code modules. In the future, when an IX entity will have changing values, knowledge of its recent range of values could also be valuable. The ix_eDict.txt file can be thought of as the seed for "An IX Entity Warehouse" concept.

The most recent list of ix_eDict.txt entities can always be found in Article 215. An inspection of the ix_eDict.txt file in Source 24 will NOT find any actual IX_entities, per se. But it does contain Python statements that assign string values to variables. Each of these names of variables would become a valid entity name if it were prefixed by "&" and suffixed by "|". This particular format was selected so that these "often-used" phrases could easily be "grepped" either manually or programatically. It is a simple matter to create a Python dictionary of entity values by adding the correct prefix and suffix to the left side of each record. Note that many of the variable names on the left side will not only become valid IX entities, but will become special "reserved entity names" for internal use only by the IX Software macro system. The user of IX Software is cautioned to NOT change any of these "reserved entities", without a full understanding of the impact of such a change.


	Source 24 is just a snapshot of the current ix_eDict.txt file as of 2024DApr04. The most 
	recent file can be found in the author's Software Repository in Article 215.
	
Today, to minimize keystrokes, programmers often use abbreviated names for variables and for entities. But thinking ahead to future possible uses of specific entity uses in upcoming code, a programmer might choose to use longer more meaningful entity names such as "&projectAcustomerNameFileIn|" instead of "&fA|". Hopefully, the concept of an "Entity Warehouse" might contribute to our code library and even to our software documentation.

Daily SSD Backups

Since around 2024CMar01, the author has been doing RPi software development on an SSD instead of on a uSD drive. It was way past due, to begin doing daily backups of the SSD. So the author created the following bash scripts to do these backups:
	bu_zip.sh
	duDesktop.sh
	
The first script zips the full contents of the Desktop to a /home/pi/bu/ folder. Currently, the size of the daily zip file is about 64MB, which is a very manageable size, taking only about 15 seconds to zip on a Raspberry Pi 5B. To make the Desktop folder size manageable (now about 400MB), it was necessary to move a few really big files (housing over 25GB of huge Flash Drives) to a new /home/pi/fla/ folder. The zip file to be created each day is stored in /home/pi/bu/ so that it is NOT on the Desktop. The user should "mv" to rename a copy of each zip file placing the "current date" in the name to make it unique. Of course, this daily backup is useless unless it is stored somewhere external to the SSD. The goal is to regularly manually copy this daily zip file to an external flash drive that has been dedicated to this purpose. This external flash drive is itself regularly backed up to a multi-terabyte (TB) external hard drive. Every conscientious programmer creates similar regular backups. Of course, the author also backs up his coding work by documenting it in his web-based articles (such as this one) and in the Software Repository in Article 215.

The second script is not originally named for the french word "du" but because of the Linux CLI command "du" that displays the size of folders. It produces a directory listing of both the Desktop folder and of the /home/pi/bu/ folder on the SSD. This listing shows the total current disk space used in the Desktop and in the "bu" folder. The two new bash scripts (named above) do this job adequately. They have been stored in Desktop/IX_assets on the SSD and in the IX Software repository in Article 215. Because these bash scripts are available in Article 215, it is not necessary to include them in the list of Sources for this article.

A namedtuple Is A class

Consider these Python REPL statements:

	# Why Python is Great: Namedtuples
	# Using namedtuple is way shorter than
	# defining a class manually:
	>>> from collections import namedtuple
	>>> Car = namedtuple('Car', 'color mileage')
	
	# Our new "Car" class works as expected:
	>>> my_car = Car('red', 3812.4)
	>>> my_car.color
	'red'
	>>> my_car.mileage
	3812.4
	
	# We get a nice string repr for free:
	>>> my_car
	Car(color='red' , mileage=3812.4)
	
	# Like tuples, namedtuples are immutable:
	>>> my_car.color = 'blue'
	AttributeError: "can't set attribute"
	
Each namedtuple object has two attribute-like list-items. No more needs to be said.

Other Thoughts

The idiom 'if __name__ == "__main__" ' is often misunderstood. In Source 04, Breuss explains it thoroughly. The code following this idiom (conditional phrase) will only be run, when invoked as a script code (CLI) but not when the module is imported into a module. This idiom permits the coder to save his "testing" code along with the function. When the whole module is imported into another program, the "testing" code following the idiom is not seen as part of the function and therefore will not run.

Source 08 describes in detail many things that the RPi config.txt controls following the release of the Bookworm OS in 2023 for the RPi 5. Most of these things will not concern the neophyte RPi user. Source 09 adds the RPi 5 temperature to the top panel (but my RPi 5 with Bookworm OS needed sightly different steps). Source 10 describes an RPi Pico HAM Transmitter. Not surprisingly, it needs an additional amplifer and filter. Source 11 provides much information about HAM Rx and Tx using an RPi, but it seems like all of its images fail to load (due to minor link-rot).

Source 12 (from 9to5Linux on 2024CMar13) describes many internal OS fixes of bugs that accompanied the RPi 5's release. As a result of the article in Source 12, the author has modified his Environ.sh bash script to document the version of the linux kernel that exists on each SSD or uSD drive. This was done by making use of the linux command "uname -srm" to identify the exact version of the Linux kernel being used.

Sources 15, 16 and 17 are about Gentoo, a Do-It-Yourself Linux Build for the computer of your choice. Apparently all of the source code is available from Gentoo. Using the Gentoo source code, the user decides exactly what his/her Linux should do on the chosen computer, then he/she compiles all of the modules. Google wanted its own Linux-based OS, they studied what was available and then built it . . using Gentoo. One of Google's results is the Chromebook OS. Source 15 attempts to answer "Why should I use Gentoo?" . Source 16 is a Guide to creating your own Gentoo OS for the Raspberry Pi. It is very detailled. Needless to say, Gentoo is geared to advanced programmers. Source 17 is the Wikipedia article about Gentoo. Video Source 02 is a funny spoof about Gentoo. The author suggests that you fast-forward through most of it. :)

Source 18 is a large collection of prints for 3D printed objects. The collection includes Raspberry Pi Cases, electronics gadgets such as boxes to hold resistors and many other unrelated objects.

Source 14 announces a new RPi 5 Hat (shown below left) that will increase WiFi speeds greatly. This Hat is the MPW7 from MCUzone for US$ 10.00 . Brad Linder says: "You can also use it for other M.2 2230 cards, including an AI accelerator featuring Google Edge TPU coprocessors." The MPW7A kit also includes WiFi 7 antennae and an aluminum box to house the RPi 5 and the WiFi 7 board.

(To enlarge .....Click it)
thumb: MCUzone_MPW7.jpg
MCUzone MPW7


(To enlarge .....Click it)
thumb: MCUzone_MPW7A.jpg
MCUzone MPW7A




Source 26 may seem out of place, but it is the most recent definition of the I2S audio standard used everywhere (eg on CDs). Apparently, Phillips (and its current owner NXP, as of 2024) no longer publish it on the web. The author is told that this copy exists, courtesy of "The Wayback Machine". The I2S format interests the author because it is the audio format created by the inexpensive MEMS IC. The MEMS IC will be used in the author's "MAK Pi ADC" project (image below right and Article 165). Source 27 is a very simplified explanation of the I2S format. Knowledgeable electronic engineers need only know that I2S is a data stream of samples of the stereo audio signals. The sample width can be from 4 to 32 bits/sample. A typical sample rate is 44,100 Hz. A typical bit depth is 16 bits. The left and right sounds are sent in alternating samples. Of course, to listen to the re-created audio wave, a speaker must receive an audio wave, not a digital stream. The audio wave is usually generated by a DAC (Digital to Analog Converter). An I2S data stream can be converted and stored in a .wav (WAV) format. A .wav format is the format used for audio CDs. Each WAV sample is stored as a 32 bit unsigned integer. It is a two-channel LPCM (Linear Pulse-Code Modulation) audio wave sampled at 44.1 kHz with 16 bits per sample. A valid WAV file must be less than 4 GiB in size. CDs do not use the exact WAV file format, using instead Red Book audio. The RF64 (used in Europe) is a superior format to the WAV format. The MP3 format is a compressed WAV format. For more information (eg RIFF explanation), look up WAV at Wikipedia.org . Source 28 is an article that describes how to create an I2S file using a MEMS board on a Raspberry Pi (image below left) using Python. The MEMS board includes a tiny microphone. The article in Source 28 shows, in detail, the necessary wiring and Python coding. Apparently, the code requires slight changes as of 2024.

(To enlarge .....Click it)
thumb: MEMS_to_RPi.jpg
MEMS to RPi


     
(To enlarge .....Click it)
thumb: MAKPiADC.jpg
MAKPiADC Layout


Source 29 recommends some 3D Printers. But my friend Myrle (who owns a Prusha Mini ) says that the Chinese 3D printer manufacturer "Bambu Labs" (Source 31) should also be on the list. The Bambu Labs A1-mini (US $249 shown below) and A1 (US $400) and and P1S (US $600) are available. Video Source 04 describes the Bambu Lab A1. Bambu Labs provides a very informative "Wiki", that is found by clicking on the main menu tab labelled "Support", then on "Official Wiki". Myrle says that Prusha is Open Source but Bambu Labs is not. He says that Open Source products usually cost more.

(To enlarge .....Click it)
thumb: Bambu_A1_Mini.jpg
Bambu A1 Mini


Source 30 is a comprehensive article about ChatGPT. It explains how to sign up for free and how to start using it.

Video Source 03 is a highly speeded up (time lapse) video of a person designing, printing and assembling a 3D case for a Riden Power Supply. It opens in a new browser tab, but you might need to click on the new tab to view the video. I skipped quickly through much of it.

Source 32 compares many of today's 3D CAD software systems. Source 33, FreeCAD, is not an entry-level system, but is truly a full-featured 3D CAD system, fully open-sourced, written in Python that permits conversion of a 2D sketch into a 3D drawing (see sample design below). FreeCAD creates downloadable STL drawings ready to be converted into g-code for most 3D printers. The author plans to start his 3D adventure by learning how to use FreeCAD. Video Source 05 shows that any STL 3D can be imported into FreeCAD for some "limited" editting.

(To enlarge .....Click it)
thumb: Glia_Inj_Mold.png
FreeCAD drawing: Glia Injection Mold


The Pimoroni NVMe Base

(To enlarge .....Click it)
thumb: NVIMe_Base.jpg
Pimoroni NVIMe_Base.jpg


In Source 13, Tim Robertson at Pimoroni, teaches us a lot about the Pimoroni "NVMe Base" device (shown above) for the PCIe interface on the Raspberry Pi 5. PCIe devices to date have hooked up SSD drives to the RPi 5. But Tim says that the Pimoroni "NVMe Base" allows graphics cards, USB hubs and more to be attached to the RPi 5. In his article, Tim describes, in great detail, how to connect each end of the "FPC ribbon cable" that connects the NVMe Base to the RPi 5. This is more difficult than it seems, so be patient and read Tim's instructions first. Tim's instructions for installing the OS onto the RPi 5 are also very clear, so read and follow them as well. If this is the first time attaching a SSD to a RPi 5, the author strongly recommends that you follow Tim's instructions exactly, step by step. Cudos to Tim Robertson at Pimoroni for a very good article. 'Tis a pity' that Pimoroni is in Great Britain. Buying directly from Pimoroni is difficult in North America. One US Pimoroni dealer in the PiShop, but their local inventory might be limited.

There is one thing that Tim didn't mention. After the NVMe Base is attached to the bottom of the RPi 5, it is difficult or impossible to insert/remove a uSD card from the uSD connector on the RPi 5. This is because the PCIe ribbon is in the way. It is possible to plug the uSD card into an adapter that plugs into a USB port on the Raspberry Pi. But the author has encountered transfer errors after doing this. Another option is to NOT screw the "NVMe Base" to the RPi 5, leaving it dangling. Fortunately, if the SSD will be permanently attached to the RPi 5, and used to boot up the RPi 5, there will be little need for a uSD card. Perhaps, someday, someone handy with a 3D printer will design an RPi 5 case with a hinge on the edge where the uSD card is located. This would facilitate the swapping of uSD cards without needing to remove the "FPC ribbon cable".

(To enlarge .....Click it)
thumb: Geekworm_X876.jpg
SupTronics X876 V1.1 from Geekworm


The author has found it preferable to mount the chosen SSD on a Geekworm X876 USB3 board (image above) and get the Bookworm OS-64 running on a RPi 4. The Geekworm X876 is described as a "NVMe M.2 SSD Shield" for RPi 4B, manufactured by SupTronics Technologies. When using small SSD drives, such as the 2230, it can be powered by the USB-3 connector alone. (See more about it in Article 206.) Booting it up on the RPi 4 proves that everything on the SSD is working. Then connect it to a USB3 port on the RPi5 to prove that everything is still working, even booting up the RPi 5 from the SSD via the USB3 port. Then the author removes the SSD drive from the USB board and connects it to the Pimoroni "NVMe Base". Then the ribbon cable needs to be connected very carefully. Setting up a few of the options for the PCIe interface is all that is left to do. That is the process that the author followed to attach the SSD (on the Pineapple Top board) to the RPi 5. The transfer speeds are similar whether the USB3 or PCIe interface is used. So the author finally left the SSD attached to the USB3 board. (Any SSD is about 10 times the speed of a uSD card.) This way, the SSD is easily transferred between any RPi 4 or RPi 5. The author likes the PCIe interface, but finds that being able to easily move the SSD between any RPi 4, Pi400 or RPi 5 (using the compatible USB3 port) is more advantageous than the speed improvement.

The author warns that the X876 is designed to only mount onto an RPi 4B. Apparently when large storage SSDs are connected, the USB-3 connector does not provide enough power. See Source 25 for more info about the X876. To resolve this power issue, 2 pogo pins (see above image) reach up to the RPi 4B to touch 5V and ground contacts on the RPi 4B. This solves the problem but makes the board mate only with the RPi 4B. Fortunately when using a small-sized 256 GB 2230 SSD, the extra power from the pogo pins is not needed. So the X876 board can be left dangling. Alternatively, a piece of cardboard can be used to isolate the pogo pins when attached to an RPi 5B. The author advises caution because the pogo pins might damage any computer board other than a Raspberry Pi 4B.

Source 19 is about Dan Bader, a Python expert, who curates "Dan's Python Tricks". Source 20 is about the many ways to use the underscore ("_" aka Dunder) in Python. Source 21 is about special variables in bash. For those readers who don't know how to "save" CLI output in a variable and refer to it later. . . . consider the following:
 
	>OUTPUT=$(ls -1)	#BEWARE: normal round brackets (parentheses)
	>echo "${OUTPUT}"	#BEWARE: curly braces
	
The first command (above) houses the CLI command "ls -1" that lists the current directory in 1 column. But instead of displaying it in the terminal window, the resulting listing is stored in the special system variable named OUTPUT. All CLI variables fully in upper case are special Linux system variables. The second command displays the OUTPUT variable in the terminal window. Note that the ${ } pair of curly braces encloses the variable name OUTPUT on the second command line. Many other special system phrases (or variables) can be enclosed in a ${ } curly brace pair. Some of them are described in Source 21. The CLI commands (below) accomplish the same thing in a user-defined variable instead. If the output is saved to a variable, all LFs and color are lost.
 
	>aOUT=$(ls -1)	#NOTE:	Linux dir > aOUT variable
	>echo $aOUT	#NOTE:	aOUT is a user variable
	

RPi 6 Wish List

In Source 35, Ayush Pande detai0/kgs his wish list for a future RPi 6. In reality, Pande lists his pet peeves concerning the RPi 5. He purports to be a RPi 5 proponent, but his article says otherwise.

Perfect Numbers

In 3 days, on 2024DApr06, the day of the month will be 6 which some people say is a "Perfect Number". The next "perfect numbered" date will be the April 28. Source 23 contains some scribbled noted about Perfect Numbers. In Source 23, there is a URL of a 20 minute video about perfect numbers. The video mentions one of the oldest known mathematical questions which is:
	 Are there any odd perfect numbers?
	
This question is about 3000 years old (in the Euclid era) and has not yet been answered. If you are interested, watch the video or at least fast-forward through it.

Sources

Video Sources

Video Source V217:01: Raspberry Pi 5 Setup: Getting Started Guide (Step By Step) (2:00 min) By Jon Wagner of Wagners Tech Talk c2023LDec01
Video Source V217:02: Gentoo 2013 Base Installation LOL (20:42 min) By Spatry c2014EMay05
Video Source V217:03: 3D Printed RIDEN PSU Case - FINISHED - RD6018W (40:08ms min) By FunctionalPrintFriday c2023ISep01
Video Source V217:04: The Bambu Lab A1-...Best Printer Ever (with sarcasm) (25:16ms min) By Nathan Builds Robots c2024AJan01
Video Source V217:05: FreeCAD...Importing... Editing .STL Files (3:56 ms min) By thehardwareguy 2020KNov03


Web Sources

Web Source S217:01:www commandOS_selectCode_macro.py.txt (text) by D@CC on 2024CMar12
Web Source S217:02:www commandOS_selectCode_macroDef.py.txt (text) by D@CC on 2024CMar12
Web Source S217:03:www selectCode_commandOS_macro.py.txt (text) by D@CC on 2024CMar12
Web Source S217:04:www Explains if __name__ == "__main__" by Martin Breuss before 2024CMar12
Web Source S217:05:www IXp - A Simple Macro Processor for python (182.html) by D@CC on 2024CMar15
Web Source S217:06:www IX/IXc - A General Purpose Macro Processor (183.html) by D@CC on 2024CMar17
Web Source S217:07:www Pi: Creating Python Packages of Functions (174.html) by D@CC on 2022BFeb26
Web Source S217:08:www The config.txt file by Raspberry before 2024CMar24
Web Source S217:09:www RPi temperature by Raspberry Tips before 2024CMar24
Web Source S217:10:www RPi Pi HAM Transmitter using PIO . . by Ash Hill before 2024AJan07
Web Source S217:11:www Ham Radio on an RPi (missing images :( !! ) by Ham Radio Planet before 2024CMar24
Web Source S217:12:www RPi OS. . Linux 6.6 LTS improves RPi 5 Support by Marius Nestor on 2024CMar13
Web Source S217:13:www Getting Started with NVMe Base for RPi 5 by Tim Richardson on 2024BFeb20
Web Source S217:14:www Pi5 PCIe HAT adds a WiFi 7 adapter or M.2 2230 modules by Brad Linder on 2024BFeb17
Web Source S217:15:www Why should I use Gentoo? by Chimmichurri on 2014EMay05
Web Source S217:16:www Raspberry Pi Install Guide for Gentoo by Larry the Cow on 2023LDec30
Web Source S217:17:www Wikipedia: Gentoo_Linux by Wikipedia as of 2024CMar29
Web Source S217:18:www Thingiverse: Many 3D Print Objects by Harebonse as of 2024CMar30
Web Source S217:19:www Dan Bader: Improve your Python Skills by Dan Bader as of 2024DApr01
Web Source S217:20:www Role of Underscore in Python by Datacamp as of 2024DApr01
Web Source S217:21:www bash - special variables by baeldung as of 2024DApr01
Web Source S217:22:www testX_batch.py (text) by D@CC on 2024DApr02
Web Source S217:23:www Perfect Numbers by D@CC on 2024DApr03
Web Source S217:24:www ix_eDict.txt by D@CC on 2023KNov11
Web Source S217:25:www SupTronics X876 (RPi 4B) by Geekworm on 2023KNov11
Web Source S217:26:www I2SBUS.pdf by Phillips on 1986BFeb01
Web Source S217:27:www I2S sound tutorial for esp32 by DiyIOt on 2020FJun30
Web Source S217:28:www Recording Stereo Audio on a Raspberry Pi (using Python) by Joshua Hrisko on 2020KNov22
Web Source S217:29:www Best Budget 3D Printers 2024 by Anj Bryant of Tom's Hardware on 2024CMar31
Web Source S217:30:www ChatGPT Cheat Sheet: .... for 2024 by Megan Crouse of TechRepublic on 2024DApr04
Web Source S217:31:www Bambu Labs US P1S A1 mini 3D printer by Bambu Labs c2024
Web Source S217:32:www 12 Best CAD: 2023 by 3DSOURCES on 2024BFeb20
Web Source S217:33:www 2D and 3D OpenSource FreeCAD v0.21 (in Python) by FreeCAD on 2023HAug01
Web Source S217:34:www FreeCAD Documentation: English by FreeCAD on 2023HAug01
Web Source S217:35:www 6 features that would make the Raspberry Pi 6 a must-buy by QXDA on 2024DApr10


/SourcesEnd

WebMaster: Ye Old King Cole

There is a way to "google" any of the part-numbers, words and phrases in all of the author's articles. This "google" search limits itself ONLY to his articles. Just go to the top of "Articles by Old King Cole" ( www.ephotocaption.com ) and look for the "search" input box named "freefind".

Click here to return to ePC Articles by Old King Cole

Date Written: 2024 C Mar 11
Last Updated: 2024 D Apr 19

All rights reserved 2024 by © ICH180RR

saved in E:\E\2022\DevE\MyPagesE\Globat\ePhotoCaption.com\a\217\217.html
backed up to ePhotoCaption.com\a\217\217_2024CMar25.html

Font: Courier New 10 (monospaced)
/217.html