From 10578f2f1fd4228dc05eaa5b43ae61456a583865 Mon Sep 17 00:00:00 2001 From: AzzaOmer1 <103028535+AzzaOmer1@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:25:45 +0300 Subject: [PATCH] Fixed order of pages, expired items issue and search bar of recipes --- requirements.txt | 0 .../data/{clean_data => clean_data.py} | 0 smart_pantry_manager/data/pantry_data.xlsx | Bin 5411 -> 0 bytes smart_pantry_manager/data/pantry_omnia.xlsx | Bin 5305 -> 0 bytes .../pantry_azozomer743@gmail.com.xlsx | Bin 0 -> 5197 bytes ...d_recipes.py => 1- recommended_recipes.py} | 5 +- .../{all_recipes.py => 2- all_recipes.py} | 10 + smart_pantry_manager/requirements.txt | 1 - smart_pantry_manager/smart_pantry.py | 210 +++++++++++++----- the_app/data/pantry_omnia.xlsx | Bin 5155 -> 0 bytes 10 files changed, 174 insertions(+), 52 deletions(-) delete mode 100644 requirements.txt rename smart_pantry_manager/data/{clean_data => clean_data.py} (100%) delete mode 100644 smart_pantry_manager/data/pantry_data.xlsx delete mode 100644 smart_pantry_manager/data/pantry_omnia.xlsx create mode 100644 smart_pantry_manager/data/user_data/pantry_azozomer743@gmail.com.xlsx rename smart_pantry_manager/pages/{recommended_recipes.py => 1- recommended_recipes.py} (98%) rename smart_pantry_manager/pages/{all_recipes.py => 2- all_recipes.py} (88%) delete mode 100644 the_app/data/pantry_omnia.xlsx diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e69de29..0000000 diff --git a/smart_pantry_manager/data/clean_data b/smart_pantry_manager/data/clean_data.py similarity index 100% rename from smart_pantry_manager/data/clean_data rename to smart_pantry_manager/data/clean_data.py diff --git a/smart_pantry_manager/data/pantry_data.xlsx b/smart_pantry_manager/data/pantry_data.xlsx deleted file mode 100644 index 317f359bacbe71e3242d43ab3f208e681b2fecc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5411 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 F{tw(seyRWf diff --git a/smart_pantry_manager/data/pantry_omnia.xlsx b/smart_pantry_manager/data/pantry_omnia.xlsx deleted file mode 100644 index c5b3b2303e87a9aa6280945c4eaad247c8ac83c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5305 zcmZ`-1yodP+a0>4y9Ehh$U*7uZfQ^$0qJfD>5}d)l^Qyg4gq24E@=d$hY;i+uj~Hz z%J+RI*IDcA=bUG+r{1lugoI24007VdQ)#Y-vi&OZ$?&IP_=yKUEga0%ogExqIG;N@ za(LR=sz6n+y11}WSKBq+8`C3ciXKRdMCN>ia=3*wu-XM*A08l!*?W0?rYzuyc*xX{ zCUeZOVaLnUk0U-$H;Zw=I6c}Aifm=jNgVbGMHxm%Tjm`gMt+8r%l0(+g z4Voh9Q6s3q)V-z$60ByXEnB3*#M}0MBpOAiB#_F6au=mFM68vwHRy0-fzpoFLaP3U z3raslufD;5eFy;n!2h=k<_^x_Up^GatEhEzVTaxgCi(1@fQ6)uIp|dB=(#a^fQlc1 z1y|}XCcm#tq*Nev#+cuTzP4qnh?NWrR%4Y`w%~@K&Rns(F4iz;5CQaQ$37Uv2Ge%? z$*YcCDx-xfwwEDE8tYIx$`7(=a$!U@kRboT|8#xz+kGtIpHSvHDdegUKHixO`*+FRLR{*#z= zqbvqIQ~)5082})J6XR*i>1qYG1OL8q|Dxu=z|du$pXl`=WYp6VW{1`HB(R~5(8bz) zp)Ni6K$SKGUoXbi%OXD5zg_ML-cSn8L4rz4rZ6)5?vD`}x$PZ(uhZ`wFLpx1lTJL% z<~SFoTN^o-SD*1P;!ALmzk?igU=}Nz)Eig>y7~PE2no6ugQC!tJc)*;jaCgQI2oqD z)h#UP23y5dJb}<2j^Wub`-1Y$DaHRL*r47RLH9utvOxUXSVG{5*T%b)}t1)u=&dPlF}G0 zwTTPAmAy=>EX)`~wFw)KrHR;xzKT&#oVH`NuZB+e(|FT@ouv+4Vhd z0^<5j@d-MBo}>HpmvN*N6V_|DB#Jre)+Gs}Bfake5%@p*dXcBC$H`Mbvd-CYC=UQn zd&ZR2eIXJJk*i$^<3@4QP}X{N3iOQ6f#-5T^u3Fa+w;`cfg)b-Y(K#fn`&ldw=FLQ@%;2tEkBR*@(C$yPl7GiyTpD2bEt zS~32ubuGg?SEk_|UW!gx2&G$?)wrhy92Q5BqJ`>3wZ+yz& zXW2OsWwdX6y|lA#Y|Ac<27 ztd(GFx``S*dfbDtV`2CRV@P`f{4|mvN0QQhSL<9cd1e#R%+e%mxUmb#cNJxaUJ09S z*9~LwN;NtweQQW+t7qa@JgffVs7f=0Eo3R2K;7VxN6q&GX6(MfP|T&3D%C_G*@k4- zlD#O%pOY5fj5(nh=g@t%+v`cOCNPKG?3eb;ZhLlY?8kPeMpfaVa0+#Q_(=i%_x#W_ zXtz|DmycioRM9&(X46an4M_V!VH*)_6DMXc%rFe#%cJs~jz} za)D-BlP9wA|8(aogcj{0>bqr6c+Fps&5vxLf>Q{Wu z#l}}9!6J}1Pa-W*EI1|CYTIU0=+WVCm*H(7NG1EYzSZxfs?|i`-hhk9)7&cX!5hqN zS#q)h#KvGZ4A&=EQ!zf6+Rn_X^nU1mo$AqPc5>@DAcwmtv~7B3ELHg$wzPh2pC}}+ zoaNPvkKihU$dF8hNq|WPyhl#H|qGztz1MPyfc`T+#2c;r9MR=X1X0;!kR?ahFg)FRT z54TdZF_|fKd)sFNneP@%CPnHsjldo=f?I5&^yJ>f62cRCX+$II~7$f5QFI;F(tk!wlIlf1;YVR#iN*T zr#Cz)+)%|7Ui^88Q`OZI`}H8I9pPRBsS+;oL<9f=F#rIv-wnjW!P&;e3Ji92;r#3N zR~Ly%Ik3;+;iR!XvZ6el7h7qWnP_IO zgbK^)L=m-5oOKsvIm;Wg-47*|5yBPuYyHXo2kkh*;{`qLbn=7}n4XA>7{Jk`yo&*A z9A%9WVT`ckyYp-JR$Jl&!gf}wsl0>q7qBd`%#jPNtUpKr$6ir+f^OZh-^L2!?yG)ZzxxWzKUmH(q0`?X4yn8Ykg#pAD2EUWLBxR zPDq!f`~)-+Q%#pR57vZgQsYjb4V*G{Gm2+Nl4I7?I*M>C;spl{CCW6DKl}L#T`Ka) z=2DY$|9jxX52vZyvb>WGYhg2dhyGrVjiQgVj!of|R$oN+yrACS9qA>t8&+*n&Dba- z2m2bd(x{ITOqe@|;&O(7>o~PRnT_?h!0w$PmCG@Ap{(2s7g{`aZ)7aF2R>bA(7ujh zM_xT_!VF_^$2kFd_Hw*6EcEWQd}_&ADRgQ9^oKeq4ax293hwNC?i9CZ%{a1FenQk( zsjT0VtMbvsLLO$x;-9lToY`Y(#*6bwVB?~-rX);tPf!4L^?<*_N|Pk9LdNA{+q8{<=UoX)`YUd6QLwss7ip8mVNKI`@eH)H zn?RR+p{>fXm6q|lKciJ0_{sMY9HSV~$df9?qehoTH&lY0mx$th2Sq?#z zHJJp`ar5Q6h99)qs&Nkr=^s>YD1A#K-k+Hv%*4|RHJNM!q4VUd^IG=NFu$125P3J0 zp-H%poN7li-3OXrc{RgFT;%tT;xSN@6&PME^(kUrR!g%%oIsdjWw9upPC4{6eK8X4 z`Mg<0!}!KmyF<*y1FiHhCvQqr4x@aau#3dr%36#mFP$n4m+ugB%;i%L{d+x!OE_ zI&JH-Rl1t(YZ}Hkxzt1KCHKRsa79zhG_6wu5}n%F)W{w#;hdOfNe_Uc?ML6b^{etb`!Rderi;6)Tmh1!viauGjH&jM{2>glga{>JE}U>Uc~( z$D&}wgf@@yX>VK@RDV!oUhcw)EPCOU?Bc<>j$_!B;oKH%ZCchAJ3C!nh8p99WJm8a zL2{@<+J`G{MmB;%+wpM(ClK zV?%sU%Qt_0Cs7786Lq#>lM0Hnjd{WMc5|eqIEHWf?Gp_1jNMX-#8&R?iElpqEJKW6 znUlE7a#cITS&feiGhNN^LgUso?88ZXtzBDFbYe$1cNto~pLi|ZG4j}2reRU|!i
  • o|M6@Zy`W53VHXO%dyVv8CI~=BVlyw_yz{nI|@Fs%fG#$ckcm7*qfIm~DQEo4?1D+Zr@HD}K-%Xvtwl16;zpmK{O7@7{ z*y0cZu&MHg9z^c)*(#}Utfq+V!t|ba^PV$U%xnjp6~5A4P7V-)3(9k+a#<+0?c?e( zn$zX00Myv9n&mhZ=so6B5(nmk;zrMB@z}RcAAJUP+fO$tyWA+8VaD&ZxYU6?Fp16) zE-Sk;7!;)p=9~g}mF|WYp_r2y*^u)6n|FLiC0|a@>Ea6HWE|Pg;)|4{+>;=pIZJIt zr^V(k>O5C=?oLPF6Ny~4XLA)tjL*nmFga9Rq5SD6%nhNG3%(v?g*%G-yB7TFTrV6P zY<^XPn7A=j_=-@*zjzRB)ILZ-dCw#*Kmi?3$_HxTQJ>P2yl!6wVOC&OUJGIFpP3%` zWRew*D^wr_dmeM@!lA=9K76|P45)Vid5=W;5~q5Kz+icM5~SA|S-KpQ=xab!o7HJ0 z;oR0cQWglKO*tfUJun1RzZBPxQ3a9~);=3K8Hz^D&VC4fD=sdo7~@l^lys)3aa{ekq2+MutOjOn*g%+llh1x9OcBM;XzmUfzs7w zT?dq%0D0;bE`4Z9`-O!iCn(ZKNMlBb;M9s~5Zu2{11J)rB9@u{O2ia%Ubsf*ho!nJ zQIBp-wbDsTUh_FtSp)dsj()ymmru0f^;`a9TrS^o+V@fxtdBPhH;lS$VNW^@5?JDj zS@w*sYgCH@h6wOEQLW$B?k4(s7mI}%FCIK==7Y*LRnV`V?XBH&GBZ>%m=f;b4ASq# zv!RrOy(`$>)l}Qd5$t08D~c)-p}(T2&$RfP+bdpvNAW$5XtwvjJnvDB4N;Ib6}fC1k{;T0TA@ayUS08wm1bN~PV diff --git a/smart_pantry_manager/data/user_data/pantry_azozomer743@gmail.com.xlsx b/smart_pantry_manager/data/user_data/pantry_azozomer743@gmail.com.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..09a996d917c2b4341009614a61a3ff15e85b3e90 GIT binary patch literal 5197 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 DDUk;u literal 0 HcmV?d00001 diff --git a/smart_pantry_manager/pages/recommended_recipes.py b/smart_pantry_manager/pages/1- recommended_recipes.py similarity index 98% rename from smart_pantry_manager/pages/recommended_recipes.py rename to smart_pantry_manager/pages/1- recommended_recipes.py index c1f9de4..0fbe22e 100644 --- a/smart_pantry_manager/pages/recommended_recipes.py +++ b/smart_pantry_manager/pages/1- recommended_recipes.py @@ -24,7 +24,10 @@ username = st.session_state["username"] USER_FILE = os.path.join( - "smart_pantry_manager", "data", f"pantry_{username.replace(' ', '_').lower()}.xlsx" + "smart_pantry_manager", + "data", + "user_data", + f"pantry_{username.replace(' ', '_').lower()}.xlsx", ) # ---------- Load pantry ---------- diff --git a/smart_pantry_manager/pages/all_recipes.py b/smart_pantry_manager/pages/2- all_recipes.py similarity index 88% rename from smart_pantry_manager/pages/all_recipes.py rename to smart_pantry_manager/pages/2- all_recipes.py index 3e34ff2..b7e12bd 100644 --- a/smart_pantry_manager/pages/all_recipes.py +++ b/smart_pantry_manager/pages/2- all_recipes.py @@ -48,6 +48,16 @@ def load_recipes(): st.info("No recipes found.") st.stop() +# ---------- Search Recipes ---------- +search_query = st.text_input("๐Ÿ” Search recipes by title:", "") +if search_query: + # Filter recipes where the title contains the typed text (case-insensitive) + recipes = recipes[recipes["Title"].str.contains(search_query, case=False, na=False)] + +if recipes.empty: + st.info("No recipes match your search.") + st.stop() + # ---------- Parse Ingredients ---------- def parse_ingredients(ingredients_str): diff --git a/smart_pantry_manager/requirements.txt b/smart_pantry_manager/requirements.txt index 09aa0c5..5d7b81b 100644 --- a/smart_pantry_manager/requirements.txt +++ b/smart_pantry_manager/requirements.txt @@ -1,4 +1,3 @@ pandas streamlit openpyxl -sqlite3 diff --git a/smart_pantry_manager/smart_pantry.py b/smart_pantry_manager/smart_pantry.py index 21ffdf6..c0d3129 100644 --- a/smart_pantry_manager/smart_pantry.py +++ b/smart_pantry_manager/smart_pantry.py @@ -1,16 +1,16 @@ -# spell-checker: disable """ smart_pantry.py Smart Pantry Web App (Home Page Only) Features: -- Personal pantry for each user (saved to Excel) -- Add, edit, and track products with expiry alerts -- Quantity + unit input (numeric or count) -- Small loading animation when saving -- Optional intro/demo video +- Personal pantry for each user, stored in Excel +- Add, edit, and track products with automatic expiry alerts +- Support for numeric quantities and units (e.g., g, kg, ml, count) +- Visual indicators for soon-to-expire and expired items +- Optional loading animation when saving +- Optional demo video to guide new users -Date: 29/10/2025 +Date: 27/11/2025 """ import os @@ -20,6 +20,26 @@ import pandas as pd import streamlit as st + +# ----- Styling Functions ----- +def style_expired(expired_products_df): + return expired_products_df.style.set_properties( + **{ + "background-color": "#EB4343", # Light Red + "color": "black", + } + ) + + +def style_expiring_soon(expiring_products_df): + return expiring_products_df.style.set_properties( + **{ + "background-color": "#F5DB76", # Light Yellow + "color": "black", + } + ) + + # ---------- Page Setup ---------- st.set_page_config(page_title="Smart Pantry Manager", page_icon="๐Ÿงบ", layout="centered") @@ -37,22 +57,23 @@ st.session_state["username"] = username # Create user-specific file path -os.makedirs("smart_pantry_manager/data", exist_ok=True) -USER_FILE = os.path.join( - "smart_pantry_manager", "data", f"pantry_{username.replace(' ', '_').lower()}.xlsx" -) +os.makedirs("smart_pantry_manager/data/user_data", exist_ok=True) +USER_FILE = f"smart_pantry_manager/data/user_data/pantry_{username.replace(' ', '_').lower()}.xlsx" # ---------- Load Pantry ---------- @st.cache_data def load_pantry(file_path): - """Load pantry data or create empty DataFrame.""" + """Load pantry data for a specific user or create empty table.""" try: df = pd.read_excel(file_path) df["Expiry Date"] = pd.to_datetime(df["Expiry Date"], errors="coerce") df["Days Left"] = (df["Expiry Date"] - datetime.now()).dt.days - df = df[df["Days Left"] >= 0].reset_index(drop=True) + + # Remove expired items automatically + # df = df[df["Days Left"] >= 0].reset_index(drop=True) return df + except FileNotFoundError: return pd.DataFrame( columns=[ @@ -66,12 +87,17 @@ def load_pantry(file_path): ) -data = load_pantry(USER_FILE) +pantry_df = load_pantry(USER_FILE) + +# Store in session_state +if "pantry_data" not in st.session_state: + st.session_state["pantry_data"] = pantry_df # ---------- Add New Product ---------- st.header("โž• Add a New Product") -product = st.text_input("Product name:") -category = st.selectbox( + +product_name = st.text_input("Product name:") +product_category = st.selectbox( "Category:", [ "Uncategorized", @@ -89,57 +115,135 @@ def load_pantry(file_path): "Frozen Foods", "Canned Goods", "Spices & Seasonings", + "Milks & Alternatives", "Drinks", ], ) -quantity = st.number_input("Quantity (numeric or count):", min_value=0.0, step=0.1) -unit = st.selectbox("Unit:", ["count", "g", "kg", "ml", "L", "cup", "tbsp", "tsp"]) -expiry = st.date_input("Expiry date:") +product_quantity = st.number_input( + "Quantity (numeric or count):", min_value=0.0, step=0.1 +) +product_unit = st.selectbox( + "Unit:", ["count", "g", "kg", "ml", "L", "cup", "tbsp", "tsp"] +) +expiry_date = st.date_input("Expiry date:") if st.button("๐Ÿ’พ Save product"): - if product: + if product_name: with st.spinner("๐Ÿ’พ Saving product... please wait..."): - time.sleep(1) + time.sleep(2) today = datetime.now().date() - days_left = (expiry - today).days - new_row = { - "Product": product, - "Category": category, - "Quantity": quantity, - "Unit": unit, - "Expiry Date": expiry, + days_left = (expiry_date - today).days + new_product = { + "Product": product_name, + "Category": product_category, + "Quantity": product_quantity, + "Unit": product_unit, + "Expiry Date": expiry_date, "Days Left": days_left, } - data = pd.concat([data, pd.DataFrame([new_row])], ignore_index=True) - data.to_excel(USER_FILE, index=False) + pantry_df = st.session_state["pantry_data"] + pantry_df = pd.concat( + [pantry_df, pd.DataFrame([new_product])], ignore_index=True + ) + + # Sort by Days Left + pantry_df = pantry_df.sort_values( + by="Days Left", ascending=True + ).reset_index(drop=True) + pantry_df.to_excel(USER_FILE, index=False) + st.session_state["pantry_data"] = pantry_df st.cache_data.clear() - st.success(f"โœ… {product} added successfully!") + st.success(f"โœ… {product_name} added successfully!") else: st.warning("Please enter a product name.") # ---------- Update Days Left ---------- -if not data.empty: - data["Expiry Date"] = pd.to_datetime(data["Expiry Date"], errors="coerce") +if not st.session_state["pantry_data"].empty: + pantry_df = st.session_state["pantry_data"] + pantry_df["Expiry Date"] = pd.to_datetime(pantry_df["Expiry Date"], errors="coerce") today = pd.Timestamp(datetime.now().date()) - data["Days Left"] = (data["Expiry Date"] - today).dt.days + pantry_df["Days Left"] = (pantry_df["Expiry Date"] - today).dt.days + st.session_state["pantry_data"] = pantry_df # ---------- Alerts ---------- -st.header("โš ๏ธ Expiry Alerts") -if not data.empty: - expired = data[data["Days Left"] < 0] - expiring_soon = data[(data["Days Left"] >= 0) & (data["Days Left"] <= 3)] - if not expired.empty: - st.error("โŒ Some products have expired:") - st.table(expired[["Product", "Expiry Date", "Days Left"]]) - if not expiring_soon.empty: - st.warning("โฐ Some products are expiring soon:") - st.table(expiring_soon[["Product", "Expiry Date", "Days Left"]]) +st.markdown("### โš ๏ธ Expiry Alerts") + +current_df = st.session_state["pantry_data"] # renamed + +if not current_df.empty: + expired_items = current_df[current_df["Days Left"] < 0].copy() # renamed + expired_items = expired_items.sort_values(by="Days Left") + expiring_items = current_df[ + (current_df["Days Left"] >= 0) & (current_df["Days Left"] <= 3) + ].copy() # renamed + + # ---------- Expired Products ---------- + if not expired_items.empty: + with st.expander("โŒ Expired Products (Click to view)", expanded=True): + st.dataframe( + style_expired(expired_items[["Product", "Expiry Date", "Days Left"]]), + use_container_width=True, + ) + col1, col2 = st.columns([2, 1]) + if col2.button("๐Ÿ—‘๏ธ Delete ALL expired"): + current_df = current_df[current_df["Days Left"] >= 0] + st.session_state["pantry_data"] = current_df + current_df.to_excel(USER_FILE, index=False) + + st.success("All expired products deleted!") + + # ---------- Expiring Soon ---------- + if not expiring_items.empty: + with st.expander("โฐ Expiring Soon (Within 3 days)", expanded=True): + st.dataframe( + style_expiring_soon( + expiring_items[["Product", "Expiry Date", "Days Left"]] + ), + use_container_width=True, + ) + + # ---------- Manage Section ---------- + st.markdown("---") + st.markdown("### ๐Ÿ›  Manage Products") + + colA, colB = st.columns([2, 3]) + selected_item = colA.selectbox("Choose a product:", current_df["Product"].unique()) + + if selected_item: + item_row = current_df[current_df["Product"] == selected_item].iloc[0] + colB.markdown(f"**Current:** {item_row['Quantity']} {item_row['Unit']}") + + new_quantity = colB.number_input( + "New quantity:", + min_value=0.0, + max_value=float(item_row["Quantity"]), + value=float(item_row["Quantity"]), + step=0.1, + ) + + col1, col2 = st.columns([1, 1]) + if col1.button("โœ”๏ธ Update Quantity"): + current_df.loc[current_df["Product"] == selected_item, "Quantity"] = ( + new_quantity + ) + st.session_state["pantry_data"] = current_df + current_df.to_excel(USER_FILE, index=False) + st.success("Quantity updated!") + + if col2.button("๐Ÿ—‘๏ธ Delete Product"): + current_df = current_df[current_df["Product"] != selected_item] + st.session_state["pantry_data"] = current_df + current_df.to_excel(USER_FILE, index=False) + st.success(f"{selected_item} deleted!") + +st.markdown("---") # ---------- Pantry Table ---------- st.header("๐Ÿ“ฆ Your Pantry Items") def color_days(val): + """Color based on days left.""" if val < 0: color = "#ff4d4d" elif val <= 3: @@ -149,15 +253,21 @@ def color_days(val): return f"background-color: {color}; color: black;" -if not data.empty: - # Sort by Days Left ascending - data_sorted = data.sort_values("Days Left").reset_index(drop=True) - styled_data = data_sorted.style.applymap(color_days, subset=["Days Left"]) +if not pantry_df.empty: + display_data = pantry_df.sort_values(by="Days Left", ascending=True) + styled_data = display_data.reset_index(drop=True).style.applymap( + color_days, subset=["Days Left"] + ) st.dataframe(styled_data, use_container_width=True) else: st.info("Your pantry is empty. Add your first product above!") # ---------- Manual Save ---------- if st.button("๐Ÿ”„ Save Changes"): - data.to_excel(USER_FILE, index=False) - st.success("Pantry data saved successfully!") + if not pantry_df.empty: + pantry_df_sorted = pantry_df.sort_values(by="Days Left", ascending=True) + pantry_df_sorted.to_excel(USER_FILE, index=False) + st.session_state["pantry_data"] = pantry_df_sorted + st.success("Pantry data saved successfully (sorted by expiry)!") + else: + st.info("No items to save.") diff --git a/the_app/data/pantry_omnia.xlsx b/the_app/data/pantry_omnia.xlsx deleted file mode 100644 index d5664c2fe2f60b7f8b9a6a5abe439ad8723f2ba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5155 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 Mrh=n>321=-0Q#`(CjbBd