Skip to content

Commit 69fabeb

Browse files
committed
bug fixes
1 parent 7bf994d commit 69fabeb

4 files changed

Lines changed: 77 additions & 15 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ options:
4040
default CS50 config)
4141
```
4242

43+
`STYLE50_IGNORE` is also supported as a comma-separated environment variable fallback for ignore patterns.
44+
4345
`character`, `split`, and `unified` modes output character-based, side-by-side, and unified (respectively) diffs between the inputted file and the correctly styled version. `score` outputs the raw percentage of correct (unchanged) lines, `json` outputs a JSON object containing structured results, `html` outputs browser-readable results, and `format` outputs only the formatted code.
4446

4547
### Common examples

‎style50/__main__.py‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def main():
3131
# Define command-line arguments.
3232
parser = argparse.ArgumentParser(prog="style50")
3333
parser.add_argument("file", metavar="FILE", nargs="+", help="file or directory to lint")
34-
parser.add_argument("-o", "--output", action="store", default="character",
34+
parser.add_argument("-o", "--output", action="store", default=None,
3535
choices=["character", "split", "unified", "score", "json", "html", "format"], metavar="MODE",
3636
help="output mode, which can be character (default), split, unified, score, json, html, or format")
3737
parser.add_argument("-y", "--side-by-side", action="store_true",
@@ -56,22 +56,27 @@ def main():
5656
if args.in_place and args.side_by_side:
5757
sys.exit("--in-place cannot be combined with --side-by-side")
5858

59+
if args.in_place and args.output is not None:
60+
sys.exit("--in-place cannot be combined with --output")
61+
62+
output = args.output or "character"
63+
5964
if args.side_by_side:
60-
args.output = "split"
65+
output = "split"
6166

6267
if args.in_place:
63-
if args.output != "character":
64-
sys.exit("--in-place cannot be combined with --output")
65-
Style50("format", clang_format_style=args.clang_format_style).format_files_in_place(args.file, ignore=ignore)
68+
_, failures = Style50("format", clang_format_style=args.clang_format_style).format_files_in_place(args.file, ignore=ignore)
69+
if failures:
70+
sys.exit(1)
6671
return
6772

68-
if args.output == "format":
73+
if output == "format":
6974
if len(args.file) != 1:
7075
sys.exit("format mode requires exactly one file")
7176
sys.stdout.write(Style50("format", clang_format_style=args.clang_format_style).format_file(args.file[0]))
7277
return
7378

74-
Style50(args.output, clang_format_style=args.clang_format_style).run(args.file, ignore=ignore)
79+
Style50(output, clang_format_style=args.clang_format_style).run(args.file, ignore=ignore)
7580

7681

7782

‎style50/_api.py‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __init__(self, output="character", clang_format_style=None):
3838
self._warn_chars = set()
3939
self.clang_format_style = clang_format_style
4040

41-
# Set run function as apropriate for output mode.
41+
# Set run function as appropriate for output mode.
4242
if output in ["score", "format"]:
4343
self.diff = lambda old, new: ""
4444
elif output in ["json", "html"]:
@@ -78,16 +78,21 @@ def _files_for_paths(self, paths, ignore=None):
7878

7979
def format_files_in_place(self, paths, ignore=None):
8080
"""Format files and rewrite each path in place."""
81+
formatted_count = 0
82+
failed_count = 0
8183
for path in self._files_for_paths(paths, ignore=ignore):
8284
try:
8385
results = self._check(path)
8486
except Error as e:
8587
termcolor.cprint(f"{path}: {e.msg}", "red", file=sys.stderr)
88+
failed_count += 1
8689
continue
8790

8891
if results.original != results.styled:
89-
with open(path, "w") as file:
92+
with open(path, "w", encoding="utf-8", newline="") as file:
9093
file.write(results.styled)
94+
formatted_count += 1
95+
return formatted_count, failed_count
9196

9297
def run(self, *args, **kwargs):
9398
"""Wraps Style50.check and renders the results using the renderer determined by self.output"""
@@ -114,9 +119,9 @@ def run(self, *args, **kwargs):
114119
print(render(**results))
115120

116121

117-
def check(self, paths, ignore=[]):
122+
def check(self, paths, ignore=None):
118123
"""
119-
Run checks on paths recursively, ignoring pataterns in ignore, returning a dict of results
124+
Run checks on paths recursively, ignoring patterns in ignore, returning a dict of results
120125
"""
121126
files = self._files_for_paths(paths, ignore=ignore)
122127

@@ -158,7 +163,7 @@ def check(self, paths, ignore=[]):
158163

159164
def _check(self, file):
160165
"""
161-
Run apropriate check based on `file`'s extension and return it,
166+
Run appropriate check based on `file`'s extension and return it,
162167
otherwise raise an Error
163168
"""
164169

@@ -178,7 +183,7 @@ def _check(self, file):
178183
raise Error("unknown file type \"{}\", skipping...".format(file))
179184

180185
try:
181-
with open(file) as f:
186+
with open(file, encoding="utf-8", newline="") as f:
182187
code = "\n".join(line.rstrip() for line in f)
183188
except UnicodeDecodeError:
184189
raise Error("file does not seem to contain text, skipping...")
@@ -322,8 +327,12 @@ class StyleCheck(metaclass=StyleMeta):
322327

323328
# Contains substrings to be matched against libmagic's output if file extension not recognized
324329
magic_names = []
330+
CONFIG_KEYS = {"clang_format_style"}
325331

326332
def __init__(self, code, **kwargs):
333+
unknown = set(kwargs) - self.CONFIG_KEYS
334+
if unknown:
335+
raise Error("unknown configuration option(s): {}".format(", ".join(sorted(unknown))))
327336
self._config = kwargs
328337
self.original = code
329338

@@ -379,7 +388,7 @@ def run(command, input=None, exit=0, shell=False):
379388

380389
def count_comments(self, code):
381390
"""
382-
Returns number of coments in `code`. If not implemented by child, will not warn about comments.
391+
Returns number of comments in `code`. If not implemented by child, will not warn about comments.
383392
"""
384393

385394
@abstractmethod

‎tests/test_cli.py‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import os
12
import subprocess
23
import sys
34

5+
import pytest
6+
47

58
def run_style50(args, *, cwd=None, env=None):
69
merged_env = None
710
if env is not None:
811
merged_env = dict(**env)
9-
import os
1012
baseline = os.environ.copy()
1113
baseline.update(merged_env)
1214
merged_env = baseline
@@ -131,6 +133,15 @@ def test_in_place_and_output_conflict(tmp_path):
131133
assert "--in-place cannot be combined with --output" in (proc.stdout + proc.stderr)
132134

133135

136+
def test_in_place_and_output_character_conflict(tmp_path):
137+
f = tmp_path / "t.py"
138+
f.write_text("x=1\nprint( x)\n", encoding="utf-8")
139+
140+
proc = run_style50(["-i", "-o", "character", str(f)])
141+
assert proc.returncode != 0
142+
assert "--in-place cannot be combined with --output" in (proc.stdout + proc.stderr)
143+
144+
134145
def test_in_place_directory_input_and_ignore(tmp_path):
135146
src = tmp_path / "src"
136147
src.mkdir()
@@ -158,3 +169,38 @@ def test_in_place_respects_style50_ignore_env(tmp_path):
158169
assert "print(x)" in keep.read_text(encoding="utf-8")
159170
assert "print( y)" in skip.read_text(encoding="utf-8")
160171

172+
173+
def test_in_place_returns_nonzero_when_any_file_fails(tmp_path):
174+
existing = tmp_path / "good.py"
175+
missing = tmp_path / "missing.py"
176+
existing.write_text("x=1\nprint( x)\n", encoding="utf-8")
177+
178+
proc = run_style50(["-i", str(existing), str(missing)])
179+
assert proc.returncode != 0
180+
# Existing file should still be processed.
181+
assert "print(x)" in existing.read_text(encoding="utf-8")
182+
183+
184+
def test_format_mode_html_css_sql(tmp_path):
185+
html = tmp_path / "t.html"
186+
css = tmp_path / "t.css"
187+
sql = tmp_path / "t.sql"
188+
html.write_text("<html><body><h1>x</h1></body></html>\n", encoding="utf-8")
189+
css.write_text("body{color:red;}\n", encoding="utf-8")
190+
sql.write_text("select * from users where id=1;\n", encoding="utf-8")
191+
192+
proc_html = run_style50(["-o", "format", str(html)])
193+
# Skip only if djhtml is unavailable in this environment.
194+
if proc_html.returncode != 0 and "requires djhtml" in (proc_html.stdout + proc_html.stderr):
195+
pytest.skip("djhtml not installed in test environment")
196+
assert proc_html.returncode == 0
197+
assert proc_html.stdout.strip() != ""
198+
199+
proc_css = run_style50(["-o", "format", str(css)])
200+
assert proc_css.returncode == 0
201+
assert proc_css.stdout.strip() != ""
202+
203+
proc_sql = run_style50(["-o", "format", str(sql)])
204+
assert proc_sql.returncode == 0
205+
assert "SELECT" in proc_sql.stdout
206+

0 commit comments

Comments
 (0)