--- /dev/null
+# CNF Specifications - Data and SQL Document Section
+
+
+<span id="content" class="span-content">This section is part of the main ⇾ [CNF specifications](docs/PerlCNF/Specifications_For_CNF_ReadMe.md)</span>
+
+
+## CNF Data and SQL Document Section
+
+CNF scripted delimited data property, having uniform table data rows.
+The data is converted by default into arrays as rows. If it contains a header (recommended) these becomes the header of the data. A table header will be produced that contains the row's column's information, like name and type specs.
+The table header contains the actual data reference.
+
+This table is and specs are used to access, map and translate the data.
+There is a basic header being created by the parser and placed into its data hash.
+
+Example on how to access the data table of a property.
+
+```Perl
+my $table = CNFParser->new(...)->data(){'MyDataProperty'};
+my $header = $$table->{header};
+my $lbls = CNFMeta::_deRefArray(@$$header[0]);
+my $spec = CNFMeta::_deRefArray(@$$header[3]);
+my $data = CNFMeta::_deRefArray($$table->{data});
+```
+
+## SQL Based CNF Instruction
+>
+> The following reserved words or instructions are data and SQL related.
+<style>
+ .CNF_md_table{
+ background-color: beige;
+ td{
+ border-right: solid black 1px;
+ border-bottom: solid #ab0433 1px;
+ }
+ }
+</style>
+<table class="CNF_md_table">
+ <thead>
+ <tr>
+ <th>Instruction</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>DATA</td>
+ <td>CNF scripted delimited data property, expected to have uniform table data rows, where very first is the table header if provided.</td>
+ </tr>
+ <tr>
+ <td>FILE</td>
+ <td>CNF scripted delimited data property is in a separate data file location, from the current script.</td>
+ </tr>
+ <tr>
+ <td>TABLE</td>
+ <td> SQL create Table body statement part. These can unfortunately differ from one database system to another.</td>
+ </tr>
+ <tr>
+ <td>INDEX</td>
+ <td>SQL create Index body. Indexes provide faster and more efficient data searches and updates.</td>
+ </tr>
+ <tr>
+ <td>VIEW</td>
+ <td>SQL create View body. Views provide a faster and narrower snapshot of what can be a lot of data.</td>
+ </tr>
+ <tr>
+ <td>SQL</td>
+ <td>Direct SQL statement without any variable unknowns.</td>
+ </tr>
+ <tr>
+ <td>MIGRATE</td>
+ <td>Same as SQL instruction, but migrations related towards tables and the application version.</td>
+ </tr>
+ </tbody>
+</table>
+
+## CNFMeta SQL Related
+>
+>Meta tags in use are DATA instruction related.
+
+1. Meta for what actions on data and table is required.
+ 1. <code>_SQL_PostgreSQL_</code>
+ - Data is to be linked or related to a Postgres SQL Database, default is SQLite.
+ - In cases where the SQL underlining driver is something different from SQLite or Postgres this should be set to true and hope for the best.
+ 2. <code>_SQL_TABLE_</code>
+ - Create or link to an SQL Table equivalent.
+ 3. <code>AUTO_NUMBERED</code>
+ - ID column is auto numbered from 1-* if an '#' is found as the value.
+ 4. <code>_HAS_HEADER_</code>
+ - Specifically instruct first record is the header labels and specs for the columns.
+ 5. Column CNF type is optionally placed next to header label, default column type is TEXT.
+ - i.e. <code> `ID _INT_`Name _TEXT_`Entered _DATE_`Active _BOOL_~ </code>
+
+### Table Column CNF Data Types
+
+1. Column Data type are geared towards the CNF data type provision.
+ 1. See %CNFMeta::CNF_DATA_TYPES global.
+ - BOOL INT NUMBER DATE TEXT
+ 2. The date type is specific in CNF set to an universal date and time stamp of <code>YYY-MM-DD hh:mm:ss</code>.
+ 3. Global $CNFMeta::SQL_CNF_VAR_LENGTH = 2024; can be changed to provide for the __TEXT__ limit translation of specified columns for a database. You can expect database errors if inserting texts that is larger than this variable limit
+
+### CNFSQL Package
+
+- Accessed as a global instance of CNFParser, it is the SQL based utility for external database interactions.
+- Its actual use is expected to be via a PLUGIN instructed CNF property, to provide driver to be used, credentials and other details.
+- Example method to use it: `my $sql = CNFParser->new()->SQL();`
+- Required method when using tables with a database based storage:`$sql->initDatabase($db, $do_not_auto_synch, $map)`
+- The __DataProcessingPlugin__ provides the concept of processing the scripted data to to CNF table column type conversion.
+ - The parser provided header gets updated by this plugin.
+ - The parsers natural processing mechanism will require all SQL properties in raw form before a plugin is called to process further.
+
+>The following is extract from the original ⇾ <span id="content"> [specifications](docs/PerlCNF/Specifications_For_CNF_ReadMe.md)</span>.
+
+### CNF DATA Instruction
+
+CNF Instructions are parallel with the reserved words. Which means, you can't use these reserved words to replace with your own instruction. This section explains in more detail, what these are, and how are implemented.
+
+1. DATA
+ 1. Data is specifically parsed, not requiring quoted strings and isn't delimited by new lines alone.
+ 2. Data rows are ended with the __"~"__ delimiter. In the tag body.
+ 3. Data columns are delimited with the invert quote __"`"__ (back tick) making the row.
+ 4. First column can be taken as the unique and record identity column (UID).
+ 1. If no UID is set, or specified with # or, 0, ID is considered to be auto-numbered based on data position plus 1, so not to have zero IDs.
+ 2. When UID is specified, an existing previous assigned UID cannot be overridden, therefore can cause duplicates.
+ 3. Data processing plugins can be installed to cater and change behavior on this whole concept.
+ 5. Data is to be updated in storage if any column other than the UID, has its contents changed in the file.
+ 1. This behavior can be controlled by disabling something like an auto file storage update. i.e. during application upgrades. To prevent user set settings to reset to factory defaults.
+ 2. The result would then be that database already stored data remains, and only new ones are added. This exercise is out of scope of this specification.
+ 6. First row labels the columns and is prefixed to PerlCNF datatype.
+ 1. Generic data rows, do not require, but processors and plugins would definitely need it.
+ 2. Current known types are, __@__{label} as CNFDateTime, __#__ for number or if in row autonumbering to be applied. Text is default without signifier.
+
+ ```CNF
+ <<MyAliasTable<DATA
+ 01`admin`admin@inc.com`Super User~
+ 02`chef`chef@inc.com`Bruno Allinoise~
+ 03`juicy`sfox@inc.com`Samantha Fox~
+ >>
+ ```
+
+2. FILE
+ 1. Expects a file name assigned value, file containing actual further CNF DATA rows instructions, separately.
+ 2. The file is expected to be located next to the config file.
+ 3. File is to be sequentially buffer read and processed instead as a whole in one go.
+ 4. The same principles apply in the file as to the DATA instruction CNF tag format, that is expected to be contained in it.
+
+ ```CNF
+ <<MyItemsTbl<FILE data_my_app.cnf>
+ ```
+
+## CNF Meta Instructions
+
+> Various Meta instructions are available to aid processing and decision-making
+
+***
+
+ Document is from project ⇾ <https://lifelog.hopto.org/gitweb/>
+
+ An open source application.
+
+ Please refer to this specifications header and item or section points, on any desired clarifications or research/troubleshooting inquires.
+
+<center>Sun Stage - v.1.0 2024</center>
3. It is not recommended to use reserve anons as their value settings, that is; can be modified in scripts for their value.
4. Reserve anon if present is usually a placeholder, lookup setting, that in contrast if not found there, might rise exceptions from the application using CNF.
- ```CNF Example 2:
+ ```CNF Example 3:
Notice to Admin, following please don't modify in any way!
Start --> {
<<^RELEASE>2.3>>
2. For arrays, values are delimited by new line or a comma.
3. White space is preserved if values are quoted, otherwise are trimmed.
- ```TEXT
+ ```
Format: <<@<{T}NAME>DATA>>>
Examples:
- # Following provides an array of listed animal types.
+ # Following provides an array of listed animal types. Notice how you don't need text to be quoted.
<<@<@animals<Cat,"Dog","Pigeon",Horse>>>
# Following provides an array with numbers from 0..8
<<@<@numbers<1,2,3,4,5
## Instructions And Reserved Words
-Quick Jump: [Introduction](#introduction) | [CNF Tag Formats](#cnf-tag-formats) | [CNF Collections Formatting](#cnf-collections-formatting) | [Scripted Data Related Instructions](#scripted-data-related-instructions)
+
+*Quick Jump* :[
+ [Introduction](#introduction) |
+ [CNF Tag Formats](#cnf-tag-formats) |
+ [CNF Collections Formatting](#cnf-collections-formatting) |
+ [Scripted Data Related Instructions](#scripted-data-related-instructions) |
+ <span id="content" class="span-content">[CNF Data Tables](Specifications_For_CNF_Data_Tables.md).</span>
+]
1. Reserved words relate to instructions, that are specially treated, and interpreted by the parser to perform extra or specifically processing on the current value.
2. Reserved instructions can't be used for future custom ones, and also not recommended tag or property names.
3. Macro format specifications, have been aforementioned in this document. However, make sure that your macro a constant also including the *$* signifier if desired.
- LIB - Loads dynamically an external Perl package via either path or as a standard module. This is ghosting normal 'use' and 'require' statements.
- DO - Performs a controlled out scope evaluation of an embedded Perl script or execution of a shell system command. This requires the DO_ENABLED constance to be set for the parser. Otherwise, is not enabled by default.
+ - APP_SETTINGS - Provides external expected application settings defaults to the configuration.
+ 1. These are added and processed in place as they appear sequentially in the script.
+ 1. It can be made possible in the future, to meta instruct to run APP_SETTING at the processing or post processing stages of CNF parsing.
+ 2. These can be externally added constance type CNF items if are found missing or not specified in current cnf file or from includes.
+ 3. An application usually obtains its settings object as an CNF property. Decoupling the CNF from handling this, making it abstract to the parser.
+ - ARGUMENTS - Special case command line options to CNF anonons as default value/settings arguments and conversions.
## CNF META Instructions
4. It is recommended to setup a 'TZ' constant or the default system locale will be used for timezone if available on your system. ```<<TZ<CONST>{country}/{city}>>```
1. Timezone string is in form of {country}/{city}, note that these are not available for every possible city or town in the world.
2. It is not possible or recommended of having date times in config from different time zones, so this TZ value is constant once set.
+10. ARGUMENTS (NEW FEATURE - 20250625)
+ 1. CLI options and arguments functionality merging with CNF provided defaults.
+ 2. CLI arguments to an application have precedence to CNF set (anons) and over those by the application provided defaults, if any.
+ 3. Protocol for naming and assigning of arguments, is option and/or argument name and value pair.
+ 1. ```--some_option``` translates to: ```['-some_option',1]```
+ 2. ```--some_option=0``` translates to: ```['-some_option',0]```
+ 3. ```-some_option``` translates to: ```['-some_option',0]```
+ 4. ```-Some-Option=1``` translates to: ```['-some_option',1]```
+ 5. Illegal is to use at command line or query in app unprefixed with an dash: ```some_option=1```
+ 6. CNF based apps should use all lowercase arguments, multiple for words preferred are to separated with underscore, but dash is also taken into account.
## Sample Perl Language Usage
```
***
+*Quick Jump* :[
+ *[Introduction](#intoduction)* |
+ *[CNF Tag Formats](#cnf-tag-formats)* |
+ *[CNF Collections Formatting](#cnf-collections-formatting)* |
+ *[Instructions & Reserved Words](#instructions-and-reserved-words)* |
+ *[Scripted Data Related Instructions](#scripted-data-related-instructions)* ] |
+ *[Sample Perl Language Usage](#sample-perl-language-usage)* ]
+ <span id="content" class="span-content">[CNF Data Tables](Specifications_For_CNF_Data_Tables.md).</span>
+]
- Document is from project ⇾ <https://github.com/wbudic/PerlCNF/>
+***
- An open source application.
+ Document is from project ⇾ <https://lifelog.hopto.org/gitweb/><br>
+ The original project location ⇾ <https://github.com/wbudic/PerlCNF/>
- Please refer to this specifications header and item or section points, on any desired clarifications or research/troubleshooting inquires.
+ An open source application.
-<center>Sun Stage - v.3.0 2023</center>
+<center>Sun Stage - PerlCNF v.3.3 2025</center>
--- /dev/null
+#!/usr/bin/env perl
+use v5.38;
+use warnings; no warnings 'once';
+use strict;
+use lib::relative "system/modules";
+ require CNFParser;
+ require CNFDateTime;
+ require CNFScriptToANSIFormatter;
+use Pod::Usage;
+use Term::ANSIColor qw(:constants colored);
+use Getopt::Long qw(Configure);
+
+my $data_dir = ""; #$ENV{HOME}."/.config";
+my $data_file = "$0.cnf.dat";
+ $data_file =~ s/^(\/.+)\///gs;
+my $history = "";
+my $command = "install";
+my $TZ = "Australia/Sydney";
+my $XZ = 0;
+my $showLog = 0;
+Configure( "default", "pass_through", "bundling_override", "require_order");
+my $result = GetOptions(
+ "help|h|?" => \&help,
+ "data_dir=s" => \$data_dir,
+ "data_file=s" => \$data_file,
+ "tz|timezone=s" => \$TZ,
+ "xz=i" => \$XZ,
+ "hist|history" => \$history,
+ "cmd|command|c=s" => \$command,
+ "apt-help" => sub{say `apt-get --help`; help()},
+ "showLog" => \$showLog
+ );
+my $packages = join ' ', @ARGV;
+ $data_dir .= "/" if $data_dir && $data_dir !~ m/ \/$/;
+ $data_file = $data_dir . $data_file;
+my $CNF = CNFParser -> blank({XZ_STORE=>$XZ,STACK_TRACE=>1});
+if($showLog){
+ undef $command; if( -f $data_file ){
+ say ${CNFScriptToANSIFormatter::_format( &loadDataCNFFile )};
+ }else{
+ say "There is no -> $data_file yet!"
+ }
+}
+elsif($history) {
+ &history_list; exit 1;
+}
+elsif ( -f $data_file ) {
+ &loadDataCNFFile;
+}
+else {
+ &createDataCNFFile;
+}
+
+if(!$command){
+ $packages =~ m/^(\w*)\s*(.*)/g;
+ $command = $1;
+ $packages = $2;
+}
+if ( !$command || $command eq 'install' && !$packages )
+{say "Hey ", RED, $ENV{USER}, RESET,"! What you wanna do? Maybe try option [ $0 --help ]?";exit 0}
+
+$| = 1; # Disable buffering on STDOUT.
+
+print BOLD ON_GREEN "[> ", RESET;
+say " Command is: ", GREEN, "sudo apt-get", RED, " $command ", BLUE, $packages, RESET;
+
+print BLINK RED "Please wait, don't debate...",RESET;
+
+if ( defined($command) and $result = `sudo -k apt-get --yes $command $packages 2>&1` ) {
+
+ print chr(0x08) for(1..28);
+ print BOLD GREEN ">> ", RESET;
+
+ my @res = split "\n", $result;
+ my $log = 1; my $output =""; my ($aptResult, $f_app_ins, $f_app_rem, $appRemoved,$appInstalled);
+
+ foreach my $next(@res){
+ if($next =~ m/sudo: a terminal is required to read the password;/){
+ die $next
+ }elsif ( $next =~ m/E:\s*(.*)/s ) {
+ say "Error: $1";
+ $output .= "$next\n"; $log = 0
+ }
+ elsif ($f_app_ins){$f_app_ins = 0; $next =~ s/^\s*//; $appInstalled .= "$next,"}
+ elsif ($f_app_rem){$f_app_rem = 0; $next =~ s/^\s*//; $appRemoved .= "$next,"}
+ elsif ( $next =~ m/The following NEW packages will be installed:\s*(.*)/s){
+ $f_app_ins = 1
+ }
+ elsif ( $next =~ m/The following packages will be REMOVED:\s*(.*)/s){
+ $f_app_rem = 1
+ }
+ elsif ( $next =~ m/(.*)\sis already the newest version\s(.*)/ ){
+ my ($p,$v)=($1,$2);
+ say $next;
+ $packages =~ s/$p/"*$p-$v"/e;
+ }
+ elsif ($next =~ m/(\d+) to upgrade, (\d+) to newly install, (\d+) to remove/ ){
+ if($1==$2==$3==0){
+ say "Command not will be logged." ;
+ $log = 0;
+ }else{
+ $aptResult = {upgrades=>$1,installs=>$2,removes=>$3}
+ }
+ $output .= "$next\n"; say $next;
+ }
+ elsif ( $next !~ m/\.\.\.(\s*|\d+\%|\s*Done)/ ) {
+ $output .= "$next\n"; say $next;
+ }
+ }
+ if($log){
+ my $date = CNFDateTime->now( { TZ => $TZ } )->toDateTimeFormatWithZone();
+ my $cnf_data_log = "$date`$command`$packages`~";
+ my $cnf_output_log = "$date`$packages`\n$output`~\n";
+ say $cnf_data_log;
+ $CNF->parse( undef,qq(<< APT_LOG <DATA> __HAS_HEADER__
+ TimeStamp _DATE_ `Command`Package~
+ $cnf_data_log
+ >>));
+ $CNF->parse(undef,qq(<< APT_OUTPUT_LOG <DATA> __HAS_HEADER__
+ TimeStamp _DATE_ `Package`Output~
+ $cnf_output_log
+ >>));
+
+ if($appInstalled){
+ my @installed = split ',', $appInstalled;
+ foreach my $package(@installed){
+ my $info = `apt-cache show $package`;
+ $info =~ m/^Description[-\w]*:\s*(.*?)\./ms; my $desc = $1;
+ $info =~ m/(^Version\s*:.*?\n)/ms; my $ver = $1; $ver =~ m/.*?:(.*)/; my $version = $1;
+ $info =~ m/(^Origin\s*:.*?\n)/ms; my $origin = $1;
+ $info =~ m/(^Depends\s*:.*?\n)/ms; my $depends = $1;
+ $info =~ m/(^Recommends\s*:.*?\n)/ms; my $recommends = $1;
+ $info =~ m/(^Filename\s*:.*?\n)/ms; my $filename = $1;
+ $info =~ m/(^Maintainer\s*:.*?\n)/ms; my $maintainer = $1;
+ my $log_row = "$date`$package`$version`$desc`\n$ver$origin$depends$recommends$filename$maintainer`~\n";
+ $CNF->parse(undef,qq(
+ << CNF_APP_APT_INSTALLED_PACKAGES <DATA> __HAS_HEADER__
+ Install Date _DATE_ `Package`Version`Description`Info~
+ $log_row
+ >>))
+ }
+ }
+ if($appRemoved){
+ my @removed = split ',', $appRemoved;
+ foreach my $package(@removed){
+ my $info = `apt-cache show $package`;
+ $info =~ m/^Description[-\w]*:\s*(.*?)\./ms; my $desc = $1;
+ $info =~ m/(^Version\s*:.*?\n)/ms; my $ver = $1; $ver =~ m/.*?:(.*)/; my $version = $1;
+ $info =~ m/(^Filename\s*:.*?\n)/ms; my $filename = $1;
+ my $log_row = "$date`$package`$version`$desc`$filename~";
+ $CNF->parse(undef,qq(
+ << CNF_APP_APT_REMOVED_PACKAGES <DATA> __HAS_HEADER__
+ Removal Date _DATE_ `Package`Version`Description`Filename~
+ $log_row
+ >>))
+ }
+ }
+
+ }else{
+ exit 1;
+ }
+ &writeDataCNFFile;
+ exit 0;
+}
+else {
+ say 'Command was not accepted!';
+ exit 0;
+}
+
+sub history_list {
+ &loadDataCNFFile;
+ if(@ARGV>0){
+ foreach my $next (@ARGV) {
+ history_list_by_package($next)
+ }
+ }else{
+ history_list_by_package('*')
+ }
+}
+
+sub history_list_by_package{
+
+ my $package = shift;
+ my @collen;
+ my $header_shown;
+ my $table_spec = $CNF->data()->{APT_LOG};
+ my @head = @{ $$table_spec->{header} };
+ my @data = @{ $$table_spec->{data} };
+
+ foreach my $r_ptr (@data) {
+ my @row = @$r_ptr;
+ my $date = $row[0];
+ my $ap_cmd = $row[1];
+ my $packages = $row[2]; $packages ="" if !$packages;
+ if ( $package ne '*' && defined($packages) && $packages !~ m/$package/ ) {
+ next;
+ }
+ else {
+ if ( !$header_shown ) {
+ my $header = ${ $head[ $CNFMeta::TABLE_HEADER{F_NAMES} ] }; $header =~ s/"/ /g;
+ my @col_names = split ',', $header;
+ $header = join ' | ', @col_names;
+
+ $collen[@collen] = length( $date );
+ $collen[@collen] = length( $col_names[1] );
+ $collen[@collen] = length( $col_names[2] )+2;
+
+ say "-"x$collen[0], ' + ', "-"x$collen[1], ' + ', "-"x$collen[2];
+ say fit($col_names[0],$collen[0]),fit($col_names[1],$collen[1]),$col_names[2];
+ say "-"x$collen[0], ' + ', "-"x$collen[1], ' + ', "-"x$collen[2];
+ $header_shown = 1;
+ }
+ say fit($date, $collen[0]), fit($ap_cmd, $collen[1]), $packages
+ }
+ }
+}
+
+sub fit {
+ my ( $txt, $len ) = @_;
+ my $l = $len - length($txt);
+ $l = 0 if $l < 1;
+ return $txt . ' ' x $l . " | ";
+}
+
+sub createDataCNFFile {
+ my @content = <DATA>;
+ $CNF->parse( undef, \@content );
+}
+
+sub loadDataCNFFile {
+ $CNF->loadDataFile($data_file);
+}
+
+sub writeDataCNFFile{
+ $CNF->writeToDataFile($data_file);
+}
+
+sub help {
+say qq(Apt Installation with PerlCNF v.).CNFParser::VERSION().qq(
+
+This script runs sudo apt install, keeping in CNF data script format an log, of apt-get packages installed.
+Useful is also tool to list all what have installed using it, to output to an bash script format.
+So when that new computer arrives, you edit and run that bash script, on it to install, what commonly use.
+
+Note - You can modify this script to suit your needs for provided default options.
+
+Usage: perl sudo_apt.pl install {some_package}
+Available options:
+ --help|? - This Help.
+ --apt-help - To obtain available apt-get commands to ie. -> apt-get --help
+ --data_dir={\$path} - Where to log history, default is "$data_dir".
+ --data_file={name} - Name of log to use, default is "$data_file".
+ --showLog - Prints the whole log sofar to the STDOUT, useful if --xz=1.
+ --tz|timezone={zone} - Timezone to use, default is "$TZ".
+ --xz={1/0} - Compress the CNF data log data, default is true.
+ --hist|history - Display the CNF data log data, other options are ignored.
+ --generate-batch - Generate install batchfile from history.
+
+ --cmd|command|c={apt command} The default apt-get command to use is assumed to be 'install',
+ followed by packages or list of packages.
+);
+exit
+}
+
+__END__
+
+__DATA__
+!CNF3.3.4
+<< APT_LOG <DATA> __HAS_HEADER__
+TimeStamp _DATE_ `Command`Package~
+>>
+
+<< CNF_APP_APT_INSTALLED_PACKAGES <DATA> __HAS_HEADER__
+Install Date _DATE_ `Package`Description`Full Info~
+>>
+++ /dev/null
-#!/usr/bin/env perl
-use warnings; use strict;
-use Syntax::Keyword::Try;
-#no critic "eval"
-
-###
-# To debug in vscode you need the extension LanguageServer and Debuger by Gerald Richter
-# the optional Perl Navigator uses also the LanguagerServer but isn't one.
-# To debug in vs code the following local use lib ... have to be commented out.
-# Do not forget to uncoment them when commiting or using outside of vscode.
-# Setup settings.json now too.
-#
-# Here is LanguageServer settings example for settings.json
-# requires full paths for Gerald's extension because it is dumb to reslove this.
-#
-# "perl.perlInc": [
-# "tests",
-# "system/modules"
-# ]
-# After that disable all the followin use lib ... statements:
-###
-use lib "tests";
-use lib "local";
-use lib "system/modules";
-
-require TestManager;
-my $test = TestManager -> new($0);
-my $cnf;
-
-try{
- ###
- # Test instance creation.
- #
- die $test->failed() if not $cnf = CNFParser->new();
- $test->case("Passed new instance CNFParser.");
- #
- $test-> nextCase();
- #
-
- #
- $test->done();
- #
-}
-catch{
- $test -> dumpTermination($@);
- $test -> doneFailed();
-}
-
-#
-# TESTING ANY POSSIBLE SUBS ARE FOLLOWING FROM HERE #
-#
\ No newline at end of file