Created DDG::Goodie::VimCheatSheet +tests

master
Eric Johnson 2014-01-15 19:20:30 +00:00
parent 42a81dab94
commit b2b2c56cda
8 changed files with 999 additions and 0 deletions

View File

@ -56,6 +56,9 @@ Lingua::EN::Words2Nums = 0
Locale::Currency::Format = 1.30
Net::IP = 0
Math::BaseConvert = 0
Text::Xslate = 3.1.0
JSON = 2.90
Web::Scraper = 0.37
[Prereqs / TestRequires]
Test::More = 0.98

View File

@ -0,0 +1,125 @@
package DDG::Goodie::VimCheatSheet;
# ABSTRACT: Provide a cheatsheet for common vim syntax
use HTML::Entities;
use DDG::Goodie;
use Text::Xslate;
use JSON;
zci answer_type => "vim_cheat";
name "VimCheatSheet";
description "Vim cheat sheet";
source "http://rtorruellas.com/vim-cheat-sheet/";
code_url "https://github.com/duckduckgo/zeroclickinfo-spice/blob/master/lib/DDG/Goodie/VimCheatSheet.pm";
category "cheat_sheets";
topics "computing", "geek", "programming", "sysadmin";
primary_example_queries 'vim help';
secondary_example_queries 'vi quick reference';
triggers startend =>
'vi cheat sheet',
'vi cheatsheet',
'vi commands',
'vi guide',
'vi help',
'vi quick reference',
'vi reference',
'vim cheat sheet',
'vim cheatsheet',
'vim commands',
'vim guide',
'vim help',
'vim quick reference',
'vim reference',
;
attribution github => ["kablamo", "Eric Johnson"],
twitter => ["kablamo_", "Eric Johnson"],
web => ["http://kablamo.org", "Eric Johnson"];
handle remainder => sub {
# retrieve data from a file
my $json = share("vimcheat.json")->slurp(iomode => '<:encoding(UTF-8)');
my $columns = JSON->new->utf8->decode($json);
return
heading => 'Vim Cheat Sheet',
html => html($columns),
answer => answer($columns);
};
# Generate an html answer from $columns. Returns a string.
sub html {
my $columns = shift;
my $css = share("style.css")->slurp;
my $html = "<style type='text/css'>$css</style>\n";
my $template = template();
my $vars = { columns => $columns };
$html .= Text::Xslate->new()->render_string($template, $vars);
return $html;
}
sub template {
return <<'EOF';
<div id="vim-container">
: for $columns -> $tables {
<div class="vim-column">
: for $tables -> $table {
<b><: $table.name :></b>
<table class="vim-table">
: for $table.rows -> $row {
<tr>
<td>
: for $row.cmds -> $cmd {
<code><: $cmd :></code><br>
: }
</td>
<td><: $row.help :></td>
</tr>
: }
</table>
: }
</div>
: }
</div>
EOF
}
# Generate a plain text answer from $columns. Returns a string.
sub answer {
my $columns = shift;
my $answer = '';
foreach my $tables (@$columns) {
foreach my $table (@$tables) {
$answer .= $table->{name} . "\n\n";
foreach my $row (@{ $table->{rows} }) {
my $cmds = $row->{cmds};
my $left = join ' or ', @$cmds;
$answer .= sprintf('%-35s ', $left);
$answer .= $row->{help};
$answer .= "\n";
}
$answer .= "\n";
}
$answer .= "\n";
}
return $answer;
}
1;

View File

@ -0,0 +1,14 @@
INTRODUCTION
parser.pl generates vimcheat.json which is used by Goodie::VimCheatSheet.
The data comes from:
https://github.com/rtorr/vim-cheat-sheet
INSTRUCTIONS
fetch.sh
parser.pl

View File

@ -0,0 +1,3 @@
#!/bin/bash
mkdir -p download
wget "http://raw2.github.com/rtorr/vim-cheat-sheet/develop/app/index.html" -O download/vimcheat.html

View File

