introduce the --optional flag (FS#61336)

Message ID 20200605150616.GD27697@incertia.net
State Changes Requested, archived
Headers show
Series introduce the --optional flag (FS#61336) | expand

Commit Message

Will Song June 5, 2020, 3:06 p.m. UTC
I have pasted the patch below as per the normal pacman patch guidelines.
This is my first archlinux patch so go ham. I expect that there will
probably be a need for some formatting changes.

From 4582d89f3c41ed94f1895aecbf123af0082ddc9c Mon Sep 17 00:00:00 2001
From: Will Song <incertia@incertia.net>
Date: Wed, 3 Jun 2020 19:48:22 -0400
Subject: [PATCH] introduce the --optional flag

--optional[=DEPTH] will control the depth at which optional dependencies
are printed. Its behavior is fairly simple. If the maximum depth is not
yet exceeded, and it encounters an optional dependency, it will print it
like a normal dependency except with an optional tag. A negative number
means infinite depth.

Signed-off-by: Will Song <incertia@incertia.net>
---
 src/pactree.c | 160 +++++++++++++++++++++++++++++++++++---------------
 1 file changed, 114 insertions(+), 46 deletions(-)

Comments

Eli Schwartz June 5, 2020, 4:02 p.m. UTC | #1
On 6/5/20 11:06 AM, Will Song wrote:
> I have pasted the patch below as per the normal pacman patch guidelines.
> This is my first archlinux patch so go ham. I expect that there will
> probably be a need for some formatting changes.
> 
> From 4582d89f3c41ed94f1895aecbf123af0082ddc9c Mon Sep 17 00:00:00 2001
> From: Will Song <incertia@incertia.net>
> Date: Wed, 3 Jun 2020 19:48:22 -0400
> Subject: [PATCH] introduce the --optional flag

FWIW, if you're going to send a patch by email but add stuff before the
patch contents, it's not immediately obvious how to apply the patch with
git am. (That being said, it seems your email client did not mangle the
patch, as many do -- even though you didn't use git send-email, which
was written purely to send patches in a way guaranteed not to break due
to email client mangling. So the good news is the patch itself applies
fine. :))

However, git has a built-in feature for this, mentioned in the manpage
for git-format-patch(1) in the section marked "DISCUSSION". If you
remove the "From 4582d89f3c41ed94f1895aecbf123af0082ddc9c" line, and
replace it with a line consisting solely of "-- >8 --", git am
--scissors can apply it.

(Actually I'm not entirely sure why even when using scissors, but NOT
when applying a patch without foreword comments it seems it is necessary
to remove the "From 4582d89f3c41ed94f1895aecbf123af0082ddc9c" line.
Though git format-patch documents you should "remove unnecessary header
fields", it doesn't say if you don't, the headers will break processing.)

It's still possible to apply the patch, e.g. with

curl https://patchwork.archlinux.org/patch/1678/mbox/ | \
  sed '1,/From 4582d89f3c41ed94f1895aecbf123af0082ddc9c/d' | git am

Anyway, thanks for the patch, it looks interesting and useful. Could you
please also update the manpage at doc/pactree.8.txt ?

> --optional[=DEPTH] will control the depth at which optional dependencies
> are printed. Its behavior is fairly simple. If the maximum depth is not
> yet exceeded, and it encounters an optional dependency, it will print it
> like a normal dependency except with an optional tag. A negative number
> means infinite depth.
> 
> Signed-off-by: Will Song <incertia@incertia.net>
> ---

A more common convention is to add additional commentary right here. All
lines following the "---" until the diff itself, are considered to be
patch comments (including this "one file changed, XXX insertions, XXX
deletions" stuff, in fact).

Again, this convention too is documented in git-format-patch(1) in the
section marked "DISCUSSION".

>  src/pactree.c | 160 +++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 114 insertions(+), 46 deletions(-)
> 
> diff --git a/src/pactree.c b/src/pactree.c
> index cf83326..39328e7 100644
> --- a/src/pactree.c
> +++ b/src/pactree.c
> @@ -28,6 +28,8 @@
>  
>  #define LINE_MAX     512
>  
> +#include <limits.h>
> +
>  typedef struct tdepth {
>  	struct tdepth *prev;
>  	struct tdepth *next;
> @@ -37,6 +39,7 @@ typedef struct tdepth {
>  /* output */
>  struct graph_style {
>  	const char *provides;
> +	const char *optional;
>  	const char *tip;
>  	const char *last;
>  	const char *limb;
> @@ -50,6 +53,7 @@ struct graph_style {
>  
>  static struct graph_style graph_utf8 = {
>  	" provides",
> +	" (optional)",
>  	UTF_VR UTF_H,
>  	UTF_UR UTF_H,
>  	UTF_V " ",
> @@ -58,6 +62,7 @@ static struct graph_style graph_utf8 = {
>  
>  static struct graph_style graph_default = {
>  	" provides",
> +	" (optional)",
>  	"|-",
>  	"`-",
>  	"|",
> @@ -69,6 +74,7 @@ static struct graph_style graph_linear = {
>  	"",
>  	"",
>  	"",
> +	"",
>  	0
>  };
>  
> @@ -124,6 +130,7 @@ static int reverse = 0;
>  static int unique = 0;
>  static int searchsyncs = 0;
>  static int debug = 0;
> +static int opt_level = 0;
>  static const char *dbpath = DBPATH;
>  static const char *configfile = CONFFILE;
>  static const char *gpgdir = GPGDIR;
> @@ -243,20 +250,22 @@ static void usage(void)
>  	fprintf(stdout, "pactree v" PACKAGE_VERSION "\n\n"
>  			"A simple dependency tree viewer.\n\n"
>  			"Usage: pactree [options] PACKAGE\n\n"
> -			"  -a, --ascii          use ASCII characters for tree formatting\n"
> -			"  -b, --dbpath <path>  set an alternate database location\n"
> -			"  -c, --color          colorize output\n"
> -			"  -d, --depth <#>      limit the depth of recursion\n"
> -			"  -g, --graph          generate output for graphviz's dot\n"
> -			"  -h, --help           display this help message\n"
> -			"  -l, --linear         enable linear output\n"
> -			"  -r, --reverse        list packages that depend on the named package\n"
> -			"  -s, --sync           search sync databases instead of local\n"
> -			"  -u, --unique         show dependencies with no duplicates (implies -l)\n"
> -			"  -v, --version        display the version\n"
> -			"      --config <path>  set an alternate configuration file\n"
> -			"      --debug          display debug messages\n"
> -			"      --gpgdir <path>  set an alternate home directory for GnuPG\n");
> +			"  -a, --ascii            use ASCII characters for tree formatting\n"
> +			"  -b, --dbpath <path>    set an alternate database location\n"
> +			"  -c, --color            colorize output\n"
> +			"  -d, --depth <#>        limit the depth of recursion\n"
> +			"  -g, --graph            generate output for graphviz's dot\n"
> +			"  -h, --help             display this help message\n"
> +			"  -l, --linear           enable linear output\n"
> +			"  -r, --reverse          list packages that depend on the named package\n"
> +			"  -s, --sync             search sync databases instead of local\n"
> +			"  -u, --unique           show dependencies with no duplicates (implies -l)\n"
> +			"  -o, --optional[=DEPTH] controls at which depth to stop printing optional deps\n"
> +			"                         (-1 for no limit)\n"
> +			"  -v, --version          display the version\n"
> +			"      --config <path>    set an alternate configuration file\n"
> +			"      --debug            display debug messages\n"
> +			"      --gpgdir <path>    set an alternate home directory for GnuPG\n");
>  }
>  
>  static void version(void)
> @@ -280,6 +289,7 @@ static int parse_options(int argc, char *argv[])
>  		{"reverse", no_argument,          0, 'r'},
>  		{"sync",    no_argument,          0, 's'},
>  		{"unique",  no_argument,          0, 'u'},
> +		{"optional",optional_argument,    0, 'o'},
>  		{"version", no_argument,          0, 'v'},
>  
>  		{"config",  required_argument,    0, OP_CONFIG},
> @@ -294,7 +304,7 @@ static int parse_options(int argc, char *argv[])
>  		style = &graph_utf8;
>  	}
>  
> -	while((opt = getopt_long(argc, argv, "ab:cd:ghlrsuv", opts, &option_index))) {
> +	while((opt = getopt_long(argc, argv, "ab:cd:ghlrsuo::v", opts, &option_index))) {
>  		if(opt < 0) {
>  			break;
>  		}
> @@ -345,6 +355,17 @@ static int parse_options(int argc, char *argv[])
>  			case 'v':
>  				version();
>  				cleanup(0);
> +			case 'o':
> +				if(optarg) {
> +					opt_level = (int)strtol(optarg, &endptr, 10);
> +					if(*endptr != '\0') {
> +						fprintf(stderr, "error: invalid optional depth -- %s\n", optarg);
> +						return 1;
> +					}
> +				} else {
> +					opt_level = 1;
> +				}
> +				break;
>  			case 'h':
>  				usage();
>  				cleanup(0);
> @@ -364,9 +385,10 @@ static int parse_options(int argc, char *argv[])
>  
>  /* pkg provides provision */
>  static void print_text(const char *pkg, const char *provision,
> -		tdepth *depth, int last)
> +		tdepth *depth, int last, int opt_dep)
>  {
>  	const char *tip = "";
> +	const char *opt_str = opt_dep ? style->optional : "";
>  	int level = 1;
>  	if(!pkg && !provision) {
>  		/* not much we can do */
> @@ -395,38 +417,39 @@ static void print_text(const char *pkg, const char *provision,
>  	 * want to print the provided package. This makes output easier to parse and
>  	 * to reuse. */
>  	if(!pkg && provision) {
> -		printf("%s%s%s%s [unresolvable]%s\n", tip, color->leaf1,
> -				provision, color->branch1, color->off);
> +		printf("%s%s%s%s [unresolvable]%s%s\n", tip, color->leaf1,
> +				provision, color->branch1, opt_str, color->off);
>  	} else if(provision && strcmp(pkg, provision) != 0 && *(style->provides) != '\0') {
> -		printf("%s%s%s%s%s %s%s%s\n", tip, color->leaf1, pkg,
> -				color->leaf2, style->provides, color->leaf1, provision,
> +		printf("%s%s%s%s%s %s%s%s%s\n", tip, color->leaf1, pkg,
> +				color->leaf2, style->provides, color->leaf1, provision, opt_str,
>  				color->off);
>  	} else {
> -		printf("%s%s%s%s\n", tip, color->leaf1, pkg, color->off);
> +		printf("%s%s%s%s%s\n", tip, color->leaf1, pkg, opt_str, color->off);
>  	}
>  }
>  
> -static void print_graph(const char *parentname, const char *pkgname, const char *depname)
> +static void print_graph(const char *parentname, const char *pkgname, const char *depname, int opt_dep)
>  {
> +	const char *style = opt_dep ? ", style=dotted" : "";
>  	if(depname) {
> -		printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
> +		printf("\"%s\" -> \"%s\" [color=chocolate4%s];\n", parentname, depname, style);
>  		if(pkgname && strcmp(depname, pkgname) != 0 && !alpm_list_find_str(provisions, depname)) {
> -			printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
> +			printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey%s];\n", depname, pkgname, style);
>  			provisions = alpm_list_add(provisions, strdup(depname));
>  		}
>  	} else if(pkgname) {
> -		printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
> +		printf("\"%s\" -> \"%s\" [color=chocolate4%s];\n", parentname, pkgname, style);
>  	}
>  }
>  
>  /* parent depends on dep which is satisfied by pkg */
>  static void print(const char *parentname, const char *pkgname,
> -		const char *depname, tdepth *depth, int last)
> +		const char *depname, tdepth *depth, int last, int opt_dep)
>  {
>  	if(graphviz) {
> -		print_graph(parentname, pkgname, depname);
> +		print_graph(parentname, pkgname, depname, opt_dep);
>  	} else {
> -		print_text(pkgname, depname, depth, last);
> +		print_text(pkgname, depname, depth, last, opt_dep);
>  	}
>  }
>  
> @@ -442,7 +465,7 @@ static void print_start(const char *pkgname, const char *provname)
>  			NULL,
>  			0
>  		};
> -		print_text(pkgname, provname, &d, 0);
> +		print_text(pkgname, provname, &d, 0, 0);
>  	}
>  }
>  
> @@ -465,23 +488,34 @@ static alpm_list_t *get_pkg_deps(alpm_pkg_t *pkg)
>  	return dep_strings;
>  }
>  
> -/**
> - * walk dependencies, showing dependencies of the target
> - */
> -static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev)
> +static alpm_list_t *get_pkg_optdeps(alpm_pkg_t *pkg)
>  {
> -	alpm_list_t *deps, *i;
> -
> -	if(!pkg || ((max_depth >= 0) && (depth->level > max_depth))) {
> -		return;
> +	alpm_list_t *i, *dep_strings = NULL;
> +	for(i = alpm_pkg_get_optdepends(pkg); i; i = alpm_list_next(i)) {
> +		alpm_depend_t *dep = i->data;
> +		char *ds = alpm_dep_compute_string(dep);
> +		dep_strings = alpm_list_add(dep_strings, ds);
>  	}
> +	return dep_strings;
> +}
>  
> -	walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
> +/* forward declaration */
> +static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev, int optional);
>  
> -	if(rev) {
> -		deps = alpm_pkg_compute_requiredby(pkg);
> +/**
> + * print the dependency list given, passing the optional parameter when required
> + */
> +static void print_dep_list(alpm_list_t *deps, alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev, int optional, int opt_dep)
> +{
> +	alpm_list_t *i;
> +	int new_optional;
> +
> +	if(optional > 0) {
> +		/* decrease the depth by 1 */
> +		new_optional = optional - 1;
>  	} else {
> -		deps = get_pkg_deps(pkg);
> +		/* preserve 0 (ran out of depth) and negative numbers (infinite depth) */
> +		new_optional = optional;
>  	}
>  
>  	for(i = deps; i; i = alpm_list_next(i)) {
> @@ -494,10 +528,10 @@ static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int r
>  			/* if we've already seen this package, don't print in "unique" output
>  			 * and don't recurse */
>  			if(!unique) {
> -				print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last);
> +				print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last, opt_dep);
>  			}
>  		} else {
> -			print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last);
> +			print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last, opt_dep);
>  			if(dep_pkg) {
>  				tdepth d = {
>  					depth,
> @@ -506,7 +540,7 @@ static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int r
>  				};
>  				depth->next = &d;
>  				/* last dep, cut off the limb here */
> -				if(last) {
> +				if((last && optional && opt_dep) || (last && !optional)) {
>  					if(depth->prev) {
>  						depth->prev->next = &d;
>  						d.prev = depth->prev;
> @@ -515,13 +549,47 @@ static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int r
>  						d.prev = NULL;
>  					}
>  				}
> -				walk_deps(dblist, dep_pkg, &d, rev);
> +				walk_deps(dblist, dep_pkg, &d, rev, new_optional);
>  				depth->next = NULL;
>  			}
>  		}
>  	}
> +}
> +
> +/**
> + * walk dependencies, showing dependencies of the target
> + */
> +static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev, int optional)
> +{
> +	alpm_list_t *deps;
> +
> +	if(!pkg || ((max_depth >= 0) && (depth->level > max_depth))) {
> +		return;
> +	}
> +
> +	walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
> +
> +	if(rev) {
> +		deps = alpm_pkg_compute_requiredby(pkg);
> +	} else {
> +		deps = get_pkg_deps(pkg);
> +	}
> +
> +	print_dep_list(deps, dblist, pkg, depth, rev, optional, 0);
>  
>  	FREELIST(deps);
> +
> +	if(optional){
> +		if(rev) {
> +			deps = alpm_pkg_compute_optionalfor(pkg);
> +		} else {
> +			deps = get_pkg_optdeps(pkg);
> +		}
> +
> +		print_dep_list(deps, dblist, pkg, depth, rev, optional, 1);
> +
> +		FREELIST(deps);
> +	}
>  }
>  
>  int main(int argc, char *argv[])
> @@ -576,7 +644,7 @@ int main(int argc, char *argv[])
>  		NULL,
>  		1
>  	};
> -	walk_deps(dblist, pkg, &d, reverse);
> +	walk_deps(dblist, pkg, &d, reverse, opt_level);
>  
>  	print_end();
>  
>

