Showing posts with label perl. Show all posts
Showing posts with label perl. Show all posts

Friday, September 29, 2017

Freeside Development Environment (day 1)

I'm on the job market again after two wonderful years at Broadbean. Sadly, budget cuts eliminated my position and several others, but enough about that. Anyway, as part of my job search, I came upon Freeside, which is a billing application for ISP's, CLEC's, and similar. They work on an open source, paid support model, so after discussion with Ivan, the CEO and "head geek", I thought I'd do some work "on spec" as a job application of sorts. But to begin with, I need a development environment.

At Broadbean I got used to every package we worked on having its own cpanfile. This meant that, using perlbrew, all I had to do was perlbrew lib create perl-<version>@<package>, and then cpanm --installdeps ., and I was set. Freeside doesn't work like that, so I'll set up a VirtualBox VM for it instead.

I'm going to use Debian 8.9.0 (Jessie), as that is the version where installation instructions are completely documented in the Freeside wiki. So I've downloaded the Debian network install CD image from https://cdimage.debian.org/cdimage/archive/8.9.0/amd64/iso-cd/ and will install it into a VM. I've got Google Fiber, so this shouldn't take long.

Since I intend to interact with this VM via SSH and a browser, I've selected the "web server", "SSH server", and "standard system utilities" collections. A few minutes later, the system is installed, I've rebooted the VM, and installed sudo (I prefer using sudo to simply doing su). I set up a network interface, put in my SSH key for passwordless login, and I'm ready to start setting up my development environment.

First thing to install is vim. This is my preferred editor for code, and I can't abide nano. ;) Next I set up the package repositories as specified in the Freeside installation instructions and install the Freeside packages. I'll note here that aptitude is recommending that I remove the packages exim4, exim4-base, exim4-config, and exim4-daemon-light, and is not installing the recommendation of the Perl EV package. I assume since Freeside is installing Mojolicious, it is going to be running under Mojo::IOLoop (which I was just working with at Broadbean. Freeside++!) No, I was mistaken. Only very minimal usage of Mojolicious in Freeside.

OK, a few minutes later all the packages are installed and I'm setting up the database. Note where the docs say "[ as postgres/pgsql user ]" they mean "user" to be the system user (from /etc/passwd) "postgres". I set up the database role with a crappy password (this box isn't exposed to the internet, after all) and as the "freeside" user, I execute freeside-setup -d example.com. Note that the domain is important - if it's not a valid TLD, freeside-setup will throw an error and you'll have to blow away the freeside database and start over. Anyway, freeside-setup barfs, so now I need to figure out what I did wrong.

Turns out the wiki has things in the wrong order. I need to set up the RT database before I run freeside-setup. With that done, I can move on, install the system users and so on, and I should be up and running. Sure enough, I am able to restart the Freeside daemon successfully.

Since I want to hack on Freeside, I think I need to blow away the Freeside packages and set up a fresh installation via the instructions on installing from source. This might allow me to use perlbrew as well, but I'm not certain of that. But that's a task for another day.




Wednesday, April 17, 2013

Auto-generating setters and getters in Perl with Moose

As part of a project for work interfacing with the RDS encoder for the radio station (an Inovonics Model 730) I thought, "wouldn't it be nice to have a Perl object that provides a complete interface to this device?"  The only trouble is that the device takes a lot of different commands, and it would be really tedious to write sub set_foo { ... }; sub get_foo { ... } umpteen million times for every last command / datum the encoder supported, since every set_foo { ... } was going to boil down to:

sub set_foo {
    my ($self, $value) = @_;
    return _set('foo', $value);
}

sub get_foo {
    my $self = shift;
    return _get('foo');
}

Where _set() and _get() took care of the actual business of communicating with the encoder.

Now, I could have done this in Vim with regular expressions - just copy/paste and use the regex to change the appropriate things. Still tedious, though. I also could have done something like this:

sub set_property {
    my ($self, $property, $value) = @_;
    return _set($property, $value);
}

sub get_property {
    my ($self, $property) = @_;
    return _get($property);
}

