packages feed

idna2008-1.0.0.0: internal/tools/update

#!/bin/sh
# Regenerate every Unicode-derived asset in the package from the
# upstream Unicode sources for the requested version:
#
#   * The library's table modules under
#     internal/Text/IDNA2008/Internal/*/Data.hs (Property, NFC,
#     Joining, Script, Width, Case, Bidi, Combining, Emoji).
#   * The UTS #46 reference data the `unicode-conformance` test
#     suite consults, bundled XZ-compressed under tests/data/.
#
# Usage:
#     internal/tools/update [<unicode-version>]
#
# When no version argument is supplied the script reads the default
# from the @x-unicode-version@ custom field in @idna2008.cabal@,
# which is the single source of truth for the Unicode version the
# library tracks.  Bump the field there and re-run this script to
# move to a new Unicode release; everything Unicode-derived stays
# in lockstep.
#
# All output paths are anchored at this script's directory, so the
# script can be run from anywhere.
#
# Override the Python interpreter via the PYTHON environment variable
# (e.g. PYTHON=python3.12 ./update 17.0.0).
#
# The IDNA disposition table is derived locally per RFC 5892 section 3 by
# genIdnaDerive.py from UCD inputs; we no longer fetch from the IANA
# IDNA-tables-properties registry, which lags Unicode releases (it
# tops out at 12.0.0 as of mid-2025).  The local derivation has been
# verified to reproduce IANA's published 12.0.0 CSV byte-for-byte.

set -eu

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
CABAL_FILE="${SCRIPT_DIR}/../../idna2008.cabal"

read_unicode_version() {
    awk '/^x-unicode-version:/ { print $2; exit }' "$CABAL_FILE"
}

DEFAULT_VERSION=$(read_unicode_version)
if [ -z "$DEFAULT_VERSION" ]; then
    echo "update: could not read x-unicode-version from $CABAL_FILE" >&2
    exit 1
fi

VERSION="${1:-$DEFAULT_VERSION}"
PYTHON="${PYTHON:-python3}"

#-----------------------------------------------------------------------
# Source URL templates -- edit here if Unicode reorganises its
# filesystem layout.  '%s' is the Unicode version string passed in
# as the first argument (e.g. "17.0.0").
#-----------------------------------------------------------------------

# Unicode UCD bundle: most files live under /Public/<ver>/ucd/.
UCD_BASE="https://www.unicode.org/Public/%s/ucd"

UNICODE_DATA_URL="${UCD_BASE}/UnicodeData.txt"
COMPOSITION_EXCLUSIONS_URL="${UCD_BASE}/CompositionExclusions.txt"
DERIVED_CORE_PROPERTIES_URL="${UCD_BASE}/DerivedCoreProperties.txt"
DERIVED_NORM_PROPS_URL="${UCD_BASE}/DerivedNormalizationProps.txt"
PROP_LIST_URL="${UCD_BASE}/PropList.txt"
HANGUL_SYLLABLE_TYPE_URL="${UCD_BASE}/HangulSyllableType.txt"
SCRIPTS_URL="${UCD_BASE}/Scripts.txt"
DERIVED_JOINING_TYPE_URL="${UCD_BASE}/extracted/DerivedJoiningType.txt"
DERIVED_COMBINING_CLASS_URL="${UCD_BASE}/extracted/DerivedCombiningClass.txt"
DERIVED_BIDI_CLASS_URL="${UCD_BASE}/extracted/DerivedBidiClass.txt"

# Emoji file: in 13.0+ it's part of the UCD bundle (Public/<ver>/ucd/emoji/);
# in 12.x and earlier it lived in a separate Public/emoji/<MAJ.MIN>/ tree.
EMOJI_UCD_URL="${UCD_BASE}/emoji/emoji-data.txt"
EMOJI_LEGACY_URL="https://www.unicode.org/Public/emoji/%s/emoji-data.txt"

