]> lifelog.hopto.org Git - LifeLog.git/commitdiff
CNFServices started with a bang, RSSFeedsPlugin.
authorWill Budic <redacted>
Sat, 16 Sep 2023 04:55:26 +0000 (14:55 +1000)
committerWill Budic <redacted>
Sat, 16 Sep 2023 04:55:26 +0000 (14:55 +1000)
16 files changed:
.gitignore
htdocs/cgi-bin/CNFServices.cgi [new file with mode: 0755]
htdocs/cgi-bin/index.cgi
htdocs/cgi-bin/login_ctr.cgi
htdocs/cgi-bin/main.cgi
htdocs/cgi-bin/rss_output/tree_feed_CPAN.cnf [new file with mode: 0644]
htdocs/cgi-bin/system/modules/CNFDateTime.pm [new file with mode: 0644]
htdocs/cgi-bin/system/modules/CNFNode.pm
htdocs/cgi-bin/system/modules/CNFParser.pm
htdocs/cgi-bin/system/modules/HTMLIndexProcessorPlugin.pm
htdocs/cgi-bin/system/modules/MarkdownPlugin.pm
htdocs/cgi-bin/system/modules/RSSFeedsPlugin.pm [new file with mode: 0644]
htdocs/cgi-bin/wsrc/feeds.css [new file with mode: 0644]
htdocs/cgi-bin/wsrc/main.js
install_cpan_modules_required.pl [new file with mode: 0755]
rss_output/tree_feed_CPAN.cnf [new file with mode: 0644]

index fab73206858174db512b872c375a1ed8c76e2767..bbd220e9b06ef49b7019c17de875a448f0779b19 100644 (file)
@@ -24,3 +24,5 @@ tags
 bck_*
 dbLifeLog/current_theme
 lighttpd.conf
+dump_*
+*.rdf
diff --git a/htdocs/cgi-bin/CNFServices.cgi b/htdocs/cgi-bin/CNFServices.cgi
new file mode 100755 (executable)
index 0000000..9a83aa4
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/env perl
+#
+#
+use v5.30;
+use strict;
+use warnings;
+use Exception::Class ('CNFHTMLServiceError');
+use Syntax::Keyword::Try;
+use CGI;
+use CGI::Session '-ip_match';
+use feature qw(signatures);
+##
+# We use dynamic perl compilations. The following ONLY HERE required to carp to browser on
+# system requirments or/and unexpected perl compiler errors.
+##
+use CGI::Carp qw(fatalsToBrowser set_message);
+
+BEGIN {
+   sub handle_errors {
+      my $err = shift;
+      say "<html><body><h2>Server Error</h2><code style='color:crimson; font-weight:bold'>$err<code></body></html>";
+   }
+   set_message(\&handle_errors);
+}
+
+use lib "system/modules";
+require CNFParser;
+require CNFNode;
+
+our $GLOB_HTML_SERVE = "'{}/*.cgi' '{}/*.htm' '{}/*.html' '{}/*.md' '{}/*.txt'";
+our $script_path = $0; $script_path =~ s/\w+.cgi$//;
+
+exit &CNFHTMLService;
+
+sub CNFHTMLService {
+    my ($cgi,$ptr)   = (CGI -> new(),undef); $cgi->param('service', 'feeds');
+    my  $cnf  = CNFParser -> new (undef,{ DO_ENABLED => 1, HAS_EXTENSIONS=>1, ANONS_ARE_PUBLIC => 1, CGI=>$cgi });
+        $cnf->parse(undef,_getServiceScript($cgi));
+        $ptr = $cnf->data();
+        $ptr = $ptr->{'PAGE'};
+    say $$ptr if $ptr;
+    # open my $fh, ">dump_of_output_to_browser.html";
+    # print $fh $$ptr;
+    # close $fh;
+    return 0
+}
+
+sub _getServiceScript($cgi) {
+     my $service = $cgi->param('service'); $service = "undef" if not $service;
+     if($service eq 'feeds'){
+        return _CNF_Script_For_Feeds();
+     }
+     CNFHTMLServiceError->throw(error=>"UNKNOWN SERVICE -> $service", show_trace=>1)
+}
+
+sub _CNF_Script_For_Feeds {
+<<__CNF_IS_COOL__;
+<<PROCESS_RSS_FEEDS<PLUGIN>
+
+    RUN_FEEDS = yes
+    CONVERT_TO_CNF_NODES = yes
+    OUTPUT_TO_CONSOLE = false
+    OUTPUT_TO_MD = no
+    BENCHMARK = no
+    TZ=Australia/Sydney
+    OUTPUT_DIR = "./rss_output"
+
+
+    CONVERT_CNF_HTML  = yes
+    CNF_TREE_STORE    = true
+
+    package     : RSSFeedsPlugin
+    subroutine  : process
+    property    : RSS_FEEDS
+
+>>
+// Following is a table having a list of details for available RSS feeds to process.
+|| The more rows have here the longer it takes to fetch them, what is it, once a day, week, month?
+<<    RSS_FEEDS     <DATA>
+ID`Name`URL`Description~
+01`CPAN`http://search.cpan.org/uploads.rdf`CPAN modules news and agenda.~
+>>
+__CNF_IS_COOL__
+}
+
+
+1;
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/LifeLog
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
index d8c475d2bc007358efe510cfb419b0b4e3f2edef..812e4d1168b42c9e66b90240c44a1ea7ff8d0f61 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env perl
 # A delegated CNFParser processed rendering of the Document Index Web page, a Model-View-Controller Pattern approuch.
-# The index.cnf script contains the structure and page skeleton, 
+# The index.cnf script contains the structure and page skeleton,
 # all configuration as well as the HTMLIndexProcessorPlugin converting the CNF to final HTML.
 # It is very convienient, as both style and script for the page is separated and developed in the index.cnf.
 # Which then can be moved to a respective include file over there.
@@ -15,15 +15,15 @@ use warnings;
 use Exception::Class ('LifeLogException');
 use Syntax::Keyword::Try;
 ##
-# We use dynamic perl compilations. The following ONLY HERE required to carp to browser on 
+# We use dynamic perl compilations. The following ONLY HERE required to carp to browser on
 # system requirments or/and unexpected perl compiler errors.
 ##
 use CGI::Carp qw(fatalsToBrowser set_message);
-    
+
 BEGIN {
    sub handle_errors {
       my $err = shift;
-      say "<html><body><h2>Server Error</h2><pre>Error: $err</pre></body></html>"; 
+      say "<html><body><h2>Server Error</h2><pre>Error: $err</pre></body></html>";
   }
   set_message(\&handle_errors);
 }
@@ -37,20 +37,20 @@ our $script_path = $0; $script_path =~ s/\w+.cgi$//;
 
 exit &HTMLPageBuilderFromCNF;
 