And that would have worked fine. I could have put some code in there to throw an exception on an invalid property, maybe something to validate the values based on the property name, all that sort of thing. But Moose gives us a nifty trick to avoid having to validate the property name:

    package MooseSketch;
    use Moose;

    my $meta = __PACKAGE__->meta;
    foreach my $prop (qw/foo bar baz bak/) {
        $meta->add_method(qq/set_$prop/, sub { 
                my $self = shift;
                my $value = shift;
                return $self->_set($prop, $value);
            }
        );
        $meta->add_method(qq/get_$prop/, sub { 
                my $self = shift;
                return $self->_get($prop);
            }
        );
    }

This $meta business comes from Class::MOP::Class, which allows us to do introspection and manipulation of Perl 5 objects. So with Class::MOP::Class, I can add or remove methods programatically at runtime, or even create entire classes. Neat, huh?

Hope you find this useful. I know I sure will. Many thanks to the good people over at Stack Overflow who helped me figure this out. My original question: How to auto-generate a bunch of setters/getters tied to a network service in Moose?

Monday, February 11, 2013

Generating a Word document with Perl and Win32::OLE

I'm about to have to throw this particular bit of code away, as I'm not able to get this to work from Scheduled Tasks on Windows 7.  Before I sent it to the bit bucket, however, I thought I'd post it here with the hope that someone will find it useful.



# Expects arguments as a hashref with the keys:
# # log_date: Date of the log
# # data: an arrayref of arrayrefs.  First line is treated as column headings, following lines are treated as data.
#
# A double horizontal rule will be added between the column headings and the data.
#
# NB: The reason that everything gets its own object (e.g. "my $tables = $doc->Tables; my $table = $tables->Add(...);")
# is not (neccessarily) for "Law of Demeter" reasons, but rather MS recommended practice when
# automating Office applications from Visual Studio (and by extension, OLE): http://support.microsoft.com/kb/317109
# Experimentally, I have noticed instances of the Word executable remaining in memory after program exit;
# refactoring the code in this way is an attempt to deal with that issue.
# 11 Feb 2013 KP
sub _print_with_word {
    my $args = shift;

    if ( ref $args ne q/HASH/ ) {
        croak(
            sprintf q/Usage: %s /,
            ( caller 0 )[$FUNCTION_NAME_POSITION]
        );
    }
    foreach my $required_key (qw/log_date data/) {
        if ( !$args->{$required_key} ) {
            croak(qq/Missing required key '$required_key' in args/);
        }
    }

    my $header = _slurp_file( $CONFIG->{'_'}{'header_file'} );
    my $footer = _slurp_file( $CONFIG->{'_'}{'footer_file'} );

    my @rows = @{ $args->{'data'} };

    my $word   = Win32::OLE->new( 'Word.Application', 'Quit' );
    _debug(q/Created new Word object/);

    my $doc    = $word->Documents->Add();
    _debug(q/Added new document/);

    my $selection = $word->Selection;
    _debug(q/Got Selection instance/);    

    
    my $selection_paragraph_format = $selection->ParagraphFormat;
    _debug(q/Got ParagraphFormat instance for selection/);
    
    $selection_paragraph_format->{'SpaceAfter'} = 0;
    _debug(q/Set paragraph spacing for header/);
    
    $selection->TypeText( { 'Text' => qq/$header\n\n/, } );
    _debug(q/Typing header into selection/);
    
    $selection->BoldRun();
    _debug(q/started bold run/);
    
    $selection_paragraph_format->{'Alignment'} = wdAlignParagraphRight;
    _debug(q/Set date paragraph format to right/);
    
    $selection->TypeText(
        {
            'Text' => Time::Piece->strptime(
                $args->{'log_date'}, q|%m/%d/%Y %H:%M:%S|
              )->strftime(qq/%A %B %d %Y\n\n/)
        }
    );
    _debug(q/Typing date header into selection/);
    
    $selection->BoldRun();
    _debug(q/End bold run/);

    my $range  = $selection->Range;
    _debug(q/Got Range instance from selection/);
    
    my $tables = $doc->Tables;
    _debug(q/Got Tables collection from document/);
    
    my $table  = $tables->Add( $range, scalar @rows, scalar @{ $rows[0] } );
    _debug(q/Added new table to document/);
    
    for my $rownum ( 0 .. $#rows ) {
        my $cols = $rows[$rownum];
        for my $colnum ( 0 .. $#{ $rows[$rownum] } ) {
            my @cellpos    = ( $rownum + 1, $colnum + 1 );
            my $cell       = $table->Cell(@cellpos);
            _debug(qq/Got cell at ($cellpos[0], $cellpos[1]) /);
            
            my $cell_range = $cell->Range;
            _debug(qq/Got Range instance for cell at ($cellpos[0], $cellpos[1])/);
            
            $cell_range->{'Text'} = $cols->[$colnum];
            _debug(qq/Set text of Range for cell at ($cellpos[0], $cellpos[1]) to "$cols->[$colnum]"/);
        }
    }
    my $rows                 = $table->Rows;
    _debug(q/Got Rows collection from table/);
    
    my $first_row            = $rows->First;
    _debug(q/Got first Row object (header row) from Rows collection/);
    
    my $first_row_range      = $rows->First->Range;
    _debug(q/Got Range instance for header row/);
    
    my $first_row_range_font = $first_row_range->Font;
    _debug(q/Got Font instance for header row Range/);
    
    $first_row_range_font->{'Bold'} = 1;
    _debug(q/Set header row Range Font to bold/);
    
    my $first_row_range_paragraph_format = $first_row_range->ParagraphFormat;
    _debug(q/Got ParagraphFormat instance for header row/);
    
    $first_row_range_paragraph_format->{'Alignment'} = wdAlignParagraphCenter;
    _debug(q/Set alignment for table headers to center/);
    
    my $first_row_bottom_border = $first_row->Borders(wdBorderBottom);
    _debug(q/Got Border instance for bottom of header row/);
    
    @{$first_row_bottom_border}{qw/LineStyle LineWidth/} =
      ( wdLineStyleDouble, wdLineWidth100pt );
    _debug(q/Set bottom border of header row to 10 pt double line/);
      
    my $paragraphs            = $doc->Paragraphs;
    _debug(q/Got Paragraphs collection from document/);
    
    my $last_paragraph        = $paragraphs->Last;
    _debug(q/Got last paragraph from document/);
    
    my $last_paragraph_format = $last_paragraph->Format;
    _debug(q/Got Format instance for last paragraph/);
    
    $last_paragraph_format->{'Alignment'}  = wdAlignParagraphLeft;
    _debug(q/Set last paragraph alignment to left/);
    
    $last_paragraph_format->{'SpaceAfter'} = 0;
    _debug(q/Set spacing on last paragraph/);
    
    my $last_paragraph_range = $last_paragraph->Range;
    _debug(q/Got Range instance for last paragraph/);
    
    $last_paragraph_range->InsertAfter( { 'Text' => qq/\n$footer/ } );
    _debug(q/Inserting footer after last paragraph range/);
    
    #$doc->SaveAs( { 'Filename' => Cwd::getcwd . '/test.doc' } );
    
    $doc->PrintOut();
    _debug(q/Printed document/);
    
    $doc->Close( { 'SaveChanges' => wdDoNotSaveChanges } );
    _debug(q/Close document without saving/);
    
    $word->Quit();
    _debug(q/Quit Word/);
    
    return 1;
}

