1151 lines
50 KiB
Prolog
Executable file
1151 lines
50 KiB
Prolog
Executable file
#!/usr/bin/perl
|
|
|
|
use v5.36;
|
|
use FU -spawn, -procname => 'manned';
|
|
use FU::SQL;
|
|
use FU::XMLWriter ':html5_';
|
|
use FU::Util 'httpdate_format', 'uri_escape';
|
|
use POSIX 'ceil', 'strftime';
|
|
use List::Util 'uniq', 'min';
|
|
use Time::Local 'timegm';
|
|
|
|
use Cwd 'abs_path';
|
|
our $ROOT;
|
|
BEGIN { ($ROOT = abs_path $0) =~ s{/www/index\.pl$}{}; }
|
|
|
|
use lib "$ROOT/ManUtils/blib/lib", "$ROOT/ManUtils/blib/arch";
|
|
use ManUtils;
|
|
|
|
|
|
# Database must be configured through env vars
|
|
FU::init_db(sub { FU::Pg->connect('')->set_type(bytea => '$hex') });
|
|
FU::debug_info('/fu-debug', 'debug~', 10);
|
|
FU::log_slow_reqs 500;
|
|
|
|
|
|
FU::before_request {
|
|
fu->set_header('cache-control' => 'max-age=31536000');
|
|
fu->send_file("$ROOT/www", fu->path);
|
|
fu->reset;
|
|
};
|
|
|
|
|
|
sub fmtdate { strftime '%Y-%m-%d', gmtime $_[0] }
|
|
|
|
# Set the last modification time
|
|
sub FU::obj::set_lastmod($, $d) {
|
|
fu->set_header('last-modified', httpdate_format $d) if $d;
|
|
}
|
|
|
|
|
|
# The systems table doesn't change often, so keep an in-memory cache for quick lookups.
|
|
sub systems {
|
|
state $s ||= [ map {
|
|
$_->{full} = $_->{name}.($_->{release}?' '.$_->{release}:'');
|
|
$_
|
|
} fu->sql('SELECT id, name, release, short FROM systems ORDER BY name, id')->allh->@* ];
|
|
}
|
|
|
|
sub sysbyid { state $s ||= { map +($_->{id}, $_), systems->@* } }
|
|
sub sysbyshort { state $s ||= { map +($_->{short}, $_), systems->@* } }
|
|
|
|
# URL-unescape some special characters that may occur in man names.
|
|
# Firefox seems to escape [ and ] in URLs. It doesn't really have to...
|
|
sub normalize_name { $_[0] =~ s/%5b/[/irg =~ s/%5d/]/irg =~ s/%20/ /rg }
|
|
|
|
sub shorthash_to_hex { unpack 'H*', pack 'i', $_[0] } # int -> hex
|
|
sub shorthash_to_int { unpack 'i', pack 'H*', $_[0] } # hex -> int
|
|
|
|
sub escape_like { $_[0] =~ s/([_%\\])/\\$1/rg }
|
|
|
|
|
|
# Returns ($pkg_obj, $ver_str, $should_redir)
|
|
sub pkg_frompath($sys_where, $path) {
|
|
# $path could either be:
|
|
# $name
|
|
# $name/$version
|
|
# $category/$name (deprecated)
|
|
# $category/$name/$version (deprecated)
|
|
# $category may contain a slash. We don't have the categories in the
|
|
# database anymore, so we'll just provide a redirect for anything that
|
|
# looks like it might have been a category.
|
|
# $name currently never contains a slash but may do so in the future, so
|
|
# let's also handle that.
|
|
|
|
my @comp = split '/', $path;
|
|
my @names = map join('/', @$_), map +([@comp[$_..$#comp]], [@comp[$_..$#comp-1]]), 0..$#comp;
|
|
|
|
my $pkg = fu->SQL('
|
|
SELECT id, system, name
|
|
FROM packages p
|
|
WHERE c_hasman AND', $sys_where, 'AND name', IN \@names, '
|
|
ORDER BY system DESC, length(name) DESC
|
|
LIMIT 1
|
|
')->rowh || return (undef, '', 0);
|
|
|
|
my $ver = $path =~ m{\Q$pkg->{name}\E/([^/]+)$} ? $1 : '';
|
|
($pkg, $ver, $path !~ /^\Q$pkg->{name}/);
|
|
}
|
|
|
|
|
|
# Get the preferred man page for the given filters.
|
|
sub man_pref($section, $where) {
|
|
$where = AND $where, SQL 'm.section LIKE', escape_like($section).'%' if length $section;
|
|
|
|
# Criteria to determine a "preferred" man page:
|
|
# 1. english: English versions of a man page have preference over other locales
|
|
# 2. pkgver: Newer versions of the same package have preference over older versions
|
|
# 3. stdloc: Prefer man pages in standard locations
|
|
# 4. secmatch: Prefer an exact section match
|
|
# 5. arch: Prefer Arch over other systems (because it tends to be the most up-to-date, and closest to upstreams)
|
|
# 6. debian: If there's no Arch, prefer latest Debian over other systems (again, tends to be more up-to-date)
|
|
# (also resolves distro-specific tooling disputes such as https://code.blicky.net/yorhel/manned/issues/1 )
|
|
# 7. sysrel: Prefer a more recent system release over an older release
|
|
# 8. secorder: Lower sections before higher sections (because man does it this way, for some reason)
|
|
# 9. pkgdate: Prefer more recent packages (cross-distro)
|
|
# 10. Fall back on shorthash comparison, to ensure the result is stable
|
|
|
|
state $archid = sysbyshort->{arch}{id};
|
|
state $debid = (sort { $b->{id} <=> $a->{id} } grep $_->{short} =~ /^debian-/, systems->@*)[0]{id};
|
|
|
|
fu->SQL(q{
|
|
WITH unfiltered AS (
|
|
SELECT m.name, m.section, l.locale, f.shorthash, f.content, f.filename, s AS sys, p AS pkg, v AS ver
|
|
FROM files f
|
|
JOIN locales l ON l.id = f.locale
|
|
JOIN mans m ON m.id = f.man
|
|
JOIN package_versions v ON v.id = f.pkgver
|
|
JOIN packages p ON p.id = v.package
|
|
JOIN systems s ON s.id = p.system
|
|
WHERE}, $where, q{
|
|
), f_english AS(
|
|
SELECT * FROM unfiltered WHERE NOT EXISTS(SELECT 1 FROM unfiltered WHERE is_english_locale(locale)) OR is_english_locale(locale)
|
|
), f_pkgver AS(
|
|
SELECT * FROM f_english a WHERE NOT EXISTS(SELECT 1 FROM f_english b WHERE (a.ver).package = (b.ver).package AND (a.ver).released < (b.ver).released)
|
|
), f_stdloc AS(
|
|
SELECT * FROM f_pkgver WHERE NOT EXISTS(SELECT 1 FROM f_pkgver WHERE is_standard_man_location(filename)) OR is_standard_man_location(filename)
|
|
), f_secmatch AS(
|
|
SELECT * FROM f_stdloc WHERE NOT EXISTS(SELECT 1 FROM f_stdloc WHERE section =}, $section, q{) OR section =}, $section, q{
|
|
), f_arch AS(
|
|
SELECT * FROM f_secmatch WHERE NOT EXISTS(SELECT 1 FROM}, length $section ? 'f_secmatch' : 'f_stdloc', RAW qq{WHERE (sys).id = $archid) OR (sys).id = $archid
|
|
), f_debian AS(
|
|
SELECT * FROM f_arch WHERE NOT EXISTS(SELECT 1 FROM f_arch WHERE (sys).id = $debid) OR (sys).id = $debid
|
|
), f_sysrel AS(
|
|
SELECT * FROM f_debian a WHERE NOT EXISTS(SELECT 1 FROM f_debian b WHERE (a.sys).name = (b.sys).name AND (a.sys).id < (b.sys).id)
|
|
), f_secorder AS(
|
|
SELECT * FROM f_sysrel a WHERE NOT EXISTS(SELECT 1 FROM f_sysrel b WHERE a.section > b.section)
|
|
), f_pkgdate AS(
|
|
SELECT * FROM f_secorder a WHERE NOT EXISTS(SELECT 1 FROM f_secorder b WHERE (a.ver).released < (b.ver).released)
|
|
)
|
|
SELECT (pkg).system, (pkg).name AS package, (ver).version, (ver).released, (ver).id AS verid,
|
|
name, section, filename, locale, shorthash, content
|
|
FROM f_pkgdate ORDER BY shorthash LIMIT 1
|
|
})->rowh;
|
|
}
|
|
|
|
|
|
# Given the name of a man page with optional section, find out the actual name
|
|
# and section suffix of the man page and the preferred version.
|
|
sub man_pref_name($name, $where) {
|
|
# Check the <name>.<section> format first, because ~most~ cases where
|
|
# there's a collision in the format, the <name>-only page is either
|
|
# uninteresting or a file name parsing error.
|
|
if ($name =~ /^(.+)\.([^.]+)$/) {
|
|
my($n, $s) = ($1,$2);
|
|
my $man = man_pref $s, AND $where, SQL 'm.name =', $n;
|
|
return ($man, $s) if $man;
|
|
}
|
|
|
|
my $man = man_pref undef, AND $where, SQL 'm.name =', $name;
|
|
$man ? ($man, '') : (undef, '');
|
|
}
|
|
|
|
|
|
sub man_languages($name, $sect) {
|
|
fu->SQL(
|
|
'SELECT DISTINCT l.locale
|
|
FROM files f
|
|
JOIN mans m ON m.id = f.man
|
|
JOIN locales l ON l.id = f.locale
|
|
WHERE m.name =', $name, 'AND m.section =', $sect, '
|
|
ORDER BY l.locale'
|
|
)->flat;
|
|
}
|
|
|
|
|
|
sub framework_ {
|
|
my $content = pop;
|
|
my(%o) = @_;
|
|
|
|
fu->set_body(html_ lang => 'en', sub {
|
|
head_ sub {
|
|
link_ rel => 'stylesheet', type => 'text/css', href => '/man.css?7';
|
|
title_ $o{title}.' - manned.org';
|
|
};
|
|
body_ sub {
|
|
header_ sub {
|
|
a_ href => '/', 'Manned.org';
|
|
form_ action => '/browse/search', method => 'get', sub {
|
|
input_ type => 'text', name => 'q', id => 'q', placeholder => 'ncdu, btrfs.8, git-*', value => $o{q}, tabindex => 1;
|
|
input_ type => 'submit', value => 'Search';
|
|
}
|
|
};
|
|
main_ class => $o{mainclass}, $content;
|
|
footer_ sub {
|
|
span_ sub {
|
|
a_ href => '/info/about', 'about'; txt_ ' | ';
|
|
a_ href => 'mailto:manned@yorhel.nl', 'contact'; txt_ ' | ';
|
|
a_ href => 'https://code.blicky.net/yorhel/manned', 'source';
|
|
};
|
|
span_ 'all manual pages are copyrighted by their respective authors.';
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
sub paginate_($url, $count, $perpage, $p) {
|
|
return if $count <= $perpage;
|
|
|
|
my sub l_ {
|
|
my($c)= @_;
|
|
a_ href => "$url$c", $c if $c != $p;
|
|
b_ $c if $c == $p;
|
|
};
|
|
|
|
my $lp = ceil($count/$perpage);
|
|
nav_ class => 'paginate', sub {
|
|
l_ 1 if $p > 1+4;
|
|
b_ '...' if $p > 1+5;
|
|
l_ $_ for (($p > 4 ? $p-4 : 1)..($p+4 > $lp ? $lp : $p+4));
|
|
b_ '...' if $p < $lp-5;
|
|
l_ $lp if $p < $lp-4;
|
|
}
|
|
}
|
|
|
|
|
|
FU::on_error 404 => sub {
|
|
my $title = 'No manual entry for '.fu->path;
|
|
framework_ title => $title, sub {
|
|
h1_ $title;
|
|
p_ 'That is, the page you were looking for doesn\'t exist.';
|
|
};
|
|
};
|
|
|
|
|
|
FU::get '/' => sub {
|
|
my $stats = fu->sql('SELECT * FROM stats_cache')->rowh;
|
|
|
|
sub num { local $_=shift; 1 while(s/(\d)(\d{3})($|,)/$1,$2/); $_ };
|
|
|
|
framework_ title => 'Man Pages Archive', mainclass => 'thin', sub {
|
|
h1_ 'Welcome to Manned.org';
|
|
h2_ 'The archive for man pages';
|
|
lit_ sprintf <<' _', map num($stats->{$_}), qw|hashes mans files packages|;
|
|
<p>
|
|
Indexing <b>%s</b> versions of <b>%s</b> manual pages found in
|
|
<b>%s</b> files of <b>%s</b> packages.
|
|
</p><p>
|
|
Manned.org aims to index all manual pages from a variety of systems, both
|
|
old and new, and provides a convenient interface for looking up and viewing
|
|
the various versions of each man page.
|
|
<a href="/info/about">More information »</a>
|
|
</p>
|
|
_
|
|
|
|
h2_ 'Indexed systems';
|
|
div_ class => 'systems', sub {
|
|
my %sys;
|
|
push $sys{$_->{name}}->@*, $_ for systems->@*;
|
|
div_ sub {
|
|
my $sys = $sys{$_};
|
|
my $img = $sys->[0]{short} =~ s/^(.+)-.+$/$1/r;
|
|
if(@$sys == 1) {
|
|
a_ href => "/pkg/$sys->[0]{short}", sub {
|
|
img_ width => 50, height => 50, src => "images/$img.png";
|
|
b_ $sys->[0]{name};
|
|
};
|
|
return;
|
|
}
|
|
img_ width => 50, height => 50, src => "images/$img.png";
|
|
div_ sub {
|
|
b_ $sys->[0]{name};
|
|
for(reverse @$sys) {
|
|
a_ href => "/pkg/$_->{short}", $_->{release};
|
|
lit_ ' ';
|
|
}
|
|
};
|
|
} for sort keys %sys;
|
|
};
|
|
|
|
h2_ 'Other relevant sites';
|
|
ul_ sub {
|
|
li_ sub { a_ href => 'https://man7.org/linux/man-pages/index.html', 'man7.org'; txt_ ' - Linux man pages from several upstream projects.' };
|
|
li_ sub { a_ href => 'https://manpag.es/', 'ManPag.es'; txt_ ' - Man pages from several Linux distributions.' };
|
|
li_ sub { a_ href => 'https://manpage.me/', 'manpage.me'; txt_ ' - Has a large collection as well, including from older Unices.' };
|
|
li_ sub { a_ href => 'https://www.mankier.com/', 'ManKier'; txt_ ' - Fedora Rawhide + some manually imported man pages; Nicely formatted and with some unique features.' };
|
|
li_ sub { a_ href => 'https://man.cx/', 'man.cx'; txt_ ' - Man pages extracted from Debian testing.' };
|
|
li_ sub { a_ href => 'http://man.he.net/', 'man.he.net'; txt_ ' - Also seems to be from a Debian-like system.' };
|
|
li_ sub { a_ href => 'https://linux.die.net/man/', 'die.net'; txt_ ' - Seems to be based on an RPM-based Linux distribution.' };
|
|
li_ sub { a_ href => 'https://manpages.org/', 'manpages.org'; txt_ ' - Lots of mostly-nicely formatted man pages, no clue about source.' };
|
|
li_ sub { a_ href => 'https://www.manpagez.com/', 'manpagez.com'; txt_ ' - Mac OS X, has some GTK-html and texinfo documentation as well.' };
|
|
li_ sub { a_ href => 'https://man.archlinux.org/', 'Arch Linux Man Pages' };
|
|
li_ sub { a_ href => 'https://manpages.debian.org/', 'Debian Man Pages' };
|
|
li_ sub { a_ href => 'https://www.dragonflybsd.org/cgi/web-man', 'DragonFlyBSD Man Pages' };
|
|
li_ sub { a_ href => 'https://www.freebsd.org/cgi/man.cgi', 'FreeBSD.org Man Pages' };
|
|
li_ sub { a_ href => 'https://man.netbsd.org/', 'NetBSD Man Pages' };
|
|
li_ sub { a_ href => 'https://www.openbsd.org/cgi-bin/man.cgi', 'OpenBSD Man Pages' };
|
|
li_ sub { a_ href => 'https://manpages.ubuntu.com/', 'Ubuntu Manuals' };
|
|
li_ sub { a_ href => 'https://man.voidlinux.org/', 'Void Linux manpages' };
|
|
};
|
|
};
|
|
};
|
|
|
|
FU::get '/info/about' => sub {
|
|
framework_ title => 'About', mainclass => 'thin', sub {
|
|
h1_ 'About Manned.org';
|
|
lit_ <<' _';
|
|
<h2 id="goal">Goal</h2>
|
|
<p>
|
|
The state of online indices of manual pages used to be a sad one. Existing
|
|
sites used to only offer you a single version of a man page: From one
|
|
origin, and often only in a single language. Most didn't even tell you
|
|
where the manual actually originated from, making it very hard to
|
|
determine whether the manual you found applied to your situation and even
|
|
harder to find a manual for a specific system. Additionally, some sites
|
|
rendered the manuals in an unreadable way, didn't correctly handle special
|
|
formatting - like tables - or didn't correctly display non-ASCII
|
|
characters.
|
|
</p><p>
|
|
Nowadays there are many good alternatives, but Manned.org was one of the
|
|
sites created in order to improve that situation. This site aims to index
|
|
the manual pages from a variaty of systems, both old and new, and allows you
|
|
to browse through the various versions of a manual page to find out how each
|
|
system behaves. The manuals are stored in the database as UTF-8, and are
|
|
passed through <a href="http://www.gnu.org/software/groff/">groff</a> to
|
|
render them in (mostly) the same way as they are displayed in your terminal.
|
|
</p><p>
|
|
This website is <a href="https://code.blicky.net/yorhel/manned">open
|
|
source</a> (AGPL licensed) and written in a combination of Perl and Rust.
|
|
The entire PostgreSQL database is available
|
|
<a href="#database-download">for download</a>.
|
|
</p>
|
|
|
|
<h2 id="url-format">URL format</h2>
|
|
<p>You can link to specific packages and man pages with several URL formats.
|
|
These URLs will keep working in the future, so you should not have to worry
|
|
about eventual dead links.</p>
|
|
<h3>Man pages</h3>
|
|
<p>The following URLs are available to refer to an individual man page:</p>
|
|
<dl>
|
|
<dt><code>/<name>[.<section>]</code> or <code>/man/<name>[.<section>]</code></dt><dd>
|
|
Attempts to get the latest and most-close-to-upstream version of a man
|
|
page. Examples:<br>
|
|
<a href="/socket">/socket</a><br>
|
|
<a href="/socket.7">/socket.7</a><br>
|
|
<a href="/man/socket.7">/man/socket.7</a><br>
|
|
This grabs the man page from any of the available systems, which may
|
|
result in confusing scenarios for system-specific documentation. I try
|
|
to at least keep the selection algorithm stable and deterministic, but
|
|
can't provide any guarantees.</dd>
|
|
<dt><code>/man/<system>/<name>[.<section>]</code></dt><dd>
|
|
Get the latest version of a man page from the given system, e.g.:<br>
|
|
<a href="/man/ubuntu/rsync">/man/ubuntu/rsync</a><br>
|
|
<a href="/man/ubuntu-xenial/rsync">/man/ubuntu-xenial/rsync</a></dd>
|
|
<dt><code>/man/<system>/<package>/<name>[.<section>]</code></dt><dd>
|
|
Get the latest version of a man page from the given package, e.g.:<br>
|
|
<a href="/man/ubuntu-xenial/rsync/rsync">/man/ubuntu-xenial/rsync/rsync</a></dd>
|
|
<dt><code>/man/<system>/<package>/<version>/<name>[.<section>]</code></dt><dd>
|
|
Get the man page from a specific package version, e.g.:<br>
|
|
<a href="/man/ubuntu-xenial/rsync/3.1.1-3ubuntu1/rsync">/man/ubuntu-xenial/rsync/3.1.1-3ubuntu1/rsync</a></dd>
|
|
<dt><code>/man.<language>/...</code></dt><dd>
|
|
Adding a language code to the <code>/man/</code> component will select
|
|
the man page in the requested language. The man page has to be available
|
|
in that language, otherwise you will get a 404. Redirects to other
|
|
languages as fallback may be implemented in the future. English man
|
|
pages are typically not tagged with a language at all, so explicitely
|
|
requesting <code>/man.en/...</code> will usually fail. This, too, may be
|
|
improved in the future. Examples:<br>
|
|
<a href="/man.de/faked-tcp">/man.de/faked-tcp</a><br>
|
|
<a href="/man.fr/fedora/rsync.1">/man.de/fedora/rsync.1</a></dd>
|
|
<dt><code>/man.<8-hex-digits>/...</code></dt><dd>
|
|
Permalink format. Adding the shorthash of the man page to the
|
|
<code>/man/</code> component of the above URLs will get that specific
|
|
man page from the requested system and/or package. The contents of the
|
|
man page should generally be the same regardless of which system or
|
|
package is included in the URL, but the UI may provide a different
|
|
nagivation context. Examples:<br>
|
|
<a href="/man.910be0ed/ls">/man.910be0ed/ls</a><br>
|
|
<a href="/man.910be0ed/fedora/ls">/man.910be0ed/fedora/ls</a><br>
|
|
<a href="/man.910be0ed/arch/ls">/man.910be0ed/arch/ls</a><br>
|
|
<a href="/man.910be0ed/fedora-24/coreutils-common/ls">/man.910be0ed/fedora-24/coreutils-common/ls</a></dd>
|
|
<dt><code>/raw...</code></dt><dd>
|
|
In all of the above URL formats, you can change <code>/man</code> with
|
|
<code>/raw</code> to get the raw UTF-8 encoded man page source, e.g.:<br>
|
|
<a href="/raw/socket.7">/raw/socket.7</a><br>
|
|
<a href="/raw/ubuntu-xenial/rsync/3.1.1-3ubuntu1/rsync">/raw/ubuntu-xenial/rsync/3.1.1-3ubuntu1/rsync</a><br>
|
|
<a href="/raw.de/faked-tcp">/raw.de/faked-tcp</a><br>
|
|
<a href="/raw.910be0ed/fedora/ls">/raw.910be0ed/fedora/ls</a></dd>
|
|
<dt><code>/<name>/<8-hex-digits></code></dt><dd>
|
|
Old permalink format for a specific man page (e.g. <a href="/ls/910be0ed">/ls/910be0ed</a>).</dd>
|
|
</dl>
|
|
<p>In all URLs where an optional <code>.<section></code> can be provided,
|
|
the search is performed as a prefix match. For example, <a
|
|
href="/cat.3">/cat.3</a> will provide the <code>cat.3tcl</code> man page if
|
|
no exact <code>cat.3</code> version is available. Linking to the full
|
|
section name is also possible: <a href="/cat.3tcl">/cat.3tcl</a>. If no
|
|
section is given and multiple sections are available, the lowest section
|
|
number is chosen.</p>
|
|
<h3>Packages</h3>
|
|
<p>Linking to individual packages is also possible. These pages will show a
|
|
listing of all manual pages available in the given package.</p>
|
|
<dl>
|
|
<dt><code>/pkg/<system>/<package></code></dt><dd>
|
|
For the latest version of a package (e.g. <a
|
|
href="/pkg/arch/coreutils">/pkg/arch/coreutils</a>).</dd>
|
|
<dt><code>/pkg/<system>/<package>/<version></code></dt><dd>
|
|
For a particular version of a package (e.g. <a
|
|
href="/pkg/arch/coreutils/8.25-2">/pkg/arch/coreutils/8.25-2</a>).</dd>
|
|
</dl>
|
|
<p>This site only indexes packages that actually have manual pages,
|
|
linking to a package that doesn't have any will result in a 404 page.</p>
|
|
|
|
<h2 id="indexing">The indexing process</h2>
|
|
<p>
|
|
All man pages are fetched right from the (binary) packages available on the
|
|
public repositories of Linux distributions. In particular:<br />
|
|
</p>
|
|
<dl>
|
|
<dt>Alpine Linux</dt><dd>
|
|
The main (since 3.0) and community (since 3.3) repositories are indexed
|
|
for the x86_64 architecture. Indexing started in December 2021, packages
|
|
and releases not available in the repositories at that time have not
|
|
been indexed. I haven't found an archive for version 2.x releases
|
|
yet.</dd>
|
|
<dt>Arch Linux</dt><dd>
|
|
The core, extra and community repositories are fetched from a local
|
|
Arch mirror. Indexing started around begin June 2012. The i686
|
|
architecture was indexed until November 6th, 2016, packages after that
|
|
were fetched from from x86_64.</dd>
|
|
<dt>Debian</dt><dd>
|
|
Historical releases were fetched from <a
|
|
href="http://archive.debian.org/debian/">http://archive.debian.org/debian/</a>
|
|
and <a href="http://snapshot.debian.org/">http://snapshot.debian.org/</a>.
|
|
For buzz, rex and bo, we're missing a few man pages because some packages
|
|
were missing from the repository archives. Where available, all components
|
|
(main, contrib and non-free) from the $release and $release-updates
|
|
repositories are indexed.</dd>
|
|
<dt>CentOS</dt><dd>
|
|
Historical releases were fetched from <a
|
|
href="http://vault.centos.org/">vault.centos.org</a>, current releases
|
|
from a local mirror. Where applicable, the following repositories were
|
|
indexed: addons, centosplus, contrib, extras, os. The i386 architecture
|
|
was indexed for versions lower than 7.0, since 7.0 the packages from
|
|
x86_64 are indexed.
|
|
<dt>Fedora</dt><dd>
|
|
Historical releases were fetched from <a
|
|
href="http://archives.fedoraproject.org/pub/archive/fedora/linux/">archives.fedoraproject.org</a>,
|
|
current releases from a local repository. Fedora Core 1 till 6 are
|
|
(incorrectly) called 'Fedora' here. To compensate for that, Fedora 3 till
|
|
6 also include the Extras repository. For Fedora 7 and later, the
|
|
'Everything' and 'updates' repositories are indexed. The i386 arch was
|
|
indexed for Fedora 17 and older, the x86_64 arch starting with Fedora
|
|
18.</dd>
|
|
<dt>FreeBSD</dt><dd>
|
|
Historical releases were fetched from <a
|
|
href="http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/">http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/</a>.
|
|
The base installation tarballs are included in the database as packages
|
|
prefixed with <i>core-</i>. The package repositories have also been
|
|
indexed, except for 2.0.5 - 2.2.7 and 3.0 - 3.3 because those were not
|
|
available on the ftp archive. Only the -RELEASE repositories have been
|
|
included, which is generally a snapshot of the ports directory around the
|
|
time of the release. The release dates indicated for many packages were
|
|
guessed from the file modification dates in the tarball, and may be
|
|
inaccurate. The i368 arch was indexed for FreeBSD 11.0 and older, the
|
|
amd64 arch starting with 11.1.</dd>
|
|
<dt>NetBSD</dt><dd>
|
|
Only the core installation sets have been indexed, <a
|
|
href="https://www.pkgsrc.org/">pkgsrc</a> is awesome but out of scope
|
|
for now. The i368 arch was indexed for 5.x and older, the amd64 arch
|
|
starting with 6.0. Releases before 1.3 only distributed preformatted man
|
|
pages and have therefore not been indexed. The original roff sources
|
|
could perhaps be extracted from the source tarballs, but that's a
|
|
project for another time.</dd>
|
|
<dt>OpenBSD</dt><dd>
|
|
Only the core file sets have been indexed, no packages (yet). All from
|
|
amd64. Releases before 5.0 distributed preformatted man pages and have
|
|
therefore not been indexed.</dd>
|
|
<dt>Ubuntu</dt><dd>
|
|
Historical releases were fetched from <a
|
|
href="http://old-releases.ubuntu.com/ubuntu/">http://old-releases.ubuntu.com/ubuntu/</a>,
|
|
supported releases from a local mirror. All components (main, universe,
|
|
restricted and multiverse) from the $release, $release-updates and
|
|
$release-security repositories are indexed. Indexing started around mid
|
|
June 2012. All releases before 2017 were indexed from the i386
|
|
repositories, starting with 17.04 the amd64 repositories were used.</dd>
|
|
</dl>
|
|
<p>
|
|
Only packages for a single architecture (i386 or amd64) are scanned. To my
|
|
knowledge, packages that come with different manuals for different
|
|
architectures either don't exist or are extremely rare. It does happen that
|
|
some packages are not available for all architectures. Usually, though,
|
|
every package is at least available for the most popular architecture, so
|
|
hopefully we're not missing out on much. <br /><br />
|
|
The repositories are scanned for new packages on a daily basis.
|
|
</p>
|
|
|
|
<h2 id="database-download">Database download</h2>
|
|
<p>
|
|
This site is backed by a PostgreSQL database containing all the man pages.
|
|
Weekly dumps of the full database are available for download at
|
|
<a href="http://dl.manned.org/dumps/">http://dl.manned.org/dumps/</a>.
|
|
<br /><br />
|
|
Be warned that the download server may not be terribly fast or reliable,
|
|
so it is advisable to use a client that supports resumption of partial
|
|
downloads. See <a href="/wget">wget's -c</a> or
|
|
<a href="/curl">curl's -C</a>.
|
|
<br /><br />
|
|
The database schema is "documented" at <a
|
|
href="https://code.blicky.net/yorhel/manned/src/branch/master/schema.sql">schema.sql</a>
|
|
in the git repo. Keep in mind that these dumps don't constitute a stable
|
|
API and, while this won't happen frequently, incompatible schema changes
|
|
or Postgres major version bumps will occassionally occur.
|
|
</p>
|
|
|
|
<h2 id="future-plans">Future plans</h2>
|
|
<p>
|
|
This site isn't nearly as awesome yet as it could be. Here's some ideas that
|
|
would be nice to have in the future:
|
|
<ul>
|
|
<li>Index a few more systems: Gentoo (now that it has official binary packages) and perhaps others.</li>
|
|
<li>Better browsing and discovery features.</li>
|
|
<li>Improved, more intelligent, search,</li>
|
|
<li><a href="/apropos.1">apropos(1)</a> emulation(?),</li>
|
|
<li>Diffs between various versions of a man page,</li>
|
|
<li>Anchor links within man pages, for easier linking to a section or paragraph,</li>
|
|
<li>Alternative formats (Text, PDF, more semantic HTML, etc),</li>
|
|
<li>A command-line client, like <a href="/man.1">man(1)</a> with manned.org as database backend.</li>
|
|
</ul>
|
|
</p>
|
|
|
|
<h2 id="copyright">Copyright</h2>
|
|
<p>
|
|
All manual pages are copyrighted by their respective authors. The manuals
|
|
have been fetched from publically available repositories of free and
|
|
(primarily) open source software. The distributors of said software have put
|
|
in efforts to only include software and documentation that allows free
|
|
distribution. Nonetheless, if a manual that does not allow to be
|
|
redistributed has been inadvertently included in our index, please let me
|
|
know and I will have it removed as soon as possible.
|
|
</p>
|
|
_
|
|
};
|
|
};
|
|
|
|
FU::get '/browse/search' => sub {
|
|
my $q = fu->query(q => { onerror => '', accept_array => 'first' });
|
|
|
|
my $name = $q;
|
|
my $sect = $name =~ s/^([0-9])\s+// || $name =~ s/\(([a-zA-Z0-9]+)\)$// || $name =~ s/\.([0-9][a-zA-Z0-9]*)$// ? $1 : '';
|
|
($name,$sect) = ($sect,'') if !length $name;
|
|
|
|
# Redirect if we have an exact match
|
|
my @sectsql = length $sect ? SQL 'AND section =', $sect : ();
|
|
my $man = length $name && fu->SQL('SELECT name, section FROM mans WHERE name =', $name, @sectsql, 'ORDER BY section LIMIT 1')->rowh;
|
|
fu->redirect(temp => "/man/$man->{name}".(length $sect ? ".$man->{section}" : '')) if $man;
|
|
|
|
# Otherwise, do case-insensitive glob search
|
|
my $nameq = escape_like(lc $name) =~ tr/?*/_%/r;
|
|
my $lst = !length $nameq ? [] : fu->SQL('
|
|
SELECT name, section
|
|
FROM mans WHERE lower(name) LIKE', $nameq, @sectsql, '
|
|
ORDER BY name, section
|
|
LIMIT 500'
|
|
)->alla;
|
|
|
|
framework_ title => 'Search results for '.$q, mainclass => 'searchres', q => $q, sub {
|
|
h1_ 'Search results for '.(length $sect ? "$name in section $sect" : $q);
|
|
if(@$lst) {
|
|
p_ 'Truncated to the first 500 results.' if @$lst >= 500;
|
|
ul_ sub {
|
|
li_ sub {
|
|
a_ href => "/man/$_->[0].$_->[1]", $_->[0];
|
|
small_ " $_->[1]";
|
|
} for @$lst;
|
|
}
|
|
} else {
|
|
p_ 'No results :-(';
|
|
p_ sub {
|
|
a_ href => '?q='.uri_escape($name), 'Try again in other sections?' if length $sect;
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
# Object to represent the various URLs to a man page.
|
|
#
|
|
# Parameters:
|
|
# fmt => man|txt|raw
|
|
# shorthash => 8-char hex
|
|
# lang => language code
|
|
# system => system shortname
|
|
# package => name of the package
|
|
# version => package version
|
|
# man => name of the man page
|
|
# section => man page section
|
|
#
|
|
# URL format:
|
|
# /$fmt[.$shorthash][.$lang][/$system[[/$category]/$package[/$version]]]/$man[.$section]
|
|
#
|
|
# Note that the URL format has some ambiguity:
|
|
# - $category (deprecated, only used for compatibility with old URLs) and
|
|
# $package may contain a slash, so a database lookup is required to
|
|
# disambiguate between URLs with [/$version] and those without.
|
|
# - $man may contain a dot, so a database lookup is required to disambiguate
|
|
# between URLs with [.$section] and those without
|
|
#
|
|
# $system may also refer to system shortnames without the version suffix (e.g.
|
|
# 'ubuntu' rather than 'ubuntu-impish'). In that case the man page from the
|
|
# latest release of that system is chosen.
|
|
package ManUrl {
|
|
sub new { my($p,%o)=@_; bless \%o, $p }
|
|
sub set { my($o,@o)=@_; bless +{%$o,@o}, ref $o }
|
|
sub mansect { $_[0]{man}.(defined $_[0]{section} ? ".$_[0]{section}" : '') }
|
|
use overload '""' => sub {
|
|
my($o)=@_;
|
|
"/$o->{fmt}".(defined $o->{shorthash} ? ".$o->{shorthash}" : '').(defined $o->{lang} ? ".$o->{lang}" : '')
|
|
.(defined $o->{system} ? ("/$o->{system}"
|
|
.(defined $o->{package} ? ("/$o->{package}"
|
|
.(defined $o->{version} ? "/$o->{version}" : '')) : '')) : '')
|
|
.'/'.$o->mansect
|
|
};
|
|
};
|
|
|
|
|
|
sub man_nav_($man, $url, $toc, $htmllang) {
|
|
my $systems = fu->SQL('
|
|
SELECT DISTINCT p.system
|
|
FROM packages p
|
|
JOIN package_versions v ON v.package = p.id
|
|
JOIN files f ON f.pkgver = v.id
|
|
JOIN mans m ON m.id = f.man
|
|
WHERE m.name =', $man->{name}, 'AND m.section =', $man->{section}
|
|
)->flat;
|
|
|
|
my $sect = fu->SQL(
|
|
'SELECT DISTINCT section FROM mans WHERE name =', $man->{name}, 'ORDER BY section'
|
|
)->flat;
|
|
|
|
my $lang = man_languages $man->{name}, $man->{section};
|
|
|
|
nav_ sub {
|
|
form_ action => '/sysredir/'.$url->mansect(), method => 'get',
|
|
onsubmit => 'location.href="/man/"+system_select[system_select.selectedIndex].value+"/'.$url->mansect().'";return false',
|
|
sub {
|
|
my %names;
|
|
push $names{$_->{name}}->@*, $_ for map sysbyid->{$_}, sort { $b <=> $a } @$systems;
|
|
select_ id => 'system_select', name => 'system', sub {
|
|
for (sort { ($names{$b}->@* == 1) <=> ($names{$a}->@* == 1) || $a cmp $b } keys %names) {
|
|
my $s = $names{$_};
|
|
if (@$s == 1) {
|
|
option_ value => $s->[0]{short}, selected => $s->[0]{id} == $man->{system}?'':undef, $s->[0]{full};
|
|
next;
|
|
}
|
|
optgroup_ label => $_, sub {
|
|
option_ value => $_->{short}, selected => $_->{id} == $man->{system}?'':undef, $_->{full} for @$s;
|
|
};
|
|
}
|
|
};
|
|
input_ type => 'submit', value => 'Go';
|
|
} if @$systems > 1;
|
|
|
|
# TODO: This is ugly, especially because clicking on a translation or
|
|
# section, you can end up with a man page that is nowhere close to the
|
|
# man page you're currently reading. Sections or languages available
|
|
# for the currently selected system should be highlighted.
|
|
if(@$sect > 1) {
|
|
b_ 'Sections';
|
|
p_ sub {
|
|
for (@$sect) {
|
|
if($man->{section} eq $_) {
|
|
i_ $_;
|
|
} else {
|
|
a_ href => "/man/$man->{name}.$_", $_;
|
|
}
|
|
txt_ ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
if(@$lang > 1) {
|
|
b_ 'Languages';
|
|
p_ sub {
|
|
for (@$lang) {
|
|
if(($_||'') eq $man->{locale}) {
|
|
i_ $_ || 'default';
|
|
} else {
|
|
a_ href => $_ ? "/man.$_/$man->{name}.$man->{section}" : "/man/$man->{name}.$man->{section}", $_ || 'default';
|
|
}
|
|
txt_ ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
if(@$toc > 1) {
|
|
b_ 'Table of Contents';
|
|
ul_ sub {
|
|
for (0..$#$toc) {
|
|
li_ sub {
|
|
a_ @$htmllang, href => sprintf('#head%d', $_+1), sub { lit_ lc $toc->[$_] };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
sub man_page($man, $url) {
|
|
fu->set_lastmod($man->{released});
|
|
|
|
my($hash, $content, $fmt) = fu->SQL('SELECT hash, content, html FROM contents WHERE id =', $man->{content})->rowl;
|
|
if($url->{fmt} eq 'raw') {
|
|
fu->set_header('content-type', 'text/plain');
|
|
fu->set_header('content-disposition', sprintf 'filename="%s.%s"', $man->{name}, $man->{section});
|
|
utf8::encode($content);
|
|
fu->set_body($content);
|
|
fu->done;
|
|
}
|
|
|
|
# Find out of this is a redirect-type man page, i.e. one containing only a single .so and no other content.
|
|
my $follow;
|
|
if (length($content) < 2048 && (() = $content =~ /^\.so/mg) == 1) {
|
|
my $data = $content =~ s/^\.\\".*//rmg;
|
|
if ($data =~ m{^\s*\.so (?:[^\s]*/)?([^\s/]+)\s*$}s) {
|
|
($follow) = man_pref_name $1, SQL 'v.id =', $man->{verid};
|
|
($content, $fmt) = fu->SQL('SELECT content, html FROM contents WHERE id =', $follow->{content})->rowl if $follow;
|
|
}
|
|
}
|
|
|
|
$fmt //= ManUtils::html ManUtils::fmt $content;
|
|
if($url->{fmt} eq 'txt') {
|
|
# TODO: The 'txt' format is kind of broken right now as it includes our HTML formatting codes.
|
|
# This feature is a WIP and not advertised at the moment, anyway.
|
|
fu->set_header('content-type', 'text/plain; charset=UTF-8');
|
|
fu->set_header('content-disposition', sprintf 'filename="%s.%s.txt"', $man->{name}, $man->{section});
|
|
utf8::encode($fmt);
|
|
fu->set_body($fmt);
|
|
fu->done;
|
|
}
|
|
|
|
# Prefix links to other man pages with the current system, to ensure we
|
|
# grab the most relevant man page.
|
|
my $sys = sysbyid->{$man->{system}}{short};
|
|
$fmt =~ s{<a href="/}{<a href="/man/$sys/}g;
|
|
|
|
my @toc;
|
|
$fmt =~ s{\n<b>([^<\n]+?)<\/b>\n}{
|
|
push @toc, $1;
|
|
my $c = @toc;
|
|
qq{\n<a href="#head$c" id="head$c">$1</a>\n}
|
|
}eg;
|
|
|
|
my @htmllang = $man->{locale} =~ /^([a-z]{2,3})(?:_([A-Z]{2}))?(?:$|@|\.)/ ? (lang => $1.($2?"-$2":'')) : ();
|
|
|
|
framework_ title => $man->{name}, mainclass => 'manpage', sub {
|
|
man_nav_ $man, $url, \@toc, \@htmllang;
|
|
div_ id => 'manbuttons', sub {
|
|
h1_ $man->{name};
|
|
ul_ sub {
|
|
li_ sub { a_ href => $url->set(fmt => 'raw'), 'source' };
|
|
li_ sub { a_ href => $url->set(system => sysbyid->{$man->{system}}{short}, package => undef, shorthash => shorthash_to_hex $man->{shorthash}), 'permalink' };
|
|
li_ sub { a_ href => "/ver.".shorthash_to_hex($man->{shorthash}).($man->{locale}?".$man->{locale}":'')."/$man->{name}.$man->{section}", 'versions' };
|
|
li_ sub { a_ href => "/loc/$hash", 'locations' };
|
|
}
|
|
};
|
|
em_ sub {
|
|
txt_ 'Displaying the included man page: ';
|
|
a_ href => $url->set(
|
|
man => $follow->{name}, section => $follow->{section},
|
|
system => sysbyid->{$man->{system}}{short}, shorthash => shorthash_to_hex $follow->{shorthash}
|
|
), "$follow->{name}($follow->{section})";
|
|
} if $follow;
|
|
pre_ @htmllang, sub { lit_ $fmt };
|
|
};
|
|
}
|
|
|
|
|
|
# /<name>[.section] - short and handy catch-all URL for man pages
|
|
# /<name>/<shorthash> - old permalink format
|
|
# This one has to go before the other mappings, to ensure that links work for
|
|
# man pages called 'pkg' or 'man'.
|
|
FU::get qr{/([^/]+)(?:/([0-9a-f]{8}))?} => sub($name, $shorthash=undef) {
|
|
$name = normalize_name $name;
|
|
|
|
my($man, $sect) = man_pref_name $name, $shorthash ? SQL 'f.shorthash =', shorthash_to_int $shorthash : RAW 'true';
|
|
fu->notfound if !$man->{name};
|
|
|
|
man_page $man, ManUrl->new(
|
|
fmt => 'man',
|
|
man => length $sect ? $man->{name} : $name,
|
|
section => length $sect ? $sect : undef,
|
|
);
|
|
};
|
|
|
|
|
|
# /<name>/<shorthash>/src - old URL format to get the raw man page
|
|
FU::get qr{/([^/]+)/([0-9a-f]{8})/src} => sub($name, $shorthash) {
|
|
$name = normalize_name $name;
|
|
|
|
my($man) = man_pref_name $name, SQL 'f.shorthash =', shorthash_to_int $shorthash;
|
|
fu->notfound if !$man->{name};
|
|
man_page $man, ManUrl->new(fmt => 'raw', man => $name);
|
|
};
|
|
|
|
|
|
FU::get qr{/(man|txt|raw)(?:\.([a-fA-F0-9]{8}))?(?:\.([^/]+))?/(.+)} => sub($fmt, $shorthash, $lang, $path) {
|
|
my @where;
|
|
my $name = normalize_name($path =~ s{/?([^/]+)$}{} && $1);
|
|
my $system = $path =~ s{^([^/]+)/?}{} && $1;
|
|
|
|
# $sys can be either a full system 'short' name, or a prefix (e.g. 'debian' meaning 'any debian-* version')
|
|
if($system) {
|
|
my $sysid = sysbyshort->{$system};
|
|
$sysid = $sysid ? [$sysid->{id}] : [ map sysbyshort->{$_}{id}, grep /^\Q$system\E-/, keys sysbyshort->%* ];
|
|
fu->notfound if !@$sysid;
|
|
push @where, SQL system => IN $sysid;
|
|
}
|
|
|
|
my($pkg, $ver, $redir) = length $path ? pkg_frompath AND(@where), $path : (undef,undef);
|
|
fu->notfound if length $path && !$pkg;
|
|
push @where, SQL 'p.id =', $pkg->{id} if $pkg;
|
|
push @where, SQL 'v.version =', $ver if length $ver;
|
|
|
|
push @where, SQL 'f.shorthash =', shorthash_to_int $shorthash if $shorthash;
|
|
push @where, SQL 'l.locale =', $lang if $lang;
|
|
|
|
my($man, $section) = man_pref_name $name, AND @where;
|
|
fu->notfound if !$man;
|
|
|
|
my $url = ManUrl->new(
|
|
fmt => $fmt,
|
|
shorthash => $shorthash,
|
|
lang => $lang,
|
|
system => length $system ? $system : undef,
|
|
package => $pkg ? $pkg->{name} : undef,
|
|
version => length $ver ? $ver : undef,
|
|
man => length $section ? $man->{name} : $name,
|
|
section => length $section ? $section : undef,
|
|
);
|
|
fu->redirect(perm => $url) if $redir;
|
|
man_page $man, $url;
|
|
};
|
|
|
|
|
|
FU::get qr{/pkg/([^/]+)} => sub($short) {
|
|
my $sys = sysbyshort->{$short};
|
|
fu->notfound if !$sys;
|
|
|
|
$FU::REQ->{qs} =~ s/;/&/g if $FU::REQ->{qs}; # HACK: old URLs used ';' as separator instead of '&'
|
|
my $f = fu->query(
|
|
c => { onerror => 'all', enum => [ '0', 'all', 'a'..'z' ] },
|
|
p => { onerror => 1, uint => 1, range => [1,200] },
|
|
);
|
|
|
|
my $where = SQL 'c_hasman AND NOT dead AND system =', $sys->{id}, $f->{c} ne 'all' ? ('AND match_firstchar(name,', $f->{c}, ')') : ();
|
|
my $count = fu->SQL('SELECT count(*) FROM packages p WHERE', $where)->val;
|
|
my $pkg = fu->SQL(
|
|
'SELECT id, system, name FROM packages p WHERE', $where, 'ORDER BY name LIMIT 200 OFFSET', ($f->{p}-1)*200,
|
|
)->allh;
|
|
|
|
framework_ title => $sys->{full}, mainclass => 'pkglist', sub {
|
|
div_ sub {
|
|
div_ sub {
|
|
h1_ $sys->{full};
|
|
};
|
|
nav_ class => 'charselect', sub {
|
|
for('all', 0, 'a'..'z') {
|
|
a_ href => "/pkg/$short?c=$_", $_?uc$_:'#' if $_ ne $f->{c};
|
|
b_ $_?uc$_:'#' if $_ eq $f->{c};
|
|
}
|
|
};
|
|
};
|
|
small_ '(Packages without man pages are not listed)';
|
|
|
|
paginate_ "/pkg/$short?c=$f->{c}&p=", $count, 200, $f->{p};
|
|
ul_ sub {
|
|
li_ sub {
|
|
a_ href => "/pkg/$short/$_->{name}", $_->{name};
|
|
} for @$pkg;
|
|
};
|
|
paginate_ "/pkg/$short?c=$f->{c}&p=", $count, 200, $f->{p};
|
|
};
|
|
};
|
|
|
|
|
|
# Package info: /pkg/$system[/$category]/$name[/$version]; $category and $name may contain slashes, too.
|
|
FU::get qr{/pkg/([^/]+)/(.+)} => sub($short, $path) {
|
|
my $sys = sysbyshort->{$short};
|
|
fu->notfound if !$sys;
|
|
|
|
my($pkg, $ver, $redir) = pkg_frompath(SQL('system =', $sys->{id}), $path);
|
|
fu->notfound if !$pkg;
|
|
fu->redirect(perm => "/pkg/$short/$pkg->{name}".($ver?"/$ver":'')) if $redir;
|
|
|
|
my $vers = fu->SQL('
|
|
SELECT id, version, released
|
|
FROM package_versions v
|
|
WHERE package =', $pkg->{id}, '
|
|
AND EXISTS(SELECT 1 FROM files f WHERE f.pkgver = v.id)
|
|
ORDER BY released DESC'
|
|
)->allh;
|
|
my $sel = $ver ? (grep $_->{version} eq $ver, @$vers)[0] : $vers->[0];
|
|
fu->notfound if !$sel;
|
|
|
|
my $p = fu->query(p => { onerror => 1, uint => 1, range => [1,100] });
|
|
|
|
my $count = fu->SQL('SELECT count(*) FROM files WHERE pkgver =', $sel->{id})->val;
|
|
my $mans = fu->SQL('
|
|
WITH lst AS (
|
|
SELECT f.man, m.name, m.section, f.shorthash, f.filename, l.locale
|
|
FROM files f
|
|
JOIN locales l ON l.id = f.locale
|
|
JOIN mans m ON m.id = f.man
|
|
WHERE f.pkgver =', $sel->{id}, '
|
|
), needlang AS (
|
|
SELECT man FROM lst GROUP BY man HAVING count(*) > 1
|
|
), needhash AS (
|
|
SELECT man, locale FROM lst GROUP BY man, locale HAVING count(*) > 1
|
|
) SELECT name, section, shorthash, filename, locale
|
|
, EXISTS(SELECT 1 FROM needlang WHERE man = l.man) AS needlang
|
|
, EXISTS(SELECT 1 FROM needhash WHERE man = l.man AND locale = l.locale) AS needhash
|
|
FROM lst l
|
|
ORDER BY name, section, locale, filename
|
|
LIMIT 200 OFFSET', ($p-1)*200
|
|
)->allh;
|
|
|
|
# Latest version of this package determines last modification date of the page.
|
|
fu->set_lastmod($vers->[0]{released});
|
|
|
|
my $subtitle = " / $pkg->{name}";
|
|
my $pkgpath = "$sys->{short}/$pkg->{name}";
|
|
framework_ title => "$sys->{full}$subtitle $sel->{version}", mainclass => 'pkgpage', sub {
|
|
h1_ sub {
|
|
a_ href => "/pkg/$sys->{short}", $sys->{full};
|
|
txt_ $subtitle;
|
|
};
|
|
|
|
div_ sub {
|
|
section_ sub {
|
|
h2_ 'Versions';
|
|
ul_ sub {
|
|
li_ sub {
|
|
a_ href => "/pkg/$pkgpath/$_->{version}", $_->{version} if $_ != $sel;
|
|
b_ " $_->{version}" if $_ == $sel;
|
|
small_ ' '.fmtdate $_->{released};
|
|
} for(@$vers);
|
|
}
|
|
};
|
|
|
|
section_ sub {
|
|
h2_ "Manuals for version $sel->{version}";
|
|
paginate_ "/pkg/$pkgpath/$sel->{version}?p=", $count, 200, $p;
|
|
ul_ sub {
|
|
li_ sub {
|
|
# Only add the hash or locale to the URL if it's necessary to select the proper man page.
|
|
my $ext = $_->{needhash} ? '.'.shorthash_to_hex $_->{shorthash} : $_->{needlang} && length $_->{locale} ? ".$_->{locale}" : '';
|
|
a_ href => "/man$ext/$pkgpath/$sel->{version}/$_->{name}.$_->{section}", "$_->{name}($_->{section})";
|
|
b_ " $_->{locale}" if $_->{locale};
|
|
small_ " $_->{filename}";
|
|
} for(@$mans);
|
|
};
|
|
paginate_ "/pkg/$pkgpath/$sel->{version}?p=", $count, 200, $p;
|
|
};
|
|
};
|
|
}
|
|
};
|
|
|
|
# /browse/<pkg> has been moved to /pkg/.
|
|
FU::get qr{/browse/(.+)} => sub($pkg) { fu->redirect(perm => "/pkg/$pkg") };
|
|
|
|
# Redirect for the system selection box, for visitors who have disabled JS.
|
|
FU::get qr{/sysredir/([^/]+)} => sub($path) { fu->redirect(temp => '/man/'.(fu->query('system')//'arch')."/$path") };
|
|
|
|
# Redirect for a specific language for a man page. I have no idea if anyone
|
|
# still uses this URL format, but it was supported at some point, so let's keep
|
|
# it around.
|
|
FU::get qr{/lang/([^/]+)/([^/]+)} => sub($lang, $man) { fu->redirect(temp => "/man.$lang/$man") };
|
|
|
|
|
|
FU::get qr{/loc/([a-fA-F0-9]{40})}, sub($hash) {
|
|
# There are a few files that have been duplicated far too many times for
|
|
# this page to be very useful. Add some limits to make sure the page at
|
|
# least manages to load something.
|
|
my $maxlisting = 30_000;
|
|
my $maxpersys = 500;
|
|
|
|
my $l = fu->SQL('
|
|
SELECT p.system, p.name AS package, v.version, f.filename, f.shorthash, m.name, m.section
|
|
FROM files f
|
|
JOIN mans m ON m.id = f.man
|
|
JOIN package_versions v ON v.id = f.pkgver
|
|
JOIN packages p ON p.id = v.package
|
|
WHERE f.content = (SELECT id FROM contents WHERE hash =', $hash, ')
|
|
ORDER BY p.system DESC, p.name, v.released DESC, f.filename
|
|
LIMIT ', $maxlisting
|
|
)->allh;
|
|
fu->notfound if !@$l;
|
|
|
|
my %sys;
|
|
push $sys{ sysbyid->{$_->{system}}{name} }->@*, $_ for @$l;
|
|
|
|
my @mans = uniq sort map "$_->{name}($_->{section})", @$l;
|
|
|
|
framework_ title => 'Locations for '.join(', ', @mans), mainclass => 'locpage', sub {
|
|
h1_ 'Locations of this man page';
|
|
p_ sub {
|
|
txt_ 'Listing all man pages identified by SHA1 ';
|
|
code_ $hash;
|
|
txt_ '.';
|
|
if (@$l >= $maxlisting) {
|
|
br_;
|
|
b_ sprintf 'WARNING: This file has more than %d locations, the list below is incomplete.', $maxlisting;
|
|
}
|
|
br_;
|
|
br_;
|
|
txt_ 'Included man pages: ';
|
|
for (0..$#mans) {
|
|
txt_ ', ' if $_ > 0;
|
|
a_ href => "/man.".lc(substr($hash,0,8))."/".($mans[$_] =~ s/\(/./r =~ s/\)//r), $mans[$_];
|
|
}
|
|
txt_ '.';
|
|
if (keys %sys > 1) {
|
|
br_;
|
|
br_;
|
|
txt_ 'System index: ';
|
|
my @sys = sort keys %sys;
|
|
for (0..$#sys) {
|
|
txt_ ', ' if $_ > 0;
|
|
a_ href => "#$sys[$_]", $sys[$_];
|
|
}
|
|
txt_ '.';
|
|
}
|
|
};
|
|
|
|
for my $sysname (sort keys %sys) {
|
|
h2_ sub { a_ href => "#$sysname", id => "$sysname", $sysname };
|
|
table_ sub {
|
|
thead_ sub { tr_ sub {
|
|
td_ 'Release' if sysbyid->{$sys{$sysname}[0]{system}}{release};
|
|
td_ 'Package';
|
|
td_ 'Path';
|
|
}};
|
|
my $lastrel = '';
|
|
tr_ sub {
|
|
my $sys = sysbyid->{$_->{system}};
|
|
td_ $lastrel eq $sys->{release} ? '' : $sys->{release} if $sys->{release};
|
|
$lastrel = $sys->{release};
|
|
td_ sub {
|
|
a_ href => "/pkg/$sys->{short}/$_->{package}/$_->{version}", $_->{package}.'-'.$_->{version};
|
|
};
|
|
td_ $_->{filename};
|
|
} for @{$sys{$sysname}}[0..min $maxpersys, $#{$sys{$sysname}}];
|
|
};
|
|
small_ sprintf 'List truncated to the first %d results out of %d.', $maxpersys, scalar $sys{$sysname}->@* if $sys{$sysname}->@* > 500;
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
# /ver[.$shorthash][.$lang]/$name.$section
|
|
FU::get qr{/ver(?:\.([a-fA-F0-9]{8}))?(?:\.([^/]+))?/([^/]+)\.([0-9a-zA-Z]+)}, sub($shorthash, $lang, $name, $sect) {
|
|
$shorthash = $shorthash ? shorthash_to_int $shorthash : -1;
|
|
$lang ||= '';
|
|
|
|
my $l = fu->SQL('
|
|
SELECT p.system, p.name AS package, v.version, v.released, f.shorthash
|
|
FROM files f
|
|
JOIN package_versions v ON v.id = f.pkgver
|
|
JOIN packages p ON p.id = v.package
|
|
WHERE f.man = (SELECT id FROM mans WHERE name =', $name, 'AND section =', $sect, ')
|
|
AND f.locale IN(SELECT id FROM locales WHERE locale =', $lang, ')
|
|
ORDER BY p.system DESC, p.name, v.released DESC, f.shorthash
|
|
')->allh;
|
|
|
|
my $sectl = fu->SQL('SELECT DISTINCT section FROM mans WHERE name =', $name, 'ORDER BY section')->flat;
|
|
my $langs = man_languages $name, $sect;
|
|
|
|
my %sys;
|
|
push $sys{ sysbyid->{$_->{system}}{name} }->@*, $_ for @$l;
|
|
|
|
my $title = "Versions of $name($sect)".($lang ? " in locale $lang" : '');
|
|
framework_ title => $title, mainclass => 'verpage', sub {
|
|
h1_ $title;
|
|
|
|
p_ sub {
|
|
txt_ 'Alternative sections: ';
|
|
for (0..$#{$sectl}) {
|
|
txt_ ', ' if $_ > 0;
|
|
if(($sectl->[$_]||'') eq $sect) {
|
|
b_ $sectl->[$_];
|
|
} else {
|
|
a_ href => '/ver'.($lang?".$lang":'')."/$name.$sectl->[$_]", $sectl->[$_];
|
|
}
|
|
}
|
|
txt_ '.';
|
|
} if @$sectl > 1;
|
|
|
|
p_ sub {
|
|
txt_ 'Available languages: ';
|
|
for (0..$#{$langs}) {
|
|
txt_ ', ' if $_ > 0;
|
|
if(($langs->[$_]||'') eq $lang) {
|
|
b_ $langs->[$_] || 'default';
|
|
} else {
|
|
a_ href => '/ver'.($langs->[$_]?".$langs->[$_]":'')."/$name.$sect", $langs->[$_] || 'default';
|
|
}
|
|
}
|
|
txt_ '.';
|
|
} if @$langs > 1;
|
|
|
|
p_ sub {
|
|
txt_ 'System index: ';
|
|
my @sys = sort keys %sys;
|
|
for (0..$#sys) {
|
|
txt_ ', ' if $_ > 0;
|
|
a_ href => "#$sys[$_]", $sys[$_];
|
|
}
|
|
txt_ '.';
|
|
} if keys %sys > 1;
|
|
|
|
for my $sysname (sort keys %sys) {
|
|
h2_ sub { a_ href => "#$sysname", id => "$sysname", $sysname };
|
|
table_ sub {
|
|
thead_ sub { tr_ sub {
|
|
td_ 'Release' if sysbyid->{$sys{$sysname}[0]{system}}{release};
|
|
td_ 'Package';
|
|
td_ 'Date';
|
|
td_ 'Hash';
|
|
}};
|
|
my $lastrel = '';
|
|
tr_ sub {
|
|
my $sys = sysbyid->{$_->{system}};
|
|
td_ $lastrel eq $sys->{release} ? '' : $sys->{release} if $sys->{release};
|
|
$lastrel = $sys->{release};
|
|
td_ sub {
|
|
a_ href => "/pkg/$sys->{short}/$_->{package}/$_->{version}", $_->{package}.'-'.$_->{version};
|
|
};
|
|
td_ fmtdate $_->{released};
|
|
td_ class => 'sh', sub {
|
|
my $hex = shorthash_to_hex $_->{shorthash};
|
|
txt_ $hex if $_->{shorthash} == $shorthash;
|
|
a_ href => "/man.$hex/$sys->{short}/$name.$sect", $hex if $_->{shorthash} != $shorthash;
|
|
};
|
|
} for $sys{$sysname}->@*;
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
FU::run();
|