crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

nyt-to-xd.sh (4907B)


      1 #!/usr/bin/env bash
      2 #
      3 # Convert a NYT puzzle JSON to XD using the app's NYTToXDConverter.
      4 #
      5 # Usage:
      6 #   Scripts/nyt-to-xd.sh --json <path>            # convert an existing file
      7 #   Scripts/nyt-to-xd.sh --date YYYY-MM-DD        # fetch then convert
      8 #   [--output <path>]                              # default: stdout
      9 
     10 set -euo pipefail
     11 
     12 repo_root="$(cd "$(dirname "$0")/.." && pwd)"
     13 fetch_script="${repo_root}/Scripts/fetch-nyt.sh"
     14 
     15 # The converter's slice of the app target, compiled from the real sources rather
     16 # than mirrored into a stub. This used to hand-copy XD's constants to avoid
     17 # building the whole app, but the converter now also writes the `## Decorations`
     18 # section, which pulls in Puzzle and its neighbours — and a stub of those would
     19 # drift silently. These seven files are self-contained (nothing here reaches into
     20 # Core Data or CloudKit), so compiling them directly costs a second and can't go
     21 # stale.
     22 sources=(
     23     "${repo_root}/Crossmate/Models/XD.swift"
     24     "${repo_root}/Crossmate/Models/XDDecorationWriter.swift"
     25     "${repo_root}/Crossmate/Models/XDMarkup.swift"
     26     "${repo_root}/Crossmate/Models/Puzzle.swift"
     27     "${repo_root}/Crossmate/Models/PlayerSelection.swift"
     28     "${repo_root}/Crossmate/Models/GridPosition.swift"
     29     "${repo_root}/Crossmate/Services/NYTOverlaySlicer.swift"
     30     "${repo_root}/Crossmate/Services/NYTOverlayLetterRecognizer.swift"
     31     "${repo_root}/Crossmate/Services/NYTToXDConverter.swift"
     32 )
     33 for source in "${sources[@]}"; do
     34     if [[ ! -f "$source" ]]; then
     35         echo "error: missing source file: $source" >&2
     36         exit 1
     37     fi
     38 done
     39 
     40 json_path=""
     41 date_arg=""
     42 output_path=""
     43 
     44 while [[ $# -gt 0 ]]; do
     45     case "$1" in
     46         --json)  json_path="$2"; shift 2 ;;
     47         --date)  date_arg="$2"; shift 2 ;;
     48         --output) output_path="$2"; shift 2 ;;
     49         -h|--help)
     50             sed -n '2,8p' "$0" | sed 's/^# \{0,1\}//'
     51             exit 0
     52             ;;
     53         *) echo "error: unknown arg '$1'" >&2; exit 2 ;;
     54     esac
     55 done
     56 
     57 if [[ -z "$json_path" && -z "$date_arg" ]]; then
     58     echo "error: pass --json <path> or --date YYYY-MM-DD" >&2
     59     exit 2
     60 fi
     61 if [[ -n "$json_path" && -n "$date_arg" ]]; then
     62     echo "error: --json and --date are mutually exclusive" >&2
     63     exit 2
     64 fi
     65 
     66 tmp_dir="$(mktemp -d)"
     67 trap 'rm -rf "$tmp_dir"' EXIT
     68 
     69 if [[ -n "$date_arg" ]]; then
     70     json_path="${tmp_dir}/nyt-${date_arg}.json"
     71     bash "$fetch_script" "$date_arg" "$json_path" >/dev/null
     72 fi
     73 
     74 if [[ ! -f "$json_path" ]]; then
     75     echo "error: JSON file not found: $json_path" >&2
     76     exit 1
     77 fi
     78 
     79 # Fetch both overlay phases, when present, so the converted .xd carries the
     80 # same baked-in artwork the app would produce. Asset hosts need no cookie.
     81 before_start_path=""
     82 before_start_uri="$(jq -r '
     83     (.body[0].overlays.beforeStart // empty) as $i
     84     | if ($i | type) == "number" then (.assets[$i - 1].uri // empty) else empty end
     85 ' "$json_path" 2>/dev/null || true)"
     86 if [[ -n "$before_start_uri" ]]; then
     87     before_start_path="${tmp_dir}/before-start.png"
     88     if ! curl -sSfL "$before_start_uri" -o "$before_start_path"; then
     89         echo "warning: could not fetch overlay ${before_start_uri}; converting without it" >&2
     90         before_start_path=""
     91     fi
     92 fi
     93 
     94 after_solve_path=""
     95 after_solve_uri="$(jq -r '
     96     (.body[0].overlays.afterSolve // empty) as $i
     97     | if ($i | type) == "number" then (.assets[$i - 1].uri // empty) else empty end
     98 ' "$json_path" 2>/dev/null || true)"
     99 if [[ -n "$after_solve_uri" ]]; then
    100     after_solve_path="${tmp_dir}/after-solve.png"
    101     if ! curl -sSfL "$after_solve_uri" -o "$after_solve_path"; then
    102         echo "warning: could not fetch overlay ${after_solve_uri}; converting without it" >&2
    103         after_solve_path=""
    104     fi
    105 fi
    106 
    107 driver="${tmp_dir}/main.swift"
    108 cat > "$driver" <<'SWIFT'
    109 import Foundation
    110 
    111 guard CommandLine.arguments.count >= 2 else {
    112     FileHandle.standardError.write(Data("error: missing JSON path\n".utf8))
    113     exit(2)
    114 }
    115 let url = URL(fileURLWithPath: CommandLine.arguments[1])
    116 let data = try Data(contentsOf: url)
    117 var beforeStartImage: Data? = nil
    118 if CommandLine.arguments.count >= 3, !CommandLine.arguments[2].isEmpty {
    119     beforeStartImage = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[2]))
    120 }
    121 var afterSolveImage: Data? = nil
    122 if CommandLine.arguments.count >= 4, !CommandLine.arguments[3].isEmpty {
    123     afterSolveImage = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[3]))
    124 }
    125 let xd = try NYTToXDConverter.convert(
    126     jsonData: data,
    127     beforeStartImage: beforeStartImage,
    128     afterSolveImage: afterSolveImage
    129 )
    130 print(xd)
    131 SWIFT
    132 
    133 binary="${tmp_dir}/nyt-to-xd"
    134 swiftc -O -swift-version 6 "${sources[@]}" "$driver" -o "$binary"
    135 
    136 if [[ -n "$output_path" ]]; then
    137     "$binary" "$json_path" "$before_start_path" "$after_solve_path" > "$output_path"
    138     echo "wrote $output_path" >&2
    139 else
    140     "$binary" "$json_path" "$before_start_path" "$after_solve_path"
    141 fi