Developer-Dashboard

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

`-src.jar`, `src.zip`, `war`, and `jar` files under the current roots,
`~/.m2/repository`, Gradle caches, and `JAVA_HOME`. When a local archive
still does not provide the requested class, the helper can fetch a matching
Maven source jar, cache it under
`~/.developer-dashboard/cache/open-file/`, and then open the extracted Java
source.

## Data Query Commands

These built-in commands parse structured text and can then either extract a
dotted path or evaluate a Perl expression against the decoded document through
`$d`:

- `dashboard jq [path] [file]` for JSON
- `dashboard yq [path] [file]` for YAML
- `dashboard tomq [path] [file]` for TOML
- `dashboard propq [path] [file]` for Java properties

If the selected value is a hash or array, the command prints canonical JSON.
If the selected value is a scalar, it prints the scalar plus a trailing
newline.

The file path and query text are order-independent, and `$d` selects the
whole parsed document. For example, `cat file.json | dashboard jq '$d'` and
`dashboard jq file.json '$d'` return the same result. If the query text uses
`$d` inside a Perl expression, the command evaluates that expression against
the decoded document. For example, `echo '{"foo":[1],"bar":[2]}' | dashboard
jq 'sort keys %$d'` prints `["bar","foo"]`. The same contract applies to
`yq`, `tomq`, `propq`, `iniq`, `csvq`, and `xmlq`.

`xmlq` follows the same decoded-data model as the other query commands. XML
elements decode into nested hashes and arrays, repeated sibling tags become
arrays, attributes live under `_attributes`, and mixed text lives under
`_text`. That means `printf '<root`&lt;value>demo&lt;/value>&lt;/root>' | dashboard
xmlq root.value> prints `demo`, while `dashboard xmlq feed.xml '$d'` prints
the full decoded XML tree as canonical JSON.

# MANUAL

## Installation

Bootstrap a blank Alpine, Debian, Ubuntu, Fedora, or macOS machine from a checkout with:

    ./install.sh

Bootstrap a blank Windows PowerShell host from a checkout or the current shell with:

install.ps1  view on Meta::CPAN

        throw ("{0} failed for {1}: {2}" -f $Label, $Uri, $_.Exception.Message)
    }
    finally {
        $webClient.Dispose()
    }
}

function Get-RemoteJson {
    # Purpose: fetch JSON metadata for Windows bootstrap decisions through the shared resilient downloader.
    # Input: a URL string plus an optional label for diagnostics.
    # Output: returns the decoded PowerShell object graph from the remote JSON payload.
    param(
        [Parameter(Mandatory = $true)]
        [string]$Uri,
        [string]$Label = 'remote JSON request'
    )

    $jsonText = Get-RemoteText -Uri $Uri -Label $Label
    return ($jsonText | ConvertFrom-Json)
}

integration/blank-env/run-integration.pl  view on Meta::CPAN

    my $dir = dirname($destination);
    make_path($dir) if defined $dir && $dir ne '' && !-d $dir;
    copy( $source, $destination )
      or die "Unable to copy $source to $destination: $!";
    return $destination;
}

# _decode_json_tail($text)
# Decodes the trailing JSON object or array embedded at the end of command output.
# Input: output text string.
# Output: decoded Perl structure.
sub _decode_json_tail {
    my ($text) = @_;
    $text = '' if !defined $text;
    if ( $text =~ /(\[\s*[\s\S]*\])\s*\z/ ) {
        return decode_json($1);
    }
    if ( $text =~ /(\{\s*[\s\S]*\})\s*\z/ ) {
        return decode_json($1);
    }
    die "Unable to locate trailing JSON payload in command output\n";

integration/browser/run-bookmark-browser-smoke.pl  view on Meta::CPAN

        my $response = $ua->get($url);
        return if $response->is_success;
        sleep 0.25;
    }
    die "Timed out waiting for HTTP success at $url\n";
}

# _fetch_text($url)
# Fetches one browser or ajax URL over HTTP.
# Input: URL string.
# Output: decoded response body string.
sub _fetch_text {
    my ($url) = @_;
    my $ua = LWP::UserAgent->new( timeout => 5 );
    my $response = $ua->get($url);
    die "HTTP fetch failed for $url: " . $response->status_line . "\n"
      if !$response->is_success;
    return $response->decoded_content( charset => 'none' );
}

# _run_browser_dom(%args)
# Dumps the browser DOM for one URL through headless Chromium.
# Input: named args with url, user_data_dir, and optional browser_binary override.
# Output: dumped DOM string.
sub _run_browser_dom {
    my (%args) = @_;
    my $browser = $args{browser_binary} || _browser_binary();
    my @command = (

integration/windows/run-qemu-windows-smoke.sh  view on Meta::CPAN

  if ! command -v perl >/dev/null 2>&1; then
    echo "WINDOWS_STRAWBERRY_URL is required when perl is unavailable on the host" >&2
    exit 1
  fi

  WINDOWS_STRAWBERRY_URL="$(
    perl -MJSON::XS=decode_json -MLWP::UserAgent -e '
      my $ua = LWP::UserAgent->new( timeout => 20 );
      my $res = $ua->get("https://strawberryperl.com/releases.json");
      die $res->status_line unless $res->is_success;
      my $data = decode_json( $res->decoded_content );
      for my $release ( @{$data} ) {
        next if ( $release->{archname} || "" ) ne "MSWin32-x64-multi-thread";
        my $url = $release->{edition}{msi}{url} || "";
        next if $url eq "";
        print $url;
        exit 0;
      }
      die "No 64-bit Strawberry Perl MSI URL found in releases.json\n";
    '
  )"

lib/Developer/Dashboard.pm  view on Meta::CPAN

F<-src.jar>, F<src.zip>, F<war>, and F<jar> files under the current roots,
F<~/.m2/repository>, Gradle caches, and C<JAVA_HOME>. When a local archive
still does not provide the requested class, the helper can fetch a matching
Maven source jar, cache it under
F<~/.developer-dashboard/cache/open-file/>, and then open the extracted Java
source.

=head2 Data Query Commands

These built-in commands parse structured text and can then either extract a
dotted path or evaluate a Perl expression against the decoded document through
C<$d>:

=over 4

=item *

C<dashboard jq [path] [file]> for JSON

=item *

lib/Developer/Dashboard.pm  view on Meta::CPAN

=back

If the selected value is a hash or array, the command prints canonical JSON.
If the selected value is a scalar, it prints the scalar plus a trailing
newline.

The file path and query text are order-independent, and C<$d> selects the
whole parsed document. For example, C<cat file.json | dashboard jq '$d'> and
C<dashboard jq file.json '$d'> return the same result. If the query text uses
C<$d> inside a Perl expression, the command evaluates that expression against
the decoded document. For example, C<echo '{"foo":[1],"bar":[2]}' | dashboard
jq 'sort keys %$d'> prints C<["bar","foo"]>. The same contract applies to
C<yq>, C<tomq>, C<propq>, C<iniq>, C<csvq>, and C<xmlq>.

C<xmlq> follows the same decoded-data model as the other query commands. XML
elements decode into nested hashes and arrays, repeated sibling tags become
arrays, attributes live under C<_attributes>, and mixed text lives under
C<_text>. That means C<printf '<root><value>demo</value></root>' | dashboard
xmlq root.value> prints C<demo>, while C<dashboard xmlq feed.xml '$d'> prints
the full decoded XML tree as canonical JSON.

=head1 MANUAL

=head2 Installation

Bootstrap a blank Alpine, Debian, Ubuntu, Fedora, or macOS machine from a checkout with:

  ./install.sh

Bootstrap a blank Windows PowerShell host from a checkout or the current shell with:

lib/Developer/Dashboard/ActionRunner.pm  view on Meta::CPAN

            ( $action->{id} || '' ),
            ( $page->can('canonical_instruction') ? $page->canonical_instruction : '' ),
        ),
    };
    return encode_payload( json_encode($payload) );
}