# UTS #46 reference data used by the `unicode-conformance` test suite.
# The Unicode Consortium reorganised the IDNA data-file layout at
# Unicode 17.0.0 -- for >=17 the files live alongside the main UCD
# at /Public/<ver>/idna/, for <=16 at /Public/idna/<ver>/, and the
# /Public/idna/latest/ symlink continues to track the most-recently
# published version regardless of layout.
UTS46_NEW_BASE="https://www.unicode.org/Public/%s/idna"
UTS46_OLD_BASE="https://www.unicode.org/Public/idna/%s"

#-----------------------------------------------------------------------
# Compute concrete URLs and output paths.
#-----------------------------------------------------------------------

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
IDNA_DIR="${SCRIPT_DIR}/../Text/IDNA2008/Internal"

unidata_url=$(printf "$UNICODE_DATA_URL"            "$VERSION")
compex_url=$(printf  "$COMPOSITION_EXCLUSIONS_URL"  "$VERSION")
dcp_url=$(printf     "$DERIVED_CORE_PROPERTIES_URL" "$VERSION")
nfcprops_url=$(printf "$DERIVED_NORM_PROPS_URL"     "$VERSION")
plist_url=$(printf   "$PROP_LIST_URL"               "$VERSION")
hst_url=$(printf     "$HANGUL_SYLLABLE_TYPE_URL"    "$VERSION")
scripts_url=$(printf "$SCRIPTS_URL"                 "$VERSION")
jt_url=$(printf      "$DERIVED_JOINING_TYPE_URL"    "$VERSION")
ccc_url=$(printf     "$DERIVED_COMBINING_CLASS_URL" "$VERSION")
bidi_url=$(printf    "$DERIVED_BIDI_CLASS_URL"      "$VERSION")

case "$VERSION" in
    12.*|11.*|10.*|9.*|8.*|7.*|6.*)
        # Pre-13: emoji files in the legacy Public/emoji/<MAJ.MIN>/ tree.
        short_ver=$(echo "$VERSION" | cut -d. -f1,2)
        emoji_url=$(printf "$EMOJI_LEGACY_URL" "$short_ver")
        ;;
    *)
        emoji_url=$(printf "$EMOJI_UCD_URL" "$VERSION")
        ;;
esac

# Decide which UTS #46 layout to use based on the version's major
# component.  Versions >= 17 use the new under-UCD layout; older
# versions use /Public/idna/<ver>/.  An explicit "latest" value
# resolves through the symlink.
uts46_major=${VERSION%%.*}
case "$VERSION" in
    latest)
        uts46_test_url="https://www.unicode.org/Public/idna/latest/IdnaTestV2.txt"
        uts46_map_url="https://www.unicode.org/Public/idna/latest/IdnaMappingTable.txt"
        ;;
    *)
        case "$uts46_major" in
            ''|*[!0-9]*)
                uts46_base=$(printf "$UTS46_OLD_BASE" "$VERSION")
                ;;
            *)
                if [ "$uts46_major" -ge 17 ]; then
                    uts46_base=$(printf "$UTS46_NEW_BASE" "$VERSION")
                else
                    uts46_base=$(printf "$UTS46_OLD_BASE" "$VERSION")
                fi
                ;;
        esac
        uts46_test_url="${uts46_base}/IdnaTestV2.txt"
        uts46_map_url="${uts46_base}/IdnaMappingTable.txt"
        ;;
esac

TESTS_DATA_DIR="${SCRIPT_DIR}/../../tests/data"

#-----------------------------------------------------------------------
# Fetch all sources to a scratch dir, then run each generator.
#-----------------------------------------------------------------------

WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT

fetch() {
    # $1 = URL, $2 = local filename inside $WORK
    echo "fetching: $1"
    curl --fail --silent --show-error --location \
         --output "$WORK/$2" "$1"
}

fetch "$unidata_url"    UnicodeData.txt
fetch "$compex_url"     CompositionExclusions.txt
fetch "$dcp_url"        DerivedCoreProperties.txt
fetch "$nfcprops_url"   DerivedNormalizationProps.txt
fetch "$plist_url"      PropList.txt
fetch "$hst_url"        HangulSyllableType.txt
fetch "$scripts_url"    Scripts.txt
fetch "$jt_url"         DerivedJoiningType.txt
fetch "$ccc_url"        DerivedCombiningClass.txt
fetch "$bidi_url"       DerivedBidiClass.txt
fetch "$emoji_url"      emoji-data.txt
fetch "$uts46_test_url" IdnaTestV2.txt
fetch "$uts46_map_url"  IdnaMappingTable.txt

