SnipMate Cheatsheets Generator
SnipMate Cheatsheets Generator
Although SnipMate is a powerful Vim plugin, I don’t use it as nearly often as I should. Mostly because I simply don’t remember all the delicious snippets that it brings at my fingertips. The solution to that silly situation is, fortunately, quite simple: I need a way to print cheatsheets for those snippet files.
Now, anybody with a sensible bone in their body would have written a small script to turn a snippets file into an html file and be done with it. Me, I saw an opportunity to work a little more on my Template::Caribou pet project.
Bottom-line: there is now the files snipmate_cheatsheet.pl
and
snipmate_index.pl
in Template::Caribou’s
repo. They can be used straight
from the repo checkout as follow:
# create one cheatsheet
$ perl -Ilib -Iexamples/lib
examples/snipmate_cheatsheet.pl ~/.vim/snippets/perl.snippets
# create'em all, with an index in bonus
$ mkdir cheatsheets
$ cd cheatsheets
$ perl -I../lib -I../examples/lib ../examples/snipmate_index.pl
And the result they should give you should be just like that.
If all you care about is the final product, you can already stop reading, hurry to go clone my repo and run the advertised scripts to generate your very own batch of ready-to-print cheatsheets. If you are curious to see what weird new twists I’ve added to my template system, and what the classes that generate the index and cheatsheets look like, soldier on.
What’s New, Caribou?
While no change was made to the core engine, I’ve sprinkled some additional sugar that should make the writing of tags a little easier.
In that regard, the most notable new feature is the ability to generate custom tags per-template. Because while
div { attr class => 'snippet';
div { attr class => 'header';
div { attr class => 'keyword';
$label;
};
div { attr class => 'comment';
$comment;
};
};
};
is already okay,
div_snippet {
div_header {
div_keyword { $label; };
div_comment { $comment; };
};
};
would be awesome. And now it’s totally possible by creating those special tags with
Template::Caribou::Tags
:
use Template::Caribou::Tags
mytag => {
-as => 'span_placeholder', class => 'placeholder', tag => 'span'
},
# if 'tag' is not given, defaults to 'div'
mytag => { -as => 'div_snippet', class => 'snippet' },
...
Along the same lines, I’ve also introduced some shortcut tag functions, css
and anchor
as part
of the collection provided by Template::Caribou::Tags::HTML
:
css <<'END_CSS';
body { color: magenta; }
END_CSS
anchor 'http://foo.com', 'a link';
anchor 'http://foo.com', sub { 'a ' . bold { 'link' } };
Nothing earth-shattering, as they are strictly equivalent to
style {
attr type => 'text/css';
"body { color: magenta; }";
}
a {
attr href => 'http://foo.com';
'a link';
};
a {
attr href => 'http://foo.com';
'a ' . bold { 'link' };
}
But I think it’s fair to say that they provide comfortable shorthands for their most common uses.
And, finally, although I won’t use it directly in this case, tag attributes can now be queried and appended to:
div {
... # lotsa stuff
attr '+class' => 'important' if attr('id') eq 'TheOne';
}
Cheatsheet Template
Enough shameless display of mechanics. Now, let’s put all those toys together to generate ourselves a pretty cheatsheet, shall we? First, package declaration and import of all important stuff:
package SnipMate::Snippets;
use 5.14.0;
use strict;
use warnings;
use Method::Signatures;
use Moose;
use MooseX::Types::Path::Class;
use Template::Caribou::Utils;
use Template::Caribou::Tags::HTML qw/ :all /;
use Template::Caribou::Tags
mytag => {
-as => 'span_placeholder', class => 'placeholder', tag => 'span'
},
map { ( mytag => { -as => "div_$_", class => $_ } ) } qw/
comment code snippet keyword snippets header
/
;
with 'Template::Caribou';
Notice that I just created the custom tags div_comment
, div_code
, etc,
on-the-fly. And if you catch yourself thinking that it would be a good idea to
have the -as parameter defaults to $tag_$class
, well, so do I. But I
didn’t find the proper way to twist Sub::Exporter to my bidding
there yet… Dark gods provide, it should be there for the next iteration though.
Once done with the preamble, we define a couple of attributes that are going to hold the snippet information:
has snippet_file => (
is => 'ro',
isa => 'Path::Class::File',
coerce => 1,
required => 1,
);
has snippets => (
is => 'ro',
lazy => 1,
builder => '_build_snippets',
);
Next comes the builder _build_snippets
, where we parse the snippets file.
It’s not the most elegant parsing ever done, but it’s serviceable. And it
comes with a bonus: comment lines with two ’#’ are seen as being section
names, useful for peeps like me who want to know which snippets are for
general Perl stuff, for Catalyst, for Dancer, etc.
method _build_snippets {
my @lines = $self->snippet_file->slurp;
my @all_snippets;
my $current_title;
my @current_snippets;
my $i = -1;
LINE:
while( my $line = $lines[++$i] ) {
if ( $line =~ s/^##s*(.*?)s*/$1/ ) {
if ( @current_snippets ) {
push @all_snippets,
[ $current_title => @current_snippets ];
}
$current_title = $line;
@current_snippets = ();
next LINE;
}
if ( $line =~ s/^s*snippets+(.*)// ) {
my $snippet = $1;
my $comment;
if ( $i > 0 and $lines[$i-1] =~ /^#(.*)/ ) {
$comment = $1;
}
my $code = $lines[++$i];
$code =~ s/^(s+)//;
my $spaces = $1;
$code .= $lines[$i] while $lines[++$i] =~ s/^$spaces//;
push @current_snippets, [ $snippet, $comment, $code ];
}
}
push @all_snippets, [ $current_title => @current_snippets ]
if @current_snippets;
return @all_snippets;
}
And after this, it’s templates all the way down. We first have the top-level template, the webpage itself:
template webpage => method {
html {
head {
show('style');
};
body {
h1 { $self->snippet_file->basename };
div_snippets {
show( 'section' => @$_ ) for @{ $self->snippets };
}
};
}
};
Then the individual sections:
template section => method( $title,@snippets ) {
h2 { $title } if $title;
show( 'snippet' => @$_ ) for @snippets;
};
Which are made of snippets:
template snippet => method ( $label, $comment, $code ) {
div_snippet {
div_header {
div_keyword { $label; };
div_comment { $comment; };
};
div_code sub {
my $regex = qr#(${d+.*?})#;
for ( split $regex, $code ) {
if ( /$regex/ ) {
span_placeholder { $_ };
}
else {
print $_;
}
}
};
}
};
And the last touch, the style sheet:
template style => sub {
css <<'END_CSS';
@page { size: landscape; }
body {
font-family: monospace;
}
h2 {
background-color:
darkblue; color: white;
padding: 3px;
text-align: center;
}
.header { border-bottom: 1px black solid; }
.keyword {
display: inline-block;
font-size: 1.5em;
}
.comment {
float: right;
font-style: italic;
}
.desc {
margin-top: 0.5em;
}
.code {
padding-left: 0.5em;
white-space: pre;
margin-top: 1em;
overflow: hidden;
}
.placeholder {
color: red;
}
.snippets {
-moz-column-count: 3;
-moz-column-gap: 20px;
-moz-column-rule: 1px solid black;
}
.snippet {
column-break-inside: avoid; /* doesn't work. boo */
display: inline-block; /* workaround for column-break suckiness */
width: 100%;
margin-bottom: 1em;
}
END_CSS
};
Incidentally, yes, it took me almost as much time figuring out how to use CSS3’s columns with Firefox than it took me to implement the rest. The Web is truly a horrible, horrible place.
For the index generator, it’s all of the same, only more simple still:
package SnipMate::Index;
use 5.14.0;
use strict;
use warnings;
use Method::Signatures;
use Moose;
use MooseX::Types::Path::Class;
use SnipMate::Snippets;
use Template::Caribou::Utils;
use Template::Caribou::Tags::HTML qw/ :all /;
with 'Template::Caribou';
has 'snippet_dir' => (
isa => 'Path::Class::Dir',
is => 'ro',
default => $ENV{HOME}.'/.vim/snippets',
coerce => 1,
);
has snippet_files => (
is => 'ro',
traits => [ 'Array' ],
lazy => 1,
default => sub {[
map { Path::Class::file($_) }
grep { /.snippets$/ }
$_[0]->snippet_dir->children
]},
handles => {
'all_snippet_files' => 'elements'
},
);
template webpage => method {
html { body { ul {
li {
anchor $_->basename.'.html' => $_->basename;
} for $self->all_snippet_files;
} } }
};
method generate_pages {
for ( $self->all_snippet_files ) {
generate_snippet_file( $_, $_->basename . '.html' );
}
}
sub generate_snippet_file {
my ( $src, $dest ) = @_;
open my $fh, '>', $dest or die $!;
print $fh SnipMate::Snippets->new( snippet_file => $src)
->render('webpage');
}
__PACKAGE__->meta->make_immutable;
1;
And that’s all there is to it. Nifty, isn’t?