pactree: Add "--config" option
[pacman-ng.git] / src / util / pactree.c
blob5e98f794c1e7a655d3fb586f605c2b5801fa401b
1 /*
2 * pactree.c - a simple dependency tree viewer
4 * Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@archlinux.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "config.h"
22 #include <ctype.h>
23 #include <getopt.h>
24 #include <stdio.h>
25 #include <string.h>
27 #include <alpm.h>
28 #include <alpm_list.h>
30 #define LINE_MAX 512
32 /* output */
33 struct graph_style {
34 const char *provides;
35 const char *tip1;
36 const char *tip2;
37 int indent;
40 static struct graph_style graph_default = {
41 " provides",
42 "|--",
43 "+--",
47 static struct graph_style graph_linear = {
48 "",
49 "",
50 "",
54 /* color choices */
55 struct color_choices {
56 const char *branch1;
57 const char *branch2;
58 const char *leaf1;
59 const char *leaf2;
60 const char *off;
63 static struct color_choices use_color = {
64 "\033[0;33m", /* yellow */
65 "\033[0;37m", /* white */
66 "\033[1;32m", /* bold green */
67 "\033[0;32m", /* green */
68 "\033[0m"
71 static struct color_choices no_color = {
72 "",
73 "",
74 "",
75 "",
79 /* long operations */
80 enum {
81 OP_CONFIG = 1000
84 /* globals */
85 alpm_handle_t *handle = NULL;
86 alpm_list_t *walked = NULL;
87 alpm_list_t *provisions = NULL;
89 /* options */
90 struct color_choices *color = &no_color;
91 struct graph_style *style = &graph_default;
92 int graphviz = 0;
93 int max_depth = -1;
94 int reverse = 0;
95 int unique = 0;
96 int searchsyncs = 0;
97 const char *dbpath = DBPATH;
98 const char *configfile = CONFFILE;
100 #ifndef HAVE_STRNDUP
101 /* A quick and dirty implementation derived from glibc */
102 static size_t strnlen(const char *s, size_t max)
104 register const char *p;
105 for(p = s; *p && max--; ++p);
106 return (p - s);
109 char *strndup(const char *s, size_t n)
111 size_t len = strnlen(s, n);
112 char *new = (char *) malloc(len + 1);
114 if(new == NULL)
115 return NULL;
117 new[len] = '\0';
118 return (char *)memcpy(new, s, len);
120 #endif
122 static char *strtrim(char *str)
124 char *pch = str;
126 if(str == NULL || *str == '\0') {
127 /* string is empty, so we're done. */
128 return str;
131 while(isspace((unsigned char)*pch)) {
132 pch++;
134 if(pch != str) {
135 size_t len = strlen(pch);
136 if(len) {
137 memmove(str, pch, len + 1);
138 } else {
139 *str = '\0';
143 /* check if there wasn't anything but whitespace in the string. */
144 if(*str == '\0') {
145 return str;
148 pch = (str + (strlen(str) - 1));
149 while(isspace((unsigned char)*pch)) {
150 pch--;
152 *++pch = '\0';
154 return str;
157 static int register_syncs(void) {
158 FILE *fp;
159 char *ptr, *section = NULL;
160 char line[LINE_MAX];
161 const alpm_siglevel_t level = ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL;
163 fp = fopen(configfile, "r");
164 if(!fp) {
165 return 1;
168 while(fgets(line, LINE_MAX, fp)) {
169 strtrim(line);
171 if(line[0] == '#' || !strlen(line)) {
172 continue;
175 if((ptr = strchr(line, '#'))) {
176 *ptr = '\0';
177 strtrim(line);
180 if(line[0] == '[' && line[strlen(line) - 1] == ']') {
181 free(section);
182 section = strndup(&line[1], strlen(line) - 2);
184 if(section && strcmp(section, "options") != 0) {
185 alpm_db_register_sync(handle, section, level);
190 free(section);
191 fclose(fp);
193 return 0;
196 static int parse_options(int argc, char *argv[])
198 int opt, option_index = 0;
199 char *endptr = NULL;
201 static const struct option opts[] = {
202 {"dbpath", required_argument, 0, 'b'},
203 {"color", no_argument, 0, 'c'},
204 {"depth", required_argument, 0, 'd'},
205 {"graph", no_argument, 0, 'g'},
206 {"help", no_argument, 0, 'h'},
207 {"linear", no_argument, 0, 'l'},
208 {"reverse", no_argument, 0, 'r'},
209 {"sync", no_argument, 0, 'S'},
210 {"unique", no_argument, 0, 'u'},
212 {"config", required_argument, 0, OP_CONFIG},
213 {0, 0, 0, 0}
216 while((opt = getopt_long(argc, argv, "b:cd:ghlrsu", opts, &option_index))) {
217 if(opt < 0) {
218 break;
221 switch(opt) {
222 case OP_CONFIG:
223 configfile = optarg;
224 break;
225 case 'b':
226 dbpath = optarg;
227 break;
228 case 'c':
229 color = &use_color;
230 break;
231 case 'd':
232 /* validate depth */
233 max_depth = (int)strtol(optarg, &endptr, 10);
234 if(*endptr != '\0') {
235 fprintf(stderr, "error: invalid depth -- %s\n", optarg);
236 return 1;
238 break;
239 case 'g':
240 graphviz = 1;
241 break;
242 case 'l':
243 style = &graph_linear;
244 break;
245 case 'r':
246 reverse = 1;
247 break;
248 case 's':
249 searchsyncs = 1;
250 break;
251 case 'u':
252 unique = 1;
253 style = &graph_linear;
254 break;
255 case 'h':
256 case '?':
257 default:
258 return 1;
262 if(!argv[optind]) {
263 return 1;
266 return 0;
269 static void usage(void)
271 fprintf(stderr, "pactree v" PACKAGE_VERSION "\n"
272 "Usage: pactree [options] PACKAGE\n\n"
273 " -b, --dbpath <path> set an alternate database location\n"
274 " -c, --color colorize output\n"
275 " -d, --depth <#> limit the depth of recursion\n"
276 " -g, --graph generate output for graphviz's dot\n"
277 " -h, --help display this help message\n"
278 " -l, --linear enable linear output\n"
279 " -r, --reverse show reverse dependencies\n"
280 " -s, --sync search sync DBs instead of local\n"
281 " -u, --unique show dependencies with no duplicates (implies -l)\n"
282 " --config <path> set an alternate configuration file\n");
285 static void cleanup(void)
287 alpm_list_free(walked);
288 alpm_list_free(provisions);
289 alpm_release(handle);
292 /* pkg provides provision */
293 static void print_text(const char *pkg, const char *provision, int depth)
295 int indent_sz = (depth + 1) * style->indent;
297 if(!pkg && !provision) {
298 /* not much we can do */
299 return;
302 if(!pkg && provision) {
303 /* we failed to resolve provision */
304 printf("%s%*s%s%s%s [unresolvable]%s\n", color->branch1, indent_sz,
305 style->tip1, color->leaf1, provision, color->branch1, color->off);
306 } else if(provision && strcmp(pkg, provision) != 0) {
307 /* pkg provides provision */
308 printf("%s%*s%s%s%s%s %s%s%s\n", color->branch2, indent_sz, style->tip2,
309 color->leaf1, pkg, color->leaf2, style->provides, color->leaf1, provision,
310 color->off);
311 } else {
312 /* pkg is a normal package */
313 printf("%s%*s%s%s%s\n", color->branch1, indent_sz, style->tip1, color->leaf1,
314 pkg, color->off);
318 static void print_graph(const char *parentname, const char *pkgname, const char *depname)
320 if(depname) {
321 printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
322 if(pkgname && strcmp(depname, pkgname) != 0 && !alpm_list_find_str(provisions, depname)) {
323 printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
324 provisions = alpm_list_add(provisions, (char *)depname);
326 } else if(pkgname) {
327 printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
331 /* parent depends on dep which is satisfied by pkg */
332 static void print(const char *parentname, const char *pkgname, const char *depname, int depth)
334 if(graphviz) {
335 print_graph(parentname, pkgname, depname);
336 } else {
337 print_text(pkgname, depname, depth);
341 static void print_start(const char *pkgname, const char *provname)
343 if(graphviz) {
344 printf("digraph G { START [color=red, style=filled];\n"
345 "node [style=filled, color=green];\n"
346 " \"START\" -> \"%s\";\n", pkgname);
347 } else {
348 print_text(pkgname, provname, 0);
352 static void print_end(void)
354 if(graphviz) {
355 /* close graph output */
356 printf("}\n");
360 static alpm_pkg_t *get_pkg_from_dbs(alpm_list_t *dbs, const char *needle) {
361 alpm_list_t *i;
362 alpm_pkg_t *ret;
364 for(i = dbs; i; i = alpm_list_next(i)) {
365 ret = alpm_db_get_pkg(alpm_list_getdata(i), needle);
366 if(ret) {
367 return ret;
370 return NULL;
374 * walk dependencies in reverse, showing packages which require the target
376 static void walk_reverse_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, int depth)
378 alpm_list_t *required_by, *i;
380 if(!pkg || ((max_depth >= 0) && (depth == max_depth + 1))) {
381 return;
384 walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
385 required_by = alpm_pkg_compute_requiredby(pkg);
387 for(i = required_by; i; i = alpm_list_next(i)) {
388 const char *pkgname = alpm_list_getdata(i);
390 if(alpm_list_find_str(walked, pkgname)) {
391 /* if we've already seen this package, don't print in "unique" output
392 * and don't recurse */
393 if(!unique) {
394 print(alpm_pkg_get_name(pkg), pkgname, NULL, depth);
396 } else {
397 print(alpm_pkg_get_name(pkg), pkgname, NULL, depth);
398 walk_reverse_deps(dblist, get_pkg_from_dbs(dblist, pkgname), depth + 1);
402 FREELIST(required_by);
406 * walk dependencies, showing dependencies of the target
408 static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, int depth)
410 alpm_list_t *i;
412 if((max_depth >= 0) && (depth == max_depth + 1)) {
413 return;
416 walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
418 for(i = alpm_pkg_get_depends(pkg); i; i = alpm_list_next(i)) {
419 alpm_depend_t *depend = alpm_list_getdata(i);
420 alpm_pkg_t *provider = alpm_find_dbs_satisfier(handle, dblist, depend->name);
422 if(provider) {
423 const char *provname = alpm_pkg_get_name(provider);
425 if(alpm_list_find_str(walked, provname)) {
426 /* if we've already seen this package, don't print in "unique" output
427 * and don't recurse */
428 if(!unique) {
429 print(alpm_pkg_get_name(pkg), provname, depend->name, depth);
431 } else {
432 print(alpm_pkg_get_name(pkg), provname, depend->name, depth);
433 walk_deps(dblist, provider, depth + 1);
435 } else {
436 /* unresolvable package */
437 print(alpm_pkg_get_name(pkg), NULL, depend->name, depth);
442 int main(int argc, char *argv[])
444 int freelist = 0, ret = 0;
445 enum _alpm_errno_t err;
446 const char *target_name;
447 alpm_pkg_t *pkg;
448 alpm_list_t *dblist = NULL;
450 if(parse_options(argc, argv) != 0) {
451 usage();
452 ret = 1;
453 goto finish;
456 handle = alpm_initialize(ROOTDIR, dbpath, &err);
457 if(!handle) {
458 fprintf(stderr, "error: cannot initialize alpm: %s\n",
459 alpm_strerror(err));
460 ret = 1;
461 goto finish;
464 if(searchsyncs) {
465 if(register_syncs() != 0) {
466 fprintf(stderr, "error: failed to register sync DBs\n");
467 ret = 1;
468 goto finish;
470 dblist = alpm_option_get_syncdbs(handle);
471 } else {
472 dblist = alpm_list_add(dblist, alpm_option_get_localdb(handle));
473 freelist = 1;
476 /* we only care about the first non option arg for walking */
477 target_name = argv[optind];
479 pkg = alpm_find_dbs_satisfier(handle, dblist, target_name);
480 if(!pkg) {
481 fprintf(stderr, "error: package '%s' not found\n", target_name);
482 ret = 1;
483 goto finish;
486 print_start(alpm_pkg_get_name(pkg), target_name);
488 if(reverse) {
489 walk_reverse_deps(dblist, pkg, 1);
490 } else {
491 walk_deps(dblist, pkg, 1);
494 print_end();
496 if(freelist) {
497 alpm_list_free(dblist);
500 finish:
501 cleanup();
502 return ret;
505 /* vim: set ts=2 sw=2 noet: */