Compare commits
3 Commits
a5cb6fa7fa
...
ab9abdb53e
| Author | SHA1 | Date | |
|---|---|---|---|
| ab9abdb53e | |||
| 17c8270742 | |||
| e7252c4a74 |
6740
2025-restaurants.json
Normal file
6740
2025-restaurants.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2230,18 +2230,42 @@
|
||||
{
|
||||
"name": "Albi Italian",
|
||||
"desc": "Mixed greens, cucumber, red onion, croutons, pepperoncini, tomato, Italian vinaigrette GFA"
|
||||
},
|
||||
{
|
||||
"name": "“A” Street Chips",
|
||||
"desc": "deep fried thin sliced potatoes; choice of Flying Goat seasoning or salt & vinegar DF – add cup of creamy gorgonzola dressing $2.25"
|
||||
},
|
||||
{
|
||||
"name": "German Sausage Plate",
|
||||
"desc": "German sausage, sauerkraut, potato wedges, house mustard GFA"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Alberta",
|
||||
"desc": "red sauce, fresh mozzarella, pepperoni, red onion, roasted basil (7” pizza) GFA – sub GF crust for additional charge"
|
||||
},
|
||||
{
|
||||
"name": "Short Rib Pizza",
|
||||
"desc": "Extra virgin olive oil, short ribs, marinated portabella mushrooms, goat cheese, roasted garlic, green onion (7” pizza) GFA – sub GF crust for additional charge"
|
||||
},
|
||||
{
|
||||
"name": "Driscoll",
|
||||
"desc": "Roasted red pepper sauce, goat cheese, roasted eggplant, red onions, roasted cremini mushrooms, with truffle oil tossed arugula \u0026 shredded pecorino (7” pizza) – sub GF crust for additional charge GFA"
|
||||
"desc": "roasted red pepper sauce, goat cheese, roasted eggplant, red onions, roasted cremini mushrooms, with truffle oil tossed arugula & shredded pecorino (7” pizza) GFA V – sub GF crust for additional charge"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Affogato",
|
||||
"desc": "Espresso, house-made biscotti \u0026 ice cream GFA"
|
||||
"name": "Glazed Cinnamon Roll",
|
||||
"desc": "house-made cinnamon roll"
|
||||
},
|
||||
{
|
||||
"name": "“Affogato",
|
||||
"desc": "espresso, house-made biscotti & ice cream GFA V"
|
||||
},
|
||||
{
|
||||
"name": "Root Beer Float",
|
||||
"desc": "Pelican root beer, vanilla ice cream GF"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5058,13 +5082,43 @@
|
||||
{
|
||||
"name": "Repub Fries",
|
||||
"desc": "Crispy Idaho fries tossed in togarashi, topped with aioli and chili sauce. Garnished with cilantro, pickled jalapeños, blue cheese, and garlic confit – add candied bacon $3 GF"
|
||||
},
|
||||
{
|
||||
"name": "Double Cooked Chicken Wings",
|
||||
"desc": "Cured, slow-cooked, and then fried. Choose BBQ spice, salt and pepper, hot, buffalo, or honey butter"
|
||||
},
|
||||
{
|
||||
"name": "Deviled Eggs",
|
||||
"desc": "Beer mustard whipped yolk, chile sauce, seed mix, candied bacon, green onion"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
|
||||
{
|
||||
"name": "Loco Moco",
|
||||
"desc": "8 oz. grilled WA beef on a bed of jasmine rice, covered in house-made mushroom gravy, with a sunny-side-up egg, kimchi, avocado, and fresh cilantro"
|
||||
},
|
||||
{
|
||||
"name": "Monte Cristo",
|
||||
"desc": "Crispy bacon, melted cheddar cheese and stone-ground dijon served on French toast with a side of house-made raspberry jam and choice of side – add a fried egg $2"
|
||||
},
|
||||
{
|
||||
"name": "Macaroni and Cheese",
|
||||
"desc": "Curly noodles tossed in a creamy garlic-cheese sauce then baked until golden brown – make it dirty $4"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
|
||||
{
|
||||
"name": "Brown Butter Old Fashioned Donut",
|
||||
"desc": "Classic glazed donut, cooked in brown butter, served warm, then topped with vanilla bean ice cream and sea salt"
|
||||
},
|
||||
{
|
||||
"name": "NY Style Cheesecake",
|
||||
"desc": "Whipped cream, caramel and candied pecans"
|
||||
},
|
||||
{
|
||||
"name": "Churro Waffle",
|
||||
"desc": "Tossed in cinnamon and sugar with salted caramel ice cream and fudge sauce"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5090,12 +5144,24 @@
|
||||
{
|
||||
"name": "Roasted Beet Salad",
|
||||
"desc": "Balsamic roasted beets, goat cheese, arugula, candied pine nuts, sherry citrus vinaigrette GF"
|
||||
},
|
||||
{
|
||||
"name": "Pi Bites",
|
||||
"desc": "baked and crisp-fried pi dough, sea salt, Perry Street beer cheese sauce, green onion"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Pork Carnitas Pizza",
|
||||
"desc": "roasted tomatillo verde, cheese blend, jalapenos, red onion, chipotle crema (7” pizza) – sub GF crust for additional charge GFA"
|
||||
},
|
||||
{
|
||||
"name": "30th Off Grand",
|
||||
"desc": "rossa sauce, Italian sausage, pepperoni, house cured ham, cheese blend (7” pizza) – sub GF crust for additional charge GFA"
|
||||
},
|
||||
{
|
||||
"name": "Wild Mushroom Pizza",
|
||||
"desc": "Roasted pepper coulis, goat cheese, baby kale, caramelized shallots (7” pizza) – sub GF crust for additional charge GFA"
|
||||
"desc": "roasted pepper coulis, goat cheese, baby kale, caramelized shallots (7” pizza) – sub GF crust for additional charge GFA V"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
@@ -5246,24 +5312,44 @@
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "Fried CalamariDeep-fried calamari with a lemon garlic aioli",
|
||||
"desc": "Zuppa ToscanaSpicy Italian sausage, kale and potatoes in a rich chicken broth Antipasto SaladSalami, pepperoni, Castelvetrano olives, mozzarella, roasted cherry tomatoes, romaine, red wine vinegar dressing"
|
||||
"name": "Fried Calamari",
|
||||
"desc": "Deep-fried calamari with a lemon garlic aioli DF"
|
||||
},
|
||||
{
|
||||
"name": "Zuppa Toscana",
|
||||
"desc": "Spicy Italian sausage, kale and potatoes in a rich chicken broth GF"
|
||||
},
|
||||
{
|
||||
"name": "Antipasto Salad",
|
||||
"desc": "Salami, pepperoni, Castelvetrano olives, mozzarella, roasted cherry tomatoes, romaine, red wine vinegar dressing GF"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Clam LinguineManila clams, linguine pasta, white wine clam sauce",
|
||||
"desc": "Spicy Sausage Penne PastaGarlic, shallots, vodka-tomato cream sauce, parmesan, toasted bread, basil Grilled Chicken Bruschetta Sandwich6 oz. chicken breast, tomato-olive tapenade, sliced mozzarella, arugula, basil, roasted garlic puree on a ciabatta with tomato-basil chips A"
|
||||
"name": "Clam Linguine",
|
||||
"desc": "Manila clams, linguine pasta, white wine clam sauce DF"
|
||||
},
|
||||
{
|
||||
"name": "Spicy Sausage Penne Pasta",
|
||||
"desc": "Garlic, shallots, vodka-tomato cream sauce, parmesan, toasted bread, basil"
|
||||
},
|
||||
{
|
||||
"name": "ZGrilled Chicken Bruschetta Sandwich",
|
||||
"desc": "6 oz. chicken breast, tomato-olive tapenade, sliced mozzarella, arugula, basil, roasted garlic puree on a ciabatta with tomato-basil chips GFA"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Creme Brulée",
|
||||
"desc": "House-made custard, caramelized candy shell, fresh berries GF"
|
||||
"name": "Lemon Ricotta Cheesecake",
|
||||
"desc": "Sweet ricotta, lemon, cookie crust, raspberry puree, candied lemon, whipped cream V"
|
||||
},
|
||||
{
|
||||
"name": "GF V V+",
|
||||
"desc": "SorbetChoice of seasonal offerings"
|
||||
"name": "Creme Brulée",
|
||||
"desc": "House-made custard, caramelized candy shell, fresh berries GF V"
|
||||
},
|
||||
{
|
||||
"name": "Sorbet",
|
||||
"desc": "Choice of seasonal offerings GF V V+ DF"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5286,18 +5372,42 @@
|
||||
{
|
||||
"name": "Fried Polenta",
|
||||
"desc": "Fried polenta, topped with house-made marinara, mozzarella, balsmic glaze and fresh basil GF"
|
||||
},
|
||||
{
|
||||
"name": "Salmon Cakes",
|
||||
"desc": "House-made salmon cakes seasoned with peppers garlic, shallots, on a bed of arugula and shaved fennel topped with pineapple salsa DF"
|
||||
},
|
||||
{
|
||||
"name": "Bacon Jam Crostini",
|
||||
"desc": "Warmed brie cheese, topped with house-made onion bacon jam. Garnished with parsley and pickled jalapeños"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Lemon Herbed Mahi",
|
||||
"desc": "Seasoned mahi, on a bed of lemon herb rice, with roasted asparagus and blistered cherry tomatoes topped with caper lemon butter sauce GF"
|
||||
},
|
||||
{
|
||||
"name": "Beef and Mushroom Pappardelle",
|
||||
"desc": "House-made braised short rib with mushrooms and pappardelle pasta in a cream sauce topped with parmesan and parsley – Vegetarian option available GF"
|
||||
"desc": "House-made braised short rib with mushrooms and pappardelle pasta in a cream sauce topped with parmesan and parsley – Vegetarian option available GF V"
|
||||
},
|
||||
{
|
||||
"name": "Pork Belly and Grits",
|
||||
"desc": "Pork belly over cheesy cheddar grits and roasted asparagus, topped with a house-made maple bourbon glaze and pickled jalapenos GF"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Blackberry CrispBlackberry crisp served with vanilla ice cream",
|
||||
"desc": "Sweet PretzelsCinnamon sugar pretzels served with a cream cheese frosting Chocolate Peanut Butter PiePeanut butter cups sandwiched between layers of chocolate and peanut butter mousse on an Oreo crumb crust"
|
||||
"name": "Blackberry Crisp",
|
||||
"desc": "Blackberry crisp served with vanilla ice cream GF"
|
||||
},
|
||||
{
|
||||
"name": "Sweet Pretzels",
|
||||
"desc": "Cinnamon sugar pretzels served with a cream cheese frosting"
|
||||
},
|
||||
{
|
||||
"name": "Chocolate Peanut Butter Pie",
|
||||
"desc": "Peanut butter cups sandwiched between layers of chocolate and peanut butter mousse on an Oreo crumb crust"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5433,9 +5543,13 @@
|
||||
"phone": "(208) 664-8008",
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "House Caesar Salad",
|
||||
"desc": "Crisp romaine lettuce tossed in house-made Caesar dressing. Topped with tomatoes, lemon and croutons GFA"
|
||||
},
|
||||
{
|
||||
"name": "House Bistro Salad",
|
||||
"desc": "Mixed arcadian greens tossed in your choice of our house-made dressings. Topped with grape tomatoes, mixed dried fruit and croutons GFA"
|
||||
"desc": "Mixed arcadian greens tossed in your choice of our house-made dressings. Topped with grape tomatoes, mixed dried fruit and croutons GFA V"
|
||||
},
|
||||
{
|
||||
"name": "Butternut Squash Bisque",
|
||||
@@ -5443,12 +5557,31 @@
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
|
||||
{
|
||||
"name": "Peppercorn Sirloin",
|
||||
"desc": "Pendleton Oregon sirloin steak served with house-made creamy peppercorn sauce, roasted garlic mashed potatoes, and seasonal vegetables GF"
|
||||
},
|
||||
{
|
||||
"name": "Saffron Seafood Stew",
|
||||
"desc": "Wild Norwegian salmon, Alaskan cod, shrimp, Andouille sausage, and red potatoes in a hearty house-made saffron tomato broth, with sourdough toast GFA"
|
||||
},
|
||||
{
|
||||
"name": "Cilantro Lime Chicken",
|
||||
"desc": "Two chile con limon seasoned chicken breasts served over house-made chimichurri rice and seasonal vegetables GF"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Huckleberry CheesecakeHuckleberry cheesecake served with huckleberry-gin compote",
|
||||
"desc": "Death by Chocolate CakeDouble layered chocolate cake with a chocolate cream cheese frosting and finished with whipped cream Rotating Seasonal GelatoThree scoops of locally sourced gelato from Gelato by the Lake (Ask your server for the current selection)"
|
||||
"name": "Huckleberry Cheesecake",
|
||||
"desc": "Huckleberry cheesecake served with huckleberry-gin compote"
|
||||
},
|
||||
{
|
||||
"name": "Death by Chocolate Cake",
|
||||
"desc": "Double layered chocolate cake with a chocolate cream cheese frosting and finished with whipped cream"
|
||||
},
|
||||
{
|
||||
"name": "Rotating Seasonal Gelato",
|
||||
"desc": "Three scoops of locally sourced gelato from Gelato by the Lake (Ask your server for the current selection) GF"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5643,28 +5776,44 @@
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "GF V+",
|
||||
"desc": "Fresh RollRice paper-wrapped lettuce, carrots, cabbage, cilantro, basil, cucumber and vermicelli rice noodles, served with peanut sauce and topped with special sauce"
|
||||
"name": "Fish Cakes",
|
||||
"desc": "Deep-fried blended tilapia with sliced string beans, mixed with our special curry. Served with sweet chili sauce and garnished with cucumber DF"
|
||||
},
|
||||
{
|
||||
"name": "Fresh Roll",
|
||||
"desc": "Rice paper-wrapped lettuce, carrots, cabbage, cilantro, basil, cucumber and vermicelli rice noodles, served with peanut sauce and topped with special sauce GF V+ DF "
|
||||
},
|
||||
{
|
||||
"name": "Crab Rangoon",
|
||||
"desc": "Deep fried wonton-wrapped crab meat, cream cheese, onion, celery, and carrots served with sweet chili sauce"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "GF V+",
|
||||
"desc": "Panang Curry Braised Beef/Tofu“Braised beef, Panang curry, coconut milk, lime leaves and potatoes garnished with basil and bell pepper and kaffir lime leaves. Served with rice”"
|
||||
"name": "Khao Man Gai",
|
||||
"desc": "Slow-roasted chicken, Sirinya’s special sauce, garlic, ginger, cilantro, white radish, green onion with jasmine rice and soup. Garnished with cucumber DF"
|
||||
},
|
||||
{
|
||||
"name": "Panang Curry Braised Beef/Tofu",
|
||||
"desc": "“Braised beef, Panang curry, coconut milk, lime leaves and potatoes garnished with basil and bell pepper and kaffir lime leaves. Served with rice” GF V+ DF"
|
||||
},
|
||||
{
|
||||
"name": "Bahmi Haeng Hmudaeng",
|
||||
"desc": "Lo mein noodles with roasted red pork and bok choy, topped with dried garlic, cilantro, green onion and special sauce"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "GFA V+",
|
||||
"desc": "Bua LoiRice flour rolled sweet potato cooked in pandan and sweet coconut milk"
|
||||
"name": "Bua Loi",
|
||||
"desc": "Rice flour rolled sweet potato cooked in pandan and sweet coconut milk GFA V+ DF"
|
||||
},
|
||||
{
|
||||
"name": "GFA V+",
|
||||
"desc": "Sirinya’s Black Rice PuddingSweet black rice with coconut cream and pandan, topped with peaches"
|
||||
"name": "Sirinya’s Black Rice Pudding",
|
||||
"desc": "Sweet black rice with coconut cream and pandan, topped with peaches GFA V+ DF"
|
||||
},
|
||||
{
|
||||
"name": "Deep Fried Banana Ice Cream",
|
||||
"desc": "Deep fried sweet banana wrapped with wheat flour, coconut ice cream topped with honey garnished with a cherry V+"
|
||||
"desc": "Deep fried sweet banana wrapped with wheat flour, coconut ice cream topped with honey garnished with a cherry V+ DF"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5689,20 +5838,40 @@
|
||||
"desc": "Pick two of our dips to sample with pita or veggies: Hummus, mutabel (roasted eggplant), muhamara (roasted red pepper and walnut), lebneh (mint-yogurt) – Additional dips $4 GFA"
|
||||
},
|
||||
{
|
||||
"name": "GF V V+",
|
||||
"desc": "Falafel BitesSkewers’ scratch-made falafel served with our housemade taratour (sesame sauce) and pickled turnips – Add hummus \u0026 pita $4"
|
||||
"name": "Falafel Bites",
|
||||
"desc": "Skewers’ scratch-made falafel served with our housemade taratour (sesame sauce) and pickled turnips GF V V+ DF – Add hummus & pita $4"
|
||||
},
|
||||
{
|
||||
"name": "Curry Cup",
|
||||
"desc": "Our popular Armenian chicken curry, made with imported spices GFA –Add rice pilaf $4 pita bread $1"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "GFA V V+",
|
||||
"desc": "DolmaPepper stuffed with a rice and walnut based mixture. Served with fattoush salad (pomegranate dressing) topped with pita chips and a Skewers sauce of choice"
|
||||
"name": "Ghoozoo Eechee",
|
||||
"desc": "Slow cooked Lamb shank served with lavash bread and a well-spiced rice, topped with an array of nuts. Comes with a Skewers sauce of choice GFA"
|
||||
},
|
||||
{
|
||||
"name": "Hamem Chicken",
|
||||
"desc": "Seasoned chicken quarter: bone-in, skin on, roasted. Served with pita and flavorful rice with onions and peppers. Comes with a Skewers sauce of choice GFA "
|
||||
},
|
||||
{
|
||||
"name": "Dolma",
|
||||
"desc": "Pepper stuffed with a rice and walnut based mixture. Served with fattoush salad (pomegranate dressing) topped with pita chips and a Skewers sauce of choice GFA V V+ DF"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "GF V V+",
|
||||
"desc": "Cardamom Rice PuddingCoconut-based rice pudding made with cardamom and rose. Topped with pistachios and rose petals"
|
||||
"name": "Walnut Pakhlava",
|
||||
"desc": "Pastry made with alternating thin layers of phyllo dough and walnuts. Sweetened with a house made floral-honey syrup (2 pieces) GFA – Add 2 pieces $3"
|
||||
},
|
||||
{
|
||||
"name": "GLayali Lubnan “Lebanese Nights”",
|
||||
"desc": "Semolina pudding, topped with a layer of ashta (clotted cream), garnished with a heavy layer of pistachio, and sweetened with Skewers’ rose attar (syrup) GFA"
|
||||
},
|
||||
{
|
||||
"name": "Cardamom Rice Pudding",
|
||||
"desc": "Coconut-based rice pudding made with cardamom and rose. Topped with pistachios and rose petals GF V V+ DF"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5787,15 +5956,38 @@
|
||||
{
|
||||
"name": "Caprese Canapes",
|
||||
"desc": "Rich compound cheese topped with seasoned tomatoes, basil, and balsamic reduction. Served with crostinis GFA"
|
||||
},
|
||||
{
|
||||
"name": "Crab Cakes",
|
||||
"desc": "House-made crab cakes served with house aioli"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
|
||||
{
|
||||
"name": "Bánh Mì Burger",
|
||||
"desc": "A juicy Angus beef patty cooked in a tangy marinade and topped with BBQ pork pâté, cilantro, pickled carrots and cucumber, all on a ciabatta bun GFA"
|
||||
},
|
||||
{
|
||||
"name": "Birria Ramen",
|
||||
"desc": "Rice noodles topped with birria consume, braised birria meat, pickled radish, shredded cabbage, onions, and cotija cheese GFA"
|
||||
},
|
||||
{
|
||||
"name": "Vodka Pasta with Meatballs",
|
||||
"desc": "Fettuccini noodles tossed in tomato vodka sauce with mushrooms, and red peppers. Served with house-made meatballs"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Lemon Tart",
|
||||
"desc": "Bright lemon curd in a crisp crust V"
|
||||
},
|
||||
{
|
||||
"name": "Apple Cheesecake Eggroll",
|
||||
"desc": "House-made spiced apples and cheesecake wrapped in an eggroll shell and fried to perfection. Served with house-made bourbon caramel sauce V"
|
||||
},
|
||||
{
|
||||
"name": "Chocolate Torte",
|
||||
"desc": "A rich slice of flourless paradise GF"
|
||||
"desc": "A rich slice of flourless paradise GF V"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5993,21 +6185,41 @@
|
||||
"name": "Oeuf Mayonnaise",
|
||||
"desc": "Eight-minute egg, scallion oil, flaky salt, fresh greens – Add tobiko caviar $3; chili crisp $1.50 GF"
|
||||
},
|
||||
{
|
||||
"name": "César Revisité",
|
||||
"desc": "Romaine, creamy lemon and garlic dressing, bacon, fresh and crispy Parmesan cheese GF – Add 8-minute Egg $2 Anchovies $2 Smoked Duck $8"
|
||||
},
|
||||
{
|
||||
"name": "Bisque de Tomates",
|
||||
"desc": "Tomato bisque….Rich, thick, luxurious, creamy, tangy, addictive… and CdA famous GF"
|
||||
"desc": "Tomato bisque….Rich, thick, luxurious, creamy, tangy, addictive… and CdA famous GF V"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Chicken Fricassée",
|
||||
"desc": "A French country classic: Chicken thigh and mushrooms braised with white wine, chicken stock, shallots, cream, and herbs. Served with baby potatoes"
|
||||
},
|
||||
{
|
||||
"name": "Croque Monsieur",
|
||||
"desc": "Back Pocket Bakery baguette, Béchamel sauce, dijon, French ham and Swiss cheese baked until warm and melty. Served with mixed greens – Sub tomato bisque ($2) or potato chips"
|
||||
},
|
||||
{
|
||||
"name": "Salade de Betteraves Rôties et Burrata",
|
||||
"desc": "Roasted beets, burrata cheese, scallion oil, arugula and flaky salt, served with Back Pocket Bakery baguette GFA"
|
||||
"desc": "Roasted beets, burrata cheese, scallion oil, arugula and flaky salt, served with Back Pocket Bakery baguette GFA V"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Pots de Crème",
|
||||
"desc": "Rich chocolate custard finished with whipped cream – Add 3 oz. of 2018 Duorum LBV Port $12 GF"
|
||||
},
|
||||
{
|
||||
"name": "Pouding de Croissants au Caramel et aux Pistaches",
|
||||
"desc": "Croissants baked in vanilla custard and served warm with caramel sauce, crushed pistachios and whipped cream – Add 3 oz. Rare Wine Co. Boston Bual Madeira $12"
|
||||
},
|
||||
{
|
||||
"name": "Fromage Comté",
|
||||
"desc": "Comté is a French alpine cheese from the Jura, aged 18 months for nutty, savory depth and a smooth, firm texture. Served with pear jam & hazelnuts – Add 3oz Huet Vouvray Moelleux 1ere Trie LHL $15"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6028,16 +6240,46 @@
|
||||
"phone": "(208) 667-1170",
|
||||
"courses": {
|
||||
"First Course": [
|
||||
|
||||
{
|
||||
"name": "Lumpia",
|
||||
"desc": "Crispy Filipino spring rolls filled with savory pork, veggies, garlic, and spices, fried golden and served with a sweet chili dipping sauce"
|
||||
},
|
||||
{
|
||||
"name": "Toasted Ravioli",
|
||||
"desc": "Crispy, golden-breaded ravioli stuffed with portabella mushrooms and ricotta, fried to perfection and served with warm marinara for dipping V"
|
||||
},
|
||||
{
|
||||
"name": "Smoked Steelhead Spread",
|
||||
"desc": "Creamy smoked steelhead dip blended with herbs, lemon, and spices, rich and savory with a delicate smoky finish. Served with fried pita GFA"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Oxtail Stew",
|
||||
"desc": "Slow-braised oxtail stew with fall-off-the-bone meat in a rich, savory broth of herbs, butter beans, and Jamaican spices served over rice GF"
|
||||
},
|
||||
{
|
||||
"name": "Elote Mac and Cheese",
|
||||
"desc": "Creamy mac and cheese blended with spiced chicken, roasted corn, chili, and cotija, inspired by classic elotes for a smoky, zesty, crave-worthy twist GFA"
|
||||
},
|
||||
{
|
||||
"name": "Spam Fried Rice",
|
||||
"desc": "Savory fried rice tossed with crispy Spam, scrambled eggs, green onions, and soy, finished with a hint of sesame for bold, comforting flavor GF"
|
||||
"desc": "Savory fried rice tossed with crispy Spam, scrambled eggs, green onions, and soy, finished with a hint of sesame for bold, comforting flavor GF DF"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
|
||||
{
|
||||
"name": "Tiramisu",
|
||||
"desc": "Classic Italian dessert with espresso-soaked ladyfingers layered with creamy mascarpone, lightly sweetened and finished with a dusting of cocoa GF"
|
||||
},
|
||||
{
|
||||
"name": "Funfetti Edible Cookie Dough",
|
||||
"desc": "Sweet, creamy edible cookie dough loaded with colorful sprinkles, white chocolate chips, rich vanilla flavor, and a fun, nostalgic crunch every bite"
|
||||
},
|
||||
{
|
||||
"name": "Spanish Flan",
|
||||
"desc": "Silky Spanish custard with rich vanilla flavor, baked to perfection and topped with a smooth, golden caramel sauce GF"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6232,21 +6474,45 @@
|
||||
"phone": "(208) 292-5678",
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "House-Made Focaccia",
|
||||
"desc": "Honey Butter | Garlic Confit | Trapani Sea Salt V"
|
||||
},
|
||||
{
|
||||
"name": "Crispy Prosciutto Wrapped Asparagus",
|
||||
"desc": "Balsamic Reduction | Crushed Almonds GF"
|
||||
"desc": "Balsamic Reduction | Crushed Almonds GF DF"
|
||||
},
|
||||
{
|
||||
"name": "Romaine Salad",
|
||||
"desc": "Marinated Anchovy | Lemon Garlic Aioli | Parmesan GF"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "DFA",
|
||||
"desc": "Duck ConfitRosemary Gnocchi | Broccolini | Butternut Squash | Fennel | Campari Glaze"
|
||||
"name": "Seafood Linguine",
|
||||
"desc": "Seafood Linguine | House-Made Pasta | Penn Cove Clams | Shrimp | Pacific Cod | Lemon Cream Sauce"
|
||||
},
|
||||
{
|
||||
"name": "Duck Confit",
|
||||
"desc": "Rosemary Gnocchi | Broccolini | Butternut Squash | Fennel | Campari Glaze DFA "
|
||||
},
|
||||
{
|
||||
"name": "Royal Ranch Short Rib",
|
||||
"desc": "Mushroom Risotto | Brussels Sprouts | Marsala Demi-Glace GF"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Tiramisu DomeSoft Sponge Cake | Zabaglione | Cocoa | Coffee Cream",
|
||||
"desc": "Lemon Mascarpone CheesecakeStrawberries | Honey | Sweet Cream Pistachio CannoliCrisp Cannoli Shell | Sweet Ricotta | Toasted Pistachio | Dark Chocolate Curls"
|
||||
"name": "Tiramisu Dome",
|
||||
"desc": "Soft Sponge Cake | Zabaglione | Cocoa | Coffee Cream V"
|
||||
},
|
||||
{
|
||||
"name": "Lemon Mascarpone Cheesecake",
|
||||
"desc": "Strawberries | Honey | Sweet Cream V "
|
||||
},
|
||||
{
|
||||
"name": "Pistachio Cannoli",
|
||||
"desc": "Crisp Cannoli Shell | Sweet Ricotta | Toasted Pistachio | Dark Chocolate Curls V"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6268,12 +6534,31 @@
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "Tomato Basil SoupA Tomato Street favorite! Served with fresh garlic bread",
|
||||
"desc": "Minestrone SoupAn Italian classic, served with fresh garlic bread A House Garden or Caesar SaladHouse salad with your choice of dressing, or a Caesar salad. Served with fresh garlic bread A"
|
||||
"name": "Tomato Basil Soup",
|
||||
"desc": "A Tomato Street favorite! Served with fresh garlic bread V"
|
||||
},
|
||||
{
|
||||
"name": "Minestrone Soup",
|
||||
"desc": "An Italian classic, served with fresh garlic bread GFA"
|
||||
},
|
||||
{
|
||||
"name": "House Garden or Caesar Salad",
|
||||
"desc": "House salad with your choice of dressing, or a Caesar salad. Served with fresh garlic bread GFA"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
|
||||
{
|
||||
"name": "Shrimp Aragosta",
|
||||
"desc": "Fettuccini with a rich lobster cream sauce, sauteed prawns and bay shrimp, green onions and oven-dried tomatoes GFA"
|
||||
},
|
||||
{
|
||||
"name": "Carbonara",
|
||||
"desc": "A classic carbonara with a roasted garlic and black pepper cream sauce, tossed with bacon and spaghetti noodles, topped with parmesan cheese GFA"
|
||||
},
|
||||
{
|
||||
"name": "Chicken Parmigiana",
|
||||
"desc": "Breaded chicken breast topped with marinara sauce, melted mozzarella, provolone and parmesan cheeses, served with a side of fettuccine Alfredo"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
@@ -6657,24 +6942,44 @@
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "PolpetteHouse Made Meatballs. Nonna Sauce. Parmigiano-Reggiano.",
|
||||
"desc": "Caesar SaladCrispy Capers. Parmigiano-Reggiano. Croutons. E OO. A FocacciaHouse Made. Tomato Sauce. Green Onions. Sea Salt. E OO."
|
||||
"name": "Polpette",
|
||||
"desc": "House Made Meatballs. Nonna Sauce. Parmigiano-Reggiano."
|
||||
},
|
||||
{
|
||||
"name": "Caesar Salad",
|
||||
"desc": "Crispy Capers. Parmigiano-Reggiano. Croutons. EVOO. GFA"
|
||||
},
|
||||
{
|
||||
"name": "Focaccia",
|
||||
"desc": "House Made. Tomato Sauce. Green Onions. Sea Salt. EVOO."
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Calabrese Pizza",
|
||||
"desc": "House Italian Sausage. Burrata. Calabrian Chili Sauce. Fresh Scallions. Sea Salt. GFA – Sub house gluten-free crust $4"
|
||||
},
|
||||
{
|
||||
"name": "Margherita Pizza",
|
||||
"desc": "San Marzano Tomato Sauce. Fresh Mozzarella. Basil. Basil Oil. – Sub house gluten-free crust $4 GFA"
|
||||
"desc": "San Marzano Tomato Sauce. Fresh Mozzarella. Basil. Basil Oil. GFA V – Sub house gluten-free crust $4"
|
||||
},
|
||||
{
|
||||
"name": "Spaghetti & Meatballs",
|
||||
"desc": "House Meatballs. Marinara. Fresh Basil. Parmigiano-Reggiano."
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Zeppole",
|
||||
"desc": "Italian Donut. Lemon. Mascarpone. V"
|
||||
},
|
||||
{
|
||||
"name": "Chocolate Panna Cotta",
|
||||
"desc": "Chantilly Cream. Shaved Chocolate. GF"
|
||||
"desc": "Chantilly Cream. Shaved Chocolate. GF V"
|
||||
},
|
||||
{
|
||||
"name": "Gelato",
|
||||
"desc": "Whiskey Cream or Amarena Cherry. GF"
|
||||
"desc": "Whiskey Cream or Amarena Cherry. GF V"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6695,20 +7000,44 @@
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "Whipped Honey CornbreadOur Famous Cornbread With Whipped Honey Butter",
|
||||
"desc": "Crawfish GougeresServed With House Creole Sauce Andouille Cheddar DipServed With Firecracker Crostini"
|
||||
"name": "Whipped Honey Cornbread",
|
||||
"desc": "Our Famous Cornbread With Whipped Honey Butter"
|
||||
},
|
||||
{
|
||||
"name": "Crawfish Gougeres",
|
||||
"desc": "Served With House Creole Sauce"
|
||||
},
|
||||
{
|
||||
"name": "Andouille Cheddar Dip",
|
||||
"desc": "Served With Firecracker Crostini"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Gator Red RedAlligator, Andouille Sausage, Red Beans, Holy Trinity",
|
||||
"desc": "Bacon-Wrapped Shrimp And GritsWith Stone-Ground Cheesy Grits And Red Eye Gravy Short Rib A La QueenCarrots, Peas, Corn, Potato"
|
||||
"name": "Gator Red Red",
|
||||
"desc": "Alligator, Andouille Sausage, Red Beans, Holy Trinity GF"
|
||||
},
|
||||
{
|
||||
"name": "Bacon-Wrapped Shrimp And Grits",
|
||||
"desc": "With Stone-Ground Cheesy Grits And Red Eye Gravy GF"
|
||||
},
|
||||
{
|
||||
"name": "Short Rib A La Queen",
|
||||
"desc": "Carrots, Peas, Corn, Potato"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Praline BlondieThink Nutty, Caramel Goodness!",
|
||||
"desc": "BeignetOur Classic French Doughnut! Have As Is, Make It An Affogato, Or A 21+ Affogato! – Affogato $8, 21+ Affogato $9 Tarte Au CitronLemon Custard Tarte With Fresh Berries"
|
||||
"name": "Praline Blondie",
|
||||
"desc": "Think Nutty, Caramel Goodness! V"
|
||||
},
|
||||
{
|
||||
"name": "Beignet",
|
||||
"desc": "Our Classic French Doughnut! Have As Is, Make It An Affogato, Or A 21+ Affogato! – Affogato $8, 21+ Affogato $9"
|
||||
},
|
||||
{
|
||||
"name": "Tarte Au Citron",
|
||||
"desc": "Lemon Custard Tarte With Fresh Berries V"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6728,29 +7057,45 @@
|
||||
"phone": "(208) 758-7770",
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "Stuffed Onion",
|
||||
"desc": "Italian Sausage. Herbs. Parmigiano-Reggiano. GF"
|
||||
},
|
||||
{
|
||||
"name": "Grilled Asparagus",
|
||||
"desc": "Whipped Tarragon Chèvre. Fresh Peas. Lemon Jelly. GF"
|
||||
"desc": "Whipped Tarragon Chèvre. Fresh Peas. Lemon Jelly. GF V"
|
||||
},
|
||||
{
|
||||
"name": "Spanish Bomba",
|
||||
"desc": "Sherry Braised Pork. Sauce Bravas. Garlic Aioli. GF"
|
||||
"desc": "Sherry Braised Pork. Sauce Bravas. Garlic Aioli. GF DF"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
{
|
||||
"name": "Goose RavioliBrown Butter. Parmigiano-Reggiano.",
|
||||
"desc": "Grilled Lamb SkewerZa’atar. Charred Broccolini. Mint Yogurt. Migas al PastorPork Belly. Chorizo. Smoked Paprika. Red Peppers. Breadcrumbs. Grapes."
|
||||
"name": "Goose Ravioli",
|
||||
"desc": "Brown Butter. Parmigiano-Reggiano."
|
||||
},
|
||||
{
|
||||
"name": "Grilled Lamb Skewer",
|
||||
"desc": "Za’atar. Charred Broccolini. Mint Yogurt. GF"
|
||||
},
|
||||
{
|
||||
"name": "Migas al Pastor",
|
||||
"desc": "Pork Belly. Chorizo. Smoked Paprika. Red Peppers. Breadcrumbs. Grapes."
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Galatopita",
|
||||
"desc": "Lemon Custard Pie. Cinnamon. Honey. V"
|
||||
},
|
||||
{
|
||||
"name": "Chocolate Crémeux",
|
||||
"desc": "Chili Olive Oil. Flaky Sea Salt. GF"
|
||||
"desc": "Chili Olive Oil. Flaky Sea Salt. GF V"
|
||||
},
|
||||
{
|
||||
"name": "Cheese Plate",
|
||||
"desc": "Tomme de Saison. Cherry Jam. House Cracker. GF"
|
||||
"desc": "Tomme de Saison. Cherry Jam. House Cracker. GF V"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6770,26 +7115,45 @@
|
||||
"phone": "(509) 838-4600",
|
||||
"courses": {
|
||||
"First Course": [
|
||||
{
|
||||
"name": "Jalapeño Cheddar Chicken Soup",
|
||||
"desc": "Chicken stock base, jalapeños, sharp cheddar, shredded chicken, fresh herbs, and a touch of cream — a must-try! GF"
|
||||
},
|
||||
{
|
||||
"name": "Mandarin Salad",
|
||||
"desc": "Organic spring greens tossed in an Asian vinaigrette, topped with vegetable slaw, mandarins, toasted cashews and crispy wontons– Vegan Available by request GFA"
|
||||
"desc": "Organic spring greens tossed in an Asian vinaigrette, topped with vegetable slaw, mandarins, toasted cashews and crispy wontons GFA V – Vegan Available by request"
|
||||
},
|
||||
{
|
||||
"name": "Roasted Beet Salad",
|
||||
"desc": "Organic spring greens tossed in a balsamic white truffle vinaigrette, topped with roasted beets, toasted hazelnuts and chèvre – Vegan Available by request GF"
|
||||
"desc": "Organic spring greens tossed in a balsamic white truffle vinaigrette, topped with roasted beets, toasted hazelnuts and chèvre GF V – Vegan Available by request"
|
||||
}
|
||||
],
|
||||
"Second Course": [
|
||||
|
||||
{
|
||||
"name": "Lamb Ragu",
|
||||
"desc": "Tender braised leg of lamb in a rich, rustic tomato sauce, tossed with bucatini pasta and finished with fine herbs and parmesan GFA"
|
||||
},
|
||||
{
|
||||
"name": "Shrimp Risotto and Lobster Butter",
|
||||
"desc": "Creamy risotto tossed with fresh asparagus, garlic-sauteed jumbo shrimp, and finished with a decadent lobster butter GF"
|
||||
},
|
||||
{
|
||||
"name": "Yellow Curry Chicken or Tofu",
|
||||
"desc": "Housemade yellow curry, choice of tofu or chicken. Served with a coconut rice cake, sweet peas, heirloom carrots, roasted red peppers, and pea shoots GF – Vegan Available by request"
|
||||
}
|
||||
],
|
||||
"Third Course": [
|
||||
{
|
||||
"name": "Mini Margarita Pie",
|
||||
"desc": "Frozen key lime pie with graham cracker crust, silver tequila, whipped cream and red sea salt V"
|
||||
},
|
||||
{
|
||||
"name": "Chocolate Pot de Crème",
|
||||
"desc": "Rich mousse with coconut cream, chocolate and a hint of spice. Topped with raspberry purée and candied pecans – Vegan Available by request GF"
|
||||
"desc": "Rich mousse with coconut cream, chocolate and a hint of spice. Topped with raspberry purée and candied pecans GF V – Vegan Available by request"
|
||||
},
|
||||
{
|
||||
"name": "Wiley’s Bourbon Crème Brûlée",
|
||||
"desc": "Brûléed custard with vanilla bean and orange peel. Topped with bourbon-nutmeg caramel and whipped cream on the side GF"
|
||||
"desc": "Brûléed custard with vanilla bean and orange peel. Topped with bourbon-nutmeg caramel and whipped cream on the side GF V"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
123
fix-tavolata.ps1
Normal file
123
fix-tavolata.ps1
Normal file
@@ -0,0 +1,123 @@
|
||||
# fix-tavolata.ps1
|
||||
# Run this after the Wayback Machine rate limit resets (wait ~30 minutes after last run)
|
||||
# Recovers tavolata's Third Course using the same-block parser strategy
|
||||
|
||||
$projectDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$jsonPath = Join-Path $projectDir '2025-restaurants.json'
|
||||
$data = Get-Content $jsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
|
||||
function Decode-Html($str) {
|
||||
if (-not $str) { return $str }
|
||||
($str -replace '&','&' -replace ''',"'" -replace '"','"' -replace '<','<' -replace '>','>' -replace ' ',' ' -replace '\s+',' ').Trim()
|
||||
}
|
||||
function Get-CleanText($rawHtml) { Decode-Html ($rawHtml -replace '<[^>]+>', ' ') }
|
||||
function Test-DietaryTag($str) { $str -match '^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$' }
|
||||
|
||||
function Get-Dish($pContent) {
|
||||
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
|
||||
$bWithBrM = [regex]::Match($pContent, '(?s)<b>(.*?)<br\s*/?>', $opts)
|
||||
if ($bWithBrM.Success) {
|
||||
$name = Get-CleanText $bWithBrM.Groups[1].Value
|
||||
if ($name.Length -ge 3 -and $name.Length -le 80 -and -not (Test-DietaryTag $name) -and $name -notmatch '^[A-Z]{1,3}:') {
|
||||
return [PSCustomObject]@{ name = $name; desc = Get-CleanText ($pContent.Substring($bWithBrM.Index + $bWithBrM.Length)) }
|
||||
}
|
||||
}
|
||||
$bM = [regex]::Match($pContent, '(?s)<b>(.*?)</b>', $opts)
|
||||
if ($bM.Success) {
|
||||
$namePart = Get-CleanText $bM.Groups[1].Value
|
||||
if ($namePart.Length -ge 3 -and -not (Test-DietaryTag $namePart)) {
|
||||
$afterB = $pContent.Substring($bM.Index + $bM.Length)
|
||||
$sM2 = [regex]::Match($afterB, '(?s)^[^<]*<strong>(.*?)</strong>(.*)', $opts)
|
||||
if ($sM2.Success) {
|
||||
$p2 = Get-CleanText $sM2.Groups[1].Value
|
||||
if (-not (Test-DietaryTag $p2) -and $p2.Length -ge 2) {
|
||||
return [PSCustomObject]@{ name = "$namePart $p2".Trim(); desc = Get-CleanText $sM2.Groups[2].Value }
|
||||
}
|
||||
}
|
||||
return [PSCustomObject]@{ name = $namePart; desc = Get-CleanText $afterB }
|
||||
}
|
||||
}
|
||||
$sM = [regex]::Match($pContent, '(?s)<strong>(.*?)</strong>', $opts)
|
||||
if ($sM.Success) {
|
||||
$name = Get-CleanText $sM.Groups[1].Value
|
||||
if ($name.Length -lt 3 -or $name.Length -gt 80 -or (Test-DietaryTag $name) -or $name -match '^[A-Z]{1,3}:') { return $null }
|
||||
$afterBr = ''
|
||||
if ($pContent -match '(?s)<br\s*/?>(.*?)$') { $afterBr = $matches[1] }
|
||||
else { $am = [regex]::Match($pContent, '(?s)</strong>(.*?)$', $opts); if ($am.Success) { $afterBr = $am.Groups[1].Value } }
|
||||
return [PSCustomObject]@{ name = $name; desc = Get-CleanText $afterBr }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-Dishes($courseHtml) {
|
||||
$dishes = [System.Collections.ArrayList]@()
|
||||
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
|
||||
foreach ($pm in [regex]::Matches($courseHtml, '(?s)<p[^>]*>(.*?)</p>', $opts)) {
|
||||
$pc = $pm.Groups[1].Value
|
||||
if ($pc -notmatch '<b>|<strong>') { continue }
|
||||
$d = Get-Dish $pc
|
||||
if ($d -and $d.name) { $null = $dishes.Add($d) }
|
||||
}
|
||||
return ,$dishes
|
||||
}
|
||||
|
||||
function Get-CourseBlock($html, $label, $nextLabel) {
|
||||
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
|
||||
if ($nextLabel) {
|
||||
$m = [regex]::Match($html, ([regex]::Escape($label) + '(.+?)(?=' + [regex]::Escape($nextLabel) + ')'), $opts)
|
||||
if ($m.Success) { return $m.Groups[1].Value }
|
||||
}
|
||||
$idx = $html.IndexOf($label)
|
||||
if ($idx -ge 0) {
|
||||
$sub = $html.Substring($idx, [Math]::Min(8000, $html.Length - $idx))
|
||||
$sameDivM = [regex]::Match($sub, '(?s)</h[123]>\s*(<p.+?)(?=</div>)', $opts)
|
||||
if ($sameDivM.Success -and $sameDivM.Groups[1].Value -match '<p') { return $sameDivM.Groups[1].Value }
|
||||
$im = [regex]::Match($sub, '(?s)et_pb_text_inner">(?!<h[123])(.+?)(?=et_pb_text_inner"><h|</div>\s*</div>\s*</div>\s*</div>\s*<div)', $opts)
|
||||
if ($im.Success) { return $im.Groups[1].Value }
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
$r = $data | Where-Object { $_.slug -eq 'tavolata' }
|
||||
Write-Host "tavolata currently: $($r.menu.courses.'First Course'.Count)/$($r.menu.courses.'Second Course'.Count)/$($r.menu.courses.'Third Course'.Count)"
|
||||
|
||||
$timestamps = @('20250306132630','20250401000000','20250415000000','20250501000000')
|
||||
$success = $false
|
||||
|
||||
foreach ($ts in $timestamps) {
|
||||
if ($success) { break }
|
||||
Write-Host "Trying timestamp $ts..." -NoNewline
|
||||
try {
|
||||
$url = "https://web.archive.org/web/$ts/https://inlanderrestaurantweek.com/project/tavolata/"
|
||||
$resp = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop
|
||||
$html = $resp.Content
|
||||
if ($html -match '429 Too Many') { throw "Rate limited" }
|
||||
|
||||
$first = Get-Dishes (Get-CourseBlock $html 'First Course' 'Second Course')
|
||||
$second = Get-Dishes (Get-CourseBlock $html 'Second Course' 'Third Course')
|
||||
$third = Get-Dishes (Get-CourseBlock $html 'Third Course' $null)
|
||||
|
||||
Write-Host " -> $($first.Count)/$($second.Count)/$($third.Count)"
|
||||
|
||||
if ($third.Count -gt 0) {
|
||||
if ($first.Count -gt 0) { $r.menu.courses.'First Course' = @($first) }
|
||||
if ($second.Count -gt 0) { $r.menu.courses.'Second Course' = @($second) }
|
||||
$r.menu.courses.'Third Course' = @($third)
|
||||
Write-Host "SUCCESS! tavolata Third Course recovered." -ForegroundColor Green
|
||||
$success = $true
|
||||
} else {
|
||||
Write-Host " Third Course still empty, trying next timestamp..."
|
||||
}
|
||||
} catch {
|
||||
Write-Host " ERROR: $_" -ForegroundColor Red
|
||||
}
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
|
||||
if (-not $success) {
|
||||
Write-Host "Could not recover tavolata Third Course. Try again later." -ForegroundColor Yellow
|
||||
} else {
|
||||
$json = $data | ConvertTo-Json -Depth 10
|
||||
[System.IO.File]::WriteAllText($jsonPath, $json, [System.Text.Encoding]::UTF8)
|
||||
Write-Host "Saved to $jsonPath"
|
||||
}
|
||||
49
memory/MEMORY.md
Normal file
49
memory/MEMORY.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Inlander Restaurant Week Picker - Project Memory
|
||||
|
||||
## Quick Reference
|
||||
- See `scraping-guide.md` for full year-scraping instructions and script templates
|
||||
- See `html-structures.md` for HTML parsing patterns per restaurant type
|
||||
- Project dir: `\\WinServ-20-3.chns.local\Profiles\derekc\Documents\Coding Projects\Gitea-CooperandGoodman-Inlander-Restaurant-Week-Picker\Inlander-Restaurant-Week-Picker`
|
||||
|
||||
## Key Constraints (CRITICAL)
|
||||
- **WebFetch cannot access web.archive.org** — use `curl` via Bash tool instead
|
||||
- **PowerShell cannot run scripts from UNC paths** (\\server\...) — always `cp` scripts to local temp first
|
||||
- **bash `/tmp`** = `C:\Users\DEREKC~1.CHN\AppData\Local\Temp` (8.3 short name)
|
||||
- **PowerShell temp** = `C:\Users\derekc.CHNSLocal\AppData\Local\Temp` (long name) — same dir, different string
|
||||
- **Wayback Machine rate limits** to ~20 requests before throttling with 429; use 3-5 sec delays, wait 30+ min after getting blocked
|
||||
|
||||
## JSON Schema
|
||||
Each entry in `YEAR-restaurants.json`:
|
||||
```json
|
||||
{
|
||||
"name": "Restaurant Name",
|
||||
"slug": "restaurantslug",
|
||||
"price": 45,
|
||||
"areas": ["Downtown"],
|
||||
"cuisine": "American",
|
||||
"url": "https://inlanderrestaurantweek.com/project/SLUG/",
|
||||
"menu": {
|
||||
"hours": "Menu served 5pm-close",
|
||||
"phone": "(509) 555-1234",
|
||||
"courses": {
|
||||
"First Course": [{"name": "Dish Name", "desc": "Description"}],
|
||||
"Second Course": [...],
|
||||
"Third Course": [...]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Price is always 25, 35, or 45. gardenparty genuinely has 4 Third Course options.
|
||||
|
||||
## 2025 Data Status
|
||||
- **File**: `2025-restaurants.json` (121 restaurants)
|
||||
- **Wayback snapshot used**: `20250306132630` (primary), `20250401000000` (backup for some)
|
||||
- **Complete (3/3/3+)**: 111 restaurants
|
||||
- **gardenparty**: 3/3/4 — correct, it genuinely offers 4 dessert choices
|
||||
- **tavolata**: 3/3/0 — needs fix-tavolata.ps1 run when rate limit resets
|
||||
- **0/0/0 (JS-only, unrecoverable)**: heritage, kismet, littlenoodle, macdaddys, purgatory, redtail, republickitchen, republicpi, vicinopizza
|
||||
|
||||
## Scripts in Project Directory
|
||||
- `fix-tavolata.ps1` — run after rate limit resets to recover tavolata Third Course
|
||||
- Copy to local temp and run: `cp ...\fix-tavolata.ps1 C:\Users\derekc.CHNSLocal\AppData\Local\Temp\`
|
||||
- Then: `powershell.exe -ExecutionPolicy Bypass -File C:\Users\derekc.CHNSLocal\AppData\Local\Temp\fix-tavolata.ps1`
|
||||
152
memory/html-structures.md
Normal file
152
memory/html-structures.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# IRW Website HTML Structure Reference
|
||||
|
||||
## Restaurant Page URL
|
||||
Live: `https://inlanderrestaurantweek.com/project/SLUG/`
|
||||
Archived: `https://web.archive.org/web/TIMESTAMP/https://inlanderrestaurantweek.com/project/SLUG/`
|
||||
|
||||
## Page Framework
|
||||
The site uses WordPress + Divi theme. Relevant container class: `et_pb_text_inner`.
|
||||
Each course section typically occupies one or two `et_pb_text_inner` divs.
|
||||
|
||||
---
|
||||
|
||||
## Course Layout Types
|
||||
|
||||
### Layout A — Heading and items in SEPARATE divs (most restaurants)
|
||||
```html
|
||||
<div class="et_pb_text_inner"><h3>First Course</h3></div>
|
||||
<div class="et_pb_text_inner">
|
||||
<p><strong>Dish Name</strong><br/>Description</p>
|
||||
<p><strong>Dish Name 2</strong><br/>Description 2</p>
|
||||
</div>
|
||||
<div class="et_pb_text_inner"><h3>Second Course</h3></div>
|
||||
...
|
||||
```
|
||||
|
||||
### Layout B — Heading and items in SAME div (tavolata, durkins, table13, others)
|
||||
```html
|
||||
<div class="et_pb_text_inner">
|
||||
<h3>First Course</h3>
|
||||
<p><strong>Dish Name</strong><br/>Description</p>
|
||||
<p><strong>Dish Name 2</strong><br/>Description 2</p>
|
||||
</div>
|
||||
<div class="et_pb_text_inner">
|
||||
<h3>Second Course</h3>
|
||||
...
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dish Name Tag Styles
|
||||
|
||||
### Style 1 — `<strong>` tag (most restaurants)
|
||||
Examples: 315cuisine, anthonys, bardenay, barkrescuepub, etc.
|
||||
```html
|
||||
<p><strong>Dish Name</strong><br/>Description text here</p>
|
||||
<p><strong>Dish Name</strong> <br/>With space before br</p>
|
||||
```
|
||||
|
||||
### Style 2 — `<b>` tag with `<br/>` inside (India House, Lebanon, Karma, ponderosa)
|
||||
```html
|
||||
<p><b>Dish Name <br/></b><span>Description text</span></p>
|
||||
<p><b>Dish Name<br/></b> Description without span</p>
|
||||
```
|
||||
Key: name is inside `<b>`, the `<br/>` is INSIDE the `<b>` tag.
|
||||
|
||||
### Style 3 — `<b>` + `<strong>` combo (1898 restaurant)
|
||||
```html
|
||||
<p><span><b>First Part</b></span><strong>Second Part</strong> Description</p>
|
||||
```
|
||||
Full dish name = "First Part" + " " + "Second Part"
|
||||
|
||||
---
|
||||
|
||||
## Field Extraction Patterns
|
||||
|
||||
### Name (from page title)
|
||||
```
|
||||
<title>Restaurant Name | Inlander Restaurant Week</title>
|
||||
```
|
||||
Regex: `<title>(.+?) \| Inlander`
|
||||
|
||||
### Price (WARNING: unreliable — use price listing page instead)
|
||||
```html
|
||||
<h1 style="text-align: left;"><strong>$45</strong></h1>
|
||||
```
|
||||
Regex: `<strong>\$(\d+)</strong>`
|
||||
PROBLEM: Some pages show drink prices like $22 that match before the real price.
|
||||
SOLUTION: Always build an authoritative slug→price map from the price listing page.
|
||||
|
||||
### Price Listing Page — Authoritative Prices
|
||||
URL: `https://inlanderrestaurantweek.com/price/` (or Wayback archived version)
|
||||
```html
|
||||
<article class="et_pb_portfolio_item ... project_category_45 ...">
|
||||
...
|
||||
<a href="https://inlanderrestaurantweek.com/project/SLUG/">
|
||||
```
|
||||
Extract price tier from `project_category_(25|35|45)` CSS class.
|
||||
Extract slug from `href=".../project/SLUG/"`.
|
||||
|
||||
### Cuisine
|
||||
```html
|
||||
CUISINE: AMERICAN COMFORT FOOD
|
||||
```
|
||||
Often inside `<strong>` or `<em>` tags. Extract uppercase text after "CUISINE:".
|
||||
Apply `.ToTitleCase()` for proper formatting.
|
||||
|
||||
### Phone
|
||||
Area codes: 509 (Spokane area) or 208 (Idaho/CDA area)
|
||||
Pattern: `(509) 555-1234` or `(208) 555-1234`
|
||||
Regex: `\((?:208|509)\) \d{3}-\d{4}`
|
||||
|
||||
### Hours
|
||||
```
|
||||
Menu served 5pm-9pm nightly
|
||||
Menu served Thursday-Sunday, 5-9pm
|
||||
```
|
||||
Regex: `Menu served [^<]+`
|
||||
|
||||
### Area
|
||||
Look for area keywords (ALL CAPS in source) anywhere in the HTML:
|
||||
- DOWNTOWN, NORTH SPOKANE, SOUTH SPOKANE, WEST SPOKANE, SPOKANE VALLEY
|
||||
- AIRWAY HEIGHTS, LIBERTY LAKE, COEUR D'ALENE, POST FALLS, HAYDEN, ATHOL, WORLEY
|
||||
Default to ["Downtown"] if nothing matched.
|
||||
Some restaurants appear in multiple areas — collect all matches.
|
||||
|
||||
---
|
||||
|
||||
## Dietary Tag Filtering
|
||||
Skip these as dish names — they appear in `<strong>` but are dietary labels, not dish names:
|
||||
- GF (gluten free)
|
||||
- GFA (gluten free available)
|
||||
- V, V+ (vegetarian, vegan)
|
||||
- DF, DFA (dairy free, dairy free available)
|
||||
- V:, V+A (legend lines)
|
||||
- 2025 (year marker some restaurants include)
|
||||
- Drink (some restaurants label beverage course)
|
||||
|
||||
Full regex: `^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$`
|
||||
Also skip names matching `^[A-Z]{1,3}:` (legend lines like "GF: Gluten Free")
|
||||
Also skip names shorter than 3 chars or longer than 80 chars.
|
||||
|
||||
---
|
||||
|
||||
## Restaurants by Known HTML Style (2025)
|
||||
|
||||
**Layout B (same-block)**: tavolata, durkins, table13, terraza, and others
|
||||
**Style 2 (`<b>` tags)**: indiahouse, lebanon, karma, ponderosa, collectivekitchen, dryfly, masselowslounge, vieuxcarre, wileys, osprey, shawnodonnells, ganderryegrass
|
||||
**Style 3 (`<b>`+`<strong>` combo)**: 1898
|
||||
|
||||
Note: These styles may change year to year as restaurants update their pages.
|
||||
Always check a few representative pages before assuming the same structure applies.
|
||||
|
||||
---
|
||||
|
||||
## JS-Only Pages (no static HTML menu content)
|
||||
These restaurants had no recoverable menu data from any Wayback snapshot in 2025:
|
||||
heritage, kismet, littlenoodle, macdaddys, purgatory, redtail, republickitchen, republicpi, vicinopizza
|
||||
|
||||
Their pages are fully JS-rendered — the static HTML captured by Wayback Machine
|
||||
shows the page shell but not the menu content. For future years, these may or may not
|
||||
have static caches depending on server-side rendering changes.
|
||||
237
memory/scraping-guide.md
Normal file
237
memory/scraping-guide.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# IRW Scraping Guide — Full Process for Adding a New Year
|
||||
|
||||
## Overview
|
||||
The Inlander Restaurant Week website (inlanderrestaurantweek.com) is WordPress/Divi.
|
||||
Menu pages are partially JS-rendered but WP-Super-Cache creates static HTML snapshots
|
||||
that the Wayback Machine archives. We scrape those static snapshots.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Find Restaurant Slugs
|
||||
|
||||
Fetch the price listing page to get all slugs for that year:
|
||||
```bash
|
||||
curl -s "https://web.archive.org/web/TIMESTAMP/https://inlanderrestaurantweek.com/price/" \
|
||||
-o /tmp/irw-price-YEAR.html
|
||||
```
|
||||
|
||||
Pick a timestamp close to the event (Wayback Machine format: YYYYMMDDHHmmss).
|
||||
The price listing page has portfolio items like:
|
||||
```html
|
||||
<article class="et_pb_portfolio_item ... project_category_45">
|
||||
<a href="https://inlanderrestaurantweek.com/project/SLUG/">
|
||||
```
|
||||
Extract slug from the href. The class `project_category_(25|35|45)` gives authoritative price.
|
||||
|
||||
**Important**: Scrape the price listing page FIRST and save the slug→price map.
|
||||
Some restaurant pages have drink prices ($22, $33) that confuse the price parser.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Scrape Each Restaurant Page
|
||||
|
||||
Use a PowerShell script (written to project dir, copied to local temp to run):
|
||||
|
||||
**Wayback Machine URL format**:
|
||||
```
|
||||
https://web.archive.org/web/TIMESTAMP/https://inlanderrestaurantweek.com/project/SLUG/
|
||||
```
|
||||
|
||||
**Key fields to extract**:
|
||||
```powershell
|
||||
# Name
|
||||
$nameM = [regex]::Match($html, '<title>(.+?) \| Inlander')
|
||||
|
||||
# Price (from page, but USE PRICE LISTING MAP - this can be wrong)
|
||||
$priceM = [regex]::Match($html, '<strong>\$(\d+)</strong>')
|
||||
|
||||
# Cuisine
|
||||
$cuisineM = [regex]::Match($html, 'CUISINE:\s*([A-Z][A-Za-z/ ]+?)(?:\s*</|\s*<)')
|
||||
$cuisine = (Get-Culture).TextInfo.ToTitleCase($c.ToLower())
|
||||
|
||||
# Phone
|
||||
$phoneM = [regex]::Match($html, '\((?:208|509)\) \d{3}-\d{4}')
|
||||
|
||||
# Hours
|
||||
$hoursM = [regex]::Match($html, 'Menu served [^<]+')
|
||||
|
||||
# Area (match against known area keys, case-insensitive)
|
||||
$areaMap keys: "AIRWAY HEIGHTS","ATHOL","COEUR D'ALENE","POST FALLS","HAYDEN",
|
||||
"LIBERTY LAKE","NORTH SPOKANE","SOUTH SPOKANE","SPOKANE VALLEY",
|
||||
"WEST SPOKANE","WORLEY","DOWNTOWN"
|
||||
```
|
||||
|
||||
**Rate limiting**: Add `Start-Sleep -Milliseconds 2000` between each request.
|
||||
After a 429, stop and wait 30+ minutes before trying again.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Parse Menu Courses
|
||||
|
||||
### Course Block Extraction (`Get-CourseBlock`)
|
||||
Two HTML layouts exist:
|
||||
|
||||
**Layout A** (most common): heading and items in SEPARATE `et_pb_text_inner` blocks
|
||||
```powershell
|
||||
# Strategy 1: find content between this label and next label
|
||||
$m = [regex]::Match($html, [regex]::Escape($label) + '(.+?)(?=' + [regex]::Escape($nextLabel) + ')', $opts)
|
||||
|
||||
# Strategy 3 (fallback): items in next et_pb_text_inner block
|
||||
$im = [regex]::Match($sub, '(?s)et_pb_text_inner">(?!<h[123])(.+?)(?=et_pb_text_inner"><h|</div>\s*</div>\s*</div>\s*</div>\s*<div)', $opts)
|
||||
```
|
||||
|
||||
**Layout B** (some restaurants — tavolata, durkins, table13, etc.): heading + items in SAME block
|
||||
```powershell
|
||||
# Strategy 2: extract <p> tags after </h3> within same div
|
||||
$sameDivM = [regex]::Match($sub, '(?s)</h[123]>\s*(<p.+?)(?=</div>)', $opts)
|
||||
```
|
||||
|
||||
### Dish Parsing (`Parse-Dish`)
|
||||
Three tag styles exist:
|
||||
|
||||
**Style 1** (most restaurants): `<strong>` for name
|
||||
```html
|
||||
<p><strong>Dish Name</strong><br/>Description text</p>
|
||||
```
|
||||
|
||||
**Style 2** (India House, Lebanon, Karma, others): `<b>` with `<br/>` before `</b>`
|
||||
```html
|
||||
<p><b>Dish Name <br/></b><span>Description text</span></p>
|
||||
```
|
||||
|
||||
**Style 3** (1898): `<b>` + `<strong>` combination
|
||||
```html
|
||||
<p><span><b>Part1</b></span><strong>Part2</strong> Description</p>
|
||||
```
|
||||
|
||||
**Multi-strategy parser** (handles all three):
|
||||
```powershell
|
||||
function Parse-Dish($pContent) {
|
||||
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
|
||||
|
||||
# Style 2: <b>Name <br/></b>
|
||||
$bWithBrM = [regex]::Match($pContent, '(?s)<b>(.*?)<br\s*/?>', $opts)
|
||||
if ($bWithBrM.Success) {
|
||||
$name = Get-CleanText $bWithBrM.Groups[1].Value
|
||||
if (Test-ValidDishName $name) {
|
||||
$desc = Get-CleanText ($pContent.Substring($bWithBrM.Index + $bWithBrM.Length))
|
||||
return [PSCustomObject]@{ name = $name; desc = $desc }
|
||||
}
|
||||
}
|
||||
|
||||
# Style 3: <b>Part1</b>...<strong>Part2</strong>
|
||||
$bM = [regex]::Match($pContent, '(?s)<b>(.*?)</b>', $opts)
|
||||
if ($bM.Success) {
|
||||
$namePart = Get-CleanText $bM.Groups[1].Value
|
||||
if (Test-ValidDishName $namePart) {
|
||||
$afterB = $pContent.Substring($bM.Index + $bM.Length)
|
||||
$sM2 = [regex]::Match($afterB, '(?s)^[^<]*<strong>(.*?)</strong>(.*)', $opts)
|
||||
if ($sM2.Success) {
|
||||
$p2 = Get-CleanText $sM2.Groups[1].Value
|
||||
if (-not (Test-DietaryTag $p2) -and $p2.Length -ge 2) {
|
||||
return [PSCustomObject]@{ name = "$namePart $p2".Trim(); desc = Get-CleanText $sM2.Groups[2].Value }
|
||||
}
|
||||
}
|
||||
return [PSCustomObject]@{ name = $namePart; desc = Get-CleanText $afterB }
|
||||
}
|
||||
}
|
||||
|
||||
# Style 1: <strong>Name</strong>
|
||||
$sM = [regex]::Match($pContent, '(?s)<strong>(.*?)</strong>', $opts)
|
||||
if ($sM.Success) {
|
||||
$name = Get-CleanText $sM.Groups[1].Value
|
||||
if (-not (Test-ValidDishName $name)) { return $null }
|
||||
$afterBr = ''
|
||||
if ($pContent -match '(?s)<br\s*/?>(.*?)$') { $afterBr = $matches[1] }
|
||||
else { $am = [regex]::Match($pContent, '(?s)</strong>(.*?)$', $opts); if ($am.Success) { $afterBr = $am.Groups[1].Value } }
|
||||
return [PSCustomObject]@{ name = $name; desc = Get-CleanText $afterBr }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Test-ValidDishName($name) {
|
||||
$name.Length -ge 3 -and $name.Length -le 80 -and
|
||||
$name -notmatch '^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$' -and
|
||||
$name -notmatch '^[A-Z]{1,3}:'
|
||||
}
|
||||
|
||||
function Test-DietaryTag($str) {
|
||||
$str -match '^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$'
|
||||
}
|
||||
```
|
||||
|
||||
### HTML Cleanup
|
||||
```powershell
|
||||
function Get-CleanText($rawHtml) {
|
||||
$t = $rawHtml -replace '<[^>]+>', ' '
|
||||
$t = $t -replace '&', '&' -replace ''', "'" -replace '"', '"'
|
||||
$t = $t -replace '<', '<' -replace '>', '>' -replace ' ', ' '
|
||||
$t = $t -replace '–', '-' -replace '—', '-'
|
||||
($t -replace '\s+', ' ').Trim()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Fix Prices
|
||||
|
||||
After scraping, apply authoritative prices from the price listing page:
|
||||
- Parse `project_category_(25|35|45)` CSS class from portfolio items
|
||||
- Match slug from adjacent `href` attribute
|
||||
- Build a hashtable and apply to all entries
|
||||
|
||||
Common gotcha: Restaurant pages may show $22 (wine), $33 (lunch) — these are NOT the event price.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Recover Missing Restaurants
|
||||
|
||||
If a restaurant has 0/0/0 courses:
|
||||
1. Try alternate Wayback timestamps: `20250401000000`, `20250415000000`, `20250501000000`, `20250601000000`
|
||||
2. Check if page uses Layout B (same-block) — add Strategy 2 to course block extractor
|
||||
3. Check if page uses `<b>` tags instead of `<strong>` for dish names
|
||||
|
||||
**Known JS-only restaurants** (no static cache recoverable for 2025):
|
||||
heritage, kismet, littlenoodle, macdaddys, purgatory, redtail, republickitchen, republicpi, vicinopizza
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Output and Validation
|
||||
|
||||
```powershell
|
||||
# Save as UTF-8 (important — special characters in restaurant names)
|
||||
$json = $data | ConvertTo-Json -Depth 10
|
||||
[System.IO.File]::WriteAllText($outPath, $json, [System.Text.Encoding]::UTF8)
|
||||
|
||||
# Validate: list any restaurant not at 3/3/3
|
||||
$data | Where-Object {
|
||||
$_.menu.courses.'First Course'.Count -ne 3 -or
|
||||
$_.menu.courses.'Second Course'.Count -ne 3 -or
|
||||
$_.menu.courses.'Third Course'.Count -ne 3
|
||||
} | ForEach-Object {
|
||||
"$($_.slug): $($_.menu.courses.'First Course'.Count)/$($_.menu.courses.'Second Course'.Count)/$($_.menu.courses.'Third Course'.Count)"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PowerShell Script Execution Pattern (REQUIRED)
|
||||
|
||||
```bash
|
||||
# Write script to project dir (via Write tool or Edit)
|
||||
# Then in bash:
|
||||
cp "//WinServ-20-3.chns.local/Profiles/derekc/Documents/Coding Projects/.../script.ps1" \
|
||||
"/c/Users/derekc.CHNSLocal/AppData/Local/Temp/script.ps1"
|
||||
powershell.exe -ExecutionPolicy Bypass -File "C:\Users\derekc.CHNSLocal\AppData\Local\Temp\script.ps1"
|
||||
```
|
||||
|
||||
**Never** use `powershell -Command "..."` for multi-line scripts — escaping is unreliable.
|
||||
**Never** try to run `.ps1` directly from `\\WinServ-20-3...` UNC path — execution policy blocks it.
|
||||
|
||||
---
|
||||
|
||||
## PowerShell Gotchas
|
||||
- `"$slug: text"` fails if `:` follows var — use `"${slug}: text"`
|
||||
- Function names like `Is-X`, `Decode-X`, `Parse-X` get PSScriptAnalyzer warnings (unapproved verbs) but work fine
|
||||
- `return ,$array` (comma prefix) forces PowerShell to return an array, not unroll it
|
||||
- `[System.IO.File]::WriteAllText(path, json, UTF8)` — use this, not `Out-File`, to avoid BOM/encoding issues
|
||||
Reference in New Issue
Block a user