]>
Dogcows Code - chaz/graphql-client/blob - lib/GraphQL/Client/CLI.pm
c23e07fe4af9aeb5115218a0daf07ee051a726f1
1 package GraphQL
::Client
::CLI
;
2 # ABSTRACT: Implementation of the graphql CLI program
8 use Getopt
::Long
2.39 qw(GetOptionsFromArray);
14 our $VERSION = '999.999'; # VERSION
16 my $JSON = JSON
::MaybeXS-
>new(canonical
=> 1);
18 sub _croak
{ require Carp
; goto &Carp
::croak
}
27 $self = $self->new if !ref $self;
29 my $options = eval { $self->_get_options(@_) };
35 if ($options->{version
}) {
36 print "graphql $VERSION\n";
39 if ($options->{help
}) {
40 _pod2usage
(-exitval
=> 0, -verbose
=> 99, -sections
=> [qw(NAME SYNOPSIS OPTIONS)]);
42 if ($options->{manual
}) {
43 _pod2usage
(-exitval
=> 0, -verbose
=> 2);
46 my $url = $options->{url
};
48 print STDERR
"The <URL> or --url option argument is required.\n";
52 my $variables = $options->{variables
};
53 my $query = $options->{query
};
54 my $operation_name = $options->{operation_name
};
55 my $unpack = $options->{unpack};
56 my $outfile = $options->{outfile
};
57 my $format = $options->{format
};
58 my $transport = $options->{transport
};
60 my $client = GraphQL
::Client-
>new(url
=> $url);
62 eval { $client->transport };
64 warn $err if $ENV{GRAPHQL_CLIENT_DEBUG
};
65 print STDERR
"Could not construct a transport for URL: $url\n";
66 print STDERR
"Is this URL correct?\n";
71 print STDERR
"Interactive mode engaged! Waiting for a query on <STDIN>...\n"
72 if -t STDIN
; ## no critic (InputOutput::ProhibitInteractiveTest)
73 binmode(STDIN
, 'encoding(UTF-8)');
74 $query = do { local $/; <STDIN
> };
77 my $resp = $client->execute($query, $variables, $operation_name, $transport);
78 my $err = $resp->{errors
};
80 my $data = $unpack ? $resp->{data
} : $resp;
83 open(my $out, '>', $outfile) or die "Open $outfile failed: $!";
87 if (my $filter = $options->{filter
}) {
88 eval { require JSON
::Path
::Evaluator
} or die "Missing dependency: JSON::Path\n";
89 my @values = JSON
::Path
::Evaluator
::evaluate_jsonpath
($data, $filter);
98 binmode(STDOUT
, 'encoding(UTF-8)');
99 _print_data
($data, $format);
101 exit($unpack && $err ? 1 : 0);
108 unshift @args, shellwords
($ENV{GRAPHQL_CLIENT_OPTIONS
} || '');
110 # assume UTF-8 args if non-ASCII
111 @args = map { decode
('UTF-8', $_) } @args if grep { /\P{ASCII}/ } @args;
114 format
=> 'json:pretty',
118 GetOptionsFromArray
(\
@args,
119 'version' => \
$options{version
},
120 'help|h|?' => \
$options{help
},
121 'manual|man' => \
$options{manual
},
122 'url|u=s' => \
$options{url
},
123 'query|mutation=s' => \
$options{query
},
124 'variables|vars|V=s' => \
$options{variables
},
125 'variable|var|d=s%' => \
$options{variables
},
126 'operation-name|n=s' => \
$options{operation_name
},
127 'transport|t=s%' => \
$options{transport
},
128 'format|f=s' => \
$options{format
},
129 'filter|p=s' => \
$options{filter
},
130 'unpack!' => \
$options{unpack},
131 'output|o=s' => \
$options{outfile
},
134 $options{url
} = shift @args if !$options{url
};
135 $options{query
} = shift @args if !$options{query
};
137 $options{query
} ||= '-';
139 my $transport = eval { _expand_vars
($options{transport
}) };
140 die "Two or more --transport keys are incompatible.\n" if $@;
142 if (ref $options{variables
}) {
143 $options{variables
} = eval { _expand_vars
($options{variables
}) };
144 die "Two or more --variable keys are incompatible.\n" if $@;
146 elsif ($options{variables
}) {
147 $options{variables
} = eval { $JSON->decode($options{variables
}) };
148 die "The --variables JSON does not parse.\n" if $@;
156 if (ref($item) eq 'ARRAY') {
157 my $first = @$item && $item->[0];
158 return join(',', @$item) if !ref($first);
159 return join(',', map { $JSON->encode($_) } @$item);
161 return $JSON->encode($item) if ref($item) eq 'HASH';
166 my ($data, $format) = @_;
167 $format = lc($format || 'json:pretty');
168 if ($format eq 'json' || $format eq 'json:pretty') {
169 my %opts = (allow_nonref
=> 1, canonical
=> 1);
170 $opts{pretty
} = 1 if $format eq 'json:pretty';
171 print JSON
::MaybeXS-
>new(%opts)->encode($data);
173 elsif ($format eq 'yaml') {
174 eval { require YAML
} or die "Missing dependency: YAML\n";
175 print YAML
::Dump
($data);
177 elsif ($format eq 'csv' || $format eq 'tsv' || $format eq 'table') {
178 my $sep = $format eq 'tsv' ? "\t" : ',';
180 my $unpacked = $data;
181 # $unpacked = $data->{data} if !$unpack && !$err;
182 $unpacked = $data->{data
} if ref $data eq 'HASH' && $data->{data
};
184 # check the response to see if it can be formatted
187 if (ref $unpacked eq 'HASH') {
188 if (keys %$unpacked == 1) {
189 my ($val) = values %$unpacked;
190 if (ref $val eq 'ARRAY') {
191 my $first = $val->[0];
192 if ($first && ref $first eq 'HASH') {
193 @columns = sort keys %$first;
195 map { [map { _stringify
($_) } @{$_}{@columns}] } @$val
199 @columns = keys %$unpacked;
200 $rows = [map { [map { _stringify
($_) } $_] } @$val];
205 elsif (ref $unpacked eq 'ARRAY') {
206 my $first = $unpacked->[0];
207 if ($first && ref $first eq 'HASH') {
208 @columns = sort keys %$first;
210 map { [map { _stringify
($_) } @{$_}{@columns}] } @$unpacked
214 @columns = qw(column);
215 $rows = [map { [map { _stringify
($_) } $_] } @$unpacked];
220 if ($format eq 'table') {
221 eval { require Text
::Table
::Any
} or die "Missing dependency: Text::Table::Any\n";
222 my $table = Text
::Table
::Any
::table
(
224 rows
=> [[@columns], @$rows],
225 backend
=> $ENV{PERL_TEXT_TABLE
},
230 eval { require Text
::CSV
} or die "Missing dependency: Text::CSV\n";
231 my $csv = Text
::CSV-
>new({binary
=> 1, sep
=> $sep, eol
=> $/});
232 $csv->print(*STDOUT
, [@columns]);
233 for my $row (@$rows) {
234 $csv->print(*STDOUT
, $row);
240 print STDERR
sprintf("Error: Response could not be formatted as %s.\n", uc($format));
244 elsif ($format eq 'string') {
248 elsif (ref $data eq 'ARRAY') {
249 print join("\n", @$data);
253 print STDERR
sprintf("Error: Response could not be formatted as %s.\n", $format);
257 elsif ($format eq 'perl') {
258 eval { require Data
::Dumper
} or die "Missing dependency: Data::Dumper\n";
259 print Data
::Dumper
::Dumper
($data);
263 print STDERR
"Error: Format not supported: $format\n";
273 my @segments = map { split(/\./, $_) } split(/(\[[^\.\]]+\])\.?/, $path);
274 for my $segment (@segments) {
275 if ($segment =~ /\[([^\.\]]+)\]/) {
276 $path[-1]{type
} = 'ARRAY' if @path;
283 $path[-1]{type
} = 'HASH' if @path;
298 while (my ($key, $value) = each %$vars) {
299 my $parsed_path = _parse_path
($key);
302 for my $segment (@$parsed_path) {
303 my $name = $segment->{name
};
304 my $type = $segment->{type
} || '';
305 my $next = $type eq 'HASH' ? {} : $type eq 'ARRAY' ? [] : $value;
306 if (ref $curr eq 'HASH') {
307 _croak
'Conflicting keys' if $segment->{index};
308 if (defined $curr->{$name}) {
309 _croak
'Conflicting keys' if $type ne ref $curr->{$name};
310 $next = $curr->{$name};
313 $curr->{$name} = $next;
316 elsif (ref $curr eq 'ARRAY') {
317 _croak
'Conflicting keys' if !$segment->{index};
318 if (defined $curr->[$name]) {
319 _croak
'Conflicting keys' if $type ne ref $curr->[$name];
320 $next = $curr->[$name];
323 $curr->[$name] = $next;
327 _croak
'Conflicting keys';
337 eval { require Pod
::Usage
};
339 my $ref = $VERSION eq '999.999' ? 'master' : "v$VERSION";
340 my $exit = (@_ == 1 && $_[0] =~ /^\d+$/ && $_[0]) //
341 (@_ % 2 == 0 && {@_}->{'-exitval'}) // 2;
343 Online documentation is available at:
345 https://github.com/chazmcgarvey/graphql-client/blob/$ref/README.md
347 Tip: To enable inline documentation, install the Pod::Usage module.
353 goto &Pod
::Usage
::pod2usage
;
362 This is the actual implementation of L<graphql>.
364 The interface is B<EXPERIMENTAL>. Don't rely on it.
This page took 0.064169 seconds and 3 git commands to generate.