# decode_action_payload($token)
# Decodes an encoded action transport token.
# Input: encoded action token string.
# Output: decoded action payload hash reference.
sub decode_action_payload {
    my ( $self, $token ) = @_;
    my $payload = Developer::Dashboard::JSON::json_decode( decode_payload($token) );
    die 'Action payload must be a hash' if ref($payload) ne 'HASH';
    return $payload;
}

# run_encoded_action(%args)
# Decodes and executes an encoded action transport payload.
# Input: encoded action token and optional params hash.

lib/Developer/Dashboard/ActionRunner.pm  view on Meta::CPAN

=head1 WHY IT EXISTS

It exists because bookmark actions need one place that owns trust checks and command execution semantics. Without that boundary, the web layer, page renderer, and saved-action transport would each grow their own action policy and drift apart.

=head1 WHEN TO USE

Use this file when changing page action security, the transport payload for encoded actions, command backgrounding rules, or the way page actions report stdout, stderr, and exit codes back to callers.

=head1 HOW TO USE

Construct it with a file registry and path registry, then call C<run_page_action> for one decoded action hash or C<encode_action_payload> when a route needs a portable transport token. Keep action execution policy here instead of duplicating it in co...

=head1 WHAT USES IT

It is used by C<Developer::Dashboard::Web::App>, by provider pages resolved through the page resolver, by saved page action buttons in the browser, and by action/web regression tests.

=head1 EXAMPLES

Example 1:

  perl -Ilib -MDeveloper::Dashboard::ActionRunner -e 1

lib/Developer/Dashboard/CLI/OpenFile.pm  view on Meta::CPAN

sub _maven_search_documents {
    my ($name) = @_;
    return if !defined $name || $name eq '';

    my $query = uri_escape_utf8(qq{fc:"$name"});
    my $url   = "https://search.maven.org/solrsearch/select?q=$query&rows=20&wt=json";
    my $ua    = LWP::UserAgent->new( timeout => 10 );
    my $res   = $ua->get($url);
    return if !$res->is_success;

    my $payload = eval { decode_json( $res->decoded_content ) };
    return if !$payload || ref($payload) ne 'HASH';
    return @{ $payload->{response}{docs} || [] };
}

# _download_maven_source_jar(%args)
# Downloads one Maven Central source jar into the dashboard cache tree when it is missing.
# Input: path registry object and one Maven search document hash reference.
# Output: local source-jar path string or undef on failure.
sub _download_maven_source_jar {
    my (%args) = @_;

lib/Developer/Dashboard/CLI/Query.pm  view on Meta::CPAN

# Input: parsed Perl data structure and raw path or expression string.
# Output: selected scalar, hash ref, array ref, or undef.
sub _select_query_value {
    my ( $data, $path ) = @_;
    return $data if !defined $path || $path eq '';
    return _evaluate_query_expression( $data, $path ) if _path_uses_perl_expression($path);
    return _extract_query_path( $data, $path );
}

# _evaluate_query_expression($data, $expr)
# Evaluates a user-supplied Perl expression with $d bound to the decoded query data.
# Input: parsed Perl data structure and expression string.
# Output: scalar result, array ref for list results, or undef.
sub _evaluate_query_expression {
    my ( $data, $expr ) = @_;
    my $code = eval <<"PERL_EVAL";
sub {
    my (\$d) = \@_;
    return do { $expr };
}
PERL_EVAL

lib/Developer/Dashboard/CLI/Query.pm  view on Meta::CPAN

    my %repeated;

    while (@items) {
        my $name = shift @items;
        my $value = shift @items;
        if ( defined $name && $name eq '0' ) {
            push @text, $value if defined $value && $value !~ /^\s*$/;
            next;
        }

        my $decoded = _xml_element_payload($value);
        if ( exists $elements{$name} ) {
            if ( !$repeated{$name} ) {
                $elements{$name} = [ $elements{$name} ];
                $repeated{$name} = 1;
            }
            push @{ $elements{$name} }, $decoded;
            next;
        }
        $elements{$name} = $decoded;
    }

    my $text = join '', @text;
    my $has_attrs = ref($attrs) eq 'HASH' && keys %{$attrs};
    my $has_children = keys %elements ? 1 : 0;

    return $text if !$has_attrs && !$has_children;

    my %node = %elements;
    $node{_attributes} = $attrs if $has_attrs;

lib/Developer/Dashboard/CLI/Query.pm  view on Meta::CPAN

output, or the exact error surface for malformed input and missing path
segments.

=head1 HOW TO USE

Call C<run_query_command> from a staged helper such as C<jq> or C<tomq>,
passing the helper name and the raw argv list. The module treats the first
existing file argument as the input source, rejoins the remaining non-file
arguments into one query string, accepts C<$d> or C<.> for the whole parsed
document, uses dotted traversal for plain path strings, and evaluates real
C<$d>-based Perl expressions such as C<sort keys %$d> against the decoded data.
Scalars print as plain text while arrays and hashes are emitted as canonical
JSON. The XML path now decodes XML into nested hashes and arrays so dotted
paths and C<$d> expressions work there too.

=head1 WHAT USES IT

It is used by the private query helper scripts under C<share/private-cli/>,
by install and release smoke runs that verify format-specific helpers, and by
coverage tests that exercise parser choice, order-independent argv handling,
root-document queries, and format-specific edge cases.

lib/Developer/Dashboard/Codec.pm  view on Meta::CPAN


    gzip \$text => \my $zipped
      or die "gzip failed: $GzipError";

    return encode_base64( $zipped, '' );
}

# decode_payload($token)
# Decodes and inflates a previously encoded payload token.
# Input: base64 token scalar.
# Output: decoded text scalar or undef for undefined/empty input.
sub decode_payload {
    my ($token) = @_;
    return if !defined $token || $token eq '';

    my $zipped = decode_base64($token);
    gunzip \$zipped => \my $text
      or die "gunzip failed: $GunzipError";

    return $text;
}

lib/Developer/Dashboard/Collector.pm  view on Meta::CPAN

    unlink $file if -f $file;
    rename $tmp, $file or die "Unable to rename $tmp to $file: $!";
    $self->{paths}->secure_file_permissions($file);
    return $file;
}

