Rename pmpkg_t to alpm_pkg_t
[pacman-ng.git] / src / util / pactree.c
blobf677a4dee97a5b3147bf5637a03dba3b6cc77a07
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 <getopt.h>
23 #include <stdio.h>
24 #include <string.h>
26 #include <alpm.h>
27 #include <alpm_list.h>
29 #define LINE_MAX 512
31 /* output */
32 struct graph_style {
33 const char *provides;
34 const char *tip1;
35 const char *tip2;
36 int indent;
39 static struct graph_style graph_default = {
40 " provides",
41 "|--",
42 "+--",
46 static struct graph_style graph_linear = {
47 "",
48 "",
49 "",
53 /* color choices */
54 struct color_choices {
55 const char *branch1;
56 const char *branch2;
57 const char *leaf1;
58 const char *leaf2;
59 const char *off;
62 static struct color_choices use_color = {
63 "\033[0;33m", /* yellow */
64 "\033[0;37m", /* white */
65 "\033[1;32m", /* bold green */
66 "\033[0;32m", /* green */
67 "\033[0m"
70 static struct color_choices no_color = {
71 "",
72 "",
73 "",
74 "",
78 /* globals */
79 alpm_handle_t *handle = NULL;
80 alpm_list_t *walked = NULL;
81 alpm_list_t *provisions = NULL;
83 /* options */
84 struct color_choices *color = &no_color;
85 struct graph_style *style = &graph_default;
86 int graphviz = 0;
87 int max_depth = -1;
88 int reverse = 0;
89 int unique = 0;
90 int searchsyncs = 0;
91 const char *dbpath = DBPATH;
93 static char *strtrim(char *str)
95 char *pch = str;
97 if(str == NULL || *str == '\0') {
98 /* string is empty, so we're done. */
99 return str;
102 while(isspace((unsigned char)*pch)) {
103 pch++;
105 if(pch != str) {
106 memmove(str, pch, (strlen(pch) + 1));
109 /* check if there wasn't anything but whitespace in the string. */
110 if(*str == '\0') {
111 return str;
114 pch = (str + (strlen(str) - 1));
115 while(isspace((unsigned char)*pch)) {
116 pch--;
118 *++pch = '\0';
120 return str;
123 static int register_syncs(void) {
124 FILE *fp;
125 char *ptr, *section = NULL;
126 char line[LINE_MAX];
128 fp = fopen(CONFFILE, "r");
129 if(!fp) {
130 return 1;
133 while(fgets(line, LINE_MAX, fp)) {
134 strtrim(line);
136 if(line[0] == '#' || !strlen(line)) {
137 continue;
140 if((ptr = strchr(line, '#'))) {
141 *ptr = '\0';
142 strtrim(line);
145 if(line[0] == '[' && line[strlen(line) - 1] == ']') {
146 free(section);
147 section = strndup(&line[1], strlen(line) - 2);
149 if(section && strcmp(section, "options") != 0) {
150 alpm_db_register_sync(handle, section, PM_PGP_VERIFY_OPTIONAL);
155 free(section);
156 fclose(fp);
158 return 0;
161 static int parse_options(int argc, char *argv[])
163 int opt, option_index = 0;
164 char *endptr = NULL;
166 static struct option opts[] = {
167 {"dbpath", required_argument, 0, 'b'},
168 {"color", no_argument, 0, 'c'},
169 {"depth", required_argument, 0, 'd'},
170 {"graph", no_argument, 0, 'g'},
171 {"help", no_argument, 0, 'h'},
172 {"linear", no_argument, 0, 'l'},
173 {"reverse", no_argument, 0, 'r'},
174 {"sync", no_argument, 0, 'S'},
175 {"unique", no_argument, 0, 'u'},
176 {0, 0, 0, 0}
179 while((opt = getopt_long(argc, argv, "b:cd:ghlrsu", opts, &option_index))) {
180 if(opt < 0) {
181 break;
184 switch(opt) {
185 case 'b':
186 dbpath = optarg;
187 break;
188 case 'c':
189 color = &use_color;
190 break;
191 case 'd':
192 /* validate depth */
193 max_depth = (int)strtol(optarg, &endptr, 10);
194 if(*endptr != '\0') {
195 fprintf(stderr, "error: invalid depth -- %s\n", optarg);
196 return 1;
198 break;
199 case 'g':
200 graphviz = 1;
201 break;
202 case 'l':
203 style = &graph_linear;
204 break;
205 case 'r':
206 reverse = 1;
207 break;
208 case 's':
209 searchsyncs = 1;
210 break;
211 case 'u':
212 unique = 1;
213 style = &graph_linear;
214 break;
215 case 'h':
216 case '?':
217 default:
218 return 1;
222 if(!argv[optind]) {
223 return 1;
226 return 0;
229 static void usage(void)
231 fprintf(stderr, "pactree v" PACKAGE_VERSION "\n"
232 "Usage: pactree [options] PACKAGE\n\n"
233 " -b, --dbpath <path> set an alternate database location\n"
234 " -c, --color colorize output\n"
235 " -d, --depth <#> limit the depth of recursion\n"
236 " -g, --graph generate output for graphviz's dot\n"
237 " -l, --linear enable linear output\n"
238 " -r, --reverse show reverse dependencies\n"
239 " -s, --sync search sync DBs instead of local\n"
240 " -u, --unique show dependencies with no duplicates (implies -l)\n\n"
241 " -h, --help display this help message\n");
244 static void cleanup(void)
246 alpm_list_free(walked);
247 alpm_list_free(provisions);
248 alpm_release(handle);
251 /* pkg provides provision */
252 static void print_text(const char *pkg, const char *provision, int depth)
254 int indent_sz = (depth + 1) * style->indent;
256 if(!pkg && !provision) {
257 /* not much we can do */
258 return;
261 if(!pkg && provision) {
262 /* we failed to resolve provision */
263 printf("%s%*s%s%s%s [unresolvable]%s\n", color->branch1, indent_sz,
264 style->tip1, color->leaf1, provision, color->branch1, color->off);
265 } else if(provision && strcmp(pkg, provision) != 0) {
266 /* pkg provides provision */
267 printf("%s%*s%s%s%s%s %s%s%s\n", color->branch2, indent_sz, style->tip2,
268 color->leaf1, pkg, color->leaf2, style->provides, color->leaf1, provision,
269 color->off);
270 } else {
271 /* pkg is a normal package */
272 printf("%s%*s%s%s%s\n", color->branch1, indent_sz, style->tip1, color->leaf1,
273 pkg, color->off);
277 static void print_graph(const char *parentname, const char *pkgname, const char *depname)
279 if(depname) {
280 printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
281 if(pkgname && strcmp(depname, pkgname) != 0 && !alpm_list_find_str(provisions, depname)) {
282 printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
283 provisions = alpm_list_add(provisions, (char *)depname);
285 } else if(pkgname) {
286 printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
290 /* parent depends on dep which is satisfied by pkg */
291 static void print(const char *parentname, const char *pkgname, const char *depname, int depth)
293 if(graphviz) {
294 print_graph(parentname, pkgname, depname);
295 } else {
296 print_text(pkgname, depname, depth);
300 static void print_start(const char *pkgname, const char *provname)
302 if(graphviz) {
303 printf("digraph G { START [color=red, style=filled];\n"
304 "node [style=filled, color=green];\n"
305 " \"START\" -> \"%s\";\n", pkgname);
306 } else {
307 print_text(pkgname, provname, 0);
311 static void print_end(void)
313 if(graphviz) {
314 /* close graph output */
315 printf("}\n");
319 static alpm_pkg_t *get_pkg_from_dbs(alpm_list_t *dbs, const char *needle) {
320 alpm_list_t *i;
321 alpm_pkg_t *ret;
323 for(i = dbs; i; i = alpm_list_next(i)) {
324 ret = alpm_db_get_pkg(alpm_list_getdata(i), needle);
325 if(ret) {
326 return ret;
329 return NULL;
333 * walk dependencies in reverse, showing packages which require the target
335 static void walk_reverse_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, int depth)
337 alpm_list_t *required_by, *i;
339 if(!pkg || ((max_depth >= 0) && (depth == max_depth + 1))) {
340 return;
343 walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
344 required_by = alpm_pkg_compute_requiredby(pkg);
346 for(i = required_by; i; i = alpm_list_next(i)) {
347 const char *pkgname = alpm_list_getdata(i);
349 if(alpm_list_find_str(walked, pkgname)) {
350 /* if we've already seen this package, don't print in "unique" output
351 * and don't recurse */
352 if(!unique) {
353 print(alpm_pkg_get_name(pkg), pkgname, NULL, depth);
355 } else {
356 print(alpm_pkg_get_name(pkg), pkgname, NULL, depth);
357 walk_reverse_deps(dblist, get_pkg_from_dbs(dblist, pkgname), depth + 1);
361 FREELIST(required_by);
365 * walk dependencies, showing dependencies of the target
367 static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, int depth)
369 alpm_list_t *i;
371 if((max_depth >= 0) && (depth == max_depth + 1)) {
372 return;
375 walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
377 for(i = alpm_pkg_get_depends(pkg); i; i = alpm_list_next(i)) {
378 pmdepend_t *depend = alpm_list_getdata(i);
379 alpm_pkg_t *provider = alpm_find_dbs_satisfier(handle, dblist, depend->name);
381 if(provider) {
382 const char *provname = alpm_pkg_get_name(provider);
384 if(alpm_list_find_str(walked, provname)) {
385 /* if we've already seen this package, don't print in "unique" output
386 * and don't recurse */
387 if(!unique) {
388 print(alpm_pkg_get_name(pkg), provname, depend->name, depth);
390 } else {
391 print(alpm_pkg_get_name(pkg), provname, depend->name, depth);
392 walk_deps(dblist, provider, depth + 1);
394 } else {
395 /* unresolvable package */
396 print(alpm_pkg_get_name(pkg), NULL, depend->name, depth);
401 int main(int argc, char *argv[])
403 int freelist = 0, ret = 0;
404 enum _pmerrno_t err;
405 const char *target_name;
406 alpm_pkg_t *pkg;
407 alpm_list_t *dblist = NULL;
409 if(parse_options(argc, argv) != 0) {
410 usage();
411 ret = 1;
412 goto finish;
415 handle = alpm_initialize(ROOTDIR, dbpath, &err);
416 if(!handle) {
417 fprintf(stderr, "error: cannot initialize alpm: %s\n",
418 alpm_strerror(err));
419 ret = 1;
420 goto finish;
423 if(searchsyncs) {
424 if(register_syncs() != 0) {
425 fprintf(stderr, "error: failed to register sync DBs\n");
426 ret = 1;
427 goto finish;
429 dblist = alpm_option_get_syncdbs(handle);
430 } else {
431 dblist = alpm_list_add(dblist, alpm_option_get_localdb(handle));
432 freelist = 1;
435 /* we only care about the first non option arg for walking */
436 target_name = argv[optind];
438 pkg = alpm_find_dbs_satisfier(handle, dblist, target_name);
439 if(!pkg) {
440 fprintf(stderr, "error: package '%s' not found\n", target_name);
441 ret = 1;
442 goto finish;
445 print_start(alpm_pkg_get_name(pkg), target_name);
447 if(reverse) {
448 walk_reverse_deps(dblist, pkg, 1);
449 } else {
450 walk_deps(dblist, pkg, 1);
453 print_end();
455 if(freelist) {
456 alpm_list_free(dblist);
459 finish:
460 cleanup();
461 return ret;
464 /* vim: set ts=2 sw=2 noet: */