---
title: "Introduction to punycoder"
author: "Bart Turczynski"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Introduction to punycoder}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

# Introduction

The `punycoder` package provides high-performance Unicode and Punycode encoding/decoding for internationalized domain names (IDNs). It addresses critical gaps in R's URL processing capabilities by offering reliable, fast conversion between Unicode and ASCII representations of domain names.

## Why punycoder?

### The Problem

International domain names containing Unicode characters (like café.com or москва.рф) need to be converted to ASCII format for use in many network protocols and systems. Existing R packages have limitations:

- **Inconsistent legacy helpers**: Existing workflows may produce incorrect punycode output
- **Limited functionality**: No comprehensive IDN handling
- **Performance**: No efficient bulk processing

### The Solution

`punycoder` provides:

- **Reliable encoding/decoding** following RFC 3492 standards
- **High performance** for large datasets
- **Comprehensive validation** with informative error messages

## Basic Usage

### Domain Encoding and Decoding

```{r basic-usage, eval=FALSE}
library(punycoder)

# Encode Unicode domains to ASCII
puny_encode("café.com")
# Returns: "xn--caf-dma.com"

puny_encode("москва.рф")
# Returns: "xn--80adxhks.xn--p1ai"

# Decode ASCII domains back to Unicode
puny_decode("xn--caf-dma.com")
# Returns: "café.com"

# Vectorized operations
domains <- c("café.com", "москва.рф", "北京.中国")
encoded <- puny_encode(domains)
print(encoded)
```

### Validation and Utilities

```{r validation, eval=FALSE}
# Check if domain is already punycode
is_punycode("xn--caf-dma.com") # TRUE
is_punycode("café.com") # FALSE

# Check if domain contains Unicode characters
is_idn("café.com") # TRUE
is_idn("example.com") # FALSE

# Comprehensive domain validation
result <- validate_domain(c("café.com", "invalid..domain", "valid.org"))
print(result)
```

## Host Normalization

`puny_encode()` is the raw RFC 3492 transform: it Punycode-encodes the labels you hand it, and nothing more. `host_normalize()` is the higher-level operation. It applies the UTS #46 canonical-host pipeline — character mapping, case folding, NFC normalization, label validation — and *then* encodes, so it answers "what is the canonical ASCII form of this host?" rather than "what is the Punycode of this string?".

```{r normalize}
library(punycoder)

# Mapped, case-folded, validated, then encoded
host_normalize("Café.Example.COM")

# The codec transforms what it is given, and leaves case alone
puny_encode("Café.Example.COM")
```

### Invalid Input Returns NA

`host_normalize()` does not follow the strict/non-strict switch described above. It is a separate contract: a host that fails UTS #46 validation is reported as `NA` and never raises an error, so callers can layer their own policy on top.

```{r normalize-na}
host_normalize(c("valid.example", "example..com", "-bad-.example"))
```

### Profile Identity

Normalization is pinned to a named profile. `normalization_profile_info()` returns that identity as a one-row data frame, which is what you record alongside results you intend to reproduce later:

```{r normalize-profile}
normalization_profile_info()
```

### Unicode Version Selection

A build of `punycoder` ships a set of vendored Unicode table versions, one of which is the pinned default:

```{r unicode-versions}
unicode_versions()
```

Any shipped version can be selected per call. The difference between versions is confined to code points assigned in the newer one — a character that does not exist yet in the older tables is disallowed there, so the host is invalid:

```{r unicode-version-select}
host <- paste0(intToUtf8(0xA7CF), ".example")

host_normalize(host, unicode_version = "16.0.0")
host_normalize(host, unicode_version = "17.0.0")
```

Selecting a non-default version is reflected in the profile token, so the identity you record always describes the normalization that actually ran:

```{r unicode-version-profile}
normalization_profile_info(unicode_version = "16.0.0")$profile
```

The argument defaults to `NULL`, which means the pinned version — deliberately not "the newest available", so adding a table set in a later release cannot silently change results under existing code. Naming a version the build does not ship is an error listing what is available, never a quiet fall back to the pin.

