Command-line Interface Tool: File Creation tool
In this project, we’ll build a command-line tool that creates new files and directories. The tool will check if a file already exists before creating it, create any missing parent directories if needed, and handle errors appropriately by sending messages to STDERR.
Project Overview
Our file creation tool will:
- Accept file paths as command-line arguments
- Create parent directories if they don’t exist
- Create the specified file if it doesn’t exist
- Output error messages to STDERR if the file already exists
- Provide options for handling existing files (overwrite, append, etc.)
- Include helpful usage information
This project demonstrates several important Python concepts:
- Command-line argument parsing
- File and directory operations
- Error handling
- Standard output and error streams
- Creating a reusable command-line tool
Step 1: Setting Up the Basic Structure
First, let’s create a basic script that can accept command-line arguments:
#!/usr/bin/env python3
"""
File Creation Tool - A utility to create files and directories.
Usage:
filecreate.py <filepath> [<filepath> ...]
filecreate.py -h | --help
"""
import sys
import os
def main():
"""Main function to process command-line arguments and create files."""
# Skip the script name (first argument)
args = sys.argv[1:]
if not args or "-h" in args or "--help" in args:
print(__doc__)
return 0
# Process each file path
for filepath in args:
create_file(filepath)
return 0
def create_file(filepath):
"""Create a file at the specified path."""
print(f"Creating file: {filepath}")
# Actual file creation will be implemented here
if __name__ == "__main__":
sys.exit(main())
This code sets up a basic command-line interface that accepts file paths as arguments and provides help information when requested.
Step 2: Implementing File Creation Logic
Now, let’s implement the file creation logic, including directory creation if needed:
def create_file(filepath):
"""
Create a file at the specified path.
Args:
filepath (str): Path to the file to create
Returns:
bool: True if successful, False otherwise
"""
try:
# Check if the file already exists
if os.path.exists(filepath):
sys.stderr.write(f"Error: File '{filepath}' already exists.\n")
return False
# Get the directory part of the path
directory = os.path.dirname(filepath)
# Create the directory if it doesn't exist
if directory and not os.path.exists(directory):
print(f"Creating directory: {directory}")
os.makedirs(directory)
# Create the file (by opening it in write mode and then closing it)
with open(filepath, 'w') as f:
pass
print(f"Successfully created file: {filepath}")
return True
except OSError as e:
sys.stderr.write(f"Error creating file '{filepath}': {e}\n")
return False
This function:
- Checks if the file already exists and outputs an error message to STDERR if it does
- Gets the directory part of the path
- Creates any missing directories using
os.makedirs()
- Creates the file by opening it in write mode and immediately closing it
- Handles any OS errors that might occur during the process
Step 3: Adding Options for Handling Existing Files
Let’s enhance our tool to handle existing files according to user preferences:
#!/usr/bin/env python3
"""
File Creation Tool - A utility to create files and directories.
Usage:
filecreate.py [options] <filepath> [<filepath> ...]
filecreate.py -h | --help
Options:
-f, --force Overwrite existing files
-a, --append Append to existing files
-s, --skip Skip existing files (default)
-h, --help Show this help message
"""
import sys
import os
import getopt
def main():
"""Main function to process command-line arguments and create files."""
# Default options
force = False
append = False
try:
# Parse command-line options
opts, args = getopt.getopt(sys.argv[1:], "hfas", ["help", "force", "append", "skip"])
except getopt.GetoptError as e:
sys.stderr.write(f"Error: {e}\n")
print(__doc__)
return 1
# Process options
for opt, _ in opts:
if opt in ("-h", "--help"):
print(__doc__)
return 0
elif opt in ("-f", "--force"):
force = True
append = False
elif opt in ("-a", "--append"):
append = True
force = False
# Check if there are file paths provided
if not args:
sys.stderr.write("Error: No file paths provided.\n")
print(__doc__)
return 1
# Process each file path
success_count = 0
for filepath in args:
if create_file(filepath, force=force, append=append):
success_count += 1
# Report results
total = len(args)
print(f"\nSummary: {success_count} of {total} files processed successfully.")
return 0 if success_count == total else 1
def create_file(filepath, force=False, append=False):
"""
Create a file at the specified path.
Args:
filepath (str): Path to the file to create
force (bool): Whether to overwrite an existing file
append (bool): Whether to append to an existing file
Returns:
bool: True if successful, False otherwise
"""
try:
# Check if the file already exists
if os.path.exists(filepath):
if force:
mode = 'w'
print(f"Overwriting file: {filepath}")
elif append:
mode = 'a'
print(f"Appending to file: {filepath}")
else:
sys.stderr.write(f"Error: File '{filepath}' already exists. Use -f to overwrite or -a to append.\n")
return False
else:
mode = 'w'
# Get the directory part of the path
directory = os.path.dirname(filepath)
# Create the directory if it doesn't exist
if directory and not os.path.exists(directory):
print(f"Creating directory: {directory}")
os.makedirs(directory)
# Create or modify the file
with open(filepath, mode) as f:
pass
if not os.path.exists(filepath):
print(f"Successfully created file: {filepath}")
else:
if force:
print(f"Successfully overwrote file: {filepath}")
elif append:
print(f"Successfully appended to file: {filepath}")
return True
except OSError as e:
sys.stderr.write(f"Error processing file '{filepath}': {e}\n")
return False
if __name__ == "__main__":
sys.exit(main())
We’ve added:
- Command-line options using
getopt
- Options for different behaviors when a file already exists:
-f
/--force
: Overwrite existing files-a
/--append
: Append to existing files-s
/--skip
: Skip existing files (default)
- A summary of the results after processing all files
Step 4: Adding Content to Created Files
Let’s enhance our tool to allow users to specify content for the files:
#!/usr/bin/env python3
"""
File Creation Tool - A utility to create files and directories.
Usage:
filecreate.py [options] <filepath> [<filepath> ...]
filecreate.py -h | --help
Options:
-f, --force Overwrite existing files
-a, --append Append to existing files
-s, --skip Skip existing files (default)
-c, --content=TEXT Add specified text to the file
-i, --interactive Prompt for content interactively
-h, --help Show this help message
"""
import sys
import os
import getopt
def main():
"""Main function to process command-line arguments and create files."""
# Default options
force = False
append = False
content = None
interactive = False
try:
# Parse command-line options
opts, args = getopt.getopt(
sys.argv[1:],
"hfasc:i",
["help", "force", "append", "skip", "content=", "interactive"]
)
except getopt.GetoptError as e:
sys.stderr.write(f"Error: {e}\n")
print(__doc__)
return 1
# Process options
for opt, arg in opts:
if opt in ("-h", "--help"):
print(__doc__)
return 0
elif opt in ("-f", "--force"):
force = True
append = False
elif opt in ("-a", "--append"):
append = True
force = False
elif opt in ("-c", "--content"):
content = arg
elif opt in ("-i", "--interactive"):
interactive = True
# Check if there are file paths provided
if not args:
sys.stderr.write("Error: No file paths provided.\n")
print(__doc__)
return 1
# If interactive mode is enabled, prompt for content
if interactive and content is None:
print("Enter content (press Ctrl+D on Unix/Linux or Ctrl+Z on Windows when finished):")
content_lines = []
try:
while True:
line = input()
content_lines.append(line)
except EOFError:
# End of input
content = "\n".join(content_lines)
print("\nContent captured.")
# Process each file path
success_count = 0
for filepath in args:
if create_file(filepath, force=force, append=append, content=content):
success_count += 1
# Report results
total = len(args)
print(f"\nSummary: {success_count} of {total} files processed successfully.")
return 0 if success_count == total else 1
def create_file(filepath, force=False, append=False, content=None):
"""
Create a file at the specified path.
Args:
filepath (str): Path to the file to create
force (bool): Whether to overwrite an existing file
append (bool): Whether to append to an existing file
content (str): Optional content to write to the file
Returns:
bool: True if successful, False otherwise
"""
try:
# Check if the file already exists
if os.path.exists(filepath):
if force:
mode = 'w'
print(f"Overwriting file: {filepath}")
elif append:
mode = 'a'
print(f"Appending to file: {filepath}")
else:
sys.stderr.write(f"Error: File '{filepath}' already exists. Use -f to overwrite or -a to append.\n")
return False
else:
mode = 'w'
# Get the directory part of the path
directory = os.path.dirname(filepath)
# Create the directory if it doesn't exist
if directory and not os.path.exists(directory):
print(f"Creating directory: {directory}")
os.makedirs(directory)
# Create or modify the file
with open(filepath, mode) as f:
if content is not None:
f.write(content)
if not content.endswith('\n'):
f.write('\n')
if not os.path.exists(filepath):
print(f"Successfully created file: {filepath}")
else:
if force:
print(f"Successfully overwrote file: {filepath}")
elif append:
print(f"Successfully appended to file: {filepath}")
return True
except OSError as e:
sys.stderr.write(f"Error processing file '{filepath}': {e}\n")
return False
if __name__ == "__main__":
sys.exit(main())
We’ve added:
- The
-c
/--content
option to specify content directly - The
-i
/--interactive
option to enter content interactively - Logic to write the specified content to the file
Step 5: Implementing Advanced Error Handling
Let’s enhance our error handling to be more robust and informative:
import os
import sys
import getopt
import errno
class FileCreationError(Exception):
"""Custom exception for file creation errors."""
pass
def create_file(filepath, force=False, append=False, content=None):
"""
Create a file at the specified path.
Args:
filepath (str): Path to the file to create
force (bool): Whether to overwrite an existing file
append (bool): Whether to append to an existing file
content (str): Optional content to write to the file
Returns:
bool: True if successful, False otherwise
Raises:
FileCreationError: If the file cannot be created for any reason
"""
try:
# Check if the file already exists
if os.path.exists(filepath):
if os.path.isdir(filepath):
raise FileCreationError(f"Path '{filepath}' is a directory, not a file")
if force:
mode = 'w'
action = "Overwriting"
elif append:
mode = 'a'
action = "Appending to"
else:
raise FileCreationError(f"File '{filepath}' already exists. Use -f to overwrite or -a to append.")
else:
mode = 'w'
action = "Creating"
# Get the directory part of the path
directory = os.path.dirname(filepath)
# Create the directory if it doesn't exist
if directory and not os.path.exists(directory):
try:
print(f"Creating directory: {directory}")
os.makedirs(directory)
except OSError as e:
if e.errno == errno.EACCES:
raise FileCreationError(f"Permission denied to create directory '{directory}'")
else:
raise FileCreationError(f"Cannot create directory '{directory}': {e}")
try:
# Create or modify the file
with open(filepath, mode) as f:
if content is not None:
f.write(content)
if not content.endswith('\n'):
f.write('\n')
except IOError as e:
if e.errno == errno.EACCES:
raise FileCreationError(f"Permission denied to write to '{filepath}'")
else:
raise FileCreationError(f"Cannot write to '{filepath}': {e}")
print(f"{action} file: {filepath}")
return True
except FileCreationError as e:
sys.stderr.write(f"Error: {e}\n")
return False
except Exception as e:
sys.stderr.write(f"Unexpected error processing '{filepath}': {e}\n")
return False
We’ve improved the error handling by:
- Creating a custom exception class for file creation errors
- Checking if the path is a directory before trying to create a file
- Providing more specific error messages for common issues like permission denied
- Using the
errno
module to check for specific error types
Step 6: Completing the Tool with Verbosity Levels
Let’s add verbosity levels to make the tool more user-friendly:
#!/usr/bin/env python3
"""
File Creation Tool - A utility to create files and directories.
Usage:
filecreate.py [options] <filepath> [<filepath> ...]
filecreate.py -h | --help
Options:
-f, --force Overwrite existing files
-a, --append Append to existing files
-s, --skip Skip existing files (default)
-c, --content=TEXT Add specified text to the file
-i, --interactive Prompt for content interactively
-v, --verbose Increase output verbosity
-q, --quiet Suppress output except for errors
-h, --help Show this help message
"""
import os
import sys
import getopt
import errno
class FileCreationError(Exception):
"""Custom exception for file creation errors."""
pass
def main():
"""Main function to process command-line arguments and create files."""
# Default options
force = False
append = False
content = None
interactive = False
verbose = 1 # Default verbosity level (0=quiet, 1=normal, 2=verbose)
try:
# Parse command-line options
opts, args = getopt.getopt(
sys.argv[1:],
"hfasc:ivq",
["help", "force", "append", "skip", "content=", "interactive", "verbose", "quiet"]
)
except getopt.GetoptError as e:
sys.stderr.write(f"Error: {e}\n")
print(__doc__)
return 1
# Process options
for opt, arg in opts:
if opt in ("-h", "--help"):
print(__doc__)
return 0
elif opt in ("-f", "--force"):
force = True
append = False
elif opt in ("-a", "--append"):
append = True
force = False
elif opt in ("-c", "--content"):
content = arg
elif opt in ("-i", "--interactive"):
interactive = True
elif opt in ("-v", "--verbose"):
verbose = 2
elif opt in ("-q", "--quiet"):
verbose = 0
# Check if there are file paths provided
if not args:
sys.stderr.write("Error: No file paths provided.\n")
print(__doc__)
return 1
# If interactive mode is enabled, prompt for content
if interactive and content is None:
print("Enter content (press Ctrl+D on Unix/Linux or Ctrl+Z on Windows when finished):")
content_lines = []
try:
while True:
line = input()
content_lines.append(line)
except EOFError:
# End of input
content = "\n".join(content_lines)
if verbose > 0:
print("\nContent captured.")
# Process each file path
success_count = 0
for filepath in args:
if create_file(filepath, force=force, append=append, content=content, verbose=verbose):
success_count += 1
# Report results
total = len(args)
if verbose > 0:
print(f"\nSummary: {success_count} of {total} files processed successfully.")
return 0 if success_count == total else 1
def create_file(filepath, force=False, append=False, content=None, verbose=1):
"""
Create a file at the specified path.
Args:
filepath (str): Path to the file to create
force (bool): Whether to overwrite an existing file
append (bool): Whether to append to an existing file
content (str): Optional content to write to the file
verbose (int): Verbosity level (0=quiet, 1=normal, 2=verbose)
Returns:
bool: True if successful, False otherwise
"""
try:
# Normalize and expand the file path
filepath = os.path.abspath(os.path.expanduser(filepath))
if verbose > 1:
print(f"Processing file: {filepath}")
# Check if the file already exists
if os.path.exists(filepath):
if os.path.isdir(filepath):
raise FileCreationError(f"Path '{filepath}' is a directory, not a file")
if force:
mode = 'w'
action = "Overwriting"
elif append:
mode = 'a'
action = "Appending to"
else:
raise FileCreationError(f"File '{filepath}' already exists. Use -f to overwrite or -a to append.")
else:
mode = 'w'
action = "Creating"
# Get the directory part of the path
directory = os.path.dirname(filepath)
# Create the directory if it doesn't exist
if directory and not os.path.exists(directory):
try:
if verbose > 0:
print(f"Creating directory: {directory}")
os.makedirs(directory)
except OSError as e:
if e.errno == errno.EACCES:
raise FileCreationError(f"Permission denied to create directory '{directory}'")
else:
raise FileCreationError(f"Cannot create directory '{directory}': {e}")
try:
# Create or modify the file
with open(filepath, mode) as f:
if content is not None:
if verbose > 1:
content_preview = content[:50] + "..." if len(content) > 50 else content
print(f"Writing content: {content_preview}")
f.write(content)
if not content.endswith('\n'):
f.write('\n')
except IOError as e:
if e.errno == errno.EACCES:
raise FileCreationError(f"Permission denied to write to '{filepath}'")
else:
raise FileCreationError(f"Cannot write to '{filepath}': {e}")
if verbose > 0:
print(f"{action} file: {filepath}")
return True
except FileCreationError as e:
sys.stderr.write(f"Error: {e}\n")
return False
except Exception as e:
sys.stderr.write(f"Unexpected error processing '{filepath}': {e}\n")
if verbose > 1:
import traceback
traceback.print_exc(file=sys.stderr)
return False
if __name__ == "__main__":
sys.exit(main())
Now our tool has:
- Verbosity levels with
-v
/--verbose
and-q
/--quiet
options - Path normalization and expansion using
os.path.abspath
andos.path.expanduser
- Detailed error messages in verbose mode, including stack traces for unexpected errors
Step 7: Testing the Tool
Let’s create a comprehensive test function to verify our tool’s functionality:
def test_file_creation():
"""Test the file creation functionality."""
import tempfile
import shutil
# Create a temporary directory for testing
test_dir = tempfile.mkdtemp()
print(f"Created test directory: {test_dir}")
try:
# Test case 1: Create a simple file
test_file1 = os.path.join(test_dir, "test1.txt")
print("\nTest 1: Creating a new file")
result = create_file(test_file1, content="Test content", verbose=2)
assert result is True
assert os.path.exists(test_file1)
# Test case 2: Attempt to create the same file (should fail)
print("\nTest 2: Creating a file that already exists (should fail)")
result = create_file(test_file1, content="Different content", verbose=2)
assert result is False
# Test case 3: Force overwrite an existing file
print("\nTest 3: Force overwriting an existing file")
result = create_file(test_file1, force=True, content="Overwritten content", verbose=2)
assert result is True
with open(test_file1, 'r') as f:
content = f.read().strip()
assert content == "Overwritten content"
# Test case 4: Append to an existing file
print("\nTest 4: Appending to an existing file")
result = create_file(test_file1, append=True, content="Additional content", verbose=2)
assert result is True
with open(test_file1, 'r') as f:
content = f.read().strip()
assert content == "Overwritten content\nAdditional content"
# Test case 5: Create a file in a non-existent directory
print("\nTest 5: Creating a file in a new directory")
nested_dir = os.path.join(test_dir, "nested", "path")
test_file2 = os.path.join(nested_dir, "test2.txt")
result = create_file(test_file2, content="Nested file content", verbose=2)
assert result is True
assert os.path.exists(test_file2)
# Test case 6: Try to create a file that's actually a directory
print("\nTest 6: Trying to create a file with the same name as a directory (should fail)")
result = create_file(nested_dir, content="This should fail", verbose=2)
assert result is False
print("\nAll tests passed!")
return True
except AssertionError as e:
print(f"Test failed: {e}")
return False
finally:
# Clean up the temporary directory
print(f"\nCleaning up test directory: {test_dir}")
shutil.rmtree(test_dir)
# Add this at the end of the main() function to run tests
if "--test" in sys.argv:
test_file_creation()
return 0
This test function:
- Creates a temporary directory for testing
- Tests various scenarios like creating, overwriting, and appending to files
- Verifies that directories are created when needed
- Checks error conditions like trying to create a file with the same name as a directory
- Cleans up after itself by removing the temporary directory
Final Complete Code
Here’s the complete code for our file creation tool:
#!/usr/bin/env python3
"""
File Creation Tool - A utility to create files and directories.
Usage:
filecreate.py [options] <filepath> [<filepath> ...]
filecreate.py --test
filecreate.py -h | --help
Options:
-f, --force Overwrite existing files
-a, --append Append to existing files
-s, --skip Skip existing files (default)
-c, --content=TEXT Add specified text to the file
-i, --interactive Prompt for content interactively
-v, --verbose Increase output verbosity
-q, --quiet Suppress output except for errors
--test Run self-test
-h, --help Show this help message
"""
import os
import sys
import getopt
import errno
import tempfile
import shutil
class FileCreationError(Exception):
"""Custom exception for file creation errors."""
pass
def main():
"""Main function to process command-line arguments and create files."""
# Default options
force = False
append = False
content = None
interactive = False
verbose = 1 # Default verbosity level (0=quiet, 1=normal, 2=verbose)
# Run self-test if requested
if "--test" in sys.argv:
return 0 if test_file_creation() else 1
try:
# Parse command-line options
opts, args = getopt.getopt(
sys.argv[1:],
"hfasc:ivq",
["help", "force", "append", "skip", "content=", "interactive", "verbose", "quiet", "test"]
)
except getopt.GetoptError as e:
sys.stderr.write(f"Error: {e}\n")
print(__doc__)
return 1
# Process options
for opt, arg in opts:
if opt in ("-h", "--help"):
print(__doc__)
return 0
elif opt in ("-f", "--force"):
force = True
append = False
elif opt in ("-a", "--append"):
append = True
force = False
elif opt in ("-c", "--content"):
content = arg
elif opt in ("-i", "--interactive"):
interactive = True
elif opt in ("-v", "--verbose"):
verbose = 2
elif opt in ("-q", "--quiet"):
verbose = 0
# Check if there are file paths provided
if not args:
sys.stderr.write("Error: No file paths provided.\n")
print(__doc__)
return 1
# If interactive mode is enabled, prompt for content
if interactive and content is None:
print("Enter content (press Ctrl+D on Unix/Linux or Ctrl+Z on Windows when finished):")
content_lines = []
try:
while True:
line = input()
content_lines.append(line)
except EOFError:
# End of input
content = "\n".join(content_lines)
if verbose > 0:
print("\nContent captured.")
# Process each file path
success_count = 0
for filepath in args:
if create_file(filepath, force=force, append=append, content=content, verbose=verbose):
success_count += 1
# Report results
total = len(args)
if verbose > 0:
print(f"\nSummary: {success_count} of {total} files processed successfully.")
return 0 if success_count == total else 1
def create_file(filepath, force=False, append=False, content=None, verbose=1):
"""
Create a file at the specified path.
Args:
filepath (str): Path to the file to create
force (bool): Whether to overwrite an existing file
append (bool): Whether to append to an existing file
content (str): Optional content to write to the file
verbose (int): Verbosity level (0=quiet, 1=normal, 2=verbose)
Returns:
bool: True if successful, False otherwise
"""
try:
# Normalize and expand the file path
filepath = os.path.abspath(os.path.expanduser(filepath))
if verbose > 1:
print(f"Processing file: {filepath}")
# Check if the file already exists
if os.path.exists(filepath):
if os.path.isdir(filepath):
raise FileCreationError(f"Path '{filepath}' is a directory, not a file")
if force:
mode = 'w'
action = "Overwriting"
elif append:
mode = 'a'
action = "Appending to"
else:
raise FileCreationError(f"File '{filepath}' already exists. Use -f to overwrite or -a to append.")
else:
mode = 'w'
action = "Creating"
# Get the directory part of the path
directory = os.path.dirname(filepath)
# Create the directory if it doesn't exist
if directory and not os.path.exists(directory):
try:
if verbose > 0:
print(f"Creating directory: {directory}")
os.makedirs(directory)
except OSError as e:
if e.errno == errno.EACCES:
raise FileCreationError(f"Permission denied to create directory '{directory}'")
else:
raise FileCreationError(f"Cannot create directory '{directory}': {e}")
try:
# Create or modify the file
with open(filepath, mode) as f:
if content is not None:
if verbose > 1:
content_preview = content[:50] + "..." if len(content) > 50 else content
print(f"Writing content: {content_preview}")
f.write(content)
if not content.endswith('\n'):
f.write('\n')
except IOError as e:
if e.errno == errno.EACCES:
raise FileCreationError(f"Permission denied to write to '{filepath}'")
else:
raise FileCreationError(f"Cannot write to '{filepath}': {e}")
if verbose > 0:
print(f"{action} file: {filepath}")
return True
except FileCreationError as e:
sys.stderr.write(f"Error: {e}\n")
return False
except Exception as e:
sys.stderr.write(f"Unexpected error processing '{filepath}': {e}\n")
if verbose > 1:
import traceback
traceback.print_exc(file=sys.stderr)
return False
def test_file_creation():
"""Test the file creation functionality."""
# Create a temporary directory for testing
test_dir = tempfile.mkdtemp()
print(f"Created test directory: {test_dir}")
try:
# Test case 1: Create a simple file
test_file1 = os.path.join(test_dir, "test1.txt")
print("\nTest 1: Creating a new file")
result = create_file(test_file1, content="Test content", verbose=2)
assert result is True
assert os.path.exists(test_file1)
# Test case 2: Attempt to create the same file (should fail)
print("\nTest 2: Creating a file that already exists (should fail)")
result = create_file(test_file1, content="Different content", verbose=2)
assert result is False
# Test case 3: Force overwrite an existing file
print("\nTest 3: Force overwriting an existing file")
result = create_file(test_file1, force=True, content="Overwritten content", verbose=2)
assert result is True
with open(test_file1, 'r') as f:
content = f.read().strip()
assert content == "Overwritten content"
# Test case 4: Append to an existing file
print("\nTest 4: Appending to an existing file")
result = create_file(test_file1, append=True, content="Additional content", verbose=2)
assert result is True
with open(test_file1, 'r') as f:
content = f.read().strip()
assert content == "Overwritten content\nAdditional content"
# Test case 5: Create a file in a non-existent directory
print("\nTest 5: Creating a file in a new directory")
nested_dir = os.path.join(test_dir, "nested", "path")
test_file2 = os.path.join(nested_dir, "test2.txt")
result = create_file(test_file2, content="Nested file content", verbose=2)
assert result is True
assert os.path.exists(test_file2)
# Test case 6: Try to create a file that's actually a directory
print("\nTest 6: Trying to create a file with the same name as a directory (should fail)")
result = create_file(nested_dir, content="This should fail", verbose=2)
assert result is False
print("\nAll tests passed!")
return True
except AssertionError as e:
print(f"Test failed: {e}")
return False
finally:
# Clean up the temporary directory
print(f"\nCleaning up test directory: {test_dir}")
shutil.rmtree(test_dir)
if __name__ == "__main__":
sys.exit(main())
How to Use the File Creation Tool
Here are some examples of how to use our file creation tool:
1. Basic Usage: Create a Single File
python filecreate.py myfile.txt
This creates myfile.txt
in the current directory if it doesn’t already exist.
2. Create Multiple Files
python filecreate.py file1.txt file2.txt file3.txt
This creates three files: file1.txt
, file2.txt
, and file3.txt
.
3. Create a File with Content
python filecreate.py --content="Hello, World!" greeting.txt
This creates greeting.txt
with the content “Hello, World!”.
4. Create a File in a New Directory
python filecreate.py new_directory/myfile.txt
This creates the directory new_directory
if it doesn’t exist and then creates myfile.txt
inside it.
5. Overwrite an Existing File
python filecreate.py --force --content="New content" existing_file.txt
This overwrites existing_file.txt
with “New content” even if the file already exists.
6. Append to an Existing File
python filecreate.py --append --content="Additional content" existing_file.txt
This adds “Additional content” to the end of existing_file.txt
if it exists.
7. Interactive Mode: Enter Content Manually
python filecreate.py --interactive myfile.txt
This prompts you to enter content for myfile.txt
. Press Ctrl+D (Unix/Linux) or Ctrl+Z followed by Enter (Windows) when you’re done entering content.
8. Increase Verbosity for More Details
python filecreate.py --verbose new_directory/myfile.txt
This shows more details about the file creation process.
9. Quiet Mode: Minimal Output
python filecreate.py --quiet myfile.txt
This creates myfile.txt
with minimal output, showing only errors if they occur.
Run Self-Tests
python filecreate.py --test
This runs the self-tests to verify that the tool is working correctly.
Understanding the Implementation
Let’s break down the key components of our file creation tool:
1. Command-Line Argument Parsing
We use the getopt
module to parse command-line arguments. This allows users to specify options and file paths when running the tool.
opts, args = getopt.getopt(
sys.argv[1:],
"hfasc:ivq",
["help", "force", "append", "skip", "content=", "interactive", "verbose", "quiet", "test"]
)
The short options (like -f
) and long options (like --force
) provide flexibility for users.
2. File Creation Logic
The core functionality is in the create_file()
function, which:
- Normalizes and expands file paths
- Checks if the file already exists
- Creates directories if needed
- Handles different modes (create, overwrite, append)
- Writes content to the file
The function returns True
if successful and False
otherwise, which allows the calling code to track successes and failures.
3. Error Handling
We use a custom exception class FileCreationError
to represent specific file creation errors. This allows us to distinguish between expected errors (like a file already existing) and unexpected errors.
class FileCreationError(Exception):
"""Custom exception for file creation errors."""
pass
We catch and handle errors at multiple levels:
- Permission errors when creating directories or writing files
- Path validation errors (like trying to create a file with the same name as a directory)
- Unexpected errors with traceback information in verbose mode
4. Verbosity Levels
We implement three verbosity levels to control the amount of output:
- Quiet (0): Only show errors
- Normal (1): Show important actions and summaries
- Verbose (2): Show detailed information and previews of content
This allows users to choose the level of detail that suits their needs.
5. Testing
The test_file_creation()
function verifies that the tool behaves as expected in various scenarios. It uses a temporary directory for testing and cleans up afterward, ensuring that tests don’t leave files on the user’s system.
Extending the Tool
Here are some ways you could extend this tool for more advanced use cases:
1. Template Support
Add support for file templates:
# Example implementation
def load_template(template_name):
"""Load a template from a templates directory."""
template_dir = os.path.join(os.path.dirname(__file__), "templates")
template_path = os.path.join(template_dir, template_name)
if not os.path.exists(template_path):
raise FileCreationError(f"Template '{template_name}' not found")
with open(template_path, 'r') as f:
return f.read()
# Add a command-line option
elif opt in ("-t", "--template"):
try:
content = load_template(arg)
except FileCreationError as e:
sys.stderr.write(f"Error: {e}\n")
return 1
2. File Permissions
Add support for setting file permissions:
# Add a command-line option
elif opt in ("--chmod"):
try:
chmod_mode = int(arg, 8) # Parse octal number
except ValueError:
sys.stderr.write(f"Error: Invalid chmod mode '{arg}'\n")
return 1
# Add to the create_file function
if 'chmod_mode' in locals():
try:
os.chmod(filepath, chmod_mode)
if verbose > 1:
print(f"Changed permissions of '{filepath}' to {oct(chmod_mode)[2:]}")
except OSError as e:
raise FileCreationError(f"Cannot change permissions of '{filepath}': {e}")
3. Batch Processing from a File List
Add support for reading file paths from a list file:
# Add a command-line option
elif opt in ("--file-list"):
try:
with open(arg, 'r') as f:
file_list = [line.strip() for line in f if line.strip()]
args.extend(file_list)
except IOError as e:
sys.stderr.write(f"Error reading file list '{arg}': {e}\n")
return 1
4. Backup Existing Files
Add support for backing up existing files before overwriting them:
# Add a command-line option
elif opt in ("--backup"):
backup = True
# Add to the create_file function
if os.path.exists(filepath) and 'backup' in locals() and backup:
backup_path = filepath + ".bak"
try:
shutil.copy2(filepath, backup_path)
if verbose > 0:
print(f"Created backup: {backup_path}")
except IOError as e:
raise FileCreationError(f"Cannot create backup of '{filepath}': {e}")
Conclusion
We’ve built a robust command-line tool for file creation that:
- Creates files and directories as needed
- Handles existing files with options to overwrite, append, or skip
- Supports adding content from command-line arguments or interactive input
- Provides clear error messages with appropriate exit codes
- Includes comprehensive testing
- Offers different verbosity levels for user feedback
This project demonstrates many important Python concepts:
- Command-line argument parsing
- File and directory operations
- Error handling and custom exceptions
- User interaction
- Testing and validation
The tool serves as a practical example of how Python can be used to create useful utilities that interact with the file system in a safe and user-friendly way.
Exercises
Exercise 1: Add a --recursive
option that creates recursively missing directories from the file path. For example, filecreate.py --recursive dir/dir_1/file.txt
would create file in the missing dir_1
directory
Exercise 2: Implement a feature that allows specifying content from a file using a syntax like --content-file=input.txt
.
Exercise 3: Add support for file templates with variable substitution. For example, a template might contain placeholders like {{name}}
that get replaced with values provided by the user.
Hint for Exercise 1: You’ll need to use the glob
module to find directories that are missing.