Perl 5 has these handy quote operators to save you time, like q{}
and qq{}
to quote strings without and with interpolation, e.g.
0> q{"I don't have to escape quotes!"}
$res[1] = ""I don't have to escape quotes!""
It also has qw{}
to create lists from quoted words. This does not interpolate variables:
1> my $message = "This is the commit message"
$res[1] = "This is the commit message"
2> qw{ git commit -m $message }
$res[2] = [
[0] "git",
[1] "commit",
[2] "-m",
[3] "$message"
]
So what if I want interpolation in my quoted word operator? qqw{}
, right? Well no, but Perl 6 has qqw{}
:
> my $message = "This is the commit message"
This is the commit message
> join "\n", qqw{ git commit -m $message }
git
commit
-m
This
is
the
commit
message
Hmmm, not quite what I need. Looks like what I want is qqww{}
> join "\n", qqww{ git commit -m "$message" }
git
commit
-m
This is the commit message
Perfect! Except I'm working with Perl 5. Enter Quote::Code which offers code interpolation. Variables are code, so:
3> use Quote::Code
4> qcw{ git commit -m {$message} }
$res[2] = [
[0] "git",
[1] "commit",
[2] "-m",
[3] "This is the commit message"
]
Bingo.
Shout out to freenode #perl regulars pink_mist for mentioning Quote::Code and mauke for building it in the first place!
Quick post, since it might help someone - I couldn't find a complete solution on the intertubes...
DBIx::Class::Schema::Versioned makes use of SQL::Translator to generate DDL and diffs.
My own use-case calls for SQLite in some test/dev environments and PostgreSQL in staging/production, but I was unable to generate diffs for a recent change:
translate: Error with parser 'SQL::Translator::Parser::SQLite': Unable to parse line ...
Postgres has neat types like timestamp with time zone which you should think about using. SQLite, on the other hand, treats types a little differently.
No problem, you say, SQLite can simply ignore this type!
Fine in theory, but SQL::Translator::Parser::SQLite's grammar is a little tighter. The solution is to use the single-word definition for the desired type, 'timezonetz'.
Thanks to ilmari and others in #dbix-class for helping me get this straight!
Here's a quick one to dust off the cobwebs and purge my system of these poisonous humours.
While running Steam on Slackware I was always keenly aware of those "Not Available on Your Platform" dialogs, their barbs sinking deep into my envy gland. "I really should get me a Windows license." I thought - "The green pastures of carefree gaming over there on Windows will be mine. It will all Just Work, Seamlessly."
Ha!
Games For Windows Live is an abortive PC gaming platform conceived by Microsoft to make Windows gaming more ... Xbox-y. Many games require it before allowing you to even play. Standard evil DRM. I mistakenly thought using Steam would limit me to one evil DRM platform.
As the GFWL Service no longer exists, this essentially requires you have an Xbox live account... and your games now complain if you haven't upgraded to "Gold" or whatever their "Fuck you, pay me" tier is called.
I've had a lot of fun with GFWL. Here's the sequence of events as I recall them.
Shite! To get my save games back I had to install this Definitely-not-dodgy Fallout 3 GFWL patch.
You remember PC Gaming, right? Any old crappy USB joypad works.
Well, not any more. There's an API called XInput which is designed to work exclusively with Xbox 360 compatible controllers. Yep, you all have to buy new controllers or, alternatively, get an advanced degree in DLL-fuckology.
But not every game is on board with this XInput bullshit. Want your expensive 360 sticks to work with games made before, say, 2006? Or ones that simply don't support Xinput? Tough shit.
This XInput / DirectInput thing is such a cluster fuck that there's a whole market of Game controllers with a switch to select which standard you need per-game
What's that? This rant belongs in 2007? Hey fuck you, I'm old. It takes me a long time to figure out the arbitrary anti-engineering decisions of fucked up commercial entities.
Good guy on the Internet Ken Guest gave me a NC100 many years ago. I since broke it by plugging in the wrong power supply by accident (Sinclair and Amstrad gear from the era usually used a negative centre power supply connector where most everyone else did the opposite).
This results in a popped surface mount fuse. There is a very naughty fix described here where you just solder a strand of wire (fuse wire if you can be bothered) across the contacts.
Because I am an oaf with clown hands and sausage fingers, I managed the soldering job but broke the LCD ribbon cable when reassembling it. Luckily I found a Replacement ribbon cable from Farnell which is now installed and working:
I picked up a spare ribbon cable in case I want to open the thing up again and put in, you know, a real fuse. The cables I got are at least 5cm longer than the original, so future maintenance work should be a little easier. Contacts for the CR2032 lithium cell also got an alcohol scrub while I was in there (if you don't have this, the unit refuses to turn on).
So Sawyer X gave this talk at this year's YAPC::NA, I urge you to watch it even if you're not into Perl.
Sawyer X - The Joy in What We Do
I have nothing to say beyond this, other than the fact that I love writing software so much that I hate software. If you ever want to see a fat guy dancing (sober), then come on over to my house when it looks like the software I'm writing is going well. If you want to see me spit blood and rage, all you have to do is tell me about some software that's only kind of crappy. I'm not a great programmer, barely a good one, but when I know it can trivially be done better and isn't, I get angry. This means I am angry with myself a lot. Anyone who knows me will know the phrase "software sucks".
So, on to Perl. These people are doing it better, but nobody seems to know about it. This is a major part of what Sawyer's talk above is about (seriously, watch it). So, I've appeared out of my hole (yeah, been quiet around here, got me a job) to briefly list some Perl awesomeness, the bits I like and some bits I anticipate using, in no particular order:
The MOP - So Stevan's crazy and wants to put the ideas behind Moose right into core Perl. This is cool for a number of reasons, not least of which (to me) are performance and packaging. Having that power right off the bat (even in your crappy system Perl) will be great.
Dancer (2) - Don't use it as an example of how to write code, but this blog was written using Dancer 2 and it was so easy to put together that there should be a law against it. I am going to update my templates later to crow about this in the footer or something. It's great, try it. Try Dancer 2.
SDL - Perl's SDL bindings have gotten a lot of love in recent years and are really excellent. (SDLx::App makes things so ridiculously easy that you have no excuse. This very site has a few posts on how simple it is. Trust me, if it wasn't simple, I wouldn't be able to do it.) Maybe we can make some more great games other than Frozen Bubble.
FFI::Raw - Because XS is scary and writing bindings needs to be easier. Graham Ollis gave a talk about it this year.
Subroutine Signatures - "Does perl have subroutine signatures yet?" "YES! (Provisionally, pending successful experimentation)". Honestly, I don't know what this is and have never needed it ;-) ...Perl's implementation goes way beyond the capabilities of most other languages, see Ricardo's 5.20 talk linked below.
Perlbrew - I need this to live. Just don't do the curl-pipe-bash thing, I hate that. Do use perlbrew, though.
Data::Printer - stop using Data::Dumper for debugging right now.
Irssi - Irssi has a Perl interface (and an AnyEvent adaptor) which means I can easily write bots and scripts to do useful (but mostly annoying) things in Perl.
Fuse - Got something which you could abstract as a filesystem? Do it in Perl. I've yet to use this in anger, but I think it's because I lack the imagination to come up with something powerful enough to require it. I wrote some toy examples to demonstrate its usefulness and accessibility on blogs.perl.org.
Modern Perl - chromatic is crazy and wrote a great book and gives it away for free (though you should buy it... every year). It's a really short, accessible set of useful practices which will enhance the way you think about programming and Perl.
And while you're here (if indeed you are still here), Ricardo Signes gave two great, funny talks this year about new and cool things in Perl:
Ricardo Signes (rjbs) - 1.21 Gigawatts
Ricardo Signes (rjbs) - Perl 5.20: Perl 5 at 20
There was also this:
Wow.
In part 1 we got some basic collision detection by checking for overlapping rectangles in each move call.
Using positions of Things at a given frame for collision detection presents a problem. If an object is small and fast moving, like a bullet, it will have fairly long vectors to travel and can appear to pass through objects if the delay between frames is long enough.
What we need is something that will calculate if two objects of given dimensions travelling along a given vector over a given time-frame will collide. As usual, CPAN provides. Collision::2D is a module which offers us this kind of continuous collision detection.
So what has changed? Well, we now keep track of the previous move to pass to
Collision::2D::dynamic_collision
as we also want to perform and show the move
(to avoid dodgy looking collision detection where things look like they hit when they merely
get close).
Game::Repliconz::Thing
now has a move
method which stores the parameters of
the move for later calculation, then applies it to x
and y
(performs the move).
sub move {
my ($self) = @_;
$self->last_x = $self->x;
$self->last_y = $self->y;
$self->last_dt = $self->dt;
$self->last_v_x = $self->v_x;
$self->last_v_y = $self->v_y;
$self->x += $self->v_x * $self->dt;
$self->y += $self->v_y * $self->dt;
}
The collision detection itself is now fairly simple:
sub collision {
my ( $self, $thing ) = @_;
return 0 if (!$self->alive || !$thing->alive);
my @rect = map { hash2rect( {
x => $_->last_x,
y => $_->last_y,
h => $_->h,
w => $_->w,
xv => $_->last_v_x,
yv => $_->last_v_y,
} ) } ( $self, $thing );
return dynamic_collision ($rect[0], $rect[1], interval => $self->last_dt);
}
sub check_collides_with {
my ( $self, $things ) = @_;
my $check_distance = 50;
for my $thing (@{$things}) {
next if (abs($self->x - $thing->x > $check_distance) ||
abs($self->y - $thing->y > $check_distance));
if ($self->collision( $thing )) {
$thing->hit;
$self->hit;
return 1;
}
}
}
The collision
function generates a pair of rectangles (rects) for use by Collision::2D.
We then check if a collision occurred at any time between the last frame and
the current one with dynamic_collision
.
We can then use check_collides_with
on our Things, pass in another set of
Things and find out if there was a hit. It only performs the expensive
detection if the objects in question are within a certain distance
So in our move
handler callback in Game::Repliconz
we simply:
$self->{hero}->check_collides_with($self->{baddies});
for my $bullet (@{$self->{hero}->{bullets}}) {
$bullet->check_collides_with($self->{baddies});
}
Then we can filter the lists of Things (bullets, baddies, whatever) by
their alive
method which simply checks remaining lives - bullets have
"lives" to end their path when they hit an enemy. You could make stronger
bullets as a bonus by modifying this value, of course. So how does this
all look?
Note: there was a small bug present in the code this video was captured from, so the collision might not be 100% represented here, but it's close enough.
If you watch this video, you might notice another addition - sound.
This is done pretty simply. There has been code present since the beginning to initialise the SDL audio subsystem and load up a few samples:
sub _init_audio {
my ( $opts ) = @_;
SDL::init(SDL_INIT_AUDIO);
if ( SDL::Mixer::open_audio( 44100, SDL::Constants::AUDIO_S16, 2, 4096) == 0 ) {
$opts->{audio} = 1;
}
else {
$opts->{audio} = 0;
carp "Audio disabled : " . SDL::get_error();
return 0;
}
SDL::Mixer::Channels::allocate_channels(4);
@{$opts->{samples}}{ qw/
bonus_sweeps
laser
explosion
/ } = (
SDL::Mixer::Samples::load_WAV("$opts->{working_dir}/sound/bonus_sweeps.wav"),
SDL::Mixer::Samples::load_WAV("$opts->{working_dir}/sound/laser.wav"),
SDL::Mixer::Samples::load_WAV("$opts->{working_dir}/sound/explosion.wav"),
)
}
This allocates four channels, which we can use for each event type we want to make noise for, shooting, explosions, bonuses and so on. Using a single channel means sounds won't "overlap", so for lasers and such it gives a classic "pew-pew-pewww" effect rather than an echoey/overlaid one. Hope I got my onomatopoeia right there.
The sample, channel and audio subsystem status are passed into the given Thing on instantiation.
$self->{hero} = Game::Repliconz::Guy->new( {
field_width => $self->{w},
field_height => $self->{h},
shoot_noise => $self->{samples}->{laser},
shoot_channel => 1,
audio => $self->{audio},
} );
...which then plays the sound when the given event occurs:
sub shoot {
...
SDL::Mixer::Channels::play_channel( $self->{shoot_channel}, $self->{shoot_noise}, 0 ) if $self->{audio};
...
}
The samples themselves were generated by sfxr, an excellent tool to generate old school FX for games. Each sample was created by hitting the "Randomize" button until a sound I liked played, though there is plenty of scope for controlling and fine tuning the sounds yourself.
That's it for now. As always, comments, criticism and contributions welcome.
Code for Repliconz is on Github.
So what's changed since part 0?
Youtube linky, Repliconz gameplay so far
The green things chasing the Guy are instances of the Baddie class. Since Baddies are
Things, many methods have been moved from Guy
to
Game::Repliconz::Thing
which are common to Baddies, including move
,
constrain_velocity_xy
and shoot
. Bullets are also Things, so now technically
bullets can shoot bullets, but we won't be doing that... yet. Anyone
like the idea of cluster bullets?
The movement of baddies is somewhere between a bullet and the guy. A vector is calculated from current baddie position and guy position, so the baddies pursue the guy. Converting this to a unit vector using the bullet's Pythagoras square method would allow the baddies to travel in any arbitrary direction. This results in them converging rather quickly, giving the appearance of a single, super-strong enemy.
It turns out that constrain_velocity_xy
, which was created to limit
the guy's speed, can also be used to limit the baddies' movement to
45 degree angles. They eventually converge using this method too, but
hopefully you'll have shot a good few of them by the time that happens.
Anyway, now Game::Repliconz::Baddie::move
is pretty simple:
sub move {
my ( $self, $target_x, $target_y, $dt, $app ) = @_;
my $v_y = $target_y - $self->{y};
my $v_x = $target_x - $self->{x};
($v_x, $v_y) = $self->constrain_velocity_xy($v_x, $v_y);
$self->{x} += $v_x * $self->{velocity} * $dt;
$self->{y} += $v_y * $self->{velocity} * $dt;
($self->{x} > 0 || $self->{x} < ($app->w - $self->{w})) &&
($self->{y} > 0 || $self->{y} < ($app->h - $self->{h})) &&
($self->{on_screen} = 1);
}
The on_screen
property will be used later in decisions about collision
detection, since bullets continue off screen and enemies spawn there.
Baddies are pushed onto a queue in the main move callback, with more being spawned when the queue goes below a certain size.
Our collision detection is currently very simple, using Collision::Util to check overlapping rectangles. I need to check out how accurate this is, as the framerate / app delay will have an effect... a bullet might be rendered on either side of a target without ever "passing through" it. The video above appears to show some baddies being "hit", yet surviving.
The last change you might have noticed is the cursor is now a sprite, which is achieved simply by hiding the cursor and rendering a png file sprite at the mouse X/Y position.
As always, comments, criticism and contributions welcome.
Code for Repliconz is on Github.
So I decided to play around with Perl's SDL bindings. A game appears to be happening, so let's play around with this dev log idea too, documenting the process and pitfalls of making some stuff move around on screen.
It turns out you don't need to know a whole lot to make this happen, in the simplest cases at least. Let's see what we have so far:
OK, so we're not going to set the world alight just yet. Anyway, we have a guy and some bullets. We are missing enemies, scoring, action, pew pew noises and any incentive to play. These come later, I hope.
Controls are somewhere between Robotron 2084 or Smash TV (both made by the same legendary game developer) and Abuse. WASD / Arrows move the guy, aim and shoot with the mouse.
Perl's SDL distribution comes with a convenient extension called SDLx::App which is built on SDLx::Controller. This makes creating game loops simple. You set a delay between iterations, handler callbacks for the things a game loop usually contains (input events, drawing and so on) and then run it. Each callback gets a delta-T value (the time that has passed since the previous iteration) so you can tie this value to your physics model and get consistent movement even if you are occasionally starved of resources or change your frame rate (delay).
The three classes of handler we need to define are event, move and show. For those of you familiar with MVC style frameworks, these handlers are (very) roughly analogous: Model ~= move, View ~= show, Controller ~= event.
It should be noted that any number of callbacks for each handler type can be added, though this code currently has just one for each.
Event processing is triggered on input, mouse movements, container changes and so on. Our callback function receives an SDL::Event instance containing the queued events which need handling. Let's take a look at the event handler in Repliconz:
sub events {
my ( $self, $event, $app ) = @_;
return $app->stop if $event->type == SDL_QUIT;
if ($event->type == SDL_KEYDOWN) {
$self->{keys}->{$event->key_sym} = 1;
}
if ($event->type == SDL_KEYUP) {
$self->{keys}->{$event->key_sym} = 0;
}
if ($event->type == SDL_MOUSEMOTION) {
$self->{mouse}->{x} = $event->motion_x;
$self->{mouse}->{y} = $event->motion_y;
}
if ($event->type == SDL_MOUSEBUTTONDOWN && $event->button_button == SDL_BUTTON_LEFT) {
$self->{mouse}->{firing} = 1;
}
if ($event->type == SDL_MOUSEBUTTONUP && $event->button_button == SDL_BUTTON_LEFT) {
$self->{mouse}->{firing} = 0;
}
}
Simple enough, we store the key up/down states in $self->{keys}
and mouse position
/ button state in $self->{mouse}
. So what do we now that we know what our
player is doing? We move...
How does our move handler look?
sub move {
my ( $self, $dt, $app, $t ) = @_;
my $v_x = 0;
my $v_y = 0;
my $bomb = 0;
for (grep { $self->{keys}->{$_} } keys %{$self->{keys}}) {
when ($self->{controls}->{keyboard}->{u}) { $v_y += -1 }
when ($self->{controls}->{keyboard}->{d}) { $v_y += 1 }
when ($self->{controls}->{keyboard}->{l}) { $v_x += -1 }
when ($self->{controls}->{keyboard}->{r}) { $v_x += 1 }
when ($self->{controls}->{keyboard}->{b}) { $bomb = 1 }
}
$self->{hero}->move( $v_x, $v_y, $dt, $app );
$self->{hero}->self_destruct if ($bomb);
$self->{hero}->shoot( $dt, $self->{mouse}->{x}, $self->{mouse}->{y} ) if ($self->{mouse}->{firing});
for my $bullet (@{$self->{hero}->{bullets}}) { $bullet->move( $dt, $app ) }
}
There's a little more going on here. First we go through the keys which were set
in the event handler and add 1 (since we end up manipulating this unit vector
with an actual velocity later) to X/Y velocity for each control. Simply setting
them to 1/-1 means that the hero will still move when opposite controls are being
pressed, so we add them to prevent this: 1 and -1 being added will cancel each
other out. Ignore the $bomb
stuff, that's in the "notion" stage of development.
$self->{hero}
is an instance of Game::Repliconz::Guy
. Let's take a look at its
move method:
sub move {
my ( $self, $v_x, $v_y, $dt, $app ) = @_;
($v_x, $v_y) = constrain_velocity_xy($v_x, $v_y);
$self->{x} += $v_x * $self->{velocity} * $dt;
$self->{y} += $v_y * $self->{velocity} * $dt;
($self->{x} < 0) && ($self->{x} = 0);
($self->{x} > ($app->w - $self->{w})) && ($self->{x} = $app->w - $self->{w});
($self->{y} < 0) && ($self->{y} = 0);
($self->{y} > ($app->h - $self->{h})) && ($self->{y} = $app->h - $self->{h});
}
The moving itself is taken care of by setting our new coords to our unit vector coords multiplied by our velocity and delta-T, the time that has passed. This is the key to getting consistent movement even if timing / framerate changes. You can check this out by reducing the delay in app setup to effectively increase framerate. You can also manipulate $dt in your callbacks to speed up or slow down the action. For example, to add a slo-mo mode you could divide $dt by 2.
The last four lines reset our position if the guy tries to move outside the bounds
of the play field. What is constrain_velocity_xy
up to?
sub constrain_velocity_xy {
my ( $v_x, $v_y ) = @_;
($v_y > 0) && ($v_y = 1);
($v_y < 0) && ($v_y = -1);
($v_x > 0) && ($v_x = 1);
($v_x < 0) && ($v_x = -1);
# Moving diagonally, moderate eventual velocity
if ( $v_y != 0 && $v_x != 0 ) {
$v_y *= 0.707; # sin(45 degrees)
$v_x *= 0.707;
}
return ($v_x, $v_y);
}
There are a couple of things going on here. The first is we reset the bounds of our unit vector set in the move handler callback. Since there are two sets of controls and we used an 'additive' approach to setting the movement, if you press two buttons for the same direction, you end up with twice the required vector length and, eventually, twice the velocity. If any direction is set, we reset it to 1 or -1.
The other thing is, if we are moving diagonally, our vector is no longer length 1, it's the length required to go from corner to corner of a square with sides of length 1. Pythagoras tells us this is ~1.41. To correct this and set our vector length back to 1, we reduce the coords of our vector end point to ~0.7 (sine of 45°) of their original value. This diagram on Mathematics For Blondes illustrates quite effectively why this works, I think.
If you watched the video above, you saw that the guy wasn't the only thing moving.
We also have bullets. Bullets are instances of Game::Repliconz::Bullet
, which are
pushed in and out of a queue (currently arbitrarily limited to 20 items) in our
instance of Game::Repliconz::Guy
when shoot
is triggered by the fire button:
sub shoot {
my ( $self, $dt, $mouse_x, $mouse_y ) = @_;
state $total_dt = 0;
$total_dt += $dt;
return unless ($total_dt >= $self->{cooling_time});
$total_dt -= $self->{cooling_time};
push @{$self->{bullets}}, Game::Repliconz::Bullet->new( {
guy => $self,
target_x => $mouse_x,
target_y => $mouse_y
});
shift @{$self->{bullets}} if (scalar @{$self->{bullets}} > $self->{max_bullets});
}
We track time elapsed by adding delta-T values until our gun cooling time is reached. This allows us to easily control rate-of-fire at any stage, to implement a high ROF bonus by just reducing the cooling time, for example.
We tell the bullet about the guy and mouse position, but how do bullets aim in the mouse cursor direction? Another unit vector is created on instantiation, based on the guy's position and the mouse cursor position:
sub new {
my ( $class, $opts ) = @_;
$opts->{x} = $opts->{guy}->{x} + ($opts->{guy}->{w} / 2);
$opts->{y} = $opts->{guy}->{y} + ($opts->{guy}->{h} / 2);
$opts->{w} = 4;
$opts->{h} = 4;
# normalise vector : guy position -> target position
$opts->{v_y} = $opts->{target_y} - $opts->{y};
$opts->{v_x} = $opts->{target_x} - $opts->{x};
my $v_len = sqrt($opts->{v_y} ** 2 + $opts->{v_x} ** 2);
$opts->{v_y} /= $v_len;
$opts->{v_x} /= $v_len;
$opts->{colour} = 0xFFFFFFFF;
$opts->{velocity} = 70;
bless $opts, $class;
}
So we set the initial x/y of our bullet to be the centre of the guy, along with width and height for drawing later. We then make use of Pythagoras' theorem again to convert the difference between the start point and mouse position into a unit vector. If you consider the X and Y axes of our coords to be the sides of a right triangle, adding their squares will give us the length of the vector guy -> cursor. We just need to divide our coords by this value to wind up with a vector of length 1.
Once you have all this information, the move method becomes very simple:
sub move {
my ( $self, $dt, $app ) = @_;
$self->{x} += $self->{v_x} * $self->{velocity} * $dt;
$self->{y} += $self->{v_y} * $self->{velocity} * $dt;
}
Now that we know where everything is (or is going to be), the next step is
drawing. Our show handler is pretty naive, simply blanking the screen and
calling draw
for each of our objects:
sub show {
my ( $self, $dt, $app ) = @_;
SDL::Video::fill_rect( $app, SDL::Rect->new(0, 0, $app->w, $app->h), 0 );
$self->{hero}->draw($app);
for my $bullet (@{$self->{hero}->{bullets}}) { $bullet->draw($app) }
$app->update();
}
The draw
method is inherited from Game::Repliconz::Thing
:
sub draw {
my ( $self, $app ) = @_;
$app->draw_rect( [ $self->{x}, $self->{y}, $self->{w}, $self->{h} ], $self->{colour} );
}
We simply draw a rect at x, y, of size w, h of the given colour for each instance directly onto a passed SDLx::App instance.
Doesn't get simpler than that, I think. I hope you found this interesting and/or useful. Comments, criticism and contributions welcome. You can check out the code for Repliconz on Github.
The Wolfire Games Blog features a couple of cool posts on linear algebra with a focus specifically on game development:
Linear algebra for game developers ~ part 1
Linear algebra for game developers ~ part 2
Just a short note on FreeBSD's newsyslog and fail2ban, since I couldn't find this information on the interwebtubes.
Fail2ban does not respond to HUP/USR1 (or any other) signal to notify config or log changes. This sort of thing is achieved using fail2ban-client.
Newsyslog is geared towards the sending of signals, since that's what a *nix daemon would traditionally expect. Recently patched and working in newsyslog is the R flag, which allows you to provide a path to some executable instead of a PID file. Config for fail2ban in /etc/newsyslog.conf now looks like this:
# logfilename mode count size when flags [/pid_file] [sig_num]
/var/log/fail2ban.log 600 5 500 $W0D5 JR /usr/local/bin/fail2ban-logrotate.sh
Contents of /usr/local/bin/fail2ban-logrotate.sh are simply:
#!/bin/sh
/usr/local/bin/fail2ban-client set logtarget /var/log/fail2ban.log >/dev/null
Something doesn't sit right about this approach but it appears to do the business. Better ideas welcome in the comments.
OpenBSD's newsyslog appears to allow any free form command as long as it's wrapped in double quotes. Handy.
After reading about chromatic's Mojolicious::Plugin::UnicodeNormalize I thought "I could have a crack at that" and created a similar thing for my own framework of choice, Dancer2. Having never written any code that might possibly be of any utility to another human being, I have never uploaded anything to CPAN. Anyway, now there's Dancer2::Plugin::UnicodeNormalize.
Though that's not as interesting as the cool things about uploading to CPAN. The first cool thing is how simple it is with Dist::Zilla which pretty much takes care of a whole load of stuff you used to have to do by hand. It has a plugin system to do neat things like Git integration (version tagging, changelog generation and such). I wish a similar thing existed for autotools (maybe it does) but the last thing that project needs is more scripts.
The second cool thing is CPAN Testers. Anyone familiar with Perl will probably know about this already, but I think it warrants mentioning at every possible opportunity... the family finds it a drag at Christmas dinner, but I will not stop. CPAN Testers is an integration framework in which a team of excellent volunteers will build and test your code on a dizzying variety of platforms. I don't have any MacOS or Windows installs here, but I know my code builds, runs and passes all tests on these platforms.
If only you could find this out before releasing the distribution... Oh, you can. -TRIAL releases are not
indexed for general release on CPAN (they show up as developer releases), though they are tested by the CPAN Testers.
Releasing a trial with Dist::Zilla is as simple as dzil release --trial
. Give it a couple of days
and you'll have access to dozens (or even hundreds) of verbose test logs. Failing test results are mailed to you.
It's a bit like Travis CI and the like, but for releases. And it requires no action
on your part, you just get it for free, whether you like it or not. But you'll like it.
Criticise or contribute to Dancer2::Plugin::UnicodeNormalize on Github. This post is longer than the code.