-#!/usr/bin/env perl
-# PODNAME: graphql
+#! perl
# ABSTRACT: Command-line GraphQL client
-
-use warnings;
-use strict;
-
-use Getopt::Long;
-use GraphQL::Client;
-use Scalar::Util qw(reftype);
-
-our $VERSION = '999.999'; # VERSION
-
-my $url;
-my $transport = {};
-my $query = '-';
-my $variables = {};
-my $format = 'json:pretty';
-my $unpack = 0;
-my $outfile;
-GetOptions(
- 'url|u=s' => \$url,
- 'transport|t=s%' => \$transport,
- 'query|mutation=s' => \$query,
- 'variable|var|d=s%' => \$variables,
- 'format|f=s' => \$format,
- 'unpack!' => \$unpack,
- 'output|o=s' => \$outfile,
-) or die "Invalid options.\n";
-
-$url = shift if !$url;
-$query = shift if !$query || $query eq '-';
-
-my $client = GraphQL::Client->new(
- %$transport,
- url => $url,
-);
-$client->transport; # just make sure we can load the transport
-
-if (!$query || $query eq '-') {
- print STDERR "Interactive mode engaged! Waiting for a query on <STDIN>...\n" if -t STDIN;
- $query = do { local $/; <> };
-}
-
-my $resp = $client->request($query, $variables);
-my $err = $resp->{errors};
-my $data = !$unpack || $err ? $resp : $resp->{data};
-
-if ($outfile) {
- open(my $out, '>', $outfile) or die "Open $outfile failed: $!";
- *STDOUT = $out;
-}
-
-$format = lc($format);
-if ($format eq 'json') {
- require JSON::MaybeXS;
- print JSON::MaybeXS->new(utf8 => 1)->encode($data);
-}
-elsif ($format eq 'json:pretty') {
- require JSON::MaybeXS;
- print JSON::MaybeXS->new(utf8 => 1, pretty => 1)->encode($data);
-}
-elsif ($format eq 'yaml') {
- require YAML;
- print YAML::Dump($data);
-}
-else {
- require Data::Dumper;
- print Data::Dumper::Dumper($data);
-}
-
-exit($unpack && $err ? 1 : 0);
+# PODNAME: graphql
=head1 SYNOPSIS
- graphql <URL> <QUERY> [--var key=value]... [--transport key=value]...
- [--[no-]unpack] [--format json|json:pretty|yaml]
+ graphql <URL> <QUERY> [ [--variables JSON] | [--variable KEY=VALUE]... ]
+ [--operation-name NAME] [--transport KEY=VALUE]...
+ [--[no-]unpack] [--filter JSONPATH]
+ [--format json|json:pretty|yaml|perl|csv|tsv|table] [--output FILE]
+
+ graphql --version|--help|--manual
=head1 DESCRIPTION
C<graphql> is a command-line program for executing queries and mutations on
a L<GraphQL|https://graphql.org/> server.
+=head1 INSTALL
+
+There are several ways to install F<graphql> to your system.
+
+=head2 from CPAN
+
+You can install F<graphql> using L<cpanm>:
+
+ cpanm GraphQL::Client
+
+=head2 from GitHub
+
+You can also choose to download F<graphql> as a self-contained executable:
+
+ curl -OL https://raw.githubusercontent.com/chazmcgarvey/graphql-client/solo/graphql
+ chmod +x graphql
+
+To hack on the code, clone the repo instead:
+
+ git clone https://github.com/chazmcgarvey/graphql-client.git
+ cd graphql-client
+ make bootstrap # installs dependencies; requires cpanm
+
=head1 OPTIONS
-=head2 --url STR
+=head2 C<--url URL>
The URL of the GraphQL server endpoint.
If no C<--url> option is given, the first argument is assumed to be the URL.
+This option is required.
+
Alias: C<-u>
-=head2 --query STR
+=head2 C<--query STR>
The query or mutation to execute.
-If no C<--query> option is given, the first argument (after URL) is assumed to
-be the query.
+If no C<--query> option is given, the next argument (after URL) is assumed to be the query.
-If the value is C<-> (which is the default), the query will be read from
-C<STDIN>.
+If the value is "-" (which is the default), the query will be read from C<STDIN>.
See: L<https://graphql.org/learn/queries/>
Alias: C<--mutation>
-=head2 --variable KEY=VALUE
+=head2 C<--variables JSON>
+
+Provide the variables as a JSON object.
+
+Aliases: C<--vars>, C<-V>
+
+=head2 C<--variable KEY=VALUE>
+
+An alternative way to provide variables one at a time. This option can be repeated to provide
+multiple variables.
-A key-value pair
+If used in combination with L</"--variables JSON">, this option is silently ignored.
See: L<https://graphql.org/learn/queries/#variables>
Aliases: C<--var>, C<-d>
-=head2 --transport KEY=VALUE
+=head2 C<--operation-name NAME>
+
+Inform the server which query/mutation to execute.
+
+Alias: C<-n>
+
+=head2 C<--output FILE>
+
+Write the response to a file instead of STDOUT.
+
+Alias: C<-o>
+
+=head2 C<--transport KEY=VALUE>
Key-value pairs for configuring the transport (usually HTTP).
Alias: C<-t>
-=head2 --unpack
+=head2 C<--format STR>
+
+Specify the output format to use. See L</FORMAT>.
+
+Alias: C<-f>
+
+=head2 C<--unpack>
+
+Enables unpack mode.
+
+By default, the response structure is printed as-is from the server, and the program exits 0.
-Enables C<unpack> mode.
+When unpack mode is enabled, if the response completes with no errors, only the data section of
+the response is printed and the program exits 0. If the response has errors, the whole response
+structure is printed as-is and the program exits 1. See L</EXAMPLES> to see what this looks like in
+practice.
-By default, the response structure is printed as-is from the server, and the
-program exits 0.
+Use C<--no-unpack> to disable if unpack mode was enabled via C<GRAPHQL_CLIENT_OPTIONS>.
-When C<unpack> mode is enabled, if the response completes with no errors, only
-the data section of the response is printed and the program exits 0. If the
-response has errors, the whole response structure is printed as-is and the
-program exits 1.
+=head2 C<--filter JSONPATH>
-=head2 --format STR
+Filter the response based on a L<JSONPath|JSON::Path/SYNOPSIS> expression.
-Sets the output format. Possible values include:
+Requires L<JSON::Path>.
+
+Alias: C<-p>
+
+=head1 FORMAT
+
+The argument for L</"--format STR"> can be one of:
=for :list
-* C<json:pretty> (default)
-* C<json>
-* C<yaml>
-* C<dump>
+* C<csv> - Comma-separated values (requires L<Text::CSV>)
+* C<json:pretty> - Human-readable JSON (default)
+* C<json> - JSON
+* C<perl> - Perl code (requires L<Data::Dumper>)
+* C<table> - Table (requires L<Text::Table::Any>)
+* C<tsv> - Tab-separated values (requires L<Text::CSV>)
+* C<yaml> - YAML (requires L<YAML>)
-Alias: C<-f>
+The C<csv>, C<tsv>, and C<table> formats will only work if the response has a particular shape:
+
+ {
+ "data" : {
+ "onefield" : [
+ {
+ "key" : "value",
+ ...
+ },
+ ...
+ ]
+ }
+ }
+
+or
+
+ {
+ "data" : {
+ "onefield" : [
+ "value",
+ ...
+ ]
+ }
+ }
+
+If the response cannot be formatted, the default format will be used instead, an error message will
+be printed to STDERR, and the program will exit 3.
+
+Table formatting can be done by one of several different modules, each with its own features and
+bugs. The default module is L<Text::Table::Tiny>, but this can be overridden using the
+C<PERL_TEXT_TABLE> environment variable if desired, like this:
+
+ PERL_TEXT_TABLE=Text::Table::HTML graphql ... -f table
+
+The list of supported modules is at L<Text::Table::Any/@BACKENDS>.
=head1 EXAMPLES
-Different ways to provide the query/mutation:
+Different ways to provide the query/mutation to execute:
- graphql http://localhost:4000/graphql {hello}
+ graphql http://myserver/graphql {hello}
- echo {hello} | graphql http://localhost:4000/graphql
+ echo {hello} | graphql http://myserver/graphql
- graphql http://localhost:4000/graphql <<END
+ graphql http://myserver/graphql <<END
> {hello}
> END
- graphql http://localhost:4000/graphql
+ graphql http://myserver/graphql
Interactive mode engaged! Waiting for a query on <STDIN>...
{hello}
^D
-This example shows the effect of L<--unpack>:
+Execute a query with variables:
- graphql http://localhost:4000/graphql {hello}
+ graphql http://myserver/graphql <<END --var episode=JEDI
+ > query HeroNameAndFriends($episode: Episode) {
+ > hero(episode: $episode) {
+ > name
+ > friends {
+ > name
+ > }
+ > }
+ > }
+ > END
+
+ graphql http://myserver/graphql --vars '{"episode":"JEDI"}'
+
+Configure the transport:
+
+ graphql http://myserver/graphql {hello} -t headers.authorization='Basic s3cr3t'
+
+This example shows the effect of L</--unpack>:
+
+ graphql http://myserver/graphql {hello}
# Output:
{
}
}
- graphql --unpack http://localhost:4000/graphql {hello}
+ graphql http://myserver/graphql {hello} --unpack
# Output:
{
"hello" : "Hello world!"
}
-Execute a query with variables:
+=head1 ENVIRONMENT
- graphql unpack http://localhost:4000/graphql <<END --var episode=JEDI
- > query HeroNameAndFriends($episode: Episode) {
- > hero(episode: $episode) {
- > name
- > friends {
- > name
- > }
- > }
- > }
- > END
+Some environment variables affect the way C<graphql> behaves:
+
+=for :list
+* C<GRAPHQL_CLIENT_DEBUG> - Set to 1 to print diagnostic messages to STDERR.
+* C<GRAPHQL_CLIENT_HTTP_USER_AGENT> - Set the HTTP user agent string.
+* C<GRAPHQL_CLIENT_OPTIONS> - Set the default set of options.
+* C<PERL_TEXT_TABLE> - Set table format backend; see L</FORMAT>.
+
+=head1 EXIT STATUS
+
+Here is a consolidated summary of what exit statuses mean:
+
+=for :list
+* C<0> - Success
+* C<1> - Client or server errors
+* C<2> - Option usage is wrong
+* C<3> - Could not format the response as requested
+
+=head1 SEE ALSO
+
+=for :list
+* L<GraphQL::Client> - Programmatic interface
+
+=cut
+
+# FATPACK - Do not remove this line.
+
+use warnings;
+use strict;
+
+use GraphQL::Client::CLI;
+
+our $VERSION = '999.999'; # VERSION
+GraphQL::Client::CLI->main(@ARGV);