whitespace-format 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: whitespace-format
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Linter and formatter for source code files and text files
5
5
  Home-page: https://github.com/DavidPal/whitespace-format
6
6
  License: MIT
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.8
14
14
  Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
18
  Project-URL: Repository, https://github.com/DavidPal/whitespace-format
19
19
  Description-Content-Type: text/markdown
20
20
 
@@ -22,7 +22,7 @@ Description-Content-Type: text/markdown
22
22
 
23
23
  [![CircleCI](https://dl.circleci.com/status-badge/img/gh/DavidPal/whitespace-format/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/DavidPal/whitespace-format/tree/main)
24
24
 
25
- ![GitHub Action](https://github.com/DavidPal/whitespace-format/actions/workflows/build.yaml/badge.svg)
25
+ [![Build, lint and test](https://github.com/DavidPal/whitespace-format/actions/workflows/build.yaml/badge.svg)](https://github.com/DavidPal/whitespace-format/actions/workflows/build.yaml)
26
26
 
27
27
  Linter and formatter for source code files and text files.
28
28
 
@@ -32,7 +32,7 @@ MarkDown, LaTeX) before checking them into a version control system.
32
32
 
33
33
  The features include:
34
34
 
35
- * Auto-detection of new line markers (Linux `\n`, Windows `\r\r`, Mac `\r`).
35
+ * Auto-detection of new line markers (Linux `\n`, Windows `\r\n`, Mac `\r`).
36
36
  * Add a new line marker at the end of the file if it is missing.
37
37
  * Make new line markers consistent.
38
38
  * Remove empty lines at the end of the file.
@@ -62,17 +62,18 @@ whitespace-format \
62
62
  --normalize-new-line-markers \
63
63
  foo.txt my_project/
64
64
  ```
65
- The command above formats `foo.txt` and all files contained `my_project/` and
66
- its subdirectories. Files that contain `.git/` or `.idea/` in their (relative)
67
- path are excluded. For example, files in `my_project/.git/` and files in
68
- `my_project/.idea/` are excluded. Likewise, files ending with `*.pyc` are
69
- excluded.
70
-
71
- If you want only know if any changes **would be** made, add `--check-only` option:
65
+ The command above formats `foo.txt` and all files contained in `my_project/`
66
+ directory and its subdirectories. Files that contain `.git/` or `.idea/` in
67
+ their (relative) path are excluded. For example, files in `my_project/.git/`
68
+ and files in `my_project/.idea/` are excluded. Likewise, files ending with
69
+ `*.pyc` are excluded.
70
+
71
+ If you want to know only if any changes **would be** made, add `--check-only`
72
+ option:
72
73
  ```shell
73
74
  whitespace-format \
74
- --exclude ".git/|.idea/|.pyc$" \
75
75
  --check-only \
76
+ --exclude ".git/|.idea/|.pyc$" \
76
77
  --new-line-marker linux \
77
78
  --normalize-new-line-markers \
78
79
  foo.txt my_project/
@@ -97,7 +98,7 @@ The regular expression is evaluated on the path of each file.
97
98
  This option is ignored when `--add-new-line-marker-at-end-of-file` is used.
98
99
  Empty lines at the end of the file are removed.
99
100
  * `--normalize-new-line-markers` -- Make new line markers consistent in each file
100
- by replacing `\\r\\n`, `\\n`, and `\r` with a consistent new line marker.
101
+ by replacing `\r\n`, `\n`, and `\r` with a consistent new line marker.
101
102
  * `--remove-trailing-whitespace` -- Remove whitespace at the end of each line.
102
103
  * `--remove-trailing-empty-lines` -- Remove empty lines at the end of each file.
103
104
  * `--new-line-marker=MARKER` -- This option specifies what new line marker to use.
@@ -105,9 +106,9 @@ by replacing `\\r\\n`, `\\n`, and `\r` with a consistent new line marker.
105
106
  * `auto` -- Use new line marker that is the most common in each individual file.
106
107
  If no new line marker is present in the file, Linux `\n` is used.
107
108
  This is the default option.
108
- * `linux` -- Use Linux new line marker `\\n`.
109
- * `mac` -- Use Mac new line marker `\\r`.
110
- * `windows` -- Use Windows new line marker `\\r\\n`.
109
+ * `linux` -- Use Linux new line marker `\n`.
110
+ * `mac` -- Use Mac new line marker `\r`.
111
+ * `windows` -- Use Windows new line marker `\r\n`.
111
112
  * `--encoding` -- Text encoding for both reading and writing files. Default encoding is `utf-8`.
112
113
  List of supported encodings can be found at
113
114
  https://docs.python.org/3/library/codecs.html#standard-encodings
@@ -162,7 +163,7 @@ Default value is `-1`.
162
163
 
163
164
  * `--normalize-non-standard-whitespace=MODE` -- Replace or remove
164
165
  non-standard whitespace characters (`\v` and `\f`). `MODE` must be one of the following:
165
- * `ignore` -- Leave `\v` and `f` as is. This is the default option.
166
+ * `ignore` -- Leave `\v` and `\f` as is. This is the default option.
166
167
  * `replace` -- Replace any occurrence of `\v` or `\f` with a single space.
167
168
  * `remove` -- Remove all occurrences of `\v` and `\f`
168
169
 
@@ -182,13 +183,7 @@ MIT
182
183
  brew install poetry
183
184
  ```
184
185
 
185
- 3) Create Python virtual environment with the correct Python version:
186
- ```shell
187
- make install-python
188
- make create-environment
189
- ```
190
-
191
- 4) Add the following lines to `.zshrc` or `.bash_profile` and restart the terminal:
186
+ 3) Add the following lines to `.zshrc` or `.bash_profile` and restart the terminal:
192
187
  ```shell
193
188
  # Pyenv settings
194
189
  export PYENV_ROOT="$HOME/.pyenv"
@@ -197,6 +192,12 @@ MIT
197
192
  eval "$(pyenv virtualenv-init -)"
198
193
  ```
199
194
 
195
+ 4) Create Python virtual environment with the correct Python version:
196
+ ```shell
197
+ make install-python
198
+ make create-environment
199
+ ```
200
+
200
201
  5) Install all dependencies
201
202
  ```shell
202
203
  make install-dependecies
@@ -0,0 +1,6 @@
1
+ whitespace_format.py,sha256=uqu0SZ76nyFGEDX6gMIyLZpmVv_rtJRx0IjcEC601Cs,26016
2
+ whitespace_format-0.0.5.dist-info/LICENSE,sha256=rT6UNfWDYFQc-eo65FioDJRMAyVOndtF95wNCUhkK74,1076
3
+ whitespace_format-0.0.5.dist-info/METADATA,sha256=Pq-GTLKbXw9u6uj7CGwItBpi15yDrOdjSyTXVzdnuVU,10233
4
+ whitespace_format-0.0.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
5
+ whitespace_format-0.0.5.dist-info/entry_points.txt,sha256=LbXoevzUZAF5MVbI2foNC9xeDjKS_Woz7VbA1ZNF5CY,60
6
+ whitespace_format-0.0.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.4.0
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
whitespace_format.py CHANGED
@@ -10,22 +10,26 @@ Usage:
10
10
  python whitespace_format.py [OPTIONS] [FILES ...]
11
11
  """
12
12
 
13
+ from __future__ import annotations
14
+
13
15
  import argparse
16
+ import copy
14
17
  import dataclasses
15
18
  import pathlib
16
19
  import re
17
20
  import sys
18
21
  from typing import Callable
22
+ from typing import Dict
19
23
  from typing import List
20
24
 
21
- VERSION = "0.0.3"
25
+ VERSION = "0.0.5"
22
26
 
23
27
  # Regular expression that does NOT match any string.
24
28
  UNMATCHABLE_REGEX = "$."
25
29
 
26
- NEW_LINE_MARKERS = {
27
- "linux": "\n",
30
+ END_OF_LINE_MARKERS = {
28
31
  "windows": "\r\n",
32
+ "linux": "\n",
29
33
  "mac": "\r",
30
34
  }
31
35
 
@@ -68,6 +72,14 @@ def color_print(message: str, parsed_arguments: argparse.Namespace):
68
72
  print(message)
69
73
 
70
74
 
75
+ def string_to_hex(text: str) -> str:
76
+ """Converts a string into a human-readable hexadecimal representation.
77
+
78
+ This function is for debugging purposes only. It is used only during development.
79
+ """
80
+ return ":".join(f"{ord(character):02x}" for character in text)
81
+
82
+
71
83
  def die(error_code: int, message: str = ""):
72
84
  """Exits the script."""
73
85
  if message:
@@ -78,7 +90,7 @@ def die(error_code: int, message: str = ""):
78
90
  def read_file_content(file_name: str, encoding: str) -> str:
79
91
  """Reads content of a file."""
80
92
  try:
81
- with open(file_name, "r", encoding=encoding) as file:
93
+ with open(file_name, "r", encoding=encoding, newline="") as file:
82
94
  return file.read()
83
95
  except IOError as exception:
84
96
  die(2, f"Cannot read file '{file_name}': {exception}")
@@ -96,99 +108,192 @@ def write_file(file_name: str, file_content: str, encoding: str):
96
108
  die(4, f"Cannot write to file '{file_name}': {exception}")
97
109
 
98
110
 
99
- def guess_new_line_marker(file_content: str) -> str:
100
- """Guesses newline character for a file.
111
+ @dataclasses.dataclass
112
+ class Line:
113
+ """Line of a text file.
101
114
 
102
- The guess is based on the counts of "\n", "\r" and "\rn" in the file.
115
+ The line is split into two parts:
116
+ 1) Content
117
+ 2) End of line marker ("\n", or "\r", or "\r\n")
103
118
  """
104
- windows_count = file_content.count("\r\n")
105
- linux_count = file_content.count("\n") - windows_count
106
- mac_count = file_content.count("\r") - windows_count
107
-
108
- # Pick the new line marker with the highest count.
109
- # Break ties according to the ordering: Linux > Windows > Mac.
110
- _, _, new_line_marker_guess = max(
111
- (linux_count, 3, "\n"),
112
- (windows_count, 2, "\r\n"),
113
- (mac_count, 1, "\r"),
114
- )
115
119
 
116
- return new_line_marker_guess
120
+ content: str
121
+ end_of_line_marker: str
117
122
 
123
+ @staticmethod
124
+ def create_from_string(line: str) -> Line:
125
+ """Creates a line from a string.
118
126
 
119
- def remove_trailing_empty_lines(file_content: str) -> str:
120
- """Removes trailing empty lines."""
121
- reduced_file_content = file_content.rstrip("\r\n")
122
- i = len(reduced_file_content)
123
- if i >= len(file_content) - 1:
124
- return file_content
125
- if file_content[i : i + 2] == "\r\n":
126
- return file_content[: i + 2]
127
- return file_content[: i + 1]
127
+ The function splits the input into content and end_of_line_marker.
128
+ """
129
+ for end_of_line_marker in ["\r\n", "\n", "\r"]:
130
+ if line.endswith(end_of_line_marker):
131
+ return Line(line[: -len(end_of_line_marker)], end_of_line_marker)
132
+ return Line(line, "")
128
133
 
134
+ def to_hex(self):
135
+ """Returns a human-readable hexadecimal representation of the line.
129
136
 
130
- def remove_trailing_whitespace(file_content: str) -> str:
131
- """Removes trailing whitespace from every line."""
132
- file_content = re.sub(r"[ \t\f\v]*\n", "\n", file_content)
133
- file_content = re.sub(r"[ \t\f\v]*\r", "\r", file_content)
134
- file_content = re.sub(r"[ \t\f\v]*$", "", file_content)
135
- return file_content
137
+ This function is for debugging purposes only. It is used only during development.
138
+ """
139
+ return f"({string_to_hex(self.content)}, {string_to_hex(self.end_of_line_marker)})"
140
+
141
+
142
+ def split_lines(text: str) -> List[Line]:
143
+ """Splits a string into lines."""
144
+ lines: List[Line] = []
145
+ current_line = ""
146
+ for i, char in enumerate(text):
147
+ current_line += char
148
+ if (char == "\n") or (
149
+ (char == "\r") and ((i >= len(text) - 1) or (not text[i + 1] == "\n"))
150
+ ):
151
+ lines.append(Line.create_from_string(current_line))
152
+ current_line = ""
153
+
154
+ if current_line:
155
+ lines.append(Line.create_from_string(current_line))
136
156
 
157
+ return lines
158
+
159
+
160
+ def concatenate_lines(lines: List[Line]) -> str:
161
+ """Concatenates a list of lines into a single string including end-of-line markers."""
162
+ return "".join(line.content + line.end_of_line_marker for line in lines)
163
+
164
+
165
+ def guess_end_of_line_marker(lines: List[Line]) -> str:
166
+ """Guesses the end of line marker.
167
+
168
+ The function returns the most common end-of-line marker.
169
+ Ties are broken in order Linux "\n", Mac "\r", Windows "\r\n".
170
+ If no end-of-line marker is present, default to the Linux "\n" end-of-line marker.
171
+ """
172
+ counts: Dict[str, int] = {"\n": 0, "\r": 0, "\r\n": 0}
173
+ for line in lines:
174
+ if line.end_of_line_marker in counts:
175
+ counts[line.end_of_line_marker] += 1
176
+ max_count = max(counts.values())
177
+ for end_of_line_marker, count in counts.items():
178
+ if count == max_count:
179
+ return end_of_line_marker
180
+ return "\n" # This return statement is never executed.
181
+
182
+
183
+ def remove_trailing_empty_lines(lines: List[Line]) -> List[Line]:
184
+ """Removes trailing empty lines.
185
+
186
+ If there are no lines, empty list is returned.
187
+ If all lines are empty, the first line is kept.
188
+ """
189
+ num_empty_trailing_lines = 0
190
+ while (num_empty_trailing_lines < len(lines) - 1) and (
191
+ not lines[-num_empty_trailing_lines - 1].content
192
+ ):
193
+ num_empty_trailing_lines += 1
194
+ return copy.deepcopy(lines[: len(lines) - num_empty_trailing_lines])
195
+
196
+
197
+ def remove_dummy_lines(lines: List[Line]) -> List[Line]:
198
+ """Remove empty lines that also have empty end-of-line markers."""
199
+ return [line for line in lines if line.content or line.end_of_line_marker]
200
+
201
+
202
+ def remove_trailing_whitespace(lines: List[Line]) -> List[Line]:
203
+ """Removes trailing whitespace from every line."""
204
+ lines = [
205
+ Line(
206
+ re.sub(r"[ \n\r\t\f\v]*$", "", line.content),
207
+ line.end_of_line_marker,
208
+ )
209
+ for line in lines
210
+ ]
211
+ return remove_dummy_lines(lines)
137
212
 
138
- def remove_all_new_line_marker_from_end_of_file(file_content: str) -> str:
139
- """Removes all new line markers from the end of the file."""
140
- return file_content.rstrip("\n\r")
141
213
 
214
+ def normalize_end_of_line_markers(lines: List[Line], new_end_of_line_marker: str) -> List[Line]:
215
+ """Replaces end-of-line marker in all lines with a new end-of-line marker.
142
216
 
143
- def remove_new_line_marker_from_end_of_file(file_content: str) -> str:
144
- """Removes the last new line marker from the last line."""
145
- if file_content.endswith("\r\n"):
146
- return file_content[:-2]
147
- if file_content.endswith("\n") or file_content.endswith("\r"):
148
- return file_content[:-1]
149
- return file_content
217
+ Lines without end-of-line markers (i.e. possibly the last line) are left unchanged.
218
+ """
219
+ return [
220
+ Line(line.content, new_end_of_line_marker) if line.end_of_line_marker else line
221
+ for line in lines
222
+ ]
150
223
 
151
224
 
152
- def add_new_line_marker_at_end_of_file(file_content: str, new_line_marker: str) -> str:
153
- """Adds new line marker to the end of file if it is missing."""
154
- return remove_new_line_marker_from_end_of_file(file_content) + new_line_marker
225
+ def remove_all_end_of_line_markers_from_end_of_file(lines: List[Line]) -> List[Line]:
226
+ """Removes all end-of-line markers from the end of the file."""
227
+ lines = remove_trailing_empty_lines(lines)
228
+ if not lines:
229
+ return []
230
+ lines[-1] = Line(lines[-1].content, "")
231
+ return remove_dummy_lines(lines)
155
232
 
156
233
 
157
- def normalize_new_line_markers(file_content: str, new_line_marker: str) -> str:
158
- """Fixes line endings."""
159
- file_content = file_content.replace("\r\n", "\n")
160
- file_content = file_content.replace("\r", "\n")
161
- return file_content.replace("\n", new_line_marker)
234
+ def add_end_of_line_marker_at_end_of_file(
235
+ lines: List[Line], new_end_of_line_marker: str
236
+ ) -> List[Line]:
237
+ """Adds new end-of-line marker to the end of file if it is missing."""
238
+ if not lines:
239
+ return [Line("", new_end_of_line_marker)]
240
+ lines = copy.deepcopy(lines)
241
+ lines[-1] = Line(lines[-1].content, new_end_of_line_marker)
242
+ return lines
162
243
 
163
244
 
164
- def normalize_empty_file(file_content: str, mode: str, new_line_marker: str) -> str:
245
+ def normalize_empty_file(lines: List[Line], mode: str, new_end_of_line_marker: str) -> List[Line]:
165
246
  """Replaces file with an empty file."""
166
247
  if mode == "empty":
167
- return ""
248
+ return []
168
249
  if mode == "one-line":
169
- return new_line_marker
170
- return file_content
250
+ return [Line("", new_end_of_line_marker)]
251
+ return copy.deepcopy(lines)
171
252
 
172
253
 
173
- def is_whitespace_only(file_content: str) -> bool:
254
+ def is_whitespace_only(lines: List[Line]) -> bool:
174
255
  """Determines if file consists only of whitespace."""
175
- return not file_content.strip(" \n\r\t\v\f")
256
+ for line in lines:
257
+ if line.content.strip(" \n\r\t\v\f"):
258
+ return False
259
+ return True
176
260
 
177
261
 
178
- def normalize_non_standard_whitespace(file_content: str, mode: str) -> str:
262
+ def normalize_non_standard_whitespace(lines: List[Line], mode: str) -> List[Line]:
179
263
  """Removes non-standard whitespace characters."""
180
264
  if mode == "ignore":
181
- return file_content
265
+ return copy.deepcopy(lines)
182
266
  if mode == "replace":
183
- return file_content.translate(str.maketrans("\v\f", " ", ""))
184
- return file_content.translate(str.maketrans("", "", "\v\f"))
267
+ return [
268
+ Line(line.content.translate(str.maketrans("\v\f", " ", "")), line.end_of_line_marker)
269
+ for line in lines
270
+ ]
271
+ return [
272
+ Line(line.content.translate(str.maketrans("", "", "\v\f")), line.end_of_line_marker)
273
+ for line in lines
274
+ ]
185
275
 
186
276
 
187
- def replace_tabs_with_spaces(file_content: str, num_spaces: int) -> str:
277
+ def replace_tabs_with_spaces(lines: List[Line], num_spaces: int) -> List[Line]:
188
278
  """Replaces tabs with spaces."""
189
279
  if num_spaces < 0:
190
- return file_content
191
- return file_content.replace("\t", num_spaces * " ")
280
+ return copy.deepcopy(lines)
281
+ return [
282
+ Line(line.content.replace("\t", num_spaces * " "), line.end_of_line_marker)
283
+ for line in lines
284
+ ]
285
+
286
+
287
+ def compute_difference(original_lines: List[Line], new_lines: List[Line]) -> List[int]:
288
+ """Computes the indices of lines that differ."""
289
+ line_numbers = [
290
+ line_number
291
+ for line_number, (original_line, new_line) in enumerate(zip(original_lines, new_lines))
292
+ if not original_line == new_line
293
+ ]
294
+ if len(original_lines) != len(new_lines):
295
+ line_numbers.append(min(len(original_lines), len(new_lines)))
296
+ return line_numbers
192
297
 
193
298
 
194
299
  @dataclasses.dataclass
@@ -199,25 +304,36 @@ class ChangeDescription:
199
304
  change: str
200
305
 
201
306
 
307
+ @dataclasses.dataclass
308
+ class LineChange:
309
+ """Description of a change on a particular line."""
310
+
311
+ check_only: str
312
+ change: str
313
+ line_number: int
314
+
315
+
202
316
  class FileContentTracker:
203
317
  """Tracks changes of the content of a file as it undergoes formatting."""
204
318
 
205
- def __init__(self, file_content: str):
319
+ def __init__(self, lines: List[Line]):
206
320
  """Initializes an instance of the file content tracker."""
207
- self.initial_content = file_content
208
- self.file_content = file_content
209
- self.changes: List[ChangeDescription] = []
321
+ self.initial_lines = lines
322
+ self.lines = copy.deepcopy(lines)
323
+ self.line_changes: List[LineChange] = []
210
324
 
211
- def format(self, change: ChangeDescription, function: Callable[..., str], *args):
325
+ def format(self, change: ChangeDescription, function: Callable[..., List[Line]], *args):
212
326
  """Applies a change to the content of the file."""
213
- previous_content = self.file_content
214
- self.file_content = function(self.file_content, *args)
215
- if previous_content != self.file_content:
216
- self.changes.append(change)
327
+ previous_content = self.lines
328
+ self.lines = function(self.lines, *args)
329
+ if previous_content != self.lines:
330
+ line_numbers = compute_difference(previous_content, self.lines)
331
+ for line_number in line_numbers:
332
+ self.line_changes.append(LineChange(change.check_only, change.change, line_number))
217
333
 
218
334
  def is_changed(self) -> bool:
219
335
  """Determines if the file content has changed."""
220
- return self.file_content != self.initial_content
336
+ return self.lines != self.initial_lines
221
337
 
222
338
 
223
339
  def format_file_content(
@@ -225,12 +341,12 @@ def format_file_content(
225
341
  parsed_arguments: argparse.Namespace,
226
342
  ):
227
343
  """Formats the content of file represented as a string."""
228
- new_line_marker = NEW_LINE_MARKERS.get(
344
+ new_line_marker = END_OF_LINE_MARKERS.get(
229
345
  parsed_arguments.new_line_marker,
230
- guess_new_line_marker(file_content_tracker.initial_content),
346
+ guess_end_of_line_marker(file_content_tracker.initial_lines),
231
347
  )
232
348
 
233
- if is_whitespace_only(file_content_tracker.initial_content):
349
+ if is_whitespace_only(file_content_tracker.initial_lines):
234
350
  changes = {
235
351
  "ignore": ChangeDescription("", ""),
236
352
  "empty": ChangeDescription(
@@ -246,7 +362,7 @@ def format_file_content(
246
362
  ),
247
363
  ),
248
364
  }
249
- if not file_content_tracker.initial_content:
365
+ if not file_content_tracker.initial_lines:
250
366
  file_content_tracker.format(
251
367
  changes[parsed_arguments.normalize_empty_files],
252
368
  normalize_empty_file,
@@ -265,8 +381,8 @@ def format_file_content(
265
381
  if parsed_arguments.remove_trailing_whitespace:
266
382
  file_content_tracker.format(
267
383
  ChangeDescription(
268
- check_only="Whitespace at the end of line(s) needs to be removed.",
269
- change="Whitespace at the end of line(s) was removed.",
384
+ check_only="Whitespace at the end of line needs to be removed.",
385
+ change="Whitespace at the end of line was removed.",
270
386
  ),
271
387
  remove_trailing_whitespace,
272
388
  )
@@ -308,7 +424,7 @@ def format_file_content(
308
424
  ),
309
425
  change=f"New line marker(s) were replaced with {repr(new_line_marker)}.",
310
426
  ),
311
- normalize_new_line_markers,
427
+ normalize_end_of_line_markers,
312
428
  new_line_marker,
313
429
  )
314
430
 
@@ -320,7 +436,7 @@ def format_file_content(
320
436
  change=f"New line marker was added to the end of the file, "
321
437
  f"or replaced with {repr(new_line_marker)}.",
322
438
  ),
323
- add_new_line_marker_at_end_of_file,
439
+ add_end_of_line_marker_at_end_of_file,
324
440
  new_line_marker,
325
441
  )
326
442
  elif parsed_arguments.remove_new_line_marker_from_end_of_file:
@@ -329,14 +445,15 @@ def format_file_content(
329
445
  check_only="New line marker(s) need to removed from the end of the file.",
330
446
  change="New line marker(s) were removed from the end of the file.",
331
447
  ),
332
- remove_all_new_line_marker_from_end_of_file,
448
+ remove_all_end_of_line_markers_from_end_of_file,
333
449
  )
334
450
 
335
451
 
336
452
  def reformat_file(file_name: str, parsed_arguments: argparse.Namespace) -> bool:
337
453
  """Reformats a file."""
338
454
  file_content = read_file_content(file_name, parsed_arguments.encoding)
339
- file_content_tracker = FileContentTracker(file_content)
455
+ lines = split_lines(file_content)
456
+ file_content_tracker = FileContentTracker(lines)
340
457
  format_file_content(file_content_tracker, parsed_arguments)
341
458
  is_changed = file_content_tracker.is_changed()
342
459
  if parsed_arguments.verbose:
@@ -348,9 +465,11 @@ def reformat_file(file_name: str, parsed_arguments: argparse.Namespace) -> bool:
348
465
  f"[RED]needs to be formatted[RESET_ALL]",
349
466
  parsed_arguments,
350
467
  )
351
- for change in file_content_tracker.changes:
468
+ for line_change in file_content_tracker.line_changes:
352
469
  color_print(
353
- f" [BOLD][BLUE]↳ [WHITE]{change.check_only}[RESET_ALL]", parsed_arguments
470
+ f" [BOLD][BLUE]↳ line {line_change.line_number + 1}: "
471
+ f"[WHITE]{line_change.check_only}[RESET_ALL]",
472
+ parsed_arguments,
354
473
  )
355
474
  else:
356
475
  if parsed_arguments.verbose:
@@ -362,9 +481,15 @@ def reformat_file(file_name: str, parsed_arguments: argparse.Namespace) -> bool:
362
481
  else:
363
482
  if is_changed:
364
483
  color_print(f"[WHITE]Reformatted [BOLD]{file_name}[RESET_ALL]", parsed_arguments)
365
- for change in file_content_tracker.changes:
366
- color_print(f" [BOLD][BLUE]↳ [WHITE]{change.change}[RESET_ALL]", parsed_arguments)
367
- write_file(file_name, file_content_tracker.file_content, parsed_arguments.encoding)
484
+ for line_change in file_content_tracker.line_changes:
485
+ color_print(
486
+ f" [BOLD][BLUE]↳ line {line_change.line_number + 1}: "
487
+ f"[WHITE]{line_change.change}[RESET_ALL]",
488
+ parsed_arguments,
489
+ )
490
+ write_file(
491
+ file_name, concatenate_lines(file_content_tracker.lines), parsed_arguments.encoding
492
+ )
368
493
  else:
369
494
  if parsed_arguments.verbose:
370
495
  color_print(f"[WHITE]{file_name} [BLUE]left unchanged[RESET_ALL]", parsed_arguments)
@@ -1,6 +0,0 @@
1
- whitespace_format.py,sha256=jjvKeV9aRxjY5-QmbPHvmjG5nDTw28LqJKw4UYHO22w,22072
2
- whitespace_format-0.0.3.dist-info/METADATA,sha256=HfGMc6udhoiDvp4KylcTLMJpZeVG-ZJ_dU3N0dpX65E,10135
3
- whitespace_format-0.0.3.dist-info/LICENSE,sha256=rT6UNfWDYFQc-eo65FioDJRMAyVOndtF95wNCUhkK74,1076
4
- whitespace_format-0.0.3.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
5
- whitespace_format-0.0.3.dist-info/entry_points.txt,sha256=LbXoevzUZAF5MVbI2foNC9xeDjKS_Woz7VbA1ZNF5CY,60
6
- whitespace_format-0.0.3.dist-info/RECORD,,