-sub HTMLPageBuilderFromCNF {    
+sub HTMLPageBuilderFromCNF {
     my $html = obtainDirListingHTML('docs');
     my $cnf  = CNFParser -> new (
                             $script_path."index.cnf",{
                              DO_ENABLED => 1, HAS_EXTENSIONS=>1,
                              ANONS_ARE_PUBLIC => 1,
-                                PAGE_HEAD     => "<h1 id=\"index_head\">Index Page of Docs Directory</h1>", 
-                                PAGE_CONTENT  => $html, 
+                                PAGE_HEAD     => "<h1 id=\"index_head\">Index Page of Docs Directory</h1>",
+                                PAGE_CONTENT  => $html,
                                 PAGE_FOOT     => "<!--Not Defined-->"
                             }
                 );
     my $ptr = $cnf->data();
-    $ptr = $ptr->{'PAGE'};   
-    say $$ptr if $ptr;    
+    $ptr = $ptr->{'PAGE'};
+    say $$ptr if $ptr;
     return 0
 }
 
@@ -58,7 +58,7 @@ sub obtainDirListingHTML {
     my ($dir, $ret) = (shift,"");
     my $html = listFiles($dir,$script_path,"");
     if($html){
-       $ret .="<ul><b>$dir &#8594;</b>\n"; 
+       $ret .="<ul><b>$dir &#8594;</b>\n";
        $ret .= $html;
         opendir (my $handle, $script_path.$dir) or die "Couldn't open directory, $!";
         while (my $node = readdir $handle) {
@@ -78,17 +78,17 @@ sub listFiles ($){
     my ($dir, $script_path, $ret) = @_;
     my $path = $script_path.$dir;
     my $spec = $GLOB_HTML_SERVE; $spec =~ s/{}/$path/gp;
-    my @files = glob ($spec);        
+    my @files = glob ($spec);
     foreach my $file(@files){
             ($file =~ m/(\w+\.\w*)$/g);
             my $name = $1;
             if($file =~ /\.md$/){
-                my @title = getDocTitle($file);                
+                my @title = getDocTitle($file);
                 $ret .= qq(\t\t\t<li><a href="$dir/$title[0]">$title[1]</a> &dash; $name</li>\n);
-            }else{                
+            }else{
                 $ret .= qq(\t\t\t<li><a href="$dir/$name">$name</a></li>\n);
             }
-    }    
+    }
     return $ret;
 }
 
@@ -101,13 +101,15 @@ sub getDocTitle($){
            last;
         }
     }
-    close $fh;    
+    close $fh;
     ($file =~ m/(\w+\.\w*)$/g);
     return ($1,$ret)
 }
-
-
-
-
-
+1;
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/LifeLog
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
 
index 332ef598e04c26e2be2fd2423997841df70d97f9..bb42c4e9c5effc37f87b7d672dd6a22dad74e53c 100755 (executable)
@@ -1,8 +1,5 @@
 #!/usr/bin/env perl
 #
-# Programed by: Will Budic
-# Open Source License -> https://choosealicense.com/licenses/isc/
-#
 use strict;
 use warnings;
 use experimental qw( switch );
@@ -67,6 +64,7 @@ try{
                                 {-type => 'text/javascript', -src => 'wsrc/jquery-ui.js'}],
                     -style => [ {-type  => 'text/css', -src => $css},
                                 {-type => 'text/css', -src => 'wsrc/effects.css'},
+                                {-type => 'text/css', -src => 'wsrc/feeds.css'},
                                 {-type => 'text/css', -src => 'wsrc/jquery-ui.css'},
                                 {-type => 'text/css', -src => 'wsrc/jquery-ui.theme.css'},
                                 {-type => 'text/css', -src => 'wsrc/jquery-ui.theme.css'}],
@@ -104,7 +102,8 @@ HTML
     print qq(
             <br>
             <div id="menu_page" style="margin-left: 85vw;"><span class="menu_head">Menu</span><hr>
-                <a class="ui-button ui-corner-all ui-widget" href="index.cgi">Index</a>
+                <a class="ui-button ui-corner-all ui-widget" href="index.cgi">Index</a><hr>
+                <a class="a_" onclick="return fetchFeeds()">Feeds</a><hr>
             </div>
             <div class="rz login">
                 $frm
@@ -117,6 +116,26 @@ HTML
                 </div>
                 <br>
             </div>
+            <div id="feeds" class="rz" style="width:60% !important;visibility:hidden">RSS</div>
+            <script>
+            function fetchFeeds(){
+                var pnl = \$('#feeds');
+                pnl.html(
+                '<div><span style="border:1px solid Crimson;padding:5px;"><font color="Crimson"><b>P l e a s e &nbsp;&nbsp;    W a i t  &nbsp;&nbsp;   D a r l i n g !</b></font></span><br><img src="images/WelloffHighlevelAlpinegoat.webp" witdht="85" height="85"></div>'
+                );
+                pnl.show();
+                pnl.css('visibility','visible');
+                \$.post('CNFServices.cgi', {service:'feeds',action:'default'}, displayFeeds).fail(
+                    function(response) {pnl.html("Service Error: "+response.status,response.responseText);pnl.fadeOut(10000);}
+                );
+                //
+            }
+            function displayFeeds(content){
+                var pnl = \$('#feeds');
+                pnl.html(content);
+                pnl.show();
+            }
+            </script>
           );
 
     Settings::printDebugHTML($DBG) if Settings::debug();
@@ -918,4 +937,10 @@ sub logout {
     exit;
 }
 
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/LifeLog
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
 
index c2a6068e98093702f5c89cdbaae8b97a8e3c97f9..6c815225bb88016dd2172c640c56f137ae36c893 100755 (executable)
@@ -271,7 +271,7 @@ $td_itm_cnt = 0;
 foreach my $key(@keys){
        if($td_itm_cnt>$present_rows_cnt){
                $td_cat .= "</ul></td><td><ul>";
-               $td_itm_cnt = 0;                
+               $td_itm_cnt = 0;
        }
        $td_cat .= "<li id='".$key."'><a href='#'>".$hshCats{$key}."</a></li>";
        $td_itm_cnt++;
@@ -676,7 +676,7 @@ sub buildLog {
                        if($log =~ m/(.*\s*.*?)<br>/){$h=$1}
                        elsif($log =~ m/(\s*.*\n)/) {$h=$1}
                        if($h){
-                               $log = $h.qq(<input type="hidden" id="h$id" value="$log"/><button id='btnRTF' onclick="return dispFullLog($id);" 
+                               $log = $h.qq(<input type="hidden" id="h$id" value="$log"/><button id='btnRTF' onclick="return dispFullLog($id);"
                                  class="ui-button ui-corner-all ui-widget"><span>&#8691;<span></button>);
                        }
                }
@@ -812,10 +812,10 @@ if($isPUBViewMode){
     <input type="hidden" name="opr" id="opr" value="0"/>
     <input type="submit" value="Sum" onclick="return sumSelected()"/>&nbsp;
     <span style="border-left: 1px solid black;padding:5px;margin:15px;">
-    <button onclick="return selectAllLogs()">Select All</button>    
+    <button onclick="return selectAllLogs()">Select All</button>
     <input type="reset" value="Unselect All"/>
     <input type="submit" value="Date Diff" onclick="return dateDiffSelected()"/>&nbsp;
-    <input type="submit" value="Export" onclick="return exportSelected()"/>&nbsp;        
+    <input type="submit" value="Export" onclick="return exportSelected()"/>&nbsp;
     <input type="submit" value="Print" onclick="return viewSelected()"/>&nbsp;
     <input id="del_sel"  type="submit" value="Delete" onclick="display('Please Wait!')"/>
     </span>
@@ -851,9 +851,9 @@ my $frm = qq(
                        &nbsp;<button type="reset"  onclick="setNow();resetDoc(); return true;">Reset</button>
 
                 <span id="cat_desc" name="cat_desc">Enter log...</span>
-            
+
                        &nbsp;&nbsp;&nbsp;Category:&nbsp;
-            
+
                 <button data-dropdown="#dropdown-standard" style="margin: 0px; padding: 0;">
                 <span id="lcat" class="ui-button">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i><font size=1>--Select --</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</i></span>
                &nbsp; &#171; &nbsp;</button>
@@ -994,11 +994,11 @@ $srh .=qq(
 &nbsp;&nbsp; &nbsp;&nbsp;
             <input id="vrtf" name="vrtf" type="hidden" value="0"/>
             View RTF Logs:&nbsp;<button id="btn_rtf" onclick="viewRTFLogs(this);">View</button>
-&nbsp;&nbsp;            
+&nbsp;&nbsp;
 
      </td>
    </tr>
-   
+
    <tr class="collpsd">
      <td align="right">Exclude Category:</td>
      <td align="left">
@@ -1101,7 +1101,7 @@ my $help = &help;
 #  Final Page Output from here!  #
 ##################################
 my $audio = &Settings::audioEnabled ? qq(
-       <audio id="au_door_chime" enabled preload="auto" 
+       <audio id="au_door_chime" enabled preload="auto"
     src="wsrc/Miki-Matsubara-WASH-WASH.mp3">
         Your browser does not support the
         <code>audio</code> element.
@@ -1589,7 +1589,7 @@ sub help{
 
          &#60;&#60;FRM&#62;my_cat_simon_frm.png&#62; &#60;&#60;TITLE&#60;Simon The Cat&#62;&#62;
          This is my pet, can you hold him for a week while I am on holiday?
-            </pre>                                     
+            </pre>
                                <p>
                                        <b>&#60;&#60;LNK&#60;<i>{url to image}</i>&#62;&#62;</b><br><br>
                                        Explicitly tag an URL in the log entry.
@@ -1597,7 +1597,7 @@ sub help{
                                        Otherwise link appears as plain text.
                                </p><br>
         <p>&#60;iframe .....&#60;/iframe>  - Experimental html embedding, useful for youtube shared videos.</p>
-       <hr>    
+       <hr>
     <h3>Log Page Particulars</h3><p> &#x219F; or &#x21A1; - Jump links to top or bottom of page respectivelly. </p>
 </div>
 </td></tr></table>)
@@ -1662,4 +1662,10 @@ sub outputPage {
 }
 
 
-1;
\ No newline at end of file
+1;
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/LifeLog
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
diff --git a/htdocs/cgi-bin/rss_output/tree_feed_CPAN.cnf b/htdocs/cgi-bin/rss_output/tree_feed_CPAN.cnf
new file mode 100644 (file)
index 0000000..e9eddb5
--- /dev/null
@@ -0,0 +1,1198 @@
+<<CNF_FEED<TREE>
+    Release: 1
+    Version: 1.0
+      <Feed<
+       Expires: 2023-09-27 08:00:00
+       File: ./rss_output/tree_feed_CPAN.cnf
+       Published: 2023-09-16 03:26:12.785 UTC
+       Title: Recent CPAN uploads - MetaCPAN
+       URL: http://search.cpan.org/uploads.rdf
+      [#[
+         CPAN modules news and agenda.
+      ]#]
+      >Feed>
+      <Brew<
+         <Item<
+          Date: 2023-09-16 03:20:02
+          Link: https://metacpan.org/release/TIMLEGGE/Crypt-OpenSSL-AES-0.08
+          Title: Crypt-OpenSSL-AES-0.08
+            <Description<
+            [#[
+               <p>A Perl wrapper around OpenSSL's AES library</p><p>Changes for 0.08</p><ul><li>Detailed Changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-16 00:05:19
+          Link: https://metacpan.org/release/PERLANCAR/Bencher-Scenario-Shell-Startup-0.001
+          Title: Bencher-Scenario-Shell-Startup-0.001
+            <Description<
+            [#[
+               <p>Benchmark startup overhead of various Unix shells</p><p>Changes for 0.001 - 2023-07-08</p><ul><li>First release.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 20:40:36
+          Link: https://metacpan.org/release/RAWLEYFOW/API-Vultr-0.001
+          Title: API-Vultr-0.001
+            <Description<
+            [#[
+               A simple interface to the Vultr v2 API
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 20:06:29
+          Link: https://metacpan.org/release/WATERKIP/WebService-Postex-0.004
+          Title: WebService-Postex-0.004
+            <Description<
+            [#[
+               <p>A Postex WebService implemenation in Perl</p><p>Changes for 0.004 - 2023-09-15T20:05:14Z</p><ul><li>Change to Object::Pad from Moose</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 19:08:32
+          Link: https://metacpan.org/release/DJERIUS/CXC-Form-Tiny-OptArgs2-0.01
+          Title: CXC-Form-Tiny-OptArgs2-0.01
+            <Description<
+            [#[
+               <p>a really awesome library</p><p>Changes for 0.01 - 2023-09-15T15:07:02-04:00</p><ul><li>First release upon an unsuspecting world.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 18:20:38
+          Link: https://metacpan.org/release/MARKOV/Mail-Box-Parser-C-3.011
+          Title: Mail-Box-Parser-C-3.011
+            <Description<
+            [#[
+               Parse mbox files with XS
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 16:22:26
+          Link: https://metacpan.org/release/WATERKIP/Dist-Zilla-PluginBundle-Author-WATERKIP-3.3
+          Title: Dist-Zilla-PluginBundle-Author-WATERKIP-3.3
+            <Description<
+            [#[
+               <p>An plugin bundle for all distributions by WATERKIP</p><p>Changes for 3.3 - 2023-09-15T16:19:02Z</p><ul><li>Add 'use Data::Dumper' to skip for prereqs</li>
+               <li>Include authors to contributors file</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 15:45:17
+          Link: https://metacpan.org/release/BPS/GnuPG-Interface-1.03
+          Title: GnuPG-Interface-1.03
+            <Description<
+            [#[
+               <p>supply object methods for interacting with GnuPG</p><p>Changes for 1.03 - 2023-09-14</p><ul><li>Add fix for running in taint mode for Perl 5.38.0(thanks to Andrew Ruthven)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 13:48:51
+          Link: https://metacpan.org/release/CUKEBOT/Gherkin-27.0.0
+          Title: Gherkin-27.0.0
+            <Description<
+            [#[
+               A parser and compiler for the Gherkin language
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 11:42:54
+          Link: https://metacpan.org/release/HANJE/Tk-CodeText-0.44
+          Title: Tk-CodeText-0.44
+            <Description<
+            [#[
+               Programmer's Swiss army knife Text widget.
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 11:19:47
+          Link: https://metacpan.org/release/HANJE/Tk-CodeText-0.43
+          Title: Tk-CodeText-0.43
+            <Description<
+            [#[
+               Programmer's Swiss army knife Text widget.
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 08:16:27
+          Link: https://metacpan.org/release/NERDVANA/CodeGen-Cpppp-0.001_02
+          Title: CodeGen-Cpppp-0.001_02
+            <Description<
+            [#[
+               <p>The C Perl-Powered Pre-Processor</p><p>Changes for 0.001_02 - 2023-09-15</p><ul><li>Fix various glaring documentation mistakes</li>
+               <li>List some ideas for future features</li>
+               <li>Remove debugging output</li>
+               <li>&quot;--help&quot; for command line</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 07:52:25
+          Link: https://metacpan.org/release/OODLER/OpenMP-Simple-0.0.2-TRIAL
+          Title: OpenMP-Simple-0.0.2-TRIAL
+            <Description<
+            [#[
+               Provides some DWIM helpers for using OpenMP via Inline::C.
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 07:42:08
+          Link: https://metacpan.org/release/TORBIAK/App-Git-Autofixup-0.004001
+          Title: App-Git-Autofixup-0.004001
+            <Description<
+            [#[
+               create fixup commits for topic branches
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 07:24:13
+          Link: https://metacpan.org/release/CORION/HTTP-Request-FromCurl-0.52
+          Title: HTTP-Request-FromCurl-0.52
+            <Description<
+            [#[
+               <p>create a HTTP::Request from a curl command line</p><p>Changes for 0.52 - 2023-09-15</p><ul><li>Switch test suite to Test2::V0</li>
+               <li>Suppress trivial Content-Length headers in -&gt;as_curl</li>
+               <li>--disable is a boolean option</li>
+               <li>Add support for --json</li>
+               <li>Add support for --max-filesize The behaviour is slightly different from cURL. --max-filesize looks at the size of the body, not at the Content-Length header.</li>
+               <li>Add support for --interface (only IP addresses, not interface names)</li>
+               <li>Fix Mojolicious snippet generation with local address</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 07:15:14
+          Link: https://metacpan.org/release/NERDVANA/CodeGen-Cpppp-0.001_01
+          Title: CodeGen-Cpppp-0.001_01
+            <Description<
+            [#[
+               <p>The C Perl-Powered Pre-Processor</p><p>Changes for 0.001_01 - 2023-09-15</p><ul><li>Basic intermingling of perl variables in C code</li>
+               <li>Arbitrary perl in '##' directives</li>
+               <li>Magic indent adjustment after variable substitution</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 00:18:28
+          Link: https://metacpan.org/release/BENNIE/VMware-vCloud-2.404
+          Title: VMware-vCloud-2.404
+            <Description<
+            [#[
+               <p>VMware vCloud API</p><p>Changes for 2.404 - 2023-09-14</p><ul><li>Added: POD test</li>
+               <li>Improved: 'use warnings' on all modules</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-15 00:05:37
+          Link: https://metacpan.org/release/PERLANCAR/Dist-Zilla-Plugin-Acme-CPANModules-Blacklist-0.002
+          Title: Dist-Zilla-Plugin-Acme-CPANModules-Blacklist-0.002
+            <Description<
+            [#[
+               <p>Blacklist prereqs using an Acme::CPANModules module</p><p>Changes for 0.002 - 2023-07-09</p><ul><li>No functional changes.</li>
+               <li>Remove the usage of smartmatch.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 21:56:09
+          Link: https://metacpan.org/release/TIMLEGGE/Crypt-OpenSSL-AES-0.07
+          Title: Crypt-OpenSSL-AES-0.07
+            <Description<
+            [#[
+               <p>A Perl wrapper around OpenSSL's AES library</p><p>Changes for 0.07</p><ul><li>Significant updates since 0.02</li>
+               <li>Detailed Changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 21:45:52
+          Link: https://metacpan.org/release/TORBIAK/App-Git-Autofixup-0.004
+          Title: App-Git-Autofixup-0.004
+            <Description<
+            [#[
+               create fixup commits for topic branches
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 20:00:02
+          Link: https://metacpan.org/release/OKURZ/Mojo-IOLoop-ReadWriteProcess-0.34
+          Title: Mojo-IOLoop-ReadWriteProcess-0.34
+            <Description<
+            [#[
+               <p>Execute external programs or internal code blocks as separate process.</p><p>Changes for 0.34 - 2023-09-14T19:55:18Z</p><ul><li>Adapt to deprecation of spurt in upstream Mojolicious</li>
+               <li>Make git work in github workflow</li>
+               <li>Turn warnings &quot;Sleeping inside locked section&quot; into notes</li>
+               <li>Avoid warnings about using undefined value as file handle</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 17:55:34
+          Link: https://metacpan.org/release/HANJE/Tk-YANoteBook-0.04
+          Title: Tk-YANoteBook-0.04
+            <Description<
+            [#[
+               <p>Yet another NoteBook widget</p><p>Changes for 0.04</p><ul><li>tidied up documentation. gave the NameTab widget it's own pm file added -autoupdate option added -onlyselect option addid -rigid option added -unselecttabcall option fixed some minor bugs</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 16:43:48
+          Link: https://metacpan.org/release/MTYRRELL/Config-Structured-2.004
+          Title: Config-Structured-2.004
+            <Description<
+            [#[
+               <p>Provides generalized and structured configuration value access</p><p>Changes for 2.004</p><ul><li>Switched to Perl6::Junction to avoid smartmatch warnings from Syntax::Keyword::Junction</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 12:22:38
+          Link: https://metacpan.org/release/ABRAXXA/Net-Silverpeak-Orchestrator-0.008000
+          Title: Net-Silverpeak-Orchestrator-0.008000
+            <Description<
+            [#[
+               <p>Silverpeak Orchestrator REST API client library</p><p>Changes for 0.008000 - 2023-09-14T14:21:59+02:00</p><ul><li>enable TLS verification by default by requiring HTTP::Tiny 0.088</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 08:33:57
+          Link: https://metacpan.org/release/SHLOMIF/XML-GrammarBase-0.2.10
+          Title: XML-GrammarBase-0.2.10
+            <Description<
+            [#[
+               <p>Provide roles and base classes for processors of specialized XML grammars.</p><p>Changes for 0.2.10 - 2023-09-14</p><ul><li>Better compatibility with bleadperl</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 08:01:08
+          Link: https://metacpan.org/release/SYP/Net-Curl-0.54_2
+          Title: Net-Curl-0.54_2
+            <Description<
+            [#[
+               Perl interface for libcurl
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 03:02:53
+          Link: https://metacpan.org/release/ANATOFUZ/Teng-0.34
+          Title: Teng-0.34
+            <Description<
+            [#[
+               <p>very simple DBI wrapper/ORMapper</p><p>Changes for 0.34 - 2023-09-14T03:02:36Z</p><ul><li>[IMPORTANT]  change the minimum supported perl version to 5.16 (#165)</li>
+               <li>FIX: Don't update when set column from null to null (#164 thanks hitode909)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 02:18:56
+          Link: https://metacpan.org/release/KIMOTO/SPVM-0.989042
+          Title: SPVM-0.989042
+            <Description<
+            [#[
+               <p>SPVM Language</p><p>Changes for 0.989042 - 2023-09-12</p><ul><li>Incompatible Changes</li>
+               <li>New Features</li>
+               <li>Document Improvement</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 00:05:27
+          Link: https://metacpan.org/release/PERLANCAR/Perinci-To-Doc-0.881
+          Title: Perinci-To-Doc-0.881
+            <Description<
+            [#[
+               <p>Convert Rinci metadata to documentation</p><p>Changes for 0.881 - 2023-07-09</p><ul><li>No functional changes.</li>
+               <li>Remove usage of smartmatch.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 23:57:14
+          Link: https://metacpan.org/release/MARIOROY/MCE-Shared-1.886
+          Title: MCE-Shared-1.886
+            <Description<
+            [#[
+               <p>MCE extension for sharing data supporting threads and processes</p><p>Changes for 1.886</p><ul><li>Add Android support. This required moving MCE::Shared::Base::Common out of MCE::Shared::Base to separate file MCE::Shared::Common.</li>
+               <li>Bump MCE dependency to 1.889.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 23:37:02
+          Link: https://metacpan.org/release/MARIOROY/MCE-1.889
+          Title: MCE-1.889
+            <Description<
+            [#[
+               <p>Many-Core Engine for Perl providing parallel processing capabilities</p><p>Changes for 1.889</p><ul><li>Add Android support. Thank you, Dimitrios Kechagias.</li>
+               <li>Revert defer signal-handling in MCE::Channel (send2 method).</li>
+               <li>Improve mutex synchronize (aka enter) with guard capability. Thank you, José Joaquín Atria.</li>
+               <li>Fix mutex re-entrant lock on the Windows platform.</li>
+               <li>Add mutex guard_lock method.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 23:35:35
+          Link: https://metacpan.org/release/MARIOROY/Mutex-1.011
+          Title: Mutex-1.011
+            <Description<
+            [#[
+               <p>Various locking implementations supporting processes and threads</p><p>Changes for 1.011</p><ul><li>Bump version.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 22:40:32
+          Link: https://metacpan.org/release/EXODIST/Test2-Suite-0.000156
+          Title: Test2-Suite-0.000156
+            <Description<
+            [#[
+               <p>Distribution with a rich set of tools built upon the Test2 framework.</p><p>Changes for 0.000156 - 2023-09-13T15:11:52-07:00</p><ul><li>Fix typo in POD for Test2::Util::Importer</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 21:57:38
+          Link: https://metacpan.org/release/PEVANS/Device-AVR-UPDI-0.14
+          Title: Device-AVR-UPDI-0.14
+            <Description<
+            [#[
+               <p>interact with an AVR microcontroller over UPDI</p><p>Changes for 0.14 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 21:30:55
+          Link: https://metacpan.org/release/AJNN/Mac-Finder-Tags-0.02
+          Title: Mac-Finder-Tags-0.02
+            <Description<
+            [#[
+               <p>Access macOS file tags (aka Finder labels)</p><p>Changes for 0.02 - 2023-09-13</p><ul><li>Avoid syntax that causes warnings in Object::Pad 0.801.</li>
+               <li>Tests no longer fail for unexpected warnings, except during author testing.</li>
+               <li>Drop Devel::CheckOS prerequisite (by bundling it).</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 21:00:16
+          Link: https://metacpan.org/release/MICHIELB/GD-Barcode-2.00
+          Title: GD-Barcode-2.00
+            <Description<
+            [#[
+               <p>Create barcode image with GD</p><p>Changes for 2.00 - 2023-09-13</p><ul><li>'Production' release, no changes to 1.99_03</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 20:58:49
+          Link: https://metacpan.org/release/BENNIE/VMware-vCloud-2.403
+          Title: VMware-vCloud-2.403
+            <Description<
+            [#[
+               <p>VMware vCloud API</p><p>Changes for 2.403 - 2023-09-13</p><ul><li>Added: POD test</li>
+               <li>Improved: 'use warnings' on all modules</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 20:44:39
+          Link: https://metacpan.org/release/RKAPL/EAI-Wrap-0.3
+          Title: EAI-Wrap-0.3
+            <Description<
+            [#[
+               framework for easy creation of Enterprise Application Integration tasks
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 20:27:57
+          Link: https://metacpan.org/release/MIYAGAWA/Starman-0.4017
+          Title: Starman-0.4017
+            <Description<
+            [#[
+               <p>High-performance preforking PSGI/Plack web server</p><p>Changes for 0.4017 - 2023-09-13T13:27:02Z</p><ul><li>Handle EINTR when doing sysread calls (Rob Mueller) #148</li>
+               <li>Requires perl 5.14</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 17:41:55
+          Link: https://metacpan.org/release/EXODIST/Term-Table-0.017
+          Title: Term-Table-0.017
+            <Description<
+            [#[
+               <p>Format a header and rows into a table</p><p>Changes for 0.017 - 2023-09-13T10:41:08-07:00</p><ul><li>Remove 'Importer' dependency</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 16:02:42
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-TSL256x-0.09
+          Title: Device-Chip-TSL256x-0.09
+            <Description<
+            [#[
+               <p>chip driver for TSL256x</p><p>Changes for 0.09 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 16:01:19
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-TCS3472x-0.05
+          Title: Device-Chip-TCS3472x-0.05
+            <Description<
+            [#[
+               <p>chip driver for TCS3472x-family</p><p>Changes for 0.05 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 16:01:04
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-SSD1306-0.14
+          Title: Device-Chip-SSD1306-0.14
+            <Description<
+            [#[
+               <p>chip driver for monochrome OLED modules</p><p>Changes for 0.14 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:59:38
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-Si5351-0.02
+          Title: Device-Chip-Si5351-0.02
+            <Description<
+            [#[
+               <p>chip driver for Si5351</p><p>Changes for 0.02 - 2023-09-13</p><ul><li>CHANGES</li>
+               <li>BUGFIXES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:58:10
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-SDCard-0.04
+          Title: Device-Chip-SDCard-0.04
+            <Description<
+            [#[
+               <p>chip driver for SD and MMC cards</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:56:44
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-PCF8574-0.06
+          Title: Device-Chip-PCF8574-0.06
+            <Description<
+            [#[
+               <p>chip driver for a PCF8574 or PCA8574</p><p>Changes for 0.06 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:56:33
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-PCF8563-0.04
+          Title: Device-Chip-PCF8563-0.04
+            <Description<
+            [#[
+               <p>chip driver for a PCF8563</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:54:59
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-OPT3001-0.03
+          Title: Device-Chip-OPT3001-0.03
+            <Description<
+            [#[
+               <p>chip driver for OPT3001</p><p>Changes for 0.03 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:53:40
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-nRF24L01P-0.08
+          Title: Device-Chip-nRF24L01P-0.08
+            <Description<
+            [#[
+               <p>chip driver for a nRF24L01+</p><p>Changes for 0.08 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:52:21
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-NoritakeGU_D-0.06
+          Title: Device-Chip-NoritakeGU_D-0.06
+            <Description<
+            [#[
+               <p>chip driver for Noritake GU-D display modules</p><p>Changes for 0.06 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:50:58
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MPL3115A2-0.13
+          Title: Device-Chip-MPL3115A2-0.13
+            <Description<
+            [#[
+               <p>chip driver for a MPL3115A2</p><p>Changes for 0.13 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:49:37
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MCP23x17-0.07
+          Title: Device-Chip-MCP23x17-0.07
+            <Description<
+            [#[
+               <p>chip driver for the MCP23x17 family</p><p>Changes for 0.07 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:48:18
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MAX7219-0.09
+          Title: Device-Chip-MAX7219-0.09
+            <Description<
+            [#[
+               <p>chip driver for a MAX7219</p><p>Changes for 0.09 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:47:00
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MAX44009-0.05
+          Title: Device-Chip-MAX44009-0.05
+            <Description<
+            [#[
+               <p>chip driver for MAX44009</p><p>Changes for 0.05 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:45:41
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-INA219-0.10
+          Title: Device-Chip-INA219-0.10
+            <Description<
+            [#[
+               <p>chip driver for an INA219</p><p>Changes for 0.10 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:44:22
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-HTU21D-0.10
+          Title: Device-Chip-HTU21D-0.10
+            <Description<
+            [#[
+               <p>chip driver for HTU21D</p><p>Changes for 0.10 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:43:03
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-DS1307-0.08
+          Title: Device-Chip-DS1307-0.08
+            <Description<
+            [#[
+               <p>chip driver for a DS1307</p><p>Changes for 0.08 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:41:44
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-CCS811-0.03
+          Title: Device-Chip-CCS811-0.03
+            <Description<
+            [#[
+               <p>chip driver for CCS811</p><p>Changes for 0.03 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:40:25
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-CC1101-0.09
+          Title: Device-Chip-CC1101-0.09
+            <Description<
+            [#[
+               <p>chip driver for a CC1101</p><p>Changes for 0.09 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:38:58
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-BV4243-0.04
+          Title: Device-Chip-BV4243-0.04
+            <Description<
+            [#[
+               <p>chip driver for a BV4243</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:37:36
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-BNO055-0.04
+          Title: Device-Chip-BNO055-0.04
+            <Description<
+            [#[
+               <p>chip driver for BNO055</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:36:09
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-BME280-0.06
+          Title: Device-Chip-BME280-0.06
+            <Description<
+            [#[
+               <p>chip driver for BME280</p><p>Changes for 0.06 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:33:22
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AVR_HVSP-0.07
+          Title: Device-Chip-AVR_HVSP-0.07
+            <Description<
+            [#[
+               <p>high-voltage serial programming for AVR chips</p><p>Changes for 0.07 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:26:50
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AS3935-0.04
+          Title: Device-Chip-AS3935-0.04
+            <Description<
+            [#[
+               <p>chip driver for AS3935</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:25:06
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AnalogConverters-0.15
+          Title: Device-Chip-AnalogConverters-0.15
+            <Description<
+            [#[
+               <p>a collection of chip drivers</p><p>Changes for 0.15 - 2023-08-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:23:40
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AD9833-0.05
+          Title: Device-Chip-AD9833-0.05
+            <Description<
+            [#[
+               <p>chip driver for AD9833</p><p>Changes for 0.05 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 14:58:27
+          Link: https://metacpan.org/release/YANICK/Dist-Zilla-Plugin-CoalescePod-1.0.0
+          Title: Dist-Zilla-Plugin-CoalescePod-1.0.0
+            <Description<
+            [#[
+               <p>merge .pod files into their .pm counterparts</p><p>Changes for 1.0.0 - 2023-09-13</p><ul><li>API CHANGES</li>
+               <li>STATISTICS</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 14:57:01
+          Link: https://metacpan.org/release/YANICK/Dist-Zilla-Plugin-CoalescePod-0.3.1
+          Title: Dist-Zilla-Plugin-CoalescePod-0.3.1
+            <Description<
+            [#[
+               <p>merge .pod files into their .pm counterparts</p><p>Changes for 0.3.1 - 2023-09-13</p><ul><li>STATISTICS</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 14:10:21
+          Link: https://metacpan.org/release/PEVANS/App-sdview-Output-Tickit-0.03
+          Title: App-sdview-Output-Tickit-0.03
+            <Description<
+            [#[
+               <p>interactive terminal-based viewer for App::sdview</p><p>Changes for 0.03 - 2023-09-13</p><ul><li>CHANGES</li>
+               <li>BUGFIXES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 11:25:40
+          Link: https://metacpan.org/release/AWNCORP/Venus-4.11
+          Title: Venus-4.11
+            <Description<
+            [#[
+               <p>OO Library for Perl 5</p><p>Changes for 4.11 - 2023-09-13</p><ul><li>[feature] Implement Venus::Assert#includes</li>
+               <li>[feature] Implement Venus::Future</li>
+               <li>[feature] Refactor Venus::Assert, Implement Venus::{Coercion,Constraint}</li>
+               <li>[feature] Implement Venus::Space#{patch,patched,unpatch}</li>
+               <li>[feature] Implement Venus::Sealed</li>
+               <li>[feature] Implement Venus::Atom</li>
+               <li>[feature] Implement Venus::Enum</li>
+               <li>[feature] Implement Venus::Role::Superable</li>
+               <li>[feature] Implement Venus::Role::Patchable</li>
+               <li>[feature] Implement Venus#clone</li>
+               <li>[feature] Implement Venus::Process#future</li>
+               <li>[feature] Implement Venus::Future#wait</li>
+               <li>[update] Refactor Venus::Test</li>
+               <li>[update] Add test and documentation for Venus::Process#is_dyadic</li>
+               <li>[update] Update Venus::Process#await, auto-reap processes</li>
+               <li>[update] Research CPANTS issue with Venus::Process</li>
+               <li>[update] Update Venus::Process, prevent PPID in dyads</li>
+               <li>[update] Use Venus::Check types in all signatures</li>
+               <li>[update] Update Venus#async to return Venus::Future</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 11:00:19
+          Link: https://metacpan.org/release/JBAIER/Pass-OTP-1.6
+          Title: Pass-OTP-1.6
+            <Description<
+            [#[
+               <p>Perl implementation of HOTP / TOTP algorithms</p><p>Changes for 1.6 - 2023-09-13</p><ul><li>fix SHA384 and SHA512 blocksize (#1)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 10:35:02
+          Link: https://metacpan.org/release/BOD/Image-Square-0.01_4
+          Title: Image-Square-0.01_4
+            <Description<
+            [#[
+               <p>Crop and resize an image to create a square image</p><p>Changes for 0.01_4</p><ul><li>Tests still fail on different builds of GD.  Now using PNG as input image and native GD format for output.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 09:13:52
+          Link: https://metacpan.org/release/MRUEDA/Convert-Pheno-0.13
+          Title: Convert-Pheno-0.13
+            <Description<
+            [#[
+               <p>A module to interconvert common data models for phenotypic data</p><p>Changes for 0.13 - 2023-09-12T00:00:00Z</p><ul><li>Pushing new version after passing all tests</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 09:12:25
+          Link: https://metacpan.org/release/TONYC/Imager-zxing-1.001
+          Title: Imager-zxing-1.001
+            <Description<
+            [#[
+               <p>Barcode scanning with libzxing-cpp</p><p>Changes for 1.001</p><ul><li>re-work std::string handling to use the typemap</li>
+               <li>fix &quot;decoder&quot; -&gt; &quot;decode&quot; in the SYNOPSIS</li>
+               <li>support all Imager image layouts</li>
+               <li>require a recent enough ExtUtils::CppGuess and set the required C++ standard</li>
+               <li>allow the zxing-cpp package name for pkg-config, which seems to be what packagers used before upstream decided on &quot;zxing.pc&quot;. https://github.com/tonycoz/imager-zxing/issues/1</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 05:55:56
+          Link: https://metacpan.org/release/KIMOTO/SPVM-Sys-0.491
+          Title: SPVM-Sys-0.491
+            <Description<
+            [#[
+               <p>System Calls for File IO, User, Process, Signal, Socket</p><p>Changes for 0.491 - 2023-09-13</p><ul><li>New Features</li>
+               <li>Incompatible Changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:12:50
+          Link: https://metacpan.org/release/GRYPHON/Log-Dispatch-Email-Mailer-1.13
+          Title: Log-Dispatch-Email-Mailer-1.13
+            <Description<
+            [#[
+               <p>Log::Dispatch::Email subclass that sends mail using Email::Mailer</p><p>Changes for 1.13 - 2023-09-12T21:12:32-07:00</p><ul><li>Require exact v1.23 (resolves issue #5)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:11:27
+          Link: https://metacpan.org/release/GRYPHON/exact-me-1.05
+          Title: exact-me-1.05
+            <Description<
+            [#[
+               <p>Original program path locations extension for exact</p><p>Changes for 1.05 - 2023-09-12T21:10:17-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:10:01
+          Link: https://metacpan.org/release/GRYPHON/exact-lib-1.04
+          Title: exact-lib-1.04
+            <Description<
+            [#[
+               <p>Compile-time @INC manipulation extension for exact</p><p>Changes for 1.04 - 2023-09-12T21:09:28-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:09:57
+          Link: https://metacpan.org/release/GRYPHON/exact-fun-1.01
+          Title: exact-fun-1.01
+            <Description<
+            [#[
+               <p>Functions and methods with parameter lists for exact</p><p>Changes for 1.01 - 2023-09-12T21:08:36-07:00</p><ul><li>Use Import::Into instead of eval to inject/import</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:08:39
+          Link: https://metacpan.org/release/GRYPHON/exact-conf-1.08
+          Title: exact-conf-1.08
+            <Description<
+            [#[
+               <p>Cascading merged application configuration extension for exact</p><p>Changes for 1.08 - 2023-09-12T21:07:37-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:07:18
+          Link: https://metacpan.org/release/GRYPHON/exact-cli-1.07
+          Title: exact-cli-1.07
+            <Description<
+            [#[
+               <p>Command-line interface helper utilities extension for exact</p><p>Changes for 1.07 - 2023-09-12T21:06:47-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:05:52
+          Link: https://metacpan.org/release/GRYPHON/exact-class-1.19
+          Title: exact-class-1.19
+            <Description<
+            [#[
+               <p>Simple class interface extension for exact</p><p>Changes for 1.19 - 2023-09-12T21:05:22-07:00</p><ul><li>New import signature change required by exact v1.23</li>
+               <li>Use Import::Into instead of eval to inject code</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 01:04:01
+          Link: https://metacpan.org/release/MBRADSHAW/Mail-BIMI-3.20230913
+          Title: Mail-BIMI-3.20230913
+            <Description<
+            [#[
+               <p>BIMI object</p><p>Changes for 3.20230913 - 2023-09-13</p><ul><li>Add policy.mark-type to Authentication-Results</li>
+               <li>Add policy.experimental to Authentication-Results</li>
+               <li>Add options to limit which mark types a MBP accepts</li>
+               <li>Add options to limit acceptance of experimental certificates</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 00:06:02
+          Link: https://metacpan.org/release/PERLANCAR/App-rimetadb-0.226
+          Title: App-rimetadb-0.226
+            <Description<
+            [#[
+               <p>Manage a Rinci metadata database</p><p>Changes for 0.226 - 2023-07-09</p><ul><li>No functional changes.</li>
+               <li>Remove the usage of smartmatch.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 23:33:11
+          Link: https://metacpan.org/release/DRCLAW/constant-more-v0.3.0
+          Title: constant-more-v0.3.0
+            <Description<
+            [#[
+               <p>Constants and Enumerations. Assign constant values from the command line</p><p>Changes for v0.3.0 - 2023-09-13</p><ul><li>Features</li>
+               <li>Tests</li>
+               <li>POD</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 23:07:51
+          Link: https://metacpan.org/release/GRYPHON/exact-1.23
+          Title: exact-1.23
+            <Description<
+            [#[
+               <p>Perl pseudo pragma to enable strict, warnings, features, mro, filehandle methods</p><p>Changes for 1.23 - 2023-09-12T16:06:03-07:00</p><ul><li>Improve/fix import of packages into other packages (resolves issue #4)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 22:14:53
+          Link: https://metacpan.org/release/JIMAVERA/ODF-MailMerge-1.000
+          Title: ODF-MailMerge-1.000
+            <Description<
+            [#[
+               <p>&quot;Mail Merge&quot; or just substitute tokens in ODF documents</p><p>Changes for 1.000</p><ul><li>ODF::MailMerge::Engine-&gt;new positional args eliminated; now use proto_elt =&gt; $table   # specify the object directly context =&gt; $context, proto_tag =&gt; &quot;tagstring&quot;  # search for it Modifier :die (&quot;Delete If Empty&quot;) replaces :delempty</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 21:49:07
+          Link: https://metacpan.org/release/SHANCOCK/Perl-Tidy-20230912
+          Title: Perl-Tidy-20230912
+            <Description<
+            [#[
+               indent and reformat perl scripts
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 21:45:10
+          Link: https://metacpan.org/release/BOD/Image-Square-0.01_3
+          Title: Image-Square-0.01_3
+            <Description<
+            [#[
+               Crop and resize an image to create a square image
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 19:38:33
+          Link: https://metacpan.org/release/TURNERJW/StreamFinder-2.19
+          Title: StreamFinder-2.19
+            <Description<
+            [#[
+               <p>Fetch actual raw streamable URLs from various radio-station, video &amp; podcast websites.</p><p>Changes for 2.19 - 2023-09-12</p><ul><li>StreamFinder::Youtube - 1) Fix failure to fetch artist, icon, etc. sometimes on embedded IFRAME urls (slight site changes) and first episode from some channels.  2) Add -youtube-site argument to specify a different default youtube site (default https://www.youtube.com). 3) Add ability to parse youtube channel URLs containing an at-sign, ie.: https://www.youtube.com/@channelID.</li>
+               <li>StreamFinder::Subsplash - Restore as EXPERIMENTAL, as this site seems to now work again, at least for audio streams on some sites.</li>
+               <li>StreamFinder::Anystream - doc. touchups.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 19:00:33
+          Link: https://metacpan.org/release/AWNCORP/Venus-4.10
+          Title: Venus-4.10
+            <Description<
+            [#[
+               <p>OO Library for Perl 5</p><p>Changes for 4.10 - 2023-09-12</p><ul><li>[feature] Implement Venus::Assert#includes</li>
+               <li>[feature] Implement Venus::Future</li>
+               <li>[feature] Refactor Venus::Assert, Implement Venus::{Coercion,Constraint}</li>
+               <li>[feature] Implement Venus::Space#{patch,patched,unpatch}</li>
+               <li>[feature] Implement Venus::Sealed</li>
+               <li>[feature] Implement Venus::Atom</li>
+               <li>[feature] Implement Venus::Enum</li>
+               <li>[feature] Implement Venus::Role::Superable</li>
+               <li>[feature] Implement Venus::Role::Patchable</li>
+               <li>[feature] Implement Venus#clone</li>
+               <li>[feature] Implement Venus::Process#future</li>
+               <li>[feature] Implement Venus::Future#wait</li>
+               <li>[update] Refactor Venus::Test</li>
+               <li>[update] Add test and documentation for Venus::Process#is_dyadic</li>
+               <li>[update] Update Venus::Process#await, auto-reap processes</li>
+               <li>[update] Research CPANTS issue with Venus::Process</li>
+               <li>[update] Update Venus::Process, prevent PPID in dyads</li>
+               <li>[update] Use Venus::Check types in all signatures</li>
+               <li>[update] Update Venus#async to return Venus::Future</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 17:09:39
+          Link: https://metacpan.org/release/DERIV/Net-Async-Blockchain-0.003
+          Title: Net-Async-Blockchain-0.003
+            <Description<
+            [#[
+               <p>base for blockchain subscription clients.</p><p>Changes for 0.003 - 2023-09-12T17:08:09+00:00</p><ul><li>Improvements</li>
+               <li>Breaking changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 14:47:38
+          Link: https://metacpan.org/release/OALDERS/Open-This-0.000033
+          Title: Open-This-0.000033
+            <Description<
+            [#[
+               <p>Try to Do the Right Thing when opening files</p><p>Changes for 0.000033 - 2023-09-12T14:46:08Z</p><ul><li>Add support for IntellJ IDEA, VSCode, VSCodium and more. Also fix typo (GH#51) (mcneb10)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 13:50:33
+          Link: https://metacpan.org/release/NLNETLABS/Net-DNS-SEC-1.22
+          Title: Net-DNS-SEC-1.22
+            <Description<
+            [#[
+               DNSSEC extensions to Net::DNS
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 09:35:41
+          Link: https://metacpan.org/release/MIKIHOSHI/WebService-Mailgun-0.16
+          Title: WebService-Mailgun-0.16
+            <Description<
+            [#[
+               <p>API client for Mailgun (https://mailgun.com/)</p><p>Changes for 0.16 - 2023-09-12T09:33:42Z</p><ul><li>fix document</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 09:29:10
+          Link: https://metacpan.org/release/MIKIHOSHI/WebService-Mailgun-0.15.1
+          Title: WebService-Mailgun-0.15.1
+            <Description<
+            [#[
+               <p>API client for Mailgun (https://mailgun.com/)</p><p>Changes for 0.15.1 - 2023-09-12T09:27:19Z</p><ul><li>fix document</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 09:16:10
+          Link: https://metacpan.org/release/MRUEDA/Convert-Pheno-0.12_4
+          Title: Convert-Pheno-0.12_4
+            <Description<
+            [#[
+               A module to interconvert common data models for phenotypic data
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 08:44:03
+          Link: https://metacpan.org/release/LICHTKIND/Graphics-Toolkit-Color-1.61
+          Title: Graphics-Toolkit-Color-1.61
+            <Description<
+            [#[
+               <p>color palette creation helper</p><p>Changes for 1.61 - 2023-09-12</p><ul><li>= fix tests</li>
+               <li>+ renamed complementary method =&gt; complement</li>
+               <li>- deprecated complementary, will be removed at 2.0</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 06:33:21
+          Link: https://metacpan.org/release/MERKYS/Graph-SSSR-0.1.0
+          Title: Graph-SSSR-0.1.0
+            <Description<
+            [#[
+               <p>Find Smallest Set of Smallest Rings in graphs</p><p>Changes for 0.1.0 - 2022-12-15</p><ul><li>Initial release.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 03:59:54
+          Link: https://metacpan.org/release/DJZORT/Net-Proxmox-VE-0.38
+          Title: Net-Proxmox-VE-0.38
+            <Description<
+            [#[
+               <p>Pure perl API for Proxmox virtualisation</p><p>Changes for 0.38 - 2023-09-11</p><ul><li>fix/use correct parameter name for user in tests thanks to MartijnLivaart</li>
+               <li>Feat/check new arguments thanks to MartijnLivaart</li>
+               <li>Fix/test access directory thanks to MartijnLivaart via GH#27</li>
+               <li>feat: check if debug parameter propagates from new() thanks to MartijnLivaart via GH#29</li>
+               <li>Pod corrections thanks to poptix via GH#31</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+      >Brew>
+>>
diff --git a/htdocs/cgi-bin/system/modules/CNFDateTime.pm b/htdocs/cgi-bin/system/modules/CNFDateTime.pm
new file mode 100644 (file)
index 0000000..966cb77
--- /dev/null
@@ -0,0 +1,111 @@
+###
+# CNFDateTime objects provide conversions from script to high precision time function not inbuild into perl interpreter.
+# They are lightly initilized, compared to using DateTime directly, so this is not merely a wrapper around DateTime.
+#
+package CNFDateTime;
+use strict;
+use warnings;
+use DateTime;
+use DateTime::Format::DateParse;
+use Time::HiRes qw(time usleep);
+use feature 'signatures';
+
+use constant{
+                FORMAT            => '%Y-%m-%d %H:%M:%S',
+                FORMAT_NANO       => '%Y-%m-%d %H:%M:%S.%3N %Z',
+                FORMAT_SCHLONG    => '%A, %d %B %Y %H:%M:%S %Z',
+                FORMAT_MEDIUM     => '%d %b %Y %H:%M:%S',
+                DEFAULT_TIME_ZONE => 'UTC'
+};
+
+sub new {
+    my $class = shift;
+    my %settings;
+    if(ref($_[0]) eq ''){
+        %settings = @_;
+    }else{
+        %settings = %{$_[0]}
+    }
+    $settings{epoch} = time if !$settings{epoch};
+    $settings{TZ}    = DEFAULT_TIME_ZONE if !$settings{TZ};
+    return bless \%settings, $class
+}
+
+sub datetime($self) {
+    return $self->{datetime} if exists $self->{datetime};
+    $self->{epoch} = time if not defined $self->{epoch};
+     my $dt = DateTime->from_epoch(epoch=>$self->{epoch},time_zone=>$self->{TZ});
+    $self->{datetime} = $dt;
+    return $dt
+}
+sub toTimestamp($self) {
+    return $self->{timestamp} if exists $self->{timestamp};
+    usleep(1_028_69);
+    $self->{timestamp} = $self->datetime() -> strftime(FORMAT_NANO)
+}
+sub toTimestampShort($self) {
+    return $self->{timestamp} if exists $self->{timestamp};
+    usleep(1_028_69);
+    $self->{timestamp} = $self->datetime() -> strftime(FORMAT)
+}
+sub toSchlong($self){
+    return $self->{long} if exists $self->{long};
+    $self->{long} = $self->datetime() -> strftime(FORMAT_SCHLONG)
+}
+sub _toCNFDate ($formated, $timezone) {
+    my $dt = DateTime::Format::DateParse->parse_datetime($formated, $timezone);
+    return new('CNFDateTime',{epoch => $dt->epoch, datetime=>$dt, TZ=>$timezone});
+}
+sub _listAvailableCountryCodes(){
+     require DateTime::TimeZone;
+     return  DateTime::TimeZone->countries();
+}
+sub _listAvailableTZ($country){
+     require DateTime::TimeZone;
+     return  length($country)==2?DateTime::TimeZone->names_in_country( $country ):DateTime::TimeZone->names_in_category( $country );
+}
+
+
+1;
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/PerlCNF.git
+Documentation : Specifications_For_CNF_ReadMe.md
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
+
+=begin history
+Implementing the DateTime module with local libraries, was always problematic at some stages
+as the Perl build or running environment changes.
+
+It is huge and in minimal form usually delivered with default basic or minimal Perl setups.
+It in full provides the most compressive list of world locales and timezones possibilities.
+This means language translations and many other, formats of date outputs based on the locale expected look.
+
+PerlCNF doesn't need it in reality as has its own fixed format to accept and produce.
+PerlCNF must also support world timezones.
+
+Hence it needs DateTime, and some of its modules to provide its timezone string and convert it back and forth.
+Other, DateTime::Format::DateParse, module itself is small, compared to the DateTime module.
+
+Without proper dev. tools and what to look for, it is very hard to figure out what is going on, that things fail.
+For example at the production site. But not on the development setup.
+
+2023-08-23
+
+On occasions DateTime in the past, since 5 eight years to this day, it would lib error crash the whole Perl running environment.
+Something veryhard to find and correct also to forsure test on various installations.
+For these and other reasons, the PerlCNF datetime format was avoided from being implemented or needed.
+
+However, CNFDateTime in its first inclination attempts again to encapsulate this long time due functionality of requirements.
+Came to life in the final of PerlCNF v.2.9, along with the new PerlCNF instruction DATE, of the release.
+
+TestManager has also now been updated to capture any weird and possible Perl underlying connectors to libraries,
+which are of no concern what so ever to the actual local code being tested.
+
+=cut history
\ No newline at end of file
index 7136781a4c40cf5e06ddad7f78244fd47ed2860e..f1fc23807454214e0527a531f49a229f7f03a63d 100644 (file)
@@ -1,13 +1,6 @@
-# 
+###
 # Represents a tree node CNF object having children and a parent node if it is not the root.
-# Programed by  : Will Budic
-# Notice - This source file is copied and usually placed in a local directory, outside of its project.
-# So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
-# Please leave source of origin in this file for future references.
-# Source of Origin : https://github.com/wbudic/PerlCNF.git
-# Documentation : Specifications_For_CNF_ReadMe.md
-# Open Source Code License -> https://choosealicense.com/licenses/isc/
-#
+###
 package CNFNode;
 use strict;
 use warnings;
@@ -33,17 +26,37 @@ sub list     {shift -> {'@@'}}
 sub script   {shift -> {'~'}}
 sub priority {shift -> {'^'}}
 sub evaluate {shift -> {'&'}}
+###
+# Obtains this nodes all public attributes.
+# What you usually only want.
+###
 sub attributes {
     my $self = shift;
-    my @nodes;
+    my @attributes;
     my $regex  = PRIVATE_FIELDS();
-    foreach(sort keys %$self){
-        my $node = $self->{$_};        
-        if($_ !~ /$regex/){
-           $nodes[@nodes] = [$_, $node]
+    foreach (sort keys %$self){;
+        if($_ !~ /^$regex/){
+           $attributes[@attributes] = [$_, $self->{$_}]
+        }
+    }
+    return @attributes;
+}
+###
+# Utility arrays any attributes by list requested.
+# $node-> array('Name','#') will return node 'Name' attribute and value if it has it, onderwise undef for either.
+###
+sub array {
+    my $self = shift;
+    my @attributes = @_;
+    my @arr;
+    foreach my $next(@attributes){
+       my $val = $self -> {$next};
+        if(ref($val) eq 'SCALAR'){
+           $val = $$val;
         }
+       $arr[@arr] = $val#'['.$next.':'.$val.']';
     }
-    return @nodes;
+    return @arr;
 }
 sub nodes {
     my $self = shift;
@@ -53,7 +66,23 @@ sub nodes {
     }
     return ();
 }
-
+###
+# Add another CNFNode to this one, to become a its parent.
+# Returns $self so you can perl them, if you want..
+##
+sub add {
+    my ($self, $node, @nodes)  = @_;
+    my $prev = $self->{'@$'};
+    if($prev) {
+        @nodes = @$prev;
+    }else{
+        @nodes = ();
+    }
+    $node->{'@'}    = \$self;
+    $nodes[@nodes]  = $node;
+    $self -> {'@$'} = \@nodes;
+    return $self;
+}
 ###
 # Convenience method, returns string scalar value dereferenced (a copy) of the property value.
 ##
@@ -65,7 +94,7 @@ sub val {
     if(!$ret && $self->{'@$'}){ #return from subproperties.
         my $buf;
         my @arr = @{$self->{'@$'}};
-        foreach my $node(@arr){               
+        foreach my $node(@arr){
            $buf .= $node -> val() ."\n";
         }
         return $buf;
@@ -78,14 +107,14 @@ sub val {
 
     my $meta =  meta(SHELL());
     sub _evaluate {
-        my $value = shift;    
+        my $value = shift;
         if($value =~ s/($meta)//i){
         $value =~ s/^`|`\s*$/""/g; #we strip as a possible monkey copy had now redundant meta in the value.
         $value = '`'.$value.'`';
         }
-        ## no critic BuiltinFunctions::ProhibitStringyEval                        
+        ## no critic BuiltinFunctions::ProhibitStringyEval
         my $ret = eval $value;
-        ## use critic            
+        ## use critic
         if ($ret){
             chomp  $ret;
             return $ret;
@@ -97,21 +126,29 @@ sub val {
 
 #
 
+sub items(){
+    my $self = shift;
+    return $self -> {'@$'}
+}
+
 ###
 # Search select nodes based on from a path statement.
-# It will always return an array for even a single subproperty
-# The reason is several subproperties of the same name can be contained by the parent property.
+# It will always return an array for even a single subproperty with a passed path ending with (/*).
+# The reason is several subproperties of the same name can be contained as elements of this node.
 # It will return an array of list values with (@@).
-# Or will return an array of its shallow list of child nodes with (@$). 
+# Or will return an array of its shallow list of child nodes with (@$).
 # Or will return an scalar value of an attribute or an property with (#).
 # NOTICE - 20221222 Future versions might provide also for more complex path statements with regular expressions enabled.
 ###
 sub find {
-    my ($self, $path, $ret, $prev, $seekArray,$ref)=@_;
-    foreach my $name(split(/\//, $path)){  
-        if(ref($self) eq "ARRAY"){            
+    my ($self, $path, $ret, $prev, $seekArray,$ref)=@_;  my @arr;
+    foreach my $name(split(/\//, $path)){
+        if( $name eq "*" && @arr){
+            return  \@arr # The path instructs to return an array, which is set but return is set to single only found element.
+        }
+        elsif(ref($self) eq "ARRAY"){
                 if($name eq '#'){
-                    if(ref($ret) eq "ARRAY"){                    
+                    if(ref($ret) eq "ARRAY"){
                         next
                     }else{
                         return $prev->val()
@@ -119,11 +156,10 @@ sub find {
                 }elsif($name =~ /\[(\d+)\]/){
                     $self = $ret = @$ret[$1];
                     next
-
                 }else{
-                    $ret = $prev->{'@$'};               
+                    $ret = $prev->{'@$'};
                 }
-        }else{             
+        }else{
                 if ($name eq '@@') {
                     $ret = $self->{'@@'}; $seekArray = 1;
                     next
@@ -138,7 +174,7 @@ sub find {
                 }
                    $ref =  ref($ret);
                 if(!$seekArray && $ref eq 'ARRAY'){ # ret can be an array of parent same name elemenents.
-                   foreach my$n(@$ret) {                     
+                   foreach my$n(@$ret) {
                      if ($n->node($name)){
                          $ret = $n; last
                      }
@@ -146,21 +182,24 @@ sub find {
                 }elsif($ref eq "CNFNode" && $seekArray){
                     $ret = $ret->{$name};
                     next
-                }else{ 
-                    $ret = $self->{'@$'} if ! $seekArray; # This will initiate further search in subproperties names.                
+                }else{
+                    if (!$seekArray){
+                         # This will initiate further search in subproperties names.
+                          $ret = $self->{'@$'};
+                          @arr = ();
+                    }
                 }
         }
            $ref =  ref($ret);
         if($ret && $ref eq 'ARRAY'){
             my $found = 0;
-            my @arr;
-            undef $prev;        
+            undef $prev;
             foreach my $ele(@$ret){
-                if($seekArray && exists $ele->{'@$'}){                    
-                    foreach my$node(@{$ele->{'@$'}}){                        
+                if($seekArray && exists $ele->{'@$'}){
+                    foreach my$node(@{$ele->{'@$'}}){
                         if ($node->{'_'} eq $name){
                             $arr[@arr] = $ele = $node;
-                        }                        
+                        }
                     }
                     if(@arr>1){
                        $ret = \@arr;
@@ -199,10 +238,10 @@ sub find {
             }
         }
         elsif($name && $ref eq "CNFNode"){
-              $ret  =  $ret -> {$name}             
-        }   
+              $ret  =  $ret -> {$name}
+        }
     }
-    return $ret;
+    return !$ret?\@arr:$ret;
 }
 ###
 # Similar to find, put simpler node by path routine.
@@ -216,13 +255,13 @@ sub node {
        if($ret){
             foreach(@$ret){
                 if ($_->{'_'} eq $path){
-                    return $_; 
+                    return $_;
                 }
             }
        }
       return
     }
-    foreach my $name(split(/\//, $path)){        
+    foreach my $name(split(/\//, $path)){
         $ret = $self->{'@$'};
         if($ret){
             foreach(@$ret){
@@ -232,19 +271,18 @@ sub node {
             }
         }
     }
-    return $ret;    
+    return $ret;
 }
-
 ###
 # Outreached subs list of collected node links found in a property.
 my  @linked_subs;
 
 ###
-# The parsing guts of the CNFNode, that from raw script, recursively creates and tree of nodes from it.
+# The parsing guts of the CNFNode, that from raw script, recursively creates a tree of nodes from it.
 ###
 sub process {
 
-    my ($self, $parser, $script)=@_;      
+    my ($self, $parser, $script)=@_;
     my ($sub, $val, $isArray,$isShortifeScript,$body) = (undef,0,0,0,"");
     my ($tag,$sta,$end)=("","",""); my $meta_shortife = &meta_node_in_shortife;
     my ($opening,$closing,$valing)=(0,0,0);
@@ -254,28 +292,28 @@ sub process {
        $val = $self->{'#'};
        if($val){
           $val .= "\n$script";
-       }else{ 
+       }else{
           $val = $script;
        }
     }else{
         my @lines = split(/\n/, $script);
         foreach my $ln(@lines){
-            $ln =~ s/^\s+|\s+$//g;          
-            if(length ($ln)){              
-                my $isShortife = ($ln =~ s/($meta_shortife)/""/sexi);  
+            $ln =~ s/^\s+|\s+$//g;
+            if(length ($ln)){
+                my $isShortife = ($ln =~ s/($meta_shortife)/""/sexi);
                 if($ln =~ /^([<>\[\]])(.*)([<>\[\]])$/ && $1 eq $3){
                    $sta = $1;
                    $tag = $2;
-                   $end = $3;  
-                   $isShortifeScript = 1 if $isShortife;                 
+                   $end = $3;
+                   $isShortifeScript = 1 if $isShortife;
                    my $isClosing = ($sta =~ /[>\]]/) ? 1 : 0;
                    if($tag =~ /^([#\*\@]+)[\[<](.*)[\]>]\/*[#\*\@]+$/){#<- The \/ can sneak in as included in closing tag.
                         if($1 eq '*'){
                             my $link = $2;
-                            my $rval = $self  -> obtainLink($parser, $link);                               
-                            if($rval){                 
+                            my $rval = $self  -> obtainLink($parser, $link);
+                            if($rval){
                                 if($opening){
-                                   $body .= qq($ln\n);                                   
+                                   $body .= qq($ln\n);
                                 }else{
                                     #Is this a child node?
                                     if(exists $self->{'@'}){
@@ -284,7 +322,7 @@ sub process {
                                         if($prev) {
                                             @nodes = @$prev;
                                         }else{
-                                            @nodes = ();                                   
+                                            @nodes = ();
                                         }
                                         $nodes[@nodes] = CNFNode->new({'_'=>$link, '*'=>$rval,'@' => \$self});
                                         $self->{'@$'}  = \@nodes;
@@ -292,20 +330,20 @@ sub process {
                                     else{
                                         #Links scripted in main tree parent are copied main tree attributes.
                                         $self->{$link} = $rval
-                                    }                                 
+                                    }
                                 }
                                 next
-                            }else{ 
+                            }else{
                                 if(!$opening){warn "Anon link $link not located with $ln for node ".$self->{'_'}};
                             }
                          }elsif($1 eq '@@'){
                                 if($opening==$closing){
-                                   $array[@array] = $2; $val="";     
-                                   next                              
+                                   $array[@array] = $2; $val="";
+                                   next
                                 }
-                         }else{ 
-                            $val = $2;                            
-                         }                         
+                         }else{
+                            $val = $2;
+                         }
                    }elsif($tag =~ /^(.*)[\[<]\/*(.*)[\]>](.*)$/ && $1 eq $3){
                         if($opening){
                                 $body .= qq($ln\n)
@@ -317,21 +355,21 @@ sub process {
                                 if($prev) {
                                     @nodes = @$prev;
                                 }else{
-                                    @nodes = ();                                   
-                                }                        
+                                    @nodes = ();
+                                }
                                 $nodes[@nodes] = $property;
                                 $self->{'@$'} = \@nodes;
                         }
                         next
                     }elsif($isClosing){
                         $opening--;
-                        $closing++;                        
+                        $closing++;
                     }else{
                         $opening++;
-                        $closing--;                        
+                        $closing--;
                     }
 
-                    if(!$sub){                
+                    if(!$sub){
                         $isArray = $isArray? 0 : 1 if $tag =~ /@@/;
                         $sub = $tag;  $body = "";
                         next
@@ -339,20 +377,20 @@ sub process {
                         if($opening==$closing){
                             if($tag eq '#'){
                                 $body =~ s/\s$//;#cut only one last nl if any.
-                                if(!$val){                                    
+                                if(!$val){
                                     $val  = $body;
-                                }else{ 
+                                }else{
                                     $val .= $body
                                 }
                                 $valing = 0;
                                 $tag ="" if $isClosing
                             }else{
                                 my $property = CNFNode->new({'_'=>$sub, '@' => \$self});
-                                my $a = $isArray;   
+                                my $a = $isArray;
                                 if($isShortifeScript){
-                                    my ($sub,$prev,$cnt_nl,$bck_p);                                    
+                                    my ($sub,$prev,$cnt_nl,$bck_p);
                                     while ($body =~ /    (.*)__+   ([\\\|]|\/*)  |  (.*)[:=](.*) | (.*)\n/gmx){
-                                        my @sel =  @{^CAPTURE};                                                                                                                                 
+                                        my @sel =  @{^CAPTURE};
                                            if(defined $sel[0]){
                                                 if ($sel[1]){
                                                     my $t = substr $sel[1],0,1;
@@ -369,11 +407,12 @@ sub process {
                                                             $sub = $parent; next
                                                         }
                                                     }
-                                                    $sub = CNFNode->new({'_'=>$sel[0], '@' => $parent});                                                        
+                                                    $t = $sel[0]; $t=~s/[\s_]*$//g;
+                                                    $sub = CNFNode->new({'_' => $t, '@' => $parent});
                                                     my @elements = exists $parent -> {'@$'} ? $parent -> {'@$'} : ();
                                                     $elements[@elements] = $sub; $prev = $parent; $cnt_nl = 0;
                                                     $parent -> {'@$'} = \@elements;
-                                                }                                           
+                                                }
                                            }
                                            elsif (defined $sel[2] && defined $sel[3]){
                                                   my $attribute = $sel[2]; $attribute =~ s/^\s*|\s*$//g;
@@ -388,9 +427,9 @@ sub process {
                                            elsif (defined  $sel[4]){
                                                   if ($sel[4] eq ''){
                                                         if(++$cnt_nl>1){ #cancel collapse chain and at root of property that is shorted.
-                                                            ##$sub = $property ; 
+                                                            ##$sub = $property ;
                                                             $cnt_nl =0
-                                                        } 
+                                                        }
                                                         next
                                                   }elsif($sel[4] !~ /^\s*\#/ ){
                                                         my $parent = $sub ? $sub->parent() : $property;
@@ -402,7 +441,7 @@ sub process {
                                                     # $sub ="";
                                                   }
                                             }
-                                    }#while                                    
+                                    }#while
                                     $isShortifeScript = 0;
                                 }else{
                                     $property -> process($parser, $body);
@@ -410,7 +449,7 @@ sub process {
                                 $isArray = $a;
                                 if($tag eq '@@'){
                                    $array[@array] = $property;
-                                   if( not exists $property->{'#'} && $body ){ 
+                                   if( not exists $property->{'#'} && $body ){
                                        $body =~ s/\n$//; $property->{'#'} = $body
                                    }
                                 }else{
@@ -419,30 +458,30 @@ sub process {
                                     if($prev) {
                                        @nodes = @$prev;
                                     }else{
-                                       @nodes = ();                                   
+                                       @nodes = ();
                                     }
                                     $nodes[@nodes] = $property;
                                     $self->{'@$'} = \@nodes;
                                 }
                                 undef $sub; $body = $val = "";
                             }
-                            next   
+                            next
                         }else{
                            # warn "Tag $sta$tag$sta failed closing -> $body"
-                        }             
-                    }               
+                        }
+                    }
                 }elsif($tag eq '#'){
                        $valing = 1;
                 }elsif($opening==0 && $isArray){
-                       $array[@array] = $ln;              
-                }elsif($opening==0 && $ln =~ /^([<\[])(.+)([<\[])(.*)([>\]])(.+)([>\]])$/ && 
+                       $array[@array] = $ln;
+                }elsif($opening==0 && $ln =~ /^([<\[])(.+)([<\[])(.*)([>\]])(.+)([>\]])$/ &&
                               $1 eq $3 && $5 eq $7 ){ #<- tagged in line
                         if($2 eq '#') {
                             if($val){$val = "$val $4"}
-                            else{$val = $4}                           
+                            else{$val = $4}
                         }elsif($2 eq '*'){
                                 my $link = $4;
-                                my $rval = $self  -> obtainLink($parser, $link);                                   
+                                my $rval = $self  -> obtainLink($parser, $link);
                                 if($rval){
                                         #Is this a child node?
                                         if(exists $self->{'@'}){
@@ -451,7 +490,7 @@ sub process {
                                             if($prev) {
                                                @nodes = @$prev;
                                             }else{
-                                               @nodes = ();                                   
+                                               @nodes = ();
                                             }
                                             $nodes[@nodes] = CNFNode->new({'_'=>$link, '*'=>$rval, '@' => \$self});
                                             $self->{'@$'} = \@nodes;
@@ -460,29 +499,29 @@ sub process {
                                             #Links scripted in main tree parent are copied main tree attributes.
                                             $self->{$link} = $rval
                                         }
-                                }else{ 
+                                }else{
                                     warn "Anon link $link not located with '$ln' for node ".$self->{'_'} if !$opening;
                                 }
                         }elsif($2 eq '@@'){
-                               $array[@array] = CNFNode->new({'_'=>$2, '#'=>$4, '@' => \$self});                               
+                               $array[@array] = CNFNode->new({'_'=>$2, '#'=>$4, '@' => \$self});
                         }else{
-                                my $property  = CNFNode->new({'_'=>$2, '#'=>$4, '@' => \$self});                                
+                                my $property  = CNFNode->new({'_'=>$2, '#'=>$4, '@' => \$self});
                                 my @nodes;
                                 my $prev = $self->{'@$'};
                                 if($prev) {
                                     @nodes = @$prev;
                                 }else{
-                                    @nodes = ();                                   
+                                    @nodes = ();
                                 }
                                 $nodes[@nodes] = $property;
                                 $self->{'@$'} = \@nodes;
                         }
-                    next                               
+                    next
                 }elsif($val){
                     $val = $self->{'#'};
                     if($val){
                         $self->{'#'} = qq($val\n$ln\n);
-                    }else{ 
+                    }else{
                         $self->{'#'} = qq($ln\n);
                     }
                 }
@@ -494,31 +533,31 @@ sub process {
                     my @attr = ($ln =~ m/([\s\w]*?)\s*[=:]\s*(.*)\s*/);
                     if(@attr>1){
                         my $n = $attr[0];
-                        my $v = $attr[1]; 
+                        my $v = $attr[1];
                         if($v =~ /[<\[]\*[<\[](.*)[]>\]]\*[>\]]/){
-                           $v = $self-> obtainLink($parser, $1) 
-                         } $v =~ m/^(['"]).*(['"])$/g; 
-                           $v =~ s/^$1|$2$//g if($1 && $2 && $1 eq $2);                            
-                        $self->{$n} = $v; 
+                           $v = $self-> obtainLink($parser, $1)
+                         } $v =~ m/^(['"]).*(['"])$/g;
+                           $v =~ s/^$1|$2$//g if($1 && $2 && $1 eq $2);
+                        $self->{$n} = $v;
                         next;
-                    }else{ 
+                    }else{
                        $val = $ln if $val;
-                    }                   
+                    }
                 }
                                     # Very complex rule, allow #comment lines in buffer withing an node value tag, ie [#[..]#]
-                $body .= qq($ln\n)  #if !$tag &&  $ln!~/^\#/ || $tag eq '#' 
+                $body .= qq($ln\n)  #if !$tag &&  $ln!~/^\#/ || $tag eq '#'
             }
             elsif($tag eq '#'){
                   $body .= qq(\n)
             }
-        }        
+        }
     }
     $self->{'@@'} = \@array if @array;
     $self->{'#'} = \$val if $val;
     ## no critic BuiltinFunctions::ProhibitStringyEval
-    no strict 'refs';       
+    no strict 'refs';
     while(@linked_subs){
-       my $entry = pop (@linked_subs);  
+       my $entry = pop (@linked_subs);
        my $node  = $entry->{node};
        my $res   = &{+$entry->{sub}}($node);
           $entry->{node}->{'*'} = \$res;
@@ -530,16 +569,16 @@ sub obtainLink {
     my ($self,$parser,$link, $ret) = @_;
     ## no critic BuiltinFunctions::ProhibitStringyEval
     no strict 'refs';
-    if($link =~/(.*)(\(\.\))$/){       
+    if($link =~/(.*)(\(\.\))$/){
        push @linked_subs, {node=>$self,link=>$link,sub=>$1};
        return 1;
     }elsif($link =~/(\w*)::\w+$/){
         use Module::Loaded qw(is_loaded);
         if(is_loaded($1)){
-           $ret = \&{+$link}($self);                                        
+           $ret = \&{+$link}($self);
         }
-    }    
-    $ret = $parser->obtainLink($link) if !$ret;        
+    }
+    $ret = $parser->obtainLink($link) if !$ret;
     return $ret;
 }
 
@@ -548,14 +587,14 @@ sub obtainLink {
 #
 sub validate {
     my $self = shift;
-    my ($tag,$sta,$end,$lnc,$errors)=("","","",0,0); 
+    my ($tag,$sta,$end,$lnc,$errors)=("","","",0,0);
     my (@opening,@closing,@singels);
     my ($open,$close) = (0,0);
-    my @lines = defined $self -> script() ? split(/\n/, $self->script()) :();    
+    my @lines = defined $self -> script() ? split(/\n/, $self->script()) :();
     foreach my $ln(@lines){
         $ln =~ s/^\s+|\s+$//g;
         $lnc++;
-        #print $ln, "<-","\n";            
+        #print $ln, "<-","\n";
         if(length ($ln)){
             #print $ln, "\n";
             if($ln =~ /^([<>\[\]])(.*)([<>\[\]])(.*)/ && $1 eq $3){
@@ -573,7 +612,7 @@ sub validate {
                       $close++;
                       push @closing, {T=>$tag, idx=>$close, L=>$lnc, N=>($open-$close+1),S=>$sta};
                 }
-                else{                      
+                else{
                       push @opening, {T=>$tag, idx=>$open, L=>$lnc, N=>($open-$close),S=>$sta};
                       $open++;
                  }
@@ -586,14 +625,14 @@ sub validate {
           my $c = pop @closing;
           if(!$c){
             $errors++;
-             warn "Error unclosed tag-> [".$o->{T}.'[ @'.$o->{L}       
+             warn "Error unclosed tag-> [".$o->{T}.'[ @'.$o->{L}
           }
-       }       
+       }
     }else{
        my $errors = 0; my $error_tag; my $nesting;
        my $cnt = $#opening;
-       for my $i (0..$cnt){          
-          my $o = $opening[$i];          
+       for my $i (0..$cnt){
+          my $o = $opening[$i];
           my $c = $closing[$cnt - $i];
           if($o->{T} ne $c->{T}){
             print '['.$o->{T}."[ idx ".$o->{idx}." line ".$o->{L}.
@@ -604,21 +643,21 @@ sub validate {
 
           if($o->{T} ne $c->{T}){
                 my $j = $cnt;
-                for ($j = $cnt; $j>-1; $j--){  # TODO 2023-0117 - For now matching by tag name, 
+                for ($j = $cnt; $j>-1; $j--){  # TODO 2023-0117 - For now matching by tag name,
                      $c = $closing[$j];# can't be bothered, to check if this will always be appropriate.
                     last if $c -> {T} eq $o->{T}
                 }
-                print "\t search [".$o->{T}.'[ idx '.$o->{idx} .' line '.$o->{L}. 
+                print "\t search [".$o->{T}.'[ idx '.$o->{idx} .' line '.$o->{L}.
                       ' top found: ]'.$c->{T}."] idx ".$c->{idx}." line ".$c->{N}." loops: $j \n" if $self->{DEBUG};
           }else{next}
 
           if($o->{T} ne $c->{T} && $o->{N} ne $c->{N}){
-             cluck "Error opening and clossing tags mismatch for ". 
+             cluck "Error opening and clossing tags mismatch for ".
                     _brk($o).' ln: '.$o->{L}.' idx: '.$o->{idx}.
                     ' wrongly matched with '._brk($c).' ln: '.$c->{L}.' idx: '.$c->{idx}."\n";
              $errors++;
           }
-       }       
+       }
     }
     return  $errors;
 }
@@ -631,39 +670,39 @@ sub validate {
 # Compare one node with  another if is equal in structure.
 ##
 sub equals {
-    my ($self, $node, $ref) = @_; $ref = ref($node); 
+    my ($self, $node, $ref) = @_; $ref = ref($node);
     if (ref($node) eq 'CNFNode'){
         my @s = sort keys %$self;
-        my @o = sort keys %$node; 
+        my @o = sort keys %$node;
         my $i=$#o;
         foreach (0..$i){
             my $n = $o[$i-$_];
             if($n eq '~' || $n eq '^'){
-               splice @o,$i-$_,1;               
-            }            
+               splice @o,$i-$_,1;
+            }
         }
         $i=$#s;
         foreach (0..$i){
             my $n = $s[$i-$_];
             if($n eq '~' || $n=~/^CNF_/ || $n=~/^DO_/){
-               splice @s,$i-$_,1;                
-            }            
+               splice @s,$i-$_,1;
+            }
         }$i=0;
         if(@s == @o){
            foreach(@s) {
              if($_ ne $o[$i++]){
                 return 0
              }
-           }  
+           }
            if($self -> {'@$'} && $node -> {'@$'}){
               @s = sort keys @{$self -> {'@$'}};
-              @o = sort keys @{$node -> {'@$'}}; 
+              @o = sort keys @{$node -> {'@$'}};
               $i = 0;
               foreach(@s) {
                 if($_ ne $o[$i++]){
                     return 0
                 }
-              }              
+              }
            }
            return 1;
         }
@@ -671,4 +710,87 @@ sub equals {
     return 0;
 }
 
-1;
\ No newline at end of file
+sub toScript {
+    my($self,$nested,$script)= @_;
+    my($isParent,$tag,$tab); $nested=1 if!$nested; $tab =3*$nested; $tab = ' 'x$tab;
+    if(not exists $self->{'@'}){
+        $script .= "<<".$self->{_}."<TREE>\n";  $isParent = 1;
+    }else{
+        $tag = $self->{_};
+       if($nested){
+          $script  .= "$tab<$tag<\n"
+       }else{
+          $script  .= "$tab\[$tag\[\n"
+       }
+    }
+    my @attr = $self -> attributes();
+    foreach (@attr){
+        if($nested){
+            if(@$_[0] ne '#' && @$_[0] ne '_'){
+                if(@$_[1]){
+                    $script .= "$tab ".@$_[0].": ".@$_[1];
+                }else{
+                    $script .= "$tab ".@$_[0]." ";
+                }
+           }
+        }else{
+           if(@$_[0] ne '#' && @$_[0] ne '_'){
+                if(@$_[1]){
+                    $script .= "$tab ".@$_[0]."=\"".@$_[1]."\"";
+                }else{
+                    $script .= "$tab ".@$_[0]." ";
+                }
+           }
+        }
+        $script .= "\n"
+    }
+    my $list = $self->{'@@'};
+    if($list){
+       foreach(@$list) {
+            $script .= "$tab  <@@<$_>@@>\n"
+       }
+    }
+
+    my $nodes = $self->{'@$'};
+    if($nodes){
+        foreach my $nd (@$nodes) {
+            my $ref = ref($nd);
+            $nd = $$nd if ($ref eq 'REF');
+            if (ref($nd) eq 'CNFNode'){
+               $script .=  toScript($nd, $nested+1);
+            }
+        }
+    }
+    my $val = $self->{'#'};
+    if($val){
+        if(ref($val) eq 'SCALAR'){
+            $val = $$val;
+        }
+        $val =~ s/\n/\n$tab   /gs; $val =~ s/\s*$//;
+        $script .= $tab."[#\[\n$tab   $val\n$tab]#]\n"
+    }
+
+    if ($isParent){
+        $script  .= ">>\n"
+    }else{
+        if($nested){
+          $script  .= "$tab>$tag>\n"
+       }else{
+          $script  .= "$tab]$tag]\n"
+       }
+    }
+    return $script;
+}
+
+1;
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/PerlCNF.git
+Documentation : Specifications_For_CNF_ReadMe.md
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
index 8a7c55458ba44404e92d640cecda26fd0fbbebbb..9d69dd4888c6c38b00c9b9b26728fcbbe556b9ae 100644 (file)
@@ -1,31 +1,23 @@
+###
 # Main Parser for the Configuration Network File Format.
-# Programed by  : Will Budic
-# Notice - This source file is copied and usually placed in a local directory, outside of its project.
-# So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
-# Please leave source of origin in this file for future references.
-# Source of Origin : https://github.com/wbudic/PerlCNF.git
-# Documentation : Specifications_For_CNF_ReadMe.md
-# Open Source Code License -> https://choosealicense.com/licenses/isc/
-#
+##
 package CNFParser;
 
 use strict;use warnings;#use warnings::unused;
-use Exception::Class ('CNFParserException'); 
+use Exception::Class ('CNFParserException');
 use Syntax::Keyword::Try;
 use Hash::Util qw(lock_hash unlock_hash);
-use Time::HiRes qw(time);
-use DateTime;
 
 require CNFMeta; CNFMeta::import();
 require CNFNode;
+require CNFDateTime;
 
-
-# Do not remove the following no critic, no security or object issues possible. 
+# Do not remove the following no critic, no security or object issues possible.
 # We can use perls default behaviour on return.
 ##no critic qw(Subroutines::RequireFinalReturn)
 ##no critic Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions
 
-use constant VERSION => '2.9';
+use constant VERSION => '3.0';
 our @files;
 our %lists;
 our %properties;
@@ -38,15 +30,15 @@ our %ANONS;
 #private -> Instance fields:
                 my  $anons;
                 my %includes;
-                my %instructs;    
+                my %instructs;
 ###
-# CNF Instruction tag covered reserved words. 
-# You probably don't want to use these as your own possible instruction implementation.
+# CNF Instruction tag covered reserved words.
+# You can't use any of these as your own possible instruction implementation, unless in lower case.
 ###
 
-our %RESERVED_WORDS = map +($_, 1), qw{ CONST CONSTANT DATA VARIABLE VAR 
-                                        FILE TABLE TREE INDEX 
-                                        VIEW SQL MIGRATE DO LIB
+our %RESERVED_WORDS = map +($_, 1), qw{ CONST CONSTANT DATA DATE VARIABLE VAR
+                                        FILE TABLE TREE INDEX
+                                        VIEW SQL MIGRATE DO LIB PROCESSOR
                                         PLUGIN MACRO %LOG INCLUDE INSTRUCTOR };
 
 sub isReservedWord    { my ($self, $word)=@_; return $word ? $RESERVED_WORDS{$word} : undef }
@@ -61,10 +53,10 @@ our $CONSTREQ = 0;
 # Create a new CNFParser instance.
 # $path - Path to some .cnf_file file, to parse, not compsuluory to add now? Make undef.
 # $attrs - is reference to hash of constances and settings to dynamically employ.
-# $del_keys -  is a reference to an array of constance attributes to dynamically remove. 
-sub new { my ($class, $path, $attrs, $del_keys, $self) = @_; 
+# $del_keys -  is a reference to an array of constance attributes to dynamically remove.
+sub new { my ($class, $path, $attrs, $del_keys, $self) = @_;
     if ($attrs){
-        $self = \%$attrs;        
+        $self = \%$attrs;
     }else{
         $self = {
                   DO_ENABLED      => 0,  # Enable/Disable DO instruction. Which could evaluated potentially be an doom execute destruction.
@@ -72,15 +64,16 @@ sub new { my ($class, $path, $attrs, $del_keys, $self) = @_;
                   ENABLE_WARNINGS => 1,  # Disable this one, and you will stare into the void, about errors or operations skipped.
                   STRICT          => 1,  # Enable/Disable strict processing to FATAL on errors, this throws and halts parsing on errors.
                   HAS_EXTENSIONS  => 0,  # Enable/Disable extension of custom instructions. These is disabled by default and ingored.
-                  DEBUG           => 0,  # Not internally used by the parser, but possible a convience bypass setting for code using it.
-                  CNF_CONTENT     => "", # Origin of the script, this wull be set by the parser, usually the path of a script file or is direct content.                 
-        }; 
-    }    
+                  DEBUG           => 0,  # Not internally used by the parser, but possible a convienince bypass setting for code using it.
+                  CNF_CONTENT     => "", # Origin of the script, this will be set by the parser, usually the path of a script file or is direct content.
+                  RUN_PROCESSORS  => 1,  # When enabled post parse processors are run, are these outside of the scope of the parsers executions.
+        };
+    }
     $CONSTREQ = $self->{CONSTANT_REQUIRED};
     if (!$self->{ANONS_ARE_PUBLIC}){ #Not public, means are private to this object, that is, anons are not static.
          $self->{ANONS_ARE_PUBLIC} = 0; #<- Caveat of Perl, if this is not set to zero, it can't be accessed legally in a protected hash.
          $self->{__ANONS__} = {};
-    }        
+    }
     if(exists  $self->{'%LOG'}){
         if(ref($self->{'%LOG'}) ne 'HASH'){
             die '%LOG'. "passed attribute is not an hash reference."
@@ -88,9 +81,10 @@ sub new { my ($class, $path, $attrs, $del_keys, $self) = @_;
             $properties{'%LOG'} = $self->{'%LOG'}
         }
     }
-    $self->{STRICT}          = 1 if not exists $self->{STRICT}; #make strict by default if missing. 
+    $self->{STRICT}          = 1 if not exists $self->{STRICT}; #make strict by default if missing.
     $self->{ENABLE_WARNINGS} = 1 if not exists $self->{ENABLE_WARNINGS};
     $self->{HAS_EXTENSIONS}  = 0 if not exists $self->{HAS_EXTENSIONS};
+    $self->{RUN_PROCESSORS}  = 1 if not exists $self->{RUN_PROCESSORS}; #Bby default enabled, disable during script dev.
     $self->{CNF_VERSION}     = VERSION;
     $self->{__DATA__}        = {};
     bless $self, $class; $self->parse($path, undef, $del_keys) if($path);
@@ -98,34 +92,36 @@ sub new { my ($class, $path, $attrs, $del_keys, $self) = @_;
 }
 #
 
-sub import {     
+sub import {
     my $caller = caller;    no strict "refs";
     {
         *{"${caller}::configDumpENV"}  = \&dumpENV;
         *{"${caller}::anon"}           = \&anon;
-        *{"${caller}::SQL"}            = \&SQL;         
+        *{"${caller}::SQL"}            = \&SQL;
+        *{"${caller}::isCNFTrue"}      = \&_isTrue;
+        *{"${caller}::now"}            = \&now;
     }
-    return 1;    
+    return 1;
 }
 
 our $meta_has_priority  = meta_has_priority();
 our $meta_priority      = meta_priority();
 our $meta_on_demand     = meta_on_demand();
 ###
-# The metaverse is that further this can be expanded, 
+# The metaverse is that further this can be expanded,
 # to provide further dynamic meta processing of the property value of an anon.
 # When the future becomes life in anonymity, unknown variables best describe the meta state.
 ##
 package META_PROCESS {
     sub constance{
-         my($class, $set) = @_; 
+         my($class, $set) = @_;
         if(!$set){
             $set =  {anonymous=>'*'}
         }
         bless $set, $class
     }
     sub process{
-        my($self, $property, $val) = @_;        
+        my($self, $property, $val) = @_;
         if($self->{anonymous} ne '*'){
            return  $self->{anonymous}($property,$val)
         }
@@ -140,14 +136,20 @@ return <<__JSON
 {"$property"="$val"}
 __JSON
 }
-
-          
-
+###
+# Check a value if it is CNFPerl boolean true.
+# For isFalse just negate check with not, as undef is concidered false or 0.
+##
+sub _isTrue{
+    my $value = shift;
+    return 0 if(not $value);
+    return ($value =~ /1|true|yes|on/i)
+}
 ###
 # Post parsing instructed special item objects. They have lower priority to Order of apperance and from CNFNodes.
 ##
 package InstructedDataItem {
-    
+
     our $dataItemCounter   = int(0);
 
     sub new { my ($class, $ele, $ins, $val) = @_;
@@ -159,7 +161,7 @@ package InstructedDataItem {
                 ins => $ins,
                 val => $val,
                 '^' => $priority
-        }, $class    
+        }, $class
     }
     sub toString {
         my $self = shift;
@@ -169,25 +171,26 @@ package InstructedDataItem {
 #
 
 ###
-# PropertyValueStyle objects must have same rule of how an property body can be scripted for attributes.
+# PropertyValueStyle objects must have same rule of how a property body can be scripted for attributes.
 ##
-package PropertyValueStyle {    
+package PropertyValueStyle {
 
     sub new {
         my ($class, $element, $script, $self) =  @_;
         $self = {} if not $self;
         $self->{element}=$element;
         if($script){
-            my ($p,$v);                     
+            my ($p,$v);
             foreach my $itm($script=~/\s*(\w*)\s*[:=]\s*(.*)\s*/gm){
                 if($itm){
                     if(!$p){
                         $p = $itm;
                     }else{
+                        $itm =~ s/^\s*(['"])(.*)\g{1}$/$2/g if $itm;
                         $self->{$p}=$itm;
                         undef $p;
                     }
-                }                
+                }
             }
         }else{
             warn "PropertyValue process what?"
@@ -197,7 +200,7 @@ package PropertyValueStyle {
     sub setPlugin{
         my ($self, $obj) =  @_;
         $self->{plugin} = $obj;
-    }    
+    }
     sub result {
         my ($self, $value) =  @_;
         $self->{value} = $value;
@@ -213,14 +216,14 @@ package PropertyValueStyle {
 # i.e. ${CNFParser->new()->anon()}{'MyDynamicAnon'} = 'something';
 # However a private config instance, will have its own anon's. And could be read only if it exist as a property, via this anon(NAME) method.
 # This hasn't been yet fully specified in the PerlCNF specs.
-# i.e. ${CNFParser->new({ANONS_ARE_PUBLIC=>0})->anon('MyDynamicAnon') # <-- Will not be available.  
+# i.e. ${CNFParser->new({ANONS_ARE_PUBLIC=>0})->anon('MyDynamicAnon') # <-- Will not be available.
 ##
 sub anon {  my ($self, $n, $args)=@_;
     my $anechoic = \%ANONS;
     if(ref($self) ne 'CNFParser'){
         $n = $self;
-    }elsif (not $self->{'ANONS_ARE_PUBLIC'}){            
-        $anechoic = $self->{'__ANONS__'};        
+    }elsif (not $self->{'ANONS_ARE_PUBLIC'}){
+        $anechoic = $self->{'__ANONS__'};
     }
     if($n){
         my $ret = %$anechoic{$n};
@@ -230,26 +233,26 @@ sub anon {  my ($self, $n, $args)=@_;
             if($ref eq 'META_PROCESS'){
                 my @arr = ($ret =~ m/(\$\$\$.+?\$\$\$)/gm);
                 foreach my $find(@arr) {# <- MACRO TAG translate. ->
-                        my $s= $find; $s =~ s/^\$\$\$|\$\$\$$//g;# 
+                        my $s= $find; $s =~ s/^\$\$\$|\$\$\$$//g;#
                         my $r = %$anechoic{$s};
                         if(!$r && exists $self->{$s}){#fallback to maybe constant property has been seek'd?
                             $r = $self->{$s};
                         }
                         if(!$r){
-                            warn "Unable to find property to translate macro expansion: $n -> $find\n" 
+                            warn "Unable to find property to translate macro expansion: $n -> $find\n"
                                  unless $self and not $self->{ENABLE_WARNINGS}
                         }else{
-                            $ret =~ s/\Q$find\E/$r/g;                    
+                            $ret =~ s/\Q$find\E/$r/g;
                         }
                 }
                 $ret = $args->process($n,$ret);
 
             }elsif($ref eq 'HASHREF'){
-                foreach my $key(keys %$args){                    
+                foreach my $key(keys %$args){
                     if($ret =~ m/\$\$\$$key\$\$\$/g){
                        my $val = %$args{$key};
                        $ret =~ s/\$\$\$$key\$\$\$/$val/g;
-                    }                    
+                    }
                 }
             }elsif($ref eq 'ARRAY'){  #we rather have argument passed as an proper array then a list with perl
                 my $cnt = 1;
@@ -260,11 +263,11 @@ sub anon {  my ($self, $n, $args)=@_;
             }else{
                 my $val =  %$anechoic{$args};
                 $ret =~ s/\$\$\$$args\$\$\$/$val/g;
-                warn "Scalar argument passed $args, did you mean array to pass? For property $n=$ret\n" 
-                                 unless $self and not $self->{ENABLE_WARNINGS}                
+                warn "Scalar argument passed $args, did you mean array to pass? For property $n=$ret\n"
+                                 unless $self and not $self->{ENABLE_WARNINGS}
             }
         }
-        my $ref = ref($ret);   
+        my $ref = ref($ret);
         return $$ret if $ref eq "REF";
         return $ret->val() if $ref eq "CNFNode";
         return $ret;
@@ -275,7 +278,7 @@ sub anon {  my ($self, $n, $args)=@_;
 ###
 # Validates and returns a constant named value as part of this configs instance.
 # Returns undef if it doesn't exist, and exception if constance required is set;
-sub const { my ($self,$c)=@_; 
+sub const { my ($self,$c)=@_;
     if(exists $self->{$c}){
        return  $self->{$c}
     }
@@ -285,32 +288,43 @@ sub const { my ($self,$c)=@_;
 
 ###
 # Collections are global, Reason for this is that any number of subsequent files parsed,
-# might contain properties that overwrite previous existing ones. 
-# Or require ones that don't includes, expecting thm to be there.
+# might contain properties that overwrite previous existing ones.
+# Or require ones that don't include, and expecting them to be there.
 # This overwritting can be erronous, but also is not expected to be very common to happen.
 # Following method, provides direct access to the properties, this method shouldn't be used in general.
 sub collections {\%properties}
 
-# Collection now returns the contained type dereferenced.
+#@Deprecated use property subroutine instead.
+sub collection {
+return property(@_);
+}
+###
+# Collection now returns the contained type dereferenced and is concidered a property.
 # Make sure you use the appropriate Perl type on the receiving end.
 # Note, if properties contain any scalar key entry, it sure hasn't been set by this parser.
-sub collection { my($self, $name) = @_;
+#
+sub property { my($self, $name) = @_;
     if(exists($properties{$name})){
        my $ret = $properties{$name};
-       if(ref($ret) eq 'ARRAY'){ 
+       my $ref = ref($ret);
+       if($ref eq 'ARRAY'){
           return  @{$ret}
-       }else{
+       }elsif($ref eq 'PropertyValueStyle'){
+          return $ret;
+       }
+       else{
           return  %{$ret}
        }
     }
     return %properties{$name}
 }
-sub data {shift->{'__DATA__'}}
 
-sub listDelimit {                 
-    my ($this, $d , $t)=@_;                 
+sub data {return shift->{'__DATA__'}}
+
+sub listDelimit {
+    my ($this, $d , $t)=@_;
     my @p = @{$lists{$t}};
-    if(@p&&$d){                   
+    if(@p&&$d){
     my @ret = ();
     foreach (@p){
         my @s = split $d, $_;
@@ -319,13 +333,13 @@ sub listDelimit {
     $lists{$t}=\@ret;
     return @{$lists{$t}};
     }
-    return;            
+    return;
 }
 sub lists {\%lists}
 sub list  {
-        my $t=shift;if(@_ > 0){$t=shift;} 
-        my $an = $lists{$t}; 
-        return @{$an} if defined $an; 
+        my $t=shift;if(@_ > 0){$t=shift;}
+        my $an = $lists{$t};
+        return @{$an} if defined $an;
         die "Error: List name '$t' not found!"
 }
 
@@ -346,14 +360,16 @@ sub addENVList { my ($self, @vars) = @_;
     }return;
 }
 
-
-sub template { my ($self, $property, %macros) = @_;    
+###
+# Perform a macro replacement on tagged strings in a property value.
+##
+sub template { my ($self, $property, %macros) = @_;
     my $val = $self->anon($property);
-    if($val){       
+    if($val){
        foreach my $m(keys %macros){
            my $v = $macros{$m};
            $m ="\\\$\\\$\\\$".$m."\\\$\\\$\\\$";
-           $val =~ s/$m/$v/gs;       
+           $val =~ s/$m/$v/gs;
        }
        my $prev;
        foreach my $m(split(/\$\$\$/,$val)){
@@ -372,7 +388,7 @@ sub template { my ($self, $property, %macros) = @_;
            }
        }
        return $val;
-    }    
+    }
 }
 #
 
@@ -383,20 +399,20 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
     $t = "" if not defined $t;
 
     if($t eq 'CONST' or $t eq 'CONSTANT'){#Single constant with mulit-line value;
-        $v =~ s/^\s//;        
-        # Not allowed to overwrite constant. i.e. it could be DO_ENABLED which is restricted.
+        # It is NOT allowed to overwrite constant.
         if (not $self->{$e}){
-            $self->{$e} = $v if not $self->{$e};
+            $v =~ s/^\s//;
+            $self->{$e} = $v;
         }else{
             warn "Skipped constant detected assignment for '$e'.";
         }
     }
     elsif($t eq 'VAR' or $t eq 'VARIABLE'){
-        $v =~ s/^\s//;        
-        $anons->{$e} = $v;        
+        $v =~ s/^\s//;
+        $anons->{$e} = $v;
     }
     elsif($t eq 'DATA'){
-        $v=~ s/^\n//; 
+        $v=~ s/^\n//;
         foreach(split /~\n/,$v){
             my @a;
             $_ =~ s/\\`/\\f/g;#We escape to form feed  the found 'escaped' backtick so can be used as text.
@@ -415,7 +431,7 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
                     }
                     push @a, $v;
                 }
-                else{                            
+                else{
                     if($t =~ /^\#(.*)/) {#First is usually ID a number and also '#' signifies number.
                         $d = $1;#substr $d, 1;
                         $d=0 if !$d; #default to 0 if not specified.
@@ -425,19 +441,31 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
                         push @a, $d;
                     }
                 }
-            }                   
+            }
 
             my $existing = $self->{'__DATA__'}{$e};
             if(defined $existing){
                 my @rows = @$existing;
-                push @rows, [@a] if scalar @a >0; 
+                push @rows, [@a] if scalar @a >0;
                 $self->{'__DATA__'}{$e} = \@rows
             }else{
-                my @rows; push @rows, [@a];            
-                $self->{'__DATA__'}{$e} = \@rows if scalar @a >0;   
+                my @rows; push @rows, [@a];
+                $self->{'__DATA__'}{$e} = \@rows if scalar @a >0;
             }
-        }           
-        
+        }
+
+    }elsif($t eq 'DATE'){
+        if($v && $v !~ /now|today/i){
+           $v =~ s/^\s//;
+           if($self->{STRICT}&&$v!~/^\d\d\d\d-\d\d-\d\d/){
+              $self-> warn("Invalid date format: $v expecting -> YYYY-MM-DD at start as possibility of  DD-MM-YYYY or MM-DD-YYYY is ambiguous.")
+           }
+           $v = CNFDateTime::_toCNFDate($v,$self->{'TZ'});
+
+        }else{
+           $v = CNFDateTime->new(TZ=>$self->{'TZ'});
+        }
+       $anons->{$e} = $v;
     }elsif($t eq 'FILE'){#@TODO Test case this
         my ($i,$path,$cnf_file) = (0,"",$self->{CNF_CONTENT});
         $v=~s/\s+//g;
@@ -486,27 +514,27 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
                                 push @a, $d
                             }
                             else{
-                            push @a, $d; 
-                            }                                                
-                        }                                
+                            push @a, $d;
+                            }
+                        }
                         my $existing = $self->{'__DATA__'}{$e};
                         if(defined $existing){
                                 my @rows = @$existing;
-                                push @rows, [@a] if scalar @a >0; 
+                                push @rows, [@a] if scalar @a >0;
                                 $self->{'__DATA__'}{$e} = \@rows
                         }else{
-                                my @rows; push @rows, [@a];   
-                                $self->{'__DATA__'}{$e} = \@rows if scalar @a >0;   
+                                my @rows; push @rows, [@a];
+                                $self->{'__DATA__'}{$e} = \@rows if scalar @a >0;
                         }
-                    }   
+                    }
                 }
-            }       
-        }              
+            }
+        }
     }elsif($t eq 'INCLUDE'){
             $includes{$e} = {loaded=>0,path=>$e,v=>$v};
     }elsif($t eq 'TREE'){
         my  $tree = 0;
-        if (!$v){                
+        if (!$v){
                 $v = $e;
                 $e = 'LAST_DO';
         }
@@ -516,12 +544,12 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
         if( $v =~ s/$meta_priority/""/sexi){
             $priority = $2;
         }
-            $tree = CNFNode->new({'_'=>$e,'~'=>$v,'^'=>$priority}); 
+            $tree = CNFNode->new({'_'=>$e,'~'=>$v,'^'=>$priority});
             $tree->{DEBUG} = 1 if $self->{DEBUG};
             $instructs{$e} = $tree;
-    }elsif($t eq 'TABLE'){         # This has now be late bound and send to the CNFSQL package. since v.2.6
-        SQL()->createTable($e,$v) }  # It is hardly been used. But in future itt might change.
-        elsif($t eq 'INDEX'){ SQL()->createIndex($v)}  
+    }elsif($t eq 'TABLE'){           # This has now be late bound and send to the CNFSQL package. since v.2.6
+        SQL()->createTable($e,$v) }  # It is hardly been used. But in future this might change.
+        elsif($t eq 'INDEX'){ SQL()->createIndex($v)}
             elsif($t eq 'VIEW'){ SQL()->createView($e,$v)}
                 elsif($t eq 'SQL'){ SQL($e,$v)}
                     elsif($t eq 'MIGRATE'){SQL()->migrate($e, $v)
@@ -529,7 +557,7 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
     elsif($t eq 'DO'){
         if($DO_ENABLED){
             my $ret;
-            if (!$v){                
+            if (!$v){
                  $v = $e;
                  $e = 'LAST_DO';
             }
@@ -538,14 +566,14 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
             }
             if( $v =~ s/($meta_priority)/""/sexi){
                 $priority = $2;
-            }            
+            }
             if($v=~ s/($meta_on_demand)/""/ei){
                $anons->{$e} = CNFNode -> new({'_'=>$e,'&'=>$v,'^'=>$priority});
                return;
             }
-            ## no critic BuiltinFunctions::ProhibitStringyEval                        
+            ## no critic BuiltinFunctions::ProhibitStringyEval
                $ret = eval $v if not $ret;
-            ## use critic            
+            ## use critic
              if ($ret){
                  chomp $ret;
                  $anons->{$e} = $ret;
@@ -558,15 +586,15 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
         }
     }elsif($t eq 'LIB'){
         if($DO_ENABLED){
-            if (!$v){                
-                        $v = $e;
-                        $e = 'LAST_LIB';
-            }          
+            if (!$v){
+                 $v = $e;
+                 $e = 'LAST_LIB';
+            }
             try{
                 use Module::Load;
                 autoload $v;
                 $v =~ s/^(.*\/)*|(\..*)$//g;
-                $anons->{$e} = $v;           
+                $anons->{$e} = $v;
             }catch{
                     $self->warn("Module DO_ENABLED library failed to load: $v\n");
                     $anons->{$e} = '<<ERROR>>';
@@ -576,35 +604,41 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
             $anons->{$e} = '<<ERROR>>';
         }
     }
-    elsif($t eq 'PLUGIN'){ 
+    elsif($t eq 'PLUGIN'){
         if($DO_ENABLED){
-            $instructs{$e} = InstructedDataItem -> new($e, 'PLUGIN', $v);                    
+            $instructs{$e} = InstructedDataItem -> new($e, 'PLUGIN', $v);
         }else{
             $self->warn("DO_ENABLED is set to false to process following plugin: $e\n")
-        }                
+        }
     }
-    elsif($t eq 'INSTRUCTOR'){ 
+    elsif($t eq 'PROCESSOR'){
+        if(not $self->registerProcessor($e, $v)){
+            CNFParserException->throw("PostParseProcessor Registration Failed for '<<$e<$t>$v>>'!\t");
+        }
+    }
+    elsif($t eq 'INSTRUCTOR'){
         if(not $self->registerInstructor($e, $v) && $self->{STRICT}){
             CNFParserException->throw("Instruction Registration Failed for '<<$e<$t>$v>>'!\t");
         }
     }
+    elsif($t eq 'MACRO'){
+        $instructs{$e}=$v;
+    }
     elsif(exists $instructors{$t}){
         if(not $instructors{$t}->instruct($e, $v) && $self->{STRICT}){
             CNFParserException->throw("Instruction processing failed for '<<$e<$t>>'!\t");
         }
     }
-    elsif($t eq 'MACRO'){                  
-          $instructs{$e}=$v;
-    }else{
-        #Register application statement as either an anonymous one. Or since v.1.2 a listing type tag.                 
+    else{
+        #Register application statement as either an anonymous one. Or since v.1.2 a listing type tag.
         if($e !~ /\$\$$/){ #<- It is not matching {name}$$ here.
             if($self->{'HAS_EXTENSIONS'}){
                 $anons->{$e} = InstructedDataItem->new($e,$t,$v)
             }else{
-                $v = $t if not $v; 
+                $v = $t if not $v;
                 if($e=~/^\$/){
                     $self->{$e}  = $v if !$self->{$e}; # Not allowed to overwrite constant.
-                }else{                        
+                }else{
                     $anons->{$e} = $v
                 }
             }
@@ -616,8 +650,8 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
             my $array = $lists{$e};
             if(!$array){$array=();$lists{$e} = \@{$array};}
             push @{$array}, $v;
-        }            
-    }            
+        }
+    }
 }
 
 ###
@@ -626,11 +660,11 @@ sub doInstruction { my ($self,$e,$t,$v) = @_;
 sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
 
     my @tags;
-    if($self->{'ANONS_ARE_PUBLIC'}){  
+    if($self->{'ANONS_ARE_PUBLIC'}){
        $anons = \%ANONS;
-    }else{          
+    }else{
        $anons = $self->{'__ANONS__'};
-    } 
+    }
     #private %includes; for now we keep on possible multiple calls to parse.
     #private instructs on this parse call.
     %instructs = ();
@@ -639,12 +673,12 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
     unlock_hash(%$self);
 
     if(not $content){
-        open(my $fh, "<:perlio", $cnf_file )  or  die "Can't open $cnf_file -> $!";        
-        read $fh, $content, -s $fh;        
+        open(my $fh, "<:perlio", $cnf_file )  or  die "Can't open $cnf_file -> $!";
+        read $fh, $content, -s $fh;
         close $fh;
         my @stat = stat($cnf_file);
-        $self->{CNF_STAT}    = \@stat; 
-        $self->{CNF_CONTENT} = $cnf_file;        
+        $self->{CNF_STAT}    = \@stat;
+        $self->{CNF_CONTENT} = $cnf_file;
     }else{
         my $type =Scalar::Util::reftype($content);
         if($type && $type eq 'ARRAY'){
@@ -658,9 +692,9 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
 
 
     my $spc =   $content =~ /\n/ ? '(<{2,3}?)(<*.*?>*)(>{2,3})' : '(<{2,3}?)(<*.*?>*?)(>{2,3})$';
-    @tags   =  ($content =~ m/$spc/gms);    
+    @tags   =  ($content =~ m/$spc/gms);
 
-    foreach my $tag (@tags){             
+    foreach my $tag (@tags){
          next if not $tag;
       next if $tag =~ m/^(>+)|^(<<)/;
       if($tag =~ m/^<(\w*)\s+(.*?)>*$/gs){ # Original fastest and early format: <<<anon value>>>
@@ -668,29 +702,28 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
            my $v = $2;
            if(isReservedWord($self,$t)){
               my $isVar = ($t eq 'VARIABLE' || $t eq 'VAR');
-              if($t eq 'CONST' or $isVar){ #constant multiple properties.                 
-                    foreach  my $line(split '\n', $v) { 
-                            $line =~ s/^\s+|\s+$//;  # strip unwanted spaces                            
+              if($t eq 'CONST' or $isVar){ #constant multiple properties.
+                    foreach  my $line(split '\n', $v) {
+                            $line =~ s/^\s+|\s+$//;  # strip unwanted spaces
                             $line =~ s/\s*>$//;
-                            $line =~ m/([\$\w]*)(\s*=\s*)(.*)/g;                            
+                            $line =~ m/([\$\w]*)(\s*=\s*)(.*)/g;
                             my $name = $1;
-                               $line = $3;
+                               $line = $3; $line =~ s/^\s*(['"])(.*)\g{1}$/$2/;#strip quotes
                             if(defined $name){
                                 if($isVar){
-                                    $line =~ s/^\s*["']|['"]\s*$//g;#strip qoutes
                                     $anons ->{$name} = $line if $line
                                 }else{
-                                    if($line and not $self->{$name}){# Not allowed to overwrite constant.
-                                    $line =~ s/^\s*["']|['"]\s*$//g;#strip qoutes
-                                    $self->{$name} = $line; 
-                                    }else{
+                                  if($line and not $self->{$name}){# Not allowed to overwrite constant.
+
+                                    $self->{$name} = $line;
+                                  }else{
                                         warn "Skipping and keeping previously set constance -> [$name] the new value ".
                                         ($line eq $self->{$name})?"matches it":"dosean't match -> $line."
-                                    }
+                                  }
                                 }
                             }
                     }
-              }else{                                
+              }else{
                 doInstruction($self,$v,$t,undef);
               }
            }else{
@@ -701,7 +734,7 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
         }else{
             #vars are e-element,t-token or instruction,v- for value, vv -array of the lot.
             my ($e,$t,$v,@vv);
-            
+
             # Check if very old format and don't parse the data for old code compatibility to (still) do it.
             # This is interesting, as a newer format file is expected to use the DATA instruction and final data specified script rules.
             if($CNF_VER eq 'CNF2.2' && $tag =~ m/(\w+)\s*(<\d+>\s)\s*(.*\n)/mg){#It is old DATA format annon
@@ -713,7 +746,7 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
             }
             # Before mauling into possible value types, let us go for the full expected tag specs first:
             # <<{$sig}{name}<{INSTRUCTION}>{value\n...value\n}>>
-            # Found in -> <https://github.com/wbudic/PerlCNF//CNF_Specs.md>  
+            # Found in -> <https://github.com/wbudic/PerlCNF//CNF_Specs.md>
             if($tag !~ /\n/ && $tag =~ /^([@%\$\.\/\w]+)\s*([ <>]+)(\w*>)(.*)/) {
                 $e = $1;
                 $t = $2;
@@ -725,7 +758,7 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
                          $t = $3;
                          $v = $5;
                 }
-            }else{            
+            }else{
                                                 #############################################################################
                 $tag =~ m/\s*([@%\$\.\/\w]+)\s* # The name.
                                 ([ <>\n])       # begin or close of instruction, where '\n' mark in script as instruction less.
@@ -735,39 +768,39 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
                                        (>$)*    # capture above value up to here from buffer, i.e. if comming from a >>> tag.
                          /gmxs;                 ###############################################################################
 
-                $e =$1; 
+                $e =$1;
                 if($e eq '@' or $2 eq '<' or ($2 eq '>' and !$4)){
-                $t = $3; 
+                $t = $3;
                 }else{
                 $t = $1;
-                $e = $3 
+                $e = $3
                 }
                 $v= $5;
                 $v =~ s/>$//m if defined($4) && $4 eq '<' or $6; #value has been crammed into an instruction?
-            
+
             }
             if(!$v && !$RESERVED_WORDS{$t}){
-                $v= $t; 
-            }            
+                $v= $t;
+            }
             $v =~ s/\\</</g; $v =~ s/\\>/>/g;# escaped brackets from v.2.8.
-           
-            #Do we have an autonumbered instructed list?   
+
+            #Do we have an autonumbered instructed list?
             #DATA best instructions are exempted and differently handled by existing to only one uniquely named property.
             #So its name can't be autonumbered.
-            if ($e =~ /(.*?)\$\$$/){    
+            if ($e =~ /(.*?)\$\$$/){
                 $e = $1;
                 if($t && $t ne 'DATA'){
                    my $array = $lists{$e};
-                   if(!$array){$array=();$lists{$e} = \@{$array};}               
+                   if(!$array){$array=();$lists{$e} = \@{$array};}
                    push @{$array}, InstructedDataItem -> new($e, $t, $v);
                    next
-                }   
+                }
             }elsif ($e eq '@'){#collection processing.
                 my $isArray = $t=~ m/^@/;
                 # if(!$v && $t =~ m/(.*)>(\s*.*\s*)/gms){
                 #     $t = $1;
                 #     $v = $2;
-                # }               
+                # }
                 my @lst = ($isArray?split(/[,\n]/, $v):split('\n', $v)); $_="";
                 my @props = map {
                         s/^\s+|\s+$//;   # strip unwanted spaces
@@ -780,15 +813,15 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
                         $self->warn("ERROR collection is trying to use a reserved property name -> $t.");
                         next
                     }else{
-                            my @arr=(); 
-                            foreach  (@props){                        
+                            my @arr=();
+                            foreach  (@props){
                                 push @arr, $_ if($_ && length($_)>0);
                             }
                             $properties{$t}=\@arr;
                     }
                 }else{
-                    my %hsh;                    
-                    my $macro = 0;                    
+                    my %hsh;
+                    my $macro = 0;
                     if(exists($properties{$t})){
                         if($self->isReservedWord($t)){
                             $self->warn("Skipped overwritting reserved property -> $t.");
@@ -797,24 +830,24 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
                             %hsh =  %{$properties{$t}}
                         }
                     }else{
-                       %hsh =();                      
+                       %hsh =();
                     }
-                    foreach  my $p(@props){ 
+                    foreach  my $p(@props){
                         if($p && $p eq 'MACRO'){$macro=1}
-                        elsif( $p && length($p)>0 ){                            
+                        elsif( $p && length($p)>0 ){
                             my @pair = ($p=~/\s*([-+_\w]*)\s*[=:]\s*(.*)/s);#split(/\s*=\s*/, $p);
-                            next if (@pair != 2 || $pair[0] =~ m/^[#\\\/]+/m);#skip, it is a comment or not '=' delimited line.                            
-                            my $name  = $pair[0]; 
+                            next if (@pair != 2 || $pair[0] =~ m/^[#\\\/]+/m);#skip, it is a comment or not '=' delimited line.
+                            my $name  = $pair[0];
                             my $value = $pair[1]; $value =~ s/^\s*["']|['"]$//g;#strip quotes
                             if($macro){
                                 my @arr = ($value =~ m/(\$\$\$.+?\$\$\$)/gm);
-                                foreach my $find(@arr) {                                
+                                foreach my $find(@arr) {
                                     my $s = $find; $s =~ s/^\$\$\$|\$\$\$$//g;
-                                    my $r = $anons->{$s};                                    
+                                    my $r = $anons->{$s};
                                     $r = $self->{$s} if !$r;
                                     $r = $instructs{$s} if !$r;
                                     CNFParserException->throw(error=>"Unable to find property for $t.$name -> $find\n",show_trace=>1) if !$r;
-                                    $value =~ s/\Q$find\E/$r/g;                    
+                                    $value =~ s/\Q$find\E/$r/g;
                                 }
                             }
                             $hsh{$name}=$value;  $self->log("macro $t.$name->$value\n") if $self->{DEBUG}
@@ -837,17 +870,17 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
                 my $v = $struct;
                 my @arr = ($v =~ m/(\$\$\$.+?\$\$\$)/gm);
                 foreach my $find(@arr) {# <- MACRO TAG translate. ->
-                        my $s= $find; $s =~ s/^\$\$\$|\$\$\$$//g;# 
+                        my $s= $find; $s =~ s/^\$\$\$|\$\$\$$//g;#
                         my $r = %$anons{$s};
-                        $r = $self->{$s} if !$r;                    
+                        $r = $self->{$s} if !$r;
                         if(!$r){
                             $self->warn("Unable to find property to translate macro expansion: $e -> $find\n");
                         }else{
-                            $v =~ s/\Q$find\E/$r/g;                    
+                            $v =~ s/\Q$find\E/$r/g;
                         }
-                }            
+                }
                 $anons->{$e}=$v;
-            }else{ 
+            }else{
                 $items[@items] = $struct;
             }
         }
@@ -856,70 +889,74 @@ sub parse {  my ($self, $cnf_file, $content, $del_keys) = @_;
 
         for my $idx(0..$#items) {
             my $struct = $items[$idx];
-            my $type =  ref($struct); 
+            my $type =  ref($struct);
             if($type eq 'CNFNode' && $struct-> priority() > 0){
                $struct->validate() if $self->{ENABLE_WARNINGS};
-               $anons ->{$struct->name()} = $struct->process($self, $struct->script());               
+               $anons ->{$struct->name()} = $struct->process($self, $struct->script());
                splice @items, $idx, 1
             }
         }
         #Now only what is left instructed data items or plugins, and nodes that have assigned last priority, if any.
         for my $idx(0..$#items) {
             my $struct = $items[$idx];
-            my $type =  ref($struct); 
-            if($type eq 'CNFNode'){   
-               $struct->validate() if $self->{ENABLE_WARNINGS};            
-               $anons->{$struct->name()} = $struct->process($self, $struct->script());               
-            }elsif($type eq 'InstructedDataItem'){ 
+            my $type =  ref($struct);
+            if($type eq 'CNFNode'){
+               $struct->validate() if $self->{ENABLE_WARNINGS};
+               $anons->{$struct->name()} = $struct->process($self, $struct->script());
+            }elsif($type eq 'InstructedDataItem'){
                 my $t = $struct->{ins};
-                if($t eq 'PLUGIN'){ 
+                if($t eq 'PLUGIN'){
                    instructPlugin($self,$struct,$anons);
-                }                
+                }
             }else{warn "What is -> $struct type:$type ?"}
         }
-        undef %instructs;        
+        undef %instructs;
     }
     #Do scripted includes.
-    my @inc = sort values %includes;    
+    my @inc = sort values %includes;
     $includes{$0} = {loaded=>1, path=>$self->{CNF_CONTENT}}; #<- to prevent circular includes.
     foreach my $file(@inc){
         if(!$file->{loaded} && $file->{path} ne $self->{CNF_CONTENT}){
            if(open(my $fh, "<:perlio", $file->{path} )){
                 read $fh, $content, -s $fh;
-              close   $fh;              
+              close   $fh;
               if($content){
                  $file->{loaded} = 1;
                  $self->parse(undef, $content)
               }else{
                  $self->error("Include content is blank for -> ".$file->{path})
-              }              
+              }
             }else{
                  CNFParserException->throw("Can't open ".$file->{path}." -> $!") if $self->{STRICT};
                  $file->{loaded} = 0;
                  $self->error("Script include not available -> ".$file->{path})
             }
         }
-    }    
-    foreach my $k(@$del_keys){        
+    }
+    foreach my $k(@$del_keys){
         delete $self->{$k} if exists $self->{$k}
     }
+    my $runProcessors = $self->{RUN_PROCESSORS} ? 1: 0;
     lock_hash(%$self);#Make repository finally immutable.
+    runPostParseProcessors($self) if $runProcessors;
+    return $self
 }
 #
 
 sub instructPlugin {
-     my ($self, $struct, $anons) = @_;
-    try{             
+    my ($self, $struct, $anons) = @_;
+    try{
         $properties{$struct->{'ele'}} = doPlugin($self, $struct, $anons);
         $self->log("Plugin instructed ->". $struct->{'ele'});
-    }catch($e){ 
+    }catch($e){
         if($self->{STRICT}){
-            CNFParserException->throw(error=>$e, show_trace=>1);
+            CNFParserException->throw(error=>$e);
         }else{
-            $self->trace("Error @ Plugin -> ". $struct->toString() ." Error-> $@")                                 
+            $self->trace("Error @ Plugin -> ". $struct->toString() ." Error-> $@")
         }
     }
 }
+#
 
 ###
 # Register Instructor on tag and value for to be externally processed.
@@ -927,51 +964,123 @@ sub instructPlugin {
 # $body     - Contains attribute(s) linking to method(s) to be registered.
 # @TODO Current Under development.
 ###
-sub registerInstructor { 
-     my ($self, $package, $body) = @_;
-     $body =~ s/^\s*|\s*$//g;
-     my ($obj, %args, $ins);
-     foreach my $ln(split(/\n/,$body)){
-             my @pair = $ln =~ /\s*(\w+)[:=](.*)\s*/;
-             my $ins  = $1; $ins = $ln if !$ins;
-             my $mth  = $2;
-             if($ins =~ /[a-z]/){
-                $args{$ins} = $mth;
-                next
-             }             
-             if(exists $instructors{$ins}){
-                $self -> error("$package<$ins> <- Instruction has been previously registered by: ".ref(${$instructors{$ins}}));
+sub registerInstructor {
+    my ($self, $package, $body) = @_;
+    $body =~ s/^\s*|\s*$//g;
+    my ($obj, %args, $ins, $mth);
+    foreach my $ln(split(/\n/,$body)){
+            my @pair = $ln =~ /\s*(\w+)[:=](.*)\s*/;
+            $ins  = $1; $ins = $ln if !$ins;
+            $mth  = $2;
+            if($ins =~ /[a-z]/i){
+               $args{$ins} = $mth;
+            }
+    }
+    if(exists $instructors{$ins}){
+       $self -> error("$package<$ins> <- Instruction has been previously registered by: ".ref(${$instructors{$ins}}));
+       return;
+    }else{
+
+        foreach(values %instructors){
+            if(ref($$_) eq $package){
+                $obj = $_; last
+            }
+        }
+
+        if(!$obj){
+            ## no critic (RequireBarewordIncludes)
+            require $package.'.pm';
+            my $methods =   Class::Inspector->methods($package, 'full', 'public');
+            my ($has_new,$has_instruct);
+            foreach(@$methods){
+                $has_new      = 1 if $_ eq "$package\::new";
+                $has_instruct = 1 if $_ eq "$package\::instruct";
+            }
+            if(!$has_new){
+                $self -> log("ERR $package<$ins> -> new() method not found for package.");
                 return;
-             }else{
-                foreach(values %instructors){
-                    if(ref($$_) eq $package){
-                       $obj = $_; last
-                    }
-                }
-                if(!$obj){
-                    ## no critic (RequireBarewordIncludes)
-                    require $package.'.pm';
-                    my $methods =   Class::Inspector->methods($package, 'full', 'public');
-                    my ($has_new,$has_instruct);
-                    foreach(@$methods){
-                        $has_new      = 1 if $_ eq "$package\::new";
-                        $has_instruct = 1 if $_ eq "$package\::instruct";
-                    }
-                    if(!$has_new){
-                        $self -> log("ERR $package<$ins> -> new() method not found for package.");
-                        return;
-                    }
-                    if(!$has_instruct){
-                        $self -> log("ERR $package<$ins> -> instruct() required method not found for package.");
-                        return;
-                    }                
-                    $obj = $package -> new(\%args);
-                }
-                $instructors{$ins} = \$obj;
-             }
-     }
-     return \$obj;
+            }
+            if(!$has_instruct){
+                $self -> log("ERR $package<$ins> -> instruct() required method not found for package.");
+                return;
+            }
+            $obj = $package -> new(\%args);
+        }
+        $instructors{$ins} = \$obj
+    }
+    return \$obj;
 }
+#
+
+###
+# Register PostParseProcessor for further externally processing.
+# $package  - Is the anonymouse package name.
+# $body     - Contains attribute(s) where function is the most required one.
+###
+sub registerProcessor {
+    my ($self, $package, $body) = @_;
+        $body =~ s/^\s*|\s*$//g if $body;
+    my ($obj, %args, $ins, $mth, $func);
+    foreach my $ln(split(/\n/,$body)){
+            my @pair = $ln =~ /\s*(\w+)[:=](.*)\s*/;
+            $ins  = $1; $ins = $ln if !$ins;
+            $mth  = $2;
+            if($ins =~ /^func\w*/){
+               $func = $mth
+            }
+            elsif($ins =~ /[a-z]/i){
+               $args{$ins} = $mth
+            }
+    }
+    $func = $ins if !$func;
+    if(!$func){
+         $self -> log("ERR <<$package<$body>> function attribute not found set.");
+        return;
+    }
+    ## no critic (RequireBarewordIncludes)
+    require $package.'.pm';
+    my $methods =   Class::Inspector->methods($package, 'full', 'public');
+    my ($has_new,$has_func);
+    foreach(@$methods){
+        $has_new  = 1 if $_ eq "$package\::new";
+        $has_func = 1 if $_ eq "$package\::$func";
+    }
+    if(!$has_new){
+        $self -> log("ERR In package $package -> new() method not found for package.");
+        return;
+    }
+    if(!$has_func){
+        $self -> log("ERR In package $package -> $func(\$parser) required method not found for package.");
+        return;
+    }
+    $obj = $package -> new(\%args);
+    $self->addPostParseProcessor($obj,$func);
+    return 1;
+}
+
+sub addPostParseProcessor {
+    my $self = shift;
+    my $processor = shift;
+    my $func = shift;
+    my @arr;
+    my $arf = $self->{POSTParseProcessors} if exists $self->{POSTParseProcessors};
+    @arr = @$arf if $arf;
+    $arr[@arr] =  [$processor, $func];
+    $self->{POSTParseProcessors} = \@arr;
+}
+
+sub runPostParseProcessors {
+    my $self = shift;
+    my $arr = $self->{POSTParseProcessors} if exists $self->{POSTParseProcessors};
+    foreach(@$arr){
+        my @objdts =@$_;
+        my $prc  = $objdts[0];
+        my $func = $objdts[1];
+        $prc -> $func($self);
+    }
+}
+
+#
 
 ###
 # Setup and pass to pluging CNF functionality.
@@ -984,20 +1093,22 @@ sub doPlugin {
     my $pck = $plugin->{package};
     my $prp = $plugin->{property};
     my $sub = $plugin->{subroutine};
-    if($pck && $prp && $sub){        
+    if($pck && $prp && $sub){
         ## no critic (RequireBarewordIncludes)
         require "$pck.pm";
-        my $obj;
-        my $settings = $properties{'%Settings'};#Properties are global.
+        #Properties are global, all plugins share a %Settings property if specifed, otherwise the default will be set from here only.
+        my $settings = $properties{'%Settings'};
         if($settings){
-           $obj = $pck->new(\%$settings);
-        }else{
-           $obj = $pck->new();
-        }        
+           foreach(keys %$settings){
+                #We allow for now, the plugin have settings set by its property, do not overwrite if exists as set.
+                $plugin->{$_} =  $settings->{$_} unless exists $plugin->{$_}
+           } ;
+        }
+        my $obj = $pck->new($plugin);
         my $res = $obj-> $sub($self, $prp);
-        if($res){            
-            $plugin->setPlugin($obj);
-            return $plugin;
+        if($res){
+           $plugin->setPlugin($obj);
+           return $plugin;
         }else{
             die "Sorry, the PLUGIN feature has not been Implemented Yet!"
         }
@@ -1016,13 +1127,13 @@ sub obtainLink {
     my $meths;
     ## no critic BuiltinFunctions::ProhibitStringyEval
     no strict 'refs';
-    if($link =~/(\w*)::\w+$/){        
+    if($link =~/(\w*)::\w+$/){
         use Module::Loaded qw(is_loaded);
         if(is_loaded($1)){
-           $ret = \&{+$link}($self);                                        
+           $ret = \&{+$link}($self);
         }else{
            eval require "$1.pm";
-           $ret = &{+$link};           
+           $ret = &{+$link};
            if(!$ret){
             $self->error( qq(Package  constance link -> $link is not available (try to place in main:: package with -> 'use $1;')));
             $ret = $link
@@ -1031,17 +1142,17 @@ sub obtainLink {
     }else{
         $ret = $self->anon($link);
         $ret = $self-> {$link} if !$ret;
-    }    
+    }
     return $ret;
 }
 
 ###
 # Writes out to a handle an CNF property or this parsers constance's as default property.
 # i.e. new CNFParser()->writeOut(*STDOUT);
-sub writeOut { my ($self, $handle, $property) = @_;      
+sub writeOut { my ($self, $handle, $property) = @_;
     my $buffer;
     if(!$property){
-        my @keys = sort keys %$self;        
+        my @keys = sort keys %$self;
         $buffer = "<<<CONST\n";
         my $with = 5;
         foreach (@keys){
@@ -1065,8 +1176,8 @@ sub writeOut { my ($self, $handle, $property) = @_;
             }
             elsif($val !~ /^\d+/){
                 $val = "\"$val\""
-            }        
-            $buffer .= ' 'x$spc. $key .  " = $val\n";     
+            }
+            $buffer .= ' 'x$spc. $key .  " = $val\n";
         }
         $buffer .= ">>";
         return $buffer if !$handle;
@@ -1078,7 +1189,7 @@ sub writeOut { my ($self, $handle, $property) = @_;
         $buffer = "<<@<$property>\n";
         if(ref $prp eq 'ARRAY') {
             my @arr = sort keys @$prp; my $n=0;
-            foreach (@arr){                
+            foreach (@arr){
                 $buffer .= "\"$_\"";
                 if($arr[-1] ne $_){
                    if($n++>5){
@@ -1087,12 +1198,12 @@ sub writeOut { my ($self, $handle, $property) = @_;
                     $buffer .= ","
                    }
                 }
-            }   
+            }
         }elsif(ref $prp eq 'HASH') {
             my %hsh = %$prp;
             my @keys = sort keys %hsh;
-            foreach my $key(@keys){                
-                $buffer .= $key . "\t= \"". $hsh{$key} ."\"\n";     
+            foreach my $key(@keys){
+                $buffer .= $key . "\t= \"". $hsh{$key} ."\"\n";
             }
         }
         $buffer .= ">>\n";
@@ -1104,12 +1215,12 @@ sub writeOut { my ($self, $handle, $property) = @_;
       $prp = $ANONS{$property};
       $prp = $self->{$property} if !$prp;
       if (!$prp){
-         $buffer = "<<ERROR<$property>Property not found!>>>\n" 
+         $buffer = "<<ERROR<$property>Property not found!>>>\n"
       }else{
         $buffer = "<<$property><$prp>>\n";
       }
       return $buffer if !$handle;
-      print $handle $buffer;      
+      print $handle $buffer;
       return 0;
     }
 }
@@ -1131,11 +1242,15 @@ sub log {
     my $self    = shift;
        my $message = shift;
     my $type    = shift; $type = "" if !$type;
+    my $isWarning = $type eq 'WARNG';
     my $attach  = join @_; $message .= $attach if $attach;
-    my %log = $self -> collection('%LOG');    
-    my $time = DateTime->from_epoch( epoch => time )->strftime('%Y-%m-%d %H:%M:%S.%3N');   
-    $message = "$type $message" if 'WARNG';
-    if($message =~ /^ERROR/ || $type eq 'WARNG'){
+    my %log = $self -> property('%LOG');
+    my $time = exists $self->{'TZ'} ? CNFDateTime -> new(TZ=>$self->{'TZ'}) -> toTimestamp() :
+                                      CNFDateTime -> new()-> toTimestamp();
+
+    $message = "$type $message" if $isWarning;
+
+    if($message =~ /^ERROR/ || $isWarning){
         warn  $time . " " .$message;
     }
     elsif(%log && $log{console}){
@@ -1145,37 +1260,38 @@ sub log {
         my $logfile  = $log{file};
         my $tail_cnt = $log{tail};
         if($log{tail} && $tail_cnt && int(`tail -n $tail_cnt $logfile | wc -l`)>$tail_cnt-1){
-            use File::ReadBackwards;
+use File::ReadBackwards;
             my $pos = do {
                my $fh = File::ReadBackwards->new($logfile) or die $!;
                $fh->readline() for 1..$tail_cnt;
                $fh->tell()
-            };            
+            };
             truncate($logfile, $pos) or die $!;
-            
+
         }
         open (my $fh, ">>", $logfile) or die ("$!");
         print $fh $time . " - " . $message ."\n";
         close $fh;
     }
+    return $time . " " .$message;
 }
 sub error {
     my $self    = shift;
-       my $message = shift;    
+       my $message = shift;
     $self->log("ERROR $message");
 }
 use Carp qw(cluck); #what the? I know...
 sub warn {
     my $self    = shift;
-       my $message = shift;    
+       my $message = shift;
     if($self->{ENABLE_WARNINGS}){
-       $self -> log($message,'WARNG');    
+       $self -> log($message,'WARNG');
     }
 }
 sub trace {
     my $self    = shift;
-       my $message = shift; 
-    my %log = $self -> collection('%LOG');
+       my $message = shift;
+    my %log = $self -> property('%LOG');
     if(%log){
         $self -> log($message)
     }else{
@@ -1183,6 +1299,8 @@ sub trace {
     }
 }
 
+sub now {return CNFDateTime->new(shift)}
+
 sub dumpENV{
     foreach (keys(%ENV)){print $_,"=", "\'".$ENV{$_}."\'", "\n"}
 }
@@ -1197,23 +1315,57 @@ sub  SQL {
 }
 our $JSON;
 sub  JSON {
-    my $self    = shift;
+    my $self = shift;
     if(!$JSON){
-        require CNFJSON; 
-        $JSON = CNFJSON-> new( {CNF_VERSION=>$self->{CNF_VERSION},
+        require CNFJSON;
+        $JSON = CNFJSON-> new(CNF_VERSION=>$self->{CNF_VERSION},
                                 CNF_CONTENT=>$self->{CNF_CONTENT},
-                                DO_ENABLED=>$self->{DO_ENABLED}
-                                } );
-    }    
+                                DO_ENABLED =>$self->{DO_ENABLED}
+                              });
+    }
     return $JSON;
 }
 
+###
+# CNFNodes are kept as anons by the TREE instruction, but these either could have been futher processed or
+# externaly assigned too as nodes to the parser.
+###
+our %NODES;
+sub addTree {
+    my ($self, $name, $node  )= @_;
+    if($name && $node){
+        $NODES{$name} = $node;
+    }
+}
+### Utility way to obtain CNFNodes from a configuration.
+sub getTree {
+    my ($self, $name) = @_;
+    return $NODES{$name} if exists $NODES{$name};
+    my $ret = $self->anon($name);
+    if(ref($ret) eq 'CNFNode'){
+        return \$ret;
+    }
+    return;
+}
 
 sub END {
 undef %ANONS;
 undef @files;
+undef %properties;
+undef %lists;
+undef %instructors;
 }
 1;
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/PerlCNF.git
+Documentation : Specifications_For_CNF_ReadMe.md
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
 
 __END__
 ## Instructions & Reserved words
@@ -1223,9 +1375,10 @@ __END__
    3. Current Reserved words list is.
        - CONST    - Concentrated list of constances, or individaly tagged name and its value.
        - VARIABLE - Concentrated list of anons, or individaly tagged name and its value.
-       - DATA     - CNF scripted delimited data property, having uniform table data rows.       
+       - DATA     - CNF scripted delimited data property, having uniform table data rows.
+       - DATE     - Translate PerlCNF date representation to DateTime object. Returns now() on empty property value.
        - FILE     - CNF scripted delimited data property is in a separate file.
-       - %LOG     - Log settings property, i.e. enabled=1, console=1.
+       - %LOG     - Log settings property, i.e. enabled=>1, console=>1.
        - TABLE    - SQL related.
        - TREE     - Property is a CNFNode tree containing multiple debth nested children nodes.
        - INCLUDE  - Include properties from another file to this repository.
@@ -1233,6 +1386,7 @@ __END__
        - INSTRUCT - Provides custom new anonymous instruction.
        - VIEW     - SQL related.
        - PLUGIN   - Provides property type extension for the PerlCNF repository.
+       - PROCESSOR- Registered processor to be called once all parsing is done and repository secured.
        - SQL      - SQL related.
        - MIGRATE  - SQL related.
        - MACRO
index 46fa171d35d8078e2c55ed980ea6b355e30302ed..7fdceab1a0ba8d968ff236e6b062914c4d0d71c8 100644 (file)
@@ -7,8 +7,7 @@ use Syntax::Keyword::Try;
 use Exception::Class ('HTMLIndexProcessorPluginException');
 use feature qw(signatures);
 use Scalar::Util qw(looks_like_number);
-use Date::Manip;
-
+use Clone qw(clone);
 use CGI;
 use CGI::Session '-ip_match';
 
@@ -16,49 +15,41 @@ use constant VERSION => '1.0';
 
 our $TAB = ' 'x4;
 
-sub new ($class, $fields={Language=>'English',DateFormat=>'US'}){      
-
-    if(ref($fields) eq 'REF'){
-       warn "Hash reference required as argument for fields!"
+sub new ($class, $plugin){
+    my $settings;
+    if($plugin){
+       $settings = clone $plugin; #clone otherwise will get hijacked with blessings.
     }
-    my $lang =   $fields->{'Language'};
-    my $frmt =   $fields->{'DateFormat'};
-    Date_Init("Language=$lang","DateFormat=$frmt");    
-   
-    return bless $fields, $class
+    return bless $settings, $class
 }
 
 ###
 # Process config data to contain expected fields and data.
 ###
-sub convert ($self, $parser, $property) {    
+sub convert ($self, $parser, $property) {
     my ($buffer,$title, $link, $body_attrs, $body_on_load, $give_me);
     my $cgi          = CGI -> new();
-    my $cgi_action   = $cgi-> param('action');    
-    my $cgi_doc      = $cgi-> param('doc'); 
+    my $cgi_action   = $cgi-> param('action');
+    my $cgi_doc      = $cgi-> param('doc');
     my $tree         = $parser-> anon($property);
-    die "Tree property '$property' is not available!" if(!$tree or ref($tree) ne 'CNFNode');    
-
+    die "Tree property '$property' is not available!" if(!$tree or ref($tree) ne 'CNFNode');
 try{
-
-
-    if (exists $parser->{'HTTP_HEADER'}){            
+    if (exists $parser->{'HTTP_HEADER'}){
         $buffer .= $parser-> {'HTTP_HEADER'};
-    }else{ 
+    }else{
         if(exists $parser -> collections()->{'%HTTP_HEADER'}){
             my %http_hdr = $parser -> collection('%HTTP_HEADER');
             $buffer = $cgi->header(%http_hdr);
         }
     }
-
-    if ($cgi_action and $cgi_action eq 'load'){        
-        $buffer .= $cgi->start_html(); my 
+    if ($cgi_action and $cgi_action eq 'load'){
+        $buffer .= $cgi->start_html(); my
         $load = loadDocument($parser, $cgi_doc);
         if($load){
-           $buffer .= $$load if $load;        
+           $buffer .= $$load if $load;
         }else{
            $buffer .= "Document is empty: $cgi_doc\n"
-        }        
+        }
     }else{
         $title  = $tree  -> {'Title'} if exists $tree->{'Title'};
         $link   = $tree  -> {'HEADER'};
@@ -69,13 +60,13 @@ try{
         if($link){
         if(ref($link) eq 'CNFNode'){
                 my $arr = $link->find('CSS/@@');
-                foreach (@$arr){                
-                    push  @hhshCSS, {-type => 'text/css', -src => $_->val()};                
+                foreach (@$arr){
+                    push  @hhshCSS, {-type => 'text/css', -src => $_->val()};
                 }
                 $arr = $link->find('JS/@@');
-                foreach (@$arr){                
-                    push  @hhshJS, {-type => 'text/javascript', -src => $_->val()};                
-                } 
+                foreach (@$arr){
+                    push  @hhshJS, {-type => 'text/javascript', -src => $_->val()};
+                }
                 $arr = $link  -> find('STYLE');
                 if(ref($arr) eq 'ARRAY'){
                     foreach (@$arr){
@@ -85,51 +76,51 @@ try{
                 }
                 $arr = $link  -> find('SCRIPT');
                 if(ref($arr) eq 'ARRAY'){
-                    foreach (@$arr){ 
+                    foreach (@$arr){
                         $give_me .= "\n<script>\n".$_ -> val()."\n</script>\n"
                     }}else{
                         $give_me .= "\n<script>\n".$arr -> val()."\n</script>\n"
                 }
-        }       
-        delete $tree -> {'HEADER'};       
-        }    
+        }
+        delete $tree -> {'HEADER'};
+        }
         $buffer .= $cgi->start_html(
                             -title   => $title,
                             -onload  => $body_on_load,
-                            # -BGCOLOR => $colBG,                        
+                            # -BGCOLOR => $colBG,
                             -style   => \@hhshCSS,
                             -script  => \@hhshJS,
                             -head=>$give_me,
                             $body_attrs
                         );
-        foreach my $node($tree->nodes()){      
+        foreach my $node($tree->nodes()){
         $buffer .= build($parser, $node)  if $node;
         }
         $buffer .= $cgi->end_html();
     }
     $parser->data()->{$property} = \$buffer;
  }catch($e){
-         HTMLIndexProcessorPluginException->throw(error=>$e ,show_trace=>1);
+         HTMLIndexProcessorPluginException->throw(error=>$e);
  }
 }
 #
 sub loadDocument($parser, $doc) {
     my $slurp = do {
-                    open my $fh, '<:encoding(UTF-8)', $doc or HTMLIndexProcessorPluginException->throw("Document not avaliable: $doc");
+                    open my $fh, '<:encoding(UTF-8)', $doc or HTMLIndexProcessorPluginException->throw(error=>"Document not avaliable -> \"$doc\" ", show_trace=>1);
                     local $/;
-                    <$fh>;       
+                    <$fh>;
     };
     if($doc =~/\.md$/){
-        require MarkdownPlugin;   
-        my @r = @{MarkdownPlugin->new()->parse($slurp)};
+        require MarkdownPlugin;
+        my @r = @{MarkdownPlugin->new(undef)->parse($slurp)};
         return $r[0];
     }
-    return \$slurp        
+    return \$slurp
 }
 
 ###
 # Builds the html version out of a CNFNode.
-# CNFNode with specific tags here are converted also here, 
+# CNFNode with specific tags here are converted also here,
 # those that are out of the scope for normal standard HTML tags.
 # i.e. HTML doesn't have row and cell tags. Neither has meta links syntax.
 ###
@@ -143,14 +134,14 @@ sub build {
         $bf .= "\t"x$tabs."<div".placeAttributes($node).">\n"."\t"x$tabs."<div>";
             foreach my $n($node->nodes()){
                 if($n->{'_'} ne '#'){
-                    my $b = build($parser, $n, $tabs+1);     
+                    my $b = build($parser, $n, $tabs+1);
                     $bf .= "$b\n" if $b;
                 }
             }
             if($node->{'#'}){
                 my $v = $node->val();
                 $v =~ s/\n\n+/\<\/br>\n/gs;
-                $bf .= "\t<div>\n\t<p>\n".$v."</p>\n\t</div>\n"; 
+                $bf .= "\t<div>\n\t<p>\n".$v."</p>\n\t</div>\n";
             }
         $bf .= "\t</div>\t</div>\n"
     }elsif( $name eq 'row' || $name eq 'cell' ){
@@ -161,13 +152,13 @@ sub build {
                     $bf .= "$b\n" if $b;
                 }
             }
-        $bf .= $node->val()."\n" if $node->{'#'};   
+        $bf .= $node->val()."\n" if $node->{'#'};
         $bf .= "\t"x$tabs."</div>"
     }elsif( $name eq 'img' ){
         $bf .= "\t\t<img".placeAttributes($node)."/>\n";
     }elsif($name eq 'list_images'){
         my $paths = $node->{'@@'};
-        foreach my $ndp (@$paths){            
+        foreach my $ndp (@$paths){
             my $path = $ndp -> val();
             my @ext = split(',',"jpg,jpeg,png,gif");
             my $exp = " ".$path."/*.". join (" ".$path."/*.", @ext);
@@ -180,22 +171,22 @@ sub build {
                 $bf .= qq(\t<div class='row'><div class='cell'>);
                 $bf .= qq(\t<a href="$enc"><img src="$enc" with='120' height='120'><br>$fn</a>\n</div></div>\n);
             }
-        }    
+        }
     }elsif($name eq '!'){
       return "<!--".$node->val()."-->\n";
-        
+
     }elsif($node->{'*'}){ #Links are already captured, in future this might be needed as a relink from here for dynamic stuff?
             my $lval = $node->{'*'};
-            if($name eq 'file_list_html'){ #Special case where html links are provided.                
+            if($name eq 'file_list_html'){ #Special case where html links are provided.
                 foreach(split(/\n/,$lval)){
                      $bf .= qq( [ $_ ] |) if $_
                 }
                 $bf =~ s/\|$//g;
             }else{ #Generic included link value.
                 #is there property data for it?
-                my $prop = $parser->data()->{$node->name()};                
+                my $prop = $parser->data()->{$node->name()};
                 #if not has it been passed as an page constance?
-                $prop = $parser -> {$node->name()} if !$prop; 
+                $prop = $parser -> {$node->name()} if !$prop;
                 if ( !$prop ) {
                     if   ( $parser->{STRICT} ) { die "Not found as property link -> " . $node->name()}
                     else                       { warn "Not found as property link -> " . $node->name()}
@@ -214,20 +205,20 @@ sub build {
     }
     else{
         my $spaced = 1;
-           $bf .= "\t"x$tabs."<".$node->name().placeAttributes($node).">";            
-            foreach my $n($node->nodes()){                
-                    my $b = build($parser, $n,$tabs+1);                    
-                        if ($b){                            
+           $bf .= "\t"x$tabs."<".$node->name().placeAttributes($node).">";
+            foreach my $n($node->nodes()){
+                    my $b = build($parser, $n,$tabs+1);
+                        if ($b){
                             if($b =~/\n/){
                                $bf =~ s/\n$//gs;
                                $bf .= "\n$b\n"
                             }else{
-                               $spaced=0;                               
-                               $bf .= $b; 
+                               $spaced=0;
+                               $bf .= $b;
                             }
-                       }                    
+                       }
             }
-        
+
         if ($node->{'#'}){
             $bf .= $node->val();
             $bf .= "</".$node->name().">";
@@ -252,7 +243,7 @@ sub placeAttributes {
         if(@$_[0] ne '#' && @$_[0] ne '_'){
            if(@$_[1]){
               $ret .= " ".@$_[0]."=\"".@$_[1]."\"";
-           }else{ 
+           }else{
               $ret .= " ".@$_[0]." ";
            }
         }
@@ -265,4 +256,15 @@ sub isParagraphName {
     return $name eq 'p' || $name eq 'paragraph' ? 1 : 0
 }
 
-1;
\ No newline at end of file
+1;
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/PerlCNF.git
+Documentation : Specifications_For_CNF_ReadMe.md
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
index 32c2c59c5087801072f7cb6b4a9f83c0479c31a4..d1408b04f98e61b6668d751014647dd7e7b67733 100644 (file)
@@ -18,7 +18,7 @@ no warnings qw(experimental::signatures);
 use Syntax::Keyword::Try;
 use Exception::Class ('MarkdownPluginException');
 use feature qw(signatures);
-use Date::Manip;
+use Clone qw(clone);
 ##no critic ControlStructures::ProhibitMutatingListFunctions
 
 use constant VERSION => '1.0';
@@ -37,17 +37,13 @@ use constant {
 };
 
 
-sub new ($class, $fields={Language=>'English',DateFormat=>'US'}){
-
-    if(ref($fields) eq 'REF'){
-       warn "Hash reference required as argument for fields!"
+sub new ($class, $plugin){
+    my $settings;
+    if($plugin){
+       $settings = clone $plugin; #clone otherwise will get hijacked with blessings.
     }
-    my $lang =   $fields->{'Language'};
-    my $frmt =   $fields->{'DateFormat'};
-    Date_Init("Language=$lang","DateFormat=$frmt");
-    $fields->{'disk_load'} = 0 if not exists $fields->{'disk_load'};
-
-    return bless $fields, $class
+    $settings->{'disk_load'} = 0 if not exists $settings->{'disk_load'};
+    return bless $settings, $class
 }
 
 ###
@@ -175,7 +171,7 @@ try{
             my $pret = ""; $pret = $1 if $1;
             my $post = ""; $post = $4 if $4;
             $tag = 'code'; $tag =$2 if $2;
-            my $inline = $3; 
+            my $inline = $3;
             $inline = inlineCNF($inline,"");
             my @code_tag = @{ setCodeTag($tag, "") };
             $ln = qq($pret<$code_tag[1] class='$code_tag[0]'>$inline</$code_tag[1]>$post\n);
@@ -403,9 +399,9 @@ try{
                 if($bqte){
                     while($bqte_nested-->0){$bqte .="</$bqte_tag></blockquote>\n"}
                     $para   .= $bqte; $bqte_nested=0;
-                    undef $bqte; 
+                    undef $bqte;
                 }
-                $para .= qq( ${style($v)} \n)
+                $para .= ${style($2)}."\n"
             }
         }else{
             if($list_root && ++$list_end>1){
@@ -493,7 +489,7 @@ sub inlineCNF($v,$spc){
         my $r = "<span ".C_PV.">[#[</span>";
            $v =~ s/\[\#\[/$r/g;
         if($v =~ m/\]\#\]/){
-           $r = "<span ".C_PV.">]#]</span>"; 
+           $r = "<span ".C_PV.">]#]</span>";
            $v =~ s/\]\#\]/$r/g;
         }
         return "$spc$v"
@@ -524,7 +520,7 @@ sub inlineCNF($v,$spc){
         $v=~/(.*)(>+\s*)$/;
         if(!$1 && $2){
             $v = $2; $v =~ s/>/&#62;/g;
-            return "$spc<span ".C_B.">$v</span>"           
+            return "$spc<span ".C_B.">$v</span>"
         }else{
             $oo =~ s/</&#60;/g;
             if($v=~m/>>>/){
@@ -782,4 +778,11 @@ div .cnf {
 
 
 
-1;
\ No newline at end of file
+1;
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
diff --git a/htdocs/cgi-bin/system/modules/RSSFeedsPlugin.pm b/htdocs/cgi-bin/system/modules/RSSFeedsPlugin.pm
new file mode 100644 (file)
index 0000000..76866b8
--- /dev/null
@@ -0,0 +1,349 @@
+package RSSFeedsPlugin;
+
+use strict;
+use warnings;
+
+use feature qw(signatures);
+use Scalar::Util qw(looks_like_number);
+use Syntax::Keyword::Try;
+use Clone qw(clone);
+use Capture::Tiny 'capture_stdout';
+use FileHandle;
+use XML::RSS::Parser;
+use Date::Manip::Date;
+use LWP::Protocol::https; #<-- 20230829  This  module some times, will not be auto installed for some reason.
+use LWP::Simple;
+
+use Benchmark;
+
+use constant VERSION => '1.0';
+
+# require CNFNode;
+# require CNFDateTime;
+
+CNFParser::import();
+
+sub new ($class, $plugin){
+    my $settings;
+    if($plugin){
+       $settings = clone $plugin; #clone otherwise will get hijacked with blessings.
+    }
+    return bless $settings, $class
+}
+
+###
+# Process config data to contain expected fields.
+###
+sub process ($self, $parser, $property) {
+    my @data = @{$parser->data()->{$property}};
+    $self->{date} = now();
+    for my $idx (0 .. $#data){
+        my @col = @{$data[$idx]};
+        if($idx>0){
+            $col[0] = $idx+1;
+            $col[4] = $self-> {date} -> toTimestamp();
+        }else{
+            $col[4] = 'last_updated';
+        }
+        $data[$idx]=\@col;
+    }
+    $parser->addPostParseProcessor($self,'collectFeeds');
+    $parser->data()->{$property} =\@data;
+}
+
+sub collectFeeds($self,$parser) {
+  my $property = $self->{property};
+  my %hdr;
+  my @data = @{$parser->data()->{$property}};
+  my $page;
+  for my $idx (0 .. $#data){
+      my @col = @{$data[$idx]};
+      if($idx==0){
+        for my $i(0..$#col){ # Get the matching table column index names as scripted.
+         $hdr{$col[$i]}=$i
+        }
+      }else{
+         my $name = $col[$hdr{Name}]; # Now use the column names as coded, if names in script are changed, you must change.
+         my $tree =  fetchFeed($self, $name, $col[$hdr{URL}], $col[$hdr{Description}]);
+         if($tree && ref($$tree) eq 'CNFNode'){
+            if(not isCNFTrue($self->{CNF_TREE_LOADED}) && isCNFTrue($self->{CNF_TREE_STORE})){
+               my $output_local = getOutputDir($self);
+               my $fname = $name; $fname =~ s/[\s|\W]/_/g; $fname = ">$output_local"."tree_feed_$fname.cnf";
+               my $FH = FileHandle->new($fname);
+               my $root = $$tree;
+               print $FH $root->toScript();
+               close $FH;
+               $parser->addTree($name, $tree);
+            }
+            if(isCNFTrue($self->{CONVERT_CNF_HTML})){
+               $page .= _treeToHTML($tree);
+            }
+         }else{
+            $parser-> warn("Feed '$name' bailed to return a CNFNode tree.")
+         }
+      }
+  }
+  $parser->data()->{PAGE} = \$page if $page;
+}
+### PerlCNF TREE to HTML Conversion Routine, XML based RSS of various Internet feeds convert to PerlCNF previously.
+sub _treeToHTML($tree){
+    my $root = $$tree;
+    my $feed = $root->node('Feed');
+    my $brew = $root->node('Brew');
+    my ($Title, $Published,$URL,$Description) = $feed -> array('Title','Published','URL','#');
+    my $bf = qq(
+        <div class="feed">
+        <div class="feeds_hdr">
+            <div class="feed_title"><h2>$Title</hd></div>
+            <div class-"feed_lbl"><div class="feed_hdr_lbl">Published:</div>$Published</span></div>
+            <div class-"feed_hdr"><div class="feed_hdr_lbl"><span style="text-align:right;width:inherit;">URL:&nbsp;</span></div>)._htmlURL($URL).qq(</div>
+            <div class-"feed_hdr"><p>$Description</p></div>
+        </div>
+        </div>
+    );
+    my $alt = 0;
+    foreach
+        my $item(@{$brew->items()}){
+        next if $item->name() ne 'Item';
+        my ($Title,$Link,$Date) = $item -> array('Title','Link','Date');
+        my $Description         = $item -> node('Description') -> val();
+        $bf.= qq(
+            <div class="feed">
+            <div class="feeds_item_$alt">
+                <div class="feed_title"><div class="feed_lbl">Title:</div>$Title</div>
+                <div class="feed_link"><div class="feed_lbl">Link:</div>)._htmlURL($Link).qq(</div>
+                <div class="feed_Date"><div class="feed_lbl">Date:</div>$Date<div><hr></div></div>
+                <div class="feed_desc"><span>$Description<span></div>
+            </div>
+            </div>
+        );
+        $alt = $alt?0:1;
+    }
+    return $bf . '<hr class="feeds">'
+}
+
+sub _htmlURL {
+    my $link = shift;
+    return qq(<a class="feed_link" href="$link" target="feed">$link</a>)
+}
+
+sub getOutputDir($self){
+    my  $output_local = $self->{OUTPUT_DIR};
+    if ($output_local){
+        $output_local.= '/';
+        mkdir $output_local unless -d $output_local;
+    }
+    return $output_local
+}
+
+sub fetchFeed($self,$name,$url,$description){
+
+    my ($MD, $tree, $brew,$bench);
+    my $console     = isCNFTrue($self->{OUTPUT_TO_CONSOLE});
+    my $convert     = isCNFTrue($self->{CONVERT_TO_CNF_NODES}); #<--_ If true,
+    my $stored      = isCNFTrue($self->{CNF_TREE_STORE});       #<-\_ Will use a fast stashed local CNF tree instead of the XML::RSS::Parser.
+    my $markup      = isCNFTrue($self->{OUTPUT_TO_MD});
+    my $benchmark   = isCNFTrue($self->{BENCHMARK});
+    my $output_local= getOutputDir($self);
+    my $fname = $name; $fname =~ s/[\s|\W]/_/g;
+
+    $fname = $output_local."rss_$fname.rdf";
+
+    if(isCNFTrue($self->{RUN_FEEDS})){
+        if(-e $fname) {
+            my $now   = new Date::Manip::Date -> new_date(); $now->parse("today");
+            my $fdate = new Date::Manip::Date;
+            my $fsepoch = (stat($fname))[9]; $fdate->parse("epoch $fsepoch"); $fdate->parse("3 business days");
+            my $delta = $fdate->calc($now);
+            $self->{CNF_TREE_LOADED} = 0;
+            if($now->cmp($fdate)>0){
+                unlink $fname;
+            }else{
+                my $cnf_fname = $name; $cnf_fname =~ s/[\s|\W]/_/g;
+                $cnf_fname =  $output_local."tree_feed_$cnf_fname.cnf";
+                if($convert && $stored && -e $cnf_fname){
+                   $self->{CNF_TREE_LOADED} = 1 if $_ = CNFParser -> new($cnf_fname,{DO_ENABLED => 1}) -> getTree('CNF_FEED');
+                   return $_;
+                }
+            }
+        }
+        unless ( -e $fname ) {
+            try{
+                print "Fetching: $fname -> $url ...";
+                my  $res = getstore($url, $fname);
+                if ($res == 200){
+                    print "\e[2Adone!\n"
+                }else{
+                    print "\e[2AError<$res>!\n"
+                }
+            }catch{
+                print "Error: $@.\n";
+                return;
+            }
+        }
+    }
+
+    my $parser = XML::RSS::Parser->new;
+    my $fh = FileHandle->new($fname);
+    my $t0 = Benchmark->new;
+    my $feed = $parser->parse_file($fh);
+    my $t1 = Benchmark->new;
+    my $td = timediff($t1, $t0);
+    $bench = "The XML parser for $fname took:\t".timestr($td)."\n" if $benchmark;
+
+    print "Parsing: $fname\n";
+
+    if(!$feed){
+        print "Failed to parse RSS feed:$name file:$fname\n";
+        return
+    }
+
+my  $buffer = capture_stdout {
+        my $Title = $feed->query('/channel/title')->text_content;
+        if($console){
+            print 'x'x60,"\n";
+            print $Title, " [ Items: ",$feed->item_count, " ]\n";
+            print 'x'x60,"\n\n";
+        }else{
+            if($markup){
+            $fname = ">$output_local"."rss_$name.md";
+            $MD = FileHandle->new($fname);
+            #binmode($MD, ":encoding(UTF-8)");
+            print $MD "# ", $Title, "\n";
+            print $MD "\n   $description\n\n";
+            print $MD "* Feed: [$name]($url)\n";
+            print $MD "* Items: ",$feed->item_count, "\n";
+            print $MD "* Date: ", $self->{date} -> toSchlong(), "\n\n";
+            }
+        }
+        if($convert){
+                    my $published = CNFDateTime->new()->toTimestamp();
+                    my $expires   = new Date::Manip::Date -> new_date(); $expires->parse("7 business days");
+                       $expires   =  $expires->printf(CNFDateTime::FORMAT());
+                    my $fnm = $name; $fnm =~ s/[\s|\W]/_/g;
+                    my $Title = $feed->query('/channel/title')->text_content;
+                    my $feed = CNFNode -> new({'_'=>'Feed',Title => $Title, Published=>$published, Expires=>$expires,
+                                                           File  => $output_local."tree_feed_$fnm.cnf", '#'=>$description,
+                                                           URL=>$url});
+                    $tree =    CNFNode -> new({'_'=>'CNF_FEED',Version=>'1.0', Release=>'1'});
+                    $brew =    CNFNode -> new({'_'=>'Brew'});
+                    $tree -> add($feed)->add($brew);
+        }
+        $t0 = Benchmark->new;
+        my $items_cnt =0;
+        foreach my $item
+                    (   $feed->query('//item') ) {
+            my $title = $item->query('title')->text_content;
+            my $date  = $item->query('pubDate');
+            my $desc  = $item->query('description')->text_content;
+            my $link  = $item->query('link')->text_content;
+            my $CNFItm; $items_cnt++;
+            if(!$date) {
+                $date  = $item->query('dc:date');
+            }
+            $date = $date->text_content;
+            $date = CNFDateTime::_toCNFDate($date, $self->{TZ})->toTimestampShort();
+            if($console){
+                    print "Title : $title\n";
+                    print "Link  : $link\n";
+                    print "Date  : $date\n";
+            }else{
+                    if($markup){
+                    print $MD "\n## $title\n\n";
+                    print $MD "* Link : <$link>\n";
+                    print $MD "* Date : $date\n\n";
+                    }
+            }
+            if($convert){
+                $CNFItm =  CNFNode -> new({
+                                            '_'     => 'Item',
+                                            Title   => $title,
+                                            Link    => $link,
+                                            Date    => $date
+                                });
+                $brew->add($CNFItm);
+            }
+            if (length($desc)>0){
+                if($console){
+                            print '-'x20,"\n";
+                            print $desc;
+                            print "\n" if $desc !~ /\s$/
+                }else{
+                            print $MD "   $desc\n" if $markup;
+                }
+                if($convert){
+                $CNFItm->add(CNFNode -> new({'_'=>"Description",'#'=>\$desc}));
+                }
+            }
+            if($console){ print '-'x40,"\n";
+            }else{
+                print $MD "\n---\n" if $markup
+            }
+        }
+        $t1 = Benchmark->new;
+        $td = timediff($t1, $t0);
+        #TODO: XML query method is very slow, we will have to resort and test the CNFParser->find in comparance.
+        #      Use XML RSS only to fetch, from foreing servers the feeds, and translate to CNFNodes.
+        $bench .= "The XML QUERY(//Item)  for $fname items($items_cnt) took:\t".timestr($td)."\n" if $benchmark;
+
+        if($console){
+            print 'X'x20, " ", $feed->query('/channel/title')->text_content." Feed End ",'X'x20,"\n\n";
+        }else{
+             $MD->close() if $markup
+        }
+    };
+    print $buffer if $console;
+    print $bench if $benchmark;
+    return \$tree if $convert;
+    return \$buffer;
+}
+
+1; # <-- The score I get for using multipe functionality returns, I know. But if can swat 7 flies in one go, why not do it?
+
+=begin scrap
+
+# Remote PerlCNF Feed Format, opposed to RSS XML, would look like this:
+<<CNF_FEED<TREE>
+    Version  = 1.0
+    Release  = 1
+    <Feed<
+        Published: 2023-12-15
+        Expires: 2023-12-30
+        URL: https://lalaland.com/feeds.cgi
+    >Feed>
+    [brew[
+        [item[
+            Title:
+            Date:
+            Link:
+            [Description[
+            [#[
+
+            ]#]
+            ]Description]
+        ]item]
+        [item[
+            Title:
+            Date:
+            Link:
+            [Description[
+            [#[
+
+            ]#]
+            ]Description]
+        ]item]
+    ]brew]
+>>
+
+=cut scrap
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/PerlCNF.git
+Documentation : Specifications_For_CNF_ReadMe.md
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
diff --git a/htdocs/cgi-bin/wsrc/feeds.css b/htdocs/cgi-bin/wsrc/feeds.css
new file mode 100644 (file)
index 0000000..51115df
--- /dev/null
@@ -0,0 +1,62 @@
+.feeds_hdr {
+    text-align: left;
+    vertical-align: auto;
+}
+
+.feed {
+    text-align:left;
+    width: 100%;
+    border: 1px solid black;
+    margin-bottom: 5px;
+    padding: 8px;
+}
+.feed div {
+    text-align:left;
+    border:0;
+
+}
+
+.feed_lbl  {
+    font-weight: bolder;
+    font-style: normal;
+    width: 48px;
+    display: inline-flex;
+
+}
+.feed_hdr_lbl {
+    font-weight: bolder;
+    font-style: oblique;
+    width: 88px;
+    display: inline-flex;
+
+}
+.feeds hr{
+    color: black;
+    height: 1px;
+}
+
+
+
+.feed_hdr_r1 {
+    background-color: #e6ffff;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+
+.feed_hdr_r2 {
+    background-color: #ccffff;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+
+
+.feed_hdr_r3 {
+    background-color: #ccfff0;
+    border: 1px solid black;
+    border-right: 1px solid black;
+    vertical-align: top;
+}
+
+
index 06e07d5450388a82ff7fccdcdea88c75171f084a..c7c41d6004cf3ab1aa56bfb0f8236a2c7015d573 100644 (file)
@@ -19,7 +19,7 @@ var RTF_DOC_CUR_ID;
 var TXT_LOG_ROWS = 3;
 var TIME_STAMP;
 var LOCALE;
-var TIMEZONE; 
+var TIMEZONE;
 var DBI_LVAR_SZ;
 var EDIT_LOG_TXT = "";
 
@@ -36,12 +36,12 @@ function onBodyLoadGeneric() {
 function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
 
     LOCALE      = locale;
-    TIMEZONE    = tz;    
+    TIMEZONE    = tz;
     TIME_STAMP  = new Date(today);
-    DBI_LVAR_SZ = parseInt(log_limit);    
-    
+    DBI_LVAR_SZ = parseInt(log_limit);
+
     onBodyLoadGeneric();
-    
+
 
     if (toggle) {
         this.toggle("#div_srh", false);
@@ -117,7 +117,7 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
             let str = $('#el').val();
             let m; var tot = 0;
 
-            while ((m = regex.exec(str)) !== null) {                
+            while ((m = regex.exec(str)) !== null) {
                 if (m.index === regex.lastIndex) {
                     regex.lastIndex++;
                 }
@@ -143,7 +143,7 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
                 });
             }
             if(tot==0){tot=""}
-            $('#am').val(tot.toFixed(2));            
+            $('#am').val(tot.toFixed(2));
         }
 
 
@@ -158,16 +158,16 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
         alignY: 'bottom',
         offsetX: 5,
         showTimeout: 100
-    });   
+    });
 
     $("#menu_close").poshytip({
         content: "<b>Do not click on this</b> little heart of mine,<br> <b>the menu will be closed</b>!",
         className: 'tip-yellowsimple',
-        showOn: 'mouseover',        
+        showOn: 'mouseover',
         alignTo: 'target',
         alignX: 'center',
-        alignY: 'bottom',    
-        showTimeout: 100    
+        alignY: 'bottom',
+        showTimeout: 100
     });
 
     $("#menu_close").click(function() {
@@ -182,40 +182,40 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
     $("#dutch_left").poshytip({
         content: "<span class='ui-icon ui-icon-arrowthick-1-w' style='float:none;'></span>Pass the dutchie to the <b>left</b> on side.",
         className: 'tip-yellowsimple',
-        showOn: 'mouseover',        
+        showOn: 'mouseover',
         alignTo: 'target',
         alignX: 'center',
-        alignY: 'bottom',    
-        showTimeout: 100    
+        alignY: 'bottom',
+        showTimeout: 100
     });
     $("#dutch_right").poshytip({
         content: "Pass the dutchie to the <b>right</b> on side.<span class='ui-icon ui-icon-arrowthick-1-e' style='float:none;'></span>",
         className: 'tip-yellowsimple',
-        showOn: 'mouseover',        
+        showOn: 'mouseover',
         alignTo: 'target',
         alignX: 'center',
-        alignY: 'bottom',    
-        showTimeout: 100    
+        alignY: 'bottom',
+        showTimeout: 100
     });
 
 
     $('#ec').show();
 
     $("#RTF").prop("checked", false);
-    
-    if ($('#editor-container').length) {        
+
+    if ($('#editor-container').length) {
         QUILL = new Quill('#editor-container', {
             placeholder: 'Enter your Document here...',
             theme: 'snow',
             modules: {
                 formula: true,
-                syntax: true,                
-                toolbar: '#toolbar-container'            
+                syntax: true,
+                toolbar: '#toolbar-container'
             }
         });
         Delta = Quill.import('delta');
         CHANGE = new Delta();
-        
+
         // toggleDocument();
     }
 
@@ -226,7 +226,7 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
         // let bg=RGBToHex('rgb(180, 169, 169)'); //<-is set in css file
         // $('#toolbar-container').css('background-color',bg);
         $('#toolbar-container').css('color',DEF_BACKGROUND);
-        
+
     }
     var amf = $( "#amf" );//Amount Field Type dropdown
     var ec = $( "#ec" );  //Category dropdown
@@ -244,7 +244,7 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
                 }
             }
         }});
-    
+
 
     jQuery.fn.dispPos = function () {
         this.css("position","absolute");
@@ -303,8 +303,8 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
         lbl = lbl.replace(/\s*$/g, "");
         lbl = lbl + "&nbsp;".repeat(16-lbl.length);
         $("#lcat_v").html(lbl);
-        $("#vc").val(ci);        
-        $("#cat_desc").show();        
+        $("#vc").val(ci);
+        $("#cat_desc").show();
     }).mouseenter(function(e){
         var pr = $(event.target).parent(); pr = pr.attr('id');
         if(pr){
@@ -334,7 +334,7 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
         }
     }).mouseleave(function(e){$("#cat_desc").hide();});
 
-    
+
     $( "#dlgValidation" ).dialog({
         dialogClass: "alert",
         buttons: [
@@ -371,19 +371,19 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
     setPageSessionTimer(expires);
 
 
-    $(function() {        
+    $(function() {
         $( "#rs_keys, #rs_keys2" ).autocomplete({
             source: AUTOWORDS
             });
     });
     var CHK_PREV;
-    
+
     $("#frm_log td").mouseover(function(e){
         if(e.target != 'thr'){
             var chk = $(e.target).find('input[name="chk"]');
             var tr = e.target.parentNode.closest("tr");
             if(tr.id != "summary_row" && tr.id !="brw_row"){
-                if(CHK_PREV && !CHK_PREV.prop('checked')){        
+                if(CHK_PREV && !CHK_PREV.prop('checked')){
                     CHK_PREV.closest("tr").removeClass("hover");
                 }
                 $(e.target.parentNode).closest("tr").addClass("hover");
@@ -397,21 +397,21 @@ function onBodyLoad(toggle, locale, tz, today, expires, rs_cur, log_limit) {
             $(e.target.parentNode).closest("tr").removeClass("hover");
         }
     });
-    
+
 
     if($("#isInViewMode").val()>0){
-        this.toggle('#div_srh', true); 
+        this.toggle('#div_srh', true);
         this.toggle('#div_log', true);
     }
 
-    $(function() {        
+    $(function() {
         $( "#rs_keys, #rs_keys2" ).autocomplete({
             source: AUTOWORDS
             });
     });
 
-    display("Log page is ready!", 5);    
-    
+    display("Log page is ready!", 5);
+
 }
 
 function encodeText(){
@@ -446,7 +446,7 @@ return true;
 }
 
 function backToMain() {// func. required as chrome submits whole form on if buttons are not falsed.
-    $("[name='confirmed']").val(0);    
+    $("[name='confirmed']").val(0);
     //window.location.href='main.cgi';
     history.back();
     return false;
@@ -478,7 +478,7 @@ function validate(dt, log) {
     if(msg){
         return dialogModal( "Sorry Form Validation Failed", msg);
     }
-    $('#frm_entry').hide();    
+    $('#frm_entry').hide();
     encodeText();
     return true;
 }
@@ -526,7 +526,7 @@ function setNow() {
 
     date.value =  year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + seconds;
     $("#submit_is_edit").val("0");
-    toggleDoc(true);    
+    toggleDoc(true);
     EDIT_LOG_TXT = "";
     return false;
 }
@@ -546,11 +546,11 @@ function decodeToHTMLText(txt) {
     txt = txt.replace("/&#62;/g", ">");
     txt = txt.replace("/&#9;/g", "\t");
     txt = txt.replace(/br\s*[\/]?>/gi, "\n");
-    txt = txt.replace(/\\n/g, "\n");    
-    txt = txt.replace(/&#10;/g, "\n");    
+    txt = txt.replace(/\\n/g, "\n");
+    txt = txt.replace(/&#10;/g, "\n");
     txt = txt.replace(/&#34;/g, "\"");
     txt = txt.replace(/&#39;/g, "'");
-    
+
 
     return txt;
 }
@@ -563,10 +563,10 @@ function decodeToText(txt) {
     txt = txt.replace(/<hr>.*RTF<\/button>/gm, "");
     txt = txt.replace(/<br\s*[\/]?>/gi, "\n");
     //If first line bolded
-    
+
     let res = txt.matchAll(REGX_BTM);
     if(res){
-       txt = txt.replace(REGX_BTM, REGX_BTM_SUBST);        
+       txt = txt.replace(REGX_BTM, REGX_BTM_SUBST);
     }
     return txt;
 }
@@ -713,23 +713,23 @@ function viewAll() {
     frm.rs_prev.value = 0;
     frm.submit_is_view.value = 1;
     frm.submit();
-    
+
     return false;
 }
 
 function resizeLogText() {
 
-    $("#div_log .collpsd").show(); 
+    $("#div_log .collpsd").show();
     $('#div_log').show();
-     
+
     if(TXT_LOG_ROWS == 3){
-        TXT_LOG_ROWS = 10;        
+        TXT_LOG_ROWS = 10;
     }
     else{
         TXT_LOG_ROWS = 3;
     }
     $("#el").prop("rows",TXT_LOG_ROWS)
-    
+
 }
 
 function resizeDoc() {
@@ -839,7 +839,7 @@ function toggle(id, mtoggle) {
             var distance = $(id).offset().top;
             if($(this).scrollTop() <= distance){
                 $(id).toggle();
-            }        
+            }
         }
     }
     else{
@@ -853,9 +853,9 @@ function toggle(id, mtoggle) {
 function showAll() {
 
    show("#menu_page");
-   
+
    if(_show_all){
-        $("#lnk_show_all").text("Hide All");   
+        $("#lnk_show_all").text("Hide All");
         show("#div_log");
         show("#div_srh");
         show("#tbl_hlp");
@@ -874,7 +874,7 @@ function showAll() {
 
 
    $("html, body").animate({ scrollTop: 0 }, "fast");
-   
+
     return false;
 }
 
@@ -1054,14 +1054,14 @@ function viewByDate(btn) {
 
 function submitNewCategory() {
 
-    var frm = $("#frm_config");    
+    var frm = $("#frm_config");
     $("#frm_config [name='cchg']").val(2);
     return true;
 }
 
-function deleteSelected() { 
+function deleteSelected() {
     display("Please Wait!",150);
-    $("#opr").val(0);  
+    $("#opr").val(0);
     $("#del_sel").click();
     display("Please Wait!",150);
     return false;
@@ -1075,7 +1075,7 @@ function exportSelected() {
     display("Please Wait!");
     $("#opr").val(2);
     return true;
-    
+
 }
 function viewSelected() {
     display("Please Wait!");
@@ -1101,7 +1101,7 @@ function sumSelected() {
             let id = chks[i].value;
             let am = $("#a"+id).text();
             let ct = $("#c"+id).text();
-            let at = $("#f"+id).val();           
+            let at = $("#f"+id).val();
 
             am = am.replace(/\,/g,"");//rem formatting
             if(ct=='Expense' || at=='2'){
@@ -1147,11 +1147,11 @@ function saveRTF(id, action) {
 
     var is_submit = (id==-1);
     if (id < 1) {
-        id = $("#submit_is_edit").val();        
+        id = $("#submit_is_edit").val();
     }
 
 
-    
+
     if(is_submit && (EDIT_LOG_TXT && $('#el').val() !== EDIT_LOG_TXT)){
 
         if(formValidation()){ //If it is false, failed. That needs to altered by the user first.
@@ -1169,18 +1169,18 @@ function saveRTF(id, action) {
                 },
                 buttons: [
 
-                    { text: "Yes",                    
+                    { text: "Yes",
                       icons: { primary: "ui-icon-circle-check" },
-                        click: function() {                            
+                        click: function() {
                             $( this ).dialog( "close" );
                             $("#frm_entry").submit();
                         }
                     },
-        
+
                     { text: "No",
                         click: function() {
                             decodeText();
-                            $( this ).dialog( "close" );                        
+                            $( this ).dialog( "close" );
                             return false;
                         }
                     }
@@ -1191,7 +1191,7 @@ function saveRTF(id, action) {
         return false;
     }
     else
-    if(is_submit && !$("#RTF").prop('checked')){           
+    if(is_submit && !$("#RTF").prop('checked')){
        return true;//we submit normal log entry
     }
     RTF_SUBMIT = true;
@@ -1202,7 +1202,7 @@ function saveRTF(id, action) {
                      dialogModal("Service Error: "+response.status,response.responseText);
                 }
                                                  );
-    if(is_submit){        
+    if(is_submit){
         $("#idx_cat").value = "SAVING DOCUMENT...";
         $("#idx_cat").show();
         //we must wait before submitting actual form!
@@ -1211,17 +1211,17 @@ function saveRTF(id, action) {
     return false;
 }
 
-function saveRTFResult(result) {    
+function saveRTFResult(result) {
     //console.log("Result->" + result);
-    var json = JSON.parse(result);    
+    var json = JSON.parse(result);
     $("html, body").animate({ scrollTop: 0 }, "fast");
-    
+
     let msg = json.response;
     if(json.log_id==0){
-        console.log(msg = "Saved to Buffer");  
+        console.log(msg = "Saved to Buffer");
 
     }else{
-        console.log(msg = "Saved document by lid -> "+json.log_id);        
+        console.log(msg = "Saved document by lid -> "+json.log_id);
     }
     display(msg, 5);
 
@@ -1280,7 +1280,7 @@ function loadRTF(under, id){
        id = RTF_DOC_CUR_ID; // btn_load_rtf clicked
     }
 
-    QUILL.setText('Loading Document...\n');    
+    QUILL.setText('Loading Document...\n');
     $.post('json.cgi', {action:'load', id:id}, loadRTFResult).fail(
            function(response) {dialogModal("Service Error: "+response.status,response.responseText);}
     );
@@ -1316,7 +1316,7 @@ function loadRTFResult(content, result, prms, quill) {
         $("#q-scroll"+id).attr('class',cls);
         if(css){
             css.backgroundColor = DEF_BACKGROUND; //Removing colours makes it inherit from parent these properties.
-            css.foregroundColor = "";//json.content.fg;            
+            css.foregroundColor = "";//json.content.fg;
         }
     }
 
@@ -1326,12 +1326,12 @@ function loadRTFResult(content, result, prms, quill) {
     }
     if(json.log_id==0){
         console.log(msg = "Loaded in Buffer");
-        $('#btn_zero_doc').show();                
+        $('#btn_zero_doc').show();
     }else{
         console.log(msg = "Loaded in document by lid -> "+json.log_id);
         $('#btn_load_doc').show();
     }
-    display(msg, 3);    
+    display(msg, 3);
 }
 
 
@@ -1368,8 +1368,8 @@ function RGBToHex(rgb) {
     return "#" + r + g + b;
 }
 
-function fetchBackup() {    
-    window.location = "config.cgi?bck=1";    
+function fetchBackup() {
+    window.location = "config.cgi?bck=1";
     setTimeout("location.reload(true);", 5000);
 }
 function deleteBackup() {
@@ -1408,7 +1408,7 @@ function setPageSessionTimer(expires) {
     var timeout;
     var now = new moment();
     var val = expires.replace(/\+|[A-Z]|[a-z]/g, '');
-    
+
     if(expires.indexOf("h")>0){
         timeout = moment(now).add(val, "h");
     }
@@ -1419,7 +1419,7 @@ function setPageSessionTimer(expires) {
     }
     else
     if(expires.indexOf("s")>0){
-        if(val<60){val=2}; 
+        if(val<60){val=2};
         timeout = moment(now).add(val, "s");
     }
     else{
@@ -1443,7 +1443,7 @@ function setPageSessionTimer(expires) {
                 }
                 display("<span id='sss_expired'>Session is about to expire!</span>",10);}
             }
-            var dsp = "<font size='1px;'>[" + tim + "]</font><span "+sty+"> Session expires in " + out + "</span>";                
+            var dsp = "<font size='1px;'>[" + tim + "]</font><span "+sty+"> Session expires in " + out + "</span>";
             $("#sss_status").html(dsp);
             if(now.isAfter(timeout)){
                 $("#sss_status").html("<span id='sss_expired'><a href='login_ctr.cgi'>Page Session has Expired!</a></span>");
@@ -1451,8 +1451,8 @@ function setPageSessionTimer(expires) {
                 $("#ed").prop( "disabled", true );
                 $("#el").prop( "disabled", true );
                 $("#am").prop( "disabled", true );
-                if($('#auto_logoff').val()=='1'){                    
-                      dialogModal("Page Session has Expired","Please login again!", true);                    
+                if($('#auto_logoff').val()=='1'){
+                      dialogModal("Page Session has Expired","Please login again!", true);
                 }
             }
 
@@ -1470,7 +1470,7 @@ function setPageSessionTimer(expires) {
  }
 
  function dialogModal(title, message, logout) {
-    
+
     if(logout) {
         $('<div></div>').dialog({
             modal: true,
@@ -1485,10 +1485,10 @@ function setPageSessionTimer(expires) {
                     $( this ).dialog( "close" );
                     location.reload();
                 }
-            }    
+            }
         });
-    }else {      
-    
+    }else {
+
             $('<div></div>').dialog({
                 modal: true,
                 title: title,
diff --git a/install_cpan_modules_required.pl b/install_cpan_modules_required.pl
new file mode 100755 (executable)
index 0000000..6053496
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/env perl
+##
+# Module installer for projects.
+# Run this script from any Project directory containing perl modules or scripts.
+##
+use warnings; use strict;
+###
+# Prerequisites for this script itself. Run first:
+# cpan Term::ReadKey;
+# cpan Term::ANSIColor;
+## no critic (ProhibitStringyEval)
+use Term::ReadKey;
+use Term::ANSIColor qw(:constants);
+
+use constant PERL_FILES_GLOB => "*local/*.pl local/*.pm system/modules/*.pm htdocs/cgi-bin/*.cgi htdocs/cgi-bin/system/modules/*.pm tests/*.pm tests/*.pl .pl *.pm *.cgi";
+
+my $project = `pwd`."/".$0; $project =~ s/\/.*.pl$//g;  $project =~ s/\s$//g;
+my @user_glob;
+our $PERL_VERSION = $^V->{'original'}; my $ERR = 0; my $key;
+
+print WHITE "\n *** Project Perl Module Installer coded by ",BRIGHT_RED, "https://github.com/wbudic", WHITE,"***", qq(
+         \nRunning scan on project path:$project
+         \nYou have Perl on $^O [$^X] version: $PERL_VERSION\n
+);
+print BLUE "<<@<\@INC<\n# Your default module package paths:\n", YELLOW;
+local $. = 0; foreach(@INC){
+  print $.++.".: $_\n";
+}
+print BLUE ">>\n", RESET;
+if($> > 0){
+  print "You are NOT installing system wide, which is required for webservers CGI.\nAre you sure about this?\n";
+  print "Tip -> If you are getting web page errors of some modules missing, try runing this utility as sudo.\n";
+}else{
+  print "You are INSTALLING modules SYSTEM WIDE, are you sure about this?\n"
+}
+if(@ARGV==0){
+  print qq(\nThis program will try to figure out now all the modules
+  required for this project, and install them if missing.
+  This can take some time.
+  );
+  print RED "Do you want to proceed (press either the 'Y'es or 'N'o key)?", RESET;
+
+do{
+  ReadMode('cbreak');
+  $key = ReadKey(0); print "\n";
+  ReadMode('normal');
+    exit 1 if(uc $key eq 'N');
+    $key = "[ENTER]" if $key =~ /\n/;
+    print "You have pressed the '$key' key, that is nice, but why?\nOnly the CTRL+C/Y/N keys do something normal." if uc $key ne 'Y';
+  }while(uc $key ne 'Y');
+}
+else{
+  foreach(@ARGV){
+    if(-d $_){
+      $_ =~ s/\s$//g;
+      print "\nGlobing for perl files in $project/$_";
+      my @located = glob("$_/*.pl $_/*.pm");
+      print " ... found ".@located." files.";
+      push @user_glob, @located;
+
+    }else{
+      warn "Argument: $_ is not a local directory."
+    }
+  }
+}
+
+my @locals=();
+print "\nGlobing for perl modules in project $project";
+my @perl_files = glob(PERL_FILES_GLOB);
+print " ... found ".@perl_files." files.\n";
+push @perl_files, @user_glob;
+my %modules; my %localPackages;
+foreach my $file(@perl_files){
+   next if $0 =~ /$file$/;
+   if($file =~ /(\w*)\.pm$/){
+      $localPackages{$1}=$file;
+   }
+   print "\nExamining:$file\n";
+   my $res  =  `perl -ne '/\\s*(^use\\s([a-zA-Z:]*))\\W/ and print "\$2;"' $file`;
+   my @list = split(/;+/,$res);
+   foreach(@list){
+     if($_=~ /^\w\d\.\d+.*/){
+      print "\tA specified 'use $_' found in ->$file\n";
+      if($PERL_VERSION ne $_){
+         $_ =~s/^v//g;
+         my @fv = split(/\./, $_);
+         $PERL_VERSION =~s/^v//g;
+         my @pv = split(/\./, $PERL_VERSION);
+         push @fv, 0 if @fv < 3;
+         for my$i(0..$#fv){
+           if( $pv[$i] < $fv[$i] ){
+              $ERR++; print "\n\t\033[31mERROR -> Perl required version has been found not matching.\033[0m\n";
+              last
+           }
+         }
+      }
+     }
+   }
+   foreach(@list){
+    $_ =~ s/^\s*|\s*use\s*//g;
+    $_ =~ s/[\'\"].*[\'\"]$//g;
+    next if !$_ or $_ =~ /^[a-z]|\d*\.\d*$|^\W/;
+    $_ =~ s/\(\)|\(.*\)|qw\(.*\)//g;
+    $modules{$_}=$file if $_;
+    print "$_\n";
+   }
+   if($file=~/\.pm$/){# it is presumed local package module.
+      $locals[@locals] = `perl -ne '/\\s*(^package\\s(\\w+))/ and print "\$2" and exit' $file`;
+   }
+}
+
+print WHITE "\nList of Modules required for thie project:\n";
+my @missing=();
+foreach my $mod (sort keys %modules){
+    my $missing;
+    eval "use $mod";
+    if ($@){
+      $missing[@missing] = $mod;
+      print MAGENTA "\t$mod \t in ", $modules{$mod}," is suspicious?\n";
+    }else{
+      print BLUE "\t$mod\n"
+    }
+}foreach(@missing){
+  if(exists $localPackages{$_}){
+      delete $modules{$_}
+  }else{
+      print BRIGHT_RED $_, MAGENTA, " is missing!\n"
+  }
+}
+my %skip_candidates;
+my $missing_count = @missing;
+if($missing_count>0){
+  foreach my $candidate(@missing){
+    foreach(@locals){
+      if($_ eq $candidate && not exists $skip_candidates{$_}){
+        $missing_count--;
+        $skip_candidates{$_} = 1;
+        print GREEN, "Found the missing $candidate module in locals.\n"
+      }
+    }
+  }
+}
+my $perls = `whereis perl`;
+print GREEN, "Following is all of ",$perls;
+print YELLOW, "Reminder -> Make sure you switched to the right brew release.\n" if $perls =~ /perlbrew/;
+print RESET, "Number of local modules:",scalar(@locals),"\n";
+print RESET, "Number of external modules:",scalar(keys %modules),"\n";
+print RESET, "Number of cpan modules about to be tried to install:",$missing_count,"\n";
+
+print GREEN, qq(
+Do you still want to continue to compile/test/install or check further modules?
+Only the first run is the depest and can take a long time, i.e. if you have to install over 5 modules.
+At other times this will only check further your current status.
+
+Now (press either the 'Y'es or 'N'o key) please?), RESET;
+do{
+ReadMode('cbreak');
+$key = ReadKey(0); print "\n";
+ReadMode('normal');
+  exit 1 if(uc $key eq 'N');
+  $key = "[ENTER]" if $key =~ /\n/;
+  print "You have pressed the '$key' key, that is nice, but why?\nOnly the CTRL+C/Y/N keys do something normal.\n" if uc $key ne 'Y';
+}while(uc $key ne 'Y');
+
+my ($mcnt,$mins) = (0,0);
+my @kangaroos = sort keys %skip_candidates;
+
+##
+# Some modules if found to be forcefeed. can be hardcoded here my friends, why not?
+# You got plenty of space on your disc, these days, don't you?
+##
+foreach ((
+                'Syntax::Keyword::Try',
+                'DBD::SQLite',
+                'DBD::Pg',
+                'LWP::Simple',
+                'LWP::Protocol::https',
+                'XML::LibXML::SAX'
+)){
+  $modules{$_}=1; print "Forcefeed: $_\n"
+}
+
+MODULES_LOOP:
+foreach my $mod (sort keys %modules){
+
+  foreach(@kangaroos){
+      if($_ eq $mod){
+        next MODULES_LOOP
+      }
+  }
+  $mcnt++;
+  ## no critic (ProhibitStringyEval)
+  eval "use $mod";
+  if ($@) {
+      system(qq(perl -MCPAN -e 'install $mod'));
+      if ($? == -1) {
+        print "failed to install: $mod\n";
+      }else{
+        my $v = eval "\$$mod\::VERSION";
+           $v = $v ? "(v$v)" : "";
+        print "Installed module $mod $v!\n";
+        $mins++
+      }
+  }else{
+   $mod =~ s/\s*$//;
+   my $v = eval "\$$mod\::VERSION";
+      $v = $v ? "(v$v)" : "";
+      print "Skipping module $mod $v, already installed!\n";
+  }
+}
+print "\nProject $project\nRequires $mcnt modules.\nInstalled New: $mins\n";
+print "WARNING! - This project requires in ($ERR) parts code that might not be compatible yet with your installed/running version of perl (v$PERL_VERSION).\n"
+if $ERR;
+
+
+=begin copyright
+Programed by  : Will Budic
+EContactHash  : 990MWWLWM8C2MI8K (https://github.com/wbudic/EContactHash.md)
+Source        : https://github.com/wbudic/PerlCNF.git
+Documentation : Specifications_For_CNF_ReadMe.md
+    This source file is copied and usually placed in a local directory, outside of its repository project.
+    So it could not be the actual or current version, can vary or has been modiefied for what ever purpose in another project.
+    Please leave source of origin in this file for future references.
+Open Source Code License -> https://github.com/wbudic/PerlCNF/blob/master/ISC_License.md
+=cut copyright
\ No newline at end of file
diff --git a/rss_output/tree_feed_CPAN.cnf b/rss_output/tree_feed_CPAN.cnf
new file mode 100644 (file)
index 0000000..14ba1d0
--- /dev/null
@@ -0,0 +1,1230 @@
+<<CNF_FEED<TREE>
+    Release: 1
+    Version: 1.0
+      <Feed<
+       Expires: 2023-09-26 08:46:42
+       File: ./rss_output/tree_feed_CPAN.cnf
+       Published: 2023-09-14 22:46:42.042 UTC
+       Title: 'Recent CPAN uploads - MetaCPAN'
+       URL: http://search.cpan.org/uploads.rdf
+      [#[
+         CPAN modules news and agenda.
+      ]#]
+      >Feed>
+      <Brew<
+         <Item<
+          Date: 2023-09-14 08:33:57
+          Link: https://metacpan.org/release/SHLOMIF/XML-GrammarBase-0.2.10
+          Title: XML-GrammarBase-0.2.10
+            <Description<
+            [#[
+               <p>Provide roles and base classes for processors of specialized XML grammars.</p><p>Changes for 0.2.10 - 2023-09-14</p><ul><li>Better compatibility with bleadperl</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 08:01:08
+          Link: https://metacpan.org/release/SYP/Net-Curl-0.54_2
+          Title: Net-Curl-0.54_2
+            <Description<
+            [#[
+               Perl interface for libcurl
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 03:02:53
+          Link: https://metacpan.org/release/ANATOFUZ/Teng-0.34
+          Title: Teng-0.34
+            <Description<
+            [#[
+               <p>very simple DBI wrapper/ORMapper</p><p>Changes for 0.34 - 2023-09-14T03:02:36Z</p><ul><li>[IMPORTANT]  change the minimum supported perl version to 5.16 (#165)</li>
+               <li>FIX: Don't update when set column from null to null (#164 thanks hitode909)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 02:18:56
+          Link: https://metacpan.org/release/KIMOTO/SPVM-0.989042
+          Title: SPVM-0.989042
+            <Description<
+            [#[
+               <p>SPVM Language</p><p>Changes for 0.989042 - 2023-09-12</p><ul><li>Incompatible Changes</li>
+               <li>New Features</li>
+               <li>Document Improvement</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-14 00:05:27
+          Link: https://metacpan.org/release/PERLANCAR/Perinci-To-Doc-0.881
+          Title: Perinci-To-Doc-0.881
+            <Description<
+            [#[
+               <p>Convert Rinci metadata to documentation</p><p>Changes for 0.881 - 2023-07-09</p><ul><li>No functional changes.</li>
+               <li>Remove usage of smartmatch.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 23:57:14
+          Link: https://metacpan.org/release/MARIOROY/MCE-Shared-1.886
+          Title: MCE-Shared-1.886
+            <Description<
+            [#[
+               <p>MCE extension for sharing data supporting threads and processes</p><p>Changes for 1.886</p><ul><li>Add Android support. This required moving MCE::Shared::Base::Common out of MCE::Shared::Base to separate file MCE::Shared::Common.</li>
+               <li>Bump MCE dependency to 1.889.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 23:37:02
+          Link: https://metacpan.org/release/MARIOROY/MCE-1.889
+          Title: MCE-1.889
+            <Description<
+            [#[
+               <p>Many-Core Engine for Perl providing parallel processing capabilities</p><p>Changes for 1.889</p><ul><li>Add Android support. Thank you, Dimitrios Kechagias.</li>
+               <li>Revert defer signal-handling in MCE::Channel (send2 method).</li>
+               <li>Improve mutex synchronize (aka enter) with guard capability. Thank you, Jos� Joaqu�n Atria.</li>
+               <li>Fix mutex re-entrant lock on the Windows platform.</li>
+               <li>Add mutex guard_lock method.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 23:35:35
+          Link: https://metacpan.org/release/MARIOROY/Mutex-1.011
+          Title: Mutex-1.011
+            <Description<
+            [#[
+               <p>Various locking implementations supporting processes and threads</p><p>Changes for 1.011</p><ul><li>Bump version.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 22:40:32
+          Link: https://metacpan.org/release/EXODIST/Test2-Suite-0.000156
+          Title: Test2-Suite-0.000156
+            <Description<
+            [#[
+               <p>Distribution with a rich set of tools built upon the Test2 framework.</p><p>Changes for 0.000156 - 2023-09-13T15:11:52-07:00</p><ul><li>Fix typo in POD for Test2::Util::Importer</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 21:57:38
+          Link: https://metacpan.org/release/PEVANS/Device-AVR-UPDI-0.14
+          Title: Device-AVR-UPDI-0.14
+            <Description<
+            [#[
+               <p>interact with an AVR microcontroller over UPDI</p><p>Changes for 0.14 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 21:30:55
+          Link: https://metacpan.org/release/AJNN/Mac-Finder-Tags-0.02
+          Title: Mac-Finder-Tags-0.02
+            <Description<
+            [#[
+               <p>Access macOS file tags (aka Finder labels)</p><p>Changes for 0.02 - 2023-09-13</p><ul><li>Avoid syntax that causes warnings in Object::Pad 0.801.</li>
+               <li>Tests no longer fail for unexpected warnings, except during author testing.</li>
+               <li>Drop Devel::CheckOS prerequisite (by bundling it).</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 21:00:16
+          Link: https://metacpan.org/release/MICHIELB/GD-Barcode-2.00
+          Title: GD-Barcode-2.00
+            <Description<
+            [#[
+               <p>Create barcode image with GD</p><p>Changes for 2.00 - 2023-09-13</p><ul><li>'Production' release, no changes to 1.99_03</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 20:58:49
+          Link: https://metacpan.org/release/BENNIE/VMware-vCloud-2.403
+          Title: VMware-vCloud-2.403
+            <Description<
+            [#[
+               <p>VMware vCloud API</p><p>Changes for 2.403 - 2023-09-13</p><ul><li>Added: POD test</li>
+               <li>Improved: 'use warnings' on all modules</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 20:44:39
+          Link: https://metacpan.org/release/RKAPL/EAI-Wrap-0.3
+          Title: EAI-Wrap-0.3
+            <Description<
+            [#[
+               framework for easy creation of Enterprise Application Integration tasks
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 20:27:57
+          Link: https://metacpan.org/release/MIYAGAWA/Starman-0.4017
+          Title: Starman-0.4017
+            <Description<
+            [#[
+               <p>High-performance preforking PSGI/Plack web server</p><p>Changes for 0.4017 - 2023-09-13T13:27:02Z</p><ul><li>Handle EINTR when doing sysread calls (Rob Mueller) #148</li>
+               <li>Requires perl 5.14</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 17:41:55
+          Link: https://metacpan.org/release/EXODIST/Term-Table-0.017
+          Title: Term-Table-0.017
+            <Description<
+            [#[
+               <p>Format a header and rows into a table</p><p>Changes for 0.017 - 2023-09-13T10:41:08-07:00</p><ul><li>Remove 'Importer' dependency</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 16:02:42
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-TSL256x-0.09
+          Title: Device-Chip-TSL256x-0.09
+            <Description<
+            [#[
+               <p>chip driver for TSL256x</p><p>Changes for 0.09 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 16:01:19
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-TCS3472x-0.05
+          Title: Device-Chip-TCS3472x-0.05
+            <Description<
+            [#[
+               <p>chip driver for TCS3472x-family</p><p>Changes for 0.05 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 16:01:04
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-SSD1306-0.14
+          Title: Device-Chip-SSD1306-0.14
+            <Description<
+            [#[
+               <p>chip driver for monochrome OLED modules</p><p>Changes for 0.14 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:59:38
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-Si5351-0.02
+          Title: Device-Chip-Si5351-0.02
+            <Description<
+            [#[
+               <p>chip driver for Si5351</p><p>Changes for 0.02 - 2023-09-13</p><ul><li>CHANGES</li>
+               <li>BUGFIXES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:58:10
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-SDCard-0.04
+          Title: Device-Chip-SDCard-0.04
+            <Description<
+            [#[
+               <p>chip driver for SD and MMC cards</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:56:44
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-PCF8574-0.06
+          Title: Device-Chip-PCF8574-0.06
+            <Description<
+            [#[
+               <p>chip driver for a PCF8574 or PCA8574</p><p>Changes for 0.06 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:56:33
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-PCF8563-0.04
+          Title: Device-Chip-PCF8563-0.04
+            <Description<
+            [#[
+               <p>chip driver for a PCF8563</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:54:59
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-OPT3001-0.03
+          Title: Device-Chip-OPT3001-0.03
+            <Description<
+            [#[
+               <p>chip driver for OPT3001</p><p>Changes for 0.03 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:53:40
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-nRF24L01P-0.08
+          Title: Device-Chip-nRF24L01P-0.08
+            <Description<
+            [#[
+               <p>chip driver for a nRF24L01+</p><p>Changes for 0.08 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:52:21
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-NoritakeGU_D-0.06
+          Title: Device-Chip-NoritakeGU_D-0.06
+            <Description<
+            [#[
+               <p>chip driver for Noritake GU-D display modules</p><p>Changes for 0.06 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:50:58
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MPL3115A2-0.13
+          Title: Device-Chip-MPL3115A2-0.13
+            <Description<
+            [#[
+               <p>chip driver for a MPL3115A2</p><p>Changes for 0.13 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:49:37
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MCP23x17-0.07
+          Title: Device-Chip-MCP23x17-0.07
+            <Description<
+            [#[
+               <p>chip driver for the MCP23x17 family</p><p>Changes for 0.07 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:48:18
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MAX7219-0.09
+          Title: Device-Chip-MAX7219-0.09
+            <Description<
+            [#[
+               <p>chip driver for a MAX7219</p><p>Changes for 0.09 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:47:00
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-MAX44009-0.05
+          Title: Device-Chip-MAX44009-0.05
+            <Description<
+            [#[
+               <p>chip driver for MAX44009</p><p>Changes for 0.05 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:45:41
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-INA219-0.10
+          Title: Device-Chip-INA219-0.10
+            <Description<
+            [#[
+               <p>chip driver for an INA219</p><p>Changes for 0.10 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:44:22
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-HTU21D-0.10
+          Title: Device-Chip-HTU21D-0.10
+            <Description<
+            [#[
+               <p>chip driver for HTU21D</p><p>Changes for 0.10 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:43:03
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-DS1307-0.08
+          Title: Device-Chip-DS1307-0.08
+            <Description<
+            [#[
+               <p>chip driver for a DS1307</p><p>Changes for 0.08 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:41:44
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-CCS811-0.03
+          Title: Device-Chip-CCS811-0.03
+            <Description<
+            [#[
+               <p>chip driver for CCS811</p><p>Changes for 0.03 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:40:25
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-CC1101-0.09
+          Title: Device-Chip-CC1101-0.09
+            <Description<
+            [#[
+               <p>chip driver for a CC1101</p><p>Changes for 0.09 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:38:58
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-BV4243-0.04
+          Title: Device-Chip-BV4243-0.04
+            <Description<
+            [#[
+               <p>chip driver for a BV4243</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:37:36
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-BNO055-0.04
+          Title: Device-Chip-BNO055-0.04
+            <Description<
+            [#[
+               <p>chip driver for BNO055</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:36:09
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-BME280-0.06
+          Title: Device-Chip-BME280-0.06
+            <Description<
+            [#[
+               <p>chip driver for BME280</p><p>Changes for 0.06 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:33:22
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AVR_HVSP-0.07
+          Title: Device-Chip-AVR_HVSP-0.07
+            <Description<
+            [#[
+               <p>high-voltage serial programming for AVR chips</p><p>Changes for 0.07 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:26:50
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AS3935-0.04
+          Title: Device-Chip-AS3935-0.04
+            <Description<
+            [#[
+               <p>chip driver for AS3935</p><p>Changes for 0.04 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:25:06
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AnalogConverters-0.15
+          Title: Device-Chip-AnalogConverters-0.15
+            <Description<
+            [#[
+               <p>a collection of chip drivers</p><p>Changes for 0.15 - 2023-08-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 15:23:40
+          Link: https://metacpan.org/release/PEVANS/Device-Chip-AD9833-0.05
+          Title: Device-Chip-AD9833-0.05
+            <Description<
+            [#[
+               <p>chip driver for AD9833</p><p>Changes for 0.05 - 2023-09-13</p><ul><li>CHANGES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 14:58:27
+          Link: https://metacpan.org/release/YANICK/Dist-Zilla-Plugin-CoalescePod-1.0.0
+          Title: Dist-Zilla-Plugin-CoalescePod-1.0.0
+            <Description<
+            [#[
+               <p>merge .pod files into their .pm counterparts</p><p>Changes for 1.0.0 - 2023-09-13</p><ul><li>API CHANGES</li>
+               <li>STATISTICS</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 14:57:01
+          Link: https://metacpan.org/release/YANICK/Dist-Zilla-Plugin-CoalescePod-0.3.1
+          Title: Dist-Zilla-Plugin-CoalescePod-0.3.1
+            <Description<
+            [#[
+               <p>merge .pod files into their .pm counterparts</p><p>Changes for 0.3.1 - 2023-09-13</p><ul><li>STATISTICS</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 14:10:21
+          Link: https://metacpan.org/release/PEVANS/App-sdview-Output-Tickit-0.03
+          Title: App-sdview-Output-Tickit-0.03
+            <Description<
+            [#[
+               <p>interactive terminal-based viewer for App::sdview</p><p>Changes for 0.03 - 2023-09-13</p><ul><li>CHANGES</li>
+               <li>BUGFIXES</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 11:25:40
+          Link: https://metacpan.org/release/AWNCORP/Venus-4.11
+          Title: Venus-4.11
+            <Description<
+            [#[
+               <p>OO Library for Perl 5</p><p>Changes for 4.11 - 2023-09-13</p><ul><li>[feature] Implement Venus::Assert#includes</li>
+               <li>[feature] Implement Venus::Future</li>
+               <li>[feature] Refactor Venus::Assert, Implement Venus::{Coercion,Constraint}</li>
+               <li>[feature] Implement Venus::Space#{patch,patched,unpatch}</li>
+               <li>[feature] Implement Venus::Sealed</li>
+               <li>[feature] Implement Venus::Atom</li>
+               <li>[feature] Implement Venus::Enum</li>
+               <li>[feature] Implement Venus::Role::Superable</li>
+               <li>[feature] Implement Venus::Role::Patchable</li>
+               <li>[feature] Implement Venus#clone</li>
+               <li>[feature] Implement Venus::Process#future</li>
+               <li>[feature] Implement Venus::Future#wait</li>
+               <li>[update] Refactor Venus::Test</li>
+               <li>[update] Add test and documentation for Venus::Process#is_dyadic</li>
+               <li>[update] Update Venus::Process#await, auto-reap processes</li>
+               <li>[update] Research CPANTS issue with Venus::Process</li>
+               <li>[update] Update Venus::Process, prevent PPID in dyads</li>
+               <li>[update] Use Venus::Check types in all signatures</li>
+               <li>[update] Update Venus#async to return Venus::Future</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 11:00:19
+          Link: https://metacpan.org/release/JBAIER/Pass-OTP-1.6
+          Title: Pass-OTP-1.6
+            <Description<
+            [#[
+               <p>Perl implementation of HOTP / TOTP algorithms</p><p>Changes for 1.6 - 2023-09-13</p><ul><li>fix SHA384 and SHA512 blocksize (#1)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 10:35:02
+          Link: https://metacpan.org/release/BOD/Image-Square-0.01_4
+          Title: Image-Square-0.01_4
+            <Description<
+            [#[
+               <p>Crop and resize an image to create a square image</p><p>Changes for 0.01_4</p><ul><li>Tests still fail on different builds of GD.  Now using PNG as input image and native GD format for output.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 09:13:52
+          Link: https://metacpan.org/release/MRUEDA/Convert-Pheno-0.13
+          Title: Convert-Pheno-0.13
+            <Description<
+            [#[
+               <p>A module to interconvert common data models for phenotypic data</p><p>Changes for 0.13 - 2023-09-12T00:00:00Z</p><ul><li>Pushing new version after passing all tests</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 09:12:25
+          Link: https://metacpan.org/release/TONYC/Imager-zxing-1.001
+          Title: Imager-zxing-1.001
+            <Description<
+            [#[
+               <p>Barcode scanning with libzxing-cpp</p><p>Changes for 1.001</p><ul><li>re-work std::string handling to use the typemap</li>
+               <li>fix &quot;decoder&quot; -&gt; &quot;decode&quot; in the SYNOPSIS</li>
+               <li>support all Imager image layouts</li>
+               <li>require a recent enough ExtUtils::CppGuess and set the required C++ standard</li>
+               <li>allow the zxing-cpp package name for pkg-config, which seems to be what packagers used before upstream decided on &quot;zxing.pc&quot;. https://github.com/tonycoz/imager-zxing/issues/1</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 05:55:56
+          Link: https://metacpan.org/release/KIMOTO/SPVM-Sys-0.491
+          Title: SPVM-Sys-0.491
+            <Description<
+            [#[
+               <p>System Calls for File IO, User, Process, Signal, Socket</p><p>Changes for 0.491 - 2023-09-13</p><ul><li>New Features</li>
+               <li>Incompatible Changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:12:50
+          Link: https://metacpan.org/release/GRYPHON/Log-Dispatch-Email-Mailer-1.13
+          Title: Log-Dispatch-Email-Mailer-1.13
+            <Description<
+            [#[
+               <p>Log::Dispatch::Email subclass that sends mail using Email::Mailer</p><p>Changes for 1.13 - 2023-09-12T21:12:32-07:00</p><ul><li>Require exact v1.23 (resolves issue #5)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:11:27
+          Link: https://metacpan.org/release/GRYPHON/exact-me-1.05
+          Title: exact-me-1.05
+            <Description<
+            [#[
+               <p>Original program path locations extension for exact</p><p>Changes for 1.05 - 2023-09-12T21:10:17-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:10:01
+          Link: https://metacpan.org/release/GRYPHON/exact-lib-1.04
+          Title: exact-lib-1.04
+            <Description<
+            [#[
+               <p>Compile-time @INC manipulation extension for exact</p><p>Changes for 1.04 - 2023-09-12T21:09:28-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:09:57
+          Link: https://metacpan.org/release/GRYPHON/exact-fun-1.01
+          Title: exact-fun-1.01
+            <Description<
+            [#[
+               <p>Functions and methods with parameter lists for exact</p><p>Changes for 1.01 - 2023-09-12T21:08:36-07:00</p><ul><li>Use Import::Into instead of eval to inject/import</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:08:39
+          Link: https://metacpan.org/release/GRYPHON/exact-conf-1.08
+          Title: exact-conf-1.08
+            <Description<
+            [#[
+               <p>Cascading merged application configuration extension for exact</p><p>Changes for 1.08 - 2023-09-12T21:07:37-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:07:18
+          Link: https://metacpan.org/release/GRYPHON/exact-cli-1.07
+          Title: exact-cli-1.07
+            <Description<
+            [#[
+               <p>Command-line interface helper utilities extension for exact</p><p>Changes for 1.07 - 2023-09-12T21:06:47-07:00</p><ul><li>Remove redundant strict (since it's provided by exact)</li>
+               <li>New import signature change required by exact v1.23</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 04:05:52
+          Link: https://metacpan.org/release/GRYPHON/exact-class-1.19
+          Title: exact-class-1.19
+            <Description<
+            [#[
+               <p>Simple class interface extension for exact</p><p>Changes for 1.19 - 2023-09-12T21:05:22-07:00</p><ul><li>New import signature change required by exact v1.23</li>
+               <li>Use Import::Into instead of eval to inject code</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 01:04:01
+          Link: https://metacpan.org/release/MBRADSHAW/Mail-BIMI-3.20230913
+          Title: Mail-BIMI-3.20230913
+            <Description<
+            [#[
+               <p>BIMI object</p><p>Changes for 3.20230913 - 2023-09-13</p><ul><li>Add policy.mark-type to Authentication-Results</li>
+               <li>Add policy.experimental to Authentication-Results</li>
+               <li>Add options to limit which mark types a MBP accepts</li>
+               <li>Add options to limit acceptance of experimental certificates</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-13 00:06:02
+          Link: https://metacpan.org/release/PERLANCAR/App-rimetadb-0.226
+          Title: App-rimetadb-0.226
+            <Description<
+            [#[
+               <p>Manage a Rinci metadata database</p><p>Changes for 0.226 - 2023-07-09</p><ul><li>No functional changes.</li>
+               <li>Remove the usage of smartmatch.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 23:33:11
+          Link: https://metacpan.org/release/DRCLAW/constant-more-v0.3.0
+          Title: constant-more-v0.3.0
+            <Description<
+            [#[
+               <p>Constants and Enumerations. Assign constant values from the command line</p><p>Changes for v0.3.0 - 2023-09-13</p><ul><li>Features</li>
+               <li>Tests</li>
+               <li>POD</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 23:07:51
+          Link: https://metacpan.org/release/GRYPHON/exact-1.23
+          Title: exact-1.23
+            <Description<
+            [#[
+               <p>Perl pseudo pragma to enable strict, warnings, features, mro, filehandle methods</p><p>Changes for 1.23 - 2023-09-12T16:06:03-07:00</p><ul><li>Improve/fix import of packages into other packages (resolves issue #4)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 22:14:53
+          Link: https://metacpan.org/release/JIMAVERA/ODF-MailMerge-1.000
+          Title: ODF-MailMerge-1.000
+            <Description<
+            [#[
+               <p>&quot;Mail Merge&quot; or just substitute tokens in ODF documents</p><p>Changes for 1.000</p><ul><li>ODF::MailMerge::Engine-&gt;new positional args eliminated; now use proto_elt =&gt; $table   # specify the object directly context =&gt; $context, proto_tag =&gt; &quot;tagstring&quot;  # search for it Modifier :die (&quot;Delete If Empty&quot;) replaces :delempty</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 21:49:07
+          Link: https://metacpan.org/release/SHANCOCK/Perl-Tidy-20230912
+          Title: Perl-Tidy-20230912
+            <Description<
+            [#[
+               indent and reformat perl scripts
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 21:45:10
+          Link: https://metacpan.org/release/BOD/Image-Square-0.01_3
+          Title: Image-Square-0.01_3
+            <Description<
+            [#[
+               Crop and resize an image to create a square image
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 19:38:33
+          Link: https://metacpan.org/release/TURNERJW/StreamFinder-2.19
+          Title: StreamFinder-2.19
+            <Description<
+            [#[
+               <p>Fetch actual raw streamable URLs from various radio-station, video &amp; podcast websites.</p><p>Changes for 2.19 - 2023-09-12</p><ul><li>StreamFinder::Youtube - 1) Fix failure to fetch artist, icon, etc. sometimes on embedded IFRAME urls (slight site changes) and first episode from some channels.  2) Add -youtube-site argument to specify a different default youtube site (default https://www.youtube.com). 3) Add ability to parse youtube channel URLs containing an at-sign, ie.: https://www.youtube.com/@channelID.</li>
+               <li>StreamFinder::Subsplash - Restore as EXPERIMENTAL, as this site seems to now work again, at least for audio streams on some sites.</li>
+               <li>StreamFinder::Anystream - doc. touchups.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 19:00:33
+          Link: https://metacpan.org/release/AWNCORP/Venus-4.10
+          Title: Venus-4.10
+            <Description<
+            [#[
+               <p>OO Library for Perl 5</p><p>Changes for 4.10 - 2023-09-12</p><ul><li>[feature] Implement Venus::Assert#includes</li>
+               <li>[feature] Implement Venus::Future</li>
+               <li>[feature] Refactor Venus::Assert, Implement Venus::{Coercion,Constraint}</li>
+               <li>[feature] Implement Venus::Space#{patch,patched,unpatch}</li>
+               <li>[feature] Implement Venus::Sealed</li>
+               <li>[feature] Implement Venus::Atom</li>
+               <li>[feature] Implement Venus::Enum</li>
+               <li>[feature] Implement Venus::Role::Superable</li>
+               <li>[feature] Implement Venus::Role::Patchable</li>
+               <li>[feature] Implement Venus#clone</li>
+               <li>[feature] Implement Venus::Process#future</li>
+               <li>[feature] Implement Venus::Future#wait</li>
+               <li>[update] Refactor Venus::Test</li>
+               <li>[update] Add test and documentation for Venus::Process#is_dyadic</li>
+               <li>[update] Update Venus::Process#await, auto-reap processes</li>
+               <li>[update] Research CPANTS issue with Venus::Process</li>
+               <li>[update] Update Venus::Process, prevent PPID in dyads</li>
+               <li>[update] Use Venus::Check types in all signatures</li>
+               <li>[update] Update Venus#async to return Venus::Future</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 17:09:39
+          Link: https://metacpan.org/release/DERIV/Net-Async-Blockchain-0.003
+          Title: Net-Async-Blockchain-0.003
+            <Description<
+            [#[
+               <p>base for blockchain subscription clients.</p><p>Changes for 0.003 - 2023-09-12T17:08:09+00:00</p><ul><li>Improvements</li>
+               <li>Breaking changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 14:47:38
+          Link: https://metacpan.org/release/OALDERS/Open-This-0.000033
+          Title: Open-This-0.000033
+            <Description<
+            [#[
+               <p>Try to Do the Right Thing when opening files</p><p>Changes for 0.000033 - 2023-09-12T14:46:08Z</p><ul><li>Add support for IntellJ IDEA, VSCode, VSCodium and more. Also fix typo (GH#51) (mcneb10)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 13:50:33
+          Link: https://metacpan.org/release/NLNETLABS/Net-DNS-SEC-1.22
+          Title: Net-DNS-SEC-1.22
+            <Description<
+            [#[
+               DNSSEC extensions to Net::DNS
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 09:35:41
+          Link: https://metacpan.org/release/MIKIHOSHI/WebService-Mailgun-0.16
+          Title: WebService-Mailgun-0.16
+            <Description<
+            [#[
+               <p>API client for Mailgun (https://mailgun.com/)</p><p>Changes for 0.16 - 2023-09-12T09:33:42Z</p><ul><li>fix document</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 09:29:10
+          Link: https://metacpan.org/release/MIKIHOSHI/WebService-Mailgun-0.15.1
+          Title: WebService-Mailgun-0.15.1
+            <Description<
+            [#[
+               <p>API client for Mailgun (https://mailgun.com/)</p><p>Changes for 0.15.1 - 2023-09-12T09:27:19Z</p><ul><li>fix document</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 09:16:10
+          Link: https://metacpan.org/release/MRUEDA/Convert-Pheno-0.12_4
+          Title: Convert-Pheno-0.12_4
+            <Description<
+            [#[
+               A module to interconvert common data models for phenotypic data
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 08:44:03
+          Link: https://metacpan.org/release/LICHTKIND/Graphics-Toolkit-Color-1.61
+          Title: Graphics-Toolkit-Color-1.61
+            <Description<
+            [#[
+               <p>color palette creation helper</p><p>Changes for 1.61 - 2023-09-12</p><ul><li>= fix tests</li>
+               <li>+ renamed complementary method =&gt; complement</li>
+               <li>- deprecated complementary, will be removed at 2.0</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 06:33:21
+          Link: https://metacpan.org/release/MERKYS/Graph-SSSR-0.1.0
+          Title: Graph-SSSR-0.1.0
+            <Description<
+            [#[
+               <p>Find Smallest Set of Smallest Rings in graphs</p><p>Changes for 0.1.0 - 2022-12-15</p><ul><li>Initial release.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 03:59:54
+          Link: https://metacpan.org/release/DJZORT/Net-Proxmox-VE-0.38
+          Title: Net-Proxmox-VE-0.38
+            <Description<
+            [#[
+               <p>Pure perl API for Proxmox virtualisation</p><p>Changes for 0.38 - 2023-09-11</p><ul><li>fix/use correct parameter name for user in tests thanks to MartijnLivaart</li>
+               <li>Feat/check new arguments thanks to MartijnLivaart</li>
+               <li>Fix/test access directory thanks to MartijnLivaart via GH#27</li>
+               <li>feat: check if debug parameter propagates from new() thanks to MartijnLivaart via GH#29</li>
+               <li>Pod corrections thanks to poptix via GH#31</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 01:48:59
+          Link: https://metacpan.org/release/KIMOTO/SPVM-IO-0.208
+          Title: SPVM-IO-0.208
+            <Description<
+            [#[
+               <p>File IO, Socket, Select/Polling.</p><p>Changes for 0.208 - 2023-09-11</p><ul><li>Prerequirement Changes</li>
+               <li>Incompatibe Changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-12 00:05:49
+          Link: https://metacpan.org/release/PERLANCAR/Perinci-Access-Perl-0.899
+          Title: Perinci-Access-Perl-0.899
+            <Description<
+            [#[
+               <p>Access Perl module, functions, variables through Riap</p><p>Changes for 0.899 - 2023-07-09</p><ul><li>No functional changes.</li>
+               <li>Remove usage of smartmatch.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 23:07:19
+          Link: https://metacpan.org/release/LICHTKIND/Graphics-Toolkit-Color-1.60
+          Title: Graphics-Toolkit-Color-1.60
+            <Description<
+            [#[
+               <p>color palette creation helper</p><p>Changes for 1.60 - 2023-09-11</p><ul><li>= API development</li>
+               <li>* added color spaces HSB HSW YIQ</li>
+               <li>+ output format array: ['rgb',1,2,3]</li>
+               <li>+ input and output format string: 'rgb: 1,2,3'</li>
+               <li>+ input and output format css_string: 'rgb(1,2,3)'</li>
+               <li>- deprecated getter method string</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 21:32:59
+          Link: https://metacpan.org/release/SKIM/PYX-GraphViz-0.05
+          Title: PYX-GraphViz-0.05
+            <Description<
+            [#[
+               <p>GraphViz output for PYX handling.</p><p>Changes for 0.05 - 2023-09-11T23:32:16+02:00</p><ul><li>Fix Makefile.PL which fails with no '.' in @INC.</li>
+               <li>Fix link to image in doc.</li>
+               <li>Improve LICENSE AND COPYRIGHT section in doc.</li>
+               <li>Improve SYNOPSIS section in doc.</li>
+               <li>Rename example file to better name.</li>
+               <li>Rewrite bugtracker to github.</li>
+               <li>Rewrite to new author github username.</li>
+               <li>Update Module::Install to 1.21 version.</li>
+               <li>Update copyright years.</li>
+               <li>Update my name to actual version.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 21:13:51
+          Link: https://metacpan.org/release/SKIM/PYX-0.09
+          Title: PYX-0.09
+            <Description<
+            [#[
+               <p>A perl module for PYX handling.</p><p>Changes for 0.09 - 2023-09-11T23:12:41+02:00</p><ul><li>Common</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 19:34:24
+          Link: https://metacpan.org/release/RJBS/App-Cronjob-1.200014
+          Title: App-Cronjob-1.200014
+            <Description<
+            [#[
+               <p>wrap up programs to be run as cron jobs</p><p>Changes for 1.200014 - 2023-09-11T15:34:04-04:00</p><ul><li>fix documentation to add missing switches</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 18:38:50
+          Link: https://metacpan.org/release/PREACTION/Minion-Backend-mysql-1.002
+          Title: Minion-Backend-mysql-1.002
+            <Description<
+            [#[
+               <p>MySQL backend</p><p>Changes for 1.002 - 2023-09-11T13:37:53-05:00</p><ul><li>Fixed</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 18:34:53
+          Link: https://metacpan.org/release/SRI/Mojolicious-9.34
+          Title: Mojolicious-9.34
+            <Description<
+            [#[
+               <p>Real-time web framework</p><p>Changes for 9.34 - 2023-09-11</p><ul><li>Added support for serving static files with a prefix.</li>
+               <li>Deprecated Mojo::File::spurt in favor of Mojo::File::spew.</li>
+               <li>Added prefix attribute to Mojolicious::Static.</li>
+               <li>Added url_for_file method to Mojolicious::Controller.</li>
+               <li>Added file_path method to Mojolicious::Static.</li>
+               <li>Added spew method to Mojo::File. (genio)</li>
+               <li>Added encoding option to slurp method in Mojo::File. (genio)</li>
+               <li>Added url_for_asset and url_for_file helpers to Mojolicious::Plugins::DefaultHelpers.</li>
+               <li>Added favicon helper to Mojolicious::Plugin::TagHelpers.</li>
+               <li>Fixed support for module_true Perl feature in Mojolicious apps. (haarg)</li>
+               <li>Fixed a bug in Mojo::Promise where map could hang on false values after concurrency limit. (ilmari)</li>
+               <li>Fixed built-in templates to not require an internet connection. (hernan604)</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 17:14:12
+          Link: https://metacpan.org/release/LEONT/version-0.9930
+          Title: version-0.9930
+            <Description<
+            [#[
+               <p>Structured version objects</p><p>Changes for 0.9930</p><ul><li>Permit a colon after a vstring, thus allowing an attrlist to follow a version declaration on a 'class' statement (Perl5 #20891)</li>
+               <li>Simplify and fix w.r.t locale handling</li>
+               <li>Make tests not fail when path to dist includes &quot;panic&quot;</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 16:14:59
+          Link: https://metacpan.org/release/SKIM/PYX-Sort-0.04
+          Title: PYX-Sort-0.04
+            <Description<
+            [#[
+               <p>Processing PYX data or file and sort element attributes.</p><p>Changes for 0.04 - 2023-09-11T18:14:08+02:00</p><ul><li>Fix bugtracker.</li>
+               <li>Fix sense of example in doc.</li>
+               <li>Fix use lib '.' in Makefile.PL.</li>
+               <li>Improve LICENSE AND COPYRIGHT section in doc.</li>
+               <li>Improve SYNOPSIS section in doc.</li>
+               <li>Rename example to better name.</li>
+               <li>Update Module::Install to 1.21 version.</li>
+               <li>Update author github username.</li>
+               <li>Update author username.</li>
+               <li>Update copyright years.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 15:29:26
+          Link: https://metacpan.org/release/RKAPL/EAI-Wrap-0.2
+          Title: EAI-Wrap-0.2
+            <Description<
+            [#[
+               framework for easy creation of Enterprise Application Integration tasks
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 15:25:17
+          Link: https://metacpan.org/release/PROCH/NBI-Slurm-0.6.1
+          Title: NBI-Slurm-0.6.1
+            <Description<
+            [#[
+               <p>NBI Slurm module</p><p>Changes for 0.6.1 - 2023-09-02</p><ul><li>Minor improvements in `make_image_from_bioconda` and `make_package`</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 15:16:08
+          Link: https://metacpan.org/release/PROCH/NBI-Slurm-0.6.0
+          Title: NBI-Slurm-0.6.0
+            <Description<
+            [#[
+               <p>NBI Slurm module</p><p>Changes for 0.6.0 - 2023-09-01</p><ul><li>Adding packaging tools: `make_image_from_bioconda` and `make_package`</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 15:00:49
+          Link: https://metacpan.org/release/CORION/Net-Async-OSC-0.01
+          Title: Net-Async-OSC-0.01
+            <Description<
+            [#[
+               <p>send/receive OSC asynchronously</p><p>Changes for 0.01 - 2023-09-11</p><ul><li>Released on an unsuspecting world</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 13:36:23
+          Link: https://metacpan.org/release/IDOPEREL/Svsh-1.003000
+          Title: Svsh-1.003000
+            <Description<
+            [#[
+               <p>Process supervision shell for daemontools/perp/s6/runit</p><p>Changes for 1.003000 - 2023-09-11T16:35:55+03:00</p><ul><li>Replace Getopt::Compact with core module Getopt::Long</li>
+               <li>Switch license to Apache 2.0</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 13:20:12
+          Link: https://metacpan.org/release/WYANT/Astro-App-Satpass2-0.051
+          Title: Astro-App-Satpass2-0.051
+            <Description<
+            [#[
+               <p>Predict satellite visibility using Astro::Coord::ECI</p><p>Changes for 0.051 - 2023-09-11</p><ul><li>Without DateTime, ParseTime::ISO8601 now accepts any zone. These are handled by setting $ENV{TZ} before the conversion and hoping for the best. The documentation warns that this is a shaky way to handle zones.</li>
+               <li>Make Warner-&gt;wail() stack dump if $Carp::Verbose true.</li>
+               <li>Add --almanac to pass(). This adds almanac data to appropriate passes. --ephemeris is more verbose, adding almanac data to all passes. This change involved refactoring event formatting to use sub-templates, rather than if-elsif-else chains.</li>
+               <li>Add 'none' as valid argument to FormatTime-&gt;round_time(). It is equivalent to specifying undef, i.e. no rounding.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 11:14:37
+          Link: https://metacpan.org/release/MRSCOTTY/Connector-1.51
+          Title: Connector-1.51
+            <Description<
+            [#[
+               <p>a generic connection to a hierarchical-structured data set</p><p>Changes for 1.51 - 2023-09-11T11:13:04Z</p><ul></ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 09:30:18
+          Link: https://metacpan.org/release/NLNETLABS/Net-DNS-SEC-1.21_01
+          Title: Net-DNS-SEC-1.21_01
+            <Description<
+            [#[
+               DNSSEC extensions to Net::DNS
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 08:05:24
+          Link: https://metacpan.org/release/KIMOTO/SPVM-Sys-0.490
+          Title: SPVM-Sys-0.490
+            <Description<
+            [#[
+               <p>System Calls for File IO, User, Process, Signal, Socket</p><p>Changes for 0.490 - 2023-09-11</p><ul><li>Bug Fix</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 07:21:21
+          Link: https://metacpan.org/release/MRSCOTTY/Connector-1.50
+          Title: Connector-1.50
+            <Description<
+            [#[
+               <p>a generic connection to a hierarchical-structured data set</p><p>Changes for 1.50 - 2023-09-11T07:06:09Z</p><ul></ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 06:52:02
+          Link: https://metacpan.org/release/MBRADSHAW/Mail-Milter-Authentication-3.20230911
+          Title: Mail-Milter-Authentication-3.20230911
+            <Description<
+            [#[
+               <p>A Perl Mail Authentication Milter</p><p>Changes for 3.20230911 - 2023-09-11T06:18:44+00:00</p><ul><li>Core: Switch from deprecated method in Net::DNS In Net::DNS::Resolver, call the rdstring method rather than the deprecated rdstring method This change bumps the minimum version of Net::DNS required to 1.01</li>
+               <li>SPF: Add option to detect and optionally mitigate SPF upgrade problems.</li>
+               <li>Core: Add authentication_milter_log command with arex subcommand which can be used to process ARex JSON log format back into standard Authentication-Results: header lines</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 04:26:54
+          Link: https://metacpan.org/release/KIMOTO/SPVM-Sys-0.489
+          Title: SPVM-Sys-0.489
+            <Description<
+            [#[
+               <p>System Calls for File IO, User, Process, Signal, Socket</p><p>Changes for 0.489 - 2023-09-11</p><ul><li>New Features</li>
+               <li>Bug Fix</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 01:44:08
+          Link: https://metacpan.org/release/KIMOTO/SPVM-Sys-0.488
+          Title: SPVM-Sys-0.488
+            <Description<
+            [#[
+               <p>System Calls for File IO, User, Process, Signal, Socket</p><p>Changes for 0.488 - 2023-09-11</p><ul><li>Prerequirement Changes</li>
+               <li>Incompatible Changes</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+         <Item<
+          Date: 2023-09-11 01:37:35
+          Link: https://metacpan.org/release/JIMAVERA/ODF-MailMerge-0.003
+          Title: ODF-MailMerge-0.003
+            <Description<
+            [#[
+               <p>&quot;Mail Merge&quot; or just substitute tokens in ODF documents</p><p>Changes for 0.003</p><ul><li>(i.e. only if the row is being replicated).  Preened docs.</li>
+               </ul>
+            ]#]
+            >Description>
+         >Item>
+      >Brew>
+>>