# Derive the IDNA disposition CSV locally per RFC 5892, then feed
# it into the existing CSV-to-Haskell generator.
echo "deriving: idna-tables-properties.csv (RFC 5892 section 3)"
"$PYTHON" "${SCRIPT_DIR}/genIdnaDerive.py" "$VERSION" \
    "$WORK/UnicodeData.txt"             \
    "$WORK/DerivedCoreProperties.txt"   \
    "$WORK/PropList.txt"                \
    "$WORK/HangulSyllableType.txt"      \
    "$WORK/DerivedNormalizationProps.txt" \
    > "$WORK/idna-tables-properties.csv"

run() {
    # $1 = output path; remaining args = command + args; stdout is redirected.
    out="$1"
    shift
    echo "writing:  $out"
    "$@" > "$out"
}

run "${IDNA_DIR}/Property/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaProperty.py" \
        "$VERSION" "$WORK/idna-tables-properties.csv"

run "${IDNA_DIR}/NFC/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaNFC.py" \
        "$VERSION" "$WORK/DerivedNormalizationProps.txt"

run "${IDNA_DIR}/NFC/Tables/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaNFCTables.py" \
        "$VERSION" "$WORK/UnicodeData.txt" "$WORK/CompositionExclusions.txt"

run "${IDNA_DIR}/Joining/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaJoining.py" \
        "$VERSION" "$WORK/DerivedJoiningType.txt" "$WORK/DerivedCombiningClass.txt"

run "${IDNA_DIR}/Script/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaScript.py" \
        "$VERSION" "$WORK/Scripts.txt"

run "${IDNA_DIR}/Width/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaWidth.py" \
        "$VERSION" "$WORK/UnicodeData.txt"

run "${IDNA_DIR}/Case/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaSimpleLower.py" \
        "$VERSION" "$WORK/UnicodeData.txt"

run "${IDNA_DIR}/Bidi/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaBidi.py" \
        "$VERSION" "$WORK/DerivedBidiClass.txt"

run "${IDNA_DIR}/Combining/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaCombining.py" \
        "$VERSION" "$WORK/UnicodeData.txt"

run "${IDNA_DIR}/Emoji/Data.hs" \
    "$PYTHON" "${SCRIPT_DIR}/genIdnaEmoji.py" \
        "$VERSION" "$WORK/emoji-data.txt" "$WORK/IdnaMappingTable.txt"

#-----------------------------------------------------------------------
# Bundle the UTS #46 reference data for the `unicode-conformance` test
# suite.  The files are committed gzip-compressed under tests/data/
# and unpacked at test time by Codec.Compression.GZip, so 'cabal
# test' runs offline without a .cache/ directory or network
# dependency.  Gzip (rather than xz) keeps the build deps to the
# Haskell @zlib@ package, which ships everywhere and doesn't need a
# pkg-config dance to find a system C library.
#-----------------------------------------------------------------------

mkdir -p "$TESTS_DATA_DIR"

bundle() {
    # $1 = source filename in $WORK; $2 = destination basename in
    # $TESTS_DATA_DIR (the .gz extension is appended).  The @-n@
    # flag suppresses the original filename and mtime from the gzip
    # header so that re-running @update@ against the same source
    # bytes produces a byte-identical output -- no spurious diffs
    # in the committed tree after a no-op regeneration.
    src="$WORK/$1"
    dst="${TESTS_DATA_DIR}/$2.gz"
    echo "bundling: $dst"
    gzip -9 -n -c "$src" > "$dst"
}

bundle IdnaTestV2.txt       IdnaTestV2.txt
bundle IdnaMappingTable.txt IdnaMappingTable.txt

echo
echo "All auto-generated IDNA tables are now aligned with Unicode $VERSION."
echo "UTS #46 conformance data refreshed under ${TESTS_DATA_DIR}/."