# _read_status_file($file)
# Reads one collector status JSON file, returning undef when it is missing or
# invalid instead of exploding across status polling paths.
# Input: collector status file path string.
# Output: decoded status hash reference or undef.
sub _read_status_file {
    my ( $self, $file ) = @_;
    return if !-f $file;
    open my $fh, '<:raw', $file or die "Unable to read $file: $!";
    local $/;
    my $raw = <$fh>;
    my $data = eval { json_decode($raw) };
    return $data if !$@;
    return;
}

lib/Developer/Dashboard/CollectorRunner.pm  view on Meta::CPAN


# _indicator_template_vars(%args)
# Decodes collector stdout JSON into the TT variable set for indicator
# templates.
# Input: collector_name string and stdout JSON text.
# Output: hash reference of template variables.
sub _indicator_template_vars {
    my ( $self, %args ) = @_;
    my $collector_name = $args{collector_name} || die 'Missing collector name';
    my $stdout = defined $args{stdout} ? $args{stdout} : '';
    my $decoded = eval { json_decode($stdout) };
    if ($@) {
        my $error = "$@";
        $error =~ s/\s+\z//;
        die sprintf "Collector '%s' indicator icon template requires collector stdout JSON: %s\n", $collector_name, $error;
    }

    my %vars = ( data => $decoded );
    if ( ref($decoded) eq 'HASH' ) {
        %vars = ( %vars, %{$decoded} );
    }
    return \%vars;
}

# _append_error_text($stderr, $error)
# Appends one explicit runtime error line to captured stderr text.
# Input: existing stderr text and error text string.
# Output: merged stderr text string.
sub _append_error_text {
    my ( $self, $stderr, $error ) = @_;

lib/Developer/Dashboard/CollectorRunner.pm  view on Meta::CPAN

    my ( $self, $name ) = @_;
    my $file = $self->_statefile($name);
    return if !-f $file;
    my $last_error = '';
    for ( 1 .. 3 ) {
        open my $fh, '<', $file or die "Unable to read $file: $!";
        local $/;
        my $payload = scalar <$fh>;
        close $fh;
        if ( defined $payload && $payload ne '' ) {
            my $decoded = eval { json_decode($payload) };
            return $decoded if $decoded;
            $last_error = $@ || 'Unable to decode loop state JSON';
        }
        else {
            $last_error = "Loop state file $file was empty";
        }
        sleep 0.01 if $_ < 3;
    }
    die $last_error;
}

lib/Developer/Dashboard/Config.pm  view on Meta::CPAN

    my $file = $self->_global_config_file;
    return {} if !-f $file;
    open my $fh, '<:raw', $file or die "Unable to read $file: $!";
    local $/;
    return json_decode(<$fh>);
}

# _load_writable_api_registry()
# Loads only the writable runtime-layer config/api.json payload.
# Input: none.
# Output: decoded API config hash reference for the writable layer only.
sub _load_writable_api_registry {
    my ($self) = @_;
    my $file = $self->_global_api_file;
    return {} if !-f $file;
    return $self->_load_json_hash_file($file);
}

# _load_json_hash_file($file)
# Reads one JSON config file and requires it to decode to a hash reference.
# Input: readable filesystem path string.
# Output: decoded hash reference.
sub _load_json_hash_file {
    my ( $self, $file ) = @_;
    open my $fh, '<:raw', $file or die "Unable to read $file: $!";
    local $/;
    my $decoded = json_decode(<$fh>);
    die "Expected JSON object in $file\n" if ref($decoded) ne 'HASH';
    return $decoded;
}

