Contents

Nushell Example #3 -- Woodstock Film Festival

Contents

The Woodstock Film Festival is next week but the site’s view of the schedule leaves more than a bit to be desired when you’re trying to plan what to see across multiple days and venues.

Aside, I’m not sure who it’s optimized for but it’s not a good experience using Firefox on desktop. I’m annoyed enough by it to include this bit of the Google Pagespeed report Google Pagespeed

After a little fiddling with the Firefox Developer Tools, I saw that they’re using a service Eventive which exposes the data via API. Perfect. We love JSON and API’s. So I turned to Nushell again. I’m going to leave out the details of exploring the API and the response payloads as it would be an article in itself.

Note: Because it’s not polite to abuse other people’s API’s I have stashed gzipped copies of the config (38kb) and a full response (286kb) (1.4MB uncompressed).

I start by defining a little function to convert a timestamp (assumed UTC) into a datetime in this timezone (NY). I have a bunch of these conveniences it my toolkit.

def ts->ny []: string -> datetime {
    $in | into datetime | date to-timezone America/New_York
}

Get the configuration via HTTP. This has a lot of data but we’re only interested in the event “bucket” and the api key:

let resp = (http get https://woodstock2025.eventive.org/config.json -f -e)
if $resp.status != 200 {
   error make {msg: $"Failed to get config!\n($resp.body|to text)"}
}
let cfg = $resp.body
let auth = ($cfg.api_key | encode base64)
let bucket = $cfg.event_bucket

Now we can assemble another HTTP request to and fetch the events:

let req = {
    scheme: "https",
    host: "api.eventive.org",
    path: $"event_buckets/($bucket)/events",
    query: ({upcoming_only: true} | url build-query)
} | url join
let resp = (http get -H ["Authorization" $"Basic ($auth)"] -f -e $req)
if $resp.status != 200 {
   error make {msg: $"Failed to get event data:\n\t($resp.body | to text)"}
}
let $events = $resp.body.events

Filter the event data into just the columns we want as table of records. There’s some regularity to the address so we can trim it to make it fit nicely. We use the function we defined earlier to coerce the timestamps.

$events | 
    each {|e|
        {
          name: $e.name,
          venue: ($e.venue.short_name),
          address: ($e.venue.address | 
                    str replace --regex ", NY 124.*$" "" |
                    str replace --regex "\n" " "),
          day: ($e.start_time | ts->ny | format date "%a"),
          time: ($e.start_time | ts->ny | format date "%F %R"),
          duration: (($e.end_time | into datetime) - ($e.start_time | into datetime))
        }
    } | sort-by time

That gives us a long table (135 records) that looks like this:

────────────────────────────────────────────────────────────────────────┬──────────────────┬────────────────────────────────────┬─────┬──────────────────┬───────────
                                  name                                  │      venue       │              address               │ day │       time       │ duration
────────────────────────────────────────────────────────────────────────┼──────────────────┼────────────────────────────────────┼─────┼──────────────────┼───────────
 Fatal Watch                                                            │ Broken Wing Barn │ 1389 State Route 212, Saugerties   │ Tue │ 2025-10-14 19:15 │  2hr 5min
 Barry Feinstein Photography Exhibition                                 │                  │                                    │ Wed │ 2025-10-15 11:00 │  4day 6hr
 A Life Illuminated                                                     │ Bearsville       │ 291 Tinker Street Woodstock        │ Wed │ 2025-10-15 17:00 │ 1hr 50min
 Blue Moon                                                              │ Playhouse        │ 103 Mill Hill Rd Woodstock         │ Wed │ 2025-10-15 17:15 │ 1hr 45min
 Arco                                                                   │ Tinker St        │ 132 Tinker Street Woodstock        │ Wed │ 2025-10-15 17:30 │ 1hr 35min
 A BREAK IN THE RAIN                                                    │ Colony           │ 22 Rock City Rd, Woodstock         │ Wed │ 2025-10-15 19:00 │ 2hr 55min
 River of Grass                                                         │ Broken Wing Barn │ 1389 State Route 212, Saugerties   │ Wed │ 2025-10-15 19:15 │ 1hr 45min
    ... elided ...

Now we can easily search for showtimes! (I’ve used a temporary variable to hold the table)

$output | where name =~ "Cover|Honky|Keeper"
───────────────────────────┬─────────────────┬────────────────────────────────────┬─────┬──────────────────┬───────────
           name                  venue                    address                day        time        duration
───────────────────────────┼─────────────────┼────────────────────────────────────┼─────┼──────────────────┼───────────
 Honky Tonkin' in Kingston  Assembly         236 Wall Street 3rd Floor Kingston  Wed  2025-10-15 19:30  3hr 25min
 Cover-Up                   Orpheum T2       156 Main St Saugerties              Sat  2025-10-18 12:45  2hr 20min
 The Keeper                 Bearsville       291 Tinker Street Woodstock         Sat  2025-10-18 16:00   2hr 5min
 Cover-Up                   Bearsville       291 Tinker Street Woodstock         Sun  2025-10-19 15:30  2hr 20min
 The Keeper                 Upstate Midtown  591 Broadway Kingston               Sun  2025-10-19 19:15   2hr 5min
───────────────────────────┴─────────────────┴────────────────────────────────────┴─────┴──────────────────┴───────────