Index
- Welcome
- Install
- The Directory Structure
- The Code Structure
- Special Files and Directories
- Generating Deliverables
- Tools
- Design Considerations
- FAQ
- Release Notes
Welcome
Welcome to the wonderful world of GBS internals, you daredevil!
This document describes how to maintain GBS.
Note that development and execution directories are the same.
So you can edit and execute immediately.
Install
Prerequistes
- You must have Perl installed. It should be either in the PATH or referred to by EnvVar GBS_PERL_PATH.
Missing Perl modules can be installed via CPAN. More info:
How to install CPAN modules
For Linux you will need xterm. Install with sudo apt install
- Decide where GBS the Development environment must be installed. E.g.:
On Windows: MyDocuments
On Linux: ~/ ($HOME)
- You must have experience with installing and using GBS.
Unzip the Install-file
- Uninstall the current install(s) of GBS, if applicable.
- Unzip the Install file to a new temporary directory (NOT where you want to install GBS).
- Navigate to the top directory: GBS DEV.
- On WIN32 execute:
DevInstall.bat
- On Linux open a terminal and execute:
chmod u+x DevInstall.sh
./DevInstall.sh
exit
Answer the questions.
If it is your first Development Install you will be prompted for a new Project name for the Version.
See below.
After the install GBS is setup in the usual way, defining the necessary shortcuts.
Additionaly a shortcut 'GBS Development Help [<version>]' is created on your DeskTop.
After the Install
- Startup GBS in the 'normal' way by clicking the GBS Startup [cur] icon on the
Desktop.
Note that development and execution directories are the same. So you can edit and execute immediately.
- Enter gbs
- If you want GBS under version-control now is the time to do so.
You WorkArea top directory is .../GBS DEV/WA/main
- Enter genhelp to create the HTML helpfiles
The sources of the Helpfiles are in the directory ../main/doc
Results go to ../main/doc. The files in this directory (not the
sub-directories) should not be placed under version-control!
Note that the first page of the Helpfile (Enter gbshelp) now reflects your
changed build.dat version (project).
You are now ready to go.
The build.dat file
If for some reason the 'Version' (Line 3) in your build.dat still ends with '?', you must edit it.
The build.dat file resides in the main directory.
Edit line 3: Version.
The format is Project-RR.VV(-patch) where:
- Project is your GBS Project name. Keep it short. Keep it unique
May contain only uppercase letters and '_', it may not be 'RMC'
- RR is the Release number. 2 digits.
- VV is the Version number. 2 digits.
- patch is an optional patch-id. Keep it short. May contain letters, numbers and '_'.
beta is an absolute valide choice.
The Perl regular expression is: /^[A-Z_]+-\d\d\.\d\d(|-\w+)$/
The Directory Structure
GBS DEV
- build Here GBS is 'built' for export
- export Here the final GBS ZipFiles go
- WA Here development takes place
- main
- cpan
- devdoc
- doc Do not place the files under version-control!
- doc
- func
- gbsglo
- glo
- gui
- Install
- plugins
- audit
More...
- build
More...
- tool
More...
- setup
- StandAlone
- STUBS
More...
- templates
- Tools
- win32utils
- R.VV_YYYY_MM_DD
- ...
The 'GBS DEV' directory
This the Top directory.
It has a space in its name on purpose.
Regrettably Windows often has spaces in paths.
This makes sure that the code will always run with one or more spaces in the Root path.
It contains the major GBS directories and a few files:
- DevInstall.* files
For the Install after UnZip
- ReadMe.txt file
UnZip and Install instructions
The 'build' directory
Contains 2 directories: gbs and dev
They contain the primary results of the buildgbs and
builddev commands.
They form the basis of the created GBS Zip files.
The 'export' directory
Contains 2 directories: gbs and dev
They contain the final results (.zip files) of the buildgbs and
builddev commands.
Each directory also contains a Release directory.
Here the actually released .zip files are copied to.
The 'WA' directory
Is actualy the GBS_SCRIPTS_ROOT for development.
It contains:
- main is the always present main. It is the top of your work-area.
- Optionaly other (older) GBS-paths (e.g.: RMS-06.01_2021-03-23)
The 'main' directory
Besides all the directories to generate GBS it also contains the toplevel code files.
E.g.: the .sh, .bat and .pl files.
It also contains the build.dat and
development.dat files which will be explained later.
The 'cpan, func, glo, gui, Install, gbsglo and setup' directories
These contain the actual code and will be explained later
The 'devdoc' directory
Contains the Development Documentation
E.g.: This document
Not delivered with GBS-export
The 'doc' directory
Contains the 'sources' for genhelp which generates and checks the Help files.
Results go to the doc directory.
Not delivered with GBS-export
The 'doc' directory
Contains the results of genhelp.
Sub-direcories are images, pdfs and scripts.
The files in this directory should not be under source-control
The 'plugins' directory
Contains the code for the various plugins.
TBS
The 'StandAlone' directory
Contains 'standalone' functions like wordrep, pgrep, etc.
They are fully independed of GBS
The 'STUBS' directory
Contains 'dummy' code that is used for SCA simulation and could be used for non-SCA Audit, Build and Tools simulation
Not delivered with GBS-export
The 'templates' directory
Contains the file-templates for gbsedit
The 'Tools' directory
Contains the tools to manage GBS development
Not delivered with GBS-export
The 'win32utils' directory
Contains a set of Unix (Linux) tools for Windows. Like grep,
find, sed, etc
The Code Structure
This chapter is about the main, cpan, func, glo, gui, Install, gbsglo and
setup directories. They contain the actual GBS code.
Scoping
The GBS code adheres to strict scope control.
Basically:
main → gui → func → gbsglo → glo + cpan (→ PerlLibs)
Install → func → gbsglo → glo + cpan (→ PerlLibs)
setup (→ PerlLibs)
Examples:
- A file in main may 'use' modules from all
- A file in gbsglo may 'use' modules in gbsglo and glo, but not in main, func or gui.
- A file in glo may 'use' modules only in cpan
main
This is the toplevel.
Contains scripts and toplevel (.pl) files.
No modules (.pm files) reside here.
gui
Contains GBS GUI specific modules
func
Contains GBS specific funcionality modules
gbsglo
Contains GBS specific general modules
glo
This is the lowest level.
Contains non-GBS specific modules.
The lowest level module is env.pm which contains basic functions like trace, dump, try, string manipulation, etc.
It is 'used' by (almost) all files and modules.
You want to be very familiar with this file!
cpan
Contains modules copied from CPAN
Only used by corresponding modules in glo.
Install
Contains 2 subdirectories:
- dev - code for install of GBS Development
- gbs - code for install of GBS (user)
The contents is copied to top of ZipFile during buildgbs and
builddev
setup
Contains MAIN scripts and icons for GBS startup.
Subdirectory subs contains sub scripts and programs for the MAIN scripts.
Copied to GBS_BOOT_PATH during GBS Setup and when executing gbssettings 4
Special Files and Directories
build.dat
Resides in main
It defines the application (GBS), current Version, current Version Date and a long name for the Application.
For compatibility reasons its name or layout may never be changed.
No comment lines!
- Line 1: Short Application name. (GBS). Never change!
- Line 2: Version Directory. Obsolete. Empty
- Line 3: Version. (6.01). Format (X*)-R.VV or (X*)-RR.VV. R=Release-number, VV=Version-number. X=[A-Z_]
- Line 4: Version Date. (YYYY-MM-DD). Only in exported version - Filled during buildgbs else comment
- Line 5: Long Application name. (Generic Build Support)
Note that in pre 5.01 versions there were two extra lines (6 & 7) containing licensing information.
build.dat example
GBS
PROJ-06.02
2023-08-12
Generic Build Support
development.dat
Resides in main
It defines:
- That we are running in Development mode.
This file is not copied during 'build'
- Files and Directories to be excluded when:
- Running testall
- Running buildgbs or builddev
- GBS Commands to be excluded when creating the 'All Commands' helpfile (genhelp
development.dat example
Note that not all specified files/directories will be in the delivered ZIP
#
# Development.dat
#
gbs-exclude-dirs
.svn
devdoc
doc
Install
MOCKS
MyStuff
SCM
STUBS
Test
tmp
Tools
util
WebTools
gbs-exclude-files
batchfix.dat
development.dat
test_cond_use.pl
Thumbs.db
help-exclude-dirs
.svn
devdoc
doc
glo
MOCKS
MyStuff
plugins
SCM
setup
STUBS
Test
tmp
Tools
util
WebTools
help-exclude-commands
DevInstall.pl
gbsguiinit.pl
gbsnew.pl
dev-exclude-dirs
.svn
MyStuff
Test
tmp
WebTools
dev-exclude-files
ActionList.xlsx
HowTo.docx
Thumbs.db
The GBS_BOOT directory
This directory is the 'starting-point' for GBS.
From here all other GBS directories can be found.
Hence a fixed location on all platforms:
- Windows
%APPDATA%\.gbs
E.g.: "C:\Users\Randy Marques\AppData\Roaming\.gbs"
- Linux
$HOME/.gbs
E.g.: /home/RandyMarques/.gbs
Note that it is hidden directory.
It is identified by the EnvVar GBS_BOOT_PATH
It is created during the first setup of GBS
It contains: All files needed to startup GBS
No data is kept here. No need to backup.
There is one special file: bootstrap.bat or bootstrap.sh
It contains a single line that defines an EnvVar for the GBS_BASE directory (see next chapter)
Default:
- Windows
%MyDocuments%\.gbs\base
E.g.: set GBS_BASE_PATH=D:\Users\Randy Marques\Documents\.gbs\base
- Linux
$HOME/.gbs/base
E.g.: \export /home/RandyMarques/.gbs/base
The GBS_BASE directory
This directory contains the basic data files for running GBS
It is found via the bootstrap.bat or bootstrap.sh file in the GBS_BOOT directory (see previous chapter)
You can change the location with the gbsmanage 7 command.
You definitely want to backup this directory. That is why it is separated from the GBS_BOOT directory.
It is identified by the EnvVar GBS_BASE_PATH
It is created during the first setup of GBS.
Some of the files it contains follow:
profile.bat/.sh
This file contains EnvVar settings as you can define and change with:
gbssettings 1 (Change Profile Settings)
Most importantly it contains the definitions of GBS_SCRIPTS_ROOT, GBS_SCRIPTS_REL and the combination of the former:
GBS_SCRIPTS_PATH.
So when GBS is started it finds:
- GBS_BOOT_PATH: At fixed location
- GBS_BASE_PATH: Via $GBS_BOOT_PATH/bootstrap.(sh|bat)
- GBS_SCRIPTS_PATH: Via $GBS_BASE_PATH/profile.(sh|bat)
Presto!
Other settings in profile.(sh|bat):
- GBS_HOME_PATH: Path
- GBS_SITE: Name
- GBS_LOG_ROOT: Path
- GBS_BEEPS: Bool
Values can be set and changed with gbssettings 1
config.ini
This file contains internal settings as you can define and change with
gbssettings 2 (Change Config Settings)
Settings in config.ini:
- Generic
- COMPANY: Name
- IGNORES: RegEx
- Per Platform ( MSWin32 & linux )
- EDITOR: Command
- BROWSER: Command
- VIEWER: Command
- NAVIGATOR: Command
- NOTIFY: Bool
- TERMINAL: (terminal-type, fg, bg, rows, cols)
- ADMINISTRATOR: System_names wild comma-list
- INTEGRATOR: System_names wild comma-list
- DEVELOPMENT_ROOT: Path (Not visible and cannot be changed)
- FIRST_INVOCATION: Date (Not visible and cannot be changed)
Values can be set and changed with gbssettings 2
Generating Deliverables
On-the-fly testing
The GBS Development environment contains all the ingredients of 'build' so you can test immediately after you change
something.
It is good practice to run the following tools so now and then to make sure that you are still on track.
(Full explanation in 'Tools' section)
- perlxref
Checks for missing and/or superfluous 'use', prototypes and functions
- perlcheck
Check for unused file global variables
Check for ENV_trace and ENV_dump
- testall
Must run till end
- perlcritic .
Check for QA warnings. No warnings/errors allowed
- batchfix
Fixes the file format (line-endings) of .bat and .sh files
Needed only when batchfiles have changed
- genhelp
Generate the GBS Help files
Needed when:
- Help files (.html) have changed
- The usage of genops.pm have changed (Definition of command arguments in *.pl files)
Do not forget to update the help files in case of relevant changes and update the release Notes.
Preparing for Release
Step by step:
- Run your tests.
- Fix Release Notes
- Edit .../doc/release_notes.html
Update date and possibly version on line +/- 82 and 84
<H2>Build RR.VV: YYYY-MM-DD</H2>
- Check if anything missing
- Run genhelp --date to create the Helpfiles after updating the
version number/dates
- Run the commands as specified above in On-the-fly testing
They must run without errors.
- Run buildgbs to create the export
- Run perlcritic build to run it on the generated export code.
No warnings/errors allowed.
- 'Commit' if you are using SCM
- Run builddev to create the development export.
All in one command:
Releasing
After all of the above:
- Move the newest .zip file in the .../GBS DEV/export/gbs directory to the
.../GBS DEV/export/gbs/Released sub-directory.
- Delete all possibly remaining .zip files in the .../GBS DEV/export/gbs directory
- Move the newest .zip file in the .../GBS DEV/export/dev directory to the
.../GBS DEV/export/dev/Released sub-directory.
- Delete all possibly remaining .zip files in the .../GBS DEV/export/dev directory
- Upload the two .zip files in the Released directories to wherever you want to present it to the world
and then notify the world.
- If you are using SCM:
- Make sure you have 'committed' everything
- Create a new branch: E.g.: ../branches/PROJ-06.01_2021-08-11
- Optionally checkout the branch under the .../GBS DEV/WA directory
- If you are NOT using SCM:
- Optionally copy the tree under .../GBS DEV/WA/main to a new
.../GBS DEV/WA/PROJ-RR.VV_YYYY_MM_DD directory
New Version or Release
Release and Version are identified by PROJ-RR.VV (Previously R.VV was allowed)
Both R and V must be numbers. E.g: 06.01.
Version may be postfixed with a patch or beta indication. E.g.: PROJ-RR.VV-beta
New Version
A new Version is required if the execution of Upgrade in gbsmaint 7 9 is required
because of a minor change in the user directory structure (directories and/or files). Also in case a development
version was downloaded from the GBS SourceForgs page.
- build.dat
Edit build.dat and change the 3rd line to reflect the Version.
E.g.: PROJ-06.02 → PROJ-06.03
- Help
- Fix Release Notes: Edit .../doc/release_notes.html
Duplicate and uncomment the commented Build template.
Update date and possibly version on line +/- 82 and 84:
<H2>Build PROJ-06.01: yyyy-xx-xx</H2>
Modify both lines: in header and actual <H2>
Do not change the layout: genhelp --date uses it to automaticaly
update the date.
- Run genhelp --date to create the Helpfiles
New Release
A new Release is required if the execution of Upgrade in gbsmaint 7 9 is required
because of a major change in the user directory structure and/or commands.
Backwards compatibility may be questionable.
- build.dat
Edit build.dat and change the 3rd line to reflect the Release and Version.
E.g.: PROJ-06.01 → PROJ-07.00
- Help
A new Release requires a new Release Notes Helpfile
- Copy the current .../doc/release_notes.html to
release_notes_old_release_number.html
- Fix anything required in the old release_notes
- In the original release_notes:
- Remove any old release stuff. Keep the commented Build template
- Adjust release-numbers and dates (see 'New Version' above)
- Fill in "Interface changes since previous_release"
- Prepare the "next build" with all the changes for this release
- Add a reference to the previous release html file at the bottom of the page
- Check if anything missing
- Run genhelp --date to create the Helpfiles
Tools
Type toolsman to get overview of GBS development tools
All commands accept --h and --help as parameter
Windows and Linux
buildgbs
'build' a GBS version and create export ZIP-file (Needs Perl Package Archive::Zip)
- Execute 'batchfix' (only on Windows)
- Replace comment lines by empty lines
- Remove leading whitespace
- Output to ../build/gbs/PROJ-RR.VV
- Zip ../build/gbs/PROJ-RR.VV to ../export/gbs/PROJ-RR.VV_YYYY-MM-DD.zip,
adding the contents of Install/gbs at the top
builddev
'build' a GBS Development WorkArea and create export ZIP-file (Needs Perl Package Archive::Zip)
- Copy selected directories and files to ../build/dev/PROJ-RR.VV
- In the build.dat file:
- Adjust Project to have a '?' at the end.
- Increase the VV with 1.
- Zip ../build/dev/PROJ-RR.VV to ../export/dev/PROJ-RR.VV_YYYY-MM-DD.zip,
adding the contents of Install/dev at the top
cleanup
Removes trailing spaces and optimises leading spaces with Tabs
exportgbs
Performs all the generation steps for a complete build of GBS and GBSDEV:
- perlxref
- perlcheck
- testall
- perlcritic .
- genhelp --date
- buildgbs
- perlcritic build
- builddev
Terminates if an error occured.
genhelp
Generates the help files from the docgen directory to the doc directory
- Index is added at the left
- 'All Commands' is generated by parsing the Perl sources
- 'All Docs' is generated
- html references are checked and optionally repaired.
This takes a while so you can skip this with the --noc (nocheck) option
- The --date option updates the version/dates
perlchange
Run the temporary perlchange.pl for automated changes in the GBS code. Caution!
This is just a wrapper for any one-time action you would like to perform on the code.
perlcheck
General checks for Perl files
- Unused file global variables
- Occurrences of uncommented references to ENV_trace and ENV_dump
perlcritic
Static Analysis for Perl files according to the O'Reilly book: Perl Best Practices
perlxref
Creates cross references for Perl files. External Functions and 'use'.
- Full external function cross reference.
Disabled by default. Activate with --full argument
- Not referred external functions
- Local refs only (to external functions)
- Missing proto's, funcs or calls
Must be empty
- Superfluous 'use'
Must be empty
- Missing 'use'
Must be empty
- Not 'use'd files
- Statistics
testall
Runs all perl scripts and shell scripts with argument --help
Windows
batchfix
Fixes the file-format for batch-files (.bat and .sh)
copy_mocks
Copy output-files from the current Root to the MOCKS directory in the same root
Linux
None
Design Considerations
Main requisites
- Must run exactly the same on Windows and Linux on the same directory tree (Shared device).
- Must be fast.
- No dependencies on CPAN downloads.
- No dependencies on other tools (except compiler suite and SCMS).
- Must be very user-friendly.
- Straight forward. Consistent. No exceptions.
- Keep it simple.
Choice of Language
GBS is written in Perl.
I needed a language that will execute exactly the same on various computers (at that time Windows and
UNIX), without being depended on installed libraries, etc.
When I started developing GBS in 2001 the best choice was Perl.
Should I start developing now I would probably chose Python. (But there is nothing wrong with Perl!)
Background
One of the main requisites of GBS is that it must be fast.
Hence command-line interface and no frills.
GBS runs on the command-line. So when you are in command-line you are not 'inside' GBS.
This ensures that you have the full potential of the command-line at your disposal.
GBS keeps track by means of Environment Variables (EnvVars), all starting with 'GBS_'
Every time you enter a GBS command, the Perl code is loaded, precompiled and executed (interpreted).
Most PerlLib modules are re-implemented in GBS.
This is because the PerlLib modules are large (and slightly slower) because they cater for most Operating Systems and
often do not offer the required functionality or too much functionality.
This also ensures constant functionality as in the past functionality was changed between Perl releases.
We want to keep the footprint of the resulting 'executable' as small as reasonably possible.
Since the start of GBS (2001) a lot has changed in Perl. Not all nice new features (like autoload) were
implemented. Who knows in the future.
There are also be some odd things that cannot be changed for historical reasons and choices made in the past.
I.e.: build.gbs was originally called target.gbs (for target-stream), controlled my module build.pm.
This conflicts with the name of build.dat, which cannot be changed. Hence build.dat is controlled by module version.pm
Object oriented approach
The code is basically Object Oriented but without blessing.
All GBS data-files are controlled by a module of the same name.
E.g.: audit.gbs is controlled by audit.pm.
Windows AND Linux
The requirement to run under both Windows and Linux poses a number of restrictions:
- File and path names are restricted to what fits both.
- File and path names are case-sensitive.
- EnvVar names are case-sensitive. All uppercase.
Communicating with the user
Messages to the user are always prefixed with:
- From Perl: The Perl main-file name in uppercase followed by ': '
- From Shell: The Shell file name in uppercase followed by '_: '
- From Perl generated script: The Perl main-file name in uppercase followed by '*: '
You must use the following functions for User I/O:
- ENV_say
- ENV_print
- ENV_whisper (print only with --verbose)
- ASK_...
- ASKMNEM_...
- ASKEDIT_...
- ASKFORM_...
They allow redirection to and from other devices, like a GUI window.
The output functions also take care of window width (wrapping).
The ASK_... functions guarantee a standard look-and-feel for user input
Debugging
You may want to look at the following functions:
- ENV_trace
- ENV_dump
- ENV_stackdump
Errors and Condition Handling
You may want to look at the following functions:
- ENV_sig
- ENV_file_sig
- ENV_try
Command arguments
All command arguments are handled by one of the two packages:
- glo::args.pm
Very simple handling. Not for user commands.
- glo::genopts.pm
Extensive handling. For all GBS commands.
Ensures proper handling of --h, --help and --verbose.
Parsed by genhelp to generate the 'All Commands' page
Paths
GBS works with paths in Perl (Unix) format and are case-sensitive.
As soon as a path 'enters' GBS it is converted to Perl format (Windows only).
The functions ENV_getenv and ENV_setenv consider EnvVars ending with _PATH, _ROOT or _CMD to be paths and will
automatically do the proper conversions.
E.g.: my $this_perl_path = ENV_getenv( $GBS_SCRIPTS_PATH);
It remains so until it has to 'exit' GBS (E.g.: execute a shell command). Then it is converted to a different
variable prefixed with os_.
E.g.: my $os_this_path = ENV_os_path( $this_path);
Nomenclature
- Directory path: path
- File path: filespec
- Directory: dir
- File name: name
- File type: type
- File: name+type
Naming Conventions
- Abs. Directory path: postfix _path or _root
- Rel. Directory path: postfix _relpath or _relroot
- Abs. File path: postfix _filespec
- Rel. File path: postfix _relfilespec
- Directory: postfix _dir
- File (Name+Type): postfix _file
System Global Variables
Between execution of GBS commands, information is maintained in Environment Variables (EnvVars).
They are full uppercase and start with GBS_.
When a GBS command starts, it will read all the GBS EnvVars and place them in System Global Variables,
all starting with GBS::
So GBS_SCRIPTS_PATH goes to GBS::SCRIPTS_PATH.
These EnvVars are controlled by the gbsglo::gbs_pre.pm and gbsglo::gbs.pm packages.
When a GBS command starts, glo::env.pm will also define some basic system globals:
- PLATFORM
- OTHER_PLATFORM
- IS_WIN32
- IS_LINUX
- SHELL_FILETYPE
- IS_INTERACTIVE
- IS_DEVELOPMENT
- PROGRAM_NAME
- TMPDIR_PATH
- APP_NAME
- APP_LONGNAME
- APP_VERSION
- APP_BUILD_DATE
- APP_SCRIPTS_PATH
- APP_BOOT_PATH
- APP_BASE_PATH
- APP_PERL_CMD
A few are copied to $GBS::...
There are also some constants defined in glo::scm.pm:
- $SCM::SVN_NAME = 'SubVersioN';
- $SCM::GIT_NAME = 'Git';
- $SCM::NO_SCMS_NAME = 'No_SCMS';
Some GBS commands change these EnvVars.
So at the end of the execution of a command, the changed EnvVars are written to a batchfile that is executed after the
Perl code exits, updating the EnvVars in the command-line environment.
You are not allowed to change a System Global Variable directly. You must use the GBSENV_setenv function in
gbsglo::gbsenv.pm.
The function controlling the export to a batch file is RESULT_write_script.
Coding and Style Standard
The code adheres to the GBS Coding and Style Standard
About TABs and SPACEs
The GBS sources use the physical TAB character (HT, X09).
The physical TAB character is interpreted as modulo 8.
This is the standard interpretation as used by most tools and the command line of most OS.
Something different is how the pressing of the TAB key is interpreted.
On OS command-lines it often invokes auto-completion.
In Editors it usually invokes indentation, often modulo 4 spaces
It is not wise to change the interpretatiom of the TAB character to anything other than 8: You will have to change
all tools and even the OS if you want to keep your layout when using commands like more, type or cat.
Proper modern editors will allow definition of hard-tabs (do notchange) and soft-tabs (indentation - set as you
please).
Some even allow definition of 'intelli-tabs' creating a proper mix of TABs and SPACEs.
FAQ
TBS
Release Notes
Build RMC-06.02: 2024-04-29
New/Changed Functionality
- Brand new. So none
Problems solved
- Brand new. So none
GBS_Development.html
Copyright 2021 © Randy Marques, Netherlands