#!/usr/bin/env perl use strict; use warnings; use FindBin; use lib "$FindBin::Bin/.."; use YAMLPatch; use Getopt::Long qw(GetOptions); # yamlpatch - command-line front end for YAMLPatch.pm. # # Exposes the same path-addressed, formatting-preserving operations as the Perl API to shell # scripts and non-Perl callers. Uses the SAME code path as the module's API (this script is a # thin argument-parsing wrapper around YAMLPatch.pm, nothing more), so behavior cannot drift # between the two - see YAMLPatch.md for the full API reference and the rationale behind the # supported subset / relaxation flags used below. my %opt = ( style => 'keep', 'dry-run' => 0, backup => 0, 'allow-flow' => 0, 'allow-block-scalar' => 0, 'allow-anchors' => 0, 'allow-multidoc' => 0, ); GetOptions( \%opt, 'style=s', 'dry-run', 'backup', 'after=s', 'before=s', 'allow-flow', 'allow-block-scalar', 'allow-anchors', 'allow-multidoc', 'help|h', ) or usage(2); usage(0) if $opt{help}; my $op = shift @ARGV or usage(2, "missing OPERATION"); my %load_opts = ( allow_flow => $opt{'allow-flow'}, allow_block_scalar => $opt{'allow-block-scalar'}, allow_anchors => $opt{'allow-anchors'}, allow_multidoc => $opt{'allow-multidoc'}, ); my $exit = eval { if ($op eq 'get') { my ($path, $file) = @ARGV; usage(2, "get: usage: get PATH FILE") unless defined $path && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); my $rec = $y->get($path); unless ($rec) { print STDERR "yamlpatch: path '$path' does not exist" . ($y->exists_commented($path) ? " (it exists, but is commented out)\n" : "\n"); return 1; } print defined($rec->{value}) ? "$rec->{value}\n" : "\n"; return 0; } if ($op eq 'exists') { my ($path, $file) = @ARGV; usage(2, "exists: usage: exists PATH FILE") unless defined $path && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); return $y->exists_path($path) ? 0 : 1; } if ($op eq 'list') { my ($file) = @ARGV; usage(2, "list: usage: list FILE") unless defined $file; my $y = YAMLPatch->load_file($file, %load_opts); print "$_\n" for $y->path_list; return 0; } if ($op eq 'set') { my ($path, $value, $file) = @ARGV; usage(2, "set: usage: set PATH VALUE FILE") unless defined $path && defined $value && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); $y->set($path, $value, style => $opt{style}); return _finish($y, $file); } if ($op eq 'delete') { my ($path, $file) = @ARGV; usage(2, "delete: usage: delete PATH FILE") unless defined $path && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); $y->delete($path); return _finish($y, $file); } if ($op eq 'comment-out') { my ($path, $file) = @ARGV; usage(2, "comment-out: usage: comment-out PATH FILE") unless defined $path && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); $y->comment_out($path); return _finish($y, $file); } if ($op eq 'uncomment') { my ($path, $file) = @ARGV; usage(2, "uncomment: usage: uncomment PATH FILE") unless defined $path && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); $y->uncomment($path); return _finish($y, $file); } if ($op eq 'insert-kv') { my ($parent, $key, $value, $file) = @ARGV; usage(2, "insert-kv: usage: insert-kv PARENT KEY VALUE FILE [--after SIBLING|--before SIBLING]") unless defined $parent && defined $key && defined $value && defined $file; my $y = YAMLPatch->load_file($file, %load_opts); my %ins = (parent => $parent, key => $key, value => $value, style => $opt{style}); $ins{after} = $opt{after} if defined $opt{after}; $ins{before} = $opt{before} if defined $opt{before}; $y->insert_kv(%ins); return _finish($y, $file); } usage(2, "unknown operation '$op'"); }; if ($@) { print STDERR "yamlpatch: $@"; $exit = 1; } exit($exit // 1); sub _finish { my ($y, $file) = @_; if ($opt{'dry-run'}) { my $diff = $y->diff; print $diff eq '' ? "(no changes)\n" : "$diff\n"; return 0; } $y->save($file, backup => $opt{backup}); return 0; } sub usage { my ($code, $msg) = @_; print STDERR "yamlpatch: $msg\n\n" if $msg; print STDERR <<'USAGE'; Usage: yamlpatch OPERATION [ARGS...] [OPTIONS] Operations: get PATH FILE print the value at PATH exists PATH FILE exit 0 if PATH exists, 1 otherwise list FILE print every addressable path, one per line set PATH VALUE FILE set PATH to VALUE delete PATH FILE remove PATH (and its subtree, if a container) comment-out PATH FILE comment out PATH (and its subtree), reversibly uncomment PATH FILE reverse a previous comment-out insert-kv PARENT KEY VALUE FILE add a new "KEY: VALUE" under PARENT [--after SIBLING | --before SIBLING] Options: --style bare|single|double|keep quoting style for the written value (default: keep) --dry-run print a diff instead of writing the file --backup write FILE.bak. before saving --allow-flow accept flow-style [..]/{..} values as opaque --allow-block-scalar accept |/> block scalars as opaque --allow-anchors accept &anchor/*alias/!tag values as opaque --allow-multidoc accept a second '---' document as an opaque tail --help this message Examples: yamlpatch get transport.compression.method sneakernet.conf.yaml yamlpatch set transport.compression.method "'xz'" sneakernet.conf.yaml yamlpatch delete datasets.ds1.maxDelta sneakernet.conf.yaml yamlpatch comment-out datasets.ds2 sneakernet.conf.yaml yamlpatch insert-kv transport verifyFullSendDataset storage/testing/verify \ sneakernet.conf.yaml --after verifyStream Exit status: 0 on success; 1 on refusal (subset violation, gate failure, path not found); 2 on a command-line usage error. USAGE exit $code; }