Skip to content

Commit

Permalink
Merge pull request #874 from bpmartin20/extensible-read_table_A
Browse files Browse the repository at this point in the history
Don't require changes to read_table_A when creating new device handlers
  • Loading branch information
hplato authored Apr 15, 2024
2 parents 79cc5be + 8fe0472 commit e07bbe6
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 37 deletions.
122 changes: 85 additions & 37 deletions lib/read_table_A.pl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
#
# See mh/code/test/test.mht for an example.
#
# The more generalized format is "TYPE, PARM1, PARM2, ...". There are lots
# of types hardcoded below. However, read_table_A is also now extensible, so
# we don't need to modify it for every new record type. Instead, just create
# a .pm module in an accessible library that matches the lowercase of the
# "TYPE" field (e.g. "type.pm") that has a "new" method. read_table_A will
# load the module and write the code to invoke the new method with the
# provided parameters. If the module also has an "init" method, it will be
# invoked after the "new" method returns, as some MH operations can't be
# performed until *after* new returns and instantiation completes. See
# "lib/read_table_a_sample.pm" for an example module.

#print_log "Using read_table_A.pl";

Expand Down Expand Up @@ -2039,8 +2049,27 @@ sub read_table_A {
$object = "Tasmota_HTTP::Fan('$address', '$other')";
}
else {
print "\nUnrecognized .mht entry: $record\n";
return;
# Doesn't match anything here, but don't just fail. Instead, make
# read_table_A extensible by searching lib for a module with
# the lower case of this name. See comments at the top for details,
# and read_table_a_sample.pm for an example.
my $lctype = lc($type); # Convert to lower case.
eval "require $lctype;"; # Can we load it?
if ($@) {
print qq<\nUnrecognized .mht entry and "require $lctype;" returned "$@": $record.\n>;
return;
}
$name = $item_info[0];
@item_info = map {$_ =~ /^['"]/?$_:"'$_'"} @item_info; # Add single quotes if no quoting present.
# Note: we don't handle grouplist here, as it may not apply to some objects. Instead,
# the "init" method can manage group membership if that's appropriate.
my $item_info = join(',',@item_info);
$object = "$lctype( $item_info )";
$additional_code = sprintf(
"%-36s %s",
"\$${name}", "-> init( $item_info ) if (\$${name}->can('init')); # noloop\n",
);
$grouplist = ''; # No grouplist processing here, though the init method might do that.
}
if ($object) {
Expand All @@ -2049,42 +2078,11 @@ sub read_table_A {
$code .= $code2;
}

$grouplist = '' unless $grouplist; # Avoid -w uninialized errors
for my $group ( split( '\|', $grouplist ) ) {
$group =~ s/ *$//;
$group =~ s/^ //;

if ( lc($group) eq 'hidden' ) {
$code .= sprintf "\$%-35s -> hidden(1);\n", $name;
next;
}

if ( $group eq ''){
&::print_log("grouplist '$grouplist' contains empty group!");
next;
}

if ( $name eq $group ) {
&::print_log(
"mht object and group name are the same: $name Bad idea!");
}
else {
# Allow for floorplan data: Bedroom(5,15)|Lights
if ( $group =~ /(\S+)\((\S+?)\)/ ) {
$group = $1;
my $loc = $2;
$loc =~ s/;/,/g;
$loc .= ',1,1' if ( $loc =~ tr/,/,/ ) < 3;
$code .= sprintf "\$%-35s -> set_fp_location($loc);\n", $name;
}
$code .= sprintf "\$%-35s = new Group;\n", $group
unless $groups{$group};
$code .= sprintf "\$%-35s -> add(\$%s);\n", $group, $name
unless $groups{$group}{$name};
$groups{$group}{$name}++;
}
}
# Process grouplist. This code was moved into a subroutine so it could be called by extension
# modules, too.
$code .= read_table_grouplist_A($name, $grouplist) if ($grouplist);

# Add in anything else some record-type above needed.
if ($additional_code) {
$code .= $additional_code;
}
Expand Down Expand Up @@ -2163,6 +2161,56 @@ sub read_table_finish_A {
return $code;
}



sub read_table_grouplist_A {

my($name, $grouplist) = @_;

$grouplist = '' unless $grouplist; # Avoid -w uninialized errors
my $code = '';
for my $group ( split( '\|', $grouplist ) ) {
$group =~ s/ *$//;
$group =~ s/^ //;

if ( lc($group) eq 'hidden' ) {
$code .= sprintf "\$%-35s -> hidden(1);\n", $name;
next;
}

if ( $group eq ''){
&::print_log("grouplist '$grouplist' contains empty group!");
next;
}

if ( $name eq $group ) {
&::print_log(
"mht object and group name are the same: $name Bad idea!");
}
else {
# Allow for floorplan data: Bedroom(5,15)|Lights
if ( $group =~ /(\S+)\((\S+?)\)/ ) {
$group = $1;
my $loc = $2;
$loc =~ s/;/,/g;
$loc .= ',1,1' if ( $loc =~ tr/,/,/ ) < 3;
$code .= sprintf "\$%-35s -> set_fp_location($loc);\n", $name;
}
unless ($groups{$group}) {
$code .= sprintf "\$%-35s = new Group unless (\$%s);\n", $group, $group;
}
unless ($groups{$group}{$name}) {
$code .= sprintf "\$%-35s -> add(\$%s);\n", $group, $name;
$groups{$group}{$name}++;
}
}
}

return $code;
}



#This is called inside each definition, this is using SCENE_BUILD as an example:
# Called with :
# - $type: Name of the device being validated
Expand Down
138 changes: 138 additions & 0 deletions lib/read_table_a_sample.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# ------------------------------------------------------------------------------

# noloop=start
#
# This is a read_table_A.pl extension module
#


=pod
=head1 B<read_table_a_sample>
J. Author (author<at>example.com)
=head2 SYNOPSIS
When defining in an .mht file:
# READ_TABLE_A_SAMPLE, item_name, parm1, ...
Ex:
READ_TABLE_A_SAMPLE, sample1, All_Lights, other-parm
Note: "READ_TABLE_A_SAMPLE" in uppercase is a convention in .mht files,
but not a requirement. The type is converted to lowercase when used as
a module name, for the convenience of the developers. All uppercase file
names are a pain to work with.
When defining in code:
Ex:
my $sample1 = new read_table_a_sample(parm1, ...);
$sample1->init(parm1, ...) if ($sample1->can('init'));
=head2 DESCRIPTION
SAMPLE object does lots of impressive sample stuff. Well, actually, nothing
at all, but that's because this is only an example. Your module will be more
useful.
=head2 FILE
read_table_a_sample.pm # Note: always lowercase filenames.
=head2 LICENSE
This free software is licensed under the terms of the GNU public license.
=head2 NOTES
(none)
=head2 METHODS
=over 4
=cut

# ------------------------------------------------------------------------------

package read_table_a_sample;

use strict;
use warnings;

@read_table_a_sample::ISA = ( 'Generic_Item' );

=item new($myname, @other_parms )
Create a new instance of the object. Name is required. Additional parms
may be included as required for your object. The "new" method is required.
=cut

sub new {
my ( $class, $myname, @other_parms ) = @_;

# Set up generic storage, inheritance, etc. Your needs may differ.
my %myhash;
my $self = \%myhash;
tie %myhash, 'Generic_Item_Hash', $self;
bless $self, $class;

# Do whatever we need to in order to create this item. In our sample case, we're not
# trying to set up new objects or structures, but you'll need more.
warn "lib/read_table_a_sample.pm wasn't intended for actual use.\n";

return $self;

}

=item init( $self, $myname, @other_parms )
Perform post instantiation set-up on the object. Some characteristics
of an object can't be performed until after the "new" method completes. This
is because the object doesn't really exist until after "new" returns a value.
One example is that the object can't be assigned to a group until after it
exists. These additional actions can be handled in the "init" method, if it
is present.
The "init" receives in its parameter list a pointer to the instantiated object
(e.g. "$self"), followed by all the same arguments as "new". If the "new"
method needs to pass additional parameters to the "init" method, it may do so
by storing them in the object. And, of course, "init" may delete them later if
they have no lasting purpose once init completes.
The "init" method is optional.
=cut

sub init {

my ( $self, $myname, @other_parms ) = @_;

# Perform any post-instantiation tasks. In this sample code, we'll assume the next parm is a
# list of groups this object should be a member of, and add it to those groups.
my $grouplist = $other_parms[0]; # Should probably do this when parsing @_ instead.
# Use read_table_A.pm's code to process the group list, so we handle all the exceptions properly.
my $code = main::read_table_grouplist_A($myname,$grouplist);
eval "package main;$code;"; # Run the group creation/assignment code created by read_table_grouplist_A.
if ($@) {
main::print_log "Error: read_table_a_sample: Unable to create/assign groups while creating $myname: $@";
}

return $self;
}

=pod
=back
=cut

# noloop=stop
1;

0 comments on commit e07bbe6

Please sign in to comment.