@ -0,0 +1,111 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Web::Scraper;
use Path::Class;
use JSON;
{
my $data = parse_html();
my $tables = $data->{tables};
my $names = $data->{names};
cleanup_tables_data( $tables );
merge_names_and_tables( $names, $tables );
my $columns = split_into_two_columns( $tables );
my $json = JSON->new->pretty->utf8->encode( $columns );
file('vimcheat.json')->spew( iomode => '>:encoding(UTF-8)', $json );
}
# Returns a data structure which looks like:
#
# { names => ['Cursor movement', 'Insert Mode', ...],
# tables => [
# {rows => [ {help => 'h - move left'}, {help => 'j - move down'}, ... ]},
# {rows => [ {help => 'i - start insert mode'}, ... ]},
# ...
# ],
# }
#
sub parse_html {
my $scraper = scraper {
process "h2", "names[]" => 'TEXT';
process "ul", "tables[]" => scraper {
process "li", "rows[]" => scraper {
process "li", "help" => 'TEXT';
};
};
};
my $string = scalar file('download/vimcheat.html')
->slurp(iomode => '<:encoding(UTF-8)');
return $scraper->scrape($string);
}
# Alters each $table->{rows} so it looks like this:
#
# [ {cmds => ['h'], help => 'move left'},
# {cmds => ['j'], help => 'move down'},
# ...
# ]
#
sub cleanup_tables_data {
my $tables = shift;
foreach my $table (@$tables) {
foreach my $row (@{ $table->{rows} }) {
# $row->{help} looks like: "gt or :tabnext or :tabn - move to next tab"
# The code below puts everything to the right of the ' - ' into
# $row->{help} and everything to the left goes into $row->{cmds}
my ($left, $right) = split / - /, $row->{help};
my @cmds = split / or /, $left;
$row->{cmds} = \@cmds;
$row->{help} = $right;
}
}
}
# Alters $tables so it looks like this:
#
# [
# {name => 'Cursor movement', rows => [...]},
# {name => 'Insert Mode', rows => [...]},
# ...
# ]
#
sub merge_names_and_tables {
my ($names, $tables) = @_;
foreach my $table (@$tables) {
$table->{name} = shift @$names;
}
}
# Returns a data structure which looks like:
#
# [ $tables[0], $tables[1], ... ],
# [ $tables[x/2], $tables[x/2 + 1], ... ],
#
# Where x is the size of the array @tables
#
sub split_into_two_columns {
my $tables = shift;
my $length = scalar @$tables;
my $half = int( $length / 2 );
my @column1 = @$tables[0..$half];
my @column2 = @$tables[$half .. $length - 1];
return [ \@column1, \@column2 ];
}

View File

@ -0,0 +1,40 @@
#zero_click_plus_wrapper {
display: none;
}
#zero_click_wrapper #zero_click #zero_click_abstract {
padding-left: 0px !important;
margin-left: 0px !important;
margin-right: 0px !important;
}
#zero_click_abstract #vim-container {
max-height: 45ex;
overflow-y: scroll;
overflow-x: hidden;
}
#zero_click_abstract .vim-column {
width: 48%;
display: inline-block;
vertical-align: top;
}
#zero_click_abstract table.vim-table {
width: 100%;
margin-bottom: 1ex;
}
#zero_click_abstract table.vim-table td {
padding-right: 1ex;
vertical-align: top;
}
#zero_click_abstract table.vim-table td code {
white-space: nowrap;
}
@media (max-width: 380px) {
#zero_click_abstract .vim-column {
width: 100%;
}
}

View File

