aboutsummaryrefslogblamecommitdiff
path: root/src/bin/paku.in
blob: f91442637a0709d00864051a6edba551158ac556 (plain) (tree)
1
2
3
4
5
6
7
8

                   





                                         


                      



                          
                                                


                         
 




                          

                                                                               

                                                 





                                                     

                                                                            
 
 

                         



                                                                   
 





                                                                         
                   

 







                              
 
 
         
                                             

                      

 



                      

 
 
                                
                                    
 
                                              










                                                                       







                                                   

                                                                      

 











                                                      





























                                                                            
 
                    
                                                          



































                                                                                

 
                
                               

                                    




                              
                                                               
                                            

                                                      
                                                                                   
 
                                                                               
                             
                                                                        





                                                  
                                                     



                                                      
 


                                                            
                           






                                                   
                                     
                                      
                           
                 












                                                            


                                                                  
                                                    



                                                   




                     

 
                 
                               

                                    
                     

                                             




                                                         
                     
                                                           






                                               

                                                                
                      
                                            

                                                      
                                                     
                                                                                    
 



                                                                         
                             
                                    
                          

                                         




                                           
                                                          




                                                   
                                                                           
                                            

                             
 

                                      
                                                                  
                                            

                             
 









                                                                             
                                                  
                                                 

                   





                              

 
                   
                               
                                   
                         
 
                                            
                                      
                                                       


                                                     
 
                                                                                                      
                                                
                                                                                 
                                          
 
                           
 
 
                                                       
 
                                                                                         
 
                                                                                                           
                               

                                      
                                                                                                                             
                                           
                                                                                            

                                                      
                                                                 


                                             
                                                                                                                                            
                                                








                                                                                      
                                                                                      
                                                                                      
 
                                                                                      
                                                                                      
                                                                                      
 
                                                                            

                                                             

                                                                                      
 

                                                                                                                                         
 
                                                                                                                               
                                                
                                                                                          
 
 

                           
 
 
                               
                          

                                              
 
 
                                                                                                      
 
                           
                                                 
 
 
                                                 
                                                                          
 
                                                               
                                                                          
 

                                                                  
 

                                                                                                   
 
                                                       
                                                               
 
 
                   


                       















                                                                         
                                                     


















                                                                        
                 
                               





















                                                                                          






























                                                           










                                                   
                                                                                                   

                                                                               
                                                                                      
                             
                                                   

                                     

                                                                           

                                      
                                                                    








                                                                                  
                                                                                            


                                                                               
                                                                                              

























                                                                                           









                                                                                          



                                    
 


                                                            









                                                                   

                                             





















                                                                                                                 
                            

                                                                                           





                                                                             
                                                                                                              














                                                                              
                                                        




                                                         


                                                                                  
                            





















                                                                                                          
                                




                                                          
                                                                                                              







                                                                                 



                           



               
                                             



                                            
                                             




                                                                
#!/usr/bin/env perl

use v5.34;
use warnings;
use feature 'signatures';
no warnings ('experimental::signatures');
use Getopt::Std ();
use JSON ();
use File::Basename ();
use Digest::MD5 ();
use Digest::SHA ();

sub usage($fh) {
	print $fh <<~'EOF'
		Usage:
		  paku [-d DIR] [-f FILE] ACTION
		  paku -h
		EOF
}

sub help($fh) {
	print $fh <<~'EOF'


		Options:
		  -d DIR        use DIR for intermediary data (default: .paku/)
		  -f FILE       use data from FILE (default: paku.lock)
		  -h, --help    show this message

		  ACTION        what to emit, one of:
		                - guix
		                - debian
		                - nix
		                - html


		Generate package definitions for different package managers.


		Examples:

		  Emit Guix package definitions using all defaults:

		    $ paku guix > packages.scm
		    $ guix build -f packages.scm


		  Emit Debian build recipes from "debian.json", using
		  ".debtmp/" as the temporary directory, and execute them
		  as is:

		    $ paku -d .debtemp/ -f debian.json debian | make -f-
		EOF
}


for (@ARGV) {
	last if $_ eq '--';
	if ($_ eq '--help') {
		usage *STDOUT;
		help *STDOUT;
		exit;
	}
}

my %opts;
if (!Getopt::Std::getopts('d:f:h', \%opts)) {
	usage *STDERR;
	exit 2;
}

