Skip to content

URL, Devel::Declare and no strings attached

December 16, 2009

This Hacker News article caught my eye yesterday which pointed to a blog post about having a raw URL within a Ruby program using some clever shenanigans. (They you go, I saved you having to click through to find all the links yourself!)

Some examples from blog post:

# display this JSON request
puts http://github.com/defunkt.json

# => "He nose the truth."
require 'json'
url = http://twitter.com/statuses/show/6592721580.json
JSON.parse(url.to_s)['text']   

Very clever indeed. Of course there will be some edge cases but it does show you how far you can stretch Ruby. NB. Didn’t work in 1.9 for me but worked with minor warning in 1.8.2 (yes I know it old but thats what shipped with Mac OSX Tiger).

So how far can we stretch Perl? Well with Devel::Declare it can be stretched all the way:

use Modern::Perl;
use JSON qw(decode_json);
use URLDSL;

# print the json
say http://twitter.com/statuses/show/6592721580.json;

# => "He nose the truth."
say decode_json( http://twitter.com/statuses/show/6592721580.json )->{text};

So Perl also has no strings attached 😉

Devel::Declare allow us to extend the Perl syntax in a robust and easy fashion:

package URLDSL;
use Modern::Perl;
use Devel::Declare ();
use LWP::Simple ();
use base 'Devel::Declare::Context::Simple';

sub import {
    my $class  = shift;
    my $caller = caller;
    my $ctx    = __PACKAGE__->new;
    
    Devel::Declare->setup_for(
        $caller,
        { 
            http => { 
                const => sub { $ctx->parser(@_) },
            },
        },
    );
    
    no strict 'refs';
    *{$caller.'::http'} = sub ($) { LWP::Simple::get( $_[0] ) };
}

sub parser {
    my $self = shift;
    $self->init(@_);
    $self->skip_declarator;          # skip past "http"

    my $line = $self->get_linestr;   # get me current line of code
    my $pos  = $self->offset;        # position just after "http"
    my $url  = substr $line, $pos;   # url & everything after "http"
    
    for my $c (split //, $url) {
        # if blank, semicolon or closing parenthesis then no longer a URL
        last if $c eq q{ };
        last if $c eq q{;};
        last if $c eq q{)};
        $pos++;
    }    
    
    # wrap the url with http() sub and quotes
    substr( $line, $pos,          0 ) = q{")};
    substr( $line, $self->offset, 0 ) = q{("http};
    
    # pass back changes to parser
    $self->set_linestr( $line );

    return;
}

1;

 

Here is a low level synopsis of how it worked:

  • Devel::Declare tells the perl parser to call parser() when it hits http keyword
  • http://twitter.com/statuses/show/6592721580.json example triggers this (perl has no problem naturally distinguishing “http” keyword from “http://”)
  • Get line of code in question (my $line = $self->get_linestr; )
  • parser() skips along the URL data following “http” ($self->offset) until it reaches a space, semicolon or a closing bracket (this covers “most” cases though comma could be an issue). This marks the end of the URL data
  • We now in essence change http://twitter.com/statuses/show/6592721580.json into http("http://twitter.com/statuses/show/6592721580.json")
  • And plonk the changed line back to the parser ($self->set_linestr($line);) and let it continue along its merry way
  • NB. http() sub was already imported into calling space and returns a LWP::Simple::get of the requested URL

 

Now is URLDSL useful in the real world? It can screw up syntax highlighting a bit (though WordPress highlighter coped extremely well, unlike Github 😦

Its robust enough to cope with normal string interpolation:

my $id = '6592721580';
say decode_json( http://twitter.com/statuses/show/$id.json )->{text};

So perhaps I should upload it to CPAN? (earmarked for ACME:: perhaps!?). Anyone think this is useful little helper module then I wrap it up the next time I get a free moment.

The exercise as been fruitful in allowing me to get my head into the amazing Devel::Declare module for the first time.

I’ve used the Devel::Declare::Context::Simple class which simplifies things. Unfortunately there isn’t a POD on this yet but the main Devel::Declare docs nicely demonstrate the correct mechanics so its easy to adapt from this.

I also found this blog post by franck cuny to be extremely helpful. As was delving through the source code of other modules that used Devel::Declare

/I3az/

6 Comments leave one →
  1. December 16, 2009 6:33 pm

    I think this is a very good example on how to use Devel::Declare, so either a Acme:: or a patch to Devel::Declare to include this in the eg/ directory would be great.

  2. December 17, 2009 7:56 pm

    You can do the same sort of thing with autobox::Core.

    use Modern::Perl;
    use autobox::Core;
    use LWP::Simple;
    use JSON::Any;
    use Data::Dumper;

    sub autobox::Core::SCALAR::json {
    return JSON::Any->new->from_json( LWP::Simple::get( $_[0] ) ) if $_[0] =~ /^\w+:\//;
    }

    say Dumper “http://twitter.com/statuses/user_timeline/94882266.json”->json;

    • December 28, 2009 3:27 pm

      Blimey, I forgot my obligatory autobox solution 😉

      Many thanks for the example Glen. Unfortunately it does come with strings attached though 😦

      It could be rigged to use Devel::Declare to create something like (wordpress will corrupt the line below a little I’m afraid):

      my $json = http://transfixedbutnotdead.com->json;

      but my gut feel is that this is pushing the boat at a bit too far.

      /I3az/

Trackbacks

  1. Contributing to a project on Github « transfixed but not dead!
  2. Couple of CPAN pressies « transfixed but not dead!

Leave a comment