# _skill_config_fragments()
# Loads installed skill config/config.json payloads as underscored runtime config fragments.
# Input: none.
# Output: ordered list of hash refs such as { _skill_name => { ... } }.
sub _skill_config_fragments {
    my ($self) = @_;
    my @fragments;
    for my $entry ( $self->_skill_config_entries ) {

lib/Developer/Dashboard/DataHelper.pm  view on Meta::CPAN

# Encodes a Perl value to canonical JSON text.
# Input: any JSON-encodable Perl value.
# Output: JSON string.
sub j {
    return json_encode( $_[0] );
}

# je($text)
# Decodes JSON text to a Perl value.
# Input: JSON string.
# Output: decoded Perl value.
sub je {
    return json_decode( $_[0] // '' );
}

1;

__END__

=head1 NAME

lib/Developer/Dashboard/EnvAudit.pm  view on Meta::CPAN

# _load_from_env()
# Lazily rehydrates the audit inventory from the exported process environment
# so exec'd child helpers can inspect the same env provenance.
# Input: none.
# Output: true value.
sub _load_from_env {
    my ($class) = @_;
    return 1 if %AUDIT;
    my $raw = $ENV{DEVELOPER_DASHBOARD_ENV_AUDIT} || '';
    return 1 if $raw eq '';
    my $decoded = json_decode($raw);
    die "DEVELOPER_DASHBOARD_ENV_AUDIT must decode to a hash\n" if ref($decoded) ne 'HASH';
    %AUDIT = map {
        $_ => {
            value   => $decoded->{$_}{value},
            envfile => $decoded->{$_}{envfile},
        }
    } CORE::keys %{$decoded};
    return 1;
}

# _sync_to_env()
# Serializes the in-process audit inventory back into the environment so exec'd
# child processes can inspect the same env provenance.
# Input: none.
# Output: true value.
sub _sync_to_env {
    my ($class) = @_;

lib/Developer/Dashboard/JSON.pm  view on Meta::CPAN

# Serializes a Perl value into canonical pretty JSON.
# Input: scalar/array/hash reference.
# Output: JSON text string.
sub json_encode {
    return JSON::XS->new->utf8->canonical->pretty->encode( $_[0] );
}

# json_decode($json)
# Parses JSON text into a Perl data structure.
# Input: JSON text string.
# Output: decoded Perl value.
sub json_decode {
    return JSON::XS->new->utf8->decode( $_[0] );
}

1;

__END__

=head1 NAME

lib/Developer/Dashboard/PageDocument.pm  view on Meta::CPAN

  $runtime_errors
</main>
</body>
</html>
HTML
}

# _decode_structured_json($text)
# Safely decodes structured JSON from instruction sections.
# Input: text block string.
# Output: decoded Perl value or empty hash reference on failure.
sub _decode_structured_json {
    my ($text) = @_;
    $text = _trim($text);
    return {} if $text eq '';
    my $value = eval { json_decode($text) };
    return defined $value ? $value : {};
}

# _decode_stash_section($text)
# Decodes older or modern STASH content into a hash reference.

lib/Developer/Dashboard/PageStore.pm  view on Meta::CPAN

    my ( $self, $instruction ) = @_;
    return 0 if !defined $instruction || $instruction eq '';
    return 1 if $instruction =~ /\[%/;
    return 1 if $instruction =~ /<\s*[A-Za-z!\/][^>]*>/;
    return 0;
}

# _read_saved_instruction($file)
# Reads one saved bookmark file and normalizes older-invalid UTF-8 bytes.
# Input: bookmark file path string.
# Output: decoded instruction text string that is safe to emit as UTF-8 HTML/text.
sub _read_saved_instruction {
    my ( $self, $file ) = @_;
    open my $fh, '<:raw', $file or die "Unable to read $file: $!";
    local $/;
    my $raw = <$fh>;
    close $fh or die "Unable to close $file: $!";
    return '' if !defined $raw;
    my $text = eval { decode( 'UTF-8', $raw, FB_CROAK ) } || decode( 'UTF-8', $raw, FB_DEFAULT );
    return $self->_normalize_legacy_icon_markup($text);
}

# _normalize_legacy_icon_markup($text)
# Repairs browser-visible icon placeholders left behind by malformed older bookmark bytes.
# Input: decoded bookmark instruction text string.
# Output: normalized instruction text string with stable fallback glyphs in icon HTML contexts.
sub _normalize_legacy_icon_markup {
    my ( $self, $text ) = @_;
    return '' if !defined $text;
    $text =~ s/\x{1F9D1}\x{FFFD}\x{1F4BB}/\x{1F9D1}\x{200D}\x{1F4BB}/g;
    $text =~ s{(<h2>)\x{FFFD}(\s+)}{$1â—ˆ$2}g;
    $text =~ s{(<span\s+class="icon">)[^<]*\x{FFFD}[^<]*(</span>)}{$1🏷️$2}g;
    return $text;
}

lib/Developer/Dashboard/SkillDispatcher.pm  view on Meta::CPAN

        return {} if ref($config) ne 'HASH';
        $merged = $self->_merge_skill_hashes( $merged, $config );
    }

    return $merged;
}

# config_fragment($skill_name)
# Wraps one installed skill config under its underscored runtime key for merged config use.
# Input: skill repo name.
# Output: hash ref containing one underscored skill key and its decoded config.
sub config_fragment {
    my ( $self, $skill_name ) = @_;
    return {} if !$skill_name;
    my $config = $self->get_skill_config($skill_name);
    return {} if ref($config) ne 'HASH' || !%{$config};
    return { '_' . $skill_name => $config };
}

# get_skill_path($skill_name)
# Returns the path to an installed skill.

lib/Developer/Dashboard/SkillDispatcher.pm  view on Meta::CPAN

# Input: skill repository name string.
# Output: hash reference keyed by ajax file path.
sub _skill_ajax_routes_for {
    my ( $self, $skill_name ) = @_;
    return $self->_skill_routes_for( $skill_name, 'ajax' );
}

# _load_skill_routes_file($routes_file)
# Parses one config/routes.json file and validates the top-level schema.
# Input: absolute routes.json file path.
# Output: decoded hash reference.
sub _load_skill_routes_file {
    my ( $self, $routes_file ) = @_;
    open my $fh, '<', $routes_file or die "Unable to read $routes_file: $!";
    local $/;
    my $json_text = <$fh>;
    close $fh;
    my $payload = eval { decode_json($json_text) };
    die "Invalid JSON in $routes_file: $@" if $@;
    die "$routes_file must contain a JSON object" if ref($payload) ne 'HASH';
    my @non_version_keys = grep { $_ ne 'version' } keys %{$payload};

lib/Developer/Dashboard/SkillDispatcher.pm  view on Meta::CPAN

        }
        else {
            $payload->{$kind} = {};
        }
    }
    return $payload;
}

# _expand_flat_skill_routes_payload($routes_file, $payload)
# Converts the flat custom-path config/routes.json schema into the internal typed route map.
# Input: absolute routes.json file path and decoded payload hash reference.
# Output: normalized payload hash reference with app/ajax/js/css/others maps.
sub _expand_flat_skill_routes_payload {
    my ( $self, $routes_file, $payload ) = @_;
    my %expanded = map { $_ => {} } qw(app ajax js css others);
    my %claimed_targets;
    for my $route_path ( sort grep { $_ ne 'version' } keys %{$payload} ) {
        die "$routes_file route path '$route_path' must start with /"
          if $route_path !~ m{\A/};
        my $route = $payload->{$route_path};
        my ( $to, $type );

lib/Developer/Dashboard/SkillManager.pm  view on Meta::CPAN

# Output: ordered list of npm install spec strings.
sub _package_json_dependency_specs {
    my ( $self, $package_json ) = @_;
    return () if !defined $package_json || !-f $package_json;

    open my $fh, '<', $package_json or die "Unable to read $package_json: $!";
    local $/;
    my $content = <$fh>;
    close $fh;

    my $decoded = eval { decode_json($content) };
    die "Unable to parse $package_json: $@" if !$decoded || $@;

    my @specs;
    for my $section ( qw(dependencies devDependencies optionalDependencies peerDependencies) ) {
        my $entries = $decoded->{$section};
        next if ref($entries) ne 'HASH';
        for my $name ( sort keys %{$entries} ) {
            my $version = $entries->{$name};
            push @specs, defined $version && $version ne '' ? "$name\@$version" : $name;
        }
    }

    return @specs;
}

lib/Developer/Dashboard/SkillManager.pm  view on Meta::CPAN

            indicator      => $indicator,
        );
        push @items, \%item;
    }
    return \@items;
}

