Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Perl/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PDFREST_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Optional: Override API base URL (default https://api.pdfrest.com)
# PDFREST_URL=https://eu-api.pdfrest.com/
# For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
44 changes: 44 additions & 0 deletions Perl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Created by https://www.toptal.com/developers/gitignore/api/perl
# Edit at https://www.toptal.com/developers/gitignore?templates=perl

### Perl ###
!Build/
.last_cover_stats
/META.yml
/META.json
/MYMETA.*
*.o
*.pm.tdy
*.bs

# Devel::Cover
cover_db/

# Devel::NYTProf
nytprof.out

# Dist::Zilla
/.build/

# Module::Build
_build/
Build
Build.bat

# Module::Install
inc/

# ExtUtils::MakeMaker
/blib/
/_eumm/
/*.gz
/Makefile
/Makefile.old
/MANIFEST.bak
/pm_to_blib
/*.zip

# Carton
local/

# End of https://www.toptal.com/developers/gitignore/api/perl
100 changes: 100 additions & 0 deletions Perl/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Repository Guidelines

## Project Structure & Module Organization
- `Endpoint Examples/JSON Payload/`: Two-step samples (upload then operate), e.g., `markdown.pl`.
- `Endpoint Examples/Multipart Payload/`: Single multipart request samples, e.g., `rasterized-pdf.pl`.
- `Complex Flow Examples/`: Multi-step workflows that chain endpoints.
- `.env.example` → copy to `.env` and set `PDFREST_API_KEY`; optional `PDFREST_URL`.
- `README.md`: Setup and usage details for this language folder.

## Build, Test, and Development Commands
- Install deps from `cpanfile` (recommended):
- `cpanm --installdeps .`
- macOS quick setup: `make install-macos` or run `scripts/setup-macos.sh`
- Run JSON sample:
- `perl "Endpoint Examples/JSON Payload/markdown.pl" /path/to/input.pdf`
- Run Multipart sample:
- `perl "Endpoint Examples/Multipart Payload/rasterized-pdf.pl" /path/to/input.pdf`
- Capture output to file: append `> response.json`


## Coding Style & Naming Conventions
- Indentation: 4 spaces; enable `use strict; use warnings; use utf8;` at top.
- Naming: `snake_case` for files/variables; script names mirror endpoints (e.g., `markdown.pl`).
- HTTP: prefer `LWP::UserAgent` + `HTTP::Request::Common`.
- Base URL: `$ENV{PDFREST_URL} // 'https://api.pdfrest.com'`.
- I/O: print API responses to STDOUT; send diagnostics to STDERR; exit non‑2xx with non‑zero status.

### Environment Loading
- Use `Dotenv` to read `.env` into `%ENV` (do not hand‑roll parsing).
- Add dependency in `cpanfile`: `requires 'Dotenv';`.
- Load with a guarded call so missing files are fine:
- JSON/Multipart examples: `my $env_path = "$Bin/../../.env"; -e $env_path and Dotenv->load($env_path);`
- Complex Flow examples: `my $env_path = "$Bin/../.env"; -e $env_path and Dotenv->load($env_path);`
- Do not override pre‑existing environment variables; rely on library defaults (no explicit override).

## Testing Guidelines
- No formal test suite required for samples. Validate by running against small, known inputs.
- Success: non‑zero exit on failures; JSON body printed to STDOUT on success.
- If adding tests, use `Test::More` under `t/` and run with `prove -lr t`.

## Commit & Pull Request Guidelines
- Commits: imperative, scoped, and reference endpoint/path (e.g., "Add multipart markdown sample").
- PRs: include what/why, run commands used for verification, expected response snippet, and linked issues.
- Avoid unrelated changes or large binaries.

## Security & Configuration Tips
- Do not commit secrets. Provide `.env.example`; load `PDFREST_API_KEY` from environment.
- Optional region override: `PDFREST_URL=https://eu-api.pdfrest.com/` for EU/GDPR routing.
- Never print API keys; rely on concise error messages. Respect proxies via `HTTPS_PROXY` when needed.

## Troubleshooting
- HTTPS support missing: install `LWP::Protocol::https` and `Mozilla::CA` (included in `cpanfile`). Re-run `cpanm --installdeps .`.
- SSL toolchain: some systems require OpenSSL dev libs (e.g., macOS: `brew install openssl`, Debian/Ubuntu: `apt-get install libssl-dev`) before `cpanm` can build `Net::SSLeay`/`IO::Socket::SSL`.

---

## Audience And Tone (Internal)

These Perl samples are customer‑facing and intended to help potential customers evaluate pdfRest quickly. Keep all code, comments, and documentation clear, minimal, and task‑focused. Avoid internal jargon and keep meta‑process notes out of `README.md` and the samples.

Key points:
- Clarity: explain what the sample does in 1–2 bullets.
- Guidance: show how to set up `.env` and how to run the script.
- Region: mention optional `PDFREST_URL` with the EU endpoint for GDPR and proximity.
- Safety: never log secrets; print only response bodies and minimal diagnostics to `STDERR`.
- Errors: exit non‑zero on non‑2xx responses with a concise message.

## Sample Header Convention (Internal)

Add this standardized header comment at the top of every Perl sample. This header is customer‑visible; the convention itself is tracked here for us (don’t place this template in `README.md`).

Template:

```
#!
# What this sample does:
# - <One–two bullets describing purpose and request style>
#
# Setup (.env):
# - Copy .env.example to .env
# - Set PDFREST_API_KEY=your_api_key_here
# - Optional: set PDFREST_URL to override the API region. For EU/GDPR compliance and proximity, use:
# PDFREST_URL=https://eu-api.pdfrest.com
# For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
#
# Usage:
# perl "<relative path to this file>" /path/to/input.pdf
#
# Output:
# - Prints the API JSON response to stdout. Non-2xx responses exit with a concise message.
# - Tip: pipe output to a file: perl ... > response.json
```

Notes:
- Match the endpoint name and request style (JSON two‑step vs multipart single request) in the bullets.
- Keep the header concise; list optional parameters near where they are used in code.

## README Scope (Internal)

Keep `README.md` focused on user setup, running samples, and high‑level background. Avoid internal conventions or meta‑process content that could confuse customers. Place internal notes and templates in `AGENTS.md` (this file).
147 changes: 147 additions & 0 deletions Perl/Complex Flow Examples/merge-different-file-types.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use FindBin qw($Bin);
use File::Basename qw(basename);
use JSON::PP qw(decode_json);
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Request::Common qw(POST);
use URI::Escape qw(uri_escape);
use Dotenv;

#!
# What this sample does:
# - Merges multiple inputs (PDFs and non-PDFs) into a single PDF.
# - Non-PDFs are converted to PDF; PDFs are uploaded. Collected IDs are merged via /merged-pdf.
#
# Setup (.env):
# - Copy .env.example to .env (Perl folder root)
# - Set PDFREST_API_KEY=your_api_key_here
# - Optional: set PDFREST_URL to override the API region. For EU/GDPR compliance and proximity, use:
# PDFREST_URL=https://eu-api.pdfrest.com
# For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
#
# Usage:
# perl "Complex Flow Examples/merge-different-file-types.pl" /path/to/file1 /path/to/file2 [/path/to/file3 ...]
#
# Output:
# - Prints the API JSON response to stdout. Non-2xx responses exit with a concise message.
# - Tip: pipe output to a file: perl ... > response.json

binmode STDOUT, ':raw';
binmode STDERR, ':encoding(UTF-8)';

# Load .env from the Perl folder root (one level up from this script)
my $env_path = "$Bin/../.env";
-e $env_path and Dotenv->load($env_path);

my $api_key = $ENV{PDFREST_API_KEY} // '';
if (!$api_key || $api_key =~ /^\s*$/) {
print STDERR "Missing PDFREST_API_KEY in .env or environment\n";
exit 1;
}

my $api_base = $ENV{PDFREST_URL} // $ENV{PDFREST_API} // 'https://api.pdfrest.com';
$api_base =~ s{/+$}{};

my @paths = @ARGV;
if (@paths < 2) {
print STDERR "Usage: perl merge-different-file-types.pl /path/to/file1 /path/to/file2 [/path/to/file3 ...]\n";
exit 1;
}
for my $p (@paths) { if (!-f $p) { print STDERR "Not a file: $p\n"; exit 1; } }

sub content_type_for {
my ($path) = @_;
my ($ext) = $path =~ /(\.[^.]+)$/;
$ext = lc($ext // '');
return 'application/pdf' if $ext eq '.pdf';
return 'image/png' if $ext eq '.png';
return 'image/jpeg' if $ext eq '.jpg' || $ext eq '.jpeg';
return 'image/gif' if $ext eq '.gif';
return 'image/tiff' if $ext eq '.tif' || $ext eq '.tiff';
return 'image/bmp' if $ext eq '.bmp';
return 'image/webp' if $ext eq '.webp';
return 'application/msword' if $ext eq '.doc';
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' if $ext eq '.docx';
return 'application/vnd.ms-powerpoint' if $ext eq '.ppt';
return 'application/vnd.openxmlformats-officedocument.presentationml.presentation' if $ext eq '.pptx';
return 'application/vnd.ms-excel' if $ext eq '.xls';
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' if $ext eq '.xlsx';
return 'text/plain' if $ext eq '.txt';
return 'application/rtf' if $ext eq '.rtf';
return 'text/html' if $ext eq '.html' || $ext eq '.htm';
return 'application/octet-stream';
}

my $ua = LWP::UserAgent->new( timeout => 120 );

eval {
my @ids;
for my $i (0..$#paths) {
my $p = $paths[$i];
my $ext = lc(($p =~ /(\.[^.]+)$/)[0] // '');
if ($ext eq '.pdf') {
# Upload and capture id
open my $fh, '<:raw', $p or do { print STDERR "Unable to read $p: $!\n"; exit 1; };
my $bytes; { local $/; $bytes = <$fh>; }
close $fh;
my $req = HTTP::Request->new('POST', "$api_base/upload");
$req->header('api-key' => $api_key);
$req->header('content-filename' => basename($p));
$req->header('Content-Type' => 'application/octet-stream');
$req->content($bytes);
my $resp = $ua->request($req);
print STDERR $resp->decoded_content // '';
if (!$resp->is_success) { print STDERR "\nUpload failed (input #" . ($i+1) . ") status " . $resp->code . "\n"; exit 1; }
my $json = decode_json($resp->decoded_content // '{}');
my $id = $json->{files} && ref $json->{files} eq 'ARRAY' ? $json->{files}[0]{id} : undef;
if (!$id) { print STDERR "Unexpected upload response format for input #" . ($i+1) . "\n"; exit 1; }
push @ids, $id;
print STDERR "Uploaded PDF (#" . ($i+1) . "); id=$id\n";
} else {
# Convert to PDF via /pdf and capture outputId
my $ct = content_type_for($p);
my $req = POST("$api_base/pdf",
'Content_Type' => 'form-data',
'Content' => [ file => [$p, basename($p), 'Content-Type' => $ct] ]
);
$req->header('api-key' => $api_key);
my $resp = $ua->request($req);
print STDERR $resp->decoded_content // '';
if (!$resp->is_success) { print STDERR "\nConversion failed (input #" . ($i+1) . ") status " . $resp->code . "\n"; exit 1; }
my $json = decode_json($resp->decoded_content // '{}');
my $id = $json->{outputId};
if (!$id) { print STDERR "Unexpected conversion response format for input #" . ($i+1) . "\n"; exit 1; }
push @ids, $id;
print STDERR "Converted non-PDF (#" . ($i+1) . "); outputId=$id\n";
}
}

# Build x-www-form-urlencoded with repeated arrays
my @parts;
for my $id (@ids) {
push @parts, 'id[]=' . uri_escape($id);
push @parts, 'pages[]=' . uri_escape('1-last');
push @parts, 'type[]=id';
}
my $body = join('&', @parts);

my $merge_req = HTTP::Request->new('POST', "$api_base/merged-pdf");
$merge_req->header('api-key' => $api_key);
$merge_req->header('Content-Type' => 'application/x-www-form-urlencoded');
$merge_req->content($body);
my $merge_resp = $ua->request($merge_req);
print STDOUT $merge_resp->decoded_content // '';
if (!$merge_resp->is_success) { print STDERR "\nMerge failed with status " . $merge_resp->code . "\n"; exit 1; }
1;
} or do {
my $err = $@ || 'Unknown error';
$err =~ s/\s+$//;
print STDERR "Error: $err\n";
exit 1;
};

__END__
Loading