Skip to content

Commit c3f471f

Browse files
committed
add ignore directories and prefixes
1 parent 29b7e64 commit c3f471f

File tree

4 files changed

+170
-94
lines changed

4 files changed

+170
-94
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This project replaces text in files based on a dictionary, given user input to s
66

77
- Replace text in files based on dictionaries defined in a configuration file.
88
- Allows user to specify the direction of replacement (keys-to-values or values-to-keys).
9-
- Allows user to specify which file extensions to ignore.
9+
- Allows user to specify which file extensions, file prefixes, or directories to ignore.
1010
- Automatically uses the only dictionary if only one is defined in the configuration file.
1111

1212
## Requirements
@@ -59,7 +59,9 @@ This project replaces text in files based on a dictionary, given user input to s
5959
"python": "rocks"
6060
}
6161
},
62-
"ignore_extensions": [".png", ".jpg", ".gif"]
62+
"ignore_extensions": [".exe", ".dll", ".bin"],
63+
"ignore_directories": ["node_modules", "venv", ".git"],
64+
"ignore_file_prefixes": [".", "_"]
6365
}
6466
```
6567

example_config.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@
1111
"python": "rocks"
1212
}
1313
},
14-
"ignore_extensions": [".png", ".jpg", ".gif"]
14+
"ignore_extensions": [".exe", ".dll", ".bin"],
15+
"ignore_directories": ["node_modules", "venv", ".git"],
16+
"ignore_file_prefixes": [".", "_"]
1517
}

replace_text/replace_text.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ def replace_text(direction: int, folder: str, dict_name: str) -> None:
2828
folder (str): Path to the folder containing text files.
2929
dict_name (str): Name of the dictionary to use from config.json.
3030
"""
31-
# Load dictionaries and ignore extensions from config file
31+
# Load dictionaries and configuration from config file
3232
with open("config.json", "r") as config_file:
3333
config = json.load(config_file)
3434

35-
# Retrieve the dictionaries and ignore extensions
35+
# Retrieve the dictionaries and configuration options
3636
dictionaries = config.get("dictionaries", {})
3737
ignore_extensions = config.get("ignore_extensions", [])
38+
ignore_directories = config.get("ignore_directories", [])
39+
ignore_file_prefixes = config.get("ignore_file_prefixes", [])
3840

3941
if not dictionaries:
4042
print("No dictionaries found in config.json")
@@ -59,22 +61,37 @@ def replace_text(direction: int, folder: str, dict_name: str) -> None:
5961
replacement_dict = {v: k for k, v in replacement_dict.items()}
6062

6163
# Process each file in the folder
62-
for root, _, files in os.walk(folder):
64+
for root, dirs, files in os.walk(folder):
65+
# Remove ignored directories from the dirs list
66+
dirs[:] = [d for d in dirs if d not in ignore_directories]
67+
6368
for file in files:
6469
file_path = os.path.join(root, file)
70+
71+
# Skip files with ignored extensions
6572
if any(file.endswith(ext) for ext in ignore_extensions):
6673
print(f"Skipped file (ignored extension): {file_path}")
6774
continue
68-
with open(file_path, "r", encoding="utf-8") as f:
69-
content = f.read()
7075

71-
for key, value in replacement_dict.items():
72-
content = content.replace(key, value)
76+
# Skip files with ignored prefixes
77+
if any(file.startswith(prefix) for prefix in ignore_file_prefixes):
78+
print(f"Skipped file (ignored prefix): {file_path}")
79+
continue
7380

74-
with open(file_path, "w", encoding="utf-8") as f:
75-
f.write(content)
81+
print(f"Processing file: {file}")
82+
try:
83+
with open(file_path, "r", encoding="utf-8") as f:
84+
content = f.read()
85+
for key, value in replacement_dict.items():
86+
content = content.replace(key, value)
7687

77-
print(f"Processed file: {file_path}")
88+
with open(file_path, "w", encoding="utf-8") as f:
89+
f.write(content)
90+
91+
print(f"Processed file: {file_path}")
92+
except Exception as e:
93+
print(f"Error processing file: {file}, continuing..")
94+
continue
7895

7996

8097
if __name__ == "__main__":
+136-81
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,165 @@
1-
import os
2-
import unittest
3-
from unittest.mock import patch, mock_open, call
1+
import unittest, json, os
42
from click.testing import CliRunner
53
from replace_text.replace_text import replace_text
64

75

86
class TestReplaceText(unittest.TestCase):
9-
def assert_path_any_call(self, mock_obj, expected_path, mode, encoding):
10-
normalized_expected_path = os.path.normpath(expected_path)
11-
for mock_call in mock_obj.call_args_list:
12-
args, kwargs = mock_call
13-
if len(args) >= 1:
14-
normalized_actual_path = os.path.normpath(args[0])
15-
if (
16-
normalized_actual_path == normalized_expected_path
17-
and args[1] == mode
18-
and kwargs.get("encoding") == encoding
19-
):
20-
return
21-
raise AssertionError(
22-
f"Expected call not found: open('{normalized_expected_path}', '{mode}', encoding='{encoding}')"
23-
)
7+
def setUp(self):
8+
self.runner = CliRunner()
9+
self.test_folder = "test_folder"
10+
self.config_file = "config.json"
11+
12+
# Create test folder and files
13+
os.makedirs(self.test_folder, exist_ok=True)
14+
with open(os.path.join(self.test_folder, "test1.txt"), "w") as f:
15+
f.write("Hello world")
16+
with open(os.path.join(self.test_folder, "test2.txt"), "w") as f:
17+
f.write("Python is awesome")
2418

25-
@patch("builtins.open", new_callable=mock_open, read_data="key1 content key2")
26-
@patch("os.walk")
27-
@patch("json.load")
28-
def test_replace_text_keys_to_values_single_dict(
29-
self, mock_json_load, mock_os_walk, mock_file
30-
):
31-
mock_json_load.return_value = {
19+
# Create config file
20+
config = {
3221
"dictionaries": {
33-
"example1": {"key1": "value1", "key2": "value2", "key3": "value3"}
22+
"test_dict": {"Hello": "Bonjour", "world": "monde", "Python": "Java"}
3423
},
35-
"ignore_extensions": [".png", ".jpg"],
24+
"ignore_extensions": [".ignore"],
25+
"ignore_directories": ["ignore_dir"],
26+
"ignore_file_prefixes": ["ignore_"],
3627
}
37-
mock_os_walk.return_value = [
38-
("/mocked/path", ("subdir",), ("file1.txt", "file2.jpg"))
39-
]
28+
with open(self.config_file, "w") as f:
29+
json.dump(config, f)
4030

41-
runner = CliRunner()
42-
result = runner.invoke(
31+
def tearDown(self):
32+
# Clean up test files and folders
33+
for root, dirs, files in os.walk(self.test_folder, topdown=False):
34+
for name in files:
35+
os.remove(os.path.join(root, name))
36+
for name in dirs:
37+
os.rmdir(os.path.join(root, name))
38+
os.rmdir(self.test_folder)
39+
os.remove(self.config_file)
40+
41+
def test_replace_text_keys_to_values(self):
42+
result = self.runner.invoke(
4343
replace_text,
44-
["--direction", "1", "--folder", "/mocked/path", "--dict-name", "example1"],
44+
[
45+
"--direction",
46+
"1",
47+
"--folder",
48+
self.test_folder,
49+
"--dict-name",
50+
"test_dict",
51+
],
4552
)
46-
47-
self.assert_path_any_call(mock_file, "/mocked/path/file1.txt", "r", "utf-8")
48-
self.assert_path_any_call(mock_file, "/mocked/path/file1.txt", "w", "utf-8")
49-
mock_file().write.assert_called_with("value1 content value2")
5053
self.assertEqual(result.exit_code, 0)
5154

52-
@patch("builtins.open", new_callable=mock_open, read_data="value1 content value2")
53-
@patch("os.walk")
54-
@patch("json.load")
55-
def test_replace_text_values_to_keys_multiple_dicts(
56-
self, mock_json_load, mock_os_walk, mock_file
57-
):
58-
mock_json_load.return_value = {
59-
"dictionaries": {
60-
"example1": {"key1": "value1", "key2": "value2", "key3": "value3"},
61-
"example2": {"hello": "world", "foo": "bar", "python": "rocks"},
62-
},
63-
"ignore_extensions": [".png", ".jpg"],
64-
}
65-
mock_os_walk.return_value = [
66-
("/mocked/path", ("subdir",), ("file1.txt", "file2.jpg"))
67-
]
55+
with open(os.path.join(self.test_folder, "test1.txt"), "r") as f:
56+
content = f.read()
57+
self.assertEqual(content, "Bonjour monde")
6858

69-
runner = CliRunner()
70-
result = runner.invoke(
59+
with open(os.path.join(self.test_folder, "test2.txt"), "r") as f:
60+
content = f.read()
61+
self.assertEqual(content, "Java is awesome")
62+
63+
def test_replace_text_values_to_keys(self):
64+
# First, replace keys with values
65+
self.runner.invoke(
7166
replace_text,
72-
["--direction", "2", "--folder", "/mocked/path", "--dict-name", "example1"],
67+
[
68+
"--direction",
69+
"1",
70+
"--folder",
71+
self.test_folder,
72+
"--dict-name",
73+
"test_dict",
74+
],
7375
)
7476

75-
self.assert_path_any_call(mock_file, "/mocked/path/file1.txt", "r", "utf-8")
76-
self.assert_path_any_call(mock_file, "/mocked/path/file1.txt", "w", "utf-8")
77-
mock_file().write.assert_called_with("key1 content key2")
77+
# Then, test replacing values with keys
78+
result = self.runner.invoke(
79+
replace_text,
80+
[
81+
"--direction",
82+
"2",
83+
"--folder",
84+
self.test_folder,
85+
"--dict-name",
86+
"test_dict",
87+
],
88+
)
7889
self.assertEqual(result.exit_code, 0)
7990

80-
@patch("builtins.open", new_callable=mock_open, read_data="hello content foo")
81-
@patch("os.walk")
82-
@patch("json.load")
83-
def test_replace_text_with_dict_name_flag(
84-
self, mock_json_load, mock_os_walk, mock_file
85-
):
86-
mock_json_load.return_value = {
87-
"dictionaries": {
88-
"example1": {"key1": "value1", "key2": "value2", "key3": "value3"},
89-
"example2": {"hello": "world", "foo": "bar", "python": "rocks"},
90-
},
91-
"ignore_extensions": [".png", ".jpg"],
92-
}
93-
mock_os_walk.return_value = [
94-
("/mocked/path", ("subdir",), ("file1.txt", "file2.jpg"))
95-
]
91+
with open(os.path.join(self.test_folder, "test1.txt"), "r") as f:
92+
content = f.read()
93+
self.assertEqual(content, "Hello world")
94+
95+
with open(os.path.join(self.test_folder, "test2.txt"), "r") as f:
96+
content = f.read()
97+
self.assertEqual(content, "Python is awesome")
9698

97-
runner = CliRunner()
98-
result = runner.invoke(
99+
def test_ignore_extensions(self):
100+
with open(os.path.join(self.test_folder, "test.ignore"), "w") as f:
101+
f.write("Hello world")
102+
103+
result = self.runner.invoke(
99104
replace_text,
100-
["--direction", "1", "--folder", "/mocked/path", "--dict-name", "example2"],
105+
[
106+
"--direction",
107+
"1",
108+
"--folder",
109+
self.test_folder,
110+
"--dict-name",
111+
"test_dict",
112+
],
101113
)
114+
self.assertEqual(result.exit_code, 0)
115+
116+
with open(os.path.join(self.test_folder, "test.ignore"), "r") as f:
117+
content = f.read()
118+
self.assertEqual(content, "Hello world") # Content should remain unchanged
119+
120+
def test_ignore_directories(self):
121+
os.makedirs(os.path.join(self.test_folder, "ignore_dir"), exist_ok=True)
122+
with open(os.path.join(self.test_folder, "ignore_dir", "test.txt"), "w") as f:
123+
f.write("Hello world")
102124

103-
self.assert_path_any_call(mock_file, "/mocked/path/file1.txt", "r", "utf-8")
104-
self.assert_path_any_call(mock_file, "/mocked/path/file1.txt", "w", "utf-8")
105-
mock_file().write.assert_called_with("world content bar")
125+
result = self.runner.invoke(
126+
replace_text,
127+
[
128+
"--direction",
129+
"1",
130+
"--folder",
131+
self.test_folder,
132+
"--dict-name",
133+
"test_dict",
134+
],
135+
)
106136
self.assertEqual(result.exit_code, 0)
107137

138+
with open(os.path.join(self.test_folder, "ignore_dir", "test.txt"), "r") as f:
139+
content = f.read()
140+
self.assertEqual(content, "Hello world") # Content should remain unchanged
141+
142+
def test_ignore_file_prefixes(self):
143+
with open(os.path.join(self.test_folder, "ignore_test.txt"), "w") as f:
144+
f.write("Hello world")
145+
146+
result = self.runner.invoke(
147+
replace_text,
148+
[
149+
"--direction",
150+
"1",
151+
"--folder",
152+
self.test_folder,
153+
"--dict-name",
154+
"test_dict",
155+
],
156+
)
157+
self.assertEqual(result.exit_code, 0)
158+
159+
with open(os.path.join(self.test_folder, "ignore_test.txt"), "r") as f:
160+
content = f.read()
161+
self.assertEqual(content, "Hello world") # Content should remain unchanged
162+
108163

109164
if __name__ == "__main__":
110165
unittest.main()

0 commit comments

Comments
 (0)