├── t ├── template │ ├── share │ │ ├── text │ │ │ └── README │ │ ├── css │ │ │ └── default.css │ │ └── javascript │ │ │ └── default.js │ ├── t │ │ └── Example.test │ ├── lib │ │ └── DDG │ │ │ └── Default.pm │ └── templates.yml ├── load.t ├── lib │ └── DDG │ │ ├── Goodie │ │ └── TwoShoes.pm │ │ └── Spice │ │ └── NagaBhutJolokiaDax.pm ├── app_duckpan.t ├── system_duckpan.t └── templates.t ├── .gitignore ├── lib └── App │ ├── DuckPAN │ ├── HasApp.pm │ ├── Option │ │ └── Global.pm │ ├── Cmd │ │ ├── Env │ │ │ ├── Cmd │ │ │ │ ├── Help.pm │ │ │ │ ├── Get.pm │ │ │ │ ├── List.pm │ │ │ │ ├── Rm.pm │ │ │ │ └── Set.pm │ │ │ └── Cmd.pm │ │ ├── Help.pm │ │ ├── Check.pm │ │ ├── Install.pm │ │ ├── Query.pm │ │ ├── Installdeps.pm │ │ ├── Roadrunner.pm │ │ ├── Env.pm │ │ ├── Publisher.pm │ │ ├── Test.pm │ │ ├── New.pm │ │ └── Server.pm │ ├── Cmd.pm │ ├── Config.pm │ ├── Template.pm │ ├── WebPublisher.pm │ ├── WebStatic.pm │ ├── TemplateDefinitions.pm │ ├── Query.pm │ ├── TemplateSet.pm │ ├── Restart.pm │ ├── Fathead.pm │ ├── DDG.pm │ ├── Perl.pm │ └── Web.pm │ └── DuckPAN.pm ├── weaver.ini ├── duckpan_pr_template.md ├── .travis.yml ├── dist.ini ├── share └── template_compiler.js ├── CONTRIBUTING.md ├── bin └── duckpan ├── LICENSE.md └── README.md /t/template/share/text/README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/template/t/Example.test: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/template/share/css/default.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/template/share/javascript/default.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | App-DuckPAN-* 2 | .build 3 | *.sw* 4 | *~ 5 | -------------------------------------------------------------------------------- /t/template/lib/DDG/Default.pm: -------------------------------------------------------------------------------- 1 | package <:$package_name:>; 2 | my $id = '<:$ia_id:>'; 3 | -------------------------------------------------------------------------------- /t/load.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use Test::LoadAllModules; 6 | 7 | BEGIN { 8 | all_uses_ok( search_path => 'App::DuckPAN', except => [ 'App::DuckPAN::Web' ] ); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /t/lib/DDG/Goodie/TwoShoes.pm: -------------------------------------------------------------------------------- 1 | package DDG::Goodie::TwoShoes; 2 | 3 | use strict; use warnings; 4 | use DDG::Goodie; 5 | 6 | triggers start => //; 7 | 8 | handle remainder => sub { 9 | return; 10 | }; 11 | 12 | 1; 13 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/HasApp.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::HasApp; 2 | # ABSTRACT: Simple role for classes which carry an object of App::DuckPAN 3 | 4 | use Moo::Role; 5 | 6 | has app => ( 7 | is => 'rw', 8 | required => 1, 9 | ); 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /t/lib/DDG/Spice/NagaBhutJolokiaDax.pm: -------------------------------------------------------------------------------- 1 | package DDG::Spice::NagaBhutJolokiaDax; 2 | 3 | use strict; use warnings; 4 | use DDG::Spice; 5 | 6 | triggers start => //; 7 | spice to => //; 8 | 9 | handle remainder => sub { 10 | return; 11 | }; 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Option/Global.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Option::Global; 2 | # ABSTRACT: Commands that can be run from anywhere. 3 | 4 | use Moo::Role; 5 | 6 | with qw( App::DuckPAN::Cmd ); 7 | 8 | around initialize => sub { return; }; 9 | 10 | 1; 11 | 12 | __END__ 13 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env/Cmd/Help.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env::Cmd::Help; 2 | # ABSTRACT: List commands and usage 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd::Env::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ( $self ) = @_; 11 | $self->env->help(); 12 | } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Help.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Help; 2 | # ABSTRACT: Launch help page 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Option::Global ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | use Pod::Usage qw(pod2usage); 9 | 10 | sub run { 11 | my ($self, $short_output) = @_; 12 | 13 | 14 | pod2usage(-verbose => 2) unless $short_output; 15 | pod2usage(-verbose => 1); 16 | } 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /weaver.ini: -------------------------------------------------------------------------------- 1 | [@CorePrep] 2 | 3 | [Name] 4 | [Version] 5 | 6 | [Region / prelude] 7 | 8 | [Generic / SYNOPSIS] 9 | [Generic / DESCRIPTION] 10 | [Generic / OVERVIEW] 11 | 12 | [Collect / EXPORTS FUNCTIONS] 13 | command = keyword 14 | 15 | [Collect / ATTRIBUTES] 16 | command = attr 17 | 18 | [Collect / METHODS] 19 | command = method 20 | 21 | [Leftovers] 22 | 23 | [Region / postlude] 24 | 25 | [Authors] 26 | 27 | [Legal] 28 | 29 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Check.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Check; 2 | # ABSTRACT: Command for checking the requirements 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ($self) = @_; 11 | 12 | $self->app->empty_cache; 13 | $self->app->check_requirements; # Exits on missing requirements. 14 | $self->app->emit_info("EVERYTHING OK! You can now go hacking! :)"); 15 | exit 0; 16 | } 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd; 2 | # ABSTRACT: Base class for commands of DuckPAN 3 | 4 | use Moo::Role; 5 | 6 | requires 'run'; 7 | 8 | has app => ( 9 | is => 'rw', 10 | ); 11 | 12 | sub initialize { 13 | my $self = shift; 14 | $self->app->initialize_working_directory(); 15 | } 16 | 17 | sub execute { 18 | my ( $self, $args, $chain ) = @_; 19 | my $app = shift @{$chain}; 20 | $self->app($app); 21 | $self->initialize(); 22 | $self->run(@{$args}); 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env/Cmd.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env::Cmd; 2 | # ABSTRACT: Base class for Env commands 3 | 4 | use Moo::Role; 5 | 6 | requires 'run'; 7 | 8 | has env => ( 9 | is => 'rw', 10 | ); 11 | 12 | has root => ( 13 | is => 'rw', 14 | ); 15 | 16 | sub execute { 17 | my ( $self, $args, $chain ) = @_; 18 | my $root = shift @{$chain}; 19 | $self->root($root); 20 | my $env = shift @{$chain}; 21 | $self->env($env); 22 | $self->run(@{$args}); 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env/Cmd/Get.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env::Cmd::Get; 2 | # ABSTRACT: Gets the specified env variable 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd::Env::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ($self, $name) = @_; 11 | $self->env->help("") if !$name; 12 | my $data = $self->env->load_env_ini; 13 | $name = uc $name; 14 | $data->{$name} ? $self->root->emit_info("export ". $name ."=". $data->{$name}) : $self->root->emit_error("'". $name ."' is not set!"); 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env/Cmd/List.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env::Cmd::List; 2 | # ABSTRACT: List all env variables 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd::Env::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ( $self ) = @_; 11 | my $data = $self->env->load_env_ini; 12 | if (keys %{$data}) { 13 | $self->root->emit_info("export ". $_ ."=". $data->{$_} ) for (sort keys %{$data}); 14 | } 15 | else { 16 | $self->root->emit_notice("There are no env variables set currently."); 17 | } 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /duckpan_pr_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request Template 2 | 3 | We ask that you please use this template when submitting a pull request so we can better understand your changes and help you along when necessary. 4 | 5 | **Which issues (if any) does this change solve? (please link them here)** 6 | 7 | 8 | **Briefly describe your fix. Are there any important details we should know about?** 9 | 10 | 11 | **Are you having any problems? Do you need our help with anything?** 12 | 13 | 14 | **Where did you hear about DuckDuckHack? (For first time contributors)** 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Install.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Install; 2 | # ABSTRACT: Install the distribution in current directory 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ( $self, @args ) = @_; 11 | 12 | if (-f 'dist.ini') { 13 | $self->app->emit_info("Found a dist.ini, suggesting a Dist::Zilla distribution"); 14 | 15 | $self->app->perl->cpanminus_install_error 16 | if (system("dzil install --install-command 'cpanm .' @args")); 17 | $self->app->emit_info("Everything fine!"); 18 | } 19 | 20 | } 21 | 22 | 1; 23 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env/Cmd/Rm.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env::Cmd::Rm; 2 | # ABSTRACT: Removes the specified env variable 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd::Env::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ( $self, $name ) = @_; 11 | $self->env->help("") if !$name; 12 | my $data = $self->env->load_env_ini; 13 | $name = uc $name; 14 | defined $data->{$name} ? delete $data->{$name} && $self->root->emit_info("Successfully removed '". $name ."'!") : $self->root->emit_error("'". $name ."' not found!"); 15 | $self->env->save_env_ini($data); 16 | } 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Query.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Query; 2 | # ABSTRACT: Command line tool for testing queries and see triggered instant answers 3 | 4 | use MooX; 5 | with qw( App::DuckPAN::Cmd App::DuckPAN::Restart ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ($self, @args) = @_; 11 | 12 | exit $self->run_restarter(\@args); 13 | } 14 | 15 | sub _run_app { 16 | my ($self, $args) = @_; 17 | 18 | $self->app->check_requirements; # Will exit if missing 19 | my @blocks = @{$self->app->ddg->get_blocks_from_current_dir(@$args)}; 20 | 21 | require App::DuckPAN::Query; 22 | App::DuckPAN::Query->run($self->app, \@blocks); 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | before_install: 4 | - export HARNESS_OPTIONS=j10:c HARNESS_TIMER=1 5 | - git config --global user.name "Dist Zilla Plugin TravisCI" 6 | - git config --global user.email $HOSTNAME":not-for-mail@travis-ci.org" 7 | install: 8 | - cpanm --quiet --notest --skip-installed Dist::Zilla 9 | - dzil authordeps | grep -ve '^\W' | xargs -n 5 -P 10 cpanm --quiet --notest --skip-installed --mirror http://www.cpan.org/ --mirror http://duckpan.org 10 | - dzil listdeps | grep -ve '^\W' | cpanm --quiet --notest --skip-installed --mirror http://www.cpan.org/ --mirror http://duckpan.org 11 | language: perl 12 | perl: 13 | - '5.16' 14 | script: 15 | - dzil smoke --release --author 16 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env/Cmd/Set.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env::Cmd::Set; 2 | # ABSTRACT: Sets the specified env variable value 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd::Env::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ($self, $name, @params) = @_; 11 | $self->env->help(" ") if !@params || !$name; 12 | my $data = $self->env->load_env_ini; 13 | $name = uc $name; 14 | $data->{$name} = join(" ", @params); 15 | eval { $self->env->save_env_ini($data) }; 16 | $self->root->emit_and_exit(1,"Please ensure that you are passing a valid value for the variable '". $name ."'!") if $@; 17 | $self->root->emit_info("Successfully set '". $name ."=". $data->{$name} ."'!"); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Installdeps.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Installdeps; 2 | # ABSTRACT: Regular way to install requirements with tests 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | 9 | sub run { 10 | my ( $self, @args ) = @_; 11 | 12 | if (-f 'dist.ini') { 13 | $self->app->emit_info("Found a dist.ini, suggesting a Dist::Zilla distribution"); 14 | $self->app->perl->cpanminus_install_error 15 | if (system("dzil authordeps --missing 2>/dev/null | grep -ve '^\\W' | cpanm")); 16 | $self->app->perl->cpanminus_install_error 17 | if (system("dzil listdeps --missing 2>/dev/null | grep -ve '^\\W' | cpanm")); 18 | $self->app->emit_info("Everything fine!"); 19 | } 20 | 21 | } 22 | 23 | 1; 24 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Config.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Config; 2 | # ABSTRACT: Configuration class of the duckpan client 3 | 4 | use Moo; 5 | use File::HomeDir; 6 | use Path::Tiny; 7 | use Config::INI::Reader; 8 | use Config::INI::Writer; 9 | 10 | has config_path => ( 11 | is => 'ro', 12 | lazy => 1, 13 | default => sub { _path_for('config') }, 14 | ); 15 | has config_file => ( 16 | is => 'ro', 17 | lazy => 1, 18 | default => sub { shift->config_path->child('config.ini') }, 19 | ); 20 | has cache_path => ( 21 | is => 'ro', 22 | lazy => 1, 23 | default => sub { _path_for('cache') }, 24 | ); 25 | 26 | sub _path_for { 27 | my $which = shift; 28 | 29 | my $from_env = $ENV{'DUCKPAN_' . uc $which . '_PATH'}; 30 | my $path = ($from_env) ? path($from_env) : path(File::HomeDir->my_home, '.duckpan', lc $which); 31 | $path->mkpath unless $path->exists; 32 | return $path; 33 | } 34 | 35 | sub set_config { 36 | my ( $self, $config ) = @_; 37 | Config::INI::Writer->write_file($config,$self->config_file); 38 | } 39 | 40 | sub get_config { 41 | my ( $self ) = @_; 42 | return unless $self->config_file->is_file; 43 | Config::INI::Reader->read_file($self->config_file); 44 | } 45 | 46 | 1; 47 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = App-DuckPAN 2 | author = DuckDuckGo , Zach Thompson , Zaahir Moolla , Torsten Raudssus L 3 | license = Apache_2_0 4 | copyright_holder = DuckDuckGo, Inc. L 5 | copyright_year = 2013 6 | 7 | main_module = lib/App/DuckPAN.pm 8 | 9 | [PromptIfStale] 10 | index_base_url = http://duckpan.org 11 | module = Dist::Zilla::Plugin::UploadToDuckPAN 12 | 13 | [UploadToDuckPAN] 14 | 15 | [@Basic] 16 | 17 | [Git::NextVersion] 18 | version_regexp = ^([0-9]+)$ 19 | 20 | [PkgVersion] 21 | [MetaConfig] 22 | [MetaJSON] 23 | [PodSyntaxTests] 24 | [GithubMeta] 25 | [InstallRelease] 26 | install_command = cpanm . 27 | 28 | [Authority] 29 | authority = cpan:DDG 30 | do_metadata = 1 31 | 32 | [PodWeaver] 33 | 34 | [@Git] 35 | tag_format = %v 36 | push_to = origin master 37 | 38 | [AutoPrereqs] 39 | skip = ^DDG::(Spice|Goodie|Fathead|Longtail|Publisher) 40 | 41 | [MetaNoIndex] 42 | directory = t/ 43 | directory = xt/ 44 | directory = ex/ 45 | directory = inc/ 46 | 47 | [ChangelogFromGit] 48 | max_age = 99999 49 | tag_regexp = ^v(.+)$ 50 | file_name = Changes 51 | wrap_column = 74 52 | debug = 0 53 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Roadrunner.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Roadrunner; 2 | # ABSTRACT: Install requirements as fast as possible 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | use Time::HiRes qw( usleep ); 9 | 10 | sub run { 11 | my ( $self, @args ) = @_; 12 | 13 | if (-f 'dist.ini') { 14 | $self->app->emit_info("Found a dist.ini, suggesting a Dist::Zilla distribution"); 15 | $self->app->perl->cpanminus_install_error 16 | if (system("dzil authordeps --missing 2>/dev/null | grep -ve '^\\W' | cpanm --quiet --notest --skip-satisfied")); 17 | $self->app->perl->cpanminus_install_error 18 | if (system("dzil listdeps --missing 2>/dev/null | grep -ve '^\\W' | cpanm --quiet --notest --skip-satisfied")); 19 | $self->app->emit_info("Everything fine!"); 20 | } 21 | elsif (-f 'Makefile.PL') { 22 | $self->app->emit_info("Found a Makefile.PL"); 23 | $self->app->perl->cpanminus_install_error 24 | if (system("cpanm --quiet --notest --skip-satisfied --installdeps .")); 25 | } 26 | elsif (-f 'Build.PL') { 27 | $self->app->emit_info("Found a Build.PL"); 28 | $self->app->perl->cpanminus_install_error 29 | if (system("cpanm --quiet --notest --skip-satisfied --installdeps .")); 30 | } 31 | 32 | $self->app->emit_info("\a"); usleep 225000; $self->app->emit_info("\a"); 33 | 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Env.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Env; 2 | # ABSTRACT: Env command class 3 | 4 | use Moo; 5 | use MooX::Cmd; 6 | 7 | with qw( App::DuckPAN::Cmd ); 8 | 9 | use Config::INI; 10 | 11 | has env_ini => ( 12 | is => 'ro', 13 | lazy => 1, 14 | builder => 1, 15 | ); 16 | 17 | sub _build_env_ini { 18 | my ( $self ) = @_; 19 | $self->command_chain->[0]->root->cfg->config_path->child('env.ini') 20 | } 21 | 22 | sub load_env_ini { 23 | my ( $self ) = @_; 24 | if ($self->env_ini->is_file) { 25 | my $data = Config::INI::Reader->read_file($self->env_ini)->{_}; 26 | defined $data ? $data : {} 27 | } 28 | else { 29 | {} 30 | } 31 | } 32 | 33 | sub save_env_ini { 34 | my ( $self, $data ) = @_; 35 | Config::INI::Writer->write_file({ _ => $data }, $self->env_ini); 36 | } 37 | 38 | sub help { 39 | my ( $self, $cmd_input ) = @_; 40 | my $help_msg = "Available Commands:\n\t get: duckpan env get \n\t help: duckpan env help\n\t ". 41 | "list: duckpan env list\n\t rm: duckpan env rm \n\t set: duckpan env set "; 42 | 43 | if($cmd_input) { 44 | $self->command_chain->[0]->root->emit_and_exit(1, "Missing arguments!\n\t Usage:\tduckpan env ". $self->command_name ." ". $cmd_input) if $self->command_name; 45 | $self->app->emit_and_exit(1, "Command '". $cmd_input ."' not found\n". $help_msg); 46 | } 47 | 48 | $self->command_name ? $self->command_chain->[0]->root->emit_info($help_msg) : $self->app->emit_info($help_msg); 49 | } 50 | 51 | sub run { 52 | my ( $self, $name ) = @_; 53 | $self->help($name) if !$self->command_name; 54 | exit 0; 55 | } 56 | 57 | 1; 58 | -------------------------------------------------------------------------------- /lib/App/DuckPAN/Cmd/Publisher.pm: -------------------------------------------------------------------------------- 1 | package App::DuckPAN::Cmd::Publisher; 2 | # ABSTRACT: Starting up the publisher test webserver 3 | 4 | use Moo; 5 | with qw( App::DuckPAN::Cmd ); 6 | 7 | use MooX::Options protect_argv => 0; 8 | use Path::Tiny; 9 | use Plack::Handler::Starman; 10 | 11 | for (qw( duckduckgo dontbubbleus donttrackus duckduckhack )) { 12 | option $_ => ( 13 | is => 'ro', 14 | format => 's', 15 | predicate => 1, 16 | ); 17 | } 18 | 19 | sub run { 20 | my ( $self, @args ) = @_; 21 | 22 | $self->app->emit_info("Checking for Publisher..."); 23 | 24 | my $publisher_pm = path('lib','DDG','Publisher.pm'); 25 | $self->app->emit_and_exit(1, "You must be in the root of the duckduckgo-publisher repository") unless $publisher_pm->exists; 26 | 27 | $self->app->emit_info("Starting up publisher webserver...", "You can stop the webserver with Ctrl-C"); 28 | 29 | my %sites = ( 30 | duckduckgo => { 31 | port => 5000, 32 | url => $self->has_duckduckgo ? $self->duckduckgo : "http://duckduckgo.com/", 33 | }, 34 | dontbubbleus => { 35 | port => 5001, 36 | url => $self->has_dontbubbleus ? $self->dontbubbleus : "http://dontbubble.us/", 37 | }, 38 | donttrackus => { 39 | port => 5002, 40 | url => $self->has_donttrackus ? $self->donttrackus : "http://donttrack.us/", 41 | }, 42 | duckduckhack => { 43 | port => 5005, 44 | url => $self->has_duckduckhack ? $self->duckduckhack : "http://duckduckhack.com/", 45 | }, 46 | ); 47 | 48 | for (sort { $sites{$a}->{port} <=> $sites{$b}->{port} } keys %sites) { 49 | $self->app->emit_info("Serving on port ".$sites{$_}->{port}.": ".$sites{$_}->{url}); 50 | } 51 | 52 | require App::DuckPAN::WebPublisher; 53 | my $web = App::DuckPAN::WebPublisher->new( 54 | app => $self->app, 55 | sites => \%sites, 56 | ); 57 | my @ports = map { $sites{$_}->{port} } keys %sites; 58 | exit Plack::Handler::Starman->new(listen => [ map { ":$_" } @ports ])->run(sub { $web->run_psgi(@_) }); 59 | } 60 | 61 | 1; 62 | -------------------------------------------------------------------------------- /t/template/templates.yml: -------------------------------------------------------------------------------- 1 | --- 2 | templates: 3 | pm: 4 | label: Perl Module 5 | input: lib/DDG/Default.pm 6 | output: t/out/lib/DDG/<:$package_name:>.pm 7 | 8 | test: 9 | label: Perl Module Test 10 | # don't use a '.t' extension so as not to interfere with actual tests 11 | # in the distribution 12 | input: t/Example.test 13 | output: t/out/t/<:$package_name:>.test 14 | 15 | js: 16 | label: Javascript 17 | input: share/javascript/default.js 18 | output: t/out/share/javascript/<:$ia_id:>.js 19 | 20 | css: 21 | label: Javascript 22 | input: share/css/default.css 23 | output: t/out/share/css/<:$ia_id:>.css 24 | 25 | complex_out_dir: 26 | label: README file 27 | input: share/text/README 28 | output: t/out/share/text/<:$ia_id:>/docs/README 29 | 30 | no_input: 31 | label: File with non-existent input 32 | input: share/text/does_not_exist.txt 33 | output: t/out/share/text/<:$ia_id:>.txt 34 | 35 | no_write_perm: 36 | label: Output directory is not writeable 37 | input: share/text/README 38 | output: t/out/readonly/<:$ia_id:>.txt 39 | 40 | template_sets: 41 | all_optional: 42 | description: Template set with all optional templates 43 | optional: [ js, pm, test ] 44 | 45 | required_and_optional: 46 | description: Template set with required and optional templates 47 | required: [ pm, test ] 48 | optional: [ js, css ] 49 | 50 | errors: 51 | description: Template set with templates that can produce errors 52 | required: [ pm ] 53 | optional: [ no_input ] 54 | 55 | subdir_support_specified: 56 | description: Template which will not work if IAs are created inside a subdirectory 57 | required: [ pm ] 58 | subdir_support: false 59 | 60 | subdir_support_not_defined: 61 | description: Template which will work if IAs are created inside a subdirectory 62 | required: [ pm ] 63 | ... 64 | 65 | -------------------------------------------------------------------------------- /t/app_duckpan.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | use Path::Tiny; 7 | use Class::Load ':all'; 8 | 9 | delete $ENV{APP_DUCKPAN_SERVER_HOSTNAME}; 10 | 11 | use App::DuckPAN; 12 | 13 | my $version = $App::DuckPAN::VERSION || '9.999'; 14 | 15 | my $tempdir = Path::Tiny->tempdir(CLEANUP => 1); 16 | 17 | my $app = App::DuckPAN->new( 18 | config => $tempdir, 19 | ); 20 | 21 | isa_ok($app,'App::DuckPAN'); 22 | is($app->cfg->config_path->absolute,$tempdir,"Checking temp config path of App::DuckPAN"); 23 | isa_ok($app->http,'LWP::UserAgent'); 24 | is($app->server_hostname, 'duckduckgo.com','Checking for default server duckduckgo.com'); 25 | 26 | ############################################################### 27 | isa_ok($app->perl,'App::DuckPAN::Perl'); 28 | is($app->perl->get_local_version('App::DuckPAN'),$version,'Checking get_local_version of perl submodule'); 29 | 30 | ############################################################### 31 | isa_ok($app->cfg,'App::DuckPAN::Config'); 32 | 33 | ############################################################### 34 | # phrase_to_camel 35 | is $app->phrase_to_camel("DuckDuckGo"), "DuckDuckGo", "phrase to camel, from camel"; 36 | is $app->phrase_to_camel("Duck Duck Go"), "DuckDuckGo", "phrase to camel, without case change"; 37 | is $app->phrase_to_camel("duck duck Go"), "DuckDuckGo", "phrase to camel, with case changes"; 38 | is $app->phrase_to_camel("duck duckGo"), "DuckDuckGo", "phrase to camel, with variable spaces"; 39 | is $app->phrase_to_camel(" duck duck Go "), "DuckDuckGo", "phrase to camel, surrounded by spaces"; 40 | is $app->phrase_to_camel("duck::duck::Go"), "Duck::Duck::Go", "phrase to camel, with package"; 41 | is $app->phrase_to_camel("duck::duck Go"), "Duck::DuckGo", "phrase to camel, with package and space"; 42 | 43 | ############################################################### 44 | SKIP: { 45 | skip "No DDG installed yet", 2 unless try_load_class('DDG'); 46 | my $ddg_version = $DDG::VERSION || '9.999'; 47 | isa_ok($app->ddg,'App::DuckPAN::DDG'); 48 | is($app->get_local_ddg_version,$ddg_version,'Checking get_local_ddg_version'); 49 | } 50 | 51 | done_testing; 52 | -------------------------------------------------------------------------------- /share/template_compiler.js: -------------------------------------------------------------------------------- 1 | (function(env) { 2 | 3 | // grab original Spice.failed to be called later 4 | var oldSpiceFailed = Spice.failed; 5 | 6 | // Create div to collect our warnings 7 | $("#message").removeClass("is-hidden").append('
'); 8 | 9 | // define new Spice.failed which calls the original 10 | // and then notifies devs on the frontend 11 | env.Spice.failed = function (ia) { 12 | 13 | // First call original Spice.failed() 14 | oldSpiceFailed(ia); 15 | 16 | if (!ia){ 17 | console.log('[ERROR] Spice.failed() called without specifying Spice ID.'); 18 | } 19 | 20 | var errorMsg = 'Spice.failed() called by Spice with ID "' + ia + '".', 21 | $errorDiv = $('
').attr("class", "msg msg--warning").text(errorMsg); 22 | 23 | // Alert on frontend 24 | $('#spice-errors').append($errorDiv); 25 | 26 | // Alert in Console - just to be safe 27 | console.log('[NOTICE] ' + errorMsg); 28 | } 29 | }(this)); 30 | 31 | 32 | $(document).ready(function() { 33 | 34 | var $script, name, content; 35 | 36 | DDG.duckpan = true; 37 | 38 | // array of spice template ")->guts); 419 | } 420 | } 421 | # HTML Goodie 422 | # TODO Remove when all HTML Goodies return structured_answer 423 | else { 424 | $zci_container->push_content( 425 | HTML::TreeBuilder->new_from_content( 426 | q(
427 |
428 |
429 |
430 |
) 431 | )->guts 432 | ); 433 | my $zci_body = $zci_container->look_down(class => 'zci__body'); 434 | 435 | # Stick the answer inside $zci_body 436 | my $answer = $result->answer; 437 | if ($result->has_html) { 438 | my $tb = HTML::TreeBuilder->new(); 439 | # Specifically allow unknown tags to support and 440 | $tb->ignore_unknown(0); 441 | # Allow empty tags 442 | $tb->empty_element_tags(1); 443 | $answer = $tb->parse_content($result->html)->guts; 444 | } 445 | $zci_body->push_content($answer); 446 | } 447 | 448 | my $zci_wrapper = $root->look_down(id => "zero_click_wrapper"); 449 | $zci_wrapper->insert_element($zci_container); 450 | 451 | my $duckbar_home = $root->look_down(id => "duckbar_home"); 452 | $duckbar_home->delete_content(); 453 | $duckbar_home->attr(class => "zcm__menu"); 454 | $duckbar_home->push_content( 455 | HTML::TreeBuilder->new_from_content( 456 | q(
  • 457 | Answer 458 |
  • ) 459 | )->guts 460 | ); 461 | 462 | my $duckbar_static_sep = $root->look_down(id => "duckbar_static_sep"); 463 | $duckbar_static_sep->attr(class => "zcm__sep--h"); 464 | 465 | my $html = $root->look_down(_tag => "html"); 466 | $html->attr(class => "set-header--fixed has-zcm js no-touch csstransforms3d csstransitions svg use-opts has-active-zci"); 467 | 468 | # Make sure we only show one Goodie (this will change down the road) 469 | last; 470 | } 471 | if ($result_type eq 'other') { 472 | # Not Spice or Goodie, inject raw Dumper() output from into page 473 | 474 | my $content = $root->look_down(id => "bottom_spacing2"); 475 | my $dump = HTML::Element->new('pre'); 476 | $dump->push_content(Dumper $result); 477 | $content->insert_element($dump); 478 | $page = $root->as_HTML; 479 | } 480 | } 481 | 482 | # Setup various script tags for IAs that can template: 483 | # calls_script : js files 484 | # calls_nrj : proxied spice api calls or goodie future 485 | # calls_nrc : css calls 486 | # calls_template : handlebars templates 487 | 488 | my $calls_nrj = join('', map { 489 | DDG::Meta::Data->get_js(id => $_) 490 | || qq(DDH.$_=DDH.$_||{};DDH.$_.meta={"tab":"Answer", "id":"$_"};) 491 | } @ids); 492 | my $calls_script = join('', map { q|| } @calls_script); 493 | # For now we only allow a single goodie. If that changes, we will do the 494 | # same join/map as with spices. 495 | if(@calls_goodie){ 496 | my $goodie = shift @calls_goodie; 497 | $calls_nrj .= "DDG.duckbar.future_signal_tab({signal:'high',from:'$goodie->{id}'});", 498 | # Uncomment following line and remove "setTimeout" line when javascript race condition is addressed 499 | # $calls_script = q||; 500 | $calls_script .= q||; 501 | } 502 | elsif(@calls_fathead){ 503 | my $fathead = shift @calls_fathead; 504 | # $calls_nrj .= "DDG.duckbar.future_signal_tab({signal:'high',from:'$fathead->{id}'});", 505 | # Uncomment following line and remove "setTimeout" line when javascript race condition is addressed 506 | # $calls_script = q||; 507 | $calls_script .= q||; 508 | } 509 | else{ 510 | $calls_nrj .= @calls_nrj ? join(';', map { "nrj('".$_."')" } @calls_nrj) . ';' : ''; 511 | } 512 | my $calls_nrc = @calls_nrc ? join(';', map { "nrc('".$_."')" } @calls_nrc) . ';' : ''; 513 | 514 | if (%calls_template) { 515 | foreach my $spice_name ( keys %calls_template ){ 516 | $calls_script .= join("",map { 517 | my $template_name = $_; 518 | my $is_ct_self = $calls_template{$spice_name}{$template_name}{"is_ct_self"}; 519 | my $template_content = $calls_template{$spice_name}{$template_name}{"content"}->slurp; 520 | "" 521 | 522 | } keys %{ $calls_template{$spice_name} }); 523 | } 524 | } 525 | 526 | 527 | $self->_inject_mock_content($root); 528 | 529 | $page = $root->as_HTML; 530 | 531 | $page =~ s/####DUCKDUCKHACK-CALL-NRJ####/$calls_nrj/g; 532 | $page =~ s/####DUCKDUCKHACK-CALL-NRC####/$calls_nrc/g; 533 | $page =~ s/####DUCKDUCKHACK-CALL-SCRIPT####/$calls_script/g; 534 | 535 | $response->content_type('text/html'); 536 | $body = $page; 537 | 538 | } 539 | else { 540 | my $res = $self->ua->request(HTTP::Request->new(GET => "http://".$hostname.$request->request_uri)); 541 | if ($res->is_success) { 542 | $body = $res->decoded_content; 543 | $response->code($res->code); 544 | $response->content_type($res->content_type); 545 | } 546 | else { 547 | p($res->status_line, color => { string => 'red' }); 548 | $body = ""; 549 | } 550 | } 551 | 552 | $response->body($body); 553 | return $response; 554 | } 555 | 556 | sub _no_results_error { 557 | my ($self, $query) = @_; 558 | 559 | my $response = Plack::Response->new(200); 560 | $response->content_type('text/html'); 561 | my $error = "Sorry, no results were returned from Instant Answer"; 562 | my $root = HTML::TreeBuilder->new; 563 | 564 | $root->parse($self->page_root); 565 | my $text_field = $root->look_down( "name", "q" ); 566 | $text_field->attr( value => $query ); 567 | $root->find_by_tag_name('body')->push_content( 568 | HTML::TreeBuilder->new_from_content( 569 | qq() 570 | )->guts 571 | ); 572 | p($error, color => { string => 'red' }); 573 | 574 | my $body = $root->as_HTML; 575 | $response->body($body); 576 | return $response; 577 | } 578 | 579 | #inject some mock results into the SERP to make it look a little more real 580 | sub _inject_mock_content { 581 | 582 | my ($self, $root) = @_; 583 | 584 | # ensure results and ad containers exist 585 | my $ad_container = $root->look_down(id => "ads"); 586 | my $links_container = $root->look_down(id => "links"); 587 | return unless $ad_container && $links_container; 588 | 589 | #inject a mock ad into the page 590 | $ad_container->attr("style", "display: block"); 591 | 592 | $ad_container->push_content( 593 | HTML::TreeBuilder->new_from_content( 594 | q() 613 | )->guts 614 | ); 615 | 616 | #inject some mock ad into the page 617 | for (1..4){ 618 | $links_container->push_content( 619 | HTML::TreeBuilder->new_from_content( 620 | qq( 648 |
    ) 649 | )->guts 650 | ); 651 | } 652 | } 653 | 654 | 1; 655 | --------------------------------------------------------------------------------