#!/usr/bin/perl
use 5.016;
use strict;
use warnings;

use Slackware::SBoKeeper;

my $sbokeeper = Slackware::SBoKeeper->init();

$sbokeeper->run();



=head1 NAME

sbokeeper - SlackBuild package manager helper

=head1 SYNOPSIS

 sbokeeper [options] command [args]

=head1 DESCRIPTION

B<sbokeeper> is a tool that can help a Slackware system administrator keep
track of their installed SlackBuilds by maintaining a database of added
packages and their dependencies. It is B<not> a package manager itself, it
simply helps in the task of managing SlackBuilds.

The package database is a text file that stores all of B<sbokeeper>'s package
data. By default, it is stored in I<~/.local/share/sbokeeper/data.sbokeeper>,
but it can be configured to be stored in another location.

=head1 CONFIGURATION

B<sbokeeper> reads it's configuration from a configuration file. A B<sbokeeper>
configuration file consists of lines of key-value pairs. A key-value pair
follows this format:

 Key = Value

Blank lines and lines starting with a hash (#) are ignored.

Unless the B<-c> option is used, B<sbokeeper> will search for a configuration
file in the following paths in descending order:

=over 4

=item I<~/.config/sbokeeper.conf>

=item I<~/.sbokeeper.conf>

=item I</etc/sbokeeper.conf>

=back

If no configuration file is found, B<sbokeeper> will use default values for
everything.

The following are valid configuration entries:

=over 4

=item B<DataFile>

Absolute path to file where package database will be stored. A '~' can be
expanded into the running user's home directory, but other kinds of shell
expansion are not performed.

Can be overrided by the B<-d> option.

Default value is I<~/.local/share/sbokeeper/data.sbokeeper>.

=item B<SBoPath>

Absolute to path to directory where local SlackBuild repository is kept. The
directory should look something like this:

 SBoPath
   academic
     pkg1
     pkg2
     ...
   business
     pkg3
     pkg4
   ...

The package categories themselves do not actually matter, all that matters is
that each SlackBuild is inside a directory that is inside B<SBoPath>.

If your SlackBuild repository directory follows the same format as the official
git repository then you should be good.

A '~' can be expanded into the running user's home directory, but other kinds
of shell expansion are not performed.

Can be overrided with the B<-s> option.

The default value depends on what SlackBuild package management tools are
installed on your system. If you are using one the following:

=over 4

=item sbopkg

=item sbotools/sbotools2

=item sbpkg

=item slpkg

=item slackrepo

=item sboui

=back

then B<sbokeeper> will use the default repo location of the first package
manager it finds installed on your system.
If none can be found, B<sbokeeper> will croak and just tell you to set it
manually.

=item B<Tag>

Packge tag used by your SlackBuild repo. The package tag is the string at the
end of a package name, usually beginning with an underscore.

Can be overrided with the B<-t> option.

The default value is '_SBo'.

=back

=head1 COMMANDS

=over 4

=item B<add> I<pkg ...>

Adds packages to package database, along with any of their dependencies.
Added packages are marked as manually added, dependencies are not. If a package
that is specified is already present in the database but not marked as manually
added, it will be marked as manually added.

If add encounters a package that is already present in the package database, it
will skip adding it. This means that if the package is missing dependencies, it
will not try to re-add those dependencies. If this poses a problem, the
B<depwant> command can help users track down missing dependencies.

This command supports aliases.

=item B<tack> I<pkg ...>

Adds package to package database. Does not add any of their dependencies. Added
packages are marked as manually added. If a package that is specified is already
present in the database but not marked as manually added, it will be marked as
manually added.

This command supports aliases.

=item B<addish> I<pkg ...>

Same thing as B<add>, but added packages are not marked as manually added.

This command supports aliases.

=item B<tackish> I<pkg ...>

Same thing as B<tack>, but added packages are not marked as manually added.

This command supports aliases.

=item B<rm> I<pkg ...>

Remove packages from package database. Does not remove dependencies.

This command supports aliases.

=item B<clean>

Remove unnecessary packages from package database. This command is the same
as running C<sbokeeper rm @unnecessary>.

=item B<deps> I<pkg>

Prints list of dependencies for I<pkg>. Does not print dependencies of those
dependencies, for that I'd recommend the B<tree> command. The dependency list
is according to the dependencies found in the database, not the dependencies
listed in the SlackBuild repo.

=item B<rdeps> I<pkg>

Prints a list of reverse dependencies for I<pkg> (packages that depend on
I<pkg>). Does not print reverse dependencies of those dependencies, for that I'd
recommend the B<rtree> command. The dependency list is according to the
dependencies found in the database, not those listed in the SlackBuild repo.

=item B<depadd> I<pkg> I<deps ...>

Add I<deps> to I<pkg>'s dependency list. Dependencies that are not present in
the database will automatically be added.

B<** IMPORTANT **>

This command provides an easy way for you to introduce circular dependencies
into your package database, which sbokeeper cannot handle and can leave your
database unable to be read. Refrain from carelessly using this command!

This command supports aliases for I<deps>, they do not work for I<pkg>.

=item B<deprm> I<pkg> I<deps ...>

Remove I<deps> from I<pkg>'s dependency list.

This command supports aliases for I<deps>, they do not work for I<pkg>.

=item B<pull>

Finds any SlackBuilds.org package installed on your system that is not present
in your package database and tries to add it to your database. All packages that
are added are marked as manually added. Packages that are already present in
your database are skipped.

=item B<diff>

Prints a list of SlackBuild packages that are present on your system but not in
your database and vice versa.

=item B<depwant>

Prints a list of packages that are, according to the SlackBuild repo, missing
dependencies in your database.

=item B<depextra>

Prints a list of packages with extra dependencies and said extra dependencies.
Extra dependencies are dependencies listed in the package database that are not
present in the SlackBuild repo.

=item B<unmanual> I<pkg ...>

Unset packages as being manually installed, but do not remove them from the
database.

This command supports aliases.

=item B<print> I<cat ...>

Print unique list of packages that are a part of the specified categories. The
following are valid categories:

=over 4

=item all

All packages present in the database.

=item manual

Packages that were manually added.

=item nonmanual

Packages that were not manually added.

=item necessary

Packages that were added manually or are a dependency of a manually added
package.

=item unnecessary

Packages that were not manually added or a dependency of a manually added
package.

=item missing

Packages that are not present in the database but are needed by packages in the
database.

=item untracked

SlackBuild packages that are installed on your system but not present in your
database.

=item phony

Packages that are present in your database that are not actually installed on
your system.

=back

If no I<cat> is specified, defaults to 'all'.

This command supports aliases.

=item B<tree> I<pkgs ...>

Prints a dependency tree. If I<pkgs> are not specified, prints a dependency tree
for each manually added package in the database. If I<pkgs> are specified,
prints a dependency tree for each package given.

A dependency tree will look something like this:

 libplacebo
   python3-meson-opt
     python3-build
       python3-pyproject-hooks
         python3-installer
           python3-flit_core
     python3-wheel
       python3-installer
         python3-flit_core
   python3-glad

This command supports aliases.

=item B<rtree> I<pkgs ...>

Same thing as B<tree>, but for reverse dependencies instead.

This command supports aliases.

=item B<dump>

Dumps contents of data file to I<stdout>.

=item B<help> I<cmd>

Print help message for specified command.

=back

=head2 Aliases

Some commands can accept aliases as arguments. An alias is an 'at' symbol (@)
followed by the package category it is aliasing. B<sbokeeper> will convert the
alias to the list of packages it is meant to represent. For example, if you
wanted to remove all packages from a package database, you could do:

 sbokeeper rm @all

and @all would be substituted for a list of every package present in the
database.

The following are valid aliases:

=over 4

=item @all

=item @manual

=item @nonmanual

=item @necessary

=item @unnecessary

=item @missing

=item @untracked

=item @phony

=back

Please refer to the documentation for the B<print> command for what each of
these categories mean.

=head1 OPTIONS

=over 4

=item B<-c> I<path>, B<--config>=I<path>

Specify the path to the configuration file.

=item B<-d> I<path>, B<--datafile>=I<path>

Specify the path to the data file.

=item B<-s> I<path>, B<--sbodir>=I<path>

Specify the path to the local SlackBuild repository.

=item B<-t> I<tag>, B<--tag>=I<tag>

Specify the tag used by your SlackBuild repo's packages.

=item B<-y>, B<--yes>

Automatically agree to any prompts.

=item B<-h>, B<--help>

Print help message and exit.

=item B<-v>, B<--version>

Print version and copyright information, then exit.

=back

=head1 DATA FILES

B<sbokeeper> stores SlackBuild information in data files. B<sbokeeper> is
designed so that you would ideally never have to edit these files by hand. It is
strongly discouraged that you edit these files, however if you do anyway,
carefully make sure that your edits are valid according to the data file format,
which will be described below.

B<sbokeeper> data files are text files. Data files contain packages, which are
a series of lines that contain package information ended by a pair of percentage
signs (%%). A package entry should look something like this:

 PACKAGE: libreoffice
 DEPS: avahi zulu-openjdk8
 MANUAL: 1
 %%

=over 4

=item PACKAGE

Name of the package, which must have a corresponding SlackBuild in the
configured SlackBuild repo. This must be the first line in a package entry.

=item DEPS

Whitespace-seperated list of packages that PACKAGE depends on. Each package
must be present in the SlackBuild repo.

=item MANUAL

Specifies whether the package was manually added or not. 1 for yes, 0 for no.

=item %%

Marks the end of the current package entry.

=back

=head1 AUTHOR

Written by Samuel Young E<lt>samyoung12788@gmail.comE<gt>.

=head1 RESTRICTIONS

B<sbokeeper> does not currently support tracking packages from multiple seperate
repos. A possible workaround would be to maintain seperate configurations for
each repo.

=head1 BUGS

B<sbokeeper> is (as of right now) incapable of handling circular dependencies.
If you stick with the official SlackBuild.org repos, this should not happen in
the wild. Circular dependencies can easily be introduced if one does not use
depadd carefully. So beware.

Report bugs on my Codeberg, L<https://codeberg.org/1-1sam>.

=head1 COPYRIGHT

Copyright (C) 2024-2025 Samuel Young

This program is free software; you can redistribute it and/or modify it under
the terms of either: the GNU General Public License as published by the Free
Software Foundation; or the Artistic License.

See L<https://dev.perl.org/licenses/> for more information.

=head1 SEE ALSO

L<sbopkg(1)>, L<sboui(1)>, L<slackpkg(1)>, L<pkgtool(1)>

=cut