if ($opts{h}) {
	usage *STDOUT;
	help *STDOUT;
	exit;
}


my $dir   = $opts{d} || '.paku';
my $fname = $opts{f} || 'paku.lock';

my $action = $ARGV[0] or die "Missing ACTION";
shift;

sub load_json() {
	my $json_str = do {
		open my $fh, $fname or die "Failed opening \"$fname\"";
		local $/;
		<$fh>;
	};
	return JSON::decode_json($json_str);
}

my $licenses = {
	guix => {
		'AGPL-3.0-or-later' => 'agpl3+',
	},
	nix => {
		'AGPL-3.0-or-later' => 'agpl3Plus',
	},
};
sub license_for($target, $mappings, $id) {
	return $mappings->{$id} || $licenses->{$target}->{$id} || $id;
}

sub inputs_for($mappings, @inputs) {
	my @out = ();
	for (@inputs) {
		if (!$mappings->{$_}) {
			push @out, $_;
		} else {
			push @out, @{$mappings->{$_}};
		}
	}
	return @out;
}

sub emit_packages() {
	for (@ARGV) {
		my $fh;
		my $size = (stat($_))[7];

		open ($fh, '<', $_) or die "Can't open \"$_\": $!";
		my $md5  = Digest::MD5->new->addfile($fh)->hexdigest;
		close $fh;

		open ($fh, '<', $_) or die "Can't open \"$_\": $!";
		my $sha1 = Digest::SHA->new(1)->addfile($fh)->hexdigest;
		close $fh;

		open ($fh, '<', $_) or die "Can't open \"$_\": $!";
		my $sha256 = Digest::SHA->new(256)->addfile($fh)->hexdigest;
		close $fh;

		open ($fh, '<', $_) or die "Can't open \"$_\": $!";
		my $sha512 = Digest::SHA->new(512)->addfile($fh)->hexdigest;
		close $fh;

		print `ar -p $_ control.tar.xz | tar -xJO ./control`;
		say "Filename: ./$_";
		say "Size: $size";
		say "MD5sum: $md5";
		say "SHA1: $sha1";
		say "SHA256: $sha256";
		print "\n";
	}
}

sub emit_release() {
	my $f = $ARGV[0] or die 'Missing "Packages" file';
	my $name = File::Basename::basename $f;
	my $size = (stat($f))[7];

	my $now = `env LANG=POSIX.UTF-8 date -u '+%a, %d %b %Y %H:%M:%S +0000'`;
	chomp $now;

	my $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $md5  = Digest::MD5->new->addfile($fh)->hexdigest;
	close $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $sha1 = Digest::SHA->new(1)->addfile($fh)->hexdigest;
	close $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $sha256 = Digest::SHA->new(256)->addfile($fh)->hexdigest;
	close $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $sha512 = Digest::SHA->new(512)->addfile($fh)->hexdigest;
	close $fh;

	print <<~EOF;
		Date: $now
		MD5Sum:
		 $md5             $size $name
		SHA1:
		 $sha1             $size $name
		SHA256:
		 $sha256             $size $name
		SHA512:
		 $sha512             $size $name
		EOF
	exit;
}

sub emit_nix() {
	my $json = load_json();
	my $ns = $json->{namespace};
	$ns =~ s/\./-/g;
	print <<~EOF;
		{ pkgs }:
		self: super: {
		  $ns = rec {
		EOF
	my $license_mapping = $json->{mappings}{nix}{licenses};
	for my $pkg (@{$json->{packages}}) {
		my $long = $pkg->{'long-description'};
		$long =~ s/^(.)/          $1/gm;
		my $license = license_for 'nix', $license_mapping, $pkg->{license};

		my $suffix = $pkg->{label} eq 'latest' ? '' : "-$pkg->{label}";
		print <<~EOF;
		    $pkg->{name}$suffix = pkgs.stdenv.mkDerivation rec {
		      name = "$pkg->{name}";
		      version = "$pkg->{version}";

		      src = fetchTarball {
		        url =
		          "$pkg->{url}";
		        sha256 = "$pkg->{sha256nix}";
		      };

		      nativeBuildInputs = with pkgs; [
		EOF

		for my $input (@{$pkg->{'native-inputs'}}) {
			print <<~EOF;
			        $input
			EOF
		}
		print <<~EOF;
		      ];

		      buildInputs = with pkgs; [
		EOF
		for my $input (@{$pkg->{inputs}}) {
			print <<~EOF;
			        $input
			EOF
		}
		print <<~EOF;
		      ];

		      makeFlags = [ "PREFIX=\$(out)" ];

		      doCheck = true;
		      enableParallelBuilding = true;

		      meta = with pkgs.lib; {
		        description = "$pkg->{description}";
		        longDescription = ''
		$long
		        '';
		        homepage = "$pkg->{homepage}";
		        changelog = "$pkg->{changelog}";
		        downloadPage = "$pkg->{'downloads-page'}";
		        license = licenses.$license;
		        platforms = platforms.unix;
		      };
		    };
		EOF
	}
	print <<~EOF;
	  };
	}
	EOF
}

