编写DuckDuckGo即时答案非常简单

编辑注:本文中的一些信息已经过时,请参阅我们的新文章《编写DuckDuckGo插件变得更简单》以获取详细信息。

几周前,我参加了纽约市的Quack & Hack,并学习了如何编写DuckDuckGo即时答案。即时答案真的很酷:它们是在用户搜索特定术语时触发的微型应用程序。例如,如果您搜索help tmux,您将看到tmux速查表显示。这太棒了——您可以将代码提交到DuckDuckGo.com,而且好消息是您不必等到下一次Quack & Hack,就可以自己学习如何编写;DuckDuckGo提供了使编写变得简单的优秀工具。

设置开发环境

DuckDuckGo支持多种不同类型的即时答案,但今天我将重点介绍创建速查表,当用户搜索匹配的一组关键字时,搜索引擎会显示速查表。

要开始,您需要Perl 5.18或更高版本,并已安装App::DuckPAN,您可以使用cpancpanminus来完成。

$ cpan App::DuckPAN
# or
$ cpanm App::DuckPAN

您还需要DuckDuckGo的即时答案repo repo的本地副本,您可以使用Git进行克隆

$ git clone https://github.com/duckduckgo/zeroclickinfo-goodies.git

在安装了App::DuckPAN和goodies repo之后,进入zeroclickinfo-goodies repo,并启动duckpan服务器

$ cd zeroclickinfo-goodies
$ duckpan server

当您运行duckpan server时,可能会看到很多输出,但您应该看到这个

Checking asset cache...
Starting up webserver...
You can stop the webserver with Ctrl-C
HTTP::Server::PSGI: Accepting connections at http://0:5000/

如果您打开浏览器并导航到http://localhost:5000,您将看到DuckDuckGo搜索页面(如果localhost不起作用,请尝试http://0:5000)。搜索“help tmux”,您应该看到与实时网站上相同的即时答案速查表。

创建即时答案

现在,您已经设置了开发环境,您准备好创建即时答案了。我将为perldoc创建一个即时答案(取自我的perldoc 文章)。我可以通过使用duckpan new创建即时答案的骨架代码来提前开始。

$ duckpan new PerldocCheatSheet

这创建了即时答案所需的基本文件。

Created file: lib/DDG/Goodie/PerldocCheatSheet.pm
Created file: t/PerldocCheatSheet.t
Successfully created Goodie: PerldocCheatSheet

即时答案的所有逻辑都在PerldocCheatSheet.pm中,并且duckpan已经创建了一个很好的骨架。

package DDG::Goodie::PerldocCheatSheet;
# ABSTRACT: Write an abstract here
# Start at https://duck.co/duckduckhack/goodie_overview if you are new
# to instant answer development

use DDG::Goodie;

zci answer_type => "perldoc_cheat_sheeet";
zci is_cached   => 1;

# Metadata.  See https://duck.co/duckduckhack/metadata for help in filling out this section.
name "PerldocCheatSheeet";
description "Succinct explanation of what this instant answer does";
primary_example_queries "first example query", "second example query";
secondary_example_queries "optional -- demonstrate any additional triggers";
# Uncomment and complete: https://duck.co/duckduckhack/metadata#category
# category "";
# Uncomment and complete: https://duck.co/duckduckhack/metadata#topics
# topics "";
code_url "https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/PerldocCheatSheet.pm";
attribution github => ["GitHubAccount", "Friendly Name"],
            twitter => "twitterhandle";

# Triggers
triggers any => "triggerWord", "trigger phrase";

# Handle statement
handle remainder => sub {

    # optional - regex guard
    # return unless qr/^\w+/;

    return unless $_; # Guard against "no answer"

    return $_;
};

1;

我将填写摘要、元数据触发器的答案,以及handle子程序。

package DDG::Goodie::PerldocCheatSheet;
# ABSTRACT: A cheat sheet for perldoc, the Perl documentation program

use DDG::Goodie;

zci answer_type => "perldoc_cheat_sheet";
zci is_cached   => 1;

# Metadata
name "PerldocCheatSheet";
source "http://perltricks.com/article/155/2015/2/26/Hello-perldoc--productivity-booster";
description "A cheat sheet for perldoc, the Perl documentation program";
primary_example_queries "help perldoc", "perldoc cheatsheet", "perldoc commands", "perldoc ref";
category "programming";
topics qw/computing geek programming sysadmin/;
code_url
  "https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/PerldocCheatSheet.pm";
attribution github  => ["dnmfarrell", "David Farrell"],
            twitter => "perltricks",
            web     => 'http://perltricks.com';

# Triggers
triggers startend => (
        "perldoc",
        "perldoc help",
        "help perldoc",
        "perldoc cheat sheet",
        "perldoc cheatsheet",
        "perldoc commands",
        "perldoc ref");

# Handle statement
my $HTML = share("perldoc_cheat_sheet.html")->slurp(iomode => '<:encoding(UTF-8)');
my $TEXT= share("perldoc_cheat_sheet.txt")->slurp(iomode => '<:encoding(UTF-8)');

