This introduces code which has long been maintained at:

https://github.com/falconindy/expac

>From the README:

expac is a data extraction tool for alpm databases. It features
printf-like flexibility and aims to be used as a simple tool for other
pacman based utilities which don't link against the library. It uses
pacman.conf as a config file for locating and loading your local and
sync databases.
---
v2:

* use alpm_pkgfrom_t instead of homegrown enum
* silently skip output when strftime fails
* split input from stdin on newlines only

 doc/Makefile.am        |   7 +-
 doc/expac.1.asciidoc   | 179 +++++++++
 src/pacman/.gitignore  |   2 +
 src/pacman/Makefile.am |   8 +-
 src/pacman/expac.c     | 858 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1051 insertions(+), 3 deletions(-)
 create mode 100644 doc/expac.1.asciidoc
 create mode 100644 src/pacman/expac.c

diff --git a/doc/Makefile.am b/doc/Makefile.am
index 2ac38cba..38f7077b 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -16,7 +16,8 @@ MANPAGES = \
        makepkg.conf.5 \
        pacman.conf.5 \
        libalpm.3 \
-       BUILDINFO.5
+       BUILDINFO.5 \
+       expac.1
 
 DOXYGEN_MANS = $(wildcard man3/*.3)
 
@@ -32,7 +33,8 @@ HTML_MANPAGES = \
        PKGBUILD.5.html \
        makepkg.conf.5.html \
        pacman.conf.5.html \
-       libalpm.3.html
+       libalpm.3.html \
+       expac.1.html
 
 HTML_OTHER = \
        index.html \
@@ -61,6 +63,7 @@ EXTRA_DIST = \
        pacman.conf.5.asciidoc \
        BUILDINFO.5.asciidoc \
        libalpm.3.asciidoc \
+       expac.1.asciidoc \
        footer.asciidoc \
        index.asciidoc \
        submitting-patches.asciidoc \
diff --git a/doc/expac.1.asciidoc b/doc/expac.1.asciidoc
new file mode 100644
index 00000000..f5ec3adf
--- /dev/null
+++ b/doc/expac.1.asciidoc
@@ -0,0 +1,179 @@
+expac(1)
+=========
+
+Name
+----
+expac - alpm data extraction utility
+
+Synopsis
+--------
+'expac' <format> <targets...> [options]
+
+Description
+-----------
+expac is a data extraction tool for alpm databases. It features printf-like
+flexibility and aims to be used as a simple tool for other pacman based
+utilities which don't link against the library. It uses pacman.conf as a config
+file for locating and loading your local and sync databases.
+
+Invoking expac consists of supplying a format string, which is generally
+described by one to many of the formatting tokens (see the 'FORMATTING'
+section), any relevant options and zero to many targets. The format string
+'must' be the first non-option argument. Targets can be a simple package name,
+a query string (in the case of a search), or in repo/package syntax when the
+-sync option is supplied.
+
+
+Options
+-------
+*-Q, \--query*::
+       Search the local database for provided targets. This is the default 
behavior.
+
+*-S, \--sync*::
+       Search the sync databases for provided targets.
+
+*-s, --search*::
+       Search for packages matching the strings specified by targets. This is a
+       boolean AND query and regex is allowed.
+
+*-g, --group*::
+       Return packages matching the specified targets as package groups.
+
+*\--config* <file>::
+       Read from <file> for alpm initialization instead of </etc/pacman.conf>.
+
+*-H, \--humansize* <size>::
+       Format package sizes in SI units according to <size>. Valid options are:
++
+B, K, M, G, T, P, E, Z, Y
+
+*-1, \--readone*::
+       Stop searching after the first result. This only has an effect on -S 
operations
+       without -s.
+
+*-d, \--delim <string>*::
+       Separate each package with the specified <string>. The default value is 
a
+       newline character.
+
+*-l, \--listdelim* <string>::
+       Separate each list item with the specified <string>. Lists are any 
interpreted
+       sequence specified with a capital letter. The default value is two 
spaces.
+
+*-p, \--file*::
+       Interpret targets as paths to local files.
+
+*-t, \--timefmt* <format>::
+       Output time described by the specified <format>. This string is passed 
directly
+       to linkman:strftime[3]. The default format is %c.
+
+*-v, \--verbose*::
+       Output more. `Package not found' errors will be shown, and empty field 
values
+       will display as 'None'.
+
+*-h, \--help*::
+
+Display the help message.
+
+*-V, \--version*::
+
+Display version information.
+
+Formatting
+----------
+
+The format argument allows the following interpreted sequences:
+
+  %B    backup files
+
+  %C    conflicts with (no version strings)
+
+  %D    depends on
+
+  %E    depends on (no version strings)
+
+  %F    files (only with -Q)
+
+  %G    groups
+
+  %H    conflicts with
+
+  %L    licenses
+
+  %N    required by
+
+  %O    optional deps
+
+  %o    optional deps (no descriptions)
+
+  %P    provides
+
+  %R    replaces (no version strings)
+
+  %T    replaces
+
+  %S    provides (no version strings)
+
+  %a    architecture
+
+  %b    build date
+
+  %d    description
+
+  %e    package base
+
+  %f    filename (only with -S)
+
+  %g    base64 encoded PGP signature (only with -S)
+
+  %h    sha256sum
+
+  %V    package validation method
+
+  %i    has install scriptlet (only with -Q)
+
+  %k    download size (only with -S)
+
+  %l    install date (only with -Q)
+
+  %m    install size
+
+  %M    modified backup files (only with -Q)
+
+  %n    package name
+
+  %p    packager name
+
+  %r    repo
+
+  %s    md5sum
+
+  %u    project URL
+
+  %v    version
+
+  %w    install reason (only with -Q)
+
+  %!    result number (auto-incremented counter, starts at 0)
+
+  %%    literal %
+
+Note that for any lowercase tokens aside from %m and %k, full printf support is
+allowed, e.g. %-20n. This does not apply to any list based, date, or numerical
+output.
+
+Standard backslash escape sequences are supported, as per linkman:printf[1].
+
+Examples
+--------
+
+expac -Ss \'%r/%n %v\n    %d\' <search terms>::
+       Emulate pacman\'s search function.
+
+expac --timefmt=%s \'%b\t%n\' | sort -n | head -10::
+       List the oldest 10 installed packages (by build date).
+
+See Also
+--------
+linkman:libalpm[3], linkman:pacman.conf[5]
+
+include::footer.asciidoc[]
diff --git a/src/pacman/.gitignore b/src/pacman/.gitignore
index 9889c35e..78b78159 100644
--- a/src/pacman/.gitignore
+++ b/src/pacman/.gitignore
@@ -5,3 +5,5 @@ pacman
 pacman.exe
 pacman-conf
 pacman-conf.exe
+expac
+expac.exe
diff --git a/src/pacman/Makefile.am b/src/pacman/Makefile.am
index 15cf20ce..deb386ab 100644
--- a/src/pacman/Makefile.am
+++ b/src/pacman/Makefile.am
@@ -21,7 +21,7 @@ libbasic_la_SOURCES = \
 libbasic_la_LIBADD = \
        $(top_builddir)/lib/libalpm/.libs/libalpm.la
 
-bin_PROGRAMS = pacman pacman-conf
+bin_PROGRAMS = pacman pacman-conf expac
 
 AM_CPPFLAGS = \
        -imacros $(top_builddir)/config.h \
@@ -66,3 +66,9 @@ pacman_conf_SOURCES = pacman-conf.c
 pacman_conf_LDADD = \
        libbasic.la \
        $(top_builddir)/lib/libalpm/.libs/libalpm.la
+
+expac_SOURCES = expac.c
+
+expac_LDADD = \
+       libbasic.la \
+       $(top_builddir)/lib/libalpm/.libs/libalpm.la
diff --git a/src/pacman/expac.c b/src/pacman/expac.c
new file mode 100644
index 00000000..7d993cf6
--- /dev/null
+++ b/src/pacman/expac.c
@@ -0,0 +1,858 @@
+/*
+ *  expac.c
+ *
+ *  Copyright (c) 2018 Pacman Development Team <[email protected]>
+ *  Copyright (c) 2010-2018 by Dave Reisner <[email protected]>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <glob.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <alpm.h>
+
+#include "conf.h"
+#include "util.h"
+
+#define DEFAULT_DELIM        "\n"
+#define DEFAULT_LISTDELIM    "  "
+#define DEFAULT_TIMEFMT      "%c"
+#define SIZE_TOKENS          "BKMGTPEZY\0"
+
+#if defined(GIT_VERSION)
+#undef PACKAGE_VERSION
+#define PACKAGE_VERSION GIT_VERSION
+#endif
+
+static char const digits[] = "0123456789";
+static char const printf_flags[] = "'-+ #0I";
+
+typedef enum search_what_t {
+       SEARCH_EXACT,
+       SEARCH_GROUPS,
+       SEARCH_REGEX,
+} search_what_t;
+
+typedef struct expac_t {
+       config_t *config;
+} expac_t;
+
+bool opt_readone = false;
+bool opt_verbose = false;
+char opt_humansize = 'B';
+alpm_pkgfrom_t opt_corpus = ALPM_PKG_FROM_LOCALDB;
+search_what_t opt_what = SEARCH_EXACT;
+const char *opt_format = NULL;
+const char *opt_timefmt = DEFAULT_TIMEFMT;
+const char *opt_listdelim = DEFAULT_LISTDELIM;
+const char *opt_delim = DEFAULT_DELIM;
+const char *opt_config_file = CONFFILE;
+int opt_pkgcounter = 0;
+
+typedef const char *(*extractfn)(void*);
+
+static int is_valid_size_unit(char *u)
+{
+       return u[0] != '\0' && u[1] == '\0' &&
+               memchr(SIZE_TOKENS, *u, strlen(SIZE_TOKENS)) != NULL;
+}
+
+static const char *alpm_backup_get_name(alpm_backup_t *bkup)
+{
+       return bkup->name;
+}
+
+static char *size_to_string(off_t pkgsize)
+{
+       static char out[64];
+
+       if(opt_humansize == 'B') {
+               snprintf(out, sizeof(out), "%jd", (intmax_t)pkgsize);
+       } else {
+               snprintf(out, sizeof(out), "%.2f %ciB", humanize_size(pkgsize, 
opt_humansize, 0, NULL), opt_humansize);
+       }
+
+       return out;
+}
+
+static char *format_optdep(alpm_depend_t *optdep)
+{
+       char *out = NULL;
+
+       if(asprintf(&out, "%s: %s", optdep->name, optdep->desc) < 0) {
+               return NULL;
+       }
+
+       return out;
+}
+
+static const char *alpm_dep_get_name(alpm_depend_t *dep)
+{
+       return dep->name;
+}
+
+static void usage(void)
+{
+       fprintf(stderr, "expac %s\n"
+                       "Usage: expac [options] <format> target...\n\n", 
PACKAGE_VERSION);
+       fprintf(stderr,
+                       " Options:\n"
+                       "  -Q, --query               search local DB 
(default)\n"
+                       "  -S, --sync                search sync DBs\n"
+                       "  -s, --search              search for matching 
regex\n"
+                       "  -g, --group               return packages matching 
targets as groups\n"
+                       "  -H, --humansize <size>    format package sizes in SI 
units (default: bytes)\n"
+                       "  -1, --readone             return only the first 
result of a sync search\n\n"
+                       "  -d, --delim <string>      separator used between 
packages (default: \"\\n\")\n"
+                       "  -l, --listdelim <string>  separator used between 
list elements (default: \"  \")\n"
+                       "  -p, --file                query local files instead 
of the DB\n"
+                       "  -t, --timefmt <fmt>       date format passed to 
strftime (default: \"%%c\")\n"
+                       "      --config <file>       read from <file> for alpm 
initialization (default: /etc/pacman.conf)\n\n"
+                       "  -v, --verbose             be more verbose\n\n"
+                       "  -V, --version             display version 
information\n"
+                       "  -h, --help                display this help 
information\n\n"
+                       "For more details see expac(1).\n");
+}
+
+static int parse_options(int *argc, char **argv[])
+{
+       static struct option opts[] = {
+               {"readone",   no_argument,        0, '1'},
+               {"delim",     required_argument,  0, 'd'},
+               {"listdelim", required_argument,  0, 'l'},
+               {"group",     required_argument,  0, 'g'},
+               {"help",      no_argument,        0, 'h'},
+               {"file",      no_argument,        0, 'p'},
+               {"humansize", required_argument,  0, 'H'},
+               {"query",     no_argument,        0, 'Q'},
+               {"sync",      no_argument,        0, 'S'},
+               {"search",    no_argument,        0, 's'},
+               {"timefmt",   required_argument,  0, 't'},
+               {"verbose",   no_argument,        0, 'v'},
+               {"version",   no_argument,        0, 'V'},
+               {"config",    required_argument,  0, 128},
+               {0, 0, 0, 0}
+       };
+
+       for(;;) {
+               int opt;
+
+               opt = getopt_long(*argc, *argv, "1l:d:gH:hf:pQSst:Vv", opts, 
NULL);
+               if(opt < 0) {
+                       break;
+               }
+
+               switch (opt) {
+                       case 'S':
+                               opt_corpus = ALPM_PKG_FROM_SYNCDB;
+                               break;
+                       case 'Q':
+                               opt_corpus = ALPM_PKG_FROM_LOCALDB;
+                               break;
+                       case '1':
+                               opt_readone = true;
+                               break;
+                       case 'd':
+                               opt_delim = optarg;
+                               break;
+                       case 'g':
+                               opt_what = SEARCH_GROUPS;
+                               break;
+                       case 'l':
+                               opt_listdelim = optarg;
+                               break;
+                       case 'H':
+                               if(!is_valid_size_unit(optarg)) {
+                                       fprintf(stderr, "error: invalid SI size 
formatter: %s\n", optarg);
+                                       return -1;
+                               }
+                               opt_humansize = *optarg;
+                               break;
+                       case 'h':
+                               usage();
+                               exit(0);
+                       case 'p':
+                               opt_corpus = ALPM_PKG_FROM_FILE;
+                               break;
+                       case 's':
+                               opt_what = SEARCH_REGEX;
+                               break;
+                       case 't':
+                               opt_timefmt = optarg;
+                               break;
+                       case 'v':
+                               opt_verbose = true;
+                               break;
+                       case 128:
+                               opt_config_file = optarg;
+                               break;
+                       case 'V':
+                               printf("expac v%s\n", PACKAGE_VERSION);
+                               break;
+
+                       case '?':
+                               return -EINVAL;
+                       default:
+                               return -EINVAL;
+               }
+       }
+
+       if(optind < *argc) {
+               opt_format = (*argv)[optind++];
+       } else {
+               fprintf(stderr, "error: missing format string (use -h for 
help)\n");
+               return -EINVAL;
+       }
+
+       *argc -= optind;
+       *argv += optind;
+
+       return 0;
+}
+
+static int print_escaped(const char *delim)
+{
+       const char *f;
+       int out = 0;
+
+       for(f = delim; *f != '\0'; f++) {
+               if(*f == '\\') {
+                       switch (*++f) {
+                               case '\\':
+                                       fputc('\\', stdout);
+                                       break;
+                               case '"':
+                                       fputc('\"', stdout);
+                                       break;
+                               case 'a':
+                                       fputc('\a', stdout);
+                                       break;
+                               case 'b':
+                                       fputc('\b', stdout);
+                                       break;
+                               case 'e': /* \e is nonstandard */
+                                       fputc('\033', stdout);
+                                       break;
+                               case 'n':
+                                       fputc('\n', stdout);
+                                       break;
+                               case 'r':
+                                       fputc('\r', stdout);
+                                       break;
+                               case 't':
+                                       fputc('\t', stdout);
+                                       break;
+                               case 'v':
+                                       fputc('\v', stdout);
+                                       break;
+                               case '0':
+                                       fputc('\0', stdout);
+                                       break;
+                               default:
+                                       fputc(*f, stdout);
+                                       break;
+                       }
+                       ++out;
+               } else {
+                       fputc(*f, stdout);
+                       ++out;
+               }
+       }
+
+       return out;
+}
+
+static int print_list(alpm_list_t *list, extractfn fn)
+{
+       alpm_list_t *i;
+       int out = 0;
+
+       if(!list) {
+               if(opt_verbose) {
+                       out += printf("None");
+               }
+               return out;
+       }
+
+       i = list;
+       for(;;) {
+               const char *item = fn ? fn(i->data) : i->data;
+               if(item == NULL) {
+                       continue;
+               }
+
+               out += printf("%s", item);
+
+               if((i = i->next)) {
+                       out += print_escaped(opt_listdelim);
+               } else {
+                       break;
+               }
+       }
+
+       return out;
+}
+
+static int print_allocated_list(alpm_list_t *list, extractfn fn)
+{
+       int out = print_list(list, fn);
+       alpm_list_free(list);
+       return out;
+}
+
+static int print_time(time_t timestamp) {
+       char buffer[64];
+       int out = 0;
+
+       if(!timestamp) {
+               if(opt_verbose) {
+                       out += printf("None");
+               }
+               return out;
+       }
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+       /* no overflow here, strftime prints a max of 64 including null */
+       if(strftime(buffer, sizeof(buffer), opt_timefmt, localtime(&timestamp)) 
> 0) {
+               out += printf("%s", buffer);
+       }
+#pragma GCC diagnostic pop
+
+       return out;
+}
+
+static int print_filelist(alpm_filelist_t *filelist)
+{
+       int out = 0;
+       size_t i;
+
+       for(i = 0; i < filelist->count; i++) {
+               out += printf("%s", (filelist->files + i)->name);
+               if(i < filelist->count - 1) {
+                       out += print_escaped(opt_listdelim);
+               }
+       }
+
+       return out;
+}
+
+static bool backup_file_is_modified(const alpm_backup_t *backup_file)
+{
+       char fullpath[PATH_MAX];
+       char *md5sum = NULL;
+       bool modified;
+
+       snprintf(fullpath, sizeof(fullpath), "%s%s", config->rootdir, 
backup_file->name);
+
+       md5sum = alpm_compute_md5sum(fullpath);
+       if(md5sum == NULL) {
+               return false;
+       }
+
+       modified = strcmp(md5sum, backup_file->hash) != 0;
+       free(md5sum);
+
+       return modified;
+}
+
+static alpm_list_t *get_modified_files(alpm_pkg_t *pkg)
+{
+       alpm_list_t *i, *modified_files = NULL;
+
+       for(i = alpm_pkg_get_backup(pkg); i; i = i->next) {
+               const alpm_backup_t *backup = i->data;
+               if(backup->hash && backup_file_is_modified(backup)) {
+                       modified_files = alpm_list_add(modified_files, 
backup->name);
+               }
+       }
+
+       return modified_files;
+}
+
+static alpm_list_t *get_validation_method(alpm_pkg_t *pkg)
+{
+       alpm_list_t *validation = NULL;
+
+       alpm_pkgvalidation_t v = alpm_pkg_get_validation(pkg);
+
+       if(v == ALPM_PKG_VALIDATION_UNKNOWN) {
+               return alpm_list_add(validation, (void*)"Unknown");
+       }
+
+       if(v & ALPM_PKG_VALIDATION_NONE) {
+               return alpm_list_add(validation, (void*)"None");
+       }
+
+       if(v & ALPM_PKG_VALIDATION_MD5SUM) {
+               validation = alpm_list_add(validation, (void*)"MD5 Sum");
+       }
+       if(v & ALPM_PKG_VALIDATION_SHA256SUM) {
+               validation = alpm_list_add(validation, (void*)"SHA256 Sum");
+       }
+       if(v & ALPM_PKG_VALIDATION_SIGNATURE) {
+               validation = alpm_list_add(validation, (void*)"Signature");
+       }
+
+       return validation;
+}
+
+static void print_pkg(alpm_pkg_t *pkg, const char *format)
+{
+       const char *f, *end;
+       int out = 0;
+
+       end = format + strlen(format);
+
+       for(f = format; f < end; f++) {
+               if(*f == '%') {
+                       char fmt[64] = {0};
+                       int l = 1;
+
+                       l += strspn(f + l, printf_flags);
+                       l += strspn(f + l, digits);
+                       memcpy(fmt, f, l);
+                       fmt[l] = 's';
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+                       f += l;
+                       switch (*f) {
+                               /* simple attributes */
+                               case 'f': /* filename */
+                                       out += printf(fmt, 
alpm_pkg_get_filename(pkg));
+                                       break;
+                               case 'e': /* package base */
+                                       out += printf(fmt, 
alpm_pkg_get_base(pkg));
+                                       break;
+                               case 'n': /* package name */
+                                       out += printf(fmt, 
alpm_pkg_get_name(pkg));
+                                       break;
+                               case 'v': /* version */
+                                       out += printf(fmt, 
alpm_pkg_get_version(pkg));
+                                       break;
+                               case 'd': /* description */
+                                       out += printf(fmt, 
alpm_pkg_get_desc(pkg));
+                                       break;
+                               case 'u': /* project url */
+                                       out += printf(fmt, 
alpm_pkg_get_url(pkg));
+                                       break;
+                               case 'p': /* packager name */
+                                       out += printf(fmt, 
alpm_pkg_get_packager(pkg));
+                                       break;
+                               case 's': /* md5sum */
+                                       out += printf(fmt, 
alpm_pkg_get_md5sum(pkg));
+                                       break;
+                               case 'a': /* architecture */
+                                       out += printf(fmt, 
alpm_pkg_get_arch(pkg));
+                                       break;
+                               case 'i': /* has install scriptlet? */
+                                       out += printf(fmt, 
alpm_pkg_has_scriptlet(pkg) ? "yes" : "no");
+                                       break;
+                               case 'r': /* repo */
+                                       out += printf(fmt, 
alpm_db_get_name(alpm_pkg_get_db(pkg)));
+                                       break;
+                               case 'w': /* install reason */
+                                       out += printf(fmt, 
alpm_pkg_get_reason(pkg) ? "dependency" : "explicit");
+                                       break;
+                               case '!': /* result number */
+                                       fmt[strlen(fmt)-1] = 'd';
+                                       out += printf(fmt, opt_pkgcounter++);
+                                       break;
+                               case 'g': /* base64 gpg sig */
+                                       out += printf(fmt, 
alpm_pkg_get_base64_sig(pkg));
+                                       break;
+                               case 'h': /* sha256sum */
+                                       out += printf(fmt, 
alpm_pkg_get_sha256sum(pkg));
+                                       break;
+
+                               /* times */
+                               case 'b': /* build date */
+                                       out += 
print_time(alpm_pkg_get_builddate(pkg));
+                                       break;
+                               case 'l': /* install date */
+                                       out += 
print_time(alpm_pkg_get_installdate(pkg));
+                                       break;
+
+                               /* sizes */
+                               case 'k': /* download size */
+                                       out += printf(fmt, 
size_to_string(alpm_pkg_get_size(pkg)));
+                                       break;
+                               case 'm': /* install size */
+                                       out += printf(fmt, 
size_to_string(alpm_pkg_get_isize(pkg)));
+                                       break;
+
+                               /* lists */
+                               case 'F': /* files */
+                                       out += 
print_filelist(alpm_pkg_get_files(pkg));
+                                       break;
+                               case 'N': /* requiredby */
+                                       out += 
print_list(alpm_pkg_compute_requiredby(pkg), NULL);
+                                       break;
+                               case 'L': /* licenses */
+                                       out += 
print_list(alpm_pkg_get_licenses(pkg), NULL);
+                                       break;
+                               case 'G': /* groups */
+                                       out += 
print_list(alpm_pkg_get_groups(pkg), NULL);
+                                       break;
+                               case 'E': /* depends (shortdeps) */
+                                       out += 
print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_get_name);
+                                       break;
+                               case 'D': /* depends */
+                                       out += 
print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_compute_string);
+                                       break;
+                               case 'O': /* optdepends */
+                                       out += 
print_list(alpm_pkg_get_optdepends(pkg), (extractfn)format_optdep);
+                                       break;
+                               case 'o': /* optdepends (shortdeps) */
+                                       out += 
print_list(alpm_pkg_get_optdepends(pkg), (extractfn)alpm_dep_get_name);
+                                       break;
+                               case 'H': /* conflicts */
+                                       out += 
print_list(alpm_pkg_get_conflicts(pkg), (extractfn)alpm_dep_compute_string);
+                                       break;
+                               case 'C': /* conflicts (shortdeps) */
+                                       out += 
print_list(alpm_pkg_get_conflicts(pkg), (extractfn)alpm_dep_get_name);
+                                       break;
+                               case 'S': /* provides (shortdeps) */
+                                       out += 
print_list(alpm_pkg_get_provides(pkg), (extractfn)alpm_dep_get_name);
+                                       break;
+                               case 'P': /* provides */
+                                       out += 
print_list(alpm_pkg_get_provides(pkg), (extractfn)alpm_dep_compute_string);
+                                       break;
+                               case 'R': /* replaces (shortdeps) */
+                                       out += 
print_list(alpm_pkg_get_replaces(pkg), (extractfn)alpm_dep_get_name);
+                                       break;
+                               case 'T': /* replaces */
+                                       out += 
print_list(alpm_pkg_get_replaces(pkg), (extractfn)alpm_dep_compute_string);
+                                       break;
+                               case 'B': /* backup */
+                                       out += 
print_list(alpm_pkg_get_backup(pkg), (extractfn)alpm_backup_get_name);
+                                       break;
+                               case 'V': /* package validation */
+                                       out += 
print_allocated_list(get_validation_method(pkg), NULL);
+                                       break;
+                               case 'M': /* modified */
+                                       out += 
print_allocated_list(get_modified_files(pkg), NULL);
+                                       break;
+                               case '%':
+                                       fputc('%', stdout);
+                                       out++;
+                                       break;
+                               default:
+                                       fputc('?', stdout);
+                                       out++;
+                                       break;
+                       }
+               } else if(*f == '\\') {
+                       char esc[3] = { f[0], f[1], '\0' };
+                       out += print_escaped(esc);
+                       ++f;
+               } else {
+                       fputc(*f, stdout);
+                       out++;
+               }
+       }
+#pragma GCC diagnostic pop
+
+       /* only print a delimeter if any package data was outputted */
+       if(out > 0) {
+               print_escaped(opt_delim);
+       }
+}
+
+static alpm_list_t *all_packages(alpm_list_t *dbs)
+{
+       alpm_list_t *i, *packages = NULL;
+
+       for(i = dbs; i; i = i->next) {
+               packages = alpm_list_join(packages, 
alpm_list_copy(alpm_db_get_pkgcache(i->data)));
+       }
+
+       return packages;
+}
+
+static alpm_list_t *search_packages(alpm_list_t *dbs, alpm_list_t *targets)
+{
+       alpm_list_t *i, *packages = NULL;
+
+       for(i = dbs; i; i = i->next) {
+               packages = alpm_list_join(packages, alpm_db_search(i->data, 
targets));
+       }
+
+       return packages;
+}
+
+static alpm_list_t *search_groups(alpm_list_t *dbs, alpm_list_t *groupnames)
+{
+       alpm_list_t *i, *j, *packages = NULL;
+
+       for(i = groupnames; i; i = i->next) {
+               for(j = dbs; j; j = j->next) {
+                       alpm_group_t *grp = alpm_db_get_group(j->data, i->data);
+                       if(grp != NULL) {
+                               packages = alpm_list_join(packages, 
alpm_list_copy(grp->packages));
+                       }
+               }
+       }
+
+       return packages;
+}
+
+static alpm_list_t *search_exact(alpm_list_t *dblist, alpm_list_t *targets)
+{
+       alpm_list_t *results = NULL;
+
+       /* resolve each target individually from the repo pool */
+       for(alpm_list_t *t = targets; t; t = t->next) {
+               char *pkgname, *reponame;
+               alpm_list_t *r;
+               int found = 0;
+
+               pkgname = reponame = t->data;
+               if(strchr(pkgname, '/')) {
+                       strsep(&pkgname, "/");
+               } else {
+                       reponame = NULL;
+               }
+
+               for(r = dblist; r; r = r->next) {
+                       alpm_db_t *repo = r->data;
+                       alpm_pkg_t *pkg;
+
+                       if(reponame && strcmp(reponame, alpm_db_get_name(repo)) 
!= 0) {
+                               continue;
+                       }
+
+                       pkg = alpm_db_get_pkg(repo, pkgname);
+                       if(pkg == NULL) {
+                               continue;
+                       }
+
+                       found = 1;
+                       results = alpm_list_add(results, pkg);
+                       if(opt_readone) {
+                               break;
+                       }
+               }
+
+               if(!found && opt_verbose) {
+                       fprintf(stderr, "error: package `%s' not found\n", 
pkgname);
+               }
+       }
+
+       return results;
+}
+
+static alpm_list_t *resolve_targets(alpm_list_t *dblist, alpm_list_t *targets)
+{
+       if(targets == NULL) {
+               return all_packages(dblist);
+       }
+
+       if(opt_what == SEARCH_REGEX) {
+               return search_packages(dblist, targets);
+       }
+
+       if(opt_what == SEARCH_GROUPS) {
+               return search_groups(dblist, targets);
+       }
+
+       return search_exact(dblist, targets);
+}
+
+static void expac_free(expac_t *expac)
+{
+       free(expac);
+       config_free(config);
+}
+
+static int expac_new(expac_t **expac, const char *config_file)
+{
+       expac_t *e;
+
+       config = config_new();
+
+       e = calloc(1, sizeof(*e));
+       if(e == NULL) {
+               return -ENOMEM;
+       }
+
+       if(parseconfig(config_file) != 0) {
+               fprintf(stderr, "error parsing '%s'\n", config_file);
+               return -EINVAL;
+       }
+
+       e->config = config;
+       *expac = e;
+
+       return 0;
+}
+
+static alpm_list_t *expac_search_files(expac_t *expac, alpm_list_t *targets)
+{
+       alpm_list_t *i, *r = NULL;
+
+       for(i = targets; i; i = i->next) {
+               const char *path = i->data;
+               alpm_pkg_t *pkg;
+
+               if(alpm_pkg_load(expac->config->handle, path, 0, 0, &pkg) != 0) 
{
+                       fprintf(stderr, "error: %s: %s\n", path,
+                                       
alpm_strerror(alpm_errno(expac->config->handle)));
+                       continue;
+               }
+
+               r = alpm_list_add(r, pkg);
+       }
+
+       return r;
+}
+
+static alpm_list_t *expac_search_local(expac_t *expac, alpm_list_t *targets)
+{
+       alpm_list_t *dblist, *r;
+
+       dblist = alpm_list_add(NULL, alpm_get_localdb(expac->config->handle));
+       r = resolve_targets(dblist, targets);
+       alpm_list_free(dblist);
+
+       return r;
+}
+
+static alpm_list_t *expac_search_sync(expac_t *expac, alpm_list_t *targets)
+{
+       return resolve_targets(alpm_get_syncdbs(expac->config->handle), 
targets);
+}
+
+static alpm_list_t *expac_search(
+               expac_t *expac, alpm_pkgfrom_t corpus, alpm_list_t *targets)
+{
+       switch (corpus) {
+       case ALPM_PKG_FROM_LOCALDB:
+               return expac_search_local(expac, targets);
+       case ALPM_PKG_FROM_SYNCDB:
+               return expac_search_sync(expac, targets);
+       case ALPM_PKG_FROM_FILE:
+               return expac_search_files(expac, targets);
+       }
+
+       /* should be unreachable */
+       return NULL;
+}
+
+static int read_targets_from_stdin(alpm_list_t **targets)
+{
+       char *line;
+       size_t line_size = 0;
+       ssize_t nread;
+       int added = 0;
+
+       while((nread = getline(&line, &line_size, stdin)) != -1) {
+               if(line[nread - 1] == '\n') {
+                       /* remove trailing newline */
+                       line[nread - 1] = '\0';
+               }
+               if(line[0] == '\0') {
+                       /* skip empty lines */
+                       continue;
+               }
+               if(!alpm_list_append_strdup(targets, line)) {
+                       return -ENOMEM;
+               }
+
+               added++;
+       }
+
+       return added;
+}
+
+static int process_targets(int argc, char **argv, alpm_list_t **targets)
+{
+       int allow_stdin;
+
+       allow_stdin = !isatty(STDIN_FILENO);
+
+       for(int i = 0; i < argc; ++i) {
+               if(allow_stdin && strcmp(argv[i], "-") == 0) {
+                       int k;
+
+                       k = read_targets_from_stdin(targets);
+                       if(k < 0) {
+                               fprintf(stderr, "error: failed to read targets 
from stdin: %s\n", strerror(-k));
+                               return k;
+                       }
+
+                       if(k == 0) {
+                               fputs("error: argument '-' specified with empty 
stdin\n", stderr);
+                               return -1;
+                       }
+
+                       allow_stdin = 0;
+               } else {
+                       alpm_list_append_strdup(targets, argv[i]);
+               }
+       }
+
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       alpm_list_t *results = NULL, *targets = NULL;
+       expac_t *expac = NULL;
+       int r;
+
+       r = parse_options(&argc, &argv);
+       if(r < 0) {
+               return 1;
+       }
+
+       r = process_targets(argc, argv, &targets);
+       if(r < 0) {
+               return 1;
+       }
+
+       r = expac_new(&expac, opt_config_file);
+       if(r < 0) {
+               return 1;
+       }
+
+       results = expac_search(expac, opt_corpus, targets);
+       if(results == NULL) {
+               return 1;
+       }
+
+       for(alpm_list_t *i = results; i; i = i->next) {
+               print_pkg(i->data, opt_format);
+       }
+
+       alpm_list_free_inner(targets, free);
+       alpm_list_free(targets);
+       alpm_list_free(results);
+       expac_free(expac);
+
+       return 0;
+}
+
+/* vim: set ts=2 sw=2 noet: */
-- 
2.18.0

Reply via email to