aboutsummaryrefslogtreecommitdiff
path: root/website/content/en/cgi/ports-autocomplete.cgi
diff options
context:
space:
mode:
Diffstat (limited to 'website/content/en/cgi/ports-autocomplete.cgi')
-rwxr-xr-xwebsite/content/en/cgi/ports-autocomplete.cgi219
1 files changed, 219 insertions, 0 deletions
diff --git a/website/content/en/cgi/ports-autocomplete.cgi b/website/content/en/cgi/ports-autocomplete.cgi
new file mode 100755
index 0000000000..438b1f3fbc
--- /dev/null
+++ b/website/content/en/cgi/ports-autocomplete.cgi
@@ -0,0 +1,219 @@
+#!/usr/local/bin/perl -T
+# Copyright (c) 2009-2023 Wolfram Schneider, https://wolfram.schneider.org
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# ports-autocomplete.cgi - autocomplete/suggestion service for FreeBSD ports
+#
+# For the OpenSearch protocol, please read https://en.wikipedia.org/wiki/OpenSearch
+#
+# expected run time on a modern CPU: ca. 16ms (10ms for perl, 6ms for GNU grep)
+
+use lib qw(. ../../lib);
+use MyCgiSimple;
+
+use strict;
+use warnings; # 3% slower
+
+$ENV{PATH} = "/usr/local/bin:/bin:/usr/bin";
+$ENV{'LANG'} = 'C';
+
+my $debug = 0;
+binmode( \*STDIN, ":bytes" );
+binmode( \*STDOUT, ":bytes" );
+binmode( \*STDERR, ":bytes" );
+
+sub suggestion {
+ my %args = @_;
+
+ my $database = $args{'database'};
+ my $icase = $args{'icase'};
+ my $stype = $args{'stype'};
+ my $query = $args{'query'};
+ my $limit = $args{'limit'};
+
+ if ( !-e $database ) {
+ warn "$!: $database\n";
+ return;
+ }
+
+ # GNU grep, ripgrep, agrep, BSD grep etc.
+ my @command = ('grep');
+
+ # read more data for prefix match <=> sub-string match
+ my $limit_factor = 8;
+
+ push @command, ( '-m', $limit * $limit_factor );
+ push @command, '-i' if $icase == 1;
+ push @command, ( '--', $query, $database );
+
+ warn join( " ", @command ), "\n" if $debug >= 2;
+ if ( !open( IN, '-|' ) ) {
+ exec @command;
+ die "@command: $! :: $?\n";
+ }
+ binmode( \*IN, ":bytes" );
+
+ my @data = ();
+ while (<IN>) {
+ chomp;
+ s,["],,g;
+
+# XXX: workaround for Firefox which ignores entries with "::" or "." inside a string
+# Note: we have to undo this in ports.cgi
+ s/::/: :/g;
+ s/\./ \./g;
+ push @data, $_;
+
+ last if scalar(@data) >= $limit * $limit_factor;
+ }
+
+ close IN;
+
+ # a sorted list, but prefix matches first
+ # e.g. 'sort(1)' is before 'alphasort(3) if you searched for 'sor'
+ my $lc_query = $icase ? lc($query) : $query;
+ my @prefix = grep { index( lc($_), $lc_query ) == 0 } @data;
+ my @non_prefix = grep { index( lc($_), $lc_query ) != 0 } @data;
+
+ # prefix first & real limit
+ @data = ( @prefix, @non_prefix );
+ @data = splice( @data, 0, $limit );
+
+ warn "data: ", join( " ", @data ), "\n" if $debug >= 2;
+ return @data;
+}
+
+sub escapeQuote {
+ my $string = shift;
+
+ $string =~ s/"/\\"/g;
+
+ return $string;
+}
+
+# create devbridge autocomplete response JSON object
+sub devbridge_autocomplete {
+ my $query = shift;
+ my $suggestion = shift;
+
+ my @suggestion = @$suggestion;
+
+ print qq/{ query:"/, escapeQuote($query), qq/", suggestions:[/;
+ print '"', join( '","', map { escapeQuote($_) } @suggestion ), '"'
+ if scalar(@suggestion) > 0;
+ print "] }\n";
+
+ warn "query '$query', suggestions: ", join ", ", @suggestion, "\n"
+ if $debug >= 1;
+}
+
+# create opensearch autocomplete response JSON object
+sub opensearch_autocomplete {
+ my $query = shift;
+ my $suggestion = shift;
+
+ my @suggestion = @$suggestion;
+
+ print '["', escapeQuote($query), '", [';
+
+ print qq{"}, join( '","', map { escapeQuote($_) } @suggestion ), qq{"}
+ if scalar(@suggestion) > 0;
+ print "]]\n";
+
+ warn "query '$query', suggestions: ", join ", ", @suggestion, "\n"
+ if $debug >= 1;
+}
+
+sub check_query {
+ my $query = shift;
+
+ # XXX: Firefox opensearch autocomplete workarounds
+ # remove space before a dot
+ $query =~ s/ \././g;
+
+ # remove space between double colon
+ $query =~ s/: :/::/g;
+
+ return $query;
+}
+
+######################################################################
+# param alias: query, q: search query
+# stype, s: name or name + description
+# icase,i : case sensitive
+# debug, d: debug level
+
+my $max_suggestions = 24;
+my $database_name = '/usr/local/www/ports/etc/autocomplete/autocomplete-name.txt';
+my $database_description = '/usr/local/www/ports/etc/autocomplete/autocomplete-description.txt';
+
+my $q = new MyCgiSimple;
+
+my $test_query = "bbbike";
+
+my $query = $q->param('query') // $q->param('q') // $test_query;
+my $stype = $q->param('stype') // $q->param('s') // "";
+my $d = $q->param('debug') // $q->param('d') // $debug;
+
+# we always use case insensive search for autocomplete
+my $icase = $q->param('icase') // $q->param('i') // 1;
+
+$query = ( $query =~ /^(.+)$/ ? $1 : "" );
+$stype = ( $stype =~ /^([a-z\-]+)$/ ? $1 : "" );
+$icase = ( $icase =~ /^([01])$/ ? $1 : 0 );
+$debug = ( $d =~ /^([0-3])$/ ? $1 : $debug );
+
+my $database =
+ $stype eq 'name'
+ ? $database_name
+ : $database_description;
+
+my $expire = $debug >= 2 ? '+1s' : '+1h';
+print $q->header(
+ -type => 'text/javascript',
+ -charset => 'utf-8',
+ -expires => $expire,
+);
+
+my $query_original = $query;
+$query = &check_query($query);
+
+my @suggestion = ();
+if ( length($query) >= 2 ) {
+ @suggestion = &suggestion(
+ 'database' => $database,
+ 'limit' => $max_suggestions,
+ 'query' => $query,
+ 'icase' => $icase,
+ 'stype' => $stype,
+ );
+}
+
+# ns=devbridge, for jQuery devbridge plugin
+# &devbridge_autocomplete( $query, \@suggestion );
+
+# ns=opensearch (Firefox etc.)
+&opensearch_autocomplete( $query_original, \@suggestion );
+
+#EOF