# _read_skill_config_file($skill_path)
# Reads one skill config/config.json file directly.
# Input: absolute skill root directory path.
# Output: decoded hash reference or empty hash reference on invalid/missing JSON.
sub _read_skill_config_file {
    my ( $self, $skill_path ) = @_;
    my $config_file = File::Spec->catfile( $skill_path, 'config', 'config.json' );
    return {} if !-f $config_file;
    open my $fh, '<', $config_file or return {};
    local $/;
    my $json_text = <$fh>;
    close $fh;
    my $config = eval { decode_json($json_text) };
    return ref($config) eq 'HASH' ? $config : {};

share/private-cli/csvq  view on Meta::CPAN


Use this file when you are changing how C<dashboard csvq> chooses between STDIN and files, how it reports parse errors, or how the staged helper is wired into the shared query implementation.

=head1 HOW TO USE

Users run C<dashboard csvq [path] [file]>. The staged helper selects the CSV
backend in the shared query runtime, which accepts the file and path in either
order, reads STDIN when no file is given, and treats the dotted path as
C<row.column> indexes such as C<1.1>. Use C<$d> or C<.> to print the entire
parsed row array as JSON. If the query text uses C<$d> inside a Perl
expression, the helper evaluates that expression against the decoded CSV rows
before formatting the result.

=head1 WHAT USES IT

It is used by the public C<dashboard csvq> command, by shell snippets that want a runtime-local CSV query tool, by integration smoke runs, and by the focused query-helper coverage under C<t/>.

=head1 EXAMPLES

  printf 'alpha,beta\n7,8\n' | dashboard csvq 1.1
  dashboard csvq data.csv '$d'

share/private-cli/iniq  view on Meta::CPAN


Use this file when you are changing how C<dashboard iniq> chooses between STDIN and files, how it reports parse errors, or how the staged helper is wired into the shared query implementation.

=head1 HOW TO USE

Users run C<dashboard iniq [path] [file]>. The staged helper selects the INI
backend in the shared query runtime, which accepts the file and path in either
order, reads STDIN when no file is given, treats section keys as dotted paths
such as C<alpha.beta>, and prints the whole parsed document when the path is
C<$d> or C<.>. If the query text uses C<$d> inside a Perl expression, the
helper evaluates that expression against the decoded INI document before
formatting the result.

=head1 WHAT USES IT

It is used by the public C<dashboard iniq> command, by shell snippets that want a runtime-local INI query tool, by integration smoke runs, and by the focused query-helper coverage under C<t/>.

=head1 EXAMPLES

  printf '[alpha]\nbeta=6\n' | dashboard iniq alpha.beta
  dashboard iniq config.ini '$d'

share/private-cli/jq  view on Meta::CPAN


Use this file when you are changing how C<dashboard jq> chooses between STDIN and files, how it reports parse errors, or how the staged helper is wired into the shared query implementation.

=head1 HOW TO USE

Users run C<dashboard jq [path] [file]>. The staged helper under
F<~/.developer-dashboard/cli/dd/jq> re-enters the shared query runtime, which
parses JSON, accepts the file and path in either order, reads STDIN when no
file is given, prints scalar hits as plain text, and prints the whole parsed
document when the path is C<$d> or C<.>. If the query text uses C<$d> inside a
Perl expression, the helper evaluates that expression against the decoded JSON
document before formatting the result.

=head1 WHAT USES IT

It is used by the public C<dashboard jq> command, by shell snippets that want a runtime-local JSON query tool, by integration smoke runs, and by the focused query-helper coverage under C<t/>.

=head1 EXAMPLES

  printf '{"alpha":{"beta":2}}' | dashboard jq alpha.beta
  dashboard jq response.json '$d'

share/private-cli/propq  view on Meta::CPAN


Use this file when you are changing how C<dashboard propq> chooses between STDIN and files, how it reports parse errors, or how the staged helper is wired into the shared query implementation.

=head1 HOW TO USE

Users run C<dashboard propq [path] [file]>. The staged helper selects the
Java-properties backend in the shared query runtime, which accepts the file and
path in either order, reads STDIN when no file is given, preserves dotted keys
such as C<alpha.beta>, and prints the whole parsed document when the path is
C<$d> or C<.>. If the query text uses C<$d> inside a Perl expression, the
helper evaluates that expression against the decoded Java-properties document
before formatting the result.

=head1 WHAT USES IT

It is used by the public C<dashboard propq> command, by shell snippets that want a runtime-local Java properties query tool, by integration smoke runs, and by the focused query-helper coverage under C<t/>.

=head1 EXAMPLES

  printf 'alpha.beta=5\nname=demo\n' | dashboard propq alpha.beta
  dashboard propq app.properties '$d'

share/private-cli/tomq  view on Meta::CPAN


Use this file when you are changing how C<dashboard tomq> chooses between STDIN and files, how it reports parse errors, or how the staged helper is wired into the shared query implementation.

=head1 HOW TO USE

Users run C<dashboard tomq [path] [file]>. The staged helper selects the TOML
backend in the shared query runtime, which accepts the file and path in either
order, reads STDIN when no file is given, prints scalar hits as plain text, and
prints the whole parsed document when the path is C<$d> or C<.>. If the query
text uses C<$d> inside a Perl expression, the helper evaluates that expression
against the decoded TOML document before formatting the result.

=head1 WHAT USES IT

It is used by the public C<dashboard tomq> command, by shell snippets that want a runtime-local TOML query tool, by integration smoke runs, and by the focused query-helper coverage under C<t/>.

=head1 EXAMPLES

  printf '[alpha]\nbeta = 4\n' | dashboard tomq alpha.beta
  dashboard tomq settings.toml '$d'
  dashboard tomq '$d' settings.toml

share/private-cli/xmlq  view on Meta::CPAN


This staged helper owns the public C<dashboard xmlq> command. It accepts a
path or C<$d>-based Perl expression plus an optional input file, then hands the
request to the shared query runtime so parsing, file-vs-STDIN handling, and
output formatting stay consistent with the rest of the dashboard toolchain.

=head1 WHY IT EXISTS

It exists because XML inspection is part of the dashboard query family and
should stay behind the same staged-helper and shared-runtime contract as the
other query helpers. The helper now exposes decoded XML trees instead of making
callers dig raw XML back out of a wrapper field.

=head1 WHEN TO USE

Use this file when changing the public C<dashboard xmlq> argv contract, the XML
helper examples, or the staged handoff into the shared query runtime.

=head1 HOW TO USE

Users run C<dashboard xmlq [path] [file]>. The staged helper selects the XML
backend in the shared query runtime, which accepts the file and path in either
order, reads STDIN when no file is given, decodes XML into nested Perl hashes
and arrays, stores XML attributes under C<_attributes>, stores mixed text under
C<_text>, and lets dotted paths or C<$d>-based Perl expressions walk that
decoded tree. Use C<$d> or C<.> when you need the whole decoded document.

=head1 WHAT USES IT

It is used by the public C<dashboard xmlq> command, by shell snippets that want
a runtime-local XML inspection tool, and by query-helper coverage under C<t/>
that locks in the decoded-tree contract.

=head1 EXAMPLES

  printf '<root><value>demo</value></root>' | dashboard xmlq root.value
  dashboard xmlq feed.xml '$d'
  dashboard xmlq '$d' feed.xml
  printf '<root><item id="1">x</item><item id="2">y</item></root>' | dashboard xmlq 'join q(,), map { $_->{_attributes}{id} } @{ $d->{root}{item} }'

=for comment FULL-POD-DOC END

share/private-cli/yq  view on Meta::CPAN


Use this file when you are changing how C<dashboard yq> chooses between STDIN and files, how it reports parse errors, or how the staged helper is wired into the shared query implementation.

=head1 HOW TO USE

Users run C<dashboard yq [path] [file]>. The staged helper selects the YAML
backend in the shared query runtime, which accepts the file and path in either
order, reads STDIN when no file is given, prints scalar hits as plain text, and
prints the whole parsed document when the path is C<$d> or C<.>. If the query
text uses C<$d> inside a Perl expression, the helper evaluates that expression
against the decoded YAML document before formatting the result.

=head1 WHAT USES IT

It is used by the public C<dashboard yq> command, by shell snippets that want a runtime-local YAML query tool, by integration smoke runs, and by the focused query-helper coverage under C<t/>.

=head1 EXAMPLES

  printf 'alpha:\n  beta: 3\n' | dashboard yq alpha.beta
  dashboard yq config.yml '$d'
  dashboard yq '$d' config.yml

t/01-page-flow.t  view on Meta::CPAN


is_deeply([$store->list_saved_pages], ['example'], 'saved page is listed');

my $loaded = $store->load_saved_page('example');
is($loaded->as_hash->{title}, 'Example', 'saved page loads back');
is($loaded->{meta}{raw_instruction}, $page->canonical_instruction, 'saved page load keeps the raw saved instruction text');

my $token = $store->encode_page($page);
ok($token, 'page token generated');

my $decoded = $store->load_transient_page($token);
is($decoded->as_hash->{layout}{body}, 'hello world', 'transient page decodes');
like($decoded->canonical_instruction, qr/^TITLE:\s+Example/m, 'transient page round-trips canonical instruction');

my $legacy = <<'PAGE';
TITLE: Legacy Example
:--------------------------------------------------------------------------------:
STASH:
  name => 'Michael'
:--------------------------------------------------------------------------------:
HTML: Hello [% name %]
:--------------------------------------------------------------------------------:
code1: print "Legacy output";

t/05-cli-smoke.t  view on Meta::CPAN

        sleep 0.25;
    }
    ok( $readonly_ready_response && $readonly_ready_response->is_success, 'no-editor live server exposes the system status route before route assertions begin' );
    my $render_response;
    for ( 1 .. 240 ) {
        $render_response = $readonly_ua->get("http://127.0.0.1:$readonly_port/app/readonly");
        last if $render_response->is_success;
        sleep 0.25;
    }
    ok( $render_response && $render_response->is_success, 'no-editor live server serves saved page render routes' );
    my $render_body = decode( 'UTF-8', $render_response->decoded_content );
    unlike( $render_body, qr/id="share-url"/, 'no-editor live server hides the share link from render views' );
    unlike( $render_body, qr/id="view-source-url"/, 'no-editor live server hides the view-source link from render views' );
    unlike( $render_body, qr/id="play-url"/, 'no-editor live server hides the play link from render views' );
    my $edit_response;
    for ( 1 .. 240 ) {
        $edit_response = $readonly_ua->get("http://127.0.0.1:$readonly_port/app/readonly/edit");
        last if $edit_response->code == 403;
        sleep 0.25;
    }
    is( $edit_response->code, 403, 'no-editor live server blocks direct bookmark editor routes' );

