From 1b9d3ccc83032712857b8c39896f24e95c4e4c57 Mon Sep 17 00:00:00 2001 From: Anju Date: Wed, 5 Feb 2025 16:07:22 +0530 Subject: [PATCH 1/3] Fix tkinter asksaveasfilename to auto-append file extension if not provided --- Lib/tkinter/filedialog.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/filedialog.py b/Lib/tkinter/filedialog.py index e2eff98e601c07..bacf143853c101 100644 --- a/Lib/tkinter/filedialog.py +++ b/Lib/tkinter/filedialog.py @@ -385,9 +385,27 @@ def askopenfilename(**options): def asksaveasfilename(**options): - "Ask for a filename to save as" + """Ask for a filename to save as""" + + # If no default extension is provided, set it to the first filetype + if "defaultextension" not in options: + filetypes = options.get("filetypes") + if filetypes: + first_ext = filetypes[0][1] # Get "*.txt" from ("Text files", "*.txt") + if first_ext.startswith("*"): + options["defaultextension"] = first_ext[1:] # ".txt" + + filename = SaveAs(**options).show() + + # Append extension if missing + if filename and '.' not in filename: + ext = options.get("defaultextension", "") + if ext and not filename.endswith(ext): + filename += ext + + return filename + - return SaveAs(**options).show() def askopenfilenames(**options): From 24db516f7f06f6c7eeb2ff9bf47867a922593353 Mon Sep 17 00:00:00 2001 From: Anju Date: Thu, 6 Feb 2025 18:01:02 +0530 Subject: [PATCH 2/3] Improved asksaveasfilename extension handling based on review feedback --- Lib/tkinter/filedialog.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Lib/tkinter/filedialog.py b/Lib/tkinter/filedialog.py index bacf143853c101..ec6c3a68510360 100644 --- a/Lib/tkinter/filedialog.py +++ b/Lib/tkinter/filedialog.py @@ -385,29 +385,37 @@ def askopenfilename(**options): def asksaveasfilename(**options): - """Ask for a filename to save as""" + """Ask for a filename to save as with improved extension handling.""" - # If no default extension is provided, set it to the first filetype - if "defaultextension" not in options: - filetypes = options.get("filetypes") - if filetypes: - first_ext = filetypes[0][1] # Get "*.txt" from ("Text files", "*.txt") - if first_ext.startswith("*"): - options["defaultextension"] = first_ext[1:] # ".txt" + # Ensure "filetypes" is present + filetypes = options.get("filetypes", []) + # If no default extension is provided, try setting it to the first filetype + if "defaultextension" not in options and filetypes: + first_ext = filetypes[0][1] # Extract "*.txt" from ("Text files", "*.txt") + if first_ext.startswith("*.") and len(first_ext) > 2: + options["defaultextension"] = first_ext[1:] # Convert to ".txt" + + # Show the save dialog filename = SaveAs(**options).show() - # Append extension if missing - if filename and '.' not in filename: - ext = options.get("defaultextension", "") - if ext and not filename.endswith(ext): - filename += ext + if filename: + # Extract the directory and the base filename + dir_path, base_name = os.path.split(filename) + + # Check if the filename has an extension + if '.' not in base_name: + ext = options.get("defaultextension", "") + # Ensure the default extension is properly formatted and append if needed + if ext and not filename.endswith(ext): + filename = os.path.join(dir_path, base_name + ext) return filename + def askopenfilenames(**options): """Ask for multiple filenames to open From c4366df79a175b2509111c235c2bfeff164ed1a2 Mon Sep 17 00:00:00 2001 From: Anju Date: Thu, 6 Feb 2025 18:10:35 +0530 Subject: [PATCH 3/3] Improved asksaveasfilename extension handling based on review feedback --- Lib/tkinter/filedialog.py | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Lib/tkinter/filedialog.py b/Lib/tkinter/filedialog.py index ec6c3a68510360..6301b8e21b5d70 100644 --- a/Lib/tkinter/filedialog.py +++ b/Lib/tkinter/filedialog.py @@ -384,31 +384,36 @@ def askopenfilename(**options): return Open(**options).show() -def asksaveasfilename(**options): - """Ask for a filename to save as with improved extension handling.""" - # Ensure "filetypes" is present - filetypes = options.get("filetypes", []) - # If no default extension is provided, try setting it to the first filetype - if "defaultextension" not in options and filetypes: - first_ext = filetypes[0][1] # Extract "*.txt" from ("Text files", "*.txt") - if first_ext.startswith("*.") and len(first_ext) > 2: - options["defaultextension"] = first_ext[1:] # Convert to ".txt" +def asksaveasfilename(**options): + """Ask for a filename to save as, handling missing extensions properly.""" + + # If no default extension is provided, set it to the first valid filetype extension + if "defaultextension" not in options: + filetypes = options.get("filetypes") + if filetypes: + first_ext = filetypes[0][1] # Example: "*.txt" from ("Text files", "*.txt") + + # Validate first_ext: Must start with '*' and contain only one '.' + if first_ext.startswith("*") and first_ext.count(".") == 1: + ext = first_ext[1:] # Extract ".txt" + + # Ensure the extracted extension is a valid one (not patterns like "*.a[0-9]") + if ext.isalnum() or (ext.startswith(".") and ext[1:].isalnum()): + options["defaultextension"] = ext # Set default extension - # Show the save dialog filename = SaveAs(**options).show() + # Append extension if missing, but allow extensionless filenames if filename: - # Extract the directory and the base filename - dir_path, base_name = os.path.split(filename) - - # Check if the filename has an extension - if '.' not in base_name: - ext = options.get("defaultextension", "") - # Ensure the default extension is properly formatted and append if needed - if ext and not filename.endswith(ext): - filename = os.path.join(dir_path, base_name + ext) + # Ensure filename is not a relative path ("../", "./", "/absolute/path") + if not os.path.isabs(filename) and not filename.startswith((".", "..")): + # Check if there's no explicit extension + if '.' not in os.path.basename(filename): + ext = options.get("defaultextension", "") + if ext and not filename.endswith(ext): + filename += ext return filename