sub emit_guix() {
	my $json = load_json();
	my $ns = $json->{namespace};
	$ns =~ s/\./ /g;
	print <<~EOF;
		(define-module ($ns packages)
		EOF
	for my $module (@{$json->{guix}{'module-use'}}) {
		print <<~EOF;
		  #:use-module (gnu packages $module)
		EOF
	}
	print <<~EOF;
	  #:use-module ((guix licenses) #:prefix licenses:)
	  #:use-module (guix gexp)
	  #:use-module (guix packages)
	  #:use-module (guix download)
	  #:use-module (guix build-system gnu))

	EOF

	my $pkg_mapping     = $json->{mappings}{guix}{packages};
	my $license_mapping = $json->{mappings}{guix}{licenses};
	my @pkgs = ();
	for my $pkg (@{$json->{packages}}) {
		my $long = $pkg->{'long-description'};
		$long =~ s/^(.)/          $1/gm;
		my $ver = $pkg->{version} =~ s/^v//r;
		my $license = license_for 'guix', $license_mapping, $pkg->{license};

		my $name = $pkg->{name} . (
			$pkg->{label} eq 'latest' ? '' : "-$pkg->{label}"
		);
		push @pkgs, $name;
		print <<~EOF;
		(define-public $name
		  (package
		    (name "$pkg->{name}")
		    (version "$ver")
		    (source
		      (origin
		        (method url-fetch)
		        (uri "$pkg->{url}")
		        (sha256
		          (base32 "$pkg->{sha256guix}"))))
		    (build-system gnu-build-system)
		    (native-inputs
		EOF

		print '      (list';
		for (inputs_for $pkg_mapping, @{$pkg->{'native-inputs'}}) {
			print "\n       $_";
		}
		print "))\n";

		print "    (inputs\n";
		print '      (list';
		for (inputs_for $pkg_mapping, @{$pkg->{inputs}}) {
			print "\n       $_";
		}
		print "))\n";

		print <<~EOF;
		    (arguments
		      (list
		       #:make-flags '(list (string-append "PREFIX=" %output))
		       #:phases
		       #~(modify-phases %standard-phases
		           (delete 'configure))))
		    (synopsis "$pkg->{description}")
		    (description
		      "$pkg->{'long-description'}")
		    (home-page "$pkg->{homepage}")
		    (license licenses:$license)))

		EOF
	}
	print '(list';
	for (@pkgs) {
		print "\n $_";
	}
	print ")\n";
}

sub emit_debian() {
	my $json = load_json();
	my @debs = ("debs = \\\n");
	my @targets = ();

	for my $pkg (@{$json->{packages}}) {
		push @targets, <<~EOF;
			\$(DIR)/tarballs/$pkg->{fname}:
				mkdir -p \$(\@D)
				wget -O \$\@ \\
					'$pkg->{url}'

			\$(DIR)/checkouts/$pkg->{name}-$pkg->{version}: \$(DIR)/tarballs/$pkg->{fname}
				mkdir -p \$(\@D)
				tar -C \$(\@D) -xf \$(DIR)/tarballs/$pkg->{fname}
				touch \$\@

			EOF


		next if $pkg->{architectures} ne 'any';

		push @debs, "\t\$(DIR)/debian/$pkg->{name}_$pkg->{version}_all.deb \\\n";

		my $ver = $pkg->{label} eq 'latest' ? '0.' . $pkg->{version} . '.latest' : $pkg->{version};
		$ver =~ s/^v//;

		push @targets, <<~EOF;
			\$(DIR)/builddirs/$pkg->{name}-$pkg->{version}/DEBIAN: \$(DIR)/checkouts/$pkg->{name}-$pkg->{version}
				\$(MAKE) \\
					-C \$(DIR)/checkouts/$pkg->{name}-$pkg->{version} \\
					install \\
					PREFIX=/usr \\
					DESTDIR="\$\$PWD"/\$(\@D)
				mkdir -p \$\@
				touch \$\@

			\$(DIR)/builddirs/$pkg->{name}-$pkg->{version}/DEBIAN/control: \$(DIR)/builddirs/$pkg->{name}-$pkg->{version}/DEBIAN
				printf '' > \$\@
				printf 'Package: $pkg->{name}\\n'              >> \$\@
				printf 'Version: $ver\\n'                      >> \$\@
				printf 'Section: custom\\n'                    >> \$\@
				printf 'Depends:\\n'                           >> \$\@
				printf 'Priority: optional\\n'                 >> \$\@
				printf 'Architecture: all\\n'                  >> \$\@
				printf 'Essential: no\\n'                      >> \$\@

				printf 'Maintainer: '                          >> \$\@
				printf '$pkg->{"maintainer-b64"}' | base64 -d  >> \$\@
				printf '\\n'                                   >> \$\@

				printf 'Description: '                         >> \$\@
				printf '$pkg->{"description-b64"}' | base64 -d >> \$\@
				printf '\\n'                                   >> \$\@

				printf '$pkg->{'long-description-b64'}' | \\
					base64 -d |    \\
					sed 's|^\$\$|.|' | \\
					sed 's|^| |'                           >> \$\@
				printf '\\n'                                   >> \$\@

			\$(DIR)/builddirs/$pkg->{name}-$pkg->{version}.deb: \$(DIR)/builddirs/$pkg->{name}-$pkg->{version}/DEBIAN/control
				dpkg-deb --build \$(DIR)/builddirs/$pkg->{name}-$pkg->{version}

			\$(DIR)/debian/$pkg->{name}_$pkg->{version}_all.deb: \$(DIR)/builddirs/$pkg->{name}-$pkg->{version}.deb
				mkdir -p \$(\@D)
				cp \$(DIR)/builddirs/$pkg->{name}-$pkg->{version}.deb \$\@


			EOF
	}


	print "DIR = $dir\n\n";
	print @debs, "\n";
	print <<~EOF;
		GPGKEY = '$json->{maintainer}'


		all: \$(DIR)/debian/InRelease \$(DIR)/debian/Release.gpg \$(DIR)/debian/public-key.asc

		public-dir:
			\@printf '\$(DIR)/debian'


		\$(DIR)/debian/Packages: \$(debs)
			cd \$(\@D) && paku debian-packages *.deb > \$(\@F)

		\$(DIR)/debian/Release: \$(DIR)/debian/Packages
			paku debian-release \$(DIR)/debian/Packages > \$\@

		\$(DIR)/debian/Release.gpg: \$(DIR)/debian/Release
			gpg -abs -o \$\@ \$(DIR)/debian/Release

		\$(DIR)/debian/InRelease: \$(DIR)/debian/Release
			gpg --default-key \$(GPGKEY) -a --clear-sign -o \$\@ \$(DIR)/debian/Release

		\$(DIR)/debian/public-key.asc: \$(debs)
			gpg --armour --export \$(GPGKEY) > \$\@


		EOF
	print @targets;
}

sub pascal_case($s) {
	return join('', map(ucfirst, split '-', $s))
}

sub emit_homebrew() {
	my $d = $ARGV[0] or die 'Missing "Formula/" directory';
	my $json = load_json();
	for my $pkg (@{$json->{packages}}) {
		my $name = $pkg->{name} . (
			$pkg->{label} eq 'latest' ? '' : "-$pkg->{label}"
		);
		open(my $fh, '>', "$d/$name.rb");
		my $class_name = pascal_case $name;
		print $fh <<~EOF;
			class $class_name < Formula
			  desc '$pkg->{description}'
			  homepage '$pkg->{homepage}'
			  url '$pkg->{url}'
			  sha256 '$pkg->{sha256}'
			  license 'AGPL-3.0-or-later'

			  def install
			    system 'make'
			    system 'make', 'check'
			    system 'make', 'install', "PREFIX=#{prefix}"
			  end

			  test do
			    system "#{bin}/$pkg->{name}", '-V'
			  end
			end
			EOF
		close $fh;
	}
}

sub emit_html() {
	my $json = load_json();
	print <<~EOF;
		<!DOCTYPE html>
		<html lang="en">
		  <head>
		    <meta charset="UTF-8" />
		    <meta name="viewport" content="width=device-width, initial-scale=1" />
		    <link rel="icon" type="image/svg+html" href="favicon.svg" />
		    <title>$json->{name} package index</title>

		    <style>
		      body {
		        max-width: 800px;
		        margin: 0 auto;
		      }

		      ul {
		        list-style: none;
		      }

		      li {
		        margin-top: 2em;
		      }

		      pre {
		        overflow: auto;
		        white-space: pre;
		        margin: 5px;
		        padding: 5px;
		        border-radius: 5px;
		      }

		      pre, code {
		        background-color: #ddd;
		      }

		      \@media(prefers-color-scheme: dark) {
		        :root {
		          color: white;
		          background-color: black;
		        }

		        a {
		          color: hsl(211, 100%, 60%);
		        }

		        a:visited {
		          color: hsl(242, 100%, 80%);
		        }

		        pre, code {
		          background-color: #222;
		        }
		      }
		    </style>
		  </head>
		  <body>
		    <main>
		      <h1>
		        $json->{name} package index
		      </h1>
		      <ul>
		EOF

	for my $pkg (@{$json->{packages}}) {
		my $guix_suffix = $pkg->{label} eq 'latest' ? '' : '@' . $pkg->{label} =~ s/-/./gr;
		my $suffix = $pkg->{label} eq 'latest' ? '' : "-$pkg->{label}";
		my $apt_suffix = $suffix =~ s/-/=/r;
		my $ver = $pkg->{label} eq 'latest' ? $pkg->{label} : $pkg->{version};
		print <<~EOF;
		        <li id="$pkg->{name}-$ver">
		          <details>
		            <summary>
		              <a href="#$pkg->{name}-$ver">$pkg->{name}</a>
		              ($ver) - $pkg->{description}
		            </summary>
		            <p>
		            <a href="$pkg->{homepage}/">Homepage</a>
		            </p>
		            <section>
		              <h2>Guix</h2>
		              <p>
		                After following the
		                <a href="#guix-instructions">Guix instructions</a>
		                to include this channel, you can launch a shell
		                that includes this package:
		              </p>
		              <pre><code>\$ guix shell $pkg->{name}$guix_suffix</code></pre>
		              <p>
		                Alternatively, you can install it imperatively:
		              </p>
		              <pre><code>\$ guix install $pkg->{name}$guix_suffix</code></pre>
		            </section>
		            <section>
		              <h2>Debian</h2>
		              <p>
		                After following the
		                <a href="#debian-instructions">Debian instructions</a>
		                to include this repository to
		                <code>/etc/apt/sources.list</code>, you can
		                install it:
		              </p>
		              <pre><code># apt install $pkg->{name}$apt_suffix</code></pre>
		            </section>
		            <section>
		              <h2>Nix</h2>
		              <p>
		                After following the
		                <a href="#nix-instructions">Nix instructions</a>
		                to include this repository as an overlay, you
		                can launch a shell that includes this package:
		              </p>
		              <pre><code>\$ nix-shell -p $pkg->{name}$suffix</code></pre>
		              <p>
		                Alternatively, you can install it imperatively:
		              </p>
		              <pre><code>\$ nix-env -i $pkg->{name}$suffix</code></pre>
		            </section>
		            <section>
		              <h2>Homebrew</h2>
		              <p>
		                After following the
		                <a href="#homebrew-instructions">Homebrew instructions</a>
		                to include this repository as a <code>tap</code>, you can
		                install it with:
		              </p>
		              <pre><code>\$ brew install $pkg->{name}$suffix</code></pre>
		            </section>
		          </details>
		        </li>
		EOF
	}

	my $channel_name = $json->{namespace} =~ s/\./-/gr;
	my $overlay_name = $json->{namespace} =~ s/\./-/gr;
	my $tap_name     = $json->{namespace} =~ s/\./\//gr;
	print <<~EOF;
		      </ul>
		      <article id="guix-instructions">
		        <h2>Guix instructions</h2>
		        <p>
		          Add this channel to your
		          <code>~/.config/guix/channels.scm</code>:
		        </p>
		        <pre><code>(cons*
		 (channel
		  (name '$channel_name)
		  (url "$json->{vcs}->{git}")
		  (branch "main")
		  (introduction
		   (make-channel-introduction
		    "d749e053e6db365069cb9b2ef47a78b06f9e7361"
		    (openpgp-fingerprint
		     "5BDA E9B8 B2F6 C6BC BB0D  6CE5 81F9 0EC3 CD35 6060"))))
		 %default-channels)</code></pre>
		        <p>
		          Afterwards, do a <code>guix pull</code> to make the
		          packages in this channel available to your profile.
		        </p>
		        <p>
		          See also the
		          <a href="https://guix.gnu.org/manual/en/guix.html#Channels">Guix manual on channels</a>
		          for more information.
		        </p>
		      </article>
		      <article id="debian-instructions">
		        <h2>Debian instructions</h2>
		        <p>
		          Include my public key for validating the repository
		          signatures:
		        </p>
		        <pre><code>\$ wget -qO- $json->{'base-url'}/debian/public-key.asc |
		    sudo tee /etc/apt/trusted.gpg.d/$json->{namespace}.asc</code></pre>
		        <p>
		          Afterwards, include this repository to the list of
		          repositories that <code>apt</code> uses for sources
		          by adding its URL to
		          <code>/etc/apt/sources.list</code>:
		        </p>
		        <pre><code>\$ sudo apt-add-repository 'deb $json->{'base-url'}/debian ./'</code></pre>
		        <p>
		          <code>apt-add-repository</code> will already perform
		          an <code>apt update</code>, so the packages from the
		          new repository will already be available.
		        </p>
		      </article>
		      <article id="nix-instructions">
		        <h2>Nix instructions</h2>
		        <p>
		          Add this repository as an overlay to your
		          <code>/etc/nixos/configuration.nix</code>:
		        </p>
		        <pre><code>  nixpkgs = {
		    overlays = [
		      (import (fetchTarball {
		        url = "$json->{vcs}->{tarball}";
		      }) { inherit pkgs; })
		    ];
		  };</code></pre>
		        <p>
		          All the packages live under the
		          <code>$overlay_name</code> attribute set, like.
		          <code>$overlay_name.$json->{packages}[0]->{name}</code>,
		          so it can be included in the list of packages:
		        </p>
		        <pre><code>  environment.systemPackages = with pkgs; [
		    ...
		    $overlay_name.$json->{packages}[0]->{name}
		  ];</pre></code>
		        <p>
		          To make the overlay available outside the system
		          environment of
		          <code>/etc/nixos/configuration.nix</code>, you need to
		          add this overlay to your
		          <code>~/.config/nixpkgs/overlays/</code> directory,
		          like a
		          <code>~/.config/nixpkgs/overlays/$json->{namespace}.nix</code>
		          file containing:
		        </p>
		        <pre><code>import (fetchTarball {
		  url = "$json->{vcs}->{tarball}";
		}) { pkgs = import &lt;nixpkgs&gt; {}; }</pre></code>
		        <p>
		          Then you'd be able to launch a shell with a package
		          from this overlay:
		        </p>
		        <pre><code>\$ nix-shell -p $overlay_name.$json->{packages}[0]->{name}</code></pre>
		      </article>
		      <article id="homebrew-instructions">
		        <h2>Homebrew instructions</h2>
		        <p>
		          Add this repository as a tap:
		        </p>
		        <pre><code>\$ brew tap --force-auto-update $tap_name $json->{vcs}->{http}</code></pre>
		        <p>
		          The explicit <code>--force-auto-update</code> option
		          is required, because <code>homebrew(1)</code> will only
		          fetch updates automatically for repositories hosted on
		          GitHub.  With this option, repositories not on GitHub
		          are treated equally.
		        </p>
		      </article>
		    </main>
		  </body>
		</html>
		EOF
}


my %actions = (
	'debian-packages' => \&emit_packages,
	'debian-release'  => \&emit_release,
	nix               => \&emit_nix,
	guix              => \&emit_guix,
	debian            => \&emit_debian,
	homebrew          => \&emit_homebrew,
	html              => \&emit_html,
);

my $fn = $actions{$action} or die "Unknown ACTION: \"$action\"";
&$fn;