Patch

diff --git a/src/pactree.c b/src/pactree.c
index cf83326..39328e7 100644
--- a/src/pactree.c
+++ b/src/pactree.c
@@ -28,6 +28,8 @@ 
 
 #define LINE_MAX     512
 
+#include <limits.h>
+
 typedef struct tdepth {
 	struct tdepth *prev;
 	struct tdepth *next;
@@ -37,6 +39,7 @@  typedef struct tdepth {
 /* output */
 struct graph_style {
 	const char *provides;
+	const char *optional;
 	const char *tip;
 	const char *last;
 	const char *limb;
@@ -50,6 +53,7 @@  struct graph_style {
 
 static struct graph_style graph_utf8 = {
 	" provides",
+	" (optional)",
 	UTF_VR UTF_H,
 	UTF_UR UTF_H,
 	UTF_V " ",
@@ -58,6 +62,7 @@  static struct graph_style graph_utf8 = {
 
 static struct graph_style graph_default = {
 	" provides",
+	" (optional)",
 	"|-",
 	"`-",
 	"|",
@@ -69,6 +74,7 @@  static struct graph_style graph_linear = {
 	"",
 	"",
 	"",
+	"",
 	0
 };
 
@@ -124,6 +130,7 @@  static int reverse = 0;
 static int unique = 0;
 static int searchsyncs = 0;
 static int debug = 0;
+static int opt_level = 0;
 static const char *dbpath = DBPATH;
 static const char *configfile = CONFFILE;
 static const char *gpgdir = GPGDIR;
@@ -243,20 +250,22 @@  static void usage(void)
 	fprintf(stdout, "pactree v" PACKAGE_VERSION "\n\n"
 			"A simple dependency tree viewer.\n\n"
 			"Usage: pactree [options] PACKAGE\n\n"
-			"  -a, --ascii          use ASCII characters for tree formatting\n"
-			"  -b, --dbpath <path>  set an alternate database location\n"
-			"  -c, --color          colorize output\n"
-			"  -d, --depth <#>      limit the depth of recursion\n"
-			"  -g, --graph          generate output for graphviz's dot\n"
-			"  -h, --help           display this help message\n"
-			"  -l, --linear         enable linear output\n"
-			"  -r, --reverse        list packages that depend on the named package\n"
-			"  -s, --sync           search sync databases instead of local\n"
-			"  -u, --unique         show dependencies with no duplicates (implies -l)\n"
-			"  -v, --version        display the version\n"
-			"      --config <path>  set an alternate configuration file\n"
-			"      --debug          display debug messages\n"
-			"      --gpgdir <path>  set an alternate home directory for GnuPG\n");
+			"  -a, --ascii            use ASCII characters for tree formatting\n"
+			"  -b, --dbpath <path>    set an alternate database location\n"
+			"  -c, --color            colorize output\n"
+			"  -d, --depth <#>        limit the depth of recursion\n"
+			"  -g, --graph            generate output for graphviz's dot\n"
+			"  -h, --help             display this help message\n"
+			"  -l, --linear           enable linear output\n"
+			"  -r, --reverse          list packages that depend on the named package\n"
+			"  -s, --sync             search sync databases instead of local\n"
+			"  -u, --unique           show dependencies with no duplicates (implies -l)\n"
+			"  -o, --optional[=DEPTH] controls at which depth to stop printing optional deps\n"
+			"                         (-1 for no limit)\n"
+			"  -v, --version          display the version\n"
+			"      --config <path>    set an alternate configuration file\n"
+			"      --debug            display debug messages\n"
+			"      --gpgdir <path>    set an alternate home directory for GnuPG\n");
 }
 
 static void version(void)
@@ -280,6 +289,7 @@  static int parse_options(int argc, char *argv[])
 		{"reverse", no_argument,          0, 'r'},
 		{"sync",    no_argument,          0, 's'},
 		{"unique",  no_argument,          0, 'u'},
+		{"optional",optional_argument,    0, 'o'},
 		{"version", no_argument,          0, 'v'},
 
 		{"config",  required_argument,    0, OP_CONFIG},
@@ -294,7 +304,7 @@  static int parse_options(int argc, char *argv[])
 		style = &graph_utf8;
 	}
 
-	while((opt = getopt_long(argc, argv, "ab:cd:ghlrsuv", opts, &option_index))) {
+	while((opt = getopt_long(argc, argv, "ab:cd:ghlrsuo::v", opts, &option_index))) {
 		if(opt < 0) {
 			break;
 		}
@@ -345,6 +355,17 @@  static int parse_options(int argc, char *argv[])
 			case 'v':
 				version();
 				cleanup(0);
+			case 'o':
+				if(optarg) {
+					opt_level = (int)strtol(optarg, &endptr, 10);
+					if(*endptr != '\0') {
+						fprintf(stderr, "error: invalid optional depth -- %s\n", optarg);
+						return 1;
+					}
+				} else {
+					opt_level = 1;
+				}
+				break;
 			case 'h':
 				usage();
 				cleanup(0);
@@ -364,9 +385,10 @@  static int parse_options(int argc, char *argv[])
 
 /* pkg provides provision */
 static void print_text(const char *pkg, const char *provision,
-		tdepth *depth, int last)
+		tdepth *depth, int last, int opt_dep)
 {
 	const char *tip = "";
+	const char *opt_str = opt_dep ? style->optional : "";
 	int level = 1;
 	if(!pkg && !provision) {
 		/* not much we can do */
@@ -395,38 +417,39 @@  static void print_text(const char *pkg, const char *provision,
 	 * want to print the provided package. This makes output easier to parse and
 	 * to reuse. */
 	if(!pkg && provision) {
-		printf("%s%s%s%s [unresolvable]%s\n", tip, color->leaf1,
-				provision, color->branch1, color->off);
+		printf("%s%s%s%s [unresolvable]%s%s\n", tip, color->leaf1,
+				provision, color->branch1, opt_str, color->off);
 	} else if(provision && strcmp(pkg, provision) != 0 && *(style->provides) != '\0') {
-		printf("%s%s%s%s%s %s%s%s\n", tip, color->leaf1, pkg,
-				color->leaf2, style->provides, color->leaf1, provision,
+		printf("%s%s%s%s%s %s%s%s%s\n", tip, color->leaf1, pkg,
+				color->leaf2, style->provides, color->leaf1, provision, opt_str,
 				color->off);
 	} else {
-		printf("%s%s%s%s\n", tip, color->leaf1, pkg, color->off);
+		printf("%s%s%s%s%s\n", tip, color->leaf1, pkg, opt_str, color->off);
 	}
 }
 
-static void print_graph(const char *parentname, const char *pkgname, const char *depname)
+static void print_graph(const char *parentname, const char *pkgname, const char *depname, int opt_dep)
 {
+	const char *style = opt_dep ? ", style=dotted" : "";
 	if(depname) {
-		printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
+		printf("\"%s\" -> \"%s\" [color=chocolate4%s];\n", parentname, depname, style);
 		if(pkgname && strcmp(depname, pkgname) != 0 && !alpm_list_find_str(provisions, depname)) {
-			printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
+			printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey%s];\n", depname, pkgname, style);
 			provisions = alpm_list_add(provisions, strdup(depname));
 		}
 	} else if(pkgname) {
-		printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
+		printf("\"%s\" -> \"%s\" [color=chocolate4%s];\n", parentname, pkgname, style);
 	}
 }
 
 /* parent depends on dep which is satisfied by pkg */
 static void print(const char *parentname, const char *pkgname,
-		const char *depname, tdepth *depth, int last)
+		const char *depname, tdepth *depth, int last, int opt_dep)
 {
 	if(graphviz) {
-		print_graph(parentname, pkgname, depname);
+		print_graph(parentname, pkgname, depname, opt_dep);
 	} else {
-		print_text(pkgname, depname, depth, last);
+		print_text(pkgname, depname, depth, last, opt_dep);
 	}
 }
 
@@ -442,7 +465,7 @@  static void print_start(const char *pkgname, const char *provname)
 			NULL,
 			0
 		};
-		print_text(pkgname, provname, &d, 0);
+		print_text(pkgname, provname, &d, 0, 0);
 	}
 }
 
@@ -465,23 +488,34 @@  static alpm_list_t *get_pkg_deps(alpm_pkg_t *pkg)
 	return dep_strings;
 }
 
-/**
- * walk dependencies, showing dependencies of the target
- */
-static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev)
+static alpm_list_t *get_pkg_optdeps(alpm_pkg_t *pkg)
 {
-	alpm_list_t *deps, *i;
-
-	if(!pkg || ((max_depth >= 0) && (depth->level > max_depth))) {
-		return;
+	alpm_list_t *i, *dep_strings = NULL;
+	for(i = alpm_pkg_get_optdepends(pkg); i; i = alpm_list_next(i)) {
+		alpm_depend_t *dep = i->data;
+		char *ds = alpm_dep_compute_string(dep);
+		dep_strings = alpm_list_add(dep_strings, ds);
 	}
+	return dep_strings;
+}
 
-	walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
+/* forward declaration */
+static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev, int optional);
 
-	if(rev) {
-		deps = alpm_pkg_compute_requiredby(pkg);
+/**
+ * print the dependency list given, passing the optional parameter when required
+ */
+static void print_dep_list(alpm_list_t *deps, alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev, int optional, int opt_dep)
+{
+	alpm_list_t *i;
+	int new_optional;
+
+	if(optional > 0) {
+		/* decrease the depth by 1 */
+		new_optional = optional - 1;
 	} else {
-		deps = get_pkg_deps(pkg);
+		/* preserve 0 (ran out of depth) and negative numbers (infinite depth) */
+		new_optional = optional;
 	}
 
 	for(i = deps; i; i = alpm_list_next(i)) {
@@ -494,10 +528,10 @@  static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int r
 			/* if we've already seen this package, don't print in "unique" output
 			 * and don't recurse */
 			if(!unique) {
-				print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last);
+				print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last, opt_dep);
 			}
 		} else {
-			print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last);
+			print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last, opt_dep);
 			if(dep_pkg) {
 				tdepth d = {
 					depth,
@@ -506,7 +540,7 @@  static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int r
 				};
 				depth->next = &d;
 				/* last dep, cut off the limb here */
-				if(last) {
+				if((last && optional && opt_dep) || (last && !optional)) {
 					if(depth->prev) {
 						depth->prev->next = &d;
 						d.prev = depth->prev;
@@ -515,13 +549,47 @@  static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int r
 						d.prev = NULL;
 					}
 				}
-				walk_deps(dblist, dep_pkg, &d, rev);
+				walk_deps(dblist, dep_pkg, &d, rev, new_optional);
 				depth->next = NULL;
 			}
 		}
 	}
+}
+
+/**
+ * walk dependencies, showing dependencies of the target
+ */
+static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev, int optional)
+{
+	alpm_list_t *deps;
+
+	if(!pkg || ((max_depth >= 0) && (depth->level > max_depth))) {
+		return;
+	}
+
+	walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
+
+	if(rev) {
+		deps = alpm_pkg_compute_requiredby(pkg);
+	} else {
+		deps = get_pkg_deps(pkg);
+	}
+
+	print_dep_list(deps, dblist, pkg, depth, rev, optional, 0);
 
 	FREELIST(deps);
+
+	if(optional){
+		if(rev) {
+			deps = alpm_pkg_compute_optionalfor(pkg);
+		} else {
+			deps = get_pkg_optdeps(pkg);
+		}
+
+		print_dep_list(deps, dblist, pkg, depth, rev, optional, 1);
+
+		FREELIST(deps);
+	}
 }
 
 int main(int argc, char *argv[])
@@ -576,7 +644,7 @@  int main(int argc, char *argv[])
 		NULL,
 		1
 	};
-	walk_deps(dblist, pkg, &d, reverse);
+	walk_deps(dblist, pkg, &d, reverse, opt_level);
 
 	print_end();