See `?host_normalize` for the relaxable validation flags and the full profile parameters.

## Data Analysis Workflows

### Bulk Domain Processing

```{r bulk-processing, eval=FALSE}
# Example: Processing large datasets
set.seed(123)
sample_domains <- c(
  rep("example.com", 1000),
  rep("café.com", 1000),
  rep("test.org", 1000)
)

# Efficient vectorized encoding
system.time({
  encoded_domains <- puny_encode(sample_domains)
})

# Check results
table(is_punycode(encoded_domains))
```

## Error Handling

The package provides robust error handling with informative messages:

```{r error-handling, eval=FALSE}
# Strict validation (default)
try({
  puny_encode(c("valid.com", "")) # Empty string causes error
})

# Non-strict mode returns NA for invalid input
result <- puny_encode(c("valid.com", ""), strict = FALSE)
print(result)

# Validation provides detailed error information
validation <- validate_domain(c("valid.com", "invalid..domain", ""))
print(validation)
```

## Performance Considerations

The package is designed for high-performance processing:

- **Vectorized operations**: Process thousands of domains efficiently
- **C++ backend**: Native implementation for speed
- **Memory efficient**: Handles large datasets without excessive memory use

```{r performance, eval=FALSE}
# Benchmark with large dataset
large_domains <- rep(c("example.com", "café.com"), 5000)

system.time({
  encoded <- puny_encode(large_domains)
})

# Should process 10,000+ domains per second
```

## Package Options

You can configure package behavior using R options:

```{r options, eval=FALSE}
# Set global strict validation
options(punycoder.strict = FALSE)

# Check current setting
getOption("punycoder.strict")

# Set encoding preference
options(punycoder.encoding = "UTF-8")
```

## Integration with Other Packages

`punycoder` is designed to integrate well with other R packages:

```{r integration, eval=FALSE}
# With data.table
library(data.table)
dt <- data.table(
  original = c("café.com", "москва.рф"),
  encoded = puny_encode(c("café.com", "москва.рф"))
)

# With dplyr
library(dplyr)
domains_df <- data.frame(
  unicode_domain = c("café.com", "москва.рф")
) |>
  mutate(
    ascii_domain = puny_encode(unicode_domain),
    is_international = is_idn(unicode_domain)
  )
```

## Next Steps

- Explore the function documentation: `help(package = "punycoder")`
- Check out the test suite for more examples
- Report issues at: https://gitlab.com/bart-turczynski/punycoder/-/work_items

## Technical Details

The package uses a C++ backend with Rcpp for performance, and follows RFC 3492 standards for punycode implementation. When `libidn2` is available at build time, `punycoder` uses it behind the same R-level API and falls back to the built-in implementation otherwise.

## See also

`punycoder` is used as the Punycode and IDNA engine by two sibling packages:

- **[pslr](https://CRAN.R-project.org/package=pslr)** — Public Suffix List engine. Uses `punycoder` for host canonicalization before PSL matching. Reach for it when you need eTLD or registrable-domain queries.
- **[rurl](https://CRAN.R-project.org/package=rurl)** — Full URL parsing, normalization, cleaning, and joining toolkit. Builds on both `punycoder` and `pslr` to handle the complete URL processing pipeline.

## Acknowledgments

`punycoder` descends from the earlier R
[hrbrmstr/punycode](https://github.com/hrbrmstr/punycode) package and
implements published standards directly: RFC 3492 (Punycode), the IDNA2008
RFCs, Unicode UTS #46, and UAX #15 normalization, validated against the
Unicode Consortium's IdnaTestV2 conformance data. It is built on `Rcpp` with
an optional GNU libidn2 backend.

The full list of credits — prior art, dependencies, the standards this code
implements, and the data sources it serves — is in
[`ACKNOWLEDGMENTS.md`](https://gitlab.com/bart-turczynski/punycoder/-/blob/main/ACKNOWLEDGMENTS.md).