Thursday, April 3, 2008

Learning to play with Catalyst

Yeah, I know I haven't posted in three weeks, almost. So sue me. :)

I just got a job developing a web application in Perl. And since all the cool kids are using Catalyst for web development these days, I thought I'd jump on the bandwagon.

So this thing I'm developing has an Oracle backend. First of all, let me say that Oracle has been regularly pissing me off for various reasons. Chief among them right now is the 30 character limit on identifiers, so if I name a constraint (which is a good thing to be doing) it has to be under 30 characters.

Anyway, Catalyst has this nifty bit whereby it will automatically generate table classes, or "source classes", in DBIC parlance (for use by DBIx::Class::Schema) for you. And this is really cool and timesaving. However, it does not, so far as I've been able to tell, automatically populate the hash generated by DBIx::Class::ResultSource->column_info from the table structure in the database. That bit you have to do by hand, unfortunately. It will do constraints, however, which makes up for that in part. One thing to note about constraints - you have to access the constraints by their names, not by the columns they're defined on. So if you have a table with a field 'login', you can't specify {key => 'login'} in a call to DBIx::Class::ResultSet->find. It took me a good half day to figure that one out. :)

Monday, March 10, 2008

Hack Day has a wiki!

I know I promised pictures from my ride, but Flickr is being shitty to me and won't let me upload.

I posted about Hack Day to the local Perl Mongers list. Somehow, news of this reached a fellow in Omaha by the name of Jay Hannah, and he was kind enough to start a wiki for the event.