whitespace-format 0.0.3__tar.gz → 0.0.4__tar.gz

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.4
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,6 @@ 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
18
17
  Project-URL: Repository, https://github.com/DavidPal/whitespace-format
19
18
  Description-Content-Type: text/markdown
20
19
 
@@ -22,7 +21,7 @@ Description-Content-Type: text/markdown
22
21
 
23
22
  [![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
23
 
25
- ![GitHub Action](https://github.com/DavidPal/whitespace-format/actions/workflows/build.yaml/badge.svg)
24
+ [![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
25
 
27
26
  Linter and formatter for source code files and text files.
28
27
 
@@ -32,7 +31,7 @@ MarkDown, LaTeX) before checking them into a version control system.
32
31
 
33
32
  The features include:
34
33
 
35
- * Auto-detection of new line markers (Linux `\n`, Windows `\r\r`, Mac `\r`).
34
+ * Auto-detection of new line markers (Linux `\n`, Windows `\r\n`, Mac `\r`).
36
35
  * Add a new line marker at the end of the file if it is missing.
37
36
  * Make new line markers consistent.
38
37
  * Remove empty lines at the end of the file.
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![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)
4
4
 
5
- ![GitHub Action](https://github.com/DavidPal/whitespace-format/actions/workflows/build.yaml/badge.svg)
5
+ [![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)
6
6
 
7
7
  Linter and formatter for source code files and text files.
8
8
 
@@ -12,7 +12,7 @@ MarkDown, LaTeX) before checking them into a version control system.
12
12
 
13
13
  The features include:
14
14
 
15
- * Auto-detection of new line markers (Linux `\n`, Windows `\r\r`, Mac `\r`).
15
+ * Auto-detection of new line markers (Linux `\n`, Windows `\r\n`, Mac `\r`).
16
16
  * Add a new line marker at the end of the file if it is missing.
17
17
  * Make new line markers consistent.
18
18
  * Remove empty lines at the end of the file.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "whitespace-format"
3
- version = "0.0.3"
3
+ version = "0.0.4"
4
4
  description = "Linter and formatter for source code files and text files"
5
5
  license = "MIT"
6
6
  authors = ["David Pal <davidko.pal@gmail.com>"]
@@ -25,10 +25,10 @@ coverage = "^7.2.4"
25
25
  flake8 = "^5.0.4"
26
26
  flake8-absolute-import = "^1.0.0.1"
27
27
  isort = {extras = ["colors"], version = "^5.11.5"}
28
- mypy = "^1.2.0"
28
+ mypy = "^1.4.1"
29
29
  pydocstyle = {extras = ["toml"], version = "^6.3.0"}
30
- pytest = "^7.3.1"
31
- pylint = "^2.16.3"
30
+ pytest = "^7.4.1"
31
+ pylint = "^2.17.5"
32
32
  pylint-quotes = "^0.2.3"
33
33
 
34
34
  [build-system]
@@ -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.4"
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
 
@@ -96,99 +100,185 @@ def write_file(file_name: str, file_content: str, encoding: str):
96
100
  die(4, f"Cannot write to file '{file_name}': {exception}")
97
101
 
98
102
 
99
- def guess_new_line_marker(file_content: str) -> str:
100
- """Guesses newline character for a file.
103
+ @dataclasses.dataclass
104
+ class Line:
105
+ """Line of a text file.
101
106
 
102
- The guess is based on the counts of "\n", "\r" and "\rn" in the file.
107
+ The line is split into two parts:
108
+ 1) Content
109
+ 2) End of line marker ("\n", or "\r", or "\r\n")
103
110
  """
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
111
 
116
- return new_line_marker_guess
112
+ content: str
113
+ end_of_line_marker: str
117
114
 
115
+ @staticmethod
116
+ def create_from_string(line: str) -> Line:
117
+ """Creates a line from a string.
118
118
 
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]
119
+ The function splits the input into content and end_of_line_marker.
120
+ """
121
+ for end_of_line_marker in ["\r\n", "\n", "\r"]:
122
+ if line.endswith(end_of_line_marker):
123
+ return Line(line[: -len(end_of_line_marker)], end_of_line_marker)
124
+ return Line(line, "")
128
125
 
129
126
 
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
127
+ def split_lines(text: str) -> List[Line]:
128
+ """Splits a string into lines."""
129
+ lines: List[Line] = []
130
+ current_line = ""
131
+ for i, char in enumerate(text):
132
+ current_line += char
133
+ if (char == "\n") or (
134
+ (char == "\r") and ((i >= len(text) - 1) or (not text[i + 1] == "\n"))
135
+ ):
136
+ lines.append(Line.create_from_string(current_line))
137
+ current_line = ""
136
138
 
139
+ if current_line:
140
+ lines.append(Line.create_from_string(current_line))
137
141
 
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")
142
+ return lines
141
143
 
142
144
 
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
145
+ def concatenate_lines(lines: List[Line]) -> str:
146
+ """Concatenates a list of lines into a single string including end-of-line markers."""
147
+ return "".join(line.content + line.end_of_line_marker for line in lines)
148
+
149
+
150
+ def guess_end_of_line_marker(lines: List[Line]) -> str:
151
+ """Guesses the end of line marker.
152
+
153
+ The function returns the most common end-of-line marker.
154
+ Ties are broken in order Linux "\n", Mac "\r", Windows "\r\n".
155
+ If no end-of-line marker is present, default to the Linux "\n" end-of-line marker.
156
+ """
157
+ counts: Dict[str, int] = {"\n": 0, "\r": 0, "\r\n": 0}
158
+ for line in lines:
159
+ if line.end_of_line_marker in counts:
160
+ counts[line.end_of_line_marker] += 1
161
+ max_count = max(counts.values())
162
+ for end_of_line_marker, count in counts.items():
163
+ if count == max_count:
164
+ return end_of_line_marker
165
+ return "\n" # This return statement is never executed.
166
+
167
+
168
+ def remove_trailing_empty_lines(lines: List[Line]) -> List[Line]:
169
+ """Removes trailing empty lines.
170
+
171
+ If there are no lines, empty list is returned.
172
+ If all lines are empty, the first line is kept.
173
+ """
174
+ num_empty_trailing_lines = 0
175
+ while (num_empty_trailing_lines < len(lines) - 1) and (
176
+ not lines[-num_empty_trailing_lines - 1].content
177
+ ):
178
+ num_empty_trailing_lines += 1
179
+ return copy.deepcopy(lines[: len(lines) - num_empty_trailing_lines])
180
+
181
+
182
+ def remove_dummy_lines(lines: List[Line]) -> List[Line]:
183
+ """Remove empty lines that also have empty end-of-line markers."""
184
+ return [line for line in lines if line.content or line.end_of_line_marker]
185
+
186
+
187
+ def remove_trailing_whitespace(lines: List[Line]) -> List[Line]:
188
+ """Removes trailing whitespace from every line."""
189
+ lines = [
190
+ Line(
191
+ re.sub(r"[ \n\r\t\f\v]*$", "", line.content),
192
+ line.end_of_line_marker,
193
+ )
194
+ for line in lines
195
+ ]
196
+ return remove_dummy_lines(lines)
197
+
198
+
199
+ def normalize_end_of_line_markers(lines: List[Line], new_end_of_line_marker: str) -> List[Line]:
200
+ """Replaces end-of-line marker in all lines with a new end-of-line marker.
201
+
202
+ Lines without end-of-line markers (i.e. possibly the last line) are left unchanged.
203
+ """
204
+ return [
205
+ Line(line.content, new_end_of_line_marker) if line.end_of_line_marker else line
206
+ for line in lines
207
+ ]
150
208
 
151
209
 
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
210
+ def remove_all_end_of_line_markers_from_end_of_file(lines: List[Line]) -> List[Line]:
211
+ """Removes all end-of-line markers from the end of the file."""
212
+ lines = remove_trailing_empty_lines(lines)
213
+ if not lines:
214
+ return []
215
+ lines[-1] = Line(lines[-1].content, "")
216
+ return remove_dummy_lines(lines)
155
217
 
156
218
 
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)
219
+ def add_end_of_line_marker_at_end_of_file(
220
+ lines: List[Line], new_end_of_line_marker: str
221
+ ) -> List[Line]:
222
+ """Adds new end-of-line marker to the end of file if it is missing."""
223
+ if not lines:
224
+ return [Line("", new_end_of_line_marker)]
225
+ lines = copy.deepcopy(lines)
226
+ lines[-1] = Line(lines[-1].content, new_end_of_line_marker)
227
+ return lines
162
228
 
163
229
 
164
- def normalize_empty_file(file_content: str, mode: str, new_line_marker: str) -> str:
230
+ def normalize_empty_file(lines: List[Line], mode: str, new_end_of_line_marker: str) -> List[Line]:
165
231
  """Replaces file with an empty file."""
166
232
  if mode == "empty":
167
- return ""
233
+ return []
168
234
  if mode == "one-line":
169
- return new_line_marker
170
- return file_content
235
+ return [Line("", new_end_of_line_marker)]
236
+ return copy.deepcopy(lines)
171
237
 
172
238
 
173
- def is_whitespace_only(file_content: str) -> bool:
239
+ def is_whitespace_only(lines: List[Line]) -> bool:
174
240
  """Determines if file consists only of whitespace."""
175
- return not file_content.strip(" \n\r\t\v\f")
241
+ for line in lines:
242
+ if line.content.strip(" \n\r\t\v\f"):
243
+ return False
244
+ return True
176
245
 
177
246
 
178
- def normalize_non_standard_whitespace(file_content: str, mode: str) -> str:
247
+ def normalize_non_standard_whitespace(lines: List[Line], mode: str) -> List[Line]:
179
248
  """Removes non-standard whitespace characters."""
180
249
  if mode == "ignore":
181
- return file_content
250
+ return copy.deepcopy(lines)
182
251
  if mode == "replace":
183
- return file_content.translate(str.maketrans("\v\f", " ", ""))
184
- return file_content.translate(str.maketrans("", "", "\v\f"))
252
+ return [
253
+ Line(line.content.translate(str.maketrans("\v\f", " ", "")), line.end_of_line_marker)
254
+ for line in lines
255
+ ]
256
+ return [
257
+ Line(line.content.translate(str.maketrans("", "", "\v\f")), line.end_of_line_marker)
258
+ for line in lines
259
+ ]
185
260
 
186
261
 
187
- def replace_tabs_with_spaces(file_content: str, num_spaces: int) -> str:
262
+ def replace_tabs_with_spaces(lines: List[Line], num_spaces: int) -> List[Line]:
188
263
  """Replaces tabs with spaces."""
189
264
  if num_spaces < 0:
190
- return file_content
191
- return file_content.replace("\t", num_spaces * " ")
265
+ return copy.deepcopy(lines)
266
+ return [
267
+ Line(line.content.replace("\t", num_spaces * " "), line.end_of_line_marker)
268
+ for line in lines
269
+ ]
270
+
271
+
272
+ def compute_difference(original_lines: List[Line], new_lines: List[Line]) -> List[int]:
273
+ """Computes the indices of lines that differ."""
274
+ line_numbers = [
275
+ line_number
276
+ for line_number, (original_line, new_line) in enumerate(zip(original_lines, new_lines))
277
+ if not original_line == new_line
278
+ ]
279
+ if len(original_lines) != len(new_lines):
280
+ line_numbers.append(min(len(original_lines), len(new_lines)))
281
+ return line_numbers
192
282
 
193
283
 
194
284
  @dataclasses.dataclass
@@ -199,25 +289,36 @@ class ChangeDescription:
199
289
  change: str
200
290
 
201
291
 
292
+ @dataclasses.dataclass
293
+ class LineChange:
294
+ """Description of a change on a particular line."""
295
+
296
+ check_only: str
297
+ change: str
298
+ line_number: int
299
+
300
+
202
301
  class FileContentTracker:
203
302
  """Tracks changes of the content of a file as it undergoes formatting."""
204
303
 
205
- def __init__(self, file_content: str):
304
+ def __init__(self, lines: List[Line]):
206
305
  """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] = []
306
+ self.initial_lines = lines
307
+ self.lines = copy.deepcopy(lines)
308
+ self.line_changes: List[LineChange] = []
210
309
 
211
- def format(self, change: ChangeDescription, function: Callable[..., str], *args):
310
+ def format(self, change: ChangeDescription, function: Callable[..., List[Line]], *args):
212
311
  """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)
312
+ previous_content = self.lines
313
+ self.lines = function(self.lines, *args)
314
+ if previous_content != self.lines:
315
+ line_numbers = compute_difference(previous_content, self.lines)
316
+ for line_number in line_numbers:
317
+ self.line_changes.append(LineChange(change.check_only, change.change, line_number))
217
318
 
218
319
  def is_changed(self) -> bool:
219
320
  """Determines if the file content has changed."""
220
- return self.file_content != self.initial_content
321
+ return self.lines != self.initial_lines
221
322
 
222
323
 
223
324
  def format_file_content(
@@ -225,12 +326,12 @@ def format_file_content(
225
326
  parsed_arguments: argparse.Namespace,
226
327
  ):
227
328
  """Formats the content of file represented as a string."""
228
- new_line_marker = NEW_LINE_MARKERS.get(
329
+ new_line_marker = END_OF_LINE_MARKERS.get(
229
330
  parsed_arguments.new_line_marker,
230
- guess_new_line_marker(file_content_tracker.initial_content),
331
+ guess_end_of_line_marker(file_content_tracker.initial_lines),
231
332
  )
232
333
 
233
- if is_whitespace_only(file_content_tracker.initial_content):
334
+ if is_whitespace_only(file_content_tracker.initial_lines):
234
335
  changes = {
235
336
  "ignore": ChangeDescription("", ""),
236
337
  "empty": ChangeDescription(
@@ -246,7 +347,7 @@ def format_file_content(
246
347
  ),
247
348
  ),
248
349
  }
249
- if not file_content_tracker.initial_content:
350
+ if not file_content_tracker.initial_lines:
250
351
  file_content_tracker.format(
251
352
  changes[parsed_arguments.normalize_empty_files],
252
353
  normalize_empty_file,
@@ -265,8 +366,8 @@ def format_file_content(
265
366
  if parsed_arguments.remove_trailing_whitespace:
266
367
  file_content_tracker.format(
267
368
  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.",
369
+ check_only="Whitespace at the end of line needs to be removed.",
370
+ change="Whitespace at the end of line was removed.",
270
371
  ),
271
372
  remove_trailing_whitespace,
272
373
  )
@@ -308,7 +409,7 @@ def format_file_content(
308
409
  ),
309
410
  change=f"New line marker(s) were replaced with {repr(new_line_marker)}.",
310
411
  ),
311
- normalize_new_line_markers,
412
+ normalize_end_of_line_markers,
312
413
  new_line_marker,
313
414
  )
314
415
 
@@ -320,7 +421,7 @@ def format_file_content(
320
421
  change=f"New line marker was added to the end of the file, "
321
422
  f"or replaced with {repr(new_line_marker)}.",
322
423
  ),
323
- add_new_line_marker_at_end_of_file,
424
+ add_end_of_line_marker_at_end_of_file,
324
425
  new_line_marker,
325
426
  )
326
427
  elif parsed_arguments.remove_new_line_marker_from_end_of_file:
@@ -329,14 +430,15 @@ def format_file_content(
329
430
  check_only="New line marker(s) need to removed from the end of the file.",
330
431
  change="New line marker(s) were removed from the end of the file.",
331
432
  ),
332
- remove_all_new_line_marker_from_end_of_file,
433
+ remove_all_end_of_line_markers_from_end_of_file,
333
434
  )
334
435
 
335
436
 
336
437
  def reformat_file(file_name: str, parsed_arguments: argparse.Namespace) -> bool:
337
438
  """Reformats a file."""
338
439
  file_content = read_file_content(file_name, parsed_arguments.encoding)
339
- file_content_tracker = FileContentTracker(file_content)
440
+ lines = split_lines(file_content)
441
+ file_content_tracker = FileContentTracker(lines)
340
442
  format_file_content(file_content_tracker, parsed_arguments)
341
443
  is_changed = file_content_tracker.is_changed()
342
444
  if parsed_arguments.verbose:
@@ -348,9 +450,11 @@ def reformat_file(file_name: str, parsed_arguments: argparse.Namespace) -> bool:
348
450
  f"[RED]needs to be formatted[RESET_ALL]",
349
451
  parsed_arguments,
350
452
  )
351
- for change in file_content_tracker.changes:
453
+ for line_change in file_content_tracker.line_changes:
352
454
  color_print(
353
- f" [BOLD][BLUE]↳ [WHITE]{change.check_only}[RESET_ALL]", parsed_arguments
455
+ f" [BOLD][BLUE]↳ line {line_change.line_number + 1}: "
456
+ f"[WHITE]{line_change.check_only}[RESET_ALL]",
457
+ parsed_arguments,
354
458
  )
355
459
  else:
356
460
  if parsed_arguments.verbose:
@@ -362,9 +466,15 @@ def reformat_file(file_name: str, parsed_arguments: argparse.Namespace) -> bool:
362
466
  else:
363
467
  if is_changed:
364
468
  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)
469
+ for line_change in file_content_tracker.line_changes:
470
+ color_print(
471
+ f" [BOLD][BLUE]↳ line {line_change.line_number + 1}: "
472
+ f"[WHITE]{line_change.change}[RESET_ALL]",
473
+ parsed_arguments,
474
+ )
475
+ write_file(
476
+ file_name, concatenate_lines(file_content_tracker.lines), parsed_arguments.encoding
477
+ )
368
478
  else:
369
479
  if parsed_arguments.verbose:
370
480
  color_print(f"[WHITE]{file_name} [BLUE]left unchanged[RESET_ALL]", parsed_arguments)
@@ -1,25 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- modules = \
5
- ['whitespace_format']
6
- entry_points = \
7
- {'console_scripts': ['whitespace-format = whitespace_format:main']}
8
-
9
- setup_kwargs = {
10
- 'name': 'whitespace-format',
11
- 'version': '0.0.3',
12
- 'description': 'Linter and formatter for source code files and text files',
13
- 'long_description': '# whitespace-format\n\n[![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)\n\n![GitHub Action](https://github.com/DavidPal/whitespace-format/actions/workflows/build.yaml/badge.svg)\n\nLinter and formatter for source code files and text files.\n\nThe purpose of this tool is to normalize source code files (e.g. Python, Java,\nC/C++, Ruby, Go, JavaScript, etc.) and text files (HTML, JSON, YAML, CSV,\nMarkDown, LaTeX) before checking them into a version control system.\n\nThe features include:\n\n* Auto-detection of new line markers (Linux `\\n`, Windows `\\r\\r`, Mac `\\r`).\n* Add a new line marker at the end of the file if it is missing.\n* Make new line markers consistent.\n* Remove empty lines at the end of the file.\n* Remove whitespace at the end of each line.\n* Replace tabs with spaces.\n* Remove/replace non-standard whitespace characters.\n\nThe formatting changes are\n[idempotent](https://en.wikipedia.org/wiki/Idempotence), i.e., running the tool\nsecond time (with the same parameters) has no effect.\n\n## Installation\n\n```shell\npip install whitespace-format\n```\n\nInstallation requires Python 3.7.5 or higher.\n\n## Usage\n\nA sample command that formats source code files:\n```shell\nwhitespace-format \\\n --exclude ".git/|.idea/|.pyc$" \\\n --new-line-marker linux \\\n --normalize-new-line-markers \\\n foo.txt my_project/\n```\nThe command above formats `foo.txt` and all files contained `my_project/` and\nits subdirectories. Files that contain `.git/` or `.idea/` in their (relative)\npath are excluded. For example, files in `my_project/.git/` and files in\n`my_project/.idea/` are excluded. Likewise, files ending with `*.pyc` are\nexcluded.\n\nIf you want only know if any changes **would be** made, add `--check-only` option:\n```shell\nwhitespace-format \\\n --exclude ".git/|.idea/|.pyc$" \\\n --check-only \\\n --new-line-marker linux \\\n --normalize-new-line-markers \\\n foo.txt my_project/\n```\nThis command can be used as a validation step before checking the source files\ninto a version control system. The command outputs non-zero exit code if any\nof the files would be formatted.\n\n### Options\n\n* `--check-only` -- Do not format files. Only report which files would be formatted.\n* `--follow-symlinks` -- Follow symbolic links when searching for files.\n* `--exclude=REGEX` -- Regular expression that specifies which files to exclude.\nThe regular expression is evaluated on the path of each file.\n* `--verbose` -- Print more messages than normally.\n* `--quiet` -- Do not print any messages, except for errors when reading or writing files.\n\n### Formatting options\n\n* `--add-new-line-marker-at-end-of-file` -- Add missing new line marker at end of each file.\n* `--remove-new-line-marker-from-end-of-file` -- Remove all new line marker(s) from the end of each file.\nThis option is ignored when `--add-new-line-marker-at-end-of-file` is used.\nEmpty lines at the end of the file are removed.\n* `--normalize-new-line-markers` -- Make new line markers consistent in each file\nby replacing `\\\\r\\\\n`, `\\\\n`, and `\\r` with a consistent new line marker.\n* `--remove-trailing-whitespace` -- Remove whitespace at the end of each line.\n* `--remove-trailing-empty-lines` -- Remove empty lines at the end of each file.\n* `--new-line-marker=MARKER` -- This option specifies what new line marker to use.\n`MARKER` must be one of the following:\n * `auto` -- Use new line marker that is the most common in each individual file.\n If no new line marker is present in the file, Linux `\\n` is used.\n This is the default option.\n * `linux` -- Use Linux new line marker `\\\\n`.\n * `mac` -- Use Mac new line marker `\\\\r`.\n * `windows` -- Use Windows new line marker `\\\\r\\\\n`.\n* `--encoding` -- Text encoding for both reading and writing files. Default encoding is `utf-8`.\nList of supported encodings can be found at\nhttps://docs.python.org/3/library/codecs.html#standard-encodings\n\nNote that input files can contain an arbitrary mix of new line markers `\\n`,\n`\\r`, `\\r\\n` even within the same file. The option `--new-line-marker`\nspecifies the character that should be in the formatted file.\n\nAn opinionated combination of options is:\n```shell\nwhitespace-format \\\n --new-line-marker=linux \\\n --add-new-line-marker-at-end-of-file \\\n --normalize-new-line-markers \\\n --remove-trailing-whitespace \\\n --remove-trailing-empty-lines \\\n foo.txt my_project/\n```\nThis should work well for common programming languages (e.g. Python, Java,\nC/C++, JavaScript) and common text file formats (e.g. CSV, LaTeX, JSON, YAML,\nHTML, MarkDown).\n\n### Empty files\n\nThere are separate options for handling empty files and files consisting of\nwhitespace characters only:\n\n* `--normalize-empty-files=MODE`\n* `--normalize-whitespace-only-files=MODE`\n\nwhere `MODE` is one of the following:\n\n* `ignore` -- Leave the file as is. This is the default option.\n* `empty` -- Replace the file with an empty file.\n* `one-line` -- Replace each file with a file consisting of a single new line marker.\n\nDepending on the mode, an empty file or a whitespace-only file will be either\nignored, replaced by a zero-byte file, or replaced by a file consisting of\nsingle end of line marker.\n\nIf `--normalize-whitespace-only-files` is set to `empty`,\n`--normalize-empty-files setting` set to `empty` as well. In other words,\ncombination `--normalize-whitespace-only-files=empty` and\n`--normalize-empty-files=one-line` is not allowed, since it would lead to\nbehavior that is not idempotent.\n\n### Special characters\n\n* `--replace-tabs-with-spaces=N` -- Replace tabs with spaces.\nWhere is `N` is the number of spaces. If `N` is negative, tabs are not replaced.\nDefault value is `-1`.\n\n* `--normalize-non-standard-whitespace=MODE` -- Replace or remove\nnon-standard whitespace characters (`\\v` and `\\f`). `MODE` must be one of the following:\n * `ignore` -- Leave `\\v` and `f` as is. This is the default option.\n * `replace` -- Replace any occurrence of `\\v` or `\\f` with a single space.\n * `remove` -- Remove all occurrences of `\\v` and `\\f`\n\n## License\n\nMIT\n\n## MacOS development setup\n\n1) Make sure you have [brew](https://brew.sh/) package manager installed.\n\n2) Install [pyenv](https://github.com/pyenv/pyenv), [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv)\n and [poetry](https://python-poetry.org/):\n ```shell\n brew install pyenv\n brew install pyenv-virtualenv\n brew install poetry\n ```\n\n3) Create Python virtual environment with the correct Python version:\n ```shell\n make install-python\n make create-environment\n ```\n\n4) Add the following lines to `.zshrc` or `.bash_profile` and restart the terminal:\n ```shell\n # Pyenv settings\n export PYENV_ROOT="$HOME/.pyenv"\n export PATH="$PYENV_ROOT/bin:$PATH"\n eval "$(pyenv init --path)"\n eval "$(pyenv virtualenv-init -)"\n ```\n\n5) Install all dependencies\n ```shell\n make install-dependecies\n ```\n\nIf you need to delete the Python virtual environment, you can do so with the\ncommand `make delete-environment`.\n\n## Running unit tests and code checks\n\nIf you make code change, run unit tests and code checks with the command:\n```shell\nmake clean whitespace-format-check isort-check black-check flake8 pydocstyle pylint mypy test coverage\n```\n\nEach make target runs different checks:\n- `clean` deletes temporary files\n- `whitespace-format-check` runs [whitespace-format](https://github.com/DavidPal/whitespace-format) checker on all files\n- `isort-check` runs [isort](https://pycqa.github.io/isort/) checker of imports in `*.py` files\n- `black-check` runs [black](https://github.com/psf/black/) code format checker on `*.py` files\n- `flake8` runs [flake8](https://flake8.pycqa.org/) code style checker on `*.py` files\n- `pydocstyle` runs [pydocstyle](http://www.pydocstyle.org/) docstring checker on `*.py` files\n- `pylint` runs [pylint](https://pylint.org/) code checker on `*.py` files\n- `mypy` runs [mypy](http://mypy-lang.org/) type checker on `*.py` files\n- `test` runs unit tests\n- `coverage` generates code coverage report\n\nYou can automatically format code with the command:\n```shell\nmake isort-format black-format whitespace-format\n```\n\n## Modifying dependencies\n\nThe list of Python packages that this project depends on is specified in\n`pyproject.toml` and in `poetry.lock` files. The file `pyproject.toml` can be\nedited by humans. The file `poetry.lock` is automatically generated by\n`poetry`.\n\nInstall a development dependency with the command:\n```shell\npoetry add --dev <some_new_python_tool>\n```\n\nInstall a new production dependency with the command:\n```shell\npoetry add <some_python_library>\n```\n\n### Manual modification of `pyproject.toml`\n\nInstead of using `poetry add` command, you can edit `pyproject.toml` file. Then,\nregenerate `poetry.lock` file with the command:\n```shell\npoetry lock\n```\nor the command:\n```shell\npoetry lock --no-update\n```\nThe latter command does not update already locked packages.\n\n### Fixing broken Python environment\n\nIf your Python virtual environment becomes broken or polluted with unnecessary\npackages, delete it, recreate it from scratch and install dependencies a fresh\nwith the following commands:\n```shell\nmake delete-environment\nmake create-environment\nmake install-dependencies\n```\n',
14
- 'author': 'David Pal',
15
- 'author_email': 'davidko.pal@gmail.com',
16
- 'maintainer': 'None',
17
- 'maintainer_email': 'None',
18
- 'url': 'https://github.com/DavidPal/whitespace-format',
19
- 'py_modules': modules,
20
- 'entry_points': entry_points,
21
- 'python_requires': '>=3.7.2,<4.0.0',
22
- }
23
-
24
-
25
- setup(**setup_kwargs)