handle remainder => sub {
    return
        heading => 'Perldoc Cheat Sheet',
        html    => $HTML,
        answer  => $TEXT,
};

1;

handle子程序将返回给用户的纯文本和HTML版本的速查表。share函数从share/goodie/目录加载静态文件。这些文件应位于share/goodie/perldoc_cheat_sheet/目录中,并且必须确保文件名是即时答案名称的小写版本,由下划线分隔。因此,“PerldocCheatSheet”变为“perldoc_cheat_sheet”。您可以在GitHub上查看文件。请注意,CSS文件没有直接由任何代码引用:它被DuckDuckGo自动加载(这就是为什么目录和文件名必须正确的原因)。我从tmux 示例中复制了CSS,它提供了两列文本,将并排显示或根据屏幕宽度太窄而换行到单列。

测试即时答案

测试即时答案是否正常工作的最快方法是使用duckpan query命令。我可以在终端中运行它

$ duckpan query

这启动了一个交互式命令行程序。我可以输入我perldoc即时回答的触发器之一,看看服务器是否按预期响应。

Query: perldoc ref
  You entered: perldoc ref
---
DDG::ZeroClickInfo  {
    Parents       WWW::DuckDuckGo::ZeroClickInfo
    public methods (4) : DOES, has_structured_answer, new, structured_answer
    private methods (0)
    internals: {
        answer        "perldoc [option]

Module Options
--------------
...

看起来不错!(我已经剪掉了输出,因为它很冗长)。接下来我可以尝试使用duckpan server进行浏览器测试。

$ duckpan server

然后我将我的浏览器指向http://localhost:5000,输入即时回答的触发查询。这也行得通。最后,我需要完成即时回答的单元测试脚本。我已经有了由duckpan new在开始时创建的测试脚本框架。

#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use DDG::Test::Goodie;

zci answer_type => "perldoc_cheat_sheet";
zci is_cached   => 1;

ddg_goodie_test(
    [qw( DDG::Goodie::PerldocCheatSheeet )],
    # At a minimum, be sure to include tests for all:
    # - primary_example_queries
    # - secondary_example_queries
    'example query' => test_zci('query'),
    # Try to include some examples of queries on which it might
    # appear that your answer will trigger, but does not.
    'bad example query' => undef,
);

done_testing;

我将更新测试文件,并添加一些注释。

#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use DDG::Test::Goodie;

zci answer_type => "perldoc_cheat_sheet";
zci is_cached   => 1;

# all responses for this goodie are the same
my @test_zci = (
  # regex for the plain text response
  qr/^perldoc \[option\].*Module Options.*Search Options.*Common Options.*Help.*$/s,
  # check the heading
  heading => 'Perldoc Cheat Sheet',
  # check the html pattern
  html    => qr#$#s,
);

ddg_goodie_test(
    # name of goodie to test
    ['DDG::Goodie::PerldocCheatSheet'],

    # At a minimum, be sure to include tests for all:
    # - primary_example_queries
    # - secondary_example_queries
    'help perldoc'        => test_zci(@test_zci),
    'help perldoc'        => test_zci(@test_zci),
    "perldoc"             => test_zci(@test_zci),
    "perldoc help"        => test_zci(@test_zci),
    "help perldoc"        => test_zci(@test_zci),
    "perldoc cheat sheet" => test_zci(@test_zci),
    "perldoc cheatsheet"  => test_zci(@test_zci),
    "perldoc commands"    => test_zci(@test_zci),

    # Try to include some examples of queries on which it might
    # appear that your answer will trigger, but does not.
    'perl doc help'     => undef,
    'perl documentaton' => undef,
    'perl faq'          => undef,
    'perl help'         => undef,
);

done_testing;

大部分都很容易理解;但也有一些需要注意的地方;@test_zci是一个存储成功触发即时回答的预期输出的变量。这是一个有点黑客式的解决方案:它传递给test_zci()函数,该函数期望一个与纯文本响应匹配的标量,后跟两个键值对,一个用于标题,一个用于HTML响应(更多详情请参见文档)。我可以在命令行运行此脚本。

$ prove -I t/PerldocCheatSheet.t
t/PerldocCheatSheet.t .. ok
All tests successful.
Files=1, Tests=12,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.17 cusr  0.01 csys =  0.20 CPU)
Result: PASS

所有测试都通过了,所以我准备向DuckDuckGo社区提交一个pull request!

寻求帮助的地方

虽然DuckDuckGo工具很棒,但也有一些好的文档和友好的社区在需要时支持开发。我在Gitter 聊天室上花了一些时间,即时回答仓库里的人都很友好、反应迅速(更重要的是,他们有commit权限:)。


这篇文章最初发布在PerlTricks.com上。

标签

David Farrell

David是一位职业程序员,他经常推文博客关于代码和编程艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开一个问题或pull request来帮助我们。