@ -0,0 +1,682 @@
[
[
{
"name" : "Cursor movement",
"rows" : [
{
"help" : "move left",
"cmds" : [
"h"
]
},
{
"cmds" : [
"j"
],
"help" : "move down"
},
{
"help" : "move up",
"cmds" : [
"k"
]
},
{
"cmds" : [
"l"
],
"help" : "move right"
},
{
"cmds" : [
"w"
],
"help" : "jump by start of words (punctuation considered words)"
},
{
"cmds" : [
"W"
],
"help" : "jump by words (spaces separate words)"
},
{
"cmds" : [
"e"
],
"help" : "jump to end of words (punctuation considered words)"
},
{
"cmds" : [
"E"
],
"help" : "jump to end of words (no punctuation)"
},
{
"cmds" : [
"b"
],
"help" : "jump backward by words (punctuation considered words)"
},
{
"cmds" : [
"B"
],
"help" : "jump backward by words (no punctuation)"
},
{
"cmds" : [
"0"
],
"help" : "(zero) start of line"
},
{
"cmds" : [
"^"
],
"help" : "first non-blank character of line"
},
{
"help" : "end of line",
"cmds" : [
"$"
]
},
{
"cmds" : [
"G"
],
"help" : "Go To command (prefix with number"
}
]
},
{
"name" : "Insert Mode - Inserting/Appending text",
"rows" : [
{
"cmds" : [
"i"
],
"help" : "start insert mode at cursor"
},
{
"cmds" : [
"I"
],
"help" : "insert at the beginning of the line"
},
{
"cmds" : [
"a"
],
"help" : "append after the cursor"
},
{
"help" : "append at the end of the line",
"cmds" : [
"A"
]
},
{
"help" : "open (append) blank line below current line (no need to press return)",
"cmds" : [
"o"
]
},
{
"cmds" : [
"O"
],
"help" : "open blank line above current line"
},
{
"help" : "append at end of word",
"cmds" : [
"ea"
]
},
{
"help" : "exit insert mode",
"cmds" : [
"Esc"
]
}
]
},
{
"name" : "Editing",
"rows" : [
{
"cmds" : [
"r"
],
"help" : "replace a single character (does not use insert mode)"
},
{
"cmds" : [
"J"
],
"help" : "join line below to the current one"
},
{
"cmds" : [
"cc"
],
"help" : "change (replace) an entire line"
},
{
"help" : "change (replace) to the end of word",
"cmds" : [
"cw"
]
},
{
"cmds" : [
"c$"
],
"help" : "change (replace) to the end of line"
},
{
"help" : "delete character at cursor and substitute text",
"cmds" : [
"s"
]
},
{
"help" : "delete line at cursor and substitute text (same as cc)",
"cmds" : [
"S"
]
},
{
"help" : "transpose two letter (delete and paste, technically)",
"cmds" : [
"xp"
]
},
{
"help" : "undo",
"cmds" : [
"u"
]
},
{
"help" : "redo",
"cmds" : [
"Ctrl + r"
]
},
{
"help" : "repeat last command",
"cmds" : [
"."
]
}
]
},
{
"rows" : [
{
"cmds" : [
"v"
],
"help" : "start visual mode, mark lines, then do command (such as y-yank)"
},
{
"help" : "start Linewise visual mode",
"cmds" : [
"V"
]
},
{
"help" : "move to other end of marked area",
"cmds" : [
"o"
]
},
{
"cmds" : [
"Ctrl + v"
],
"help" : "start visual block mode"
},
{
"help" : "move to Other corner of block",
"cmds" : [
"O"
]
},
{
"help" : "mark a word",
"cmds" : [
"aw"
]
},
{
"help" : "a () block (with braces)",
"cmds" : [
"ab"
]
},
{
"help" : "a {} block (with brackets)",
"cmds" : [
"aB"
]
},
{
"cmds" : [
"ib"
],
"help" : "inner () block"
},
{
"cmds" : [
"iB"
],
"help" : "inner {} block"
},
{
"help" : "exit visual mode",
"cmds" : [
"Esc"
]
}
],
"name" : "Marking text (visual mode)"
},
{
"rows" : [
{
"help" : "shift right",
"cmds" : [
">"
]
},
{
"cmds" : [
"<"
],
"help" : "shift left"
},
{
"help" : "yank (copy) marked text",
"cmds" : [
"y"
]
},
{
"cmds" : [
"d"
],
"help" : "delete marked text"
},
{
"help" : "switch case",
"cmds" : [
"~"
]
}
],
"name" : "Visual commands"
},
{
"rows" : [
{
"help" : "yank (copy) a line",
"cmds" : [
"yy"
]
},
{
"cmds" : [
"2yy"
],
"help" : "yank 2 lines"
},
{
"help" : "yank word",
"cmds" : [
"yw"
]
},
{
"cmds" : [
"y$"
],
"help" : "yank to end of line"
},
{
"help" : "put (paste) the clipboard after cursor",
"cmds" : [
"p"
]
},
{
"cmds" : [
"P"
],
"help" : "put (paste) before cursor"
},
{
"help" : "delete (cut) a line",
"cmds" : [
"dd"
]
},
{
"help" : "delete (cut) 2 lines",
"cmds" : [
"2dd"
]
},
{
"help" : "delete (cut) the current word",
"cmds" : [
"dw"
]
},
{
"cmds" : [
"D"
],
"help" : "delete (cut) to the end of line"
},
{
"help" : "delete (cut) to the end of the line",
"cmds" : [
"d$"
]
},
{
"cmds" : [
"x"
],
"help" : "delete (cut) current character"
}
],
"name" : "Cut and Paste"
}
],
[
{
"rows" : [
{
"help" : "yank (copy) a line",
"cmds" : [
"yy"
]
},
{
"cmds" : [
"2yy"
],
"help" : "yank 2 lines"
},
{
"help" : "yank word",
"cmds" : [
"yw"
]
},
{
"cmds" : [
"y$"
],
"help" : "yank to end of line"
},
{
"help" : "put (paste) the clipboard after cursor",
"cmds" : [
"p"
]
},
{
"cmds" : [
"P"
],
"help" : "put (paste) before cursor"
},
{
"help" : "delete (cut) a line",
"cmds" : [
"dd"
]
},
{
"help" : "delete (cut) 2 lines",
"cmds" : [
"2dd"
]
},
{
"help" : "delete (cut) the current word",
"cmds" : [
"dw"
]
},
{
"cmds" : [
"D"
],
"help" : "delete (cut) to the end of line"
},
{
"help" : "delete (cut) to the end of the line",
"cmds" : [
"d$"
]
},
{
"cmds" : [
"x"
],
"help" : "delete (cut) current character"
}
],
"name" : "Cut and Paste"
},
{
"rows" : [
{
"cmds" : [
":w"
],
"help" : "write (save) the file, but don't exit"
},
{
"cmds" : [
":wq"
],
"help" : "write (save) and quit"
},
{
"cmds" : [
":x"
],
"help" : "write (save) and quit"
},
{
"cmds" : [
":q"
],
"help" : "quit (fails if anything has changed)"
},
{
"cmds" : [
":q!"
],
"help" : "quit and throw away changes"
}
],
"name" : "Exiting"
},
{
"name" : "Search/Replace",
"rows" : [
{
"cmds" : [
"/pattern"
],
"help" : "search for pattern"
},
{
"help" : "search backward for pattern",
"cmds" : [
"?pattern"
]
},
{
"help" : "repeat search in same direction",
"cmds" : [
"n"
]
},
{
"help" : "repeat search in opposite direction",
"cmds" : [
"N"
]
},
{
"help" : "replace all old with new throughout file",
"cmds" : [
":%s/old/new/g"
]
},
{
"help" : "replace all old with new throughout file with confirmations",
"cmds" : [
":%s/old/new/gc"
]
}
]
},
{
"name" : "Working with multiple files",
"rows" : [
{
"cmds" : [
":e filename"
],
"help" : "Edit a file in a new buffer"
},
{
"cmds" : [
":bnext",
":bn"
],
"help" : "go to next buffer"
},
{
"cmds" : [
":bprev",
":bp"
],
"help" : "go to previous buffer"
},
{
"cmds" : [
":bd"
],
"help" : "delete a buffer (close a file)"
},
{
"help" : "Open a file in a new buffer and split window",
"cmds" : [
":sp filename"
]
},
{
"help" : "Open a file in a new buffer and vertically split window ",
"cmds" : [
":vsp filename"
]
},
{
"help" : "Split windows",
"cmds" : [
"Ctrl + ws"
]
},
{
"cmds" : [
"Ctrl + ww"
],
"help" : "switch windows"
},
{
"cmds" : [
"Ctrl + wq"
],
"help" : "Quit a window"
},
{
"cmds" : [
"Ctrl + wv"
],
"help" : "split windows vertically"
}
]
},
{
"rows" : [
{
"cmds" : [
":tabnew filename",
":tabn filename"
],
"help" : "open file in a new tab"
},
{
"help" : "move current split window into its own tab",
"cmds" : [
"Ctrl+W T"
]
},
{
"cmds" : [
"gt",
":tabnext",
":tabn"
],
"help" : "move to next tab"
},
{
"cmds" : [
"gT",
":tabprev",
":tabp"
],
"help" : "move to previous tab"
},
{
"cmds" : [
"#gt"
],
"help" : "move to tab number #"
},
{
"cmds" : [
":tabmove #"
],
"help" : "move current tab to the #th position (indexed from 0)"
},
{
"cmds" : [
":tabclose",
":tabc"
],
"help" : "close the current tab and all its windows"
},
{
"help" : "close all other tabs except for the current one",
"cmds" : [
":tabonly",
":tabo"
]
}
],
"name" : "Tabs"
}
]
]

21
t/VimCheatSheet.t Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use DDG::Test::Goodie;
use Carp::Always;
zci answer_type => 'vim_cheat';
ddg_goodie_test(
[ 'DDG::Goodie::VimCheatSheet' ],
'vim cheat sheet' => test_zci(
qr/^Cursor movement.*Insert Mode.*Editing.*Marking text.*/s,
heading => 'Vim Cheat Sheet',
html => qr#<div(.*<table.*<tr.*<td.*</table.*)+</div>$#s,
),
);
done_testing;