t/05-cli-smoke.t  view on Meta::CPAN

        sleep 0.25;
    }
    ok( $noind_ready_response && $noind_ready_response->is_success, 'no-indicators live server exposes the system status route before route assertions begin' );
    my $noind_render_response;
    for ( 1 .. 240 ) {
        $noind_render_response = $noind_ua->get("http://127.0.0.1:$noind_port/app/noind");
        last if $noind_render_response->is_success;
        sleep 0.25;
    }
    ok( $noind_render_response && $noind_render_response->is_success, 'no-indicators live server serves saved page render routes' );
    my $noind_render_body = decode( 'UTF-8', $noind_render_response->decoded_content );
    unlike( $noind_render_body, qr/id="status-on-top"/, 'no-indicators live server hides the top-right indicator strip' );
    unlike( $noind_render_body, qr/id="status-datetime"/, 'no-indicators live server hides the top-right date-time marker' );
    unlike( $noind_render_body, qr/id="status-server"/, 'no-indicators live server hides the top-right server marker' );
    unlike( $noind_render_body, qr/class="user-name-and-icon"/, 'no-indicators live server hides the top-right user marker' );
    my $noind_status_response;
    for ( 1 .. 240 ) {
        $noind_status_response = $noind_ua->get("http://127.0.0.1:$noind_port/system/status");
        last if $noind_status_response->code == 200;
        sleep 0.25;
    }
    is( $noind_status_response->code, 200, 'no-indicators live server keeps the status endpoint available' );
    like( decode( 'UTF-8', $noind_status_response->decoded_content ), qr/"array"\s*:/, 'no-indicators live server still exposes status payload data' );
    my $noind_ps1 = _run_in_home( $noind_home, "$perl -I'$lib' '$dashboard' ps1 --jobs 0" );
    like( $noind_ps1, qr/\S/, 'no-indicators mode does not blank the terminal prompt output' );
    my $noind_config_file = File::Spec->catfile( $noind_home, '.developer-dashboard', 'config', 'config.json' );
    open my $noind_config_fh, '<', $noind_config_file or die "Unable to read $noind_config_file: $!";
    my $noind_config = do { local $/; <$noind_config_fh> };
    close $noind_config_fh;
    like( $noind_config, qr/"web"\s*:\s*\{[\s\S]*"no_indicators"\s*:\s*1/s, 'dashboard serve --no-indicator persists no_indicators in config' );
    my $noind_restart = json_decode( _run_in_home( $noind_home, "$perl -I'$lib' '$dashboard' restart -o json --host 127.0.0.1 --port $noind_port" ) );
    ok( $noind_restart->{web_pid}, 'dashboard restart keeps managing the no-indicators web service' );
    my $post_restart_render;
    for ( 1 .. 240 ) {
        $post_restart_render = $noind_ua->get("http://127.0.0.1:$noind_port/app/noind");
        last if $post_restart_render->is_success;
        sleep 0.25;
    }
    ok( $post_restart_render && $post_restart_render->is_success, 'no-indicators live server remains reachable after restart' );
    my $post_restart_body = decode( 'UTF-8', $post_restart_render->decoded_content );
    unlike( $post_restart_body, qr/id="status-on-top"/, 'dashboard restart preserves the no-indicators top-right strip removal' );
    my $noind_stop = json_decode( _run_in_home( $noind_home, "$perl -I'$lib' '$dashboard' stop -o json" ) );
    ok( ref( $noind_stop->{collectors} ) eq 'ARRAY', 'dashboard stop still works after a no-indicators lifecycle run' );
}
{
    my $collector_log_home = tempdir( CLEANUP => 1 );
    local $ENV{HOME} = $collector_log_home;
    local $ENV{DEVELOPER_DASHBOARD_BOOKMARKS};
    local $ENV{DEVELOPER_DASHBOARD_CONFIGS};
    local $ENV{DEVELOPER_DASHBOARD_CHECKERS};

t/05-cli-smoke.t  view on Meta::CPAN

is( $json_value, "2\n", 'jq extracts scalar JSON values' );
my $json_file = File::Spec->catfile( $open_root, 'sample.json' );
open my $json_fh, '>', $json_file or die "Unable to write $json_file: $!";
print {$json_fh} qq|{"alpha":{"beta":2}}|;
close $json_fh;
my $json_root = _run("$perl -I'$lib' '$dashboard' jq '\$d' '$json_file'");
is_deeply( json_decode($json_root), { alpha => { beta => 2 } }, 'jq accepts file then root query with order-independent args' );
my $json_root_stdin = _run("cat '$json_file' | $perl -I'$lib' '$dashboard' jq '\$d'");
is( $json_root_stdin, $json_root, 'jq returns the same whole-document result from stdin and file input' );
my $json_keys = _run(qq{printf '{"foo":[1,2,3,4],"bar":[4,5,6]}' | $perl -I'$lib' '$dashboard' jq 'sort keys %\$d'});
is_deeply( json_decode($json_keys), [ 'bar', 'foo' ], 'jq evaluates Perl expressions against decoded stdin data through $d' );
my $json_keys_file = _run("$perl -I'$lib' '$dashboard' jq '$json_file' 'sort' 'keys' '%\$d'");
is_deeply( json_decode($json_keys_file), ['alpha'], 'jq rejoins split expression argv pieces when the file path comes first' );
my $json_direct = _run(qq{printf '{"alpha":{"beta":2}}' | $perl -I'$lib' '$runtime_jq' alpha.beta});
is( $json_direct, $json_value, 'private runtime jq matches dashboard jq output' );

my $yaml_value = _run(qq{printf 'alpha:\\n  beta: 3\\n' | $perl -I'$lib' '$dashboard' yq alpha.beta});
is( $yaml_value, "3\n", 'yq extracts scalar YAML values' );
my $yaml_file = File::Spec->catfile( $open_root, 'sample.yaml' );
open my $yaml_fh, '>', $yaml_file or die "Unable to write $yaml_file: $!";
print {$yaml_fh} "alpha:\n  beta: 3\n";
close $yaml_fh;
my $yaml_root = _run("$perl -I'$lib' '$dashboard' yq '$yaml_file' '\$d'");
is_deeply( json_decode($yaml_root), { alpha => { beta => '3' } }, 'yq accepts file then root query with order-independent args' );
my $yaml_root_stdin = _run("cat '$yaml_file' | $perl -I'$lib' '$dashboard' yq '\$d'");
is( $yaml_root_stdin, $yaml_root, 'yq returns the same whole-document result from stdin and file input' );
my $yaml_keys = _run(qq{printf 'foo:\\n  - 1\\nbar:\\n  - 2\\n' | $perl -I'$lib' '$dashboard' yq 'sort keys %\$d'});
is_deeply( json_decode($yaml_keys), [ 'bar', 'foo' ], 'yq evaluates Perl expressions against decoded YAML data through $d' );
my $yaml_direct = _run(qq{printf 'alpha:\\n  beta: 3\\n' | $perl -I'$lib' '$runtime_yq' alpha.beta});
is( $yaml_direct, $yaml_value, 'private runtime yq matches dashboard yq output' );

my $jq_hook_root = File::Spec->catdir( $ENV{HOME}, '.developer-dashboard', 'cli', 'jq.d' );
make_path($jq_hook_root);
my $jq_hook_one = File::Spec->catfile( $jq_hook_root, '00-first.pl' );
open my $jq_hook_one_fh, '>', $jq_hook_one or die "Unable to write $jq_hook_one: $!";
print {$jq_hook_one_fh} <<'PL';
#!/usr/bin/env perl
print "hook-one\n";

t/05-cli-smoke.t  view on Meta::CPAN

is( $toml_value, "4\n", 'tomq extracts scalar TOML values' );
my $toml_file = File::Spec->catfile( $open_root, 'sample.toml' );
open my $toml_fh, '>', $toml_file or die "Unable to write $toml_file: $!";
print {$toml_fh} "[alpha]\nbeta = 4\n";
close $toml_fh;
my $toml_root = _run("$perl -I'$lib' '$dashboard' tomq '\$d' '$toml_file'");
is_deeply( json_decode($toml_root), { alpha => { beta => 4 } }, 'tomq accepts file then root query with order-independent args' );
my $toml_root_stdin = _run("cat '$toml_file' | $perl -I'$lib' '$dashboard' tomq '\$d'");
is( $toml_root_stdin, $toml_root, 'tomq returns the same whole-document result from stdin and file input' );
my $toml_keys = _run(qq{printf '[foo]\\na = 1\\n[bar]\\nb = 2\\n' | $perl -I'$lib' '$dashboard' tomq 'sort keys %\$d'});
is_deeply( json_decode($toml_keys), [ 'bar', 'foo' ], 'tomq evaluates Perl expressions against decoded TOML data through $d' );
my $toml_bool = _run(qq{printf 'enabled = true\\ndisabled = false\\n' | $perl -I'$lib' '$dashboard' tomq '\$d'});
is_deeply( json_decode($toml_bool), { disabled => 0, enabled => 1 }, 'tomq returns plain Perl scalar booleans instead of TOML backend objects' );
my $toml_direct = _run(qq{printf '[alpha]\\nbeta = 4\\n' | $perl -I'$lib' '$runtime_tomq' alpha.beta});
is( $toml_direct, $toml_value, 'private runtime tomq matches dashboard tomq output' );

my $props_value = _run(qq{printf 'alpha.beta=5\\nname = demo\\n' | $perl -I'$lib' '$dashboard' propq alpha.beta});
is( $props_value, "5\n", 'propq extracts scalar Java properties values' );
my $props_file = File::Spec->catfile( $open_root, 'sample.properties' );
open my $props_fh, '>', $props_file or die "Unable to write $props_file: $!";
print {$props_fh} "alpha.beta=5\nname = demo\n";
close $props_fh;
my $props_root = _run("$perl -I'$lib' '$dashboard' propq '$props_file' '\$d'");
is_deeply( json_decode($props_root), { 'alpha.beta' => '5', name => 'demo' }, 'propq accepts file then root query with order-independent args' );
my $props_root_stdin = _run("cat '$props_file' | $perl -I'$lib' '$dashboard' propq '\$d'");
is( $props_root_stdin, $props_root, 'propq returns the same whole-document result from stdin and file input' );
my $props_keys = _run(qq{printf 'foo=1\\nbar=2\\n' | $perl -I'$lib' '$dashboard' propq 'sort keys %\$d'});
is_deeply( json_decode($props_keys), [ 'bar', 'foo' ], 'propq evaluates Perl expressions against decoded properties data through $d' );
my $props_direct = _run(qq{printf 'alpha.beta=5\\nname = demo\\n' | $perl -I'$lib' '$runtime_propq' alpha.beta});
is( $props_direct, $props_value, 'private runtime propq matches dashboard propq output' );

my $ini_value = _run(qq{printf '[alpha]\\nbeta=6\\n' | $perl -I'$lib' '$dashboard' iniq alpha.beta});
is( $ini_value, "6\n", 'iniq extracts scalar INI values' );
my $ini_keys = _run(qq{printf '[foo]\\na=1\\n[bar]\\nb=2\\n' | $perl -I'$lib' '$dashboard' iniq 'sort keys %\$d'});
is_deeply( json_decode($ini_keys), [ '_global', 'bar', 'foo' ], 'iniq evaluates Perl expressions against decoded INI data through $d' );
my $ini_direct = _run(qq{printf '[alpha]\\nbeta=6\\n' | $perl -I'$lib' '$runtime_iniq' alpha.beta});
is( $ini_direct, $ini_value, 'private runtime iniq matches dashboard iniq output' );

my $csv_value = _run(qq{printf 'alpha,beta\\n7,8\\n' | $perl -I'$lib' '$dashboard' csvq 1.1});
is( $csv_value, "8\n", 'csvq extracts scalar CSV values by row and column index' );
my $csv_expression = _run(qq{printf 'alpha,beta\\n7,8\\n' | $perl -I'$lib' '$dashboard' csvq 'join q(-), map { \$d->[1][\$_] } 0 .. \$#{ \$d->[1] }'});
is( $csv_expression, "7-8\n", 'csvq evaluates Perl expressions against decoded CSV row arrays through $d' );
my $csv_direct = _run(qq{printf 'alpha,beta\\n7,8\\n' | $perl -I'$lib' '$runtime_csvq' 1.1});
is( $csv_direct, $csv_value, 'private runtime csvq matches dashboard csvq output' );

my $xml_file = File::Spec->catfile( $open_root, 'sample.xml' );
open my $xml_fh, '>', $xml_file or die "Unable to write $xml_file: $!";
print {$xml_fh} '<root><value>demo</value><item id="1">x</item><item id="2">y</item></root>';
close $xml_fh;
my $xml_value = _run(qq{printf '<root><value>demo</value><item id="1">x</item><item id="2">y</item></root>' | $perl -I'$lib' '$dashboard' xmlq root.value});
is( $xml_value, "demo\n", 'xmlq extracts scalar XML values from the decoded XML tree' );
my $xml_root = _run("$perl -I'$lib' '$dashboard' xmlq '\$d' '$xml_file'");
is_deeply(
    json_decode($xml_root),
    {
        root => {
            value => 'demo',
            item  => [
                { _attributes => { id => '1' }, _text => 'x' },
                { _attributes => { id => '2' }, _text => 'y' },
            ],
        },
    },
    'xmlq returns the decoded XML tree for the whole-document selector',
);
my $xml_root_stdin = _run("cat '$xml_file' | $perl -I'$lib' '$dashboard' xmlq '\$d'");
is( $xml_root_stdin, $xml_root, 'xmlq returns the same decoded whole-document result from stdin and file input' );
my $xml_expression = _run(qq{printf '<root><value>demo</value><item id="1">x</item><item id="2">y</item></root>' | $perl -I'$lib' '$dashboard' xmlq 'join q(,), map { \$_->{_attributes}{id} } \@{ \$d->{root}{item} }'});
is( $xml_expression, "1,2\n", 'xmlq evaluates Perl expressions against decoded XML data through $d' );
my $xml_direct = _run(qq{printf '<root><value>demo</value><item id="1">x</item><item id="2">y</item></root>' | $perl -I'$lib' '$runtime_xmlq' root.value});
is( $xml_direct, $xml_value, 'private runtime xmlq matches dashboard xmlq output' );

my $cli_root = File::Spec->catdir( $ENV{HOME}, '.developer-dashboard', 'cli' );
make_path($cli_root);
my $ext = File::Spec->catfile( $cli_root, 'foobar' );
open my $ext_fh, '>', $ext or die "Unable to write $ext: $!";
print {$ext_fh} <<'SH';
#!/bin/sh
input="$(cat)"

t/07-core-units.t  view on Meta::CPAN

);
$config->save_global;
is_deeply( $config->load_global, {}, 'save_global defaults to an empty hash when no config is provided' );
$config->save_global($saved_global);

ok( !defined decode_payload(undef), 'decode_payload ignores undefined token' );
ok( !defined decode_payload(''), 'decode_payload ignores empty token' );
ok( !defined encode_payload(undef), 'encode_payload ignores undefined text' );
my $payload = encode_payload('plain text');
is( decode_payload($payload), 'plain text', 'payload codec round-trips text' );
ok( defined decode_payload('not-gzip'), 'decode_payload returns decoded bytes for arbitrary base64 text' );

my $page = Developer::Dashboard::PageDocument->new(
    id          => 'page-one',
    title       => 'Page <One>',
    description => 'Desc "here"',
    layout      => { body => "Hello <world>\n" },
    state       => {
        one => 'first',
        two => undef,
    },



( run in 0.762 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )