Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion app/create_file.py
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# write your code here
import os
import sys
from datetime import datetime


def parse_args(args: list[str]) -> tuple[list[str], str | None]:
directories: list[str] = []
filename: str | None = None
index = 0

while index < len(args):
token = args[index]

if token == "-d":
index += 1
while index < len(args) and not args[index].startswith("-"):
directories.append(args[index])
index += 1
continue

if token == "-f":
index += 1
if index < len(args):
filename = args[index]
index += 1
continue

index += 1

return directories, filename


def get_content_lines() -> list[str]:
lines: list[str] = []
while True:
content_line = input("Enter content line: ")
if content_line == "stop":
break
lines.append(content_line)
return lines


def write_content(file_path: str, lines: list[str]) -> None:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
numbered_lines = [
f"{index} {line}"
for index, line in enumerate(lines, start=1)
]
content_block = "\n".join([timestamp, *numbered_lines])

if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
with open(file_path, "a", encoding="utf-8") as file:
file.write("\n\n" + content_block)
else:
with open(file_path, "w", encoding="utf-8") as file:
file.write(content_block)


def main() -> None:
directories, filename = parse_args(sys.argv[1:])

target_dir = os.path.join(*directories) if directories else "."
if directories:
os.makedirs(target_dir, exist_ok=True)

if filename is None:
return

file_path = os.path.join(target_dir, filename)
content_lines = get_content_lines()
write_content(file_path, content_lines)


if __name__ == "__main__":
main()
128 changes: 35 additions & 93 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from datetime import datetime
from unittest.mock import patch

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SCRIPT_PATH = os.path.join(BASE_DIR, "app", "create_file.py")

def cleanup() -> None:
for item in ["dir1", "file.txt"]:
Expand All @@ -12,170 +14,110 @@ def cleanup() -> None:
elif os.path.isfile(item):
os.remove(item)


def test_create_dir_and_file() -> None:
cleanup()
test_args = ["app/create_file.py", "-d", "dir1", "dir2", "-f", "file.txt"]
test_args = [SCRIPT_PATH, "-d", "dir1", "dir2", "-f", "file.txt"]
test_input = ["Line1 content", "Line2 content", "stop"]

with patch("sys.argv", test_args), patch("builtins.input", side_effect=test_input):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

path = os.path.join("dir1", "dir2", "file.txt")
assert os.path.exists(path), (
f"File {path} should exist in the created directory hierarchy"
)
assert os.path.exists(path)

with open(path, "r") as source_file:
lines = source_file.readlines()
# Verify timestamp format
try:
datetime.strptime(lines[0].strip(), "%Y-%m-%d %H:%M:%S")
except ValueError:
assert False, (
f"Invalid timestamp format: '{lines[0].strip()}'. "
f"Expected format: YYYY-MM-DD HH:MM:SS"
)

assert lines[1].strip() == "1 Line1 content", (
f"Line 1 mismatch: expected '1 Line1 content', got '{lines[1].strip()}'"
)
assert lines[2].strip() == "2 Line2 content", (
f"Line 2 mismatch: expected '2 Line2 content', got '{lines[2].strip()}'"
)
cleanup()
assert False

assert lines[1].strip() == "1 Line1 content"
assert lines[2].strip() == "2 Line2 content"
cleanup()

def test_create_file_and_dirs_reversed() -> None:
cleanup()
test_args = [
"app/create_file.py",
"-f",
"file.txt",
"-d",
"dir1",
"dir2",
"dir3",
"dir4",
]
test_args = [SCRIPT_PATH, "-f", "file.txt", "-d", "dir1", "dir2", "dir3", "dir4"]
test_input = ["Line1", "stop"]

with patch("sys.argv", test_args), patch("builtins.input", side_effect=test_input):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

path = os.path.join("dir1", "dir2", "dir3", "dir4", "file.txt")
assert os.path.exists(path), (
f"File {path} should be created even when -f flag is passed before -d"
)
assert os.path.exists(path)
cleanup()


def test_create_only_file() -> None:
cleanup()
test_args = ["app/create_file.py", "-f", "file.txt"]
test_args = [SCRIPT_PATH, "-f", "file.txt"]
test_input = ["Content line", "stop"]

with patch("sys.argv", test_args), patch("builtins.input", side_effect=test_input):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

assert os.path.exists("file.txt"), (
"File 'file.txt' should be created in the current directory "
"when no directories are specified"
)
assert os.path.exists("file.txt")
cleanup()


def test_create_only_dirs() -> None:
cleanup()
test_args = ["app/create_file.py", "-d", "dir1", "dir2", "dir3", "dir4"]
test_args = [SCRIPT_PATH, "-d", "dir1", "dir2", "dir3", "dir4"]

with patch("sys.argv", test_args):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

path = os.path.join("dir1", "dir2", "dir3", "dir4")
assert os.path.isdir(path), (
f"Directory {path} should be created when only -d flag is provided"
)
assert os.path.isdir(path)
cleanup()


def test_append_with_blank_line() -> None:
cleanup()
# First entry
with (
patch("sys.argv", ["app/create_file.py", "-f", "file.txt"]),
patch("sys.argv", [SCRIPT_PATH, "-f", "file.txt"]),
patch("builtins.input", side_effect=["Line 1", "stop"]),
):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

# Second entry
with (
patch("sys.argv", ["app/create_file.py", "-f", "file.txt"]),
patch("sys.argv", [SCRIPT_PATH, "-f", "file.txt"]),
patch("builtins.input", side_effect=["Line 2", "stop"]),
):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

with open("file.txt", "r") as source_file:
content = source_file.read()

# Check for blank line between entries
parts = content.split("\n\n")
assert len(parts) == 2, (
f"Expected exactly 2 entries separated by a blank line, but found {len(parts)}"
)

# Check first entry
lines1 = parts[0].split("\n")
assert lines1[1] == "1 Line 1", (
f"First entry content mismatch: expected '1 Line 1', got '{lines1[1]}'"
)

# Check second entry
lines2 = parts[1].split("\n")
assert lines2[1] == "1 Line 2", (
f"Second entry content mismatch: expected '1 Line 2', got '{lines2[1]}'"
)
assert len(parts) == 2
assert "1 Line 1" in parts[0]
assert "1 Line 2" in parts[1]
cleanup()


def test_stop_word() -> None:
cleanup()
# Test that it stops exactly at "stop"
test_args = ["app/create_file.py", "-f", "file.txt"]
test_args = [SCRIPT_PATH, "-f", "file.txt"]
test_input = ["line", "stop", "extra"]

with patch("sys.argv", test_args), patch("builtins.input", side_effect=test_input):
runpy.run_path("app/create_file.py")
runpy.run_path(SCRIPT_PATH, run_name="__main__")

with open("file.txt", "r") as source_file:
lines = source_file.readlines()
assert len(lines) == 2, (
"File should contain exactly 2 lines "
"(timestamp and 1 content line), "
f"but has {len(lines)}"
)
assert lines[1].strip() == "1 line", (
f"Content line mismatch: expected '1 line', got '{lines[1].strip()}'"
)
assert len(lines) == 2
assert lines[1].strip() == "1 line"
cleanup()


def test_input_prompt() -> None:
cleanup()
test_args = ["app/create_file.py", "-f", "file.txt"]
test_args = [SCRIPT_PATH, "-f", "file.txt"]
test_input = ["line 1", "stop"]

with (
patch("sys.argv", test_args),
patch("builtins.input", side_effect=test_input) as mock_input,
):
runpy.run_path("app/create_file.py")

assert mock_input.call_args_list[0][0][0] == "Enter content line: ", (
f"Expected prompt 'Enter content line: ', but got '{mock_input.call_args_list[0][0][0]}'"
)
assert mock_input.call_args_list[1][0][0] == "Enter content line: ", (
f"Expected prompt 'Enter content line: ' for the stop command as well, "
f"but got '{mock_input.call_args_list[1][0][0]}'"
)
cleanup()
runpy.run_path(SCRIPT_PATH, run_name="__main__")

assert mock_input.call_args_list[0][0][0] == "Enter content line: "
assert mock_input.call_args_list[1][0][0] == "Enter content line: "
cleanup()
Loading