+# helpers used to generate our manual pages
+
+my $find_schema_default_key = sub {
+ my ($format) = @_;
+
+ my $default_key;
+ my $keyAliasProps = {};
+
+ foreach my $key (keys %$format) {
+ my $phash = $format->{$key};
+ if ($phash->{default_key}) {
+ die "multiple default keys in schema ($default_key, $key)\n"
+ if defined($default_key);
+ die "default key '$key' is an alias - this is not allowed\n"
+ if defined($phash->{alias});
+ die "default key '$key' with keyAlias attribute is not allowed\n"
+ if $phash->{keyAlias};
+ $default_key = $key;
+ }
+ my $key_alias = $phash->{keyAlias};
+ die "found keyAlias without 'alias definition for '$key'\n"
+ if $key_alias && !$phash->{alias};
+
+ if ($phash->{alias} && $key_alias) {
+ die "inconsistent keyAlias '$key_alias' definition"
+ if defined($keyAliasProps->{$key_alias}) &&
+ $keyAliasProps->{$key_alias} ne $phash->{alias};
+ $keyAliasProps->{$key_alias} = $phash->{alias};
+ }
+ }
+
+ return wantarray ? ($default_key, $keyAliasProps) : $default_key;
+};
+
+sub generate_typetext {
+ my ($format, $list_enums) = @_;
+
+ my ($default_key, $keyAliasProps) = &$find_schema_default_key($format);
+
+ my $res = '';
+ my $add_sep = 0;
+
+ my $add_option_string = sub {
+ my ($text, $optional) = @_;
+
+ if ($add_sep) {
+ $text = ",$text";
+ $res .= ' ';
+ }
+ $text = "[$text]" if $optional;
+ $res .= $text;
+ $add_sep = 1;
+ };
+
+ my $format_key_value = sub {
+ my ($key, $phash) = @_;
+
+ die "internal error" if defined($phash->{alias});
+
+ my $keytext = $key;
+
+ my $typetext = '';
+
+ if (my $desc = $phash->{format_description}) {
+ $typetext .= "<$desc>";
+ } elsif (my $text = $phash->{typetext}) {
+ $typetext .= $text;
+ } elsif (my $enum = $phash->{enum}) {
+ if ($list_enums || (scalar(@$enum) <= 3)) {
+ $typetext .= '<' . join('|', @$enum) . '>';
+ } else {
+ $typetext .= '<enum>';
+ }
+ } elsif ($phash->{type} eq 'boolean') {
+ $typetext .= '<1|0>';
+ } elsif ($phash->{type} eq 'integer') {
+ $typetext .= '<integer>';
+ } elsif ($phash->{type} eq 'number') {
+ $typetext .= '<number>';
+ } else {
+ die "internal error: neither format_description nor typetext found for option '$key'";
+ }
+
+ if (defined($default_key) && ($default_key eq $key)) {
+ &$add_option_string("[$keytext=]$typetext", $phash->{optional});
+ } else {
+ &$add_option_string("$keytext=$typetext", $phash->{optional});
+ }
+ };
+
+ my $done = {};
+
+ my $cond_add_key = sub {
+ my ($key) = @_;
+
+ return if $done->{$key}; # avoid duplicates
+
+ $done->{$key} = 1;
+
+ my $phash = $format->{$key};
+
+ return if !$phash; # should not happen
+
+ return if $phash->{alias};
+
+ &$format_key_value($key, $phash);
+
+ };
+
+ &$cond_add_key($default_key) if defined($default_key);
+
+ # add required keys first
+ foreach my $key (sort keys %$format) {
+ my $phash = $format->{$key};
+ &$cond_add_key($key) if $phash && !$phash->{optional};
+ }
+
+ # add the rest
+ foreach my $key (sort keys %$format) {
+ &$cond_add_key($key);
+ }
+
+ foreach my $keyAlias (sort keys %$keyAliasProps) {
+ &$add_option_string("<$keyAlias>=<$keyAliasProps->{$keyAlias }>", 1);
+ }
+
+ return $res;
+}
+
+sub print_property_string {
+ my ($data, $format, $skip, $path) = @_;
+
+ if (ref($format) ne 'HASH') {
+ my $schema = get_format($format);
+ die "not a valid format: $format\n" if !$schema;
+ $format = $schema;
+ }
+
+ my $errors = {};
+ check_object($path, $format, $data, undef, $errors);
+ if (scalar(%$errors)) {
+ raise "format error", errors => $errors;
+ }
+
+ my ($default_key, $keyAliasProps) = &$find_schema_default_key($format);
+
+ my $res = '';
+ my $add_sep = 0;
+
+ my $add_option_string = sub {
+ my ($text) = @_;
+
+ $res .= ',' if $add_sep;
+ $res .= $text;
+ $add_sep = 1;
+ };
+
+ my $format_value = sub {
+ my ($key, $value, $format) = @_;
+
+ if (defined($format) && ($format eq 'disk-size')) {
+ return format_size($value);
+ } else {
+ die "illegal value with commas for $key\n" if $value =~ /,/;
+ return $value;
+ }
+ };
+
+ my $done = { map { $_ => 1 } @$skip };
+
+ my $cond_add_key = sub {
+ my ($key, $isdefault) = @_;
+
+ return if $done->{$key}; # avoid duplicates
+
+ $done->{$key} = 1;
+
+ my $value = $data->{$key};
+
+ return if !defined($value);
+
+ my $phash = $format->{$key};
+
+ # try to combine values if we have key aliases
+ if (my $combine = $keyAliasProps->{$key}) {
+ if (defined(my $combine_value = $data->{$combine})) {
+ my $combine_format = $format->{$combine}->{format};
+ my $value_str = &$format_value($key, $value, $phash->{format});
+ my $combine_str = &$format_value($combine, $combine_value, $combine_format);
+ &$add_option_string("${value_str}=${combine_str}");
+ $done->{$combine} = 1;
+ return;
+ }
+ }
+
+ if ($phash && $phash->{alias}) {
+ $phash = $format->{$phash->{alias}};
+ }
+
+ die "invalid key '$key'\n" if !$phash;
+ die "internal error" if defined($phash->{alias});
+
+ my $value_str = &$format_value($key, $value, $phash->{format});
+ if ($isdefault) {
+ &$add_option_string($value_str);
+ } else {
+ &$add_option_string("$key=${value_str}");
+ }
+ };
+
+ # add default key first
+ &$cond_add_key($default_key, 1) if defined($default_key);
+
+ # add required keys first
+ foreach my $key (sort keys %$data) {
+ my $phash = $format->{$key};
+ &$cond_add_key($key) if $phash && !$phash->{optional};
+ }
+
+ # add the rest
+ foreach my $key (sort keys %$data) {
+ &$cond_add_key($key);
+ }
+
+ return $res;
+}
+
+sub schema_get_type_text {
+ my ($phash, $style) = @_;
+
+ my $type = $phash->{type} || 'string';
+
+ if ($phash->{typetext}) {
+ return $phash->{typetext};
+ } elsif ($phash->{format_description}) {
+ return "<$phash->{format_description}>";
+ } elsif ($phash->{enum}) {
+ return "<" . join(' | ', sort @{$phash->{enum}}) . ">";
+ } elsif ($phash->{pattern}) {
+ return $phash->{pattern};
+ } elsif ($type eq 'integer' || $type eq 'number') {
+ # NOTE: always access values as number (avoid converion to string)
+ if (defined($phash->{minimum}) && defined($phash->{maximum})) {
+ return "<$type> (" . ($phash->{minimum} + 0) . " - " .
+ ($phash->{maximum} + 0) . ")";
+ } elsif (defined($phash->{minimum})) {
+ return "<$type> (" . ($phash->{minimum} + 0) . " - N)";
+ } elsif (defined($phash->{maximum})) {
+ return "<$type> (-N - " . ($phash->{maximum} + 0) . ")";
+ }
+ } elsif ($type eq 'string') {
+ if (my $format = $phash->{format}) {
+ $format = get_format($format) if ref($format) ne 'HASH';
+ if (ref($format) eq 'HASH') {
+ my $list_enums = 0;
+ $list_enums = 1 if $style && $style eq 'config-sub';
+ return generate_typetext($format, $list_enums);
+ }
+ }
+ }
+
+